From a344c05343974d59d91096eb59330ed36ba50d69 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Fri, 13 Feb 2026 14:27:07 -0500 Subject: [PATCH 1/4] Updates server record schema Removes the `apikey` field from the server record schema. This was needed for the now removed websocket implementation and no longer necessary on the front Adds the `localAccessDomain` field to the server record schema, allowing for redirection and other efforts. --- eshtek/server-schema.ts | 2 +- eshtek/server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eshtek/server-schema.ts b/eshtek/server-schema.ts index 4bb5206..edff876 100644 --- a/eshtek/server-schema.ts +++ b/eshtek/server-schema.ts @@ -64,10 +64,10 @@ export const serverPoolWarningSchema = z.nativeEnum(ServerPoolWarning); export const serverRecordSchema = z.object({ hostid: z.string(), email: z.string().optional(), - apikey: z.string().optional(), lanip: z.string().optional(), wanip: z.string().optional(), nodehost: z.string().optional(), + localAccessDomain: z.string().optional(), connected: z.union([z.literal("N"), z.literal("Y")]).optional(), servername: z.string().optional(), wizardcompleted: z.union([z.date(), z.string()]).optional(), diff --git a/eshtek/server.ts b/eshtek/server.ts index eef5199..c78d01b 100644 --- a/eshtek/server.ts +++ b/eshtek/server.ts @@ -149,10 +149,10 @@ export interface Servers { export interface ServerRecord { hostid: string; email?: string; - apikey?: string; lanip?: string; wanip?: string; nodehost?: string; + localAccessDomain?: string; connected?: 'N' | 'Y'; servername?: string; wizardcompleted?: Date | string; From 22eadefaea32d3b88e3b8135cda39b602dd6a81a Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Wed, 18 Feb 2026 15:46:32 -0500 Subject: [PATCH 2/4] Provisioning state tracking using Events Additional SystemEvents for provisioning Generic EventTaskType enum, event task types and state New ServerStatus interface and explanation of server state Removes `Wizard`/`WizardJobs` types and `wizard` from `DDPClientMeta` --- eshtek/ddp.ts | 25 --------------------- eshtek/events.ts | 38 +++++++++++++++++++++++++++---- eshtek/misc.ts | 7 ++++++ eshtek/routes.ts | 2 +- eshtek/server-status.ts | 50 +++++++++++++++++++++++++++++++++++++++++ eshtek/server.ts | 2 ++ 6 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 eshtek/server-status.ts diff --git a/eshtek/ddp.ts b/eshtek/ddp.ts index ba127a9..26b741d 100644 --- a/eshtek/ddp.ts +++ b/eshtek/ddp.ts @@ -1,29 +1,5 @@ -import type { JobState } from '../truenas/webui/enums/job-state.enum'; import type { ReportingRealtimeUpdate } from '../truenas/webui/interfaces/reporting.interface'; -export interface WizardJob { - name: WizardJobs; - id?: number; - status: JobState; - progress: number; - message?: string; - referenceId?: string | number; -} -export interface Wizard { - jobs: WizardJob[]; -} - -export enum WizardJobs { - Initializing = 'Initializing', - ReformattingDrives = 'Preparing Drives', - CreatingPools = 'Creating Pools', - EnablingDocker = 'Enabling Docker', - UpdatingNetworkInterface = 'Updating Network Interface', - AssigningServerName = 'Assigning Server Name', - UpdatingServices = 'Updating Services', - Complete = 'Complete', -} - export interface Data { name: string; data?: T; @@ -34,7 +10,6 @@ export interface DDPClientMeta { hostId: string; lanIp: string; wanIp: string; - wizard?: Wizard; buildTime?: Date; buildVersion?: string; userId?: string; diff --git a/eshtek/events.ts b/eshtek/events.ts index d0a8bc9..35e5e9a 100644 --- a/eshtek/events.ts +++ b/eshtek/events.ts @@ -1,5 +1,14 @@ import type { HexTaskType } from './tasks'; +export enum EventTaskType { + PROVISION_WIPE_DISKS = 'PROVISION_WIPE_DISKS', + PROVISION_CREATE_POOLS = 'PROVISION_CREATE_POOLS', + PROVISION_VALIDATE_STORAGE = 'PROVISION_VALIDATE_STORAGE', + PROVISION_CONFIGURE_NETWORK = 'PROVISION_CONFIGURE_NETWORK', + PROVISION_CONFIGURE_DOCKER = 'PROVISION_CONFIGURE_DOCKER', + PROVISION_INSTALL_HEXOS = 'PROVISION_INSTALL_HEXOS', +} + export interface BaseEvent { eventName: string; userId: string; @@ -34,15 +43,22 @@ export type TaskEventName = `${Lowercase<`${HexTaskType}`>}_${EventState}`; export const SystemEventNames = { SERVER_CONNECTED: 'server_connected', SERVER_DISCONNECTED: 'server_disconnected', + SERVER_CLAIMED: 'server_claimed', + SERVER_UNCLAIMED: 'server_unclaimed', + SERVER_RESET: 'server_reset', + SERVER_SETUP_STARTED: 'server_setup_started', + SERVER_SETUP_COMPLETED: 'server_setup_completed', + SERVER_SETUP_FAILED: 'server_setup_failed', + TASK: 'task', // Separate from HexTasks ie. EventTaskTypes USER_LOGIN: 'user_login', USER_LOGOUT: 'user_logout', - DRIVE_UTILIZED: 'drive_utilized', // Drive added to a pool + DRIVE_UTILIZED: 'drive_utilized', 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 + DRIVE_HEALTHY: 'drive_healthy', + DRIVE_DISCOVERED: 'drive_discovered', + APP_DISCOVERED: 'app_discovered', } as const; export type SystemEventName = typeof SystemEventNames[keyof typeof SystemEventNames]; @@ -133,4 +149,18 @@ export interface EventsStatsParams { appId?: string; startDate?: string; endDate?: string; +} + +export interface EventTask { + taskType: EventTaskType; + status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'SKIPPED'; + progress: number; + errorMessage?: string; + updatedAt?: string; +} + +export interface EventTaskStatusData { + tasks: EventTask[]; + isComplete: boolean; + hasFailed: boolean; } \ No newline at end of file diff --git a/eshtek/misc.ts b/eshtek/misc.ts index 01d43fd..df77a5f 100644 --- a/eshtek/misc.ts +++ b/eshtek/misc.ts @@ -1,4 +1,11 @@ export interface IP { ip: string; ipv6?: string; +} + +export interface ConnectionState { + isMainConnected: boolean; + mode: "local" | "main" | "both"; + mainDeckUrl: string; + ip: IP; } \ No newline at end of file diff --git a/eshtek/routes.ts b/eshtek/routes.ts index fc50044..c6deffa 100644 --- a/eshtek/routes.ts +++ b/eshtek/routes.ts @@ -73,7 +73,7 @@ export interface RequestUser extends ServerUser { type: ServerUserType; } -export interface RequestFinishServer { +export interface RequestProvisionServer { hostId: string; pools: ServerPool[]; } diff --git a/eshtek/server-status.ts b/eshtek/server-status.ts new file mode 100644 index 0000000..3f9442f --- /dev/null +++ b/eshtek/server-status.ts @@ -0,0 +1,50 @@ +/** + * Server Status — 6 values representing what phase the server is in. + * + * Three columns encode server state: + * status — "What phase is this server in?" (6 values below) + * userId — "Who owns this server?" (null = unclaimed, string = claimed) + * wizardCompletedAt — "Has setup finished?" (null = not yet, ISO string = when) + * + * wizardCompletedAt is set when all provisioning tasks complete and cleared on reset/disconnect. + * + * Everything else is derived: + * Unclaimed? → userId === null + * In setup? → isServerInSetup(status) + * Operational? → isServerConnected(status) + * Provisioning progress? → Derived from events (eventName='task', EventTaskType per hostId) + * + * Status transitions: + * Server discovered → OFFLINE (userId null) + * Claim → CLAIMED (+ server_claimed event) + * Start provisioning → PROVISIONING (+ task events with EventTaskType per provisioning task) + * Provisioning done → OFFLINE → syncServerStatus → ONLINE / WAIT_FOR_LOCAL_NODE / etc. + * Reset → OFFLINE (userId, wizardCompletedAt cleared + server_reset event; claimable again) + * Unclaim/Disconnect → OFFLINE (userId, wizardCompletedAt cleared + server_unclaimed event; claimable again) + * + * syncServerStatus (post-setup only, guarded by isServerInSetup + userId): + * No TN connection → OFFLINE + * Docker not running → WAIT_FOR_LOCAL_NODE + * HexOS app missing → REINSTALL_HEXOS (auto-recovery) + * No hex connection → WAIT_FOR_LOCAL_NODE + * All good → ONLINE + */ + +export enum ServerStatus { + OFFLINE = "OFFLINE", + CLAIMED = "CLAIMED", + PROVISIONING = "PROVISIONING", + ONLINE = "ONLINE", + WAIT_FOR_LOCAL_NODE = "WAIT_FOR_LOCAL_NODE", + REINSTALL_HEXOS = "REINSTALL_HEXOS", +} + +export function isServerConnected(status?: string): boolean { + // CLAIMED and PROVISIONING have a TrueNAS connection but no local node — + // the frontend should NOT connect WebSocket or enable dashboard features. + return status === ServerStatus.ONLINE || status === ServerStatus.WAIT_FOR_LOCAL_NODE || status === ServerStatus.REINSTALL_HEXOS; +} + +export function isServerInSetup(status?: string): boolean { + return status === ServerStatus.CLAIMED || status === ServerStatus.PROVISIONING; +} diff --git a/eshtek/server.ts b/eshtek/server.ts index c78d01b..192831f 100644 --- a/eshtek/server.ts +++ b/eshtek/server.ts @@ -5,6 +5,7 @@ import type { NetworkInterfaceType } from '../truenas/webui/enums/network-interf import type { PoolStatus } from '../truenas/webui/enums/pool-status.enum'; import type { TopologyItemStatus } from '../truenas/webui/enums/vdev-status.enum'; import type { AppsHealth } from './apps'; +import type { ServerStatus } from './server-status'; import type { VMSHealth } from './vms'; export const cleanCPUModel = (model: string): string => { @@ -156,6 +157,7 @@ export interface ServerRecord { connected?: 'N' | 'Y'; servername?: string; wizardcompleted?: Date | string; + status?: ServerStatus; lastconnected?: Date | string; truenas_version?: string; } From 0b64560d2bf2a112f96accf951373ee184688653 Mon Sep 17 00:00:00 2001 From: Jerod Fritz Date: Thu, 19 Feb 2026 00:23:59 -0500 Subject: [PATCH 3/4] Continues with Tasks on Main for provisioning Adds a `ServerStatus` enum to represent server states. Refactors task types to differentiate between main provisioning tasks and subtasks. Removes wizard related types and renames `RequestFinishServer` to `RequestProvisionServer` --- eshtek/events.ts | 30 ------------------- eshtek/server-status.ts | 21 +++++-------- eshtek/tasks.ts | 65 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 47 deletions(-) diff --git a/eshtek/events.ts b/eshtek/events.ts index 35e5e9a..28b96fd 100644 --- a/eshtek/events.ts +++ b/eshtek/events.ts @@ -1,14 +1,5 @@ import type { HexTaskType } from './tasks'; -export enum EventTaskType { - PROVISION_WIPE_DISKS = 'PROVISION_WIPE_DISKS', - PROVISION_CREATE_POOLS = 'PROVISION_CREATE_POOLS', - PROVISION_VALIDATE_STORAGE = 'PROVISION_VALIDATE_STORAGE', - PROVISION_CONFIGURE_NETWORK = 'PROVISION_CONFIGURE_NETWORK', - PROVISION_CONFIGURE_DOCKER = 'PROVISION_CONFIGURE_DOCKER', - PROVISION_INSTALL_HEXOS = 'PROVISION_INSTALL_HEXOS', -} - export interface BaseEvent { eventName: string; userId: string; @@ -43,13 +34,6 @@ export type TaskEventName = `${Lowercase<`${HexTaskType}`>}_${EventState}`; export const SystemEventNames = { SERVER_CONNECTED: 'server_connected', SERVER_DISCONNECTED: 'server_disconnected', - SERVER_CLAIMED: 'server_claimed', - SERVER_UNCLAIMED: 'server_unclaimed', - SERVER_RESET: 'server_reset', - SERVER_SETUP_STARTED: 'server_setup_started', - SERVER_SETUP_COMPLETED: 'server_setup_completed', - SERVER_SETUP_FAILED: 'server_setup_failed', - TASK: 'task', // Separate from HexTasks ie. EventTaskTypes USER_LOGIN: 'user_login', USER_LOGOUT: 'user_logout', DRIVE_UTILIZED: 'drive_utilized', @@ -149,18 +133,4 @@ export interface EventsStatsParams { appId?: string; startDate?: string; endDate?: string; -} - -export interface EventTask { - taskType: EventTaskType; - status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'SKIPPED'; - progress: number; - errorMessage?: string; - updatedAt?: string; -} - -export interface EventTaskStatusData { - tasks: EventTask[]; - isComplete: boolean; - hasFailed: boolean; } \ No newline at end of file diff --git a/eshtek/server-status.ts b/eshtek/server-status.ts index 3f9442f..094ecc6 100644 --- a/eshtek/server-status.ts +++ b/eshtek/server-status.ts @@ -1,26 +1,22 @@ /** * Server Status — 6 values representing what phase the server is in. * - * Three columns encode server state: - * status — "What phase is this server in?" (6 values below) - * userId — "Who owns this server?" (null = unclaimed, string = claimed) - * wizardCompletedAt — "Has setup finished?" (null = not yet, ISO string = when) - * - * wizardCompletedAt is set when all provisioning tasks complete and cleared on reset/disconnect. + * Two columns encode server state: + * status — "What phase is this server in?" (6 values below) + * userId — "Who owns this server?" (null = unclaimed, string = claimed) * * Everything else is derived: * Unclaimed? → userId === null * In setup? → isServerInSetup(status) * Operational? → isServerConnected(status) - * Provisioning progress? → Derived from events (eventName='task', EventTaskType per hostId) + * Provisioning progress? → Read from main tasks (PROVISION_* children of PROVISIONING) * * Status transitions: * Server discovered → OFFLINE (userId null) - * Claim → CLAIMED (+ server_claimed event) - * Start provisioning → PROVISIONING (+ task events with EventTaskType per provisioning task) + * Claim → SETUP + * Start provisioning → PROVISIONING * Provisioning done → OFFLINE → syncServerStatus → ONLINE / WAIT_FOR_LOCAL_NODE / etc. - * Reset → OFFLINE (userId, wizardCompletedAt cleared + server_reset event; claimable again) - * Unclaim/Disconnect → OFFLINE (userId, wizardCompletedAt cleared + server_unclaimed event; claimable again) + * Reset / disconnect → OFFLINE (userId cleared) * * syncServerStatus (post-setup only, guarded by isServerInSetup + userId): * No TN connection → OFFLINE @@ -29,7 +25,6 @@ * No hex connection → WAIT_FOR_LOCAL_NODE * All good → ONLINE */ - export enum ServerStatus { OFFLINE = "OFFLINE", CLAIMED = "CLAIMED", @@ -47,4 +42,4 @@ export function isServerConnected(status?: string): boolean { export function isServerInSetup(status?: string): boolean { return status === ServerStatus.CLAIMED || status === ServerStatus.PROVISIONING; -} +} \ No newline at end of file diff --git a/eshtek/tasks.ts b/eshtek/tasks.ts index ddb5d46..f45e753 100644 --- a/eshtek/tasks.ts +++ b/eshtek/tasks.ts @@ -54,6 +54,20 @@ export enum HexTaskType { POOLS_DELETE_ALL = 'POOLS_DELETE_ALL', DRIVE_REPLACE = 'DRIVE_REPLACE', DOCKER_UPDATE = 'DOCKER_UPDATE', + + // Main server tasks (provisioning — stored in main MySQL, not local SQLite) + PROVISIONING = 'PROVISIONING', + PROVISION_WIPE_DISKS = 'PROVISION_WIPE_DISKS', + PROVISION_CREATE_POOLS = 'PROVISION_CREATE_POOLS', + PROVISION_VALIDATE_STORAGE = 'PROVISION_VALIDATE_STORAGE', + PROVISION_CONFIGURE_NETWORK = 'PROVISION_CONFIGURE_NETWORK', + PROVISION_CONFIGURE_DOCKER = 'PROVISION_CONFIGURE_DOCKER', + PROVISION_INSTALL_HEXOS = 'PROVISION_INSTALL_HEXOS', + + // Grandchild tasks (individual disk/pool tracking under WIPE_DISKS / CREATE_POOLS) + PROVISION_EXPORT_POOL = 'PROVISION_EXPORT_POOL', + PROVISION_WIPE_DISK = 'PROVISION_WIPE_DISK', + PROVISION_CREATE_POOL = 'PROVISION_CREATE_POOL', } @@ -70,7 +84,7 @@ export type HexTaskUpdate = { active?: boolean; data?: HexTaskDataMap[T]['data']; hostId?: string; - parentTaskId?: string; + parentTaskId?: number; }; export type HexTaskWithChildren = HexTaskBase & HexTaskDataMap[T] & { childTasks: HexTask[]; @@ -82,11 +96,12 @@ export enum HexTaskStatus { IN_PROGRESS = 'IN_PROGRESS', COMPLETED = 'COMPLETED', FAILED = 'FAILED', + SKIPPED = 'SKIPPED', DISMISSED = 'DISMISSED', } export const enforceCompletionProgress = (status: HexTaskStatus, progress: number): number => { - return status === HexTaskStatus.COMPLETED ? 100 : progress; + return (status === HexTaskStatus.COMPLETED || status === HexTaskStatus.SKIPPED) ? 100 : progress; }; export const parseTaskData = ( @@ -112,7 +127,7 @@ type HexTaskDataBase = { jobId?: number | number[]; }; -type HexTaskMeta = { +type HexTaskMeta = { hostId: string; data: TData; parentTaskId?: TParent; @@ -167,6 +182,20 @@ export type HexTaskDataMap = { [HexTaskType.PREFERENCE_LOCATION_PATH_MIGRATION]: HexTaskMeta; [HexTaskType.DRIVE_REPLACE]: HexTaskMeta; [HexTaskType.DOCKER_UPDATE]: HexTaskMeta; + + // Main server tasks (provisioning) + [HexTaskType.PROVISIONING]: HexTaskMeta; + [HexTaskType.PROVISION_WIPE_DISKS]: HexTaskMeta; + [HexTaskType.PROVISION_CREATE_POOLS]: HexTaskMeta; + [HexTaskType.PROVISION_VALIDATE_STORAGE]: HexTaskMeta; + [HexTaskType.PROVISION_CONFIGURE_NETWORK]: HexTaskMeta; + [HexTaskType.PROVISION_CONFIGURE_DOCKER]: HexTaskMeta; + [HexTaskType.PROVISION_INSTALL_HEXOS]: HexTaskMeta; + + // Grandchild tasks (individual disk/pool tracking) + [HexTaskType.PROVISION_EXPORT_POOL]: HexTaskMeta; + [HexTaskType.PROVISION_WIPE_DISK]: HexTaskMeta; + [HexTaskType.PROVISION_CREATE_POOL]: HexTaskMeta; }; // This looks a little strange with duplicated code, but we need a runtime const avail for the utils file @@ -200,4 +229,32 @@ export const HexTaskSettings: { [HexTaskType.PREFERENCE_LOCATION_PATH_MIGRATION]: { canHaveMultiple: false, predictedSecondsToComplete: 1200 }, [HexTaskType.DRIVE_REPLACE]: { canHaveMultiple: true, predictedSecondsToComplete: 120 }, [HexTaskType.DOCKER_UPDATE]: { canHaveMultiple: false, predictedSecondsToComplete: 120 }, -}; \ No newline at end of file + + // Main server tasks (provisioning) + [HexTaskType.PROVISIONING]: { canHaveMultiple: false }, + [HexTaskType.PROVISION_WIPE_DISKS]: { canHaveMultiple: false }, + [HexTaskType.PROVISION_CREATE_POOLS]: { canHaveMultiple: false }, + [HexTaskType.PROVISION_VALIDATE_STORAGE]: { canHaveMultiple: false }, + [HexTaskType.PROVISION_CONFIGURE_NETWORK]: { canHaveMultiple: false }, + [HexTaskType.PROVISION_CONFIGURE_DOCKER]: { canHaveMultiple: false }, + [HexTaskType.PROVISION_INSTALL_HEXOS]: { canHaveMultiple: false }, + + // Grandchild tasks (individual disk/pool tracking) + [HexTaskType.PROVISION_EXPORT_POOL]: { canHaveMultiple: true }, + [HexTaskType.PROVISION_WIPE_DISK]: { canHaveMultiple: true }, + [HexTaskType.PROVISION_CREATE_POOL]: { canHaveMultiple: true }, +}; + +// Task types stored in the main MySQL database (provisioning tasks) +export const MAIN_TASK_TYPES: readonly HexTaskType[] = [ + HexTaskType.PROVISIONING, + HexTaskType.PROVISION_WIPE_DISKS, + HexTaskType.PROVISION_CREATE_POOLS, + HexTaskType.PROVISION_VALIDATE_STORAGE, + HexTaskType.PROVISION_CONFIGURE_NETWORK, + HexTaskType.PROVISION_CONFIGURE_DOCKER, + HexTaskType.PROVISION_INSTALL_HEXOS, + HexTaskType.PROVISION_EXPORT_POOL, + HexTaskType.PROVISION_WIPE_DISK, + HexTaskType.PROVISION_CREATE_POOL, +]; \ No newline at end of file From 362b2a46647d40b96d66b722b2dfb6536eedf922 Mon Sep 17 00:00:00 2001 From: Garrett Date: Tue, 24 Feb 2026 12:21:06 -0600 Subject: [PATCH 4/4] Misc tweaks --- eshtek/server-status.ts | 79 +++++++++++++++++++++++++---------------- eshtek/server.ts | 4 +-- eshtek/tasks.ts | 4 +++ 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/eshtek/server-status.ts b/eshtek/server-status.ts index 094ecc6..293db25 100644 --- a/eshtek/server-status.ts +++ b/eshtek/server-status.ts @@ -1,45 +1,62 @@ /** - * Server Status — 6 values representing what phase the server is in. + * Server Status * - * Two columns encode server state: - * status — "What phase is this server in?" (6 values below) - * userId — "Who owns this server?" (null = unclaimed, string = claimed) + * Three columns encode server state: + * status - "Is the main server connected to TrueNAS and/or the local node?" (4 values below) + * userId - "Who owns this server?" (null = unclaimed, string = claimed) + * provisionStatus - "Has provisioning started, failed, completed?" (4 values below) * * Everything else is derived: - * Unclaimed? → userId === null - * In setup? → isServerInSetup(status) - * Operational? → isServerConnected(status) - * Provisioning progress? → Read from main tasks (PROVISION_* children of PROVISIONING) + * Unclaimed (Offline) → OFFLINE && userId == null + * Unclaimed (Online) → (TN_ONLY || ONLINE) && userId == null + * Claimed (Offline, not prov) → OFFLINE && userId != null && provisionStatus == NOT_STARTED + * Claimed (Online, not prov) → (TN_ONLY || ONLINE) && userId != null && provisionStatus == NOT_STARTED + * Provisioning → (TN_ONLY || ONLINE) && userId != null && provisionStatus == IN_PROGRESS + * Provisioned (Online) → ONLINE + * Provisioned (Offline) → OFFLINE && userId != null && provisionStatus == SUCCESS + * Provisioning progress? → Read from main tasks (PROVISION_* children of PROVISIONING) * - * Status transitions: - * Server discovered → OFFLINE (userId null) - * Claim → SETUP - * Start provisioning → PROVISIONING - * Provisioning done → OFFLINE → syncServerStatus → ONLINE / WAIT_FOR_LOCAL_NODE / etc. - * Reset / disconnect → OFFLINE (userId cleared) + * Transitions: + * TN → Main Connect + * If no DB server, create with userId == null & return early + * If no API key, return early + * If auth fails, Clear userId & return early + * Call syncServerStatus() + * TN → Main Disconnect → Call syncServerStatus() + * Claim → Set userId & apiKey + * Configure Setup → Set provisionStatus to IN_PROGRESS + * Provision Finish → Set provisionStatus to SUCCESS/FAILED & Call syncServerStatus() + * Hex → Main Connect → Call syncServerStatus() + * Hex → Main Disconnect → Call syncServerStatus() + * Reset/Unclaim → Clear userId & apiKey & Set provisionStatus to NOT_STARTED * - * syncServerStatus (post-setup only, guarded by isServerInSetup + userId): - * No TN connection → OFFLINE - * Docker not running → WAIT_FOR_LOCAL_NODE - * HexOS app missing → REINSTALL_HEXOS (auto-recovery) - * No hex connection → WAIT_FOR_LOCAL_NODE - * All good → ONLINE + * syncServerStatus + * Updates status based on TN connection and/or Hex connection + * If provisionStatus == SUCCESS && TN_ONLY + * if docker is running + * Put them back into setup + * else + * Attempt to reinstall/restart/start HexOS app */ + +export enum ServerProvisionStatus { + NOT_STARTED = "NOT_STARTED", + IN_PROGRESS = "IN_PROGRESS", + SUCCESS = "SUCCESS", + FAILED = "FAILED", +} + export enum ServerStatus { OFFLINE = "OFFLINE", - CLAIMED = "CLAIMED", - PROVISIONING = "PROVISIONING", + TN_ONLY = "TN_ONLY", + LOCAL_ONLY = "LOCAL_ONLY", ONLINE = "ONLINE", - WAIT_FOR_LOCAL_NODE = "WAIT_FOR_LOCAL_NODE", - REINSTALL_HEXOS = "REINSTALL_HEXOS", } -export function isServerConnected(status?: string): boolean { - // CLAIMED and PROVISIONING have a TrueNAS connection but no local node — - // the frontend should NOT connect WebSocket or enable dashboard features. - return status === ServerStatus.ONLINE || status === ServerStatus.WAIT_FOR_LOCAL_NODE || status === ServerStatus.REINSTALL_HEXOS; +export function isServerConnected(status: ServerStatus): boolean { + return status === ServerStatus.ONLINE; } -export function isServerInSetup(status?: string): boolean { - return status === ServerStatus.CLAIMED || status === ServerStatus.PROVISIONING; -} \ No newline at end of file +export function isServerInSetup(status: ServerProvisionStatus): boolean { + return status !== ServerProvisionStatus.SUCCESS; +} diff --git a/eshtek/server.ts b/eshtek/server.ts index 192831f..6a5ccde 100644 --- a/eshtek/server.ts +++ b/eshtek/server.ts @@ -5,7 +5,7 @@ import type { NetworkInterfaceType } from '../truenas/webui/enums/network-interf import type { PoolStatus } from '../truenas/webui/enums/pool-status.enum'; import type { TopologyItemStatus } from '../truenas/webui/enums/vdev-status.enum'; import type { AppsHealth } from './apps'; -import type { ServerStatus } from './server-status'; +import type { ServerProvisionStatus, ServerStatus } from './server-status'; import type { VMSHealth } from './vms'; export const cleanCPUModel = (model: string): string => { @@ -156,7 +156,7 @@ export interface ServerRecord { localAccessDomain?: string; connected?: 'N' | 'Y'; servername?: string; - wizardcompleted?: Date | string; + provisionStatus?: ServerProvisionStatus; status?: ServerStatus; lastconnected?: Date | string; truenas_version?: string; diff --git a/eshtek/tasks.ts b/eshtek/tasks.ts index 3507090..20c7975 100644 --- a/eshtek/tasks.ts +++ b/eshtek/tasks.ts @@ -63,6 +63,7 @@ export enum HexTaskType { PROVISION_CONFIGURE_NETWORK = 'PROVISION_CONFIGURE_NETWORK', PROVISION_CONFIGURE_DOCKER = 'PROVISION_CONFIGURE_DOCKER', PROVISION_INSTALL_HEXOS = 'PROVISION_INSTALL_HEXOS', + PROVISION_FINALIZE = 'PROVISION_FINALIZE', // Grandchild tasks (individual disk/pool tracking under WIPE_DISKS / CREATE_POOLS) PROVISION_EXPORT_POOL = 'PROVISION_EXPORT_POOL', @@ -191,6 +192,7 @@ export type HexTaskDataMap = { [HexTaskType.PROVISION_CONFIGURE_NETWORK]: HexTaskMeta; [HexTaskType.PROVISION_CONFIGURE_DOCKER]: HexTaskMeta; [HexTaskType.PROVISION_INSTALL_HEXOS]: HexTaskMeta; + [HexTaskType.PROVISION_FINALIZE]: HexTaskMeta; // Grandchild tasks (individual disk/pool tracking) [HexTaskType.PROVISION_EXPORT_POOL]: HexTaskMeta; @@ -238,6 +240,7 @@ export const HexTaskSettings: { [HexTaskType.PROVISION_CONFIGURE_NETWORK]: { canHaveMultiple: false }, [HexTaskType.PROVISION_CONFIGURE_DOCKER]: { canHaveMultiple: false }, [HexTaskType.PROVISION_INSTALL_HEXOS]: { canHaveMultiple: false }, + [HexTaskType.PROVISION_FINALIZE]: { canHaveMultiple: false }, // Grandchild tasks (individual disk/pool tracking) [HexTaskType.PROVISION_EXPORT_POOL]: { canHaveMultiple: true }, @@ -254,6 +257,7 @@ export const MAIN_TASK_TYPES: readonly HexTaskType[] = [ HexTaskType.PROVISION_CONFIGURE_NETWORK, HexTaskType.PROVISION_CONFIGURE_DOCKER, HexTaskType.PROVISION_INSTALL_HEXOS, + HexTaskType.PROVISION_FINALIZE, HexTaskType.PROVISION_EXPORT_POOL, HexTaskType.PROVISION_WIPE_DISK, HexTaskType.PROVISION_CREATE_POOL,