Files
eco_os/ecoos_daemon/ts_web/elements/ecoos-app.ts

266 lines
6.9 KiB
TypeScript
Raw Normal View History

/**
* EcoOS App - Main application component
* Uses dees-simple-appdash as the dashboard shell
*/
import {
html,
DeesElement,
customElement,
state,
css,
type TemplateResult,
} from '@design.estate/dees-element';
import { DeesSimpleAppdash, type IView } from '@design.estate/dees-catalog';
import type { IStatus } from '../../ts_interfaces/status.js';
import type { IDisplayInfo } from '../../ts_interfaces/display.js';
import type { IUpdateInfo } from '../../ts_interfaces/updates.js';
import { EcoosOverview } from './ecoos-overview.js';
import { EcoosDevices } from './ecoos-devices.js';
import { EcoosDisplays } from './ecoos-displays.js';
import { EcoosUpdates } from './ecoos-updates.js';
import { EcoosLogs } from './ecoos-logs.js';
@customElement('ecoos-app')
export class EcoosApp extends DeesElement {
@state()
private accessor status: IStatus | null = null;
@state()
private accessor displays: IDisplayInfo[] = [];
@state()
private accessor updateInfo: IUpdateInfo | null = null;
@state()
private accessor initialVersion: string | null = null;
private ws: WebSocket | null = null;
private statusInterval: number | null = null;
private displaysInterval: number | null = null;
private updatesInterval: number | null = null;
public static styles = [
css`
:host {
display: block;
width: 100vw;
height: 100vh;
background: #0a0a0a;
}
dees-simple-appdash {
width: 100%;
height: 100%;
}
`,
];
private viewTabs: IView[] = [
{
name: 'Overview',
iconName: 'lucide:layoutGrid',
element: EcoosOverview,
},
{
name: 'Devices',
iconName: 'lucide:cpu',
element: EcoosDevices,
},
{
name: 'Displays',
iconName: 'lucide:monitor',
element: EcoosDisplays,
},
{
name: 'Updates',
iconName: 'lucide:download',
element: EcoosUpdates,
},
{
name: 'Logs',
iconName: 'lucide:scrollText',
element: EcoosLogs,
},
];
connectedCallback(): void {
super.connectedCallback();
this.startPolling();
this.connectWebSocket();
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.stopPolling();
this.disconnectWebSocket();
}
render(): TemplateResult {
return html`
<dees-simple-appdash
name="EcoOS Management"
.viewTabs=${this.viewTabs}
@view-select=${this.handleViewSelect}
></dees-simple-appdash>
`;
}
updated(changedProperties: Map<string, unknown>): void {
super.updated(changedProperties);
// Pass data to view components when they're rendered
this.updateViewData();
}
private updateViewData(): void {
// Find and update the active view component
const appdash = this.shadowRoot?.querySelector('dees-simple-appdash');
if (!appdash) return;
// Get the current view content
const overview = appdash.shadowRoot?.querySelector('ecoos-overview') as EcoosOverview;
const devices = appdash.shadowRoot?.querySelector('ecoos-devices') as EcoosDevices;
const displays = appdash.shadowRoot?.querySelector('ecoos-displays') as EcoosDisplays;
const updates = appdash.shadowRoot?.querySelector('ecoos-updates') as EcoosUpdates;
const logs = appdash.shadowRoot?.querySelector('ecoos-logs') as EcoosLogs;
if (overview && this.status) {
overview.status = this.status;
}
if (devices && this.status?.systemInfo) {
devices.systemInfo = this.status.systemInfo;
}
if (displays) {
displays.displays = this.displays;
}
if (updates && this.updateInfo) {
updates.updateInfo = this.updateInfo;
}
if (logs && this.status) {
logs.daemonLogs = this.status.logs || [];
logs.systemLogs = this.status.systemLogs || [];
}
}
private handleViewSelect(event: CustomEvent): void {
console.log('View selected:', event.detail.view.name);
// Trigger a data update for the new view
setTimeout(() => this.updateViewData(), 100);
}
private startPolling(): void {
// Initial fetches
this.fetchStatus();
this.fetchDisplays();
this.fetchUpdates();
// Periodic polling
this.statusInterval = window.setInterval(() => this.fetchStatus(), 3000);
this.displaysInterval = window.setInterval(() => this.fetchDisplays(), 5000);
this.updatesInterval = window.setInterval(() => this.fetchUpdates(), 60000);
}
private stopPolling(): void {
if (this.statusInterval) {
clearInterval(this.statusInterval);
this.statusInterval = null;
}
if (this.displaysInterval) {
clearInterval(this.displaysInterval);
this.displaysInterval = null;
}
if (this.updatesInterval) {
clearInterval(this.updatesInterval);
this.updatesInterval = null;
}
}
private connectWebSocket(): void {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws`;
this.ws = new WebSocket(wsUrl);
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data) as IStatus;
this.handleStatusUpdate(data);
} catch (e) {
console.error('WebSocket message parse error:', e);
}
};
this.ws.onclose = () => {
console.log('WebSocket disconnected, reconnecting in 3s...');
setTimeout(() => this.connectWebSocket(), 3000);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
private disconnectWebSocket(): void {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
private handleStatusUpdate(data: IStatus): void {
// Check for version change and reload if needed
if (data.version) {
if (this.initialVersion === null) {
this.initialVersion = data.version;
} else if (data.version !== this.initialVersion) {
console.log(`Version changed from ${this.initialVersion} to ${data.version}, reloading...`);
window.location.reload();
return;
}
}
this.status = data;
this.updateViewData();
}
private async fetchStatus(): Promise<void> {
try {
const response = await fetch('/api/status');
const data = await response.json() as IStatus;
this.handleStatusUpdate(data);
} catch (error) {
console.error('Failed to fetch status:', error);
}
}
private async fetchDisplays(): Promise<void> {
try {
const response = await fetch('/api/displays');
const data = await response.json();
this.displays = data.displays || [];
this.updateViewData();
} catch (error) {
console.error('Failed to fetch displays:', error);
}
}
private async fetchUpdates(): Promise<void> {
try {
const response = await fetch('/api/updates');
const data = await response.json() as IUpdateInfo;
this.updateInfo = data;
this.updateViewData();
} catch (error) {
console.error('Failed to fetch updates:', error);
}
}
}