From 976d0aa544d2ff17eb54c68e912900fabd4404a9 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 09:01:23 -0500 Subject: [PATCH 01/26] feat: add event tracking type definitions and helper --- eshtek/common.ts | 13 ++++++++++ eshtek/events.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ eshtek/tasks.ts | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 eshtek/events.ts 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/events.ts b/eshtek/events.ts new file mode 100644 index 0000000..b1685d5 --- /dev/null +++ b/eshtek/events.ts @@ -0,0 +1,62 @@ +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', +} + +export type TaskEventName = `${Lowercase<`${HexTaskType}`>}_${EventState}`; + +export const SystemEventNames = { + SERVER_CONNECTED: 'server_connected', + SERVER_DISCONNECTED: 'server_disconnected', + USER_LOGIN: 'user_login', +} 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/tasks.ts b/eshtek/tasks.ts index 349eb71..4d5a83f 100644 --- a/eshtek/tasks.ts +++ b/eshtek/tasks.ts @@ -147,7 +147,7 @@ export type HexTaskDataMap = { 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 }, From 74b971a14d7b076832349e65486172b3a9972aa9 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 11:57:58 -0500 Subject: [PATCH 02/26] new interfaces --- eshtek/admin.types.ts | 12 ++++++++++++ eshtek/events.ts | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 eshtek/admin.types.ts diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts new file mode 100644 index 0000000..456b281 --- /dev/null +++ b/eshtek/admin.types.ts @@ -0,0 +1,12 @@ +/** + * Admin API Types and Interfaces + */ + +/** + * Response from the catalog status endpoint + */ +export interface CatalogStatusResponse { + totalApps: number; + supportedApps: number; + lastSync: Date | null; +} \ No newline at end of file diff --git a/eshtek/events.ts b/eshtek/events.ts index b1685d5..fcff9b0 100644 --- a/eshtek/events.ts +++ b/eshtek/events.ts @@ -26,6 +26,7 @@ export enum EventState { STARTED = 'started', COMPLETED = 'completed', FAILED = 'failed', + DISMISSED = 'dismissed', } export type TaskEventName = `${Lowercase<`${HexTaskType}`>}_${EventState}`; @@ -34,6 +35,13 @@ 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) } as const; export type SystemEventName = typeof SystemEventNames[keyof typeof SystemEventNames]; From 8cf4b08574d0b65e6de36ad65cf8d5e64a828a27 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 13:25:39 -0500 Subject: [PATCH 03/26] new admin type for app catalog display --- eshtek/admin.types.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts index 456b281..dd884a2 100644 --- a/eshtek/admin.types.ts +++ b/eshtek/admin.types.ts @@ -9,4 +9,42 @@ export interface CatalogStatusResponse { totalApps: number; supportedApps: 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; +} + +/** + * Response from the catalog data endpoint + */ +export interface AppsCatalogResponse { + apps: CatalogApp[]; + totalApps: number; + totalWithScripts: number; + totalDeprecated: number; + lastSync: Date | null; } \ No newline at end of file From dd3b4d5b1892b3660ad0e4ead84ae1f78b4a3ef8 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 13:26:04 -0500 Subject: [PATCH 04/26] admin catalog response tweak --- eshtek/admin.types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts index dd884a2..c11c4d0 100644 --- a/eshtek/admin.types.ts +++ b/eshtek/admin.types.ts @@ -7,7 +7,8 @@ */ export interface CatalogStatusResponse { totalApps: number; - supportedApps: number; + totalWithScripts: number; + totalDeprecated: number; lastSync: Date | null; } From 5f27424f26260631a5fc55d871fd491059970b60 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 16:53:58 -0500 Subject: [PATCH 05/26] relate ServerDrive to drives table --- eshtek/server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eshtek/server.ts b/eshtek/server.ts index 88aafe3..5856264 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 { From 84b8f682d701ce3d9e171a9b964c1dcd6c8134a4 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 16:54:15 -0500 Subject: [PATCH 06/26] stats wip poc on drives and apps in admin --- eshtek/admin.types.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts index c11c4d0..2dd07c2 100644 --- a/eshtek/admin.types.ts +++ b/eshtek/admin.types.ts @@ -37,6 +37,10 @@ export interface CatalogApp { compatibility: string | null; lastCatalogSync: Date | null; metadata: any; + // Event statistics + installs?: number; + uninstalls?: number; + failures?: number; } /** @@ -48,4 +52,39 @@ export interface AppsCatalogResponse { 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 From f612909ceabbd427fdd2fb2173f14c099f73c0b2 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 20:48:58 -0500 Subject: [PATCH 07/26] more detailed per app stats --- eshtek/admin.types.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts index 2dd07c2..9a9b5c1 100644 --- a/eshtek/admin.types.ts +++ b/eshtek/admin.types.ts @@ -37,10 +37,15 @@ export interface CatalogApp { compatibility: string | null; lastCatalogSync: Date | null; metadata: any; - // Event statistics - installs?: number; - uninstalls?: number; - failures?: number; + // Event statistics (detailed) + installsCompleted?: number; + installsFailed?: number; + uninstallsCompleted?: number; + uninstallsFailed?: number; + upgradesCompleted?: number; + upgradesFailed?: number; + updatesCompleted?: number; + updatesFailed?: number; } /** From b9d0c4b8b4f2215293a4ff9618ac4cc9a67e4fb6 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Mon, 17 Nov 2025 09:12:21 -0500 Subject: [PATCH 08/26] simplified AppListing --- eshtek/apps.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 2d74168..072ed11 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -59,8 +59,27 @@ 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: string; + version: string; + appVersion: string; + description: string; + icon: string; + categories: string[]; + keywords: string[]; + maintainers: AppMaintainer[]; + screenshots: string[]; + sources: string[]; + homepage: string; + recommended: boolean; + supported: boolean; installScript?: string; requirements?: AppRequirements; } @@ -120,6 +139,7 @@ export interface InstallationQuestion { interface AppsInstallScriptV1 { version: 1; + requirements?: AppRequirements; ensure_directories_exists?: Array; ensure_permissions_exists?: Array<{ path: string; @@ -132,6 +152,7 @@ interface AppsInstallScriptV1 { interface AppsInstallScriptV2 { version: 2; + requirements?: AppRequirements; installation_questions?: InstallationQuestion[]; ensure_directories_exists?: Array; ensure_permissions_exists?: Array<{ From 83b0c482e2ace51c9a4fa57244a7d382b2af22f7 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Mon, 17 Nov 2025 09:19:41 -0500 Subject: [PATCH 09/26] train stable or community --- eshtek/apps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 072ed11..665b355 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -67,7 +67,7 @@ export interface AppMaintainer { export interface AppListing { appId: string; name: string; - train: string; + train: "stable" | "community"; version: string; appVersion: string; description: string; From f9e6a241aac8b88417f6b8707851f5a9b7d586d1 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Mon, 17 Nov 2025 10:50:20 -0500 Subject: [PATCH 10/26] new fresh flag passed through to implement "recently curated" --- eshtek/apps.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 665b355..fac1756 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -80,6 +80,7 @@ export interface AppListing { homepage: string; recommended: boolean; supported: boolean; + fresh: boolean; installScript?: string; requirements?: AppRequirements; } From f8a7ba96b986756267545a950a28ffdb9d4b02a0 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 18 Nov 2025 16:06:21 -0500 Subject: [PATCH 11/26] New interfaces for endpoints to support apps overhaul requests To Support: - New endpoint for searching apps | allow for by category, search string, fresh=true, allow for sorting by popularity given a date range. ResponsePaginated of AppListing[]. - New endpoint to return a list of all categories across all apps : New interface for response giving an array of items sorted alphabetically containing name and also the number of apps --- eshtek/routes.ts | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/eshtek/routes.ts b/eshtek/routes.ts index 48c6efa..ac1f771 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -124,4 +124,44 @@ export interface RequestAppInstall { 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 { + 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; +} + +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 From a4126729cfbde407e216cc60083dbbcfd60e52cf Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 18 Nov 2025 23:06:06 -0500 Subject: [PATCH 12/26] Update routes.ts --- eshtek/routes.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eshtek/routes.ts b/eshtek/routes.ts index ac1f771..c9dc908 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -139,6 +139,7 @@ export enum AppSearchSortOrder { } export interface RequestAppSearch { + appId?: string; search?: string; category?: string; fresh?: boolean; @@ -151,6 +152,7 @@ export interface RequestAppSearch { popularityEndDate?: string; page?: number; pageSize?: number; + limit?: number; // -1 means return all results } export interface AppCategoryInfo { From acc32109e64b390782eba2d83d2c467d0663b319 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 19 Nov 2025 13:13:28 -0700 Subject: [PATCH 13/26] Adds app_discovery event --- eshtek/events.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eshtek/events.ts b/eshtek/events.ts index fcff9b0..1bd9788 100644 --- a/eshtek/events.ts +++ b/eshtek/events.ts @@ -42,6 +42,7 @@ export const SystemEventNames = { 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]; From 1676dd5eef03eed15bd3bf4812300724d167c0e4 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Thu, 20 Nov 2025 15:20:02 -0500 Subject: [PATCH 14/26] Update catalog status response to differentiate totals with deprecation --- eshtek/admin.types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts index 9a9b5c1..452ef54 100644 --- a/eshtek/admin.types.ts +++ b/eshtek/admin.types.ts @@ -7,7 +7,9 @@ */ export interface CatalogStatusResponse { totalApps: number; + totalNonDeprecated: number; totalWithScripts: number; + totalWithScriptsNonDeprecated: number; totalDeprecated: number; lastSync: Date | null; } From 96afab8f457ad77ee776903606fedf262cb10088 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Thu, 20 Nov 2025 20:47:27 -0500 Subject: [PATCH 15/26] server schema to include driveId from drives table --- eshtek/server-schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index cd52456..446b662 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -241,6 +241,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({ From e2040667a17762eeafd0bed1cdb251ec3e258f93 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 2 Dec 2025 10:13:03 -0700 Subject: [PATCH 16/26] Adds installsDiscovered to CatalogApp --- eshtek/admin.types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts index 452ef54..9b87a03 100644 --- a/eshtek/admin.types.ts +++ b/eshtek/admin.types.ts @@ -40,6 +40,7 @@ export interface CatalogApp { lastCatalogSync: Date | null; metadata: any; // Event statistics (detailed) + installsDiscovered?: number; installsCompleted?: number; installsFailed?: number; uninstallsCompleted?: number; From 3419337867c57e0cdd901f5059ee80c8dc7bf548 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 2 Dec 2025 19:49:29 -0700 Subject: [PATCH 17/26] Adds ServerVersion interface --- eshtek/server.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/eshtek/server.ts b/eshtek/server.ts index 5856264..0a18b56 100644 --- a/eshtek/server.ts +++ b/eshtek/server.ts @@ -409,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; From 7e2d62853312813ff631b8ad746eecdafb46fd5e Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 2 Dec 2025 19:51:26 -0700 Subject: [PATCH 18/26] npm run generate-schemas --- eshtek/server-schema.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index 446b662..4bb5206 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -197,6 +197,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(), From 5145142986d019fb527a4a4b07d1bf937f86dbaf Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 3 Dec 2025 12:27:53 -0700 Subject: [PATCH 19/26] Adds ACME-related global error codes --- eshtek/errors.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eshtek/errors.ts b/eshtek/errors.ts index 85e1876..d49b39d 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -31,6 +31,10 @@ export enum GlobalErrorCode { PROCESS_INSTALL_SCRIPT_INVALID_QUESTION_RESPONSE_TYPE = 'PROCESS_INSTALL_SCRIPT_INVALID_QUESTION_RESPONSE_TYPE', 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", + 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', } From 5f358c2fa2851bd9c85d1a51a97c86262c516003 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Thu, 4 Dec 2025 16:14:10 -0500 Subject: [PATCH 20/26] script v3 changes --- eshtek/apps.ts | 12 +++++++++++- eshtek/errors.ts | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index fac1756..023d6c4 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -165,7 +165,17 @@ 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 InstallScriptCuration { name: string; diff --git a/eshtek/errors.ts b/eshtek/errors.ts index 85e1876..d639e4f 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -29,6 +29,10 @@ 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" From d48f87e8339c145155ee9463ee476722411adf79 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 7 Dec 2025 21:41:14 -0500 Subject: [PATCH 21/26] script v3 continuation / app updates / analysis wip --- eshtek/apps.ts | 1 + eshtek/errors.ts | 8 ++++++- eshtek/routes.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ eshtek/tasks.ts | 24 +++++++++++++++++++- 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 023d6c4..8a21f3a 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -89,6 +89,7 @@ export interface AppInfo extends AppBasics { status: AppState; url_webui: string; upgradeAvailable: boolean; + updatedScriptAvailable: boolean; latestVersion: string; } export interface AppInfoDetailed extends AppInfo { diff --git a/eshtek/errors.ts b/eshtek/errors.ts index d639e4f..d1c3ee4 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -35,6 +35,12 @@ export enum GlobalErrorCode { 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' } diff --git a/eshtek/routes.ts b/eshtek/routes.ts index c9dc908..7e5f90b 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -122,6 +122,63 @@ export interface RequestAppInstall { questionResponses?: Record; } +export interface RequestAppUpdate { + installScript?: string; + questionResponses?: Record; +} + +export interface RequestAppUpdateAnalysis { + installScript?: string; + questionResponses?: Record; +} + +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' +} + +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; + }; + }; + validation: { + updateCompatible: boolean; + compatibilityConstraint?: string; + warnings: string[]; + }; + versionInfo: { + current: string; + target: string; + changelog?: string; + }; +} + export interface RequestAppDelete { deleteData?: boolean; } diff --git a/eshtek/tasks.ts b/eshtek/tasks.ts index 4d5a83f..52f421e 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,7 +159,7 @@ 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; From 9464795ca3e1424e1e6f8907ed5ffff521bf4b0b Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Mon, 8 Dec 2025 08:56:22 -0500 Subject: [PATCH 22/26] convert types for easier use in config get endpoint --- eshtek/apps.ts | 5 +++++ eshtek/routes.ts | 17 +++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 8a21f3a..38c6afb 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -178,6 +178,11 @@ interface AppsInstallScriptV3 extends Omit; +} + export interface InstallScriptCuration { name: string; url: string; diff --git a/eshtek/routes.ts b/eshtek/routes.ts index 7e5f90b..c94437e 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,22 +116,14 @@ 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 { - installScript?: string; - questionResponses?: Record; -} +export interface RequestAppUpdate extends AppConfiguration {} +export interface RequestAppUpdateAnalysis extends AppConfiguration {} -export interface RequestAppUpdateAnalysis { - installScript?: string; - questionResponses?: Record; -} export enum ResourceChangeType { STORAGE = 'storage', @@ -173,7 +166,7 @@ export interface ResponseResourceChanges { warnings: string[]; }; versionInfo: { - current: string; + current?: string; target: string; changelog?: string; }; From 687734b59ad176c2d34662975f38fd1697d41e71 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 9 Dec 2025 15:48:19 -0500 Subject: [PATCH 23/26] interface changes to support gpu macro implementation --- eshtek/apps-schema.ts | 136 ++++++++++-------- eshtek/routes.ts | 1 + eshtek/server-schema.ts | 15 +- eshtek/tasks.ts | 2 +- .../api/api-call-directory.interface.ts | 2 + truenas/webui/interfaces/gpu.interface.ts | 20 +++ ts-to-zod.config.js | 10 +- 7 files changed, 118 insertions(+), 68 deletions(-) create mode 100644 truenas/webui/interfaces/gpu.interface.ts diff --git a/eshtek/apps-schema.ts b/eshtek/apps-schema.ts index 0beecd0..887efa6 100644 --- a/eshtek/apps-schema.ts +++ b/eshtek/apps-schema.ts @@ -7,8 +7,11 @@ import { AppsError, AppsWarning, AppsActions, + InstallationQuestionType, } from "./apps"; +import { fileAccessSchema } from "./server-schema"; + export const appJobActionSchema = z.nativeEnum(AppJobAction); export const appBasicsSchema = z.object({ @@ -24,6 +27,11 @@ export const appSpecSchema = z.nativeEnum(AppSpec); export const appPermissionSchema = z.nativeEnum(AppPermission); +export const appMaintainerSchema = z.object({ + name: z.string(), + email: z.string(), +}); + export const appsErrorSchema = z.nativeEnum(AppsError); export const appsWarningSchema = z.nativeEnum(AppsWarning); @@ -37,48 +45,29 @@ export const appsHealthSchema = z.object({ actions_available: z.array(appsActionsSchema), }); -const locationPreferenceIdSchema = z.any(); +export const installationQuestionTypeSchema = z.nativeEnum( + InstallationQuestionType, +); -export const appMaintainerSchema = z.object({ - email: z.string(), - name: z.string(), - url: z.string(), +export const installationQuestionOptionSchema = z.object({ + text: z.string(), + value: z.union([z.string(), z.number(), z.boolean()]), }); -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(), +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(), + placeholder: z.string().optional(), + description: z.string().optional(), }); -const appStateSchema = z.any(); +const locationPreferenceIdSchema = z.any(); -const fileAccessSchema = z.any(); +const appStateSchema = z.any(); const chartFormValueSchema = z.any(); @@ -104,39 +93,42 @@ export const appRequirementsCheckSchema = z.object({ }), }); -export const appListingSchema = availableAppSchema.extend({ - hexos: z.boolean(), - recommended_during_setup: z.boolean().optional(), - requirements: appRequirementsSchema, +export const appListingSchema = z.object({ + appId: z.string(), + name: z.string(), + train: z.union([z.literal("stable"), z.literal("community")]), + version: z.string(), + appVersion: z.string(), + description: z.string(), + icon: z.string(), + categories: z.array(z.string()), + keywords: z.array(z.string()), + maintainers: z.array(appMaintainerSchema), + screenshots: z.array(z.string()), + sources: z.array(z.string()), + homepage: z.string(), + recommended: z.boolean(), + supported: z.boolean(), + fresh: z.boolean(), + installScript: z.string().optional(), + requirements: appRequirementsSchema.optional(), }); export const appInfoSchema = appBasicsSchema.extend({ status: appStateSchema, url_webui: z.string(), + upgradeAvailable: z.boolean(), + updatedScriptAvailable: z.boolean(), + latestVersion: 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), + requirements: appRequirementsSchema.optional(), ensure_directories_exists: z .array( z.union([ @@ -168,6 +160,7 @@ const appsInstallScriptV1Schema = z.object({ const appsInstallScriptV2Schema = z.object({ version: z.literal(2), + requirements: appRequirementsSchema.optional(), installation_questions: z.array(installationQuestionSchema).optional(), ensure_directories_exists: z .array( @@ -198,4 +191,33 @@ const appsInstallScriptV2Schema = z.object({ app_values: z.record(chartFormValueSchema), }); -export const appsInstallScriptSchema = z.union([appsInstallScriptV1Schema, appsInstallScriptV2Schema]); +const appsInstallScriptV3Schema = appsInstallScriptV2Schema + .omit({ version: true, requirements: true }) + .extend({ + version: z.literal(3), + script: z.object({ + version: z.string(), + updateCompatibility: z.string().optional(), + changeLog: z.string().optional(), + }), + requirements: appRequirementsSchema, + }); + +export const appsInstallScriptSchema = z.union([ + appsInstallScriptV1Schema, + appsInstallScriptV2Schema, + appsInstallScriptV3Schema, +]); + +export const appConfigurationSchema = z.object({ + installScript: appsInstallScriptSchema.optional().nullable(), + questionResponses: z + .record(z.union([z.string(), z.number(), z.boolean()])) + .optional(), +}); + +export const installScriptCurationSchema = z.object({ + name: z.string(), + url: z.string(), + script: appsInstallScriptSchema, +}); diff --git a/eshtek/routes.ts b/eshtek/routes.ts index c94437e..e60197b 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -170,6 +170,7 @@ export interface ResponseResourceChanges { target: string; changelog?: string; }; + partialUpdate?: Record; } export interface RequestAppDelete { diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index 4bb5206..9f16721 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -26,6 +26,7 @@ import { ServerActions, } from "./server"; +import { appsHealthSchema } from "./apps-schema"; import { vMSHealthSchema } from "./vms-schema"; export const serverUserTypeSchema = z.nativeEnum(ServerUserType); @@ -135,6 +136,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), @@ -220,8 +227,6 @@ const fileTypeSchema = z.any(); const topologyItemStatusSchema = z.any(); -const appsHealthSchema = z.any(); - const networkInterfaceTypeSchema = z.any(); export const serverPoolBasicsSchema = z.object({ @@ -316,12 +321,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/tasks.ts b/eshtek/tasks.ts index 52f421e..3c8d042 100644 --- a/eshtek/tasks.ts +++ b/eshtek/tasks.ts @@ -162,7 +162,7 @@ export type HexTaskDataMap = { [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 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..ff8603b 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", }, @@ -18,5 +18,11 @@ module.exports = [ name: "user", input: "eshtek/user.ts", output: "eshtek/user-schema.ts", - } + }, + { + name: "apps", + input: "eshtek/apps.ts", + output: "eshtek/apps-schema.ts", + }, + ]; From 4cc0bda0c45552c89e658fe14f413146a597c912 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 9 Dec 2025 16:06:58 -0500 Subject: [PATCH 24/26] remove apps-schema --- eshtek/apps-schema.ts | 223 ---------------------------------------- eshtek/server-schema.ts | 3 +- ts-to-zod.config.js | 8 +- 3 files changed, 3 insertions(+), 231 deletions(-) delete mode 100644 eshtek/apps-schema.ts diff --git a/eshtek/apps-schema.ts b/eshtek/apps-schema.ts deleted file mode 100644 index 887efa6..0000000 --- a/eshtek/apps-schema.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Generated by ts-to-zod -import { z } from "zod"; -import { - AppJobAction, - AppSpec, - AppPermission, - AppsError, - AppsWarning, - AppsActions, - InstallationQuestionType, -} from "./apps"; - -import { fileAccessSchema } from "./server-schema"; - -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 appMaintainerSchema = z.object({ - name: z.string(), - email: z.string(), -}); - -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), -}); - -export const installationQuestionTypeSchema = z.nativeEnum( - InstallationQuestionType, -); - -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(), - placeholder: z.string().optional(), - description: z.string().optional(), -}); - -const locationPreferenceIdSchema = z.any(); - -const appStateSchema = 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 = z.object({ - appId: z.string(), - name: z.string(), - train: z.union([z.literal("stable"), z.literal("community")]), - version: z.string(), - appVersion: z.string(), - description: z.string(), - icon: z.string(), - categories: z.array(z.string()), - keywords: z.array(z.string()), - maintainers: z.array(appMaintainerSchema), - screenshots: z.array(z.string()), - sources: z.array(z.string()), - homepage: z.string(), - recommended: z.boolean(), - supported: z.boolean(), - fresh: z.boolean(), - installScript: z.string().optional(), - requirements: appRequirementsSchema.optional(), -}); - -export const appInfoSchema = appBasicsSchema.extend({ - status: appStateSchema, - url_webui: z.string(), - upgradeAvailable: z.boolean(), - updatedScriptAvailable: z.boolean(), - latestVersion: z.string(), -}); - -export const appInfoDetailedSchema = appInfoSchema.extend({ - data: z.array(z.array(z.number())), -}); - -const appsInstallScriptV1Schema = z.object({ - version: z.literal(1), - requirements: appRequirementsSchema.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), -}); - -const appsInstallScriptV2Schema = z.object({ - version: z.literal(2), - requirements: appRequirementsSchema.optional(), - 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), -}); - -const appsInstallScriptV3Schema = appsInstallScriptV2Schema - .omit({ version: true, requirements: true }) - .extend({ - version: z.literal(3), - script: z.object({ - version: z.string(), - updateCompatibility: z.string().optional(), - changeLog: z.string().optional(), - }), - requirements: appRequirementsSchema, - }); - -export const appsInstallScriptSchema = z.union([ - appsInstallScriptV1Schema, - appsInstallScriptV2Schema, - appsInstallScriptV3Schema, -]); - -export const appConfigurationSchema = z.object({ - installScript: appsInstallScriptSchema.optional().nullable(), - questionResponses: z - .record(z.union([z.string(), z.number(), z.boolean()])) - .optional(), -}); - -export const installScriptCurationSchema = z.object({ - name: z.string(), - url: z.string(), - script: appsInstallScriptSchema, -}); diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index 9f16721..e0463ea 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -26,8 +26,9 @@ import { ServerActions, } from "./server"; -import { appsHealthSchema } from "./apps-schema"; import { vMSHealthSchema } from "./vms-schema"; +const appsHealthSchema = z.any(); + export const serverUserTypeSchema = z.nativeEnum(ServerUserType); diff --git a/ts-to-zod.config.js b/ts-to-zod.config.js index ff8603b..e02bcdf 100644 --- a/ts-to-zod.config.js +++ b/ts-to-zod.config.js @@ -18,11 +18,5 @@ module.exports = [ name: "user", input: "eshtek/user.ts", output: "eshtek/user-schema.ts", - }, - { - name: "apps", - input: "eshtek/apps.ts", - output: "eshtek/apps-schema.ts", - }, - + } ]; From a5e69b3efe2c653dde2707ec30fcb9f6d42a38a0 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Wed, 10 Dec 2025 16:42:54 -0500 Subject: [PATCH 25/26] preserve added to resource change action --- eshtek/routes.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/eshtek/routes.ts b/eshtek/routes.ts index e60197b..b13b785 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -138,7 +138,8 @@ export enum ResourceChangeAction { CREATE = 'create', UPDATE = 'update', DELETE = 'delete', - NO_OP = 'no-op' + NO_OP = 'no-op', + PRESERVE = 'preserve' } export interface ResponseResourceChanges { @@ -158,6 +159,7 @@ export interface ResponseResourceChanges { update: number; delete: number; noOp: number; + preserve: number; }; }; validation: { @@ -167,10 +169,9 @@ export interface ResponseResourceChanges { }; versionInfo: { current?: string; - target: string; + target?: string; changelog?: string; }; - partialUpdate?: Record; } export interface RequestAppDelete { From dd5e7b8e1af5e9c74f5cfd824a31b8ab874dc86b Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Tue, 16 Dec 2025 14:03:09 -0500 Subject: [PATCH 26/26] Adds app recommendation and config error handling Adds a flag to indicate if an app is recommended and introduces a new error code to handle cases where no current configuration is available during app updates. --- eshtek/apps.ts | 1 + eshtek/errors.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/eshtek/apps.ts b/eshtek/apps.ts index 38c6afb..87ab391 100644 --- a/eshtek/apps.ts +++ b/eshtek/apps.ts @@ -91,6 +91,7 @@ export interface AppInfo extends AppBasics { upgradeAvailable: boolean; updatedScriptAvailable: boolean; latestVersion: string; + recommended: boolean; } export interface AppInfoDetailed extends AppInfo { data: number[][]; diff --git a/eshtek/errors.ts b/eshtek/errors.ts index d1c3ee4..0dc831e 100644 --- a/eshtek/errors.ts +++ b/eshtek/errors.ts @@ -41,6 +41,7 @@ export enum GlobalErrorCode { 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_FAILED = 'UPDATE_APP_FAILED', + UPDATE_APP_NO_CURRENT_CONFIG = 'UPDATE_APP_NO_CURRENT_CONFIG', }