208 lines
7.3 KiB
JavaScript
208 lines
7.3 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require rpc';
|
|
'require secubox-theme/theme as Theme';
|
|
'require cdn-cache/nav as CdnNav';
|
|
|
|
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(2) + ' ' + 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 ' + (seconds % 60) + 's';
|
|
}
|
|
|
|
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
|
|
(document.documentElement && document.documentElement.getAttribute('lang')) ||
|
|
(navigator.language ? navigator.language.split('-')[0] : 'en');
|
|
Theme.init({ language: lang });
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return Promise.all([
|
|
callStatus(),
|
|
callStats(),
|
|
callCacheSize(),
|
|
callTopDomains()
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var status = data[0] || {};
|
|
var stats = data[1] || {};
|
|
var cacheSize = data[2] || {};
|
|
var topDomains = (data[3] && data[3].domains) || [];
|
|
|
|
return E('div', { 'class': 'cdn-dashboard' }, [
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('cdn-cache/dashboard.css') }),
|
|
CdnNav.renderTabs('overview'),
|
|
this.renderHeader(status),
|
|
this.renderMetricGrid(stats, cacheSize),
|
|
this.renderSections(stats, cacheSize, topDomains)
|
|
]);
|
|
},
|
|
|
|
renderHeader: function(status) {
|
|
var stats = [
|
|
{ icon: '🏷️', label: _('Version'), value: status.version || _('Unknown') },
|
|
{ icon: '🟢', label: _('Service'), value: status.running ? _('Running') : _('Stopped'), tone: status.running ? 'success' : 'danger' },
|
|
{ icon: '⏱', label: _('Uptime'), value: formatUptime(status.uptime || 0) },
|
|
{ icon: '📁', label: _('Cache files'), value: (status.cache_files || 0).toLocaleString() }
|
|
];
|
|
|
|
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
|
|
E('div', {}, [
|
|
E('h2', { 'class': 'sh-page-title' }, [
|
|
E('span', { 'class': 'sh-page-title-icon' }, '📦'),
|
|
_('CDN Cache Control')
|
|
]),
|
|
E('p', { 'class': 'sh-page-subtitle' },
|
|
_('Edge caching for media, firmware, and downloads'))
|
|
]),
|
|
E('div', { 'class': 'sh-header-meta' }, stats.map(this.renderHeaderChip, this))
|
|
]);
|
|
},
|
|
|
|
renderHeaderChip: function(stat) {
|
|
return E('div', { 'class': 'sh-header-chip' + (stat.tone ? ' ' + stat.tone : '') }, [
|
|
E('span', { 'class': 'sh-chip-icon' }, stat.icon || '•'),
|
|
E('div', { 'class': 'sh-chip-text' }, [
|
|
E('span', { 'class': 'sh-chip-label' }, stat.label),
|
|
E('strong', {}, stat.value)
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderMetricGrid: function(stats, cacheSize) {
|
|
return E('section', { 'class': 'cdn-metric-grid' }, [
|
|
this.renderMetricCard('🎯', _('Hit Ratio'), (stats.hit_ratio || 0) + '%', (stats.hits || 0).toLocaleString() + ' hits / ' + (stats.misses || 0).toLocaleString() + ' misses'),
|
|
this.renderMetricCard('💾', _('Cache Used'), formatBytes((cacheSize.used_kb || 0) * 1024), (cacheSize.usage_percent || 0) + '% of ' + formatBytes((cacheSize.max_kb || 0) * 1024)),
|
|
this.renderMetricCard('📊', _('Requests'), (stats.requests || 0).toLocaleString(), _('Objects cached: ') + (stats.unique_objects || 0)),
|
|
this.renderMetricCard('⚡', _('Bandwidth Saved'), formatBytes(stats.bandwidth_saved_bytes || 0), _('Local delivery savings'))
|
|
]);
|
|
},
|
|
|
|
renderMetricCard: function(icon, label, value, sub) {
|
|
return E('div', { 'class': 'cdn-metric-card' }, [
|
|
E('div', { 'class': 'cdn-card-icon' }, icon),
|
|
E('div', { 'class': 'cdn-metric-label' }, label),
|
|
E('div', { 'class': 'cdn-metric-value' }, value),
|
|
E('div', { 'class': 'cdn-metric-sub' }, sub)
|
|
]);
|
|
},
|
|
|
|
renderSections: function(stats, cacheSize, topDomains) {
|
|
return E('div', { 'class': 'cdn-sections-grid' }, [
|
|
E('div', { 'class': 'cdn-section' }, [
|
|
E('div', { 'class': 'cdn-section-header' }, [
|
|
E('div', { 'class': 'cdn-section-title' }, ['🎯', _('Hit Ratio')]),
|
|
E('span', { 'class': 'sb-badge sb-badge-ghost' }, _('Real-time'))
|
|
]),
|
|
this.renderRatioGauge(stats.hit_ratio || 0),
|
|
E('div', { 'class': 'cdn-domain-stats' }, [
|
|
_('Hits: ') + (stats.hits || 0).toLocaleString(),
|
|
_('Misses: ') + (stats.misses || 0).toLocaleString(),
|
|
_('Requests: ') + (stats.requests || 0).toLocaleString()
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cdn-section' }, [
|
|
E('div', { 'class': 'cdn-section-header' }, [
|
|
E('div', { 'class': 'cdn-section-title' }, ['🌐', _('Top Domains Served')]),
|
|
E('span', { 'class': 'sb-badge sb-badge-ghost' }, _('Last hour'))
|
|
]),
|
|
E('ul', { 'class': 'cdn-domain-list' },
|
|
topDomains.slice(0, 6).map(function(domain) {
|
|
return E('li', { 'class': 'cdn-domain-item' }, [
|
|
E('div', { 'class': 'cdn-domain-name' }, domain.name || _('Unknown')),
|
|
E('div', { 'class': 'cdn-domain-stats' }, [
|
|
_('Hits: ') + (domain.hits || 0).toLocaleString(),
|
|
_('Traffic: ') + formatBytes(domain.bytes || 0)
|
|
])
|
|
]);
|
|
}))
|
|
]),
|
|
E('div', { 'class': 'cdn-section' }, [
|
|
E('div', { 'class': 'cdn-section-header' }, [
|
|
E('div', { 'class': 'cdn-section-title' }, ['💾', _('Cache Utilisation')])
|
|
]),
|
|
this.renderProgress(_('Usage'), cacheSize.usage_percent || 0, formatBytes((cacheSize.used_kb || 0) * 1024) + ' / ' + formatBytes((cacheSize.max_kb || 0) * 1024)),
|
|
E('div', { 'class': 'cdn-savings-card' }, [
|
|
E('div', { 'class': 'cdn-savings-value' }, formatBytes(stats.bandwidth_saved_bytes || 0)),
|
|
E('div', { 'class': 'cdn-savings-label' }, _('Bandwidth saved this week'))
|
|
])
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderRatioGauge: function(ratio) {
|
|
var radius = 60;
|
|
var circumference = 2 * Math.PI * radius;
|
|
var normalized = Math.min(100, Math.max(0, ratio));
|
|
var offset = circumference - normalized / 100 * circumference;
|
|
return E('div', { 'class': 'cdn-ratio-circle' }, [
|
|
E('svg', { 'width': '140', 'height': '140', 'class': 'cdn-ratio-spark' }, [
|
|
E('circle', { 'class': 'bg', 'cx': '70', 'cy': '70', 'r': String(radius) }),
|
|
E('circle', {
|
|
'class': 'fg',
|
|
'cx': '70',
|
|
'cy': '70',
|
|
'r': String(radius),
|
|
'style': 'stroke-dasharray:' + circumference + ';stroke-dashoffset:' + offset + ';'
|
|
})
|
|
]),
|
|
E('div', { 'class': 'cdn-ratio-value' }, normalized + '%')
|
|
]);
|
|
},
|
|
|
|
renderProgress: function(label, percent, meta) {
|
|
return E('div', { 'class': 'cdn-progress-stack' }, [
|
|
E('div', { 'class': 'secubox-stat-details' }, [
|
|
E('div', { 'class': 'secubox-stat-label' }, label),
|
|
E('div', { 'class': 'secubox-stat-value' }, percent + '%')
|
|
]),
|
|
E('div', { 'class': 'cdn-progress-bar' }, [
|
|
E('div', { 'class': 'cdn-progress-fill', 'style': 'width:' + Math.min(100, Math.max(0, percent)) + '%;' })
|
|
]),
|
|
E('div', { 'class': 'secubox-stat-label' }, meta)
|
|
]);
|
|
}
|
|
});
|