/** * EcoOS Updates View * Version info, available updates, and upgrade controls */ import { html, DeesElement, customElement, property, state, css, type TemplateResult, } from '@design.estate/dees-element'; import { sharedStyles, formatAge } from '../styles/shared.js'; import type { IUpdateInfo, IRelease } from '../../ts_interfaces/updates.js'; @customElement('ecoos-updates') export class EcoosUpdates extends DeesElement { @property({ type: Object }) public accessor updateInfo: IUpdateInfo | null = null; @state() private accessor loading: boolean = false; public static styles = [ sharedStyles, css` :host { display: block; padding: 20px; } .release-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--ecoos-border); } .release-item:last-child { border-bottom: none; } .release-version { font-weight: 500; } .release-age { color: var(--ecoos-text-dim); font-size: 12px; } .release-item .btn { padding: 4px 12px; margin: 0; font-size: 11px; } .auto-upgrade-status { margin-top: 16px; font-size: 12px; color: var(--ecoos-text-dim); } .actions { margin-top: 16px; } `, ]; render(): TemplateResult { if (!this.updateInfo) { return html`
Loading...
`; } const newerReleases = this.updateInfo.releases.filter(r => r.isNewer); return html`
Updates
Current Version
v${this.updateInfo.currentVersion}
${newerReleases.length === 0 ? html`
No updates available
` : newerReleases.map(r => this.renderRelease(r)) }
${this.renderAutoUpgradeStatus()}
`; } private renderRelease(release: IRelease): TemplateResult { return html`
v${release.version} (${formatAge(release.ageHours)})
`; } private renderAutoUpgradeStatus(): TemplateResult { const { autoUpgrade, lastCheck } = this.updateInfo!; if (autoUpgrade.targetVersion) { if (autoUpgrade.waitingForStability) { return html`Auto-upgrade to v${autoUpgrade.targetVersion} in ${autoUpgrade.scheduledIn} (stability period)`; } return html`Auto-upgrade to v${autoUpgrade.targetVersion} pending...`; } if (lastCheck) { return html`Last checked: ${new Date(lastCheck).toLocaleTimeString()}`; } return html``; } private async checkForUpdates(): Promise { this.loading = true; try { const response = await fetch('/api/updates/check', { method: 'POST' }); const result = await response.json(); this.updateInfo = result; this.dispatchEvent(new CustomEvent('updates-checked', { detail: result })); } catch (error) { console.error('Failed to check updates:', error); } finally { this.loading = false; } } private async upgradeToVersion(version: string): Promise { if (!confirm(`Upgrade to version ${version}? The daemon will restart.`)) return; this.loading = true; try { const response = await fetch('/api/upgrade', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ version }), }); const result = await response.json(); if (result.success) { this.dispatchEvent(new CustomEvent('upgrade-started', { detail: result })); } else { alert(`Upgrade failed: ${result.message}`); } } catch (error) { alert(`Upgrade error: ${error}`); } finally { this.loading = false; } } }