diff --git a/eshtek/admin.types.ts b/eshtek/admin.types.ts new file mode 100644 index 0000000..452ef54 --- /dev/null +++ b/eshtek/admin.types.ts @@ -0,0 +1,97 @@ +/** + * 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) + 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.ts b/eshtek/apps.ts index 2d74168..fac1756 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; } @@ -120,6 +140,7 @@ export interface InstallationQuestion { interface AppsInstallScriptV1 { version: 1; + requirements?: AppRequirements; ensure_directories_exists?: Array; ensure_permissions_exists?: Array<{ path: string; @@ -132,6 +153,7 @@ interface AppsInstallScriptV1 { interface AppsInstallScriptV2 { version: 2; + requirements?: AppRequirements; installation_questions?: InstallationQuestion[]; ensure_directories_exists?: Array; ensure_permissions_exists?: Array<{ 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..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..c9dc908 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -124,4 +124,46 @@ 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 { + 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..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({ 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 { 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 },