secubox-openwrt/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js
CyberMind-FR 5902ac500a release: v0.1.1 - Unified Theme System with Dark/Light Mode Support
Major Features:
• Centralized theme system across SecuBox and System Hub
• Three theme modes: dark (default), light, and system (auto-detect)
• Single theme setting in SecuBox controls both plugins
• Real-time theme switching with OS preference detection

SecuBox Changes:
• Added theme.js manager for centralized theme control
• Implemented CSS variables for dark/light mode (secubox.css)
• Fixed hardcoded colors in dashboard.css, alerts.css, monitoring.css
• Integrated theme.js in all 7 views (dashboard, modules, alerts, monitoring, settings, etc.)
• Added get_theme RPC method to luci.secubox backend
• Updated ACL permissions to include get_theme (read access)
• Version updated to 0.1.1

System Hub Changes:
• Added theme.js manager using SecuBox theme API
• Implemented CSS variables for dark/light mode (dashboard.css)
• Integrated theme.js in all 9 views (overview, health, services, logs, backup, components, remote, settings, diagnostics)
• Version updated to 0.1.1
• README updated with maintainer info

Theme System Architecture:
• Configuration: /etc/config/secubox (option theme: dark|light|system)
• RPCD Backend: luci.secubox/get_theme method
• Frontend: theme.js modules (secubox/theme.js, system-hub/theme.js)
• CSS Variables: --sb-bg, --sb-bg-card, --sb-border, --sb-text, --sb-text-muted, --sb-shadow
• Auto-detection: prefers-color-scheme media query for system mode

Documentation:
• Added LUCI_DEVELOPMENT_REFERENCE.md with comprehensive LuCI development patterns
• Documented ubus/RPC types, baseclass.extend() patterns, ACL structure
• Common errors and solutions from implementation experience

Bug Fixes:
• Fixed SecuBox theme not applying visually (CSS variables now used)
• Fixed missing secubox.css in view imports
• Fixed ACL access denied for get_theme method
• Fixed hardcoded colors preventing theme switching

Testing:
• Verified theme switching works in all SecuBox tabs
• Verified theme switching works in all System Hub tabs
• Verified dark/light/system modes function correctly
• Verified single setting controls both plugins

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 16:09:15 +01:00

344 lines
9.8 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.

This file contains Unicode characters that might be confused with other characters. 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 ui';
'require dom';
'require secubox/api as API';
'require secubox/theme as Theme';
'require poll';
// Load CSS (base theme variables first)
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox/secubox.css')
}));
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox/alerts.css')
}));
// Initialize theme
Theme.init();
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
});