import { MorphFeature } from 'morphux'; import { Main } from './main'; import { formatUptime, ServiceState, setStatusState, StatusType, } from './utils'; const CELCIUS = 'ÂșC'; export class DashboardCameraRunner { private _Main: Main; container: HTMLDivElement = document.querySelector( '.ntsh_dashboard-camerarunner' ); connectionStatus: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-connectionstatus' ); connectionInfo: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-connectioninfo' ); rebootButton: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-reboot' ); processStatus: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-processstatus' ); processInfo: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-processinfo' ); restartButton: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-restart' ); uptimeInfo: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-uptime' ); errorContainer: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-error' ); errorText: HTMLDivElement = this.container.querySelector( '.ntsh_dashboard-camerarunner-errortext' ); constructor(Main: Main) { this._Main = Main; this.registerListeners(); } updateState(state: CameraRunnerStatus) { // ----------- Connection ----------- setStatusState( this.connectionStatus, { CONNECTING: 'yellow', CONNECTED: 'green', DISCONNECTED: 'red', FAILED: 'red', }[state.state] as StatusType ); this.connectionInfo.innerText = state.message ?? ''; // ----------- Process ----------- if (state.state != 'CONNECTED') { state.processStatus.state = 'STOPPED'; state.processStatus.message = 'Not connected'; state.processStatus.startTime = -1; this.restartButton.style.display = 'none'; this.rebootButton.style.display = 'none'; } else { this.rebootButton.style.display = 'flex'; if (state.processStatus.state == 'RUNNING') this.restartButton.style.display = 'flex'; else this.restartButton.style.display = 'none'; } setStatusState( this.processStatus, { RUNNING: 'green', STOPPED: 'gray', STARTING: 'yellow', PROBLEM: 'red', }[state.processStatus.state] as StatusType ); this.processInfo.innerText = state.processStatus.message ?? ''; // ----------- Uptime ----------- const uptimeSeconds = state.processStatus.startTime == -1 ? -1 : (Date.now() - state.processStatus.startTime) / 1000; this.uptimeInfo.innerText = formatUptime(uptimeSeconds); // ----------- Error ----------- const errors: string[] = []; if ((state?.error ?? '').trim().length > 0) errors.push(state.error); if ((state?.processStatus?.error ?? '').trim().length > 0) errors.push(state.processStatus.error); if (errors.length > 0) { this.errorText.innerText = errors.join('\n'); this.errorContainer.style.display = 'block'; } else { this.errorContainer.style.display = 'none'; this.errorText.innerText = ''; } this._Main.Logs.setCameraLogs(state.processStatus.output.current); } registerListeners() { this._Main.socket.on( 'cameraRunnerState', (state: CameraRunnerStatus) => { this.updateState(state); } ); this.restartButton.addEventListener('click', async () => { this.executeCommand( 'restart', 'Are you sure you want to restart the Camera Runner process?' ); }); this.rebootButton.addEventListener('click', async () => { this.executeCommand( 'reboot', 'Are you sure you want to reboot the Camera Runner machine?' ); }); } private async executeCommand(command: string, message: string) { const confirmed = await MorphFeature.Confirm({ title: 'Are you sure?', message, }); if (!confirmed) return; MorphFeature.Loader({ active: true, message: `Requesting Camera Runner ${command}...`, }); this._Main.socket.emit( 'cameraRunner', command, (response: { succeed: boolean; message?: string }) => { MorphFeature.Loader({ active: false }); if (!response.succeed) return MorphFeature.Alert({ title: 'Error', message: response.message, }); MorphFeature.Notification({ level: 'success', message: `Camera Runner is ${command}ing...`, }); } ); } } interface CameraRunnerStatus { state: ServiceState; message?: string; error?: string; processStatus: ProcessStatus; } export type ProcessStatusState = 'RUNNING' | 'STOPPED' | 'STARTING' | 'PROBLEM'; interface ProcessStatusSimple { state: ProcessStatusState; message?: string; error?: string; } interface ProcessStatus extends ProcessStatusSimple { startTime: number; output: { current: string[]; last: string[] }; }