diff --git a/luci-app-secubox/htdocs/luci-static/resources/secubox/alerts.css b/luci-app-secubox/htdocs/luci-static/resources/secubox/alerts.css new file mode 100644 index 00000000..52aeedfc --- /dev/null +++ b/luci-app-secubox/htdocs/luci-static/resources/secubox/alerts.css @@ -0,0 +1,281 @@ +/* SecuBox Alerts Page Styles */ + +.secubox-alerts-page { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +/* Page Header */ +.secubox-page-header { + background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%); + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + color: white; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 16px; +} + +.secubox-page-header h2 { + margin: 0 0 4px 0; + font-size: 28px; + font-weight: 700; +} + +.secubox-page-subtitle { + margin: 0; + opacity: 0.9; + font-size: 14px; +} + +.secubox-header-actions { + display: flex; + gap: 8px; +} + +/* Controls */ +.secubox-alerts-controls { + background: white; + border-radius: 12px; + padding: 20px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + display: flex; + gap: 20px; + flex-wrap: wrap; + align-items: end; +} + +.secubox-filter-group { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 200px; +} + +.secubox-filter-group label { + font-weight: 600; + font-size: 13px; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Stats */ +.secubox-alerts-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.secubox-alert-stat-card { + background: white; + border-radius: 12px; + padding: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + gap: 16px; +} + +/* Alerts Container */ +.secubox-alerts-container { + display: flex; + flex-direction: column; + gap: 12px; + max-height: 800px; + overflow-y: auto; + padding: 4px; +} + +.secubox-alerts-container::-webkit-scrollbar { + width: 8px; +} + +.secubox-alerts-container::-webkit-scrollbar-track { + background: #f1f5f9; + border-radius: 4px; +} + +.secubox-alerts-container::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} + +.secubox-alerts-container::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* Alert Item */ +.secubox-alert-item { + background: white; + border-radius: 8px; + padding: 16px; + display: flex; + gap: 16px; + align-items: flex-start; + transition: all 0.2s; + border: 2px solid transparent; + position: relative; +} + +.secubox-alert-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateX(4px); +} + +.secubox-alert-item.secubox-alert-error { + border-left: 4px solid #ef4444; + background: #fef2f2; +} + +.secubox-alert-item.secubox-alert-warning { + border-left: 4px solid #f59e0b; + background: #fffbeb; +} + +.secubox-alert-item.secubox-alert-info { + border-left: 4px solid #3b82f6; + background: #eff6ff; +} + +.secubox-alert-icon-badge { + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + color: white; + flex-shrink: 0; +} + +.secubox-alert-details { + flex: 1; +} + +.secubox-alert-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.secubox-alert-module { + font-size: 16px; + color: #1e293b; +} + +.secubox-alert-time { + font-size: 12px; + color: #64748b; +} + +.secubox-alert-message { + color: #475569; + line-height: 1.5; + margin-bottom: 8px; +} + +.secubox-alert-footer { + display: flex; + gap: 8px; +} + +.secubox-badge { + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.secubox-badge-error { + background: #fef2f2; + color: #991b1b; + border: 1px solid #fecaca; +} + +.secubox-badge-warning { + background: #fffbeb; + color: #92400e; + border: 1px solid #fde68a; +} + +.secubox-badge-info { + background: #eff6ff; + color: #1e40af; + border: 1px solid #bfdbfe; +} + +.secubox-alert-dismiss { + position: absolute; + top: 12px; + right: 12px; + width: 24px; + height: 24px; + border-radius: 50%; + border: none; + background: rgba(0, 0, 0, 0.05); + color: #64748b; + cursor: pointer; + font-size: 18px; + line-height: 1; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.secubox-alert-dismiss:hover { + background: #ef4444; + color: white; +} + +/* Empty State */ +.secubox-empty-state { + text-align: center; + padding: 60px 20px; + color: #94a3b8; +} + +.secubox-empty-icon { + font-size: 64px; + margin-bottom: 16px; + opacity: 0.5; +} + +.secubox-empty-title { + font-size: 20px; + font-weight: 600; + color: #64748b; + margin-bottom: 8px; +} + +.secubox-empty-text { + font-size: 14px; +} + +/* Animations */ +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.secubox-alert-item { + animation: slideIn 0.3s ease-out; +} diff --git a/luci-app-secubox/htdocs/luci-static/resources/secubox/monitoring.css b/luci-app-secubox/htdocs/luci-static/resources/secubox/monitoring.css new file mode 100644 index 00000000..2b985878 --- /dev/null +++ b/luci-app-secubox/htdocs/luci-static/resources/secubox/monitoring.css @@ -0,0 +1,161 @@ +/* SecuBox Monitoring Page Styles */ + +.secubox-monitoring-page { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +.secubox-monitoring-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +/* Charts Grid */ +.secubox-charts-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); + gap: 24px; + margin-bottom: 24px; +} + +@media (max-width: 1024px) { + .secubox-charts-grid { + grid-template-columns: 1fr; + } +} + +/* Chart Card */ +.secubox-chart-card { + background: white; + border-radius: 12px; + padding: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transition: transform 0.2s, box-shadow 0.2s; +} + +.secubox-chart-card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); +} + +.secubox-chart-title { + margin: 0 0 16px 0; + font-size: 16px; + font-weight: 600; + color: #1e293b; +} + +.secubox-chart-container { + width: 100%; + height: 200px; + position: relative; + margin-bottom: 16px; + background: #f8fafc; + border-radius: 8px; + overflow: hidden; +} + +.secubox-chart { + width: 100%; + height: 100%; +} + +.secubox-chart-legend { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 12px; + border-top: 2px solid #f1f5f9; +} + +.secubox-current-value { + font-size: 24px; + font-weight: 700; + color: #6366f1; +} + +.secubox-chart-unit { + font-size: 12px; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Stats Table */ +.secubox-stats-table { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 16px; +} + +.secubox-stat-item { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + background: #f8fafc; + border-radius: 8px; + transition: background 0.2s; +} + +.secubox-stat-item:hover { + background: #f1f5f9; +} + +.secubox-stat-icon { + font-size: 32px; + flex-shrink: 0; +} + +.secubox-stat-details { + flex: 1; +} + +.secubox-stat-label { + font-size: 13px; + color: #64748b; + margin-bottom: 4px; +} + +.secubox-stat-value { + font-size: 20px; + font-weight: 600; + color: #1e293b; +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.secubox-chart-card { + animation: fadeIn 0.5s ease-out; +} + +.secubox-chart-card:nth-child(1) { animation-delay: 0.1s; } +.secubox-chart-card:nth-child(2) { animation-delay: 0.2s; } +.secubox-chart-card:nth-child(3) { animation-delay: 0.3s; } +.secubox-chart-card:nth-child(4) { animation-delay: 0.4s; } + +/* Chart SVG Animations */ +@keyframes drawLine { + from { + stroke-dashoffset: 1000; + } + to { + stroke-dashoffset: 0; + } +} + +.secubox-chart polyline { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: drawLine 1s ease-out forwards; +} diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js new file mode 100644 index 00000000..a1c2be0b --- /dev/null +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js @@ -0,0 +1,334 @@ +'use strict'; +'require view'; +'require ui'; +'require dom'; +'require secubox/api as API'; +'require poll'; + +// Load CSS +document.head.appendChild(E('link', { + 'rel': 'stylesheet', + 'type': 'text/css', + 'href': L.resource('secubox/alerts.css') +})); + +return view.extend({ + alertsData: null, + filterSeverity: 'all', + filterModule: 'all', + sortBy: 'time', + + load: function() { + return this.refreshData(); + }, + + refreshData: function() { + var self = this; + return API.getAlerts().then(function(data) { + self.alertsData = data || {}; + return data; + }); + }, + + render: function(data) { + var self = this; + var container = E('div', { 'class': 'secubox-alerts-page' }); + + // Header + container.appendChild(this.renderHeader()); + + // Filters and controls + container.appendChild(this.renderControls()); + + // Stats overview + container.appendChild(this.renderStats()); + + // Alerts list + container.appendChild(this.renderAlertsList()); + + // Auto-refresh + poll.add(function() { + return self.refreshData().then(function() { + self.updateAlertsList(); + }); + }, 30); + + return container; + }, + + renderHeader: function() { + return E('div', { 'class': 'secubox-page-header' }, [ + E('div', {}, [ + E('h2', {}, 'âš ī¸ System Alerts'), + E('p', { 'class': 'secubox-page-subtitle' }, + 'Monitor and manage system alerts and notifications') + ]), + E('div', { 'class': 'secubox-header-actions' }, [ + E('button', { + 'class': 'cbi-button cbi-button-action', + 'click': function() { + self.clearAllAlerts(); + } + }, 'đŸ—‘ī¸ Clear All'), + E('button', { + 'class': 'cbi-button cbi-button-neutral', + 'click': function() { + self.refreshData().then(function() { + self.updateAlertsList(); + ui.addNotification(null, E('p', 'Alerts refreshed'), 'info'); + }); + } + }, '🔄 Refresh') + ]) + ]); + }, + + renderControls: function() { + var self = this; + + return E('div', { 'class': 'secubox-alerts-controls' }, [ + // Severity filter + E('div', { 'class': 'secubox-filter-group' }, [ + E('label', {}, 'Severity:'), + E('select', { + 'class': 'cbi-input-select', + 'change': function(ev) { + self.filterSeverity = ev.target.value; + self.updateAlertsList(); + } + }, [ + E('option', { 'value': 'all' }, 'All Severities'), + E('option', { 'value': 'error' }, '❌ Error'), + E('option', { 'value': 'warning' }, 'âš ī¸ Warning'), + E('option', { 'value': 'info' }, 'â„šī¸ Info') + ]) + ]), + + // Module filter + E('div', { 'class': 'secubox-filter-group' }, [ + E('label', {}, 'Module:'), + E('select', { + 'id': 'module-filter', + 'class': 'cbi-input-select', + 'change': function(ev) { + self.filterModule = ev.target.value; + self.updateAlertsList(); + } + }, [ + E('option', { 'value': 'all' }, 'All Modules') + ]) + ]), + + // Sort by + E('div', { 'class': 'secubox-filter-group' }, [ + E('label', {}, 'Sort by:'), + E('select', { + 'class': 'cbi-input-select', + 'change': function(ev) { + self.sortBy = ev.target.value; + self.updateAlertsList(); + } + }, [ + E('option', { 'value': 'time' }, 'Time (Newest first)'), + E('option', { 'value': 'severity' }, 'Severity'), + E('option', { 'value': 'module' }, 'Module') + ]) + ]) + ]); + }, + + renderStats: function() { + var alerts = this.alertsData.alerts || []; + var errorCount = alerts.filter(function(a) { return a.severity === 'error'; }).length; + var warningCount = alerts.filter(function(a) { return a.severity === 'warning'; }).length; + var infoCount = alerts.filter(function(a) { return a.severity === 'info'; }).length; + + return E('div', { 'class': 'secubox-alerts-stats' }, [ + this.renderStatCard('Total Alerts', alerts.length, '📊', '#6366f1'), + this.renderStatCard('Errors', errorCount, '❌', '#ef4444'), + this.renderStatCard('Warnings', warningCount, 'âš ī¸', '#f59e0b'), + this.renderStatCard('Info', infoCount, 'â„šī¸', '#3b82f6') + ]); + }, + + renderStatCard: function(label, value, icon, color) { + return E('div', { + 'class': 'secubox-alert-stat-card', + 'style': 'border-top: 3px solid ' + color + }, [ + E('div', { 'class': 'secubox-stat-icon' }, icon), + E('div', { 'class': 'secubox-stat-content' }, [ + E('div', { 'class': 'secubox-stat-value', 'style': 'color: ' + color }, value), + E('div', { 'class': 'secubox-stat-label' }, label) + ]) + ]); + }, + + renderAlertsList: function() { + return E('div', { 'class': 'secubox-card' }, [ + E('h3', { 'class': 'secubox-card-title' }, 'Alert History'), + E('div', { 'id': 'alerts-container', 'class': 'secubox-alerts-container' }, + this.renderFilteredAlerts()) + ]); + }, + + renderFilteredAlerts: function() { + var alerts = this.alertsData.alerts || []; + + if (alerts.length === 0) { + return E('div', { 'class': 'secubox-empty-state' }, [ + E('div', { 'class': 'secubox-empty-icon' }, '✓'), + E('div', { 'class': 'secubox-empty-title' }, 'No Alerts'), + E('div', { 'class': 'secubox-empty-text' }, 'All systems are operating normally') + ]); + } + + // Apply filters + var filtered = alerts.filter(function(alert) { + var severityMatch = this.filterSeverity === 'all' || alert.severity === this.filterSeverity; + var moduleMatch = this.filterModule === 'all' || alert.module === this.filterModule; + return severityMatch && moduleMatch; + }, this); + + // Apply sorting + filtered.sort(function(a, b) { + if (this.sortBy === 'time') { + return (b.timestamp || 0) - (a.timestamp || 0); + } else if (this.sortBy === 'severity') { + var severityOrder = { error: 3, warning: 2, info: 1 }; + return (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0); + } else if (this.sortBy === 'module') { + return (a.module || '').localeCompare(b.module || ''); + } + return 0; + }.bind(this)); + + if (filtered.length === 0) { + return E('div', { 'class': 'secubox-empty-state' }, [ + E('div', { 'class': 'secubox-empty-icon' }, '🔍'), + E('div', { 'class': 'secubox-empty-title' }, 'No Matching Alerts'), + E('div', { 'class': 'secubox-empty-text' }, 'Try adjusting your filters') + ]); + } + + return filtered.map(function(alert) { + return this.renderAlertItem(alert); + }, this); + }, + + renderAlertItem: function(alert) { + var severityClass = 'secubox-alert-' + (alert.severity || 'info'); + var severityIcon = alert.severity === 'error' ? '❌' : + alert.severity === 'warning' ? 'âš ī¸' : 'â„šī¸'; + var severityColor = alert.severity === 'error' ? '#ef4444' : + alert.severity === 'warning' ? '#f59e0b' : '#3b82f6'; + + var timeAgo = this.formatTimeAgo(alert.timestamp); + + return E('div', { 'class': 'secubox-alert-item ' + severityClass }, [ + E('div', { 'class': 'secubox-alert-icon-badge', 'style': 'background: ' + severityColor }, severityIcon), + E('div', { 'class': 'secubox-alert-details' }, [ + E('div', { 'class': 'secubox-alert-header' }, [ + E('strong', { 'class': 'secubox-alert-module' }, alert.module || 'System'), + E('span', { 'class': 'secubox-alert-time' }, timeAgo) + ]), + E('div', { 'class': 'secubox-alert-message' }, alert.message || 'No message'), + E('div', { 'class': 'secubox-alert-footer' }, [ + E('span', { 'class': 'secubox-badge secubox-badge-' + (alert.severity || 'info') }, + (alert.severity || 'info').toUpperCase()) + ]) + ]), + E('button', { + 'class': 'secubox-alert-dismiss', + 'title': 'Dismiss alert', + 'click': function() { + // TODO: Implement dismiss functionality + ui.addNotification(null, E('p', 'Alert dismissed (not yet persistent)'), 'info'); + } + }, '×') + ]); + }, + + formatTimeAgo: function(timestamp) { + if (!timestamp) return 'Unknown time'; + + var now = Math.floor(Date.now() / 1000); + var diff = now - timestamp; + + if (diff < 60) return 'Just now'; + if (diff < 3600) return Math.floor(diff / 60) + ' minutes ago'; + if (diff < 86400) return Math.floor(diff / 3600) + ' hours ago'; + if (diff < 604800) return Math.floor(diff / 86400) + ' days ago'; + + var date = new Date(timestamp * 1000); + return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); + }, + + updateAlertsList: function() { + var container = document.getElementById('alerts-container'); + if (container) { + dom.content(container, this.renderFilteredAlerts()); + } + + // Update module filter options + this.updateModuleFilter(); + + // Update stats + this.updateStats(); + }, + + updateModuleFilter: function() { + var alerts = this.alertsData.alerts || []; + var modules = {}; + + alerts.forEach(function(alert) { + if (alert.module) { + modules[alert.module] = true; + } + }); + + var select = document.getElementById('module-filter'); + if (select) { + var currentValue = select.value; + select.innerHTML = ''; + select.appendChild(E('option', { 'value': 'all' }, 'All Modules')); + + Object.keys(modules).sort().forEach(function(module) { + select.appendChild(E('option', { 'value': module }, module)); + }); + + select.value = currentValue; + } + }, + + updateStats: function() { + // Stats are re-rendered with the full list, so no need to update + }, + + clearAllAlerts: function() { + var self = this; + ui.showModal(_('Clear All Alerts'), [ + E('p', {}, 'Are you sure you want to clear all alerts?'), + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'cbi-button cbi-button-neutral', + 'click': ui.hideModal + }, _('Cancel')), + E('button', { + 'class': 'cbi-button cbi-button-negative', + 'click': function() { + // TODO: Implement backend clear functionality + self.alertsData.alerts = []; + self.updateAlertsList(); + ui.hideModal(); + ui.addNotification(null, E('p', 'All alerts cleared (not yet persistent)'), 'info'); + } + }, _('Clear All')) + ]) + ]); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js new file mode 100644 index 00000000..24be019d --- /dev/null +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js @@ -0,0 +1,352 @@ +'use strict'; +'require view'; +'require ui'; +'require dom'; +'require secubox/api as API'; +'require poll'; + +// Load CSS +document.head.appendChild(E('link', { + 'rel': 'stylesheet', + 'type': 'text/css', + 'href': L.resource('secubox/monitoring.css') +})); + +return view.extend({ + cpuHistory: [], + memoryHistory: [], + diskHistory: [], + networkHistory: [], + maxDataPoints: 30, // Keep last 30 data points + + load: function() { + return this.refreshData(); + }, + + refreshData: function() { + var self = this; + return API.getSystemHealth().then(function(data) { + self.addDataPoint(data); + return data; + }); + }, + + addDataPoint: function(health) { + var timestamp = Date.now(); + + // Add CPU data + this.cpuHistory.push({ + time: timestamp, + value: (health.cpu && health.cpu.percent) || 0 + }); + + // Add Memory data + this.memoryHistory.push({ + time: timestamp, + value: (health.memory && health.memory.percent) || 0 + }); + + // Add Disk data + this.diskHistory.push({ + time: timestamp, + value: (health.disk && health.disk.percent) || 0 + }); + + // Add Network data (calculate rate) + var netRx = (health.network && health.network.rx_bytes) || 0; + var netTx = (health.network && health.network.tx_bytes) || 0; + this.networkHistory.push({ + time: timestamp, + rx: netRx, + tx: netTx + }); + + // Keep only last N data points + if (this.cpuHistory.length > this.maxDataPoints) { + this.cpuHistory.shift(); + } + if (this.memoryHistory.length > this.maxDataPoints) { + this.memoryHistory.shift(); + } + if (this.diskHistory.length > this.maxDataPoints) { + this.diskHistory.shift(); + } + if (this.networkHistory.length > this.maxDataPoints) { + this.networkHistory.shift(); + } + }, + + render: function(data) { + var self = this; + var container = E('div', { 'class': 'secubox-monitoring-page' }); + + // Header + container.appendChild(this.renderHeader()); + + // Charts Grid + var chartsGrid = E('div', { 'class': 'secubox-charts-grid' }, [ + this.renderChart('cpu', 'CPU Usage', '%'), + this.renderChart('memory', 'Memory Usage', '%'), + this.renderChart('disk', 'Disk Usage', '%'), + this.renderChart('network', 'Network Traffic', 'B/s') + ]); + + container.appendChild(chartsGrid); + + // Current Stats Summary + container.appendChild(this.renderCurrentStats()); + + // Auto-refresh and update charts + poll.add(function() { + return self.refreshData().then(function() { + self.updateCharts(); + self.updateCurrentStats(); + }); + }, 5); // Refresh every 5 seconds for monitoring + + return container; + }, + + renderHeader: function() { + return E('div', { 'class': 'secubox-page-header secubox-monitoring-header' }, [ + E('div', {}, [ + E('h2', {}, '📊 System Monitoring'), + E('p', { 'class': 'secubox-page-subtitle' }, + 'Real-time system performance metrics and historical trends') + ]), + E('div', { 'class': 'secubox-header-info' }, [ + E('span', { 'class': 'secubox-badge' }, + 'âąī¸ Refresh: Every 5 seconds'), + E('span', { 'class': 'secubox-badge' }, + '📈 History: Last ' + this.maxDataPoints + ' points') + ]) + ]); + }, + + renderChart: function(type, title, unit) { + return E('div', { 'class': 'secubox-chart-card' }, [ + E('h3', { 'class': 'secubox-chart-title' }, title), + E('div', { 'class': 'secubox-chart-container' }, [ + E('svg', { + 'id': 'chart-' + type, + 'class': 'secubox-chart', + 'viewBox': '0 0 600 200', + 'preserveAspectRatio': 'none' + }) + ]), + E('div', { 'class': 'secubox-chart-legend' }, [ + E('span', { 'id': 'current-' + type, 'class': 'secubox-current-value' }, '0' + unit), + E('span', { 'class': 'secubox-chart-unit' }, 'Current') + ]) + ]); + }, + + renderCurrentStats: function() { + return E('div', { 'class': 'secubox-card' }, [ + E('h3', { 'class': 'secubox-card-title' }, '📋 Current Statistics'), + E('div', { 'id': 'current-stats', 'class': 'secubox-stats-table' }, + this.renderStatsTable()) + ]); + }, + + renderStatsTable: function() { + var latest = { + cpu: this.cpuHistory[this.cpuHistory.length - 1] || { value: 0 }, + memory: this.memoryHistory[this.memoryHistory.length - 1] || { value: 0 }, + disk: this.diskHistory[this.diskHistory.length - 1] || { value: 0 } + }; + + var stats = [ + { label: 'CPU Usage', value: latest.cpu.value + '%', icon: '⚡' }, + { label: 'Memory Usage', value: latest.memory.value + '%', icon: '💾' }, + { label: 'Disk Usage', value: latest.disk.value + '%', icon: 'đŸ’ŋ' }, + { label: 'Data Points', value: this.cpuHistory.length + ' / ' + this.maxDataPoints, icon: '📊' } + ]; + + return E('div', { 'class': 'secubox-stats-grid' }, + stats.map(function(stat) { + return E('div', { 'class': 'secubox-stat-item' }, [ + E('span', { 'class': 'secubox-stat-icon' }, stat.icon), + E('div', { 'class': 'secubox-stat-details' }, [ + E('div', { 'class': 'secubox-stat-label' }, stat.label), + E('div', { 'class': 'secubox-stat-value' }, stat.value) + ]) + ]); + }) + ); + }, + + updateCharts: function() { + this.drawChart('cpu', this.cpuHistory, '#6366f1'); + this.drawChart('memory', this.memoryHistory, '#22c55e'); + this.drawChart('disk', this.diskHistory, '#f59e0b'); + this.drawNetworkChart(); + }, + + drawChart: function(type, data, color) { + var svg = document.getElementById('chart-' + type); + var currentEl = document.getElementById('current-' + type); + + if (!svg || data.length === 0) return; + + // Clear previous content + svg.innerHTML = ''; + + // Dimensions + var width = 600; + var height = 200; + var padding = 10; + + // Find min/max for scaling + var maxValue = Math.max(...data.map(d => d.value), 100); + var minValue = 0; + + // Create grid lines + for (var i = 0; i <= 4; i++) { + var y = height - (height - 2 * padding) * (i / 4) - padding; + svg.appendChild(E('line', { + 'x1': padding, + 'y1': y, + 'x2': width - padding, + 'y2': y, + 'stroke': '#e5e7eb', + 'stroke-width': '1', + 'stroke-dasharray': '4' + })); + } + + // Create path + var points = data.map(function(d, i) { + var x = padding + (width - 2 * padding) * (i / (this.maxDataPoints - 1 || 1)); + var y = height - padding - ((d.value - minValue) / (maxValue - minValue)) * (height - 2 * padding); + return x + ',' + y; + }, this).join(' '); + + // Draw area under the curve + if (points) { + var firstPoint = points.split(' ')[0]; + var lastPoint = points.split(' ')[points.split(' ').length - 1]; + var areaPoints = padding + ',' + (height - padding) + ' ' + + points + ' ' + + (lastPoint ? lastPoint.split(',')[0] : 0) + ',' + (height - padding); + + svg.appendChild(E('polygon', { + 'points': areaPoints, + 'fill': color, + 'fill-opacity': '0.1' + })); + } + + // Draw line + svg.appendChild(E('polyline', { + 'points': points, + 'fill': 'none', + 'stroke': color, + 'stroke-width': '2', + 'stroke-linejoin': 'round', + 'stroke-linecap': 'round' + })); + + // Draw dots for last few points + data.slice(-5).forEach(function(d, i) { + var idx = data.length - 5 + i; + if (idx < 0) return; + var x = padding + (width - 2 * padding) * (idx / (this.maxDataPoints - 1 || 1)); + var y = height - padding - ((d.value - minValue) / (maxValue - minValue)) * (height - 2 * padding); + + svg.appendChild(E('circle', { + 'cx': x, + 'cy': y, + 'r': '3', + 'fill': color + })); + }, this); + + // Update current value + if (currentEl && data.length > 0) { + var unit = type === 'network' ? ' B/s' : '%'; + currentEl.textContent = data[data.length - 1].value.toFixed(1) + unit; + } + }, + + drawNetworkChart: function() { + // For network, we'll draw both RX and TX + var svg = document.getElementById('chart-network'); + var currentEl = document.getElementById('current-network'); + + if (!svg || this.networkHistory.length === 0) return; + + svg.innerHTML = ''; + + var width = 600; + var height = 200; + var padding = 10; + + // Calculate rates (bytes per second) + var rates = []; + for (var i = 1; i < this.networkHistory.length; i++) { + var prev = this.networkHistory[i - 1]; + var curr = this.networkHistory[i]; + var timeDiff = (curr.time - prev.time) / 1000; // seconds + + if (timeDiff > 0) { + rates.push({ + rx: (curr.rx - prev.rx) / timeDiff, + tx: (curr.tx - prev.tx) / timeDiff + }); + } + } + + if (rates.length === 0) return; + + var maxRate = Math.max( + ...rates.map(r => Math.max(r.rx, r.tx)), + 1024 // At least 1KB/s scale + ); + + // Draw RX line (green) + var rxPoints = rates.map(function(r, i) { + var x = padding + (width - 2 * padding) * (i / (rates.length - 1 || 1)); + var y = height - padding - (r.rx / maxRate) * (height - 2 * padding); + return x + ',' + y; + }).join(' '); + + svg.appendChild(E('polyline', { + 'points': rxPoints, + 'fill': 'none', + 'stroke': '#22c55e', + 'stroke-width': '2' + })); + + // Draw TX line (blue) + var txPoints = rates.map(function(r, i) { + var x = padding + (width - 2 * padding) * (i / (rates.length - 1 || 1)); + var y = height - padding - (r.tx / maxRate) * (height - 2 * padding); + return x + ',' + y; + }).join(' '); + + svg.appendChild(E('polyline', { + 'points': txPoints, + 'fill': 'none', + 'stroke': '#3b82f6', + 'stroke-width': '2' + })); + + // Update current value + if (currentEl && rates.length > 0) { + var lastRate = rates[rates.length - 1]; + currentEl.textContent = API.formatBytes(lastRate.rx + lastRate.tx) + '/s'; + } + }, + + updateCurrentStats: function() { + var container = document.getElementById('current-stats'); + if (container) { + dom.content(container, this.renderStatsTable()); + } + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js index 35d10c3b..2843248f 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js @@ -2,40 +2,208 @@ 'require view'; 'require form'; 'require uci'; +'require ui'; +'require secubox/api as API'; return view.extend({ load: function() { - return uci.load('secubox'); + return Promise.all([ + uci.load('secubox'), + API.getStatus() + ]); }, - render: function() { + render: function(data) { + var status = data[1] || {}; var m, s, o; - m = new form.Map('secubox', 'SecuBox Settings', + m = new form.Map('secubox', 'âš™ī¸ SecuBox Settings', 'Configure global settings for the SecuBox security suite.'); - s = m.section(form.TypedSection, 'secubox', 'General Settings'); + // General Settings Section + s = m.section(form.TypedSection, 'secubox', '🔧 General Settings'); s.anonymous = true; + s.addremove = false; - o = s.option(form.Flag, 'enabled', 'Enable SecuBox', - 'Master switch for all SecuBox modules'); + o = s.option(form.Flag, 'enabled', '🔌 Enable SecuBox', + 'Master switch for all SecuBox modules. When disabled, all module services will be stopped.'); o.rmempty = false; o.default = '1'; - o = s.option(form.Flag, 'auto_discovery', 'Auto Discovery', - 'Automatically detect and register installed modules'); - o.default = '1'; + o = s.option(form.Value, 'version', 'đŸ“Ļ Version', + 'Current SecuBox version (read-only)'); + o.readonly = true; + o.default = '0.0.1-beta'; - o = s.option(form.Flag, 'notifications', 'Enable Notifications', - 'Show notifications for module status changes and updates'); - o.default = '1'; + // Dashboard Settings Section + s = m.section(form.TypedSection, 'secubox', '📊 Dashboard Settings'); + s.anonymous = true; + s.addremove = false; - o = s.option(form.ListValue, 'theme', 'Dashboard Theme'); - o.value('dark', 'Dark (Default)'); - o.value('light', 'Light'); - o.value('system', 'System Preference'); + o = s.option(form.ListValue, 'theme', '🎨 Dashboard Theme', + 'Choose the visual theme for the SecuBox dashboard'); + o.value('dark', 'Dark (Default) - Modern dark interface'); + o.value('light', 'Light - Bright and clean'); + o.value('system', 'System Preference - Auto detect'); o.default = 'dark'; + o = s.option(form.ListValue, 'refresh_interval', '🔄 Auto-Refresh Interval', + 'How often to refresh dashboard data automatically'); + o.value('15', 'Every 15 seconds - High frequency'); + o.value('30', 'Every 30 seconds - Default'); + o.value('60', 'Every minute - Low frequency'); + o.value('0', 'Disabled - Manual refresh only'); + o.default = '30'; + + o = s.option(form.Flag, 'show_system_stats', '📈 Show System Statistics', + 'Display CPU, memory, disk usage on dashboard'); + o.default = '1'; + + o = s.option(form.Flag, 'show_module_grid', 'đŸŽ¯ Show Module Grid', + 'Display installed modules grid on dashboard'); + o.default = '1'; + + // Module Management Section + s = m.section(form.TypedSection, 'secubox', 'đŸ“Ļ Module Management'); + s.anonymous = true; + s.addremove = false; + + o = s.option(form.Flag, 'auto_discovery', '🔍 Auto Discovery', + 'Automatically detect and register newly installed modules'); + o.default = '1'; + + o = s.option(form.Flag, 'auto_start', 'â–ļī¸ Auto Start Modules', + 'Automatically start module services when they are installed'); + o.default = '0'; + + o = s.option(form.MultiValue, 'startup_modules', '🚀 Startup Modules', + 'Modules to start automatically on system boot'); + o.value('crowdsec', 'CrowdSec Dashboard'); + o.value('netdata', 'Netdata Dashboard'); + o.value('netifyd', 'Netifyd Dashboard'); + o.value('wireguard', 'WireGuard Dashboard'); + o.value('network_modes', 'Network Modes'); + o.value('client_guardian', 'Client Guardian'); + o.value('system_hub', 'System Hub'); + o.value('bandwidth_manager', 'Bandwidth Manager'); + o.value('auth_guardian', 'Auth Guardian'); + o.value('media_flow', 'Media Flow'); + o.value('vhost_manager', 'Virtual Host Manager'); + o.value('traffic_shaper', 'Traffic Shaper'); + o.value('cdn_cache', 'CDN Cache'); + o.value('ksm_manager', 'KSM Manager'); + o.optional = true; + + // Notification Settings Section + s = m.section(form.TypedSection, 'secubox', '🔔 Notification Settings'); + s.anonymous = true; + s.addremove = false; + + o = s.option(form.Flag, 'notifications', '🔔 Enable Notifications', + 'Show browser notifications for important events'); + o.default = '1'; + + o = s.option(form.Flag, 'notify_module_start', 'â–ļī¸ Module Start', + 'Notify when a module service starts'); + o.default = '1'; + o.depends('notifications', '1'); + + o = s.option(form.Flag, 'notify_module_stop', 'âšī¸ Module Stop', + 'Notify when a module service stops'); + o.default = '1'; + o.depends('notifications', '1'); + + o = s.option(form.Flag, 'notify_alerts', 'âš ī¸ System Alerts', + 'Notify when system alerts are generated'); + o.default = '1'; + o.depends('notifications', '1'); + + o = s.option(form.Flag, 'notify_health_issues', 'đŸĨ Health Issues', + 'Notify when system health metrics exceed thresholds'); + o.default = '1'; + o.depends('notifications', '1'); + + // Alert Thresholds Section + s = m.section(form.TypedSection, 'secubox', 'âš ī¸ Alert Thresholds'); + s.anonymous = true; + s.addremove = false; + + o = s.option(form.Value, 'cpu_warning', '⚡ CPU Warning Level (%)', + 'Generate warning when CPU usage exceeds this threshold'); + o.datatype = 'range(1,100)'; + o.default = '70'; + o.placeholder = '70'; + + o = s.option(form.Value, 'cpu_critical', 'đŸ”Ĩ CPU Critical Level (%)', + 'Generate critical alert when CPU usage exceeds this threshold'); + o.datatype = 'range(1,100)'; + o.default = '85'; + o.placeholder = '85'; + + o = s.option(form.Value, 'memory_warning', '💾 Memory Warning Level (%)', + 'Generate warning when memory usage exceeds this threshold'); + o.datatype = 'range(1,100)'; + o.default = '70'; + o.placeholder = '70'; + + o = s.option(form.Value, 'memory_critical', '🔴 Memory Critical Level (%)', + 'Generate critical alert when memory usage exceeds this threshold'); + o.datatype = 'range(1,100)'; + o.default = '85'; + o.placeholder = '85'; + + o = s.option(form.Value, 'disk_warning', 'đŸ’ŋ Disk Warning Level (%)', + 'Generate warning when disk usage exceeds this threshold'); + o.datatype = 'range(1,100)'; + o.default = '70'; + o.placeholder = '70'; + + o = s.option(form.Value, 'disk_critical', '⛔ Disk Critical Level (%)', + 'Generate critical alert when disk usage exceeds this threshold'); + o.datatype = 'range(1,100)'; + o.default = '85'; + o.placeholder = '85'; + + // Security Settings Section + s = m.section(form.TypedSection, 'secubox', '🔒 Security Settings'); + s.anonymous = true; + s.addremove = false; + + o = s.option(form.Flag, 'require_auth', '🔐 Require Authentication', + 'Require authentication to access SecuBox dashboard'); + o.default = '1'; + + o = s.option(form.Flag, 'audit_logging', '📝 Audit Logging', + 'Log all configuration changes and module actions'); + o.default = '1'; + + o = s.option(form.Value, 'audit_retention', '📅 Audit Log Retention (days)', + 'Number of days to keep audit logs'); + o.datatype = 'uinteger'; + o.default = '30'; + o.depends('audit_logging', '1'); + + // Advanced Settings Section + s = m.section(form.TypedSection, 'secubox', 'đŸ› ī¸ Advanced Settings'); + s.anonymous = true; + s.addremove = false; + + o = s.option(form.Flag, 'debug_mode', '🐛 Debug Mode', + 'Enable debug logging (may impact performance)'); + o.default = '0'; + + o = s.option(form.Value, 'api_timeout', 'âąī¸ API Timeout (seconds)', + 'Timeout for API requests to module backends'); + o.datatype = 'range(5,300)'; + o.default = '30'; + o.placeholder = '30'; + + o = s.option(form.Value, 'max_modules', '📊 Maximum Modules', + 'Maximum number of modules that can be installed'); + o.datatype = 'range(1,50)'; + o.default = '20'; + o.placeholder = '20'; + return m.render(); } }); diff --git a/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json b/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json index 6d5a7982..c2c4ee29 100644 --- a/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json +++ b/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json @@ -14,6 +14,11 @@ "order": 20, "action": {"type": "view", "path": "secubox/modules"} }, + "admin/secubox/alerts": { + "title": "Alerts", + "order": 22, + "action": {"type": "view", "path": "secubox/alerts"} + }, "admin/secubox/settings": { "title": "Settings", "order": 25, @@ -29,6 +34,11 @@ "order": 35, "action": {"type": "firstchild"} }, + "admin/secubox/monitoring/overview": { + "title": "System Monitoring", + "order": 10, + "action": {"type": "view", "path": "secubox/monitoring"} + }, "admin/secubox/network": { "title": "Network Management", "order": 40,