From 976d0aa544d2ff17eb54c68e912900fabd4404a9 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Sun, 16 Nov 2025 09:01:23 -0500 Subject: [PATCH 01/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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({