From 8ddf7adaeff90bb18c8b792f0c6d505dd9dc9ad9 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Wed, 20 Aug 2025 13:22:14 -0400 Subject: [PATCH 1/6] feat(api): restructure versioning information in GraphQL schema - Introduced new CoreVersions and PackageVersions types to encapsulate core system and software package version details. - Updated InfoVersions type to include references to CoreVersions and PackageVersions. - Modified the versions resolver to return structured version information. - Added a new query for fetching version information in the frontend. - Updated the HeaderOsVersion component to display the new version information. - Created a ReleaseNotesModal component for viewing OS release notes. --- api/dev/configs/api.json | 2 +- api/generated-schema.graphql | 21 ++- api/src/unraid-api/cli/generated/graphql.ts | 2 + .../graph/resolvers/info/info.module.ts | 6 +- .../info/info.resolver.integration.spec.ts | 7 +- .../graph/resolvers/info/info.resolver.ts | 2 +- .../info/versions/core-versions.resolver.ts | 14 ++ .../info/versions/get-api-version.ts | 20 +++ .../resolvers/info/versions/versions.model.ts | 23 ++- .../info/versions/versions.resolver.ts | 63 ++++++++ .../info/versions/versions.service.ts | 12 +- web/components/HeaderOsVersion.ce.vue | 94 ++++++++++-- web/components/ReleaseNotesModal.vue | 93 ++++++++++++ web/components/UserProfile/versions.query.ts | 20 +++ web/composables/api/use-notifications.ts | 2 +- web/composables/gql/gql.ts | 6 + web/composables/gql/graphql.ts | 140 ++++++++++-------- 17 files changed, 424 insertions(+), 103 deletions(-) create mode 100644 api/src/unraid-api/graph/resolvers/info/versions/core-versions.resolver.ts create mode 100644 api/src/unraid-api/graph/resolvers/info/versions/get-api-version.ts create mode 100644 api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts create mode 100644 web/components/ReleaseNotesModal.vue create mode 100644 web/components/UserProfile/versions.query.ts diff --git a/api/dev/configs/api.json b/api/dev/configs/api.json index df4db6b519..de99ec62a2 100644 --- a/api/dev/configs/api.json +++ b/api/dev/configs/api.json @@ -1,5 +1,5 @@ { - "version": "4.13.1", + "version": "4.14.0", "extraOrigins": [], "sandbox": true, "ssoSubIds": [], diff --git a/api/generated-schema.graphql b/api/generated-schema.graphql index 25631cb056..1bdd2a6b6d 100644 --- a/api/generated-schema.graphql +++ b/api/generated-schema.graphql @@ -1501,12 +1501,18 @@ type InfoBaseboard implements Node { memSlots: Float } -type InfoVersions implements Node { - id: PrefixedID! +type CoreVersions { + """Unraid version""" + unraid: String + + """Unraid API version""" + api: String """Kernel version""" kernel: String +} +type PackageVersions { """OpenSSL version""" openssl: String @@ -1590,9 +1596,16 @@ type InfoVersions implements Node { """Docker version""" docker: String +} - """Unraid version""" - unraid: String +type InfoVersions implements Node { + id: PrefixedID! + + """Core system versions""" + core: CoreVersions! + + """Software package versions""" + packages: PackageVersions! } type Info implements Node { diff --git a/api/src/unraid-api/cli/generated/graphql.ts b/api/src/unraid-api/cli/generated/graphql.ts index d0b08eeb9f..9f367a3da9 100644 --- a/api/src/unraid-api/cli/generated/graphql.ts +++ b/api/src/unraid-api/cli/generated/graphql.ts @@ -1041,6 +1041,8 @@ export type InfoVersions = Node & { __typename?: 'InfoVersions'; /** Apache version */ apache?: Maybe; + /** Unraid API version */ + api?: Maybe; /** Docker version */ docker?: Maybe; /** gcc version */ diff --git a/api/src/unraid-api/graph/resolvers/info/info.module.ts b/api/src/unraid-api/graph/resolvers/info/info.module.ts index a28a472b5f..c9684061fb 100644 --- a/api/src/unraid-api/graph/resolvers/info/info.module.ts +++ b/api/src/unraid-api/graph/resolvers/info/info.module.ts @@ -8,6 +8,8 @@ import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/dis import { InfoResolver } from '@app/unraid-api/graph/resolvers/info/info.resolver.js'; import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js'; import { OsService } from '@app/unraid-api/graph/resolvers/info/os/os.service.js'; +import { CoreVersionsResolver } from '@app/unraid-api/graph/resolvers/info/versions/core-versions.resolver.js'; +import { VersionsResolver } from '@app/unraid-api/graph/resolvers/info/versions/versions.resolver.js'; import { VersionsService } from '@app/unraid-api/graph/resolvers/info/versions/versions.service.js'; import { ServicesModule } from '@app/unraid-api/graph/services/services.module.js'; @@ -19,6 +21,8 @@ import { ServicesModule } from '@app/unraid-api/graph/services/services.module.j // Sub-resolvers DevicesResolver, + VersionsResolver, + CoreVersionsResolver, // Services CpuService, @@ -28,6 +32,6 @@ import { ServicesModule } from '@app/unraid-api/graph/services/services.module.j VersionsService, DisplayService, ], - exports: [InfoResolver, DevicesResolver, DisplayService], + exports: [InfoResolver, DevicesResolver, VersionsResolver, CoreVersionsResolver, DisplayService], }) export class InfoModule {} diff --git a/api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts b/api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts index 2745cfae87..eb21908e9b 100644 --- a/api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts +++ b/api/src/unraid-api/graph/resolvers/info/info.resolver.integration.spec.ts @@ -169,12 +169,7 @@ describe('InfoResolver Integration Tests', () => { const result = await infoResolver.versions(); expect(result).toHaveProperty('id', 'info/versions'); - expect(result).toHaveProperty('unraid'); - expect(result).toHaveProperty('kernel'); - expect(result).toHaveProperty('node'); - expect(result).toHaveProperty('npm'); - // Verify unraid version from mock - expect(result.unraid).toBe('6.12.0'); + // Versions now returns a stub object, with actual data resolved via field resolvers }); }); diff --git a/api/src/unraid-api/graph/resolvers/info/info.resolver.ts b/api/src/unraid-api/graph/resolvers/info/info.resolver.ts index c021800087..b029a24c0d 100644 --- a/api/src/unraid-api/graph/resolvers/info/info.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/info/info.resolver.ts @@ -94,7 +94,7 @@ export class InfoResolver { } @ResolveField(() => InfoVersions) - public async versions(): Promise { + public versions(): Partial { return this.versionsService.generateVersions(); } } diff --git a/api/src/unraid-api/graph/resolvers/info/versions/core-versions.resolver.ts b/api/src/unraid-api/graph/resolvers/info/versions/core-versions.resolver.ts new file mode 100644 index 0000000000..6ee8b24797 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/info/versions/core-versions.resolver.ts @@ -0,0 +1,14 @@ +import { ResolveField, Resolver } from '@nestjs/graphql'; + +import { versions } from 'systeminformation'; + +import { CoreVersions } from '@app/unraid-api/graph/resolvers/info/versions/versions.model.js'; + +@Resolver(() => CoreVersions) +export class CoreVersionsResolver { + @ResolveField(() => String, { nullable: true }) + async kernel(): Promise { + const softwareVersions = await versions(); + return softwareVersions.kernel; + } +} diff --git a/api/src/unraid-api/graph/resolvers/info/versions/get-api-version.ts b/api/src/unraid-api/graph/resolvers/info/versions/get-api-version.ts new file mode 100644 index 0000000000..f6c6d0eebd --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/info/versions/get-api-version.ts @@ -0,0 +1,20 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +let cachedVersion: string | undefined; + +export function getApiVersion(): string { + if (cachedVersion) { + return cachedVersion; + } + + try { + const packagePath = join(process.cwd(), 'package.json'); + const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8')); + cachedVersion = packageJson.version || 'unknown'; + return cachedVersion; + } catch (error) { + console.error('Failed to read API version from package.json:', error); + return 'unknown'; + } +} diff --git a/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts b/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts index 37ad2003ac..c7983bec1e 100644 --- a/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts +++ b/api/src/unraid-api/graph/resolvers/info/versions/versions.model.ts @@ -2,11 +2,20 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { Node } from '@unraid/shared/graphql.model.js'; -@ObjectType({ implements: () => Node }) -export class InfoVersions extends Node { +@ObjectType() +export class CoreVersions { + @Field(() => String, { nullable: true, description: 'Unraid version' }) + unraid?: string; + + @Field(() => String, { nullable: true, description: 'Unraid API version' }) + api?: string; + @Field(() => String, { nullable: true, description: 'Kernel version' }) kernel?: string; +} +@ObjectType() +export class PackageVersions { @Field(() => String, { nullable: true, description: 'OpenSSL version' }) openssl?: string; @@ -90,7 +99,13 @@ export class InfoVersions extends Node { @Field(() => String, { nullable: true, description: 'Docker version' }) docker?: string; +} - @Field(() => String, { nullable: true, description: 'Unraid version' }) - unraid?: string; +@ObjectType({ implements: () => Node }) +export class InfoVersions extends Node { + @Field(() => CoreVersions, { description: 'Core system versions' }) + core!: CoreVersions; + + @Field(() => PackageVersions, { description: 'Software package versions' }) + packages!: PackageVersions; } diff --git a/api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts b/api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts new file mode 100644 index 0000000000..b50f8ad2e5 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/info/versions/versions.resolver.ts @@ -0,0 +1,63 @@ +import { ConfigService } from '@nestjs/config'; +import { ResolveField, Resolver } from '@nestjs/graphql'; + +import { versions } from 'systeminformation'; + +import { + CoreVersions, + InfoVersions, + PackageVersions, +} from '@app/unraid-api/graph/resolvers/info/versions/versions.model.js'; + +@Resolver(() => InfoVersions) +export class VersionsResolver { + constructor(private readonly configService: ConfigService) {} + + @ResolveField(() => CoreVersions) + core(): CoreVersions { + const unraid = this.configService.get('store.emhttp.var.version') || 'unknown'; + const api = this.configService.get('api.version') || 'unknown'; + + return { + unraid, + api, + kernel: undefined, // Will be resolved separately if requested + }; + } + + @ResolveField(() => PackageVersions) + async packages(): Promise { + const softwareVersions = await versions(); + + return { + openssl: softwareVersions.openssl, + systemOpenssl: softwareVersions.systemOpenssl, + node: softwareVersions.node, + v8: softwareVersions.v8, + npm: softwareVersions.npm, + yarn: softwareVersions.yarn, + pm2: softwareVersions.pm2, + gulp: softwareVersions.gulp, + grunt: softwareVersions.grunt, + git: softwareVersions.git, + tsc: softwareVersions.tsc, + mysql: softwareVersions.mysql, + redis: softwareVersions.redis, + mongodb: softwareVersions.mongodb, + apache: (softwareVersions as any).apache, + nginx: softwareVersions.nginx, + php: softwareVersions.php, + postfix: softwareVersions.postfix, + postgresql: softwareVersions.postgresql, + perl: softwareVersions.perl, + python: softwareVersions.python, + python3: softwareVersions.python3, + pip: softwareVersions.pip, + pip3: softwareVersions.pip3, + java: softwareVersions.java, + gcc: softwareVersions.gcc, + virtualbox: softwareVersions.virtualbox, + docker: softwareVersions.docker, + }; + } +} diff --git a/api/src/unraid-api/graph/resolvers/info/versions/versions.service.ts b/api/src/unraid-api/graph/resolvers/info/versions/versions.service.ts index 42c399c19a..c1cdce42f6 100644 --- a/api/src/unraid-api/graph/resolvers/info/versions/versions.service.ts +++ b/api/src/unraid-api/graph/resolvers/info/versions/versions.service.ts @@ -1,22 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; - -import { versions } from 'systeminformation'; import { InfoVersions } from '@app/unraid-api/graph/resolvers/info/versions/versions.model.js'; @Injectable() export class VersionsService { - constructor(private readonly configService: ConfigService) {} - - async generateVersions(): Promise { - const unraid = this.configService.get('store.emhttp.var.version') || 'unknown'; - const softwareVersions = await versions(); - + generateVersions(): Partial { return { id: 'info/versions', - unraid, - ...softwareVersions, }; } } diff --git a/web/components/HeaderOsVersion.ce.vue b/web/components/HeaderOsVersion.ce.vue index bb76d4a597..165ffd8e28 100644 --- a/web/components/HeaderOsVersion.ce.vue +++ b/web/components/HeaderOsVersion.ce.vue @@ -1,16 +1,19 @@ + +