feat: container stats

This commit is contained in:
Pujit Mehrotra
2025-11-20 09:53:01 -05:00
parent 9960c78036
commit 55b8deeeba
13 changed files with 398 additions and 28 deletions

View File

@@ -1213,6 +1213,25 @@ type DockerContainerLogs {
cursor: DateTime
}
type DockerContainerStats {
id: PrefixedID!
"""CPU Usage Percentage"""
cpuPercent: Float!
"""Memory Usage String (e.g. 100MB / 1GB)"""
memUsage: String!
"""Memory Usage Percentage"""
memPercent: Float!
"""Network I/O String (e.g. 100MB / 1GB)"""
netIO: String!
"""Block I/O String (e.g. 100MB / 1GB)"""
blockIO: String!
}
type Docker implements Node {
id: PrefixedID!
containers(skipCache: Boolean! = false): [DockerContainer!]!
@@ -2772,6 +2791,7 @@ type Subscription {
serversSubscription: Server!
parityHistorySubscription: ParityCheck!
arraySubscription: UnraidArray!
dockerContainerStats: DockerContainerStats!
logFile(path: String!): LogFileContent!
systemMetricsCpu: CpuUtilization!
systemMetricsCpuTelemetry: CpuPackages!

View File

@@ -0,0 +1,117 @@
import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
import { createInterface } from 'readline';
import { execa } from 'execa';
import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
import { catchHandlers } from '@app/core/utils/misc/catch-handlers.js';
import { DockerContainerStats } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
@Injectable()
export class DockerStatsService implements OnModuleDestroy {
private readonly logger = new Logger(DockerStatsService.name);
private statsProcess: ReturnType<typeof execa> | null = null;
private readonly STATS_FORMAT =
'{{.ID}};{{.CPUPerc}};{{.MemUsage}};{{.MemPerc}};{{.NetIO}};{{.BlockIO}}';
onModuleDestroy() {
this.stopStatsStream();
}
public startStatsStream() {
if (this.statsProcess) {
return;
}
this.logger.log('Starting docker stats stream');
try {
this.statsProcess = execa('docker', ['stats', '--format', this.STATS_FORMAT, '--no-trunc'], {
all: true,
reject: false, // Don't throw on exit code != 0, handle via parsing/events
});
if (this.statsProcess.stdout) {
const rl = createInterface({
input: this.statsProcess.stdout,
crlfDelay: Infinity,
});
rl.on('line', (line) => {
if (!line.trim()) return;
this.processStatsLine(line);
});
rl.on('error', (err) => {
this.logger.error('Error reading docker stats stream', err);
});
}
if (this.statsProcess.stderr) {
this.statsProcess.stderr.on('data', (data: Buffer) => {
// Log docker stats errors but don't crash
this.logger.debug(`Docker stats stderr: ${data.toString()}`);
});
}
// Handle process exit
this.statsProcess
.then((result) => {
if (result.failed && !result.signal) {
this.logger.error('Docker stats process exited with error', result.shortMessage);
this.stopStatsStream();
}
})
.catch((err) => {
if (!err.killed) {
this.logger.error('Docker stats process ended unexpectedly', err);
this.stopStatsStream();
}
});
} catch (error) {
this.logger.error('Failed to start docker stats', error);
catchHandlers.docker(error as Error);
}
}
public stopStatsStream() {
if (this.statsProcess) {
this.logger.log('Stopping docker stats stream');
this.statsProcess.kill();
this.statsProcess = null;
}
}
private processStatsLine(line: string) {
try {
// format: ID;CPUPerc;MemUsage;MemPerc;NetIO;BlockIO
// Example: 123abcde;0.00%;10MiB / 100MiB;10.00%;1kB / 2kB;0B / 0B
// Remove ANSI escape codes if any (docker stats sometimes includes them)
// eslint-disable-next-line no-control-regex
const cleanLine = line.replace(/\x1B\[[0-9;]*[mK]/g, '');
const parts = cleanLine.split(';');
if (parts.length < 6) return;
const [id, cpuPercStr, memUsage, memPercStr, netIO, blockIO] = parts;
const stats: DockerContainerStats = {
id,
cpuPercent: this.parsePercentage(cpuPercStr),
memUsage,
memPercent: this.parsePercentage(memPercStr),
netIO,
blockIO,
};
pubsub.publish(PUBSUB_CHANNEL.DOCKER_STATS, { dockerContainerStats: stats });
} catch (error) {
this.logger.debug(`Failed to process stats line: ${line}`, error);
}
}
private parsePercentage(value: string): number {
return parseFloat(value.replace('%', '')) || 0;
}
}

View File

@@ -1,5 +1,6 @@
import {
Field,
Float,
GraphQLISODateTime,
ID,
InputType,
@@ -272,6 +273,27 @@ export class DockerContainerLogs {
cursor?: Date | null;
}
@ObjectType()
export class DockerContainerStats {
@Field(() => PrefixedID)
id!: string;
@Field(() => Float, { description: 'CPU Usage Percentage' })
cpuPercent!: number;
@Field(() => String, { description: 'Memory Usage String (e.g. 100MB / 1GB)' })
memUsage!: string;
@Field(() => Float, { description: 'Memory Usage Percentage' })
memPercent!: number;
@Field(() => String, { description: 'Network I/O String (e.g. 100MB / 1GB)' })
netIO!: string;
@Field(() => String, { description: 'Block I/O String (e.g. 100MB / 1GB)' })
blockIO!: string;
}
@ObjectType({
implements: () => Node,
})

View File

@@ -6,6 +6,7 @@ import { DockerConfigService } from '@app/unraid-api/graph/resolvers/docker/dock
import { DockerEventService } from '@app/unraid-api/graph/resolvers/docker/docker-event.service.js';
import { DockerFormService } from '@app/unraid-api/graph/resolvers/docker/docker-form.service.js';
import { DockerPhpService } from '@app/unraid-api/graph/resolvers/docker/docker-php.service.js';
import { DockerStatsService } from '@app/unraid-api/graph/resolvers/docker/docker-stats.service.js';
import { DockerTemplateScannerService } from '@app/unraid-api/graph/resolvers/docker/docker-template-scanner.service.js';
import { DockerModule } from '@app/unraid-api/graph/resolvers/docker/docker.module.js';
import { DockerMutationsResolver } from '@app/unraid-api/graph/resolvers/docker/docker.mutations.resolver.js';
@@ -13,6 +14,8 @@ import { DockerResolver } from '@app/unraid-api/graph/resolvers/docker/docker.re
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
import { DockerOrganizerConfigService } from '@app/unraid-api/graph/resolvers/docker/organizer/docker-organizer-config.service.js';
import { DockerOrganizerService } from '@app/unraid-api/graph/resolvers/docker/organizer/docker-organizer.service.js';
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
describe('DockerModule', () => {
it('should compile the module', async () => {
@@ -25,6 +28,16 @@ describe('DockerModule', () => {
.useValue({ getConfig: vi.fn() })
.overrideProvider(DockerConfigService)
.useValue({ getConfig: vi.fn() })
.overrideProvider(SubscriptionTrackerService)
.useValue({
registerTopic: vi.fn(),
subscribe: vi.fn(),
unsubscribe: vi.fn(),
})
.overrideProvider(SubscriptionHelperService)
.useValue({
createTrackedSubscription: vi.fn(),
})
.compile();
expect(module).toBeDefined();
@@ -75,6 +88,25 @@ describe('DockerModule', () => {
syncMissingContainers: vi.fn(),
},
},
{
provide: DockerStatsService,
useValue: {
startStatsStream: vi.fn(),
stopStatsStream: vi.fn(),
},
},
{
provide: SubscriptionTrackerService,
useValue: {
registerTopic: vi.fn(),
},
},
{
provide: SubscriptionHelperService,
useValue: {
createTrackedSubscription: vi.fn(),
},
},
],
}).compile();

View File

@@ -7,6 +7,7 @@ import { DockerContainerResolver } from '@app/unraid-api/graph/resolvers/docker/
import { DockerFormService } from '@app/unraid-api/graph/resolvers/docker/docker-form.service.js';
import { DockerManifestService } from '@app/unraid-api/graph/resolvers/docker/docker-manifest.service.js';
import { DockerPhpService } from '@app/unraid-api/graph/resolvers/docker/docker-php.service.js';
import { DockerStatsService } from '@app/unraid-api/graph/resolvers/docker/docker-stats.service.js';
import { DockerTemplateIconService } from '@app/unraid-api/graph/resolvers/docker/docker-template-icon.service.js';
import { DockerTemplateScannerService } from '@app/unraid-api/graph/resolvers/docker/docker-template-scanner.service.js';
import { DockerMutationsResolver } from '@app/unraid-api/graph/resolvers/docker/docker.mutations.resolver.js';
@@ -15,9 +16,10 @@ import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.ser
import { DockerOrganizerConfigService } from '@app/unraid-api/graph/resolvers/docker/organizer/docker-organizer-config.service.js';
import { DockerOrganizerService } from '@app/unraid-api/graph/resolvers/docker/organizer/docker-organizer.service.js';
import { NotificationsModule } from '@app/unraid-api/graph/resolvers/notifications/notifications.module.js';
import { ServicesModule } from '@app/unraid-api/graph/services/services.module.js';
@Module({
imports: [JobModule, NotificationsModule],
imports: [JobModule, NotificationsModule, ServicesModule],
providers: [
// Services
DockerService,
@@ -29,6 +31,7 @@ import { NotificationsModule } from '@app/unraid-api/graph/resolvers/notificatio
DockerConfigService,
DockerTemplateScannerService,
DockerTemplateIconService,
DockerStatsService,
// Jobs
ContainerStatusJob,

View File

@@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { DockerFormService } from '@app/unraid-api/graph/resolvers/docker/docker-form.service.js';
import { DockerPhpService } from '@app/unraid-api/graph/resolvers/docker/docker-php.service.js';
import { DockerStatsService } from '@app/unraid-api/graph/resolvers/docker/docker-stats.service.js';
import { DockerTemplateScannerService } from '@app/unraid-api/graph/resolvers/docker/docker-template-scanner.service.js';
import {
ContainerState,
@@ -14,6 +15,8 @@ import {
import { DockerResolver } from '@app/unraid-api/graph/resolvers/docker/docker.resolver.js';
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
import { DockerOrganizerService } from '@app/unraid-api/graph/resolvers/docker/organizer/docker-organizer.service.js';
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
import { GraphQLFieldHelper } from '@app/unraid-api/utils/graphql-field-helper.js';
vi.mock('@app/unraid-api/utils/graphql-field-helper.js', () => ({
@@ -69,6 +72,27 @@ describe('DockerResolver', () => {
syncMissingContainers: vi.fn().mockResolvedValue(false),
},
},
{
provide: DockerStatsService,
useValue: {
startStatsStream: vi.fn(),
stopStatsStream: vi.fn(),
},
},
{
provide: SubscriptionTrackerService,
useValue: {
registerTopic: vi.fn(),
subscribe: vi.fn(),
unsubscribe: vi.fn(),
},
},
{
provide: SubscriptionHelperService,
useValue: {
createTrackedSubscription: vi.fn(),
},
},
],
}).compile();

View File

@@ -7,6 +7,7 @@ import {
Query,
ResolveField,
Resolver,
Subscription,
} from '@nestjs/graphql';
import type { GraphQLResolveInfo } from 'graphql';
@@ -15,9 +16,11 @@ import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
import { UsePermissions } from '@unraid/shared/use-permissions.directive.js';
import { GraphQLJSON } from 'graphql-scalars';
import { PUBSUB_CHANNEL } from '@app/core/pubsub.js';
import { UseFeatureFlag } from '@app/unraid-api/decorators/use-feature-flag.decorator.js';
import { DockerFormService } from '@app/unraid-api/graph/resolvers/docker/docker-form.service.js';
import { DockerPhpService } from '@app/unraid-api/graph/resolvers/docker/docker-php.service.js';
import { DockerStatsService } from '@app/unraid-api/graph/resolvers/docker/docker-stats.service.js';
import { DockerTemplateSyncResult } from '@app/unraid-api/graph/resolvers/docker/docker-template-scanner.model.js';
import { DockerTemplateScannerService } from '@app/unraid-api/graph/resolvers/docker/docker-template-scanner.service.js';
import { ExplicitStatusItem } from '@app/unraid-api/graph/resolvers/docker/docker-update-status.model.js';
@@ -26,11 +29,14 @@ import {
DockerContainer,
DockerContainerLogs,
DockerContainerOverviewForm,
DockerContainerStats,
DockerNetwork,
DockerPortConflicts,
} from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
import { DockerOrganizerService } from '@app/unraid-api/graph/resolvers/docker/organizer/docker-organizer.service.js';
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
import { DEFAULT_ORGANIZER_ROOT_ID } from '@app/unraid-api/organizer/organizer.js';
import { ResolvedOrganizerV1 } from '@app/unraid-api/organizer/organizer.model.js';
import { GraphQLFieldHelper } from '@app/unraid-api/utils/graphql-field-helper.js';
@@ -42,8 +48,17 @@ export class DockerResolver {
private readonly dockerFormService: DockerFormService,
private readonly dockerOrganizerService: DockerOrganizerService,
private readonly dockerPhpService: DockerPhpService,
private readonly dockerTemplateScannerService: DockerTemplateScannerService
) {}
private readonly dockerTemplateScannerService: DockerTemplateScannerService,
private readonly dockerStatsService: DockerStatsService,
private readonly subscriptionTracker: SubscriptionTrackerService,
private readonly subscriptionHelper: SubscriptionHelperService
) {
this.subscriptionTracker.registerTopic(
PUBSUB_CHANNEL.DOCKER_STATS,
() => this.dockerStatsService.startStatsStream(),
() => this.dockerStatsService.stopStatsStream()
);
}
@UsePermissions({
action: AuthAction.READ_ANY,
@@ -312,4 +327,15 @@ export class DockerResolver {
public async syncDockerTemplatePaths() {
return this.dockerTemplateScannerService.scanTemplates();
}
@UsePermissions({
action: AuthAction.READ_ANY,
resource: Resource.DOCKER,
})
@Subscription(() => DockerContainerStats, {
resolve: (payload) => payload.dockerContainerStats,
})
public dockerContainerStats() {
return this.subscriptionHelper.createTrackedSubscription(PUBSUB_CHANNEL.DOCKER_STATS);
}
}

View File

@@ -17,6 +17,7 @@ export enum GRAPHQL_PUBSUB_CHANNEL {
OWNER = "OWNER",
SERVERS = "SERVERS",
VMS = "VMS",
DOCKER_STATS = "DOCKER_STATS",
LOG_FILE = "LOG_FILE",
PARITY = "PARITY",
}

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import { computed } from 'vue';
import type { DockerContainerStats } from '@/composables/gql/graphql';
interface Props {
containerId?: string | null;
statsMap: Map<string, DockerContainerStats>;
type: 'cpu' | 'memory';
}
const props = defineProps<Props>();
const value = computed(() => {
if (!props.containerId) return '—';
const stats = props.statsMap.get(props.containerId);
if (!stats) return '—';
if (props.type === 'cpu') {
return `${stats.cpuPercent.toFixed(2)}%`;
}
return stats.memUsage;
});
</script>
<template>
<span class="tabular-nums">{{ value }}</span>
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, h, nextTick, onBeforeUnmount, reactive, ref, resolveComponent, watch } from 'vue';
import { useApolloClient, useMutation } from '@vue/apollo-composable';
import { useApolloClient, useMutation, useSubscription } from '@vue/apollo-composable';
import BaseTreeTable from '@/components/Common/BaseTreeTable.vue';
import MultiValueCopyBadges from '@/components/Common/MultiValueCopyBadges.vue';
@@ -15,10 +15,12 @@ import { PAUSE_DOCKER_CONTAINER } from '@/components/Docker/docker-pause-contain
import { REFRESH_DOCKER_DIGESTS } from '@/components/Docker/docker-refresh-digests.mutation';
import { SET_DOCKER_FOLDER_CHILDREN } from '@/components/Docker/docker-set-folder-children.mutation';
import { START_DOCKER_CONTAINER } from '@/components/Docker/docker-start-container.mutation';
import { DOCKER_STATS_SUBSCRIPTION } from '@/components/Docker/docker-stats.subscription';
import { STOP_DOCKER_CONTAINER } from '@/components/Docker/docker-stop-container.mutation';
import { UNPAUSE_DOCKER_CONTAINER } from '@/components/Docker/docker-unpause-container.mutation';
import { UPDATE_ALL_DOCKER_CONTAINERS } from '@/components/Docker/docker-update-all-containers.mutation';
import { UPDATE_DOCKER_CONTAINERS } from '@/components/Docker/docker-update-containers.mutation';
import DockerContainerStatCell from '@/components/Docker/DockerContainerStatCell.vue';
import { ContainerState } from '@/composables/gql/graphql';
import { useContainerActions } from '@/composables/useContainerActions';
import { useDockerViewPreferences } from '@/composables/useDockerColumnVisibility';
@@ -29,6 +31,7 @@ import { useTreeData } from '@/composables/useTreeData';
import type {
DockerContainer,
DockerContainerStats,
FlatOrganizerEntry,
GetDockerContainerLogsQuery,
GetDockerContainerLogsQueryVariables,
@@ -97,6 +100,19 @@ const emit = defineEmits<{
}>();
const { client: apolloClient } = useApolloClient();
const containerStats = reactive(new Map<string, DockerContainerStats>());
const { onResult: onStatsResult } = useSubscription(DOCKER_STATS_SUBSCRIPTION, null, () => ({
fetchPolicy: 'network-only',
}));
onStatsResult((result) => {
const stat = result.data?.dockerContainerStats as DockerContainerStats | undefined;
if (stat && stat.id) {
containerStats.set(stat.id, stat);
}
});
const COMMA_SEPARATED_LIST = /\s*,\s*/;
function normalizeListString(value: string): string[] {
@@ -833,6 +849,30 @@ const columns = computed<TableColumn<TreeRow<DockerContainer>>[]>(() => {
return h(UBadge, { color }, () => state);
},
},
{
accessorKey: 'cpu',
header: 'CPU',
cell: ({ row }) => {
if (row.original.type === 'folder' || !row.original.containerId) return '';
return h(DockerContainerStatCell, {
containerId: row.original.containerId,
statsMap: containerStats,
type: 'cpu',
});
},
},
{
accessorKey: 'memory',
header: 'Memory',
cell: ({ row }) => {
if (row.original.type === 'folder' || !row.original.containerId) return '';
return h(DockerContainerStatCell, {
containerId: row.original.containerId,
statsMap: containerStats,
type: 'memory',
});
},
},
{
accessorKey: 'version',
header: 'Version',
@@ -914,6 +954,8 @@ function getDefaultColumnVisibility(isCompact: boolean): Record<string, boolean>
if (isCompact) {
return {
state: false,
cpu: false,
memory: false,
version: false,
network: false,
containerIp: false,
@@ -927,6 +969,8 @@ function getDefaultColumnVisibility(isCompact: boolean): Record<string, boolean>
} else {
return {
state: true,
cpu: true,
memory: true,
version: false,
network: false,
containerIp: false,
@@ -1525,6 +1569,35 @@ function getRowActionItems(row: TreeRow<DockerContainer>): DropdownMenuItems {
],
];
}
function handleRowClick(payload: { id: string; type: string; name: string; meta?: DockerContainer }) {
emit('row:click', {
id: payload.id,
type: payload.type as 'container' | 'folder',
name: payload.name,
containerId: payload.meta?.id,
});
}
function handleRowSelect(payload: {
id: string;
type: string;
name: string;
meta?: DockerContainer;
selected: boolean;
}) {
emit('row:select', {
id: payload.id,
type: payload.type as 'container' | 'folder',
name: payload.name,
containerId: payload.meta?.id,
selected: payload.selected,
});
}
function handleUpdateSelectedIds(ids: string[]) {
emit('update:selectedIds', ids);
}
</script>
<template>
@@ -1541,28 +1614,11 @@ function getRowActionItems(row: TreeRow<DockerContainer>): DropdownMenuItems {
:enable-drag-drop="!!flatEntries"
:searchable-keys="searchableKeys"
:search-accessor="dockerSearchAccessor"
@row:click="
(payload) =>
emit('row:click', {
id: payload.id,
type: payload.type as 'container' | 'folder',
name: payload.name,
containerId: payload.meta?.id,
})
"
@row:click="handleRowClick"
@row:contextmenu="handleRowContextMenu"
@row:select="
(payload) =>
emit('row:select', {
id: payload.id,
type: payload.type as 'container' | 'folder',
name: payload.name,
containerId: payload.meta?.id,
selected: payload.selected,
})
"
@row:select="handleRowSelect"
@row:drop="handleDropOnRow"
@update:selected-ids="(ids) => emit('update:selectedIds', ids)"
@update:selected-ids="handleUpdateSelectedIds"
>
<template #toolbar="{ selectedCount: count, globalFilter: filterText, setGlobalFilter }">
<div :class="['mb-4 flex flex-wrap items-center gap-2', compact ? 'sm:px-0.5' : '']">
@@ -1763,16 +1819,15 @@ function getRowActionItems(row: TreeRow<DockerContainer>): DropdownMenuItems {
class="flex min-w-0 flex-1 items-center gap-2"
>
<span class="i-lucide-folder text-gray-500" />
<!-- @vue-expect-error - Vue auto-unwraps refs in templates -->
<template v-if="folderOps.renamingFolderId === row.id">
<template v-if="folderOps.renamingFolderId.value === row.id">
<input
v-model="folderOps.renameValue"
class="border-default bg-default flex-1 rounded border px-2 py-1"
@keydown.enter.prevent="folderOps.commitRenameFolder(row.id)"
@keydown.esc.prevent="
() => {
folderOps.renamingFolderId = '';
folderOps.renameValue = '';
folderOps.renamingFolderId.value = '';
folderOps.renameValue.value = '';
}
"
@blur="folderOps.commitRenameFolder(row.id)"

View File

@@ -0,0 +1,14 @@
import { gql } from '@apollo/client';
export const DOCKER_STATS_SUBSCRIPTION = gql`
subscription DockerContainerStats {
dockerContainerStats {
id
cpuPercent
memUsage
memPercent
netIO
blockIO
}
}
`;

View File

@@ -43,6 +43,7 @@ type Documents = {
"\n mutation RenameDockerFolder($folderId: String!, $newName: String!) {\n renameDockerFolder(folderId: $folderId, newName: $newName) {\n version\n views {\n id\n name\n rootId\n flatEntries {\n id\n type\n name\n parentId\n depth\n position\n path\n hasChildren\n childrenIds\n meta {\n id\n names\n state\n status\n image\n ports {\n privatePort\n publicPort\n type\n }\n autoStart\n hostConfig {\n networkMode\n }\n created\n isUpdateAvailable\n isRebuildReady\n }\n }\n }\n }\n }\n": typeof types.RenameDockerFolderDocument,
"\n mutation SetDockerFolderChildren($folderId: String, $childrenIds: [String!]!) {\n setDockerFolderChildren(folderId: $folderId, childrenIds: $childrenIds) {\n version\n views {\n id\n name\n rootId\n flatEntries {\n id\n type\n name\n parentId\n depth\n position\n path\n hasChildren\n childrenIds\n }\n }\n }\n }\n": typeof types.SetDockerFolderChildrenDocument,
"\n mutation StartDockerContainer($id: PrefixedID!) {\n docker {\n start(id: $id) {\n id\n names\n state\n }\n }\n }\n": typeof types.StartDockerContainerDocument,
"\n subscription DockerContainerStats {\n dockerContainerStats {\n id\n cpuPercent\n memUsage\n memPercent\n netIO\n blockIO\n }\n }\n": typeof types.DockerContainerStatsDocument,
"\n mutation StopDockerContainer($id: PrefixedID!) {\n docker {\n stop(id: $id) {\n id\n names\n state\n }\n }\n }\n": typeof types.StopDockerContainerDocument,
"\n mutation UnpauseDockerContainer($id: PrefixedID!) {\n docker {\n unpause(id: $id) {\n id\n names\n state\n }\n }\n }\n": typeof types.UnpauseDockerContainerDocument,
"\n mutation UpdateAllDockerContainers {\n docker {\n updateAllContainers {\n id\n names\n state\n isUpdateAvailable\n isRebuildReady\n }\n }\n }\n": typeof types.UpdateAllDockerContainersDocument,
@@ -113,6 +114,7 @@ const documents: Documents = {
"\n mutation RenameDockerFolder($folderId: String!, $newName: String!) {\n renameDockerFolder(folderId: $folderId, newName: $newName) {\n version\n views {\n id\n name\n rootId\n flatEntries {\n id\n type\n name\n parentId\n depth\n position\n path\n hasChildren\n childrenIds\n meta {\n id\n names\n state\n status\n image\n ports {\n privatePort\n publicPort\n type\n }\n autoStart\n hostConfig {\n networkMode\n }\n created\n isUpdateAvailable\n isRebuildReady\n }\n }\n }\n }\n }\n": types.RenameDockerFolderDocument,
"\n mutation SetDockerFolderChildren($folderId: String, $childrenIds: [String!]!) {\n setDockerFolderChildren(folderId: $folderId, childrenIds: $childrenIds) {\n version\n views {\n id\n name\n rootId\n flatEntries {\n id\n type\n name\n parentId\n depth\n position\n path\n hasChildren\n childrenIds\n }\n }\n }\n }\n": types.SetDockerFolderChildrenDocument,
"\n mutation StartDockerContainer($id: PrefixedID!) {\n docker {\n start(id: $id) {\n id\n names\n state\n }\n }\n }\n": types.StartDockerContainerDocument,
"\n subscription DockerContainerStats {\n dockerContainerStats {\n id\n cpuPercent\n memUsage\n memPercent\n netIO\n blockIO\n }\n }\n": types.DockerContainerStatsDocument,
"\n mutation StopDockerContainer($id: PrefixedID!) {\n docker {\n stop(id: $id) {\n id\n names\n state\n }\n }\n }\n": types.StopDockerContainerDocument,
"\n mutation UnpauseDockerContainer($id: PrefixedID!) {\n docker {\n unpause(id: $id) {\n id\n names\n state\n }\n }\n }\n": types.UnpauseDockerContainerDocument,
"\n mutation UpdateAllDockerContainers {\n docker {\n updateAllContainers {\n id\n names\n state\n isUpdateAvailable\n isRebuildReady\n }\n }\n }\n": types.UpdateAllDockerContainersDocument,
@@ -284,6 +286,10 @@ export function graphql(source: "\n mutation SetDockerFolderChildren($folderId:
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation StartDockerContainer($id: PrefixedID!) {\n docker {\n start(id: $id) {\n id\n names\n state\n }\n }\n }\n"): (typeof documents)["\n mutation StartDockerContainer($id: PrefixedID!) {\n docker {\n start(id: $id) {\n id\n names\n state\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n subscription DockerContainerStats {\n dockerContainerStats {\n id\n cpuPercent\n memUsage\n memPercent\n netIO\n blockIO\n }\n }\n"): (typeof documents)["\n subscription DockerContainerStats {\n dockerContainerStats {\n id\n cpuPercent\n memUsage\n memPercent\n netIO\n blockIO\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

View File

@@ -798,6 +798,21 @@ export type DockerContainerPortConflict = {
type: ContainerPortType;
};
export type DockerContainerStats = {
__typename?: 'DockerContainerStats';
/** Block I/O String (e.g. 100MB / 1GB) */
blockIO: Scalars['String']['output'];
/** CPU Usage Percentage */
cpuPercent: Scalars['Float']['output'];
id: Scalars['PrefixedID']['output'];
/** Memory Usage Percentage */
memPercent: Scalars['Float']['output'];
/** Memory Usage String (e.g. 100MB / 1GB) */
memUsage: Scalars['String']['output'];
/** Network I/O String (e.g. 100MB / 1GB) */
netIO: Scalars['String']['output'];
};
export type DockerLanPortConflict = {
__typename?: 'DockerLanPortConflict';
containers: Array<DockerPortConflictContainer>;
@@ -2233,6 +2248,7 @@ export type SsoSettings = Node & {
export type Subscription = {
__typename?: 'Subscription';
arraySubscription: UnraidArray;
dockerContainerStats: Array<DockerContainerStats>;
logFile: LogFileContent;
notificationAdded: Notification;
notificationsOverview: NotificationOverview;
@@ -2964,6 +2980,11 @@ export type StartDockerContainerMutationVariables = Exact<{
export type StartDockerContainerMutation = { __typename?: 'Mutation', docker: { __typename?: 'DockerMutations', start: { __typename?: 'DockerContainer', id: string, names: Array<string>, state: ContainerState } } };
export type DockerContainerStatsSubscriptionVariables = Exact<{ [key: string]: never; }>;
export type DockerContainerStatsSubscription = { __typename?: 'Subscription', dockerContainerStats: Array<{ __typename?: 'DockerContainerStats', id: string, cpuPercent: number, memUsage: string, memPercent: number, netIO: string, blockIO: string }> };
export type StopDockerContainerMutationVariables = Exact<{
id: Scalars['PrefixedID']['input'];
}>;
@@ -3252,6 +3273,7 @@ export const RefreshDockerDigestsDocument = {"kind":"Document","definitions":[{"
export const RenameDockerFolderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RenameDockerFolder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"folderId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"renameDockerFolder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"folderId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"folderId"}}},{"kind":"Argument","name":{"kind":"Name","value":"newName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newName"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"rootId"}},{"kind":"Field","name":{"kind":"Name","value":"flatEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"parentId"}},{"kind":"Field","name":{"kind":"Name","value":"depth"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"hasChildren"}},{"kind":"Field","name":{"kind":"Name","value":"childrenIds"}},{"kind":"Field","name":{"kind":"Name","value":"meta"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"names"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"ports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"privatePort"}},{"kind":"Field","name":{"kind":"Name","value":"publicPort"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoStart"}},{"kind":"Field","name":{"kind":"Name","value":"hostConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"networkMode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"isUpdateAvailable"}},{"kind":"Field","name":{"kind":"Name","value":"isRebuildReady"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<RenameDockerFolderMutation, RenameDockerFolderMutationVariables>;
export const SetDockerFolderChildrenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetDockerFolderChildren"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"folderId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"childrenIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setDockerFolderChildren"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"folderId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"folderId"}}},{"kind":"Argument","name":{"kind":"Name","value":"childrenIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"childrenIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"rootId"}},{"kind":"Field","name":{"kind":"Name","value":"flatEntries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"parentId"}},{"kind":"Field","name":{"kind":"Name","value":"depth"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"hasChildren"}},{"kind":"Field","name":{"kind":"Name","value":"childrenIds"}}]}}]}}]}}]}}]} as unknown as DocumentNode<SetDockerFolderChildrenMutation, SetDockerFolderChildrenMutationVariables>;
export const StartDockerContainerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StartDockerContainer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PrefixedID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"docker"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"names"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]}}]}}]} as unknown as DocumentNode<StartDockerContainerMutation, StartDockerContainerMutationVariables>;
export const DockerContainerStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"DockerContainerStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dockerContainerStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"cpuPercent"}},{"kind":"Field","name":{"kind":"Name","value":"memUsage"}},{"kind":"Field","name":{"kind":"Name","value":"memPercent"}},{"kind":"Field","name":{"kind":"Name","value":"netIO"}},{"kind":"Field","name":{"kind":"Name","value":"blockIO"}}]}}]}}]} as unknown as DocumentNode<DockerContainerStatsSubscription, DockerContainerStatsSubscriptionVariables>;
export const StopDockerContainerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StopDockerContainer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PrefixedID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"docker"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stop"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"names"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]}}]}}]} as unknown as DocumentNode<StopDockerContainerMutation, StopDockerContainerMutationVariables>;
export const UnpauseDockerContainerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UnpauseDockerContainer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PrefixedID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"docker"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unpause"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"names"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]}}]}}]} as unknown as DocumentNode<UnpauseDockerContainerMutation, UnpauseDockerContainerMutationVariables>;
export const UpdateAllDockerContainersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateAllDockerContainers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"docker"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateAllContainers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"names"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"isUpdateAvailable"}},{"kind":"Field","name":{"kind":"Name","value":"isRebuildReady"}}]}}]}}]}}]} as unknown as DocumentNode<UpdateAllDockerContainersMutation, UpdateAllDockerContainersMutationVariables>;