252 lines
11 KiB
TypeScript
252 lines
11 KiB
TypeScript
import { LitElement, html, css, property, state, customElement } from './plugins.js';
|
|
import type { CSSResult, TemplateResult } from './plugins.js';
|
|
import { sharedStyles, panelStyles, gaugeStyles, buttonStyles, speedtestStyles } from './sw-dash-styles.js';
|
|
import { SwDashTable } from './sw-dash-table.js';
|
|
|
|
export interface IMetricsData {
|
|
cache: {
|
|
hits: number;
|
|
misses: number;
|
|
errors: number;
|
|
bytesServedFromCache: number;
|
|
bytesFetched: number;
|
|
averageResponseTime: number;
|
|
};
|
|
network: {
|
|
totalRequests: number;
|
|
successfulRequests: number;
|
|
failedRequests: number;
|
|
timeouts: number;
|
|
averageLatency: number;
|
|
totalBytesTransferred: number;
|
|
};
|
|
update: {
|
|
totalChecks: number;
|
|
successfulChecks: number;
|
|
failedChecks: number;
|
|
updatesFound: number;
|
|
updatesApplied: number;
|
|
lastCheckTimestamp: number;
|
|
lastUpdateTimestamp: number;
|
|
};
|
|
connection: {
|
|
connectedClients: number;
|
|
totalConnectionAttempts: number;
|
|
successfulConnections: number;
|
|
failedConnections: number;
|
|
};
|
|
speedtest: {
|
|
lastDownloadSpeedMbps: number;
|
|
lastUploadSpeedMbps: number;
|
|
lastLatencyMs: number;
|
|
lastTestTimestamp: number;
|
|
testCount: number;
|
|
isOnline: boolean;
|
|
};
|
|
startTime: number;
|
|
uptime: number;
|
|
cacheHitRate: number;
|
|
networkSuccessRate: number;
|
|
resourceCount: number;
|
|
}
|
|
|
|
/**
|
|
* Overview panel component with metrics gauges and stats
|
|
*/
|
|
@customElement('sw-dash-overview')
|
|
export class SwDashOverview extends LitElement {
|
|
public static styles: CSSResult[] = [
|
|
sharedStyles,
|
|
panelStyles,
|
|
gaugeStyles,
|
|
buttonStyles,
|
|
speedtestStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
}
|
|
`
|
|
];
|
|
|
|
@property({ type: Object }) accessor metrics: IMetricsData | null = null;
|
|
@state() accessor speedtestRunning = false;
|
|
@state() accessor speedtestPhase: 'idle' | 'latency' | 'download' | 'upload' | 'complete' = 'idle';
|
|
@state() accessor speedtestProgress = 0;
|
|
@state() accessor speedtestElapsed = 0;
|
|
|
|
// Speedtest timing constants (must match service worker)
|
|
private static readonly TEST_DURATION_MS = 5000; // 5 seconds per test
|
|
private progressInterval: number | null = null;
|
|
|
|
private async runSpeedtest(): Promise<void> {
|
|
if (this.speedtestRunning) return;
|
|
this.speedtestRunning = true;
|
|
this.speedtestPhase = 'latency';
|
|
this.speedtestProgress = 0;
|
|
this.speedtestElapsed = 0;
|
|
|
|
// Start progress animation (total ~10.5s: latency ~0.5s + 5s download + 5s upload)
|
|
const totalEstimatedMs = 10500;
|
|
const startTime = Date.now();
|
|
|
|
this.progressInterval = window.setInterval(() => {
|
|
this.speedtestElapsed = Date.now() - startTime;
|
|
this.speedtestProgress = Math.min(100, (this.speedtestElapsed / totalEstimatedMs) * 100);
|
|
|
|
// Estimate phase based on elapsed time
|
|
if (this.speedtestElapsed < 500) {
|
|
this.speedtestPhase = 'latency';
|
|
} else if (this.speedtestElapsed < 5500) {
|
|
this.speedtestPhase = 'download';
|
|
} else {
|
|
this.speedtestPhase = 'upload';
|
|
}
|
|
}, 100);
|
|
|
|
try {
|
|
const response = await fetch('/sw-dash/speedtest');
|
|
const result = await response.json();
|
|
|
|
this.speedtestPhase = 'complete';
|
|
this.speedtestProgress = 100;
|
|
|
|
// Dispatch event to parent to update metrics
|
|
this.dispatchEvent(new CustomEvent('speedtest-complete', {
|
|
detail: result,
|
|
bubbles: true,
|
|
composed: true
|
|
}));
|
|
} catch (err) {
|
|
console.error('Speedtest failed:', err);
|
|
this.speedtestPhase = 'idle';
|
|
} finally {
|
|
if (this.progressInterval) {
|
|
window.clearInterval(this.progressInterval);
|
|
this.progressInterval = null;
|
|
}
|
|
// Keep showing complete state briefly, then reset
|
|
setTimeout(() => {
|
|
this.speedtestRunning = false;
|
|
this.speedtestPhase = 'idle';
|
|
this.speedtestProgress = 0;
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
private getPhaseLabel(): string {
|
|
switch (this.speedtestPhase) {
|
|
case 'latency': return 'Testing latency...';
|
|
case 'download': return 'Download test...';
|
|
case 'upload': return 'Upload test...';
|
|
case 'complete': return 'Complete!';
|
|
default: return '';
|
|
}
|
|
}
|
|
|
|
private formatElapsed(): string {
|
|
const seconds = Math.floor(this.speedtestElapsed / 1000);
|
|
return `${seconds}s`;
|
|
}
|
|
|
|
public render(): TemplateResult {
|
|
if (!this.metrics) {
|
|
return html`<div class="panel">Loading metrics...</div>`;
|
|
}
|
|
|
|
const m = this.metrics;
|
|
const gaugeClass = SwDashTable.getGaugeClass;
|
|
|
|
return html`
|
|
<div class="grid">
|
|
<!-- Cache Panel -->
|
|
<div class="panel">
|
|
<div class="panel-title">[ CACHE ]</div>
|
|
<div class="gauge">
|
|
<div class="gauge-bar">
|
|
<div class="gauge-fill ${gaugeClass(m.cacheHitRate)}" style="width: ${m.cacheHitRate}%"></div>
|
|
<span class="gauge-text">${m.cacheHitRate}% hit rate</span>
|
|
</div>
|
|
</div>
|
|
<div class="row"><span class="label">Hits:</span><span class="value success">${SwDashTable.formatNumber(m.cache.hits)}</span></div>
|
|
<div class="row"><span class="label">Misses:</span><span class="value warning">${SwDashTable.formatNumber(m.cache.misses)}</span></div>
|
|
<div class="row"><span class="label">Errors:</span><span class="value ${m.cache.errors > 0 ? 'error' : ''}">${SwDashTable.formatNumber(m.cache.errors)}</span></div>
|
|
<div class="row"><span class="label">From Cache:</span><span class="value">${SwDashTable.formatBytes(m.cache.bytesServedFromCache)}</span></div>
|
|
<div class="row"><span class="label">Fetched:</span><span class="value">${SwDashTable.formatBytes(m.cache.bytesFetched)}</span></div>
|
|
<div class="row"><span class="label">Resources:</span><span class="value">${m.resourceCount}</span></div>
|
|
</div>
|
|
|
|
<!-- Network Panel -->
|
|
<div class="panel">
|
|
<div class="panel-title">[ NETWORK ]</div>
|
|
<div class="gauge">
|
|
<div class="gauge-bar">
|
|
<div class="gauge-fill ${gaugeClass(m.networkSuccessRate)}" style="width: ${m.networkSuccessRate}%"></div>
|
|
<span class="gauge-text">${m.networkSuccessRate}% success</span>
|
|
</div>
|
|
</div>
|
|
<div class="row"><span class="label">Total Requests:</span><span class="value">${SwDashTable.formatNumber(m.network.totalRequests)}</span></div>
|
|
<div class="row"><span class="label">Successful:</span><span class="value success">${SwDashTable.formatNumber(m.network.successfulRequests)}</span></div>
|
|
<div class="row"><span class="label">Failed:</span><span class="value ${m.network.failedRequests > 0 ? 'error' : ''}">${SwDashTable.formatNumber(m.network.failedRequests)}</span></div>
|
|
<div class="row"><span class="label">Timeouts:</span><span class="value ${m.network.timeouts > 0 ? 'warning' : ''}">${SwDashTable.formatNumber(m.network.timeouts)}</span></div>
|
|
<div class="row"><span class="label">Avg Latency:</span><span class="value">${m.network.averageLatency}ms</span></div>
|
|
<div class="row"><span class="label">Transferred:</span><span class="value">${SwDashTable.formatBytes(m.network.totalBytesTransferred)}</span></div>
|
|
</div>
|
|
|
|
<!-- Updates Panel -->
|
|
<div class="panel">
|
|
<div class="panel-title">[ UPDATES ]</div>
|
|
<div class="row"><span class="label">Total Checks:</span><span class="value">${SwDashTable.formatNumber(m.update.totalChecks)}</span></div>
|
|
<div class="row"><span class="label">Successful:</span><span class="value success">${SwDashTable.formatNumber(m.update.successfulChecks)}</span></div>
|
|
<div class="row"><span class="label">Failed:</span><span class="value ${m.update.failedChecks > 0 ? 'error' : ''}">${SwDashTable.formatNumber(m.update.failedChecks)}</span></div>
|
|
<div class="row"><span class="label">Updates Found:</span><span class="value">${SwDashTable.formatNumber(m.update.updatesFound)}</span></div>
|
|
<div class="row"><span class="label">Updates Applied:</span><span class="value success">${SwDashTable.formatNumber(m.update.updatesApplied)}</span></div>
|
|
<div class="row"><span class="label">Last Check:</span><span class="value">${SwDashTable.formatTimestamp(m.update.lastCheckTimestamp)}</span></div>
|
|
</div>
|
|
|
|
<!-- Connections Panel -->
|
|
<div class="panel">
|
|
<div class="panel-title">[ CONNECTIONS ]</div>
|
|
<div class="row"><span class="label">Active Clients:</span><span class="value success">${SwDashTable.formatNumber(m.connection.connectedClients)}</span></div>
|
|
<div class="row"><span class="label">Total Attempts:</span><span class="value">${SwDashTable.formatNumber(m.connection.totalConnectionAttempts)}</span></div>
|
|
<div class="row"><span class="label">Successful:</span><span class="value success">${SwDashTable.formatNumber(m.connection.successfulConnections)}</span></div>
|
|
<div class="row"><span class="label">Failed:</span><span class="value ${m.connection.failedConnections > 0 ? 'error' : ''}">${SwDashTable.formatNumber(m.connection.failedConnections)}</span></div>
|
|
<div class="row" style="margin-top: 15px; padding-top: 10px; border-top: 1px dashed var(--sw-border);">
|
|
<span class="label">Started:</span><span class="value">${SwDashTable.formatTimestamp(m.startTime)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Speedtest Panel -->
|
|
<div class="panel">
|
|
<div class="panel-title">[ SPEEDTEST ]</div>
|
|
<div class="online-indicator">
|
|
<span class="online-dot ${m.speedtest.isOnline ? 'online' : 'offline'}"></span>
|
|
<span class="value ${m.speedtest.isOnline ? 'success' : 'error'}">${m.speedtest.isOnline ? 'Online' : 'Offline'}</span>
|
|
</div>
|
|
${this.speedtestRunning ? html`
|
|
<div class="speedtest-progress">
|
|
<div class="progress-header">
|
|
<span class="progress-phase">${this.getPhaseLabel()}</span>
|
|
<span class="progress-time">${this.formatElapsed()}</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill ${this.speedtestPhase === 'complete' ? 'complete' : ''}" style="width: ${this.speedtestProgress}%"></div>
|
|
</div>
|
|
</div>
|
|
` : html`
|
|
<div class="row"><span class="label">Download:</span><span class="value">${m.speedtest.lastDownloadSpeedMbps.toFixed(2)} Mbps</span></div>
|
|
<div class="speed-bar"><div class="speed-fill" style="width: ${Math.min(m.speedtest.lastDownloadSpeedMbps, 100)}%"></div></div>
|
|
<div class="row"><span class="label">Upload:</span><span class="value">${m.speedtest.lastUploadSpeedMbps.toFixed(2)} Mbps</span></div>
|
|
<div class="speed-bar"><div class="speed-fill" style="width: ${Math.min(m.speedtest.lastUploadSpeedMbps, 100)}%"></div></div>
|
|
<div class="row"><span class="label">Latency:</span><span class="value">${m.speedtest.lastLatencyMs.toFixed(0)} ms</span></div>
|
|
`}
|
|
<div class="btn-row">
|
|
<button class="btn" ?disabled="${this.speedtestRunning}" @click="${this.runSpeedtest}">
|
|
${this.speedtestRunning ? 'Testing...' : 'Run Test'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|