diff --git a/web/__test__/components/Onboarding/OnboardingLicenseStep.test.ts b/web/__test__/components/Onboarding/OnboardingLicenseStep.test.ts index 1691374f07..80aa25f8bf 100644 --- a/web/__test__/components/Onboarding/OnboardingLicenseStep.test.ts +++ b/web/__test__/components/Onboarding/OnboardingLicenseStep.test.ts @@ -139,6 +139,15 @@ describe('OnboardingLicenseStep.vue', () => { expect(wrapper.text()).toContain('Manage License'); }); + it('renders a clear status when no licensing device is available', () => { + activationStoreMock.registrationState.value = 'ENOFLASH'; + + const wrapper = mountComponent(); + + expect(wrapper.text()).toContain('No valid license device detected'); + expect(wrapper.text()).not.toContain('Unregistered'); + }); + it('opens activation link in new tab when activate button is clicked', async () => { const windowOpenMock = vi.fn(); vi.stubGlobal('open', windowOpenMock); diff --git a/web/__test__/components/Registration.test.ts b/web/__test__/components/Registration.test.ts index cd938f9a0c..788542a161 100644 --- a/web/__test__/components/Registration.test.ts +++ b/web/__test__/components/Registration.test.ts @@ -221,6 +221,17 @@ describe('Registration.standalone.vue', () => { expect(wrapper.find('[data-testid="manage-license-button"]').exists()).toBe(false); }); + it('renders licensing-device copy when no valid licensing device is available', async () => { + serverStore.state = 'ENOFLASH'; + + await wrapper.vm.$nextTick(); + + expect(wrapper.find('h3').text()).toContain('No valid license device detected'); + expect(wrapper.find('.prose').text()).toContain( + 'No valid TPM or USB flash device was detected for licensing.' + ); + }); + it('does not show a connect sign-in action on the registration page', async () => { serverStore.state = 'ENOKEYFILE'; serverStore.registered = false; diff --git a/web/src/components/Onboarding/steps/OnboardingLicenseStep.vue b/web/src/components/Onboarding/steps/OnboardingLicenseStep.vue index 8f060fcc74..e85e70a8ab 100644 --- a/web/src/components/Onboarding/steps/OnboardingLicenseStep.vue +++ b/web/src/components/Onboarding/steps/OnboardingLicenseStep.vue @@ -35,21 +35,43 @@ const { activationCode, registrationState, hasActivationCode } = storeToRefs( useActivationCodeDataStore() ); -// Valid license states where the server is considered "Registered/Licensed" -const VALID_LICENSE_STATES = ['TRIAL', 'BASIC', 'STARTER', 'PLUS', 'PRO', 'UNLEASHED', 'LIFETIME']; +const VALID_LICENSE_STATES: ReadonlySet = new Set([ + 'TRIAL', + 'BASIC', + 'STARTER', + 'PLUS', + 'PRO', + 'UNLEASHED', + 'LIFETIME', +]); +const NO_LICENSE_DEVICE_STATES: ReadonlySet = new Set([ + 'ENOFLASH', + 'ENOFLASH1', + 'ENOFLASH2', + 'ENOFLASH3', + 'ENOFLASH4', + 'ENOFLASH5', + 'ENOFLASH6', + 'ENOFLASH7', +]); const lt = (key: string, fallback: string) => (te(key) ? t(key) : fallback); const effectiveState = computed(() => registrationState.value || state.value); const hasValidLicense = computed(() => { - return effectiveState.value && VALID_LICENSE_STATES.includes(effectiveState.value); + return Boolean(effectiveState.value && VALID_LICENSE_STATES.has(effectiveState.value)); +}); +const hasNoLicenseDevice = computed(() => { + return Boolean(effectiveState.value && NO_LICENSE_DEVICE_STATES.has(effectiveState.value)); }); // Computeds const statusText = computed(() => - hasValidLicense.value - ? lt('onboarding.licenseStep.status.registered', 'Registered') - : lt('onboarding.licenseStep.status.unregistered', 'Unregistered') + hasNoLicenseDevice.value + ? lt('onboarding.licenseStep.status.noLicenseDevice', 'No valid license device detected') + : hasValidLicense.value + ? lt('onboarding.licenseStep.status.registered', 'Registered') + : lt('onboarding.licenseStep.status.unregistered', 'Unregistered') ); const statusBoxTextClass = computed(() => (hasValidLicense.value ? 'text-green-500' : 'text-red-500')); diff --git a/web/src/locales/en.json b/web/src/locales/en.json index b4b4ab9f26..096b2a5916 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -346,6 +346,7 @@ "onboarding.licenseStep.title": "Unraid OS License", "onboarding.licenseStep.description": "Ready for activation. Click below to manage your license and server registration in the Unraid Account App.", "onboarding.licenseStep.status.registered": "Registered", + "onboarding.licenseStep.status.noLicenseDevice": "No valid license device detected", "onboarding.licenseStep.status.unregistered": "Unregistered", "onboarding.licenseStep.labels.status": "Status", "onboarding.licenseStep.labels.activationCode": "Activation Code", @@ -778,9 +779,9 @@ "server.state.enoconn.heading": "Cannot validate Unraid Trial key", "server.state.enoconn.humanReadable": "Trial Requires Internet Connection", "server.state.enoconn.message": "

Your Trial key requires an internet connection.

Please check Settings > Network

", - "server.state.enoflash.heading": "Cannot access your boot device", - "server.state.enoflash.humanReadable": "No Boot Device", - "server.state.enoflash.message": "

There is a physical problem accessing your boot device

", + "server.state.enoflash.heading": "No valid license device detected", + "server.state.enoflash.humanReadable": "No License Device", + "server.state.enoflash.message": "

No valid TPM or USB flash device was detected for licensing. Connect a license-capable USB flash device or enable TPM licensing, then refresh registration.

", "server.state.enokeyfile.heading": "Let's Unleash Your Hardware", "server.state.enokeyfile.humanReadable": "No Keyfile", "server.state.enokeyfile.message": "

Choose an option below, then use our Getting Started Guide to configure your array in less than 15 minutes.

",