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..28b96fd 100644 --- a/eshtek/events.ts +++ b/eshtek/events.ts @@ -36,13 +36,13 @@ export const SystemEventNames = { SERVER_DISCONNECTED: 'server_disconnected', 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]; 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-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-status.ts b/eshtek/server-status.ts new file mode 100644 index 0000000..094ecc6 --- /dev/null +++ b/eshtek/server-status.ts @@ -0,0 +1,45 @@ +/** + * Server Status — 6 values representing what phase the server is in. + * + * 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? → 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) + * + * 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; +} \ No newline at end of file diff --git a/eshtek/server.ts b/eshtek/server.ts index eef5199..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 => { @@ -149,13 +150,14 @@ 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; + status?: ServerStatus; lastconnected?: Date | string; truenas_version?: string; } diff --git a/eshtek/tasks.ts b/eshtek/tasks.ts index abceacc..3507090 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