Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions eshtek/ddp.ts
Original file line number Diff line number Diff line change
@@ -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<T = unknown> {
name: string;
data?: T;
Expand All @@ -34,7 +10,6 @@ export interface DDPClientMeta {
hostId: string;
lanIp: string;
wanIp: string;
wizard?: Wizard;
buildTime?: Date;
buildVersion?: string;
userId?: string;
Expand Down
8 changes: 4 additions & 4 deletions eshtek/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
7 changes: 7 additions & 0 deletions eshtek/misc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
export interface IP {
ip: string;
ipv6?: string;
}

export interface ConnectionState {
isMainConnected: boolean;
mode: "local" | "main" | "both";
mainDeckUrl: string;
ip: IP;
}
2 changes: 1 addition & 1 deletion eshtek/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export interface RequestUser extends ServerUser {
type: ServerUserType;
}

export interface RequestFinishServer {
export interface RequestProvisionServer {
hostId: string;
pools: ServerPool[];
}
Expand Down
45 changes: 45 additions & 0 deletions eshtek/server-status.ts
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 2 additions & 0 deletions eshtek/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -156,6 +157,7 @@ export interface ServerRecord {
connected?: 'N' | 'Y';
servername?: string;
wizardcompleted?: Date | string;
status?: ServerStatus;
lastconnected?: Date | string;
truenas_version?: string;
}
Expand Down
65 changes: 61 additions & 4 deletions eshtek/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}


Expand All @@ -70,7 +84,7 @@ export type HexTaskUpdate<T extends HexTaskType = HexTaskType> = {
active?: boolean;
data?: HexTaskDataMap[T]['data'];
hostId?: string;
parentTaskId?: string;
parentTaskId?: number;
};
export type HexTaskWithChildren<T extends HexTaskType = HexTaskType> = HexTaskBase<T> & HexTaskDataMap[T] & {
childTasks: HexTask[];
Expand All @@ -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 = <T extends HexTaskType>(
Expand All @@ -112,7 +127,7 @@ type HexTaskDataBase = {
jobId?: number | number[];
};

type HexTaskMeta<TData extends HexTaskDataBase, TParent extends string | never = never> = {
type HexTaskMeta<TData extends HexTaskDataBase, TParent extends number | never = never> = {
hostId: string;
data: TData;
parentTaskId?: TParent;
Expand Down Expand Up @@ -167,6 +182,20 @@ export type HexTaskDataMap = {
[HexTaskType.PREFERENCE_LOCATION_PATH_MIGRATION]: HexTaskMeta<HexTaskDataBase & { locationPreferenceId: string; oldPath: string; newPath: string }, never>;
[HexTaskType.DRIVE_REPLACE]: HexTaskMeta<HexTaskDataBase & { poolId: number; devname: string; newDevname: string; label: string; disk: string }, string>;
[HexTaskType.DOCKER_UPDATE]: HexTaskMeta<HexTaskDataBase & { poolName?: string; }, string>;

// Main server tasks (provisioning)
[HexTaskType.PROVISIONING]: HexTaskMeta<HexTaskDataBase & { serverName?: string }, never>;
[HexTaskType.PROVISION_WIPE_DISKS]: HexTaskMeta<HexTaskDataBase, string>;
[HexTaskType.PROVISION_CREATE_POOLS]: HexTaskMeta<HexTaskDataBase, string>;
[HexTaskType.PROVISION_VALIDATE_STORAGE]: HexTaskMeta<HexTaskDataBase, string>;
[HexTaskType.PROVISION_CONFIGURE_NETWORK]: HexTaskMeta<HexTaskDataBase, string>;
[HexTaskType.PROVISION_CONFIGURE_DOCKER]: HexTaskMeta<HexTaskDataBase, string>;
[HexTaskType.PROVISION_INSTALL_HEXOS]: HexTaskMeta<HexTaskDataBase, string>;

// Grandchild tasks (individual disk/pool tracking)
[HexTaskType.PROVISION_EXPORT_POOL]: HexTaskMeta<HexTaskDataBase & { poolId: number; name: string }, number>;
[HexTaskType.PROVISION_WIPE_DISK]: HexTaskMeta<HexTaskDataBase & { devname: string }, number>;
[HexTaskType.PROVISION_CREATE_POOL]: HexTaskMeta<HexTaskDataBase & { name: string }, number>;
};

// This looks a little strange with duplicated code, but we need a runtime const avail for the utils file
Expand Down Expand Up @@ -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 },
};

// 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,
];