Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5a6b271
feat(onboarding-api): move wizard persistence to the server
Ajit-Mehrotra Mar 31, 2026
dbb7c56
feat(onboarding-web): drive the wizard from server bootstrap state
Ajit-Mehrotra Mar 31, 2026
f0e9dec
test(onboarding): cover the server-owned wizard flow
Ajit-Mehrotra Mar 31, 2026
121faa6
docs(onboarding): replace outdated wizard docs
Ajit-Mehrotra Mar 31, 2026
ee4172b
fix(onboarding-ui): show step overlay while transition saves run
Ajit-Mehrotra Mar 31, 2026
8a90223
fix(onboarding-ui): move transition loading into steps
Ajit-Mehrotra Mar 31, 2026
a5eef6c
fix(onboarding-ui): replace step content during transition saves
Ajit-Mehrotra Mar 31, 2026
8065ed9
fix(onboarding-exit): clear server draft on modal exits
Ajit-Mehrotra Mar 31, 2026
4cd79c3
fix(onboarding-core): gate core settings on live baseline
Ajit-Mehrotra Mar 31, 2026
63d92cd
revert(onboarding-core): remove baseline gating follow-up
Ajit-Mehrotra Mar 31, 2026
79789d8
feat(onboarding-boot): persist device metadata in wizard draft
Ajit-Mehrotra Mar 31, 2026
763f615
fix(onboarding-ui): tighten internal boot summary flow
Ajit-Mehrotra Apr 1, 2026
545bd81
docs(onboarding): document core settings precedence
Ajit-Mehrotra Apr 1, 2026
ddc90ad
fix(onboarding-ui): align boot mode option order
Ajit-Mehrotra Apr 1, 2026
753a347
refactor(onboarding): move wizard draft boundary to JSON
Ajit-Mehrotra Apr 1, 2026
a16e540
test(onboarding): update tracker state expectations
Ajit-Mehrotra Apr 1, 2026
b591f47
chore(codegen): refresh api graphql types
Ajit-Mehrotra Apr 2, 2026
edb6217
fix(onboarding-tracker): persist drafts from base tracker state
Ajit-Mehrotra Apr 2, 2026
30ca000
fix(onboarding-tracker): preserve base fields in overrides
Ajit-Mehrotra Apr 2, 2026
b2b4057
fix(onboarding-tracker): clamp devices to slot count
Ajit-Mehrotra Apr 2, 2026
0faf65f
fix(onboarding-web): preserve sparse draft semantics
Ajit-Mehrotra Apr 2, 2026
e84d083
fix(onboarding-web): tighten numeric draft normalization
Ajit-Mehrotra Apr 2, 2026
7bf48af
fix(onboarding-boot): ignore stale storage selections
Ajit-Mehrotra Apr 2, 2026
e6bc0da
fix(onboarding-modal): lock stepper during saves
Ajit-Mehrotra Apr 2, 2026
b23bcde
docs(onboarding): update internal boot draft example
Ajit-Mehrotra Apr 2, 2026
ba94b8c
docs(onboarding): document sparse wizard drafts
Ajit-Mehrotra Apr 2, 2026
7748999
fix(onboarding-i18n): localize boot config warnings
Ajit-Mehrotra Apr 2, 2026
f7bdd7b
chore(onboarding-api): remove unused model imports
Ajit-Mehrotra Apr 2, 2026
f4f661f
test(onboarding-web): use nextTick for pool mode reactivity
Ajit-Mehrotra Apr 2, 2026
9681973
refactor(onboarding): simplify step state ownership
Ajit-Mehrotra Apr 3, 2026
c2e0618
chore: bump api version to 4.32.0 and update generated index file for…
Ajit-Mehrotra Apr 3, 2026
3696544
refactor: remove input parameter and reason enum from closeOnboarding…
Ajit-Mehrotra Apr 3, 2026
1211f26
refactor: remove CloseOnboarding mutation and associated types from G…
Ajit-Mehrotra Apr 3, 2026
fabb64a
refactor(onboarding-web): collapse step blocking state
Ajit-Mehrotra Apr 3, 2026
27a5f76
fix(onboarding-web): update step save failure copy
Ajit-Mehrotra Apr 3, 2026
d0c032c
fix(onboarding-web): normalize standalone boot alert
Ajit-Mehrotra Apr 3, 2026
065b609
refactor(onboarding): unify modal completion flow
Ajit-Mehrotra Apr 9, 2026
dde30cb
fix(onboarding-web): preserve selected timezone snapshot
Ajit-Mehrotra Apr 10, 2026
722e415
fix(onboarding): keep activation step in session
Ajit-Mehrotra Apr 10, 2026
0142e2c
fix(onboarding): clarify internal boot licensing
Ajit-Mehrotra Apr 10, 2026
71f3858
fix(onboarding): refine internal boot licensing copy
Ajit-Mehrotra Apr 10, 2026
3736269
fix(onboarding): tune internal boot reboot copy
Ajit-Mehrotra Apr 10, 2026
9fc0917
style: update InternalBootConfirmDialog alert color to neutral and fi…
Ajit-Mehrotra Apr 10, 2026
6746900
fix(onboarding): reuse confirm dialog in standalone boot flow
Ajit-Mehrotra Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/dev/configs/api.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"version": "4.29.2",
"version": "4.32.0",
"extraOrigins": [],
"sandbox": false,
"ssoSubIds": [],
"plugins": [
"unraid-api-plugin-connect"
]
}
}
57 changes: 54 additions & 3 deletions api/generated-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,29 @@ type OnboardingState {
activationRequired: Boolean!
}

type OnboardingWizardInternalBootState {
applyAttempted: Boolean!
applySucceeded: Boolean!
}

type OnboardingWizard {
currentStepId: OnboardingWizardStepId
visibleStepIds: [OnboardingWizardStepId!]!
draft: JSON!
internalBootState: OnboardingWizardInternalBootState!
}

"""Server-provided onboarding wizard step identifiers"""
enum OnboardingWizardStepId {
OVERVIEW
CONFIGURE_SETTINGS
CONFIGURE_BOOT
ADD_PLUGINS
ACTIVATE_LICENSE
SUMMARY
NEXT_STEPS
}

"""Onboarding completion state and context"""
type Onboarding {
"""
Expand All @@ -1051,6 +1074,9 @@ type Onboarding {

"""Runtime onboarding state values used by the onboarding flow"""
onboardingState: OnboardingState!

"""Server-owned onboarding wizard state used by the web onboarding modal"""
wizard: OnboardingWizard!
}

"""
Expand Down Expand Up @@ -1210,6 +1236,16 @@ type ArrayMutations {
input ArrayStateInput {
"""Array state"""
desiredState: ArrayStateInputState!

"""
Optional password used to unlock encrypted array disks when starting the array
"""
decryptionPassword: String

"""
Optional keyfile contents used to unlock encrypted array disks when starting the array. Accepts a data URL or raw base64 payload.
"""
decryptionKeyfile: String
Comment on lines +1240 to +1248
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Document the unlock-input constraints in the schema.

The backend already rejects requests that send both fields together and only accepts printable-ASCII passwords, but neither rule is exposed here. Please reflect those constraints in the source field descriptions so clients do not discover them only at runtime. See api/src/unraid-api/graph/resolvers/array/array.service.ts:34-106 and api/src/unraid-api/graph/resolvers/array/array.service.ts:129-180.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/generated-schema.graphql` around lines 1292 - 1300, Update the GraphQL
field descriptions for decryptionPassword and decryptionKeyfile to document the
backend unlocking constraints: state that decryptionPassword must contain only
printable‑ASCII characters (0x20–0x7E) and that decryptionPassword and
decryptionKeyfile are mutually exclusive (clients must not send both), and keep
the existing note that decryptionKeyfile accepts a data URL or raw base64;
mirror the exact validation semantics implemented in the ArrayService/unlock
resolver (see array.service.ts unlock handling) so the schema descriptions match
the backend behavior.

}

enum ArrayStateInputState {
Expand Down Expand Up @@ -1407,9 +1443,6 @@ type OnboardingMutations {
"""Force the onboarding modal open"""
openOnboarding: Onboarding!

"""Close the onboarding modal"""
closeOnboarding: Onboarding!

"""Temporarily bypass onboarding in API memory"""
bypassOnboarding: Onboarding!

Expand All @@ -1422,6 +1455,9 @@ type OnboardingMutations {
"""Clear onboarding override state and reload from disk"""
clearOnboardingOverride: Onboarding!

"""Persist server-owned onboarding wizard draft state"""
saveOnboardingDraft(input: SaveOnboardingDraftInput!): Boolean!

"""Create and configure internal boot pool via emcmd operations"""
createInternalBootPool(input: CreateInternalBootPoolInput!): OnboardingInternalBootResult!

Expand Down Expand Up @@ -1505,6 +1541,21 @@ input PartnerInfoOverrideInput {
branding: BrandingConfigInput
}

input SaveOnboardingDraftInput {
draft: JSON
navigation: OnboardingWizardNavigationInput
internalBootState: OnboardingWizardInternalBootStateInput
}

input OnboardingWizardNavigationInput {
currentStepId: OnboardingWizardStepId
}

input OnboardingWizardInternalBootStateInput {
applyAttempted: Boolean
applySucceeded: Boolean
}

"""Input for creating an internal boot pool during onboarding"""
input CreateInternalBootPoolInput {
poolName: String!
Expand Down
65 changes: 63 additions & 2 deletions api/src/unraid-api/cli/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ export enum ArrayState {
}

export type ArrayStateInput = {
/** Optional keyfile contents used to unlock encrypted array disks when starting the array. Accepts a data URL or raw base64 payload. */
decryptionKeyfile?: InputMaybe<Scalars['String']['input']>;
/** Optional password used to unlock encrypted array disks when starting the array */
decryptionPassword?: InputMaybe<Scalars['String']['input']>;
/** Array state */
desiredState: ArrayStateInputState;
};
Expand Down Expand Up @@ -1976,6 +1980,8 @@ export type Onboarding = {
shouldOpen: Scalars['Boolean']['output'];
/** The current onboarding status (INCOMPLETE, UPGRADE, DOWNGRADE, or COMPLETED) */
status: OnboardingStatus;
/** Server-owned onboarding wizard state used by the web onboarding modal */
wizard: OnboardingWizard;
};

/** Current onboarding context for configuring internal boot */
Expand All @@ -1985,12 +1991,21 @@ export type OnboardingInternalBootContext = {
assignableDisks: Array<Disk>;
bootEligible?: Maybe<Scalars['Boolean']['output']>;
bootedFromFlashWithInternalBootSetup: Scalars['Boolean']['output'];
driveWarnings: Array<OnboardingInternalBootDriveWarning>;
enableBootTransfer?: Maybe<Scalars['String']['output']>;
poolNames: Array<Scalars['String']['output']>;
reservedNames: Array<Scalars['String']['output']>;
shareNames: Array<Scalars['String']['output']>;
};

/** Warning metadata for an assignable internal boot drive */
export type OnboardingInternalBootDriveWarning = {
__typename?: 'OnboardingInternalBootDriveWarning';
device: Scalars['String']['output'];
diskId: Scalars['String']['output'];
warnings: Array<Scalars['String']['output']>;
};

/** Result of attempting internal boot pool setup */
export type OnboardingInternalBootResult = {
__typename?: 'OnboardingInternalBootResult';
Expand All @@ -2006,8 +2021,6 @@ export type OnboardingMutations = {
bypassOnboarding: Onboarding;
/** Clear onboarding override state and reload from disk */
clearOnboardingOverride: Onboarding;
/** Close the onboarding modal */
closeOnboarding: Onboarding;
/** Mark onboarding as completed */
completeOnboarding: Onboarding;
/** Create and configure internal boot pool via emcmd operations */
Expand All @@ -2020,6 +2033,8 @@ export type OnboardingMutations = {
resetOnboarding: Onboarding;
/** Clear the temporary onboarding bypass */
resumeOnboarding: Onboarding;
/** Persist server-owned onboarding wizard draft state */
saveOnboardingDraft: Scalars['Boolean']['output'];
/** Override onboarding state for testing (in-memory only) */
setOnboardingOverride: Onboarding;
};
Expand All @@ -2031,6 +2046,12 @@ export type OnboardingMutationsCreateInternalBootPoolArgs = {
};


/** Onboarding related mutations */
export type OnboardingMutationsSaveOnboardingDraftArgs = {
input: SaveOnboardingDraftInput;
};


/** Onboarding related mutations */
export type OnboardingMutationsSetOnboardingOverrideArgs = {
input: OnboardingOverrideInput;
Expand Down Expand Up @@ -2072,6 +2093,40 @@ export enum OnboardingStatus {
UPGRADE = 'UPGRADE'
}

export type OnboardingWizard = {
__typename?: 'OnboardingWizard';
currentStepId?: Maybe<OnboardingWizardStepId>;
draft: Scalars['JSON']['output'];
internalBootState: OnboardingWizardInternalBootState;
visibleStepIds: Array<OnboardingWizardStepId>;
};

export type OnboardingWizardInternalBootState = {
__typename?: 'OnboardingWizardInternalBootState';
applyAttempted: Scalars['Boolean']['output'];
applySucceeded: Scalars['Boolean']['output'];
};

export type OnboardingWizardInternalBootStateInput = {
applyAttempted?: InputMaybe<Scalars['Boolean']['input']>;
applySucceeded?: InputMaybe<Scalars['Boolean']['input']>;
};

export type OnboardingWizardNavigationInput = {
currentStepId?: InputMaybe<OnboardingWizardStepId>;
};

/** Server-provided onboarding wizard step identifiers */
export enum OnboardingWizardStepId {
ACTIVATE_LICENSE = 'ACTIVATE_LICENSE',
ADD_PLUGINS = 'ADD_PLUGINS',
CONFIGURE_BOOT = 'CONFIGURE_BOOT',
CONFIGURE_SETTINGS = 'CONFIGURE_SETTINGS',
NEXT_STEPS = 'NEXT_STEPS',
OVERVIEW = 'OVERVIEW',
SUMMARY = 'SUMMARY'
}

export type Owner = {
__typename?: 'Owner';
avatar: Scalars['String']['output'];
Expand Down Expand Up @@ -2594,6 +2649,12 @@ export enum Role {
VIEWER = 'VIEWER'
}

export type SaveOnboardingDraftInput = {
draft?: InputMaybe<Scalars['JSON']['input']>;
internalBootState?: InputMaybe<OnboardingWizardInternalBootStateInput>;
navigation?: InputMaybe<OnboardingWizardNavigationInput>;
};

export type SensorConfig = {
__typename?: 'SensorConfig';
enabled?: Maybe<Scalars['Boolean']['output']>;
Expand Down
18 changes: 18 additions & 0 deletions api/src/unraid-api/config/api-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ describe('OnboardingTracker', () => {
completed: false,
completedAtVersion: undefined,
forceOpen: false,
draft: {},
navigation: {},
internalBootState: {
applyAttempted: false,
applySucceeded: false,
},
},
});
});
Expand Down Expand Up @@ -196,6 +202,12 @@ describe('OnboardingTracker', () => {
completed: true,
completedAtVersion: '7.1.0',
forceOpen: false,
draft: {},
navigation: {},
internalBootState: {
applyAttempted: false,
applySucceeded: false,
},
},
});
});
Expand Down Expand Up @@ -288,6 +300,12 @@ describe('OnboardingTracker', () => {
completed: true,
completedAtVersion: '6.12.0',
forceOpen: false,
draft: {},
navigation: {},
internalBootState: {
applyAttempted: false,
applySucceeded: false,
},
},
});
});
Expand Down
76 changes: 74 additions & 2 deletions api/src/unraid-api/config/onboarding-tracker.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,72 @@
export const ONBOARDING_STEP_IDS = [
'OVERVIEW',
'CONFIGURE_SETTINGS',
'CONFIGURE_BOOT',
'ADD_PLUGINS',
'ACTIVATE_LICENSE',
'SUMMARY',
'NEXT_STEPS',
] as const;

export type OnboardingStepId = (typeof ONBOARDING_STEP_IDS)[number];

export type OnboardingPoolMode = 'dedicated' | 'hybrid';

export type OnboardingBootMode = 'usb' | 'storage';

export type OnboardingInternalBootDevice = {
id: string;
sizeBytes: number;
deviceName: string;
};

export type OnboardingInternalBootSelection = {
poolName?: string;
slotCount?: number;
devices?: OnboardingInternalBootDevice[];
bootSizeMiB?: number;
updateBios?: boolean;
poolMode?: OnboardingPoolMode;
};

export type OnboardingCoreSettingsDraft = {
serverName?: string;
serverDescription?: string;
timeZone?: string;
theme?: string;
language?: string;
useSsh?: boolean;
};

export type OnboardingPluginsDraft = {
selectedIds?: string[];
};

export type OnboardingInternalBootDraft = {
bootMode?: OnboardingBootMode;
skipped?: boolean;
selection?: OnboardingInternalBootSelection | null;
};

export type OnboardingDraft = {
activationStepIncluded?: boolean;
coreSettings?: OnboardingCoreSettingsDraft;
plugins?: OnboardingPluginsDraft;
internalBoot?: OnboardingInternalBootDraft;
};

export type OnboardingNavigationState = {
currentStepId?: OnboardingStepId;
};

export type OnboardingInternalBootState = {
applyAttempted?: boolean;
applySucceeded?: boolean;
};

/**
* Simplified onboarding tracker state.
* Tracks whether onboarding has been completed and at which version.
* Durable onboarding tracker state.
* Tracks onboarding completion plus the server-owned wizard draft.
*/
export type TrackerState = {
/** Whether the onboarding flow has been completed */
Expand All @@ -9,4 +75,10 @@ export type TrackerState = {
completedAtVersion?: string;
/** Whether onboarding has been explicitly forced open */
forceOpen?: boolean;
/** Durable server-owned wizard draft */
draft?: OnboardingDraft;
/** Durable navigation state for onboarding resume */
navigation?: OnboardingNavigationState;
/** Operational internal boot state that is not user-entered draft data */
internalBootState?: OnboardingInternalBootState;
};
Loading
Loading