diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts new file mode 100644 index 0000000..9b87a03 --- /dev/null +++ b/eshtek/admin.types.ts @@ -0,0 +1,98 @@ +/** + * Admin API Types and Interfaces + */ + +/** + * Response from the catalog status endpoint + */ +export interface CatalogStatusResponse { + totalApps: number; + totalNonDeprecated: number; + totalWithScripts: number; + totalWithScriptsNonDeprecated: number; + totalDeprecated: number; + lastSync: Date | null; +} + +/** + * Individual app in the catalog + */ +export interface CatalogApp { + id: number; + name: string; + train: string | null; + version: string | null; + appVersion: string | null; + title: string | null; + description: string | null; + home: string | null; + iconUrl: string | null; + screenshots: string[]; + sources: string[]; + categories: string[]; + keywords: string[]; + maintainers: any[]; + supported: boolean; + deprecated: boolean; + installScript: string | null; + requirements: any; + compatibility: string | null; + lastCatalogSync: Date | null; + metadata: any; + // Event statistics (detailed) + installsDiscovered?: number; + installsCompleted?: number; + installsFailed?: number; + uninstallsCompleted?: number; + uninstallsFailed?: number; + upgradesCompleted?: number; + upgradesFailed?: number; + updatesCompleted?: number; + updatesFailed?: number; +} + +/** + * Response from the catalog data endpoint + */ +export interface AppsCatalogResponse { + apps: CatalogApp[]; + totalApps: number; + totalWithScripts: number; + totalDeprecated: number; + lastSync: Date | null; +} + +/** + * Drive data with event statistics + */ +export interface DriveData { + id: number; + manufacturer: string; + model: string; + name: string | null; + type: string | null; + size: number | null; + smr: boolean; + notes: string | null; + isUserDiscovered: boolean; + createdAt: Date; + updatedAt: Date; + // Event statistics + utilized: number; + replaced: number; + removed: number; + failed: number; + discovered: number; +} + +/** + * Response from the drives data endpoint + */ +export interface DrivesResponse { + drives: DriveData[]; + totalDrives: number; + totalSMR: number; + totalUtilized: number; + totalFailed: number; + totalRemoved: number; +} \ No newline at end of file diff --git a/eshtek/apps-schema.ts b/eshtek/apps-schema.ts deleted file mode 100644 index 0beecd0..0000000 --- a/eshtek/apps-schema.ts +++ /dev/null @@ -1,201 +0,0 @@ -// Generated by ts-to-zod -import { z } from "zod"; -import { - AppJobAction, - AppSpec, - AppPermission, - AppsError, - AppsWarning, - AppsActions, -} from "./apps"; - -export const appJobActionSchema = z.nativeEnum(AppJobAction); - -export const appBasicsSchema = z.object({ - id: z.string(), - name: z.string(), - description: z.string(), - version: z.string(), - icon: z.string(), - train: z.string(), -}); - -export const appSpecSchema = z.nativeEnum(AppSpec); - -export const appPermissionSchema = z.nativeEnum(AppPermission); - -export const appsErrorSchema = z.nativeEnum(AppsError); - -export const appsWarningSchema = z.nativeEnum(AppsWarning); - -export const appsActionsSchema = z.nativeEnum(AppsActions); - -export const appsHealthSchema = z.object({ - healthy: z.boolean(), - errors: z.array(appsErrorSchema), - warnings: z.array(appsWarningSchema), - actions_available: z.array(appsActionsSchema), -}); - -const locationPreferenceIdSchema = z.any(); - -export const appMaintainerSchema = z.object({ - email: z.string(), - name: z.string(), - url: z.string(), -}); - -const appMetadataSchema = z.any(); - -const apiTimestampSchema = z.any(); - -export const availableAppSchema = z.object({ - healthy: z.boolean(), - installed: z.boolean(), - categories: z.array(z.string()), - name: z.string(), - title: z.string(), - description: z.string(), - app_readme: z.string(), - app_metadata: appMetadataSchema, - location: z.string(), - healthy_error: z.string(), - latest_version: z.string(), - latest_app_version: z.string(), - icon_url: z.string(), - train: z.string(), - catalog: z.string(), - last_update: apiTimestampSchema, - recommended: z.boolean(), - maintainers: z.array(appMaintainerSchema), - tags: z.array(z.string()), - home: z.string(), - latest_human_version: z.string(), - screenshots: z.array(z.string()), - sources: z.array(z.string()), - versions: z.unknown(), -}); - -const appStateSchema = z.any(); - -const fileAccessSchema = z.any(); - -const chartFormValueSchema = z.any(); - -export const appRequirementsSchema = z.object({ - permissions: z.array(appPermissionSchema), - specifications: z.array(appSpecSchema), - locations: z.array(locationPreferenceIdSchema), - ports: z.array(z.number()), -}); - -export const appRequirementsCheckSchema = z.object({ - permissions: z.object({ - met: z.array(appPermissionSchema), - unmet: z.array(appPermissionSchema), - }), - specifications: z.object({ - met: z.array(appSpecSchema), - unmet: z.array(appSpecSchema), - }), - locations: z.object({ - met: z.array(locationPreferenceIdSchema), - unmet: z.array(locationPreferenceIdSchema), - }), -}); - -export const appListingSchema = availableAppSchema.extend({ - hexos: z.boolean(), - recommended_during_setup: z.boolean().optional(), - requirements: appRequirementsSchema, -}); - -export const appInfoSchema = appBasicsSchema.extend({ - status: appStateSchema, - url_webui: z.string(), -}); - -export const appInfoDetailedSchema = appInfoSchema.extend({ - data: z.array(z.array(z.number())), -}); - -export const installationQuestionTypeSchema = z.enum(['text', 'number', 'select', 'boolean']); - -export const installationQuestionOptionSchema = z.object({ - text: z.string(), - value: z.union([z.string(), z.number(), z.boolean()]), -}); - -export const installationQuestionSchema = z.object({ - question: z.string(), - type: installationQuestionTypeSchema, - key: z.string(), - options: z.array(installationQuestionOptionSchema).optional(), - required: z.boolean().optional(), - default: z.union([z.string(), z.number(), z.boolean()]).optional(), -}); - -const appsInstallScriptV1Schema = z.object({ - version: z.literal(1), - ensure_directories_exists: z - .array( - z.union([ - z.string(), - z.object({ - path: z.string(), - network_share: z.boolean().optional(), - posix: z.boolean().optional(), - }), - ]), - ) - .optional(), - ensure_permissions_exists: z - .array( - z.object({ - path: z.string(), - username: z.string(), - access: fileAccessSchema, - posix: z - .object({ - groupname: z.string(), - }) - .optional(), - }), - ) - .optional(), - app_values: z.record(chartFormValueSchema), -}); - -const appsInstallScriptV2Schema = z.object({ - version: z.literal(2), - installation_questions: z.array(installationQuestionSchema).optional(), - ensure_directories_exists: z - .array( - z.union([ - z.string(), - z.object({ - path: z.string(), - network_share: z.boolean().optional(), - posix: z.boolean().optional(), - }), - ]), - ) - .optional(), - ensure_permissions_exists: z - .array( - z.object({ - path: z.string(), - username: z.string(), - access: fileAccessSchema, - posix: z - .object({ - groupname: z.string(), - }) - .optional(), - }), - ) - .optional(), - app_values: z.record(chartFormValueSchema), -}); - -export const appsInstallScriptSchema = z.union([appsInstallScriptV1Schema, appsInstallScriptV2Schema]); diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 2d74168..87ab391 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -59,8 +59,28 @@ export interface AppRequirementsCheck { }; } -export interface AppListing extends AvailableApp { - hexos: boolean; +export interface AppMaintainer { + name: string; + email: string; +} + +export interface AppListing { + appId: string; + name: string; + train: "stable" | "community"; + version: string; + appVersion: string; + description: string; + icon: string; + categories: string[]; + keywords: string[]; + maintainers: AppMaintainer[]; + screenshots: string[]; + sources: string[]; + homepage: string; + recommended: boolean; + supported: boolean; + fresh: boolean; installScript?: string; requirements?: AppRequirements; } @@ -69,7 +89,9 @@ export interface AppInfo extends AppBasics { status: AppState; url_webui: string; upgradeAvailable: boolean; + updatedScriptAvailable: boolean; latestVersion: string; + recommended: boolean; } export interface AppInfoDetailed extends AppInfo { data: number[][]; @@ -120,6 +142,7 @@ export interface InstallationQuestion { interface AppsInstallScriptV1 { version: 1; + requirements?: AppRequirements; ensure_directories_exists?: Array; ensure_permissions_exists?: Array<{ path: string; @@ -132,6 +155,7 @@ interface AppsInstallScriptV1 { interface AppsInstallScriptV2 { version: 2; + requirements?: AppRequirements; installation_questions?: InstallationQuestion[]; ensure_directories_exists?: Array; ensure_permissions_exists?: Array<{ @@ -143,7 +167,22 @@ interface AppsInstallScriptV2 { app_values: Record; } -export type AppsInstallScript = AppsInstallScriptV1 | AppsInstallScriptV2; +interface AppsInstallScriptV3 extends Omit { + version: 3; + script: { + version: string; + updateCompatibility?: string; + changeLog?: string; + }; + requirements: AppRequirements; // Required in V3 +} + +export type AppsInstallScript = AppsInstallScriptV1 | AppsInstallScriptV2 | AppsInstallScriptV3; + +export interface AppConfiguration { + installScript?: AppsInstallScript | null; + questionResponses?: Record; +} export interface InstallScriptCuration { name: string; diff --git a/eshtek/common.ts b/eshtek/common.ts index 9a4732a..208c298 100644 --- a/eshtek/common.ts +++ b/eshtek/common.ts @@ -505,3 +505,16 @@ export function getStepSize(range: number, steps: number = 10, acceptableSteps: return Math.abs(curr - rawStep) < Math.abs(prev - rawStep) ? curr : prev; }); } + +import type { HexTaskType } from './tasks'; +import type { EventState, TaskEventName } from './events'; + +/** + * Generates a task event name from a task type and event state. + * @param {HexTaskType} taskType - The type of the task. + * @param {EventState} state - The state of the event (started, completed, failed). + * @returns {TaskEventName} - Returns the generated event name (e.g., 'app_install_started'). + */ +export function getTaskEventName(taskType: HexTaskType, state: EventState): TaskEventName { + return `${taskType.toLowerCase()}_${state}` as TaskEventName; +} diff --git a/eshtek/errors.ts b/eshtek/errors.ts index 85e1876..afebb43 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -29,8 +29,22 @@ export enum GlobalErrorCode { PROCESS_INSTALL_SCRIPT_INVALID_MEMORY_VALUE = 'PROCESS_INSTALL_SCRIPT_INVALID_MEMORY_VALUE', PROCESS_INSTALL_SCRIPT_MISSING_QUESTION_RESPONSE = 'PROCESS_INSTALL_SCRIPT_MISSING_QUESTION_RESPONSE', PROCESS_INSTALL_SCRIPT_INVALID_QUESTION_RESPONSE_TYPE = 'PROCESS_INSTALL_SCRIPT_INVALID_QUESTION_RESPONSE_TYPE', + PROCESS_INSTALL_SCRIPT_MISSING_SCRIPT_METADATA = 'PROCESS_INSTALL_SCRIPT_MISSING_SCRIPT_METADATA', + PROCESS_INSTALL_SCRIPT_MISSING_REQUIREMENTS = 'PROCESS_INSTALL_SCRIPT_MISSING_REQUIREMENTS', + PROCESS_INSTALL_SCRIPT_INVALID_SCRIPT_VERSION = 'PROCESS_INSTALL_SCRIPT_INVALID_SCRIPT_VERSION', + PROCESS_INSTALL_SCRIPT_INVALID_IF_CONDITION = 'PROCESS_INSTALL_SCRIPT_INVALID_IF_CONDITION', RUN_INSTALL_SCRIPT_UNSUPPORTED_VERSION = 'RUN_INSTALL_SCRIPT_UNSUPPORTED_VERSION', DDP_CLIENT_DISCONNECTED = 'DDP_CLIENT_DISCONNECTED', - TASK_FAILED_STALE = "TASK_FAILED_STALE" - + TASK_FAILED_STALE = "TASK_FAILED_STALE", + UPDATE_APP_NOT_FOUND = 'UPDATE_APP_NOT_FOUND', + UPDATE_APP_NOT_IN_CATALOG = 'UPDATE_APP_NOT_IN_CATALOG', + UPDATE_APP_INVALID_SCRIPT = 'UPDATE_APP_INVALID_SCRIPT', + UPDATE_APP_INVALID_CATALOG_SCRIPT = 'UPDATE_APP_INVALID_CATALOG_SCRIPT', + UPDATE_APP_ANALYSIS_FAILED = 'UPDATE_APP_ANALYSIS_FAILED', + UPDATE_APP_FAILED = 'UPDATE_APP_FAILED', + UPDATE_APP_NO_CURRENT_CONFIG = 'UPDATE_APP_NO_CURRENT_CONFIG', + DOMAIN_NOT_FOUND = 'DOMAIN_NOT_FOUND', + ACME_ACCOUNT_EMAIL_REQUIRED = 'ACME_ACCOUNT_EMAIL_REQUIRED', + ACME_CHALLENGE_FAILED = 'ACME_CHALLENGE_FAILED', + ACME_CERTIFICATE_GENERATION_FAILED = 'ACME_CERTIFICATE_GENERATION_FAILED', } diff --git a/eshtek/events.ts b/eshtek/events.ts new file mode 100644 index 0000000..1bd9788 --- /dev/null +++ b/eshtek/events.ts @@ -0,0 +1,71 @@ +import type { HexTaskType } from './tasks'; + +export interface BaseEvent { + eventName: string; + userId: string; + hostId?: string; + timestamp: Date; + properties?: Record; +} + +export interface TaskEvent extends BaseEvent { + taskId: string; + taskType: HexTaskType; + taskStatus: string; + errorMessage?: string; +} + +export interface AppEvent extends TaskEvent { + appId: string; + appTrain?: string; + appVersion?: string; + appTrueNasVersion?: string; +} + +export enum EventState { + STARTED = 'started', + COMPLETED = 'completed', + FAILED = 'failed', + DISMISSED = 'dismissed', +} + +export type TaskEventName = `${Lowercase<`${HexTaskType}`>}_${EventState}`; + +export const SystemEventNames = { + SERVER_CONNECTED: 'server_connected', + SERVER_DISCONNECTED: 'server_disconnected', + USER_LOGIN: 'user_login', + USER_LOGOUT: 'user_logout', + DRIVE_UTILIZED: 'drive_utilized', // Drive added to a pool + DRIVE_REPLACED: 'drive_replaced', + DRIVE_REMOVED: 'drive_removed', + DRIVE_FAILED: 'drive_failed', + DRIVE_HEALTHY: 'drive_healthy', // Drive has no errors + DRIVE_DISCOVERED: 'drive_discovered', // Drive found on system (assigned or unassigned) + APP_DISCOVERED: 'app_discovered', // App found on system +} as const; + +export type SystemEventName = typeof SystemEventNames[keyof typeof SystemEventNames]; +export type EventName = TaskEventName | SystemEventName; + +export interface EventQueryOptions { + eventName?: EventName | EventName[]; + userId?: string; + hostId?: string; + taskType?: HexTaskType; + appId?: string; + appTrain?: string; + appVersion?: string; + startDate?: Date; + endDate?: Date; + limit?: number; +} + +export interface AppPopularityMetrics { + appId: string; + installCount: number; + uninstallCount: number; + upgradeCount: number; + failureCount: number; + lastInstalled: Date; +} \ No newline at end of file diff --git a/eshtek/routes.ts b/eshtek/routes.ts index 48c6efa..b13b785 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -1,4 +1,5 @@ import { GlobalErrorCode } from './errors'; +import type { AppConfiguration } from './apps'; import type { LocationPreferenceId } from './preferences'; import type { ServerPool, ServerPoolNew, ServerUser, ServerUserType } from './server'; import type { VMBasics, VMSettings } from './vms'; @@ -115,13 +116,106 @@ export interface RequestDockerUpdatePool { poolName: string; } -export interface RequestAppInstall { +export interface RequestAppInstall extends AppConfiguration { id: string; train?: 'community' | 'stable'; - installScript?: string; - questionResponses?: Record; +} + +export interface RequestAppUpdate extends AppConfiguration {} +export interface RequestAppUpdateAnalysis extends AppConfiguration {} + + +export enum ResourceChangeType { + STORAGE = 'storage', + NETWORK = 'network', + ENVIRONMENT = 'environment', + RESOURCE = 'resource', + PERMISSION = 'permission', + CONFIG = 'config' +} + +export enum ResourceChangeAction { + CREATE = 'create', + UPDATE = 'update', + DELETE = 'delete', + NO_OP = 'no-op', + PRESERVE = 'preserve' +} + +export interface ResponseResourceChanges { + resourceChanges: Array<{ + address: string; + type: ResourceChangeType; + change: { + actions: Array; + before: any; + after: any; + }; + }>; + planSummary: { + totalChanges: number; + changesByAction: { + create: number; + update: number; + delete: number; + noOp: number; + preserve: number; + }; + }; + validation: { + updateCompatible: boolean; + compatibilityConstraint?: string; + warnings: string[]; + }; + versionInfo: { + current?: string; + target?: string; + changelog?: string; + }; } export interface RequestAppDelete { deleteData?: boolean; -} \ No newline at end of file +} + +export enum AppSearchSortBy { + NAME = 'name', + POPULARITY = 'popularity', + CREATED_AT = 'createdAt', + UPDATED_AT = 'updatedAt', +} + +export enum AppSearchSortOrder { + ASC = 'asc', + DESC = 'desc', +} + +export interface RequestAppSearch { + appId?: string; + search?: string; + category?: string; + fresh?: boolean; + supported?: boolean; + recommended?: boolean; + train?: 'stable' | 'community'; + sortBy?: AppSearchSortBy; + sortOrder?: AppSearchSortOrder; + popularityStartDate?: string; + popularityEndDate?: string; + page?: number; + pageSize?: number; + limit?: number; // -1 means return all results +} + +export interface AppCategoryInfo { + name: string; + appCount: number; +} + +export interface RequestAppCategories { + train?: 'stable' | 'community'; + supported?: boolean; + fresh?: boolean; +} + +export type ResponseAppCategories = Response; \ No newline at end of file diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index cd52456..e0463ea 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -27,6 +27,8 @@ import { } from "./server"; import { vMSHealthSchema } from "./vms-schema"; +const appsHealthSchema = z.any(); + export const serverUserTypeSchema = z.nativeEnum(ServerUserType); @@ -135,6 +137,12 @@ export const serverSystemDataSystemDeviceDataSchema = z.object({ networking: z.array(serverSystemDataSystemDeviceSchema).optional(), }); +export const serverSystemDataApplicationsSchema = + serverStatusBasicsSchema.extend({ + type: z.literal(ServerStatusType.APPLICATIONS), + health: appsHealthSchema, + }); + export const serverSystemDataVirtualizationSchema = serverStatusBasicsSchema.extend({ type: z.literal(ServerStatusType.VIRTUALIZATION), @@ -197,6 +205,13 @@ export const serverHealthSchema = z.object({ actions_available: z.array(serverActionsSchema), }); +export const serverVersionSchema = z.object({ + isVersionLatest: z.boolean(), + isVersionSupported: z.boolean(), + currentVersion: z.string(), + latestVersion: z.string(), +}); + export const serverUpgradeInfoSchema = z.object({ serverName: z.string(), hostId: z.string(), @@ -213,8 +228,6 @@ const fileTypeSchema = z.any(); const topologyItemStatusSchema = z.any(); -const appsHealthSchema = z.any(); - const networkInterfaceTypeSchema = z.any(); export const serverPoolBasicsSchema = z.object({ @@ -241,6 +254,7 @@ export const serverDriveSchema = z.object({ existingData: z.boolean().optional(), temperature: z.number().optional(), healthDetails: topologyItemStatusSchema.optional(), + driveId: z.number().optional(), }); export const serverPoolNewSchema = serverPoolBasicsSchema.extend({ @@ -308,12 +322,6 @@ export const serverSystemDataStorageSchema = serverStatusBasicsSchema.extend({ }), }); -export const serverSystemDataApplicationsSchema = - serverStatusBasicsSchema.extend({ - type: z.literal(ServerStatusType.APPLICATIONS), - health: appsHealthSchema, - }); - export const serverSystemDataSchema = z.union([ serverSystemDataSystemSchema, serverSystemDataStorageSchema, diff --git a/eshtek/server.ts b/eshtek/server.ts index 88aafe3..0a18b56 100644 --- a/eshtek/server.ts +++ b/eshtek/server.ts @@ -244,6 +244,7 @@ export interface ServerDrive { existingData?: boolean; temperature?: number; healthDetails?: TopologyItemStatus; + driveId?: number; // Database ID from drives table } export interface ServerDrivesGroupedBySize { @@ -408,6 +409,13 @@ export interface ServerHealth { actions_available: ServerActions[]; } +export interface ServerVersion { + isVersionLatest: boolean; + isVersionSupported: boolean; + currentVersion: string; + latestVersion: string; +} + export interface ServerUpgradeInfo { serverName: string; hostId: string; diff --git a/eshtek/tasks.ts b/eshtek/tasks.ts index 349eb71..3c8d042 100644 --- a/eshtek/tasks.ts +++ b/eshtek/tasks.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // tasks.ts +import type { ResponseResourceChanges } from './routes'; import type { DiskType } from '../truenas/webui/enums/disk-type.enum'; export interface HexTaskBase { @@ -115,6 +116,27 @@ type HexTaskMeta = + R extends HexTaskAppUpdateReason ? HexTaskAppUpdateDataMap[R] : HexTaskAppUpdateDataMap[HexTaskAppUpdateReason]; + export type HexTaskDataMap = { [HexTaskType.RESTART]: HexTaskMeta; [HexTaskType.SHUTDOWN]: HexTaskMeta; @@ -137,17 +159,17 @@ export type HexTaskDataMap = { [HexTaskType.APP_INSTALL]: HexTaskMeta; [HexTaskType.APP_UNINSTALL]: HexTaskMeta; [HexTaskType.APP_UPGRADE]: HexTaskMeta; - [HexTaskType.APP_UPDATE]: HexTaskMeta; + [HexTaskType.APP_UPDATE]: HexTaskMeta; [HexTaskType.PREFERENCE_LOCATION_PATH_MIGRATION]: HexTaskMeta; [HexTaskType.DRIVE_REPLACE]: HexTaskMeta; - [HexTaskType.DOCKER_UPDATE]: HexTaskMeta; + [HexTaskType.DOCKER_UPDATE]: HexTaskMeta; }; // This looks a little strange with duplicated code, but we need a runtime const avail for the utils file export const HexTaskSettings: { [K in HexTaskType]: HexTaskTypeInfo; } = { - [HexTaskType.RESTART]: { canHaveMultiple: false, predictedSecondsToComplete: 120 }, + [HexTaskType.RESTART]: { canHaveMultiple: false, predictedSecondsToComplete: 500 }, [HexTaskType.SHUTDOWN]: { canHaveMultiple: false, predictedSecondsToComplete: 120 }, [HexTaskType.NETWORK_UPDATE]: { canHaveMultiple: false, predictedSecondsToComplete: 90 }, [HexTaskType.POOL_CREATE]: { canHaveMultiple: true, predictedSecondsToComplete: 30 }, diff --git a/truenas/webui/interfaces/api/api-call-directory.interface.ts b/truenas/webui/interfaces/api/api-call-directory.interface.ts index 1f4bf14..4b59abb 100644 --- a/truenas/webui/interfaces/api/api-call-directory.interface.ts +++ b/truenas/webui/interfaces/api/api-call-directory.interface.ts @@ -204,6 +204,7 @@ import type { SystemHealth } from '../system-health.interface'; import type { App, AppQueryParams, AppUpgradeParams } from '../app.interface'; import type { DetailsDisk, DiskSmartAttribute } from '../disk.interface'; import { DockerConfig, DockerStatusData } from '../../enums/docker-config.interface'; +import type { GpuChoices } from '../gpu.interface'; /** * API definitions for `call` methods. @@ -267,6 +268,7 @@ export interface ApiCallDirectory { 'app.similar': { params: [app_name: string, train: string]; response: AvailableApp[] }; 'app.rollback_versions': { params: [app_name: string]; response: string[] }; 'app.used_ports': { params: void; response: number[] }; + 'app.gpu_choices': { params: void; response: GpuChoices }; // Audit 'audit.config': { params: void; response: AuditConfig }; diff --git a/truenas/webui/interfaces/gpu.interface.ts b/truenas/webui/interfaces/gpu.interface.ts new file mode 100644 index 0000000..0f8342a --- /dev/null +++ b/truenas/webui/interfaces/gpu.interface.ts @@ -0,0 +1,20 @@ +/** + * GPU interface types based on TrueNAS middleware API + * From middlewared/middlewared/api/v25_04_1/app.py + */ + +export interface GpuVendorSpecificConfig { + uuid?: string; + [key: string]: unknown; +} + +export interface GpuDevice { + vendor: string | null; + description: string | null; + error: string | null; + vendor_specific_config: GpuVendorSpecificConfig; + gpu_details: Record; + pci_slot: string | null; +} + +export type GpuChoices = Record; \ No newline at end of file diff --git a/ts-to-zod.config.js b/ts-to-zod.config.js index 4a9c3e9..e02bcdf 100644 --- a/ts-to-zod.config.js +++ b/ts-to-zod.config.js @@ -1,6 +1,6 @@ module.exports = [ { - name: "vms", + name: "apps", input: "eshtek/vms.ts", output: "eshtek/vms-schema.ts", },