diff --git a/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts b/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts
index 7cac35b2d4..20fc69baef 100644
--- a/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts
+++ b/web/__test__/components/Onboarding/OnboardingInternalBootStep.test.ts
@@ -61,6 +61,28 @@ vi.mock('@unraid/ui', () => ({
template:
'',
},
+ Select: {
+ props: ['modelValue', 'items', 'disabled', 'placeholder'],
+ emits: ['update:modelValue'],
+ template: `
+
+ `,
+ },
}));
vi.mock('@vue/apollo-composable', () => ({
diff --git a/web/src/components/Onboarding/steps/OnboardingCoreSettingsStep.vue b/web/src/components/Onboarding/steps/OnboardingCoreSettingsStep.vue
index fad2806ae1..b932b52704 100644
--- a/web/src/components/Onboarding/steps/OnboardingCoreSettingsStep.vue
+++ b/web/src/components/Onboarding/steps/OnboardingCoreSettingsStep.vue
@@ -356,6 +356,8 @@ const languageItems = computed(() => {
});
const isLanguageDisabled = computed(() => isLanguagesLoading.value || !!languagesQueryError.value);
+const onboardingSelectClasses =
+ 'w-full border-muted bg-bg text-highlighted data-[placeholder]:text-muted focus:ring-primary focus:ring-offset-0';
const handleSubmit = async () => {
if (serverNameValidation.value || serverDescriptionValidation.value) {
@@ -506,7 +508,7 @@ const isBusy = computed(() => isSaving.value || (props.isSavingStep ?? false));
v-model="selectedTimeZone"
:items="timeZoneItems"
:placeholder="t('onboarding.coreSettings.selectTimezonePlaceholder')"
- class="w-full"
+ :class="onboardingSelectClasses"
:disabled="isBusy"
size="lg"
/>
@@ -523,7 +525,7 @@ const isBusy = computed(() => isSaving.value || (props.isSavingStep ?? false));
:placeholder="
isLanguagesLoading ? t('common.loading') : t('onboarding.coreSettings.selectLanguage')
"
- class="w-full"
+ :class="onboardingSelectClasses"
:disabled="isBusy || isLanguageDisabled"
size="lg"
/>
@@ -582,7 +584,7 @@ const isBusy = computed(() => isSaving.value || (props.isSavingStep ?? false));
diff --git a/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue b/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
index 6549585c43..7391070979 100644
--- a/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
+++ b/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
@@ -10,7 +10,7 @@ import {
ChevronRightIcon,
ExclamationTriangleIcon,
} from '@heroicons/vue/24/solid';
-import { BrandButton } from '@unraid/ui';
+import { BrandButton, Select } from '@unraid/ui';
import { REFRESH_INTERNAL_BOOT_CONTEXT_MUTATION } from '@/components/Onboarding/graphql/refreshInternalBootContext.mutation';
import { useOnboardingDraftStore } from '@/components/Onboarding/store/onboardingDraft';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue';
@@ -20,6 +20,7 @@ import type {
OnboardingBootMode,
OnboardingInternalBootSelection,
} from '@/components/Onboarding/store/onboardingDraft';
+import type { SelectItemType } from '@unraid/ui';
import type { GetInternalBootContextQuery } from '~/composables/gql/graphql';
import { GetInternalBootContextDocument } from '~/composables/gql/graphql';
@@ -397,6 +398,24 @@ const visiblePresetOptions = computed(() => {
}));
});
+const slotCountItems = computed(() =>
+ slotOptions.value.map((option) => ({
+ value: option,
+ label: String(option),
+ }))
+);
+
+const bootSizePresetItems = computed(() => [
+ ...visiblePresetOptions.value.map((option) => ({
+ value: option.value,
+ label: option.label,
+ })),
+ {
+ value: 'custom',
+ label: t('onboarding.internalBootStep.bootSize.custom'),
+ },
+]);
+
const bootSizeMiB = computed(() => {
if (bootSizePreset.value === 'custom') {
const sizeGb = Number.parseInt(customBootSizeGb.value, 10);
@@ -497,6 +516,48 @@ const isDeviceDisabled = (deviceId: string, index: number) => {
);
};
+const getDeviceSelectItems = (index: number): SelectItemType[] =>
+ deviceOptions.value.map((option) => ({
+ value: option.value,
+ label: option.label,
+ disabled: isDeviceDisabled(option.value, index),
+ }));
+
+const toSelectString = (value: unknown): string => {
+ if (typeof value === 'string') {
+ return value;
+ }
+
+ if (typeof value === 'number' || typeof value === 'bigint') {
+ return String(value);
+ }
+
+ return '';
+};
+
+const handleSlotCountChange = (value: unknown) => {
+ const parsedValue =
+ typeof value === 'number'
+ ? value
+ : typeof value === 'bigint'
+ ? Number(value)
+ : Number.parseInt(toSelectString(value), 10);
+ if (Number.isFinite(parsedValue) && parsedValue >= 1 && parsedValue <= 2) {
+ slotCount.value = parsedValue;
+ }
+};
+
+const handleDeviceSelection = (index: number, value: unknown) => {
+ selectedDevices.value[index] = toSelectString(value);
+};
+
+const handleBootSizePresetChange = (value: unknown) => {
+ bootSizePreset.value = toSelectString(value);
+};
+
+const onboardingSelectClasses =
+ 'w-full border-muted bg-bg text-highlighted data-[placeholder]:text-muted focus:ring-primary focus:ring-offset-0';
+
const buildValidatedSelection = (): OnboardingInternalBootSelection | null => {
const normalizedPoolName = poolName.value.trim();
if (!normalizedPoolName) {
@@ -793,13 +854,13 @@ const primaryButtonText = computed(() => t('onboarding.internalBootStep.actions.
{{ t('onboarding.internalBootStep.fields.slots') }}
-
+ @update:model-value="handleSlotCountChange"
+ />
@@ -811,21 +872,14 @@ const primaryButtonText = computed(() => t('onboarding.internalBootStep.actions.
-
+ @update:model-value="handleDeviceSelection(index - 1, $event)"
+ />
@@ -834,16 +888,13 @@ const primaryButtonText = computed(() => t('onboarding.internalBootStep.actions.
{{ t('onboarding.internalBootStep.fields.bootReservedSize') }}
-
+ @update:model-value="handleBootSizePresetChange"
+ />