Skip to content

Commit 1b42c17

Browse files
committed
feat(upgrade): implement upgrade onboarding system for Unraid OS
- Added `UpgradeInfo` type to track OS version changes, including current and previous versions. - Enhanced `InfoVersions` and GraphQL resolvers to expose upgrade information. - Introduced `upgradeOnboarding` store to manage visibility and steps for users upgrading their OS. - Updated `ActivationModal` to handle both fresh installs and upgrade onboarding, displaying relevant steps based on the user's upgrade path. - Created configuration for defining upgrade steps and conditions in `releaseConfigs.ts`. - Added new components and logic to facilitate the upgrade onboarding experience, improving user guidance during OS upgrades. This update streamlines the upgrade process, ensuring users receive contextual onboarding steps when upgrading their Unraid OS, enhancing overall user experience.
1 parent b9d09d2 commit 1b42c17

25 files changed

Lines changed: 745 additions & 299 deletions

api/dev/configs/api.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"ssoSubIds": [],
66
"plugins": [
77
"unraid-api-plugin-connect"
8-
]
8+
],
9+
"lastSeenOsVersion": "6.11.2"
910
}

api/generated-schema.graphql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,6 +1670,17 @@ type PackageVersions {
16701670
docker: String
16711671
}
16721672

1673+
type UpgradeInfo {
1674+
"""Whether the OS version has changed since last boot"""
1675+
isUpgrade: Boolean!
1676+
1677+
"""Previous OS version before upgrade"""
1678+
previousVersion: String
1679+
1680+
"""Current OS version"""
1681+
currentVersion: String
1682+
}
1683+
16731684
type InfoVersions implements Node {
16741685
id: PrefixedID!
16751686

@@ -1678,6 +1689,9 @@ type InfoVersions implements Node {
16781689

16791690
"""Software package versions"""
16801691
packages: PackageVersions
1692+
1693+
"""OS upgrade information"""
1694+
upgrade: UpgradeInfo!
16811695
}
16821696

16831697
type Info implements Node {

api/src/unraid-api/cli/generated/graphql.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,8 @@ export type InfoVersions = Node & {
10861086
id: Scalars['PrefixedID']['output'];
10871087
/** Software package versions */
10881088
packages?: Maybe<PackageVersions>;
1089+
/** OS upgrade information */
1090+
upgrade: UpgradeInfo;
10891091
};
10901092

10911093
export type InitiateFlashBackupInput = {
@@ -1249,6 +1251,8 @@ export type Mutation = {
12491251
unreadNotification: Notification;
12501252
updateApiSettings: ConnectSettingsValues;
12511253
updateSettings: UpdateSettingsResponse;
1254+
/** Update system time configuration */
1255+
updateSystemTime: SystemTime;
12521256
vm: VmMutations;
12531257
};
12541258

@@ -1362,6 +1366,11 @@ export type MutationUpdateSettingsArgs = {
13621366
input: Scalars['JSON']['input'];
13631367
};
13641368

1369+
1370+
export type MutationUpdateSystemTimeArgs = {
1371+
input: UpdateSystemTimeInput;
1372+
};
1373+
13651374
export type Network = Node & {
13661375
__typename?: 'Network';
13671376
accessUrls?: Maybe<Array<AccessUrl>>;
@@ -1705,6 +1714,8 @@ export type Query = {
17051714
services: Array<Service>;
17061715
settings: Settings;
17071716
shares: Array<Share>;
1717+
/** Retrieve current system time configuration */
1718+
systemTime: SystemTime;
17081719
upsConfiguration: UpsConfiguration;
17091720
upsDeviceById?: Maybe<UpsDevice>;
17101721
upsDevices: Array<UpsDevice>;
@@ -2062,6 +2073,19 @@ export type SubscriptionLogFileArgs = {
20622073
path: Scalars['String']['input'];
20632074
};
20642075

2076+
/** System time configuration and current status */
2077+
export type SystemTime = {
2078+
__typename?: 'SystemTime';
2079+
/** Current server time in ISO-8601 format (UTC) */
2080+
currentTime: Scalars['String']['output'];
2081+
/** Configured NTP servers (empty strings indicate unused slots) */
2082+
ntpServers: Array<Scalars['String']['output']>;
2083+
/** IANA timezone identifier currently in use */
2084+
timeZone: Scalars['String']['output'];
2085+
/** Whether NTP/PTP time synchronization is enabled */
2086+
useNtp: Scalars['Boolean']['output'];
2087+
};
2088+
20652089
/** Temperature unit */
20662090
export enum Temperature {
20672091
CELSIUS = 'CELSIUS',
@@ -2282,6 +2306,27 @@ export enum UpdateStatus {
22822306
UP_TO_DATE = 'UP_TO_DATE'
22832307
}
22842308

2309+
export type UpdateSystemTimeInput = {
2310+
/** Manual date/time to apply when disabling NTP, expected format YYYY-MM-DD HH:mm:ss */
2311+
manualDateTime?: InputMaybe<Scalars['String']['input']>;
2312+
/** Ordered list of up to four NTP servers. Supply empty strings to clear positions. */
2313+
ntpServers?: InputMaybe<Array<Scalars['String']['input']>>;
2314+
/** New IANA timezone identifier to apply */
2315+
timeZone?: InputMaybe<Scalars['String']['input']>;
2316+
/** Enable or disable NTP-based synchronization */
2317+
useNtp?: InputMaybe<Scalars['Boolean']['input']>;
2318+
};
2319+
2320+
export type UpgradeInfo = {
2321+
__typename?: 'UpgradeInfo';
2322+
/** Current OS version */
2323+
currentVersion?: Maybe<Scalars['String']['output']>;
2324+
/** Whether the OS version has changed since last boot */
2325+
isUpgrade: Scalars['Boolean']['output'];
2326+
/** Previous OS version before upgrade */
2327+
previousVersion?: Maybe<Scalars['String']['output']>;
2328+
};
2329+
22852330
export type Uptime = {
22862331
__typename?: 'Uptime';
22872332
timestamp?: Maybe<Scalars['String']['output']>;

api/src/unraid-api/config/api-config.module.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const createDefaultConfig = (): ApiConfig => ({
1616
sandbox: false,
1717
ssoSubIds: [],
1818
plugins: [],
19+
lastSeenOsVersion: undefined,
1920
});
2021

2122
/**
@@ -79,6 +80,20 @@ export class ApiConfigPersistence
7980

8081
async onApplicationBootstrap() {
8182
this.configService.set('api.version', API_VERSION);
83+
await this.trackOsVersionUpgrade();
84+
}
85+
86+
private async trackOsVersionUpgrade() {
87+
const currentOsVersion = this.configService.get<string>('store.emhttp.var.version');
88+
const lastSeenOsVersion = this.configService.get<string>('api.lastSeenOsVersion');
89+
90+
if (currentOsVersion && currentOsVersion !== lastSeenOsVersion) {
91+
this.configService.set('api.lastSeenOsVersion', currentOsVersion);
92+
const currentConfig = this.configService.get<ApiConfig>('api');
93+
if (currentConfig) {
94+
await this.persist(currentConfig);
95+
}
96+
}
8297
}
8398

8499
async migrateConfig(): Promise<ApiConfig> {

api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,26 @@ export class PackageVersions {
4141
docker?: string;
4242
}
4343

44+
@ObjectType()
45+
export class UpgradeInfo {
46+
@Field(() => Boolean, { description: 'Whether the OS version has changed since last boot' })
47+
isUpgrade!: boolean;
48+
49+
@Field(() => String, { nullable: true, description: 'Previous OS version before upgrade' })
50+
previousVersion?: string;
51+
52+
@Field(() => String, { nullable: true, description: 'Current OS version' })
53+
currentVersion?: string;
54+
}
55+
4456
@ObjectType({ implements: () => Node })
4557
export class InfoVersions extends Node {
4658
@Field(() => CoreVersions, { description: 'Core system versions' })
4759
core!: CoreVersions;
4860

4961
@Field(() => PackageVersions, { nullable: true, description: 'Software package versions' })
5062
packages?: PackageVersions;
63+
64+
@Field(() => UpgradeInfo, { description: 'OS upgrade information' })
65+
upgrade!: UpgradeInfo;
5166
}

api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CoreVersions,
88
InfoVersions,
99
PackageVersions,
10+
UpgradeInfo,
1011
} from '@app/unraid-api/graph/resolvers/info/versions/versions.model.js';
1112

1213
@Resolver(() => InfoVersions)
@@ -45,4 +46,20 @@ export class VersionsResolver {
4546
return null;
4647
}
4748
}
49+
50+
@ResolveField(() => UpgradeInfo)
51+
upgrade(): UpgradeInfo {
52+
const currentVersion = this.configService.get<string>('store.emhttp.var.version');
53+
const lastSeenVersion = this.configService.get<string>('api.lastSeenOsVersion');
54+
55+
const isUpgrade = Boolean(
56+
lastSeenVersion && currentVersion && lastSeenVersion !== currentVersion
57+
);
58+
59+
return {
60+
isUpgrade,
61+
previousVersion: isUpgrade ? lastSeenVersion : undefined,
62+
currentVersion: currentVersion || undefined,
63+
};
64+
}
4865
}

packages/unraid-shared/src/services/api-config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@ export class ApiConfig {
2626
@IsArray()
2727
@IsString({ each: true })
2828
plugins!: string[];
29+
30+
@Field({ nullable: true })
31+
@IsOptional()
32+
@IsString()
33+
lastSeenOsVersion?: string;
2934
}

web/__test__/components/Activation/ActivationSteps.test.ts

Lines changed: 0 additions & 113 deletions
This file was deleted.

web/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ declare module 'vue' {
126126
UptimeExpire: typeof import('./src/components/UserProfile/UptimeExpire.vue')['default']
127127
USelectMenu: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.0.0-alpha.0_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@_655bac6707ae017754653173419b3890/node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
128128
'UserProfile.standalone': typeof import('./src/components/UserProfile.standalone.vue')['default']
129+
UStepper: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.0.0-alpha.0_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@_655bac6707ae017754653173419b3890/node_modules/@nuxt/ui/dist/runtime/components/Stepper.vue')['default']
129130
USwitch: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.0.0-alpha.0_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@_655bac6707ae017754653173419b3890/node_modules/@nuxt/ui/dist/runtime/components/Switch.vue')['default']
130131
UTabs: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.0.0-alpha.0_@babel+parser@7.28.4_@netlify+blobs@9.1.2_change-case@5.4.4_db0@_655bac6707ae017754653173419b3890/node_modules/@nuxt/ui/dist/runtime/components/Tabs.vue')['default']
131132
'WanIpCheck.standalone': typeof import('./src/components/WanIpCheck.standalone.vue')['default']

0 commit comments

Comments
 (0)