266 lines
6.9 KiB
TypeScript
266 lines
6.9 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|
|
}
|
|
}
|