diff --git a/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts b/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts index 946fb6df7a..1005019235 100644 --- a/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts +++ b/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts @@ -2,7 +2,14 @@ import { flushPromises, mount } from '@vue/test-utils'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { GetInternalBootContextQuery } from '~/composables/gql/graphql'; + import OnboardingInternalBootStep from '~/components/Onboarding/steps/OnboardingInternalBootStep.vue'; +import { + ArrayState, + DiskInterfaceType, + GetInternalBootContextDocument, +} from '~/composables/gql/graphql'; import { createTestI18n } from '../../utils/i18n'; type MockInternalBootSelection = { @@ -13,30 +20,7 @@ type MockInternalBootSelection = { updateBios: boolean; }; -type MockContext = { - array: { - state?: string | null; - boot?: { device?: string | null } | null; - parities: Array<{ device?: string | null }>; - disks: Array<{ device?: string | null }>; - caches: Array<{ name?: string | null; device?: string | null }>; - }; - vars?: { - fsState?: string | null; - bootEligible?: boolean | null; - enableBootTransfer?: string | null; - reservedNames?: string | null; - } | null; - shares: Array<{ name?: string | null }>; - disks: Array<{ - device: string; - size: number; - emhttpDeviceId?: string | null; - interfaceType?: string | null; - }>; -}; - -const { draftStore, contextResult, contextLoading, contextError } = vi.hoisted(() => { +const { draftStore, contextResult, contextLoading, contextError, useQueryMock } = vi.hoisted(() => { const store = { bootMode: 'usb' as 'usb' | 'storage', internalBootSelection: null as MockInternalBootSelection | null, @@ -54,9 +38,10 @@ const { draftStore, contextResult, contextLoading, contextError } = vi.hoisted(( return { draftStore: store, - contextResult: { value: null as MockContext | null, __v_isRef: true }, + contextResult: { value: null as GetInternalBootContextQuery | null, __v_isRef: true }, contextLoading: { value: false, __v_isRef: true }, contextError: { value: null as unknown, __v_isRef: true }, + useQueryMock: vi.fn(), }; }); @@ -70,11 +55,13 @@ vi.mock('@unraid/ui', () => ({ })); vi.mock('@vue/apollo-composable', () => ({ - useQuery: () => ({ - result: contextResult, - loading: contextLoading, - error: contextError, - }), + useQuery: useQueryMock, +})); + +useQueryMock.mockImplementation(() => ({ + result: contextResult, + loading: contextLoading, + error: contextError, })); vi.mock('@/components/Onboarding/store/onboardingDraft', () => ({ @@ -108,7 +95,7 @@ describe('OnboardingInternalBootStep', () => { draftStore.bootMode = 'storage'; contextResult.value = { array: { - state: 'STARTED', + state: ArrayState.STARTED, boot: { device: '/dev/sda' }, parities: [{ device: '/dev/sdb' }], disks: [{ device: '/dev/sdc' }], @@ -122,12 +109,48 @@ describe('OnboardingInternalBootStep', () => { }, shares: [], disks: [ - { device: '/dev/sda', size: gib(32), emhttpDeviceId: 'boot-disk', interfaceType: 'SATA' }, - { device: '/dev/sdb', size: gib(32), emhttpDeviceId: 'parity-disk', interfaceType: 'SATA' }, - { device: '/dev/sdc', size: gib(32), emhttpDeviceId: 'array-disk', interfaceType: 'SATA' }, - { device: '/dev/sdd', size: gib(32), emhttpDeviceId: 'cache-disk', interfaceType: 'SATA' }, - { device: '/dev/sde', size: gib(6), emhttpDeviceId: 'small-disk', interfaceType: 'SATA' }, - { device: '/dev/sdf', size: gib(32), emhttpDeviceId: 'usb-disk', interfaceType: 'USB' }, + { + device: '/dev/sda', + size: gib(32), + serialNum: 'BOOT-1', + emhttpDeviceId: 'boot-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdb', + size: gib(32), + serialNum: 'PARITY-1', + emhttpDeviceId: 'parity-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdc', + size: gib(32), + serialNum: 'ARRAY-1', + emhttpDeviceId: 'array-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdd', + size: gib(32), + serialNum: 'CACHE-1', + emhttpDeviceId: 'cache-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sde', + size: gib(6), + serialNum: 'SMALL-1', + emhttpDeviceId: 'small-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdf', + size: gib(32), + serialNum: 'USB-1', + emhttpDeviceId: 'usb-disk', + interfaceType: DiskInterfaceType.USB, + }, ], }; @@ -152,11 +175,59 @@ describe('OnboardingInternalBootStep', () => { expect(wrapper.find('[data-testid="brand-button"]').attributes('disabled')).toBeDefined(); }); + it('loads internal boot context from the network when the step mounts', async () => { + mountComponent(); + await flushPromises(); + + expect(useQueryMock).toHaveBeenCalledWith( + GetInternalBootContextDocument, + null, + expect.objectContaining({ + fetchPolicy: 'network-only', + }) + ); + }); + + it('shows drive serials in the selectable device labels', async () => { + draftStore.bootMode = 'storage'; + contextResult.value = { + array: { + state: ArrayState.STOPPED, + boot: null, + parities: [], + disks: [], + caches: [], + }, + vars: { + fsState: 'Stopped', + bootEligible: true, + enableBootTransfer: 'yes', + reservedNames: '', + }, + shares: [], + disks: [ + { + device: '/dev/sda', + size: gib(32), + serialNum: 'WD-TEST-1234', + emhttpDeviceId: 'eligible-disk', + interfaceType: DiskInterfaceType.SATA, + }, + ], + }; + + const wrapper = mountComponent(); + await flushPromises(); + + expect(wrapper.text()).toContain('WD-TEST-1234 - 32.0 GB (sda)'); + expect(wrapper.text()).not.toContain('eligible-disk - 32.0 GB (sda)'); + }); + it('defaults the storage pool name to cache', async () => { draftStore.bootMode = 'storage'; contextResult.value = { array: { - state: 'STOPPED', + state: ArrayState.STOPPED, boot: null, parities: [], disks: [], @@ -170,7 +241,13 @@ describe('OnboardingInternalBootStep', () => { }, shares: [], disks: [ - { device: '/dev/sda', size: gib(32), emhttpDeviceId: 'eligible-disk', interfaceType: 'SATA' }, + { + device: '/dev/sda', + size: gib(32), + serialNum: 'ELIGIBLE-1', + emhttpDeviceId: 'eligible-disk', + interfaceType: DiskInterfaceType.SATA, + }, ], }; @@ -188,7 +265,7 @@ describe('OnboardingInternalBootStep', () => { draftStore.bootMode = 'storage'; contextResult.value = { array: { - state: 'STOPPED', + state: ArrayState.STOPPED, boot: null, parities: [], disks: [], @@ -202,7 +279,13 @@ describe('OnboardingInternalBootStep', () => { }, shares: [], disks: [ - { device: '/dev/sda', size: gib(32), emhttpDeviceId: 'eligible-disk', interfaceType: 'SATA' }, + { + device: '/dev/sda', + size: gib(32), + serialNum: 'ELIGIBLE-1', + emhttpDeviceId: 'eligible-disk', + interfaceType: DiskInterfaceType.SATA, + }, ], }; @@ -216,7 +299,7 @@ describe('OnboardingInternalBootStep', () => { draftStore.bootMode = 'storage'; contextResult.value = { array: { - state: 'STOPPED', + state: ArrayState.STOPPED, boot: { device: '/dev/sda' }, parities: [{ device: '/dev/sdb' }], disks: [], @@ -230,8 +313,20 @@ describe('OnboardingInternalBootStep', () => { }, shares: [], disks: [ - { device: '/dev/sda', size: gib(32), emhttpDeviceId: 'boot-disk', interfaceType: 'SATA' }, - { device: '/dev/sdb', size: gib(32), emhttpDeviceId: 'parity-disk', interfaceType: 'SATA' }, + { + device: '/dev/sda', + size: gib(32), + serialNum: 'BOOT-1', + emhttpDeviceId: 'boot-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdb', + size: gib(32), + serialNum: 'PARITY-1', + emhttpDeviceId: 'parity-disk', + interfaceType: DiskInterfaceType.SATA, + }, ], }; @@ -250,7 +345,7 @@ describe('OnboardingInternalBootStep', () => { draftStore.bootMode = 'storage'; contextResult.value = { array: { - state: 'STARTED', + state: ArrayState.STARTED, boot: null, parities: [], disks: [], @@ -264,7 +359,13 @@ describe('OnboardingInternalBootStep', () => { }, shares: [], disks: [ - { device: '/dev/sda', size: gib(32), emhttpDeviceId: 'eligible-disk', interfaceType: 'SATA' }, + { + device: '/dev/sda', + size: gib(32), + serialNum: 'ELIGIBLE-1', + emhttpDeviceId: 'eligible-disk', + interfaceType: DiskInterfaceType.SATA, + }, ], }; @@ -280,7 +381,7 @@ describe('OnboardingInternalBootStep', () => { draftStore.bootMode = 'storage'; contextResult.value = { array: { - state: 'STOPPED', + state: ArrayState.STOPPED, boot: null, parities: [], disks: [], @@ -294,10 +395,34 @@ describe('OnboardingInternalBootStep', () => { }, shares: [], disks: [ - { device: '/dev/sda', size: gib(32), emhttpDeviceId: 'cache-disk', interfaceType: 'SATA' }, - { device: '/dev/sdb', size: gib(6), emhttpDeviceId: 'small-disk', interfaceType: 'SATA' }, - { device: '/dev/sdc', size: gib(32), emhttpDeviceId: 'eligible-disk', interfaceType: 'SATA' }, - { device: '/dev/sdd', size: gib(32), emhttpDeviceId: 'usb-disk', interfaceType: 'USB' }, + { + device: '/dev/sda', + size: gib(32), + serialNum: 'CACHE-1', + emhttpDeviceId: 'cache-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdb', + size: gib(6), + serialNum: 'SMALL-1', + emhttpDeviceId: 'small-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdc', + size: gib(32), + serialNum: 'ELIGIBLE-1', + emhttpDeviceId: 'eligible-disk', + interfaceType: DiskInterfaceType.SATA, + }, + { + device: '/dev/sdd', + size: gib(32), + serialNum: 'USB-1', + emhttpDeviceId: 'usb-disk', + interfaceType: DiskInterfaceType.USB, + }, ], }; @@ -308,10 +433,10 @@ describe('OnboardingInternalBootStep', () => { const selects = wrapper.findAll('select'); expect(selects).toHaveLength(3); const deviceSelect = selects[1]; - expect(deviceSelect.text()).toContain('eligible-disk'); - expect(deviceSelect.text()).toContain('usb-disk'); - expect(deviceSelect.text()).not.toContain('cache-disk'); - expect(deviceSelect.text()).not.toContain('small-disk'); + expect(deviceSelect.text()).toContain('ELIGIBLE-1'); + expect(deviceSelect.text()).toContain('USB-1'); + expect(deviceSelect.text()).not.toContain('CACHE-1'); + expect(deviceSelect.text()).not.toContain('SMALL-1'); expect(wrapper.text()).not.toContain('ASSIGNED_TO_CACHE'); const biosWarning = wrapper.get('[data-testid="internal-boot-update-bios-warning"]'); const eligibilityPanel = wrapper.get('[data-testid="internal-boot-eligibility-panel"]'); diff --git a/web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts b/web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts index 1b2d14d6e9..1bc07a3fe4 100644 --- a/web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts +++ b/web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts @@ -9,13 +9,19 @@ import { UPDATE_SSH_SETTINGS_MUTATION, } from '@/components/Onboarding/graphql/coreSettings.mutations'; import { GET_CORE_SETTINGS_QUERY } from '@/components/Onboarding/graphql/getCoreSettings.query'; -import { GET_INTERNAL_BOOT_CONTEXT_QUERY } from '@/components/Onboarding/graphql/getInternalBootContext.query'; import { INSTALLED_UNRAID_PLUGINS_QUERY } from '@/components/Onboarding/graphql/installedPlugins.query'; import { UPDATE_SYSTEM_TIME_MUTATION } from '@/components/Onboarding/graphql/updateSystemTime.mutation'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { GetInternalBootContextQuery } from '~/composables/gql/graphql'; + import OnboardingSummaryStep from '~/components/Onboarding/steps/OnboardingSummaryStep.vue'; -import { PluginInstallStatus } from '~/composables/gql/graphql'; +import { + ArrayState, + DiskInterfaceType, + GetInternalBootContextDocument, + PluginInstallStatus, +} from '~/composables/gql/graphql'; import { createTestI18n } from '../../utils/i18n'; const { @@ -74,7 +80,7 @@ const { value: null as unknown, }, coreSettingsError: { value: null as unknown }, - internalBootContextResult: { value: null as unknown }, + internalBootContextResult: { value: null as GetInternalBootContextQuery | null }, installedPluginsResult: { value: { installedUnraidPlugins: [] as string[] } }, availableLanguagesResult: { value: { @@ -219,7 +225,7 @@ const setupApolloMocks = () => { if (doc === GET_CORE_SETTINGS_QUERY) { return { result: coreSettingsResult, error: coreSettingsError }; } - if (doc === GET_INTERNAL_BOOT_CONTEXT_QUERY) { + if (doc === GetInternalBootContextDocument) { return { result: internalBootContextResult }; } if (doc === INSTALLED_UNRAID_PLUGINS_QUERY) { @@ -302,6 +308,7 @@ describe('OnboardingSummaryStep', () => { }; internalBootContextResult.value = { array: { + state: ArrayState.STOPPED, boot: null, parities: [], disks: [], @@ -310,20 +317,21 @@ describe('OnboardingSummaryStep', () => { vars: { bootEligible: true, }, + shares: [], disks: [ { device: '/dev/sda', size: 500 * 1024 * 1024 * 1024, + serialNum: 'DISK-A', emhttpDeviceId: 'diskA', - emhttpSectors: null, - emhttpSectorSize: null, + interfaceType: DiskInterfaceType.SATA, }, { device: '/dev/sdb', size: 250 * 1024 * 1024 * 1024, + serialNum: 'DISK-B', emhttpDeviceId: 'diskB', - emhttpSectors: null, - emhttpSectorSize: null, + interfaceType: DiskInterfaceType.SATA, }, ], }; @@ -1092,8 +1100,8 @@ describe('OnboardingSummaryStep', () => { const { wrapper } = mountComponent(); - expect(wrapper.text()).toContain('diskA - 500 GB (sda)'); - expect(wrapper.text()).toContain('diskB - 250 GB (sdb)'); + expect(wrapper.text()).toContain('DISK-A - 500 GB (sda)'); + expect(wrapper.text()).toContain('DISK-B - 250 GB (sdb)'); }); it('requires confirmation before applying storage boot drive changes', async () => { diff --git a/web/src/components/Onboarding/OnboardingModal.vue b/web/src/components/Onboarding/OnboardingModal.vue index e10ad79446..5b4a881213 100644 --- a/web/src/components/Onboarding/OnboardingModal.vue +++ b/web/src/components/Onboarding/OnboardingModal.vue @@ -7,7 +7,6 @@ import { useMutation, useQuery } from '@vue/apollo-composable'; import { ArrowTopRightOnSquareIcon, XMarkIcon } from '@heroicons/vue/24/solid'; import { Dialog } from '@unraid/ui'; import { COMPLETE_ONBOARDING_MUTATION } from '@/components/Onboarding/graphql/completeUpgradeStep.mutation'; -import { GET_INTERNAL_BOOT_STEP_VISIBILITY_QUERY } from '@/components/Onboarding/graphql/getInternalBootStepVisibility.query'; import type { BrandButtonProps } from '@unraid/ui'; import type { StepId } from '~/components/Onboarding/stepRegistry'; @@ -21,6 +20,7 @@ import { useOnboardingDraftStore } from '~/components/Onboarding/store/onboardin import { useOnboardingModalStore } from '~/components/Onboarding/store/onboardingModalVisibility'; import { useOnboardingStore } from '~/components/Onboarding/store/onboardingStatus'; import { cleanupOnboardingStorage } from '~/components/Onboarding/store/onboardingStorageCleanup'; +import { GetInternalBootStepVisibilityDocument } from '~/composables/gql/graphql'; import { usePurchaseStore } from '~/store/purchase'; import { useServerStore } from '~/store/server'; import { useThemeStore } from '~/store/theme'; @@ -76,13 +76,9 @@ const showActivationStep = computed(() => { return hasCode && ACTIVATION_STEP_REGISTRATION_STATES.has(regState); }); -const { result: internalBootVisibilityResult } = useQuery( - GET_INTERNAL_BOOT_STEP_VISIBILITY_QUERY, - null, - { - fetchPolicy: 'cache-first', - } -); +const { result: internalBootVisibilityResult } = useQuery(GetInternalBootStepVisibilityDocument, null, { + fetchPolicy: 'network-only', +}); const showInternalBootStep = computed(() => { const setting = internalBootVisibilityResult.value?.vars?.enableBootTransfer; diff --git a/web/src/components/Onboarding/graphql/getInternalBootContext.query.ts b/web/src/components/Onboarding/graphql/getInternalBootContext.query.ts index 221b5eb181..874f427d49 100644 --- a/web/src/components/Onboarding/graphql/getInternalBootContext.query.ts +++ b/web/src/components/Onboarding/graphql/getInternalBootContext.query.ts @@ -30,6 +30,7 @@ export const GET_INTERNAL_BOOT_CONTEXT_QUERY = gql` disks { device size + serialNum emhttpDeviceId interfaceType } diff --git a/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue b/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue index 0fea2f8919..7eadd998da 100644 --- a/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue +++ b/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue @@ -6,7 +6,6 @@ import { useQuery } from '@vue/apollo-composable'; import { ChevronLeftIcon, CircleStackIcon, InformationCircleIcon } from '@heroicons/vue/24/outline'; import { ChevronDownIcon, ChevronRightIcon, ExclamationTriangleIcon } from '@heroicons/vue/24/solid'; import { BrandButton } from '@unraid/ui'; -import { GET_INTERNAL_BOOT_CONTEXT_QUERY } from '@/components/Onboarding/graphql/getInternalBootContext.query'; import { useOnboardingDraftStore } from '@/components/Onboarding/store/onboardingDraft'; import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'; @@ -14,6 +13,9 @@ import type { OnboardingBootMode, OnboardingInternalBootSelection, } from '@/components/Onboarding/store/onboardingDraft'; +import type { GetInternalBootContextQuery } from '~/composables/gql/graphql'; + +import { GetInternalBootContextDocument } from '~/composables/gql/graphql'; export interface Props { onComplete: () => void; @@ -49,29 +51,6 @@ interface InternalBootTemplateData { poolNames: string[]; } -interface InternalBootContext { - array: { - state?: string | null; - boot?: { device?: string | null } | null; - parities: { device?: string | null }[]; - disks: { device?: string | null }[]; - caches: { name?: string | null; device?: string | null }[]; - }; - vars?: { - fsState?: string | null; - bootEligible?: boolean | null; - enableBootTransfer?: string | null; - reservedNames?: string | null; - } | null; - shares: { name?: string | null }[]; - disks: { - device: string; - size: number; - emhttpDeviceId?: string | null; - interfaceType?: string | null; - }[]; -} - type InternalBootTransferState = 'enabled' | 'disabled' | 'unknown'; type InternalBootEligibilityState = 'eligible' | 'ineligible' | 'unknown'; type InternalBootSystemEligibilityCode = @@ -153,20 +132,20 @@ const normalizeDeviceName = (value: string | null | undefined): string => { return trimmed; }; -const buildDeviceLabel = (optionValue: string, sizeLabel: string, device: string): string => { - if (optionValue === device) { - return `${optionValue} - ${sizeLabel}`; +const buildDeviceLabel = (displayId: string, sizeLabel: string, device: string): string => { + if (displayId === device) { + return `${displayId} - ${sizeLabel}`; } - return `${optionValue} - ${sizeLabel} (${device})`; + return `${displayId} - ${sizeLabel} (${device})`; }; const { result: contextResult, loading: contextLoading, error: contextError, -} = useQuery(GET_INTERNAL_BOOT_CONTEXT_QUERY, null, { - fetchPolicy: 'cache-first', +} = useQuery(GetInternalBootContextDocument, null, { + fetchPolicy: 'network-only', }); const formError = ref(null); @@ -202,7 +181,7 @@ const addDiskEligibilityCode = ( }; const diskEligibilityCodesByDevice = computed(() => { - const data = contextResult.value as InternalBootContext | null; + const data: GetInternalBootContextQuery | null | undefined = contextResult.value; const codesByDevice = new Map>(); if (!data) { return codesByDevice; @@ -223,7 +202,7 @@ const diskEligibilityCodesByDevice = computed(() => { }); const templateData = computed(() => { - const data = contextResult.value as InternalBootContext | null; + const data: GetInternalBootContextQuery | null | undefined = contextResult.value; if (!data) { return null; } @@ -239,12 +218,14 @@ const templateData = computed(() => { ineligibilityCodes.push('TOO_SMALL'); } + const serialNum = disk.serialNum?.trim() || ''; const emhttpDeviceId = disk.emhttpDeviceId?.trim() || ''; const optionValue = emhttpDeviceId || device; + const displayId = serialNum || emhttpDeviceId || device; const sizeLabel = formatBytes(sizeBytes); return { value: optionValue, - label: buildDeviceLabel(optionValue, sizeLabel, device), + label: buildDeviceLabel(displayId, sizeLabel, device), device, sizeMiB, ineligibilityCodes, diff --git a/web/src/components/Onboarding/steps/OnboardingSummaryStep.vue b/web/src/components/Onboarding/steps/OnboardingSummaryStep.vue index 800d729b2e..175d606074 100644 --- a/web/src/components/Onboarding/steps/OnboardingSummaryStep.vue +++ b/web/src/components/Onboarding/steps/OnboardingSummaryStep.vue @@ -39,7 +39,6 @@ import { UPDATE_SSH_SETTINGS_MUTATION, } from '@/components/Onboarding/graphql/coreSettings.mutations'; import { GET_CORE_SETTINGS_QUERY } from '@/components/Onboarding/graphql/getCoreSettings.query'; -import { GET_INTERNAL_BOOT_CONTEXT_QUERY } from '@/components/Onboarding/graphql/getInternalBootContext.query'; import { INSTALLED_UNRAID_PLUGINS_QUERY } from '@/components/Onboarding/graphql/installedPlugins.query'; import { UPDATE_SYSTEM_TIME_MUTATION } from '@/components/Onboarding/graphql/updateSystemTime.mutation'; import { useOnboardingModalStore } from '@/components/Onboarding/store/onboardingModalVisibility'; @@ -49,10 +48,15 @@ import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'; import type { LogEntry } from '@/components/Onboarding/components/OnboardingConsole.vue'; import type { OnboardingErrorDiagnostics } from '@/components/Onboarding/composables/onboardingErrorDiagnostics'; +import type { GetInternalBootContextQuery } from '~/composables/gql/graphql'; import { useActivationCodeDataStore } from '~/components/Onboarding/store/activationCodeData'; import { useOnboardingDraftStore } from '~/components/Onboarding/store/onboardingDraft'; -import { PluginInstallStatus, ThemeName } from '~/composables/gql/graphql'; +import { + GetInternalBootContextDocument, + PluginInstallStatus, + ThemeName, +} from '~/composables/gql/graphql'; export interface Props { onComplete: () => void; @@ -95,8 +99,8 @@ const { result: installedPluginsResult, refetch: refetchInstalledPlugins } = use const { result: availableLanguagesResult } = useQuery(GET_AVAILABLE_LANGUAGES_QUERY, null, { fetchPolicy: 'cache-first', }); -const { result: internalBootContextResult } = useQuery(GET_INTERNAL_BOOT_CONTEXT_QUERY, null, { - fetchPolicy: 'cache-first', +const { result: internalBootContextResult } = useQuery(GetInternalBootContextDocument, null, { + fetchPolicy: 'network-only', }); const draftPluginsCount = computed(() => draftStore.selectedPlugins?.size ?? 0); @@ -134,16 +138,6 @@ const summaryServerDescription = computed( () => draftStore.serverDescription || coreSettingsResult.value?.server?.comment || '' ); -interface InternalBootContextDisk { - device: string; - size: number; - emhttpDeviceId?: string | null; -} - -interface InternalBootContextData { - disks?: InternalBootContextDisk[]; -} - const showBootConfiguration = computed( () => draftStore.internalBootInitialized && !draftStore.internalBootSkipped ); @@ -194,7 +188,7 @@ const normalizeDeviceName = (value: string | null | undefined): string => { }; const internalBootDeviceLabelById = computed(() => { - const data = internalBootContextResult.value as InternalBootContextData | null; + const data: GetInternalBootContextQuery | null | undefined = internalBootContextResult.value; const disks = data?.disks ?? []; const labels = new Map(); @@ -204,11 +198,14 @@ const internalBootDeviceLabelById = computed(() => { continue; } + const serialNum = disk.serialNum?.trim() || ''; const emhttpDeviceId = disk.emhttpDeviceId?.trim() || ''; const optionValue = emhttpDeviceId || device; + const displayId = serialNum || emhttpDeviceId || device; const sizeBytes = disk.size; const sizeLabel = formatBytes(sizeBytes); - const label = `${optionValue} - ${sizeLabel} (${device})`; + const label = + displayId === device ? `${displayId} - ${sizeLabel}` : `${displayId} - ${sizeLabel} (${device})`; labels.set(optionValue, label); labels.set(device, label); diff --git a/web/src/composables/gql/gql.ts b/web/src/composables/gql/gql.ts index 7b377588c7..beafb3feb3 100644 --- a/web/src/composables/gql/gql.ts +++ b/web/src/composables/gql/gql.ts @@ -74,7 +74,7 @@ type Documents = { "\n mutation UpdateSshSettings($enabled: Boolean!, $port: Int = 22) {\n updateSshSettings(input: { enabled: $enabled, port: $port }) {\n id\n useSsh\n portssh\n }\n }\n": typeof types.UpdateSshSettingsDocument, "\n mutation CreateInternalBootPool($input: CreateInternalBootPoolInput!) {\n onboarding {\n createInternalBootPool(input: $input) {\n ok\n code\n output\n }\n }\n }\n": typeof types.CreateInternalBootPoolDocument, "\n query GetCoreSettings {\n customization {\n activationCode {\n system {\n serverName\n comment\n }\n }\n }\n vars {\n name\n sysModel\n useSsh\n localTld\n }\n server {\n name\n comment\n }\n display {\n theme\n locale\n }\n systemTime {\n timeZone\n }\n info {\n primaryNetwork {\n ipAddress\n }\n }\n }\n": typeof types.GetCoreSettingsDocument, - "\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n emhttpDeviceId\n interfaceType\n }\n }\n": typeof types.GetInternalBootContextDocument, + "\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n serialNum\n emhttpDeviceId\n interfaceType\n }\n }\n": typeof types.GetInternalBootContextDocument, "\n query GetInternalBootStepVisibility {\n vars {\n enableBootTransfer\n }\n }\n": typeof types.GetInternalBootStepVisibilityDocument, "\n mutation InstallLanguage($input: InstallPluginInput!) {\n unraidPlugins {\n installLanguage(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n": typeof types.InstallLanguageDocument, "\n mutation InstallPlugin($input: InstallPluginInput!) {\n unraidPlugins {\n installPlugin(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n": typeof types.InstallPluginDocument, @@ -159,7 +159,7 @@ const documents: Documents = { "\n mutation UpdateSshSettings($enabled: Boolean!, $port: Int = 22) {\n updateSshSettings(input: { enabled: $enabled, port: $port }) {\n id\n useSsh\n portssh\n }\n }\n": types.UpdateSshSettingsDocument, "\n mutation CreateInternalBootPool($input: CreateInternalBootPoolInput!) {\n onboarding {\n createInternalBootPool(input: $input) {\n ok\n code\n output\n }\n }\n }\n": types.CreateInternalBootPoolDocument, "\n query GetCoreSettings {\n customization {\n activationCode {\n system {\n serverName\n comment\n }\n }\n }\n vars {\n name\n sysModel\n useSsh\n localTld\n }\n server {\n name\n comment\n }\n display {\n theme\n locale\n }\n systemTime {\n timeZone\n }\n info {\n primaryNetwork {\n ipAddress\n }\n }\n }\n": types.GetCoreSettingsDocument, - "\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n emhttpDeviceId\n interfaceType\n }\n }\n": types.GetInternalBootContextDocument, + "\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n serialNum\n emhttpDeviceId\n interfaceType\n }\n }\n": types.GetInternalBootContextDocument, "\n query GetInternalBootStepVisibility {\n vars {\n enableBootTransfer\n }\n }\n": types.GetInternalBootStepVisibilityDocument, "\n mutation InstallLanguage($input: InstallPluginInput!) {\n unraidPlugins {\n installLanguage(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n": types.InstallLanguageDocument, "\n mutation InstallPlugin($input: InstallPluginInput!) {\n unraidPlugins {\n installPlugin(input: $input) {\n id\n url\n name\n status\n createdAt\n updatedAt\n finishedAt\n output\n }\n }\n }\n": types.InstallPluginDocument, @@ -441,7 +441,7 @@ export function graphql(source: "\n query GetCoreSettings {\n customization /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n emhttpDeviceId\n interfaceType\n }\n }\n"): (typeof documents)["\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n emhttpDeviceId\n interfaceType\n }\n }\n"]; +export function graphql(source: "\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n serialNum\n emhttpDeviceId\n interfaceType\n }\n }\n"): (typeof documents)["\n query GetInternalBootContext {\n array {\n state\n boot {\n device\n }\n parities {\n device\n }\n disks {\n device\n }\n caches {\n name\n device\n }\n }\n vars {\n fsState\n bootEligible\n enableBootTransfer\n reservedNames\n }\n shares {\n name\n }\n disks {\n device\n size\n serialNum\n emhttpDeviceId\n interfaceType\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/web/src/composables/gql/graphql.ts b/web/src/composables/gql/graphql.ts index 378896cb9c..d00f44c1fb 100644 --- a/web/src/composables/gql/graphql.ts +++ b/web/src/composables/gql/graphql.ts @@ -3904,7 +3904,7 @@ export type GetCoreSettingsQuery = { __typename?: 'Query', customization?: { __t export type GetInternalBootContextQueryVariables = Exact<{ [key: string]: never; }>; -export type GetInternalBootContextQuery = { __typename?: 'Query', array: { __typename?: 'UnraidArray', state: ArrayState, boot?: { __typename?: 'ArrayDisk', device?: string | null } | null, parities: Array<{ __typename?: 'ArrayDisk', device?: string | null }>, disks: Array<{ __typename?: 'ArrayDisk', device?: string | null }>, caches: Array<{ __typename?: 'ArrayDisk', name?: string | null, device?: string | null }> }, vars: { __typename?: 'Vars', fsState?: string | null, bootEligible?: boolean | null, enableBootTransfer?: string | null, reservedNames?: string | null }, shares: Array<{ __typename?: 'Share', name?: string | null }>, disks: Array<{ __typename?: 'Disk', device: string, size: number, emhttpDeviceId?: string | null, interfaceType: DiskInterfaceType }> }; +export type GetInternalBootContextQuery = { __typename?: 'Query', array: { __typename?: 'UnraidArray', state: ArrayState, boot?: { __typename?: 'ArrayDisk', device?: string | null } | null, parities: Array<{ __typename?: 'ArrayDisk', device?: string | null }>, disks: Array<{ __typename?: 'ArrayDisk', device?: string | null }>, caches: Array<{ __typename?: 'ArrayDisk', name?: string | null, device?: string | null }> }, vars: { __typename?: 'Vars', fsState?: string | null, bootEligible?: boolean | null, enableBootTransfer?: string | null, reservedNames?: string | null }, shares: Array<{ __typename?: 'Share', name?: string | null }>, disks: Array<{ __typename?: 'Disk', device: string, size: number, serialNum: string, emhttpDeviceId?: string | null, interfaceType: DiskInterfaceType }> }; export type GetInternalBootStepVisibilityQueryVariables = Exact<{ [key: string]: never; }>; @@ -4095,7 +4095,7 @@ export const SetLocaleDocument = {"kind":"Document","definitions":[{"kind":"Oper export const UpdateSshSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSshSettings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"enabled"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"port"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"22"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSshSettings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"enabled"},"value":{"kind":"Variable","name":{"kind":"Name","value":"enabled"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"port"},"value":{"kind":"Variable","name":{"kind":"Name","value":"port"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"useSsh"}},{"kind":"Field","name":{"kind":"Name","value":"portssh"}}]}}]}}]} as unknown as DocumentNode; export const CreateInternalBootPoolDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateInternalBootPool"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateInternalBootPoolInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"onboarding"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createInternalBootPool"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"output"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetCoreSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCoreSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customization"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activationCode"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"system"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverName"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"sysModel"}},{"kind":"Field","name":{"kind":"Name","value":"useSsh"}},{"kind":"Field","name":{"kind":"Name","value":"localTld"}}]}},{"kind":"Field","name":{"kind":"Name","value":"server"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"display"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"theme"}},{"kind":"Field","name":{"kind":"Name","value":"locale"}}]}},{"kind":"Field","name":{"kind":"Name","value":"systemTime"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timeZone"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"primaryNetwork"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ipAddress"}}]}}]}}]}}]} as unknown as DocumentNode; -export const GetInternalBootContextDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetInternalBootContext"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"array"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"boot"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}}]}},{"kind":"Field","name":{"kind":"Name","value":"parities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}}]}},{"kind":"Field","name":{"kind":"Name","value":"disks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}}]}},{"kind":"Field","name":{"kind":"Name","value":"caches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"device"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fsState"}},{"kind":"Field","name":{"kind":"Name","value":"bootEligible"}},{"kind":"Field","name":{"kind":"Name","value":"enableBootTransfer"}},{"kind":"Field","name":{"kind":"Name","value":"reservedNames"}}]}},{"kind":"Field","name":{"kind":"Name","value":"shares"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"disks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"emhttpDeviceId"}},{"kind":"Field","name":{"kind":"Name","value":"interfaceType"}}]}}]}}]} as unknown as DocumentNode; +export const GetInternalBootContextDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetInternalBootContext"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"array"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"boot"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}}]}},{"kind":"Field","name":{"kind":"Name","value":"parities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}}]}},{"kind":"Field","name":{"kind":"Name","value":"disks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}}]}},{"kind":"Field","name":{"kind":"Name","value":"caches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"device"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fsState"}},{"kind":"Field","name":{"kind":"Name","value":"bootEligible"}},{"kind":"Field","name":{"kind":"Name","value":"enableBootTransfer"}},{"kind":"Field","name":{"kind":"Name","value":"reservedNames"}}]}},{"kind":"Field","name":{"kind":"Name","value":"shares"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"disks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"device"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"serialNum"}},{"kind":"Field","name":{"kind":"Name","value":"emhttpDeviceId"}},{"kind":"Field","name":{"kind":"Name","value":"interfaceType"}}]}}]}}]} as unknown as DocumentNode; export const GetInternalBootStepVisibilityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetInternalBootStepVisibility"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enableBootTransfer"}}]}}]}}]} as unknown as DocumentNode; export const InstallLanguageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InstallLanguage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InstallPluginInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unraidPlugins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"installLanguage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"output"}}]}}]}}]}}]} as unknown as DocumentNode; export const InstallPluginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InstallPlugin"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InstallPluginInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unraidPlugins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"installPlugin"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"output"}}]}}]}}]}}]} as unknown as DocumentNode;