secubox-openwrt/package/secubox/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js
CyberMind-FR 8a242cb229 fix(cdn-cache): Use correct field name for bandwidth saved stats
The RPCD returns 'bytes_saved' but the JS was looking for
'bandwidth_saved_bytes', causing the "BW Saved" stat to always show 0.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-03 11:32:47 +01:00

199 lines
7.8 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require poll';
'require secubox/kiss-theme';
var callStatus = rpc.declare({
object: 'luci.cdn-cache',
method: 'status',
expect: {}
});
var callStats = rpc.declare({
object: 'luci.cdn-cache',
method: 'stats',
expect: {}
});
var callCacheSize = rpc.declare({
object: 'luci.cdn-cache',
method: 'cache_size',
expect: {}
});
var callTopDomains = rpc.declare({
object: 'luci.cdn-cache',
method: 'top_domains',
expect: { domains: [] }
});
function formatBytes(bytes) {
if (!bytes) return '0 B';
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + units[i];
}
function formatUptime(seconds) {
if (!seconds) return '0s';
var days = Math.floor(seconds / 86400);
var hours = Math.floor((seconds % 86400) / 3600);
var minutes = Math.floor((seconds % 3600) / 60);
if (days) return days + 'd ' + hours + 'h';
if (hours) return hours + 'h ' + minutes + 'm';
return minutes + 'm';
}
return view.extend({
load: function() {
return Promise.all([
L.resolveDefault(callStatus(), {}),
L.resolveDefault(callStats(), {}),
L.resolveDefault(callCacheSize(), {}),
L.resolveDefault(callTopDomains(), { domains: [] })
]);
},
render: function(data) {
var status = data[0] || {};
var stats = data[1] || {};
var cacheSize = data[2] || {};
var topDomains = (data[3] && data[3].domains) || [];
var self = this;
// Setup polling
poll.add(function() {
return Promise.all([
L.resolveDefault(callStatus(), {}),
L.resolveDefault(callStats(), {}),
L.resolveDefault(callCacheSize(), {}),
L.resolveDefault(callTopDomains(), { domains: [] })
]).then(function(d) {
self.updateStats(d[0], d[1], d[2], d[3].domains || []);
});
}, 10);
return KissTheme.wrap([
// Header
KissTheme.E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;' }, [
KissTheme.E('div', {}, [
KissTheme.E('h1', { 'style': 'font-size:28px;font-weight:700;margin:0;' }, '💾 CDN Cache'),
KissTheme.E('p', { 'style': 'color:var(--kiss-muted);margin:4px 0 0;' }, 'Edge caching for media, firmware, and downloads')
]),
KissTheme.badge(status.running ? 'RUNNING' : 'STOPPED', status.running ? 'green' : 'red')
]),
// Stats Grid
KissTheme.E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin-bottom:20px;' }, [
KissTheme.stat(stats.hit_ratio || 0, 'Hit Ratio %', 'var(--kiss-green)'),
KissTheme.stat(formatBytes((cacheSize.used_kb || 0) * 1024), 'Cache Used'),
KissTheme.stat((stats.requests || 0).toLocaleString(), 'Requests'),
KissTheme.stat(formatBytes(stats.bytes_saved || 0), 'BW Saved', 'var(--kiss-cyan)')
]),
// Details Grid
KissTheme.E('div', { 'class': 'kiss-grid kiss-grid-3' }, [
// Service Info
KissTheme.E('div', { 'class': 'kiss-card kiss-panel-green' }, [
KissTheme.E('div', { 'class': 'kiss-card-title' }, '⚙️ Service'),
KissTheme.E('table', { 'class': 'kiss-table', 'style': 'margin-top:12px;' }, [
KissTheme.E('tbody', {}, [
this.tableRow('Version', status.version || 'Unknown'),
this.tableRow('Uptime', formatUptime(status.uptime || 0)),
this.tableRow('Cache Files', (status.cache_files || 0).toLocaleString()),
this.tableRow('Objects', (stats.unique_objects || 0).toLocaleString())
])
])
]),
// Hit/Miss Stats
KissTheme.E('div', { 'class': 'kiss-card kiss-panel-blue' }, [
KissTheme.E('div', { 'class': 'kiss-card-title' }, '🎯 Cache Performance'),
this.renderGauge(stats.hit_ratio || 0),
KissTheme.E('div', { 'style': 'display:flex;justify-content:space-between;margin-top:12px;font-size:13px;' }, [
KissTheme.E('span', {}, ['Hits: ', KissTheme.E('strong', { 'style': 'color:var(--kiss-green);' }, (stats.hits || 0).toLocaleString())]),
KissTheme.E('span', {}, ['Misses: ', KissTheme.E('strong', { 'style': 'color:var(--kiss-red);' }, (stats.misses || 0).toLocaleString())])
])
]),
// Storage
KissTheme.E('div', { 'class': 'kiss-card kiss-panel-purple' }, [
KissTheme.E('div', { 'class': 'kiss-card-title' }, '💿 Storage'),
KissTheme.E('div', { 'style': 'margin:16px 0;' }, [
KissTheme.E('div', { 'style': 'display:flex;justify-content:space-between;margin-bottom:6px;font-size:13px;' }, [
KissTheme.E('span', {}, (cacheSize.usage_percent || 0) + '% used'),
KissTheme.E('span', { 'style': 'color:var(--kiss-muted);' }, formatBytes((cacheSize.used_kb || 0) * 1024) + ' / ' + formatBytes((cacheSize.max_kb || 0) * 1024))
]),
KissTheme.E('div', { 'class': 'kiss-progress' }, [
KissTheme.E('div', { 'class': 'kiss-progress-fill', 'style': 'width:' + (cacheSize.usage_percent || 0) + '%;' })
])
]),
KissTheme.E('div', { 'style': 'text-align:center;padding:16px;background:rgba(0,200,83,0.05);border-radius:8px;margin-top:12px;' }, [
KissTheme.E('div', { 'style': 'font-family:Orbitron,monospace;font-size:24px;color:var(--kiss-green);' }, formatBytes(stats.bytes_saved || 0)),
KissTheme.E('div', { 'style': 'font-size:11px;color:var(--kiss-muted);text-transform:uppercase;letter-spacing:1px;' }, 'Bandwidth Saved')
])
])
]),
// Top Domains
KissTheme.E('div', { 'class': 'kiss-card', 'style': 'margin-top:16px;' }, [
KissTheme.E('div', { 'class': 'kiss-card-title' }, '🌐 Top Cached Domains'),
KissTheme.E('table', { 'class': 'kiss-table' }, [
KissTheme.E('thead', {}, [
KissTheme.E('tr', {}, [
KissTheme.E('th', {}, 'Domain'),
KissTheme.E('th', { 'style': 'text-align:right;' }, 'Hits'),
KissTheme.E('th', { 'style': 'text-align:right;' }, 'Traffic')
])
]),
KissTheme.E('tbody', { 'id': 'top-domains-body' },
topDomains.slice(0, 8).map(function(d) {
return KissTheme.E('tr', {}, [
KissTheme.E('td', { 'style': 'font-family:monospace;' }, d.name || 'unknown'),
KissTheme.E('td', { 'style': 'text-align:right;color:var(--kiss-green);' }, (d.hits || 0).toLocaleString()),
KissTheme.E('td', { 'style': 'text-align:right;color:var(--kiss-muted);' }, formatBytes(d.bytes || 0))
]);
})
)
])
])
], 'admin/services/cdn-cache');
},
tableRow: function(label, value) {
return KissTheme.E('tr', {}, [
KissTheme.E('td', { 'style': 'color:var(--kiss-muted);' }, label),
KissTheme.E('td', { 'style': 'font-family:monospace;' }, value)
]);
},
renderGauge: function(ratio) {
var r = 50, c = 2 * Math.PI * r;
var offset = c - (ratio / 100) * c;
return KissTheme.E('div', { 'style': 'text-align:center;margin:16px 0;' }, [
KissTheme.E('svg', { 'width': '120', 'height': '120', 'style': 'transform:rotate(-90deg);' }, [
KissTheme.E('circle', { 'cx': '60', 'cy': '60', 'r': String(r), 'fill': 'none', 'stroke': 'rgba(255,255,255,0.1)', 'stroke-width': '8' }),
KissTheme.E('circle', { 'cx': '60', 'cy': '60', 'r': String(r), 'fill': 'none', 'stroke': 'var(--kiss-green)', 'stroke-width': '8', 'stroke-linecap': 'round', 'stroke-dasharray': c, 'stroke-dashoffset': offset })
]),
KissTheme.E('div', { 'style': 'margin-top:-80px;font-family:Orbitron,monospace;font-size:24px;font-weight:700;color:var(--kiss-green);' }, ratio + '%')
]);
},
updateStats: function(status, stats, cacheSize, topDomains) {
// Update stats via DOM - poll refresh
var statValues = document.querySelectorAll('.kiss-stat-value');
if (statValues.length >= 4) {
statValues[0].textContent = (stats.hit_ratio || 0);
statValues[1].textContent = formatBytes((cacheSize.used_kb || 0) * 1024);
statValues[2].textContent = (stats.requests || 0).toLocaleString();
statValues[3].textContent = formatBytes(stats.bytes_saved || 0);
}
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});