secubox-openwrt/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js
CyberMind-FR edca533f07 feat(ui): Apply KISS theme with C3BOX sidebar to SecuBox views
- InterceptoR: Refactor to use shared KissTheme.wrap() module
  - Remove duplicate inline CSS (~200 lines)
  - Use shared theme for sidebar navigation

- IoT Guard: Update to KISS dark theme styling
  - Use KissTheme.wrap() with sidebar
  - Update stat cards to use KISS classes
  - Update device chips and anomaly table styling

- mitmproxy: Add KISS theme wrapper
  - Add KissTheme.wrap() for sidebar navigation
  - Update info card styling to match theme

- System Hub: Update to KISS theme
  - Add KissTheme.wrap() for sidebar navigation
  - Update quick actions to use kiss-btn class
  - Inject KISS-compatible extra styles for cards

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-11 12:09:42 +01:00

364 lines
13 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
'require secubox/kiss-theme';
var callStatus = rpc.declare({
object: 'luci.system-hub',
method: 'status',
expect: {}
});
var callHealth = rpc.declare({
object: 'luci.system-hub',
method: 'get_health',
expect: {}
});
var callServices = rpc.declare({
object: 'luci.system-hub',
method: 'list_services',
expect: {}
});
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function formatUptime(seconds) {
var d = Math.floor(seconds / 86400);
var h = Math.floor((seconds % 86400) / 3600);
var m = Math.floor((seconds % 3600) / 60);
return d + 'd ' + h + 'h ' + m + 'm';
}
function getHealthColor(percent) {
if (percent >= 80) return '#e74c3c';
if (percent >= 60) return '#f39c12';
return '#27ae60';
}
function getScoreColor(score) {
if (score >= 80) return '#27ae60';
if (score >= 60) return '#3498db';
if (score >= 40) return '#f39c12';
return '#e74c3c';
}
// Helper to extract health metrics from either get_health or status format
function extractHealth(health, status) {
var h = health || {};
var s = status || {};
var sh = s.health || {};
return {
cpuLoad: (h.cpu && h.cpu.load_1m) || sh.cpu_load || '0.00',
cpuUsage: (h.cpu && h.cpu.usage) || 0,
memPercent: (h.memory && h.memory.usage) || sh.mem_percent || 0,
memUsedKb: (h.memory && h.memory.used_kb) || sh.mem_used_kb || 0,
memTotalKb: (h.memory && h.memory.total_kb) || sh.mem_total_kb || 0,
diskPercent: (h.disk && h.disk.usage) || s.disk_percent || 0,
temperature: (h.temperature && h.temperature.value) || 0,
score: h.score || 0
};
}
return view.extend({
load: function() {
return Promise.all([
callStatus(),
callHealth().catch(function() { return {}; }),
callServices().catch(function() { return { services: [] }; })
]);
},
renderStatusCards: function(status, health) {
var uptime = formatUptime(status.uptime || 0);
var hostname = status.hostname || 'SecuBox';
var model = status.model || 'Unknown';
var metrics = extractHealth(health, status);
var serviceCount = status.service_count || 0;
return E('div', { 'class': 'sh-cards' }, [
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-icon', 'style': 'background:#3498db' }, '\uD83D\uDDA5'),
E('div', { 'class': 'sh-card-content' }, [
E('div', { 'class': 'sh-card-value', 'data-stat': 'hostname' }, hostname),
E('div', { 'class': 'sh-card-label' }, model)
])
]),
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-icon', 'style': 'background:#27ae60' }, '\u23F1'),
E('div', { 'class': 'sh-card-content' }, [
E('div', { 'class': 'sh-card-value', 'data-stat': 'uptime' }, uptime),
E('div', { 'class': 'sh-card-label' }, 'Uptime')
])
]),
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-icon', 'style': 'background:#9b59b6' }, '\uD83D\uDD27'),
E('div', { 'class': 'sh-card-content' }, [
E('div', { 'class': 'sh-card-value', 'data-stat': 'services' }, String(serviceCount)),
E('div', { 'class': 'sh-card-label' }, 'Services')
])
]),
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-icon', 'style': 'background:#e74c3c' }, '\uD83D\uDD25'),
E('div', { 'class': 'sh-card-content' }, [
E('div', { 'class': 'sh-card-value', 'data-stat': 'cpu' }, metrics.cpuLoad),
E('div', { 'class': 'sh-card-label' }, 'CPU Load')
])
]),
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-icon', 'style': 'background:#f39c12' }, '\uD83C\uDF21'),
E('div', { 'class': 'sh-card-content' }, [
E('div', { 'class': 'sh-card-value', 'data-stat': 'temp' }, metrics.temperature + '\u00B0C'),
E('div', { 'class': 'sh-card-label' }, 'Temperature')
])
]),
E('div', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-icon', 'data-stat': 'score-icon', 'style': 'background:' + getScoreColor(metrics.score) }, '\u2764'),
E('div', { 'class': 'sh-card-content' }, [
E('div', { 'class': 'sh-card-value', 'data-stat': 'score' }, metrics.score),
E('div', { 'class': 'sh-card-label' }, 'Health Score')
])
])
]);
},
renderResourceBars: function(health, status) {
var metrics = extractHealth(health, status);
var memUsed = metrics.memUsedKb * 1024;
var memTotal = metrics.memTotalKb * 1024;
var resources = [
{
id: 'memory',
label: 'Memory',
percent: metrics.memPercent,
detail: formatBytes(memUsed) + ' / ' + formatBytes(memTotal),
icon: '\uD83D\uDCBE'
},
{
id: 'disk',
label: 'Storage',
percent: metrics.diskPercent,
detail: metrics.diskPercent + '% used',
icon: '\uD83D\uDCBF'
},
{
id: 'cpu',
label: 'CPU Usage',
percent: metrics.cpuUsage,
detail: metrics.cpuUsage + '% active',
icon: '\u2699'
}
];
return E('div', { 'class': 'sh-section' }, [
E('h3', {}, 'Resource Usage'),
E('div', { 'class': 'sh-resources' },
resources.map(function(r) {
return E('div', { 'class': 'sh-resource' }, [
E('div', { 'class': 'sh-resource-header' }, [
E('span', {}, r.icon + ' ' + r.label),
E('span', { 'data-stat': r.id + '-detail' }, r.detail)
]),
E('div', { 'class': 'sh-resource-bar' }, [
E('div', {
'class': 'sh-resource-fill',
'data-stat': r.id + '-bar',
'style': 'width:' + r.percent + '%;background:' + getHealthColor(r.percent)
})
]),
E('div', { 'class': 'sh-resource-percent', 'data-stat': r.id + '-percent' }, r.percent + '%')
]);
})
)
]);
},
renderQuickActions: function() {
return E('div', { 'class': 'sh-section' }, [
E('h3', {}, ' Quick Actions'),
E('div', { 'class': 'sh-actions' }, [
E('button', {
'class': 'kiss-btn',
'click': function() { window.location.href = L.url('admin/system/system'); }
}, ' System Settings'),
E('button', {
'class': 'kiss-btn',
'click': function() { window.location.href = L.url('admin/system/reboot'); }
}, '🔄 Reboot'),
E('button', {
'class': 'kiss-btn',
'click': function() { window.location.href = L.url('admin/system/flash'); }
}, '📦 Backup/Flash'),
E('a', {
'href': L.url('admin/secubox/system/system-hub/health'),
'class': 'kiss-btn',
'style': 'text-decoration: none;'
}, ' Health Check')
])
]);
},
renderServicesTable: function(services) {
var serviceList = (services && services.services) || [];
var running = serviceList.filter(function(s) { return s.running; }).length;
var total = serviceList.length;
var topServices = serviceList.slice(0, 10);
var rows = topServices.map(function(svc) {
return E('tr', {}, [
E('td', {}, svc.name || '-'),
E('td', {}, E('span', {
'class': 'sh-status-badge',
'style': 'background:' + (svc.running ? '#27ae60' : '#e74c3c')
}, svc.running ? 'Running' : 'Stopped')),
E('td', {}, svc.enabled ? '\u2713' : '\u2717')
]);
});
if (rows.length === 0) {
rows.push(E('tr', {}, [
E('td', { 'colspan': '3', 'style': 'text-align:center;color:#999' }, 'No services found')
]));
}
return E('div', { 'class': 'sh-section' }, [
E('h3', {}, 'Services (' + running + '/' + total + ' running)'),
E('table', { 'class': 'table' }, [
E('thead', {}, E('tr', {}, [
E('th', {}, 'Service'),
E('th', {}, 'Status'),
E('th', {}, 'Enabled')
])),
E('tbody', {}, rows)
])
]);
},
render: function(data) {
var status = data[0] || {};
var health = data[1] || {};
var services = data[2] || {};
var self = this;
// Start polling for live updates
poll.add(function() {
return Promise.all([callStatus(), callHealth().catch(function() { return {}; })]).then(function(results) {
var s = results[0] || {};
var h = results[1] || {};
var m = extractHealth(h, s);
// Update cards
var uptimeEl = document.querySelector('[data-stat="uptime"]');
var cpuEl = document.querySelector('[data-stat="cpu"]');
var servicesEl = document.querySelector('[data-stat="services"]');
var tempEl = document.querySelector('[data-stat="temp"]');
var scoreEl = document.querySelector('[data-stat="score"]');
var scoreIcon = document.querySelector('[data-stat="score-icon"]');
if (uptimeEl) uptimeEl.textContent = formatUptime(s.uptime || 0);
if (cpuEl) cpuEl.textContent = m.cpuLoad;
if (servicesEl) servicesEl.textContent = String(s.service_count || 0);
if (tempEl) tempEl.textContent = m.temperature + '\u00B0C';
if (scoreEl) scoreEl.textContent = m.score;
if (scoreIcon) scoreIcon.style.background = getScoreColor(m.score);
// Update resource bars
var memUsed = m.memUsedKb * 1024;
var memTotal = m.memTotalKb * 1024;
var memBar = document.querySelector('[data-stat="memory-bar"]');
var memPercentEl = document.querySelector('[data-stat="memory-percent"]');
var memDetail = document.querySelector('[data-stat="memory-detail"]');
var diskBar = document.querySelector('[data-stat="disk-bar"]');
var diskPercentEl = document.querySelector('[data-stat="disk-percent"]');
var diskDetail = document.querySelector('[data-stat="disk-detail"]');
var cpuBar = document.querySelector('[data-stat="cpu-bar"]');
var cpuPercentEl = document.querySelector('[data-stat="cpu-percent"]');
var cpuDetail = document.querySelector('[data-stat="cpu-detail"]');
if (memBar) {
memBar.style.width = m.memPercent + '%';
memBar.style.background = getHealthColor(m.memPercent);
}
if (memPercentEl) memPercentEl.textContent = m.memPercent + '%';
if (memDetail) memDetail.textContent = formatBytes(memUsed) + ' / ' + formatBytes(memTotal);
if (diskBar) {
diskBar.style.width = m.diskPercent + '%';
diskBar.style.background = getHealthColor(m.diskPercent);
}
if (diskPercentEl) diskPercentEl.textContent = m.diskPercent + '%';
if (diskDetail) diskDetail.textContent = m.diskPercent + '% used';
if (cpuBar) {
cpuBar.style.width = m.cpuUsage + '%';
cpuBar.style.background = getHealthColor(m.cpuUsage);
}
if (cpuPercentEl) cpuPercentEl.textContent = m.cpuUsage + '%';
if (cpuDetail) cpuDetail.textContent = m.cpuUsage + '% active';
});
}, 5);
// Additional KISS-compatible styles
var extraStyles = `
.sh-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; margin-bottom: 24px; }
.sh-card { background: var(--kiss-card); border: 1px solid var(--kiss-line); border-radius: 10px; padding: 16px; display: flex; align-items: center; gap: 12px; }
.sh-card-icon { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; color: #fff; flex-shrink: 0; }
.sh-card-value { font-size: 18px; font-weight: 700; color: var(--kiss-text); }
.sh-card-label { font-size: 11px; color: var(--kiss-muted); }
.sh-section { background: var(--kiss-card); border: 1px solid var(--kiss-line); border-radius: 12px; padding: 20px; }
.sh-section h3 { margin: 0 0 16px 0; font-size: 16px; font-weight: 700; color: var(--kiss-text); }
.sh-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.sh-resources { display: flex; flex-direction: column; gap: 16px; }
.sh-resource-header { display: flex; justify-content: space-between; margin-bottom: 6px; font-size: 13px; color: var(--kiss-muted); }
.sh-resource-bar { height: 10px; background: rgba(255,255,255,0.06); border-radius: 5px; overflow: hidden; }
.sh-resource-fill { height: 100%; transition: width 0.3s, background 0.3s; border-radius: 5px; }
.sh-resource-percent { font-size: 11px; color: var(--kiss-muted); margin-top: 4px; text-align: right; }
.sh-status-badge { display: inline-block; padding: 3px 10px; border-radius: 4px; color: #fff; font-size: 10px; font-weight: 600; letter-spacing: 0.5px; }
`;
// Inject extra styles
if (!document.querySelector('#sh-kiss-extra')) {
var style = document.createElement('style');
style.id = 'sh-kiss-extra';
style.textContent = extraStyles;
document.head.appendChild(style);
}
var content = [
// Header
E('div', { 'style': 'margin-bottom: 24px;' }, [
E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0 0 8px 0;' }, '📊 System Hub'),
E('p', { 'style': 'color: var(--kiss-muted); margin: 0;' }, 'Unified system monitoring and management dashboard')
]),
// Status Cards
this.renderStatusCards(status, health),
// Two column grid
E('div', { 'class': 'kiss-grid kiss-grid-2', 'style': 'margin-bottom: 20px;' }, [
this.renderResourceBars(health, status),
this.renderQuickActions()
]),
// Services Table
this.renderServicesTable(services)
];
return KissTheme.wrap(content, 'admin/secubox/system/system-hub');
},
handleSave: null,
handleSaveApply: null,
handleReset: null
});