secubox-openwrt/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js
CyberMind-FR b884b12970 feat: Integrate global CyberMood theme into core modules
Migrated three core modules to use the global secubox-theme package:

Modules Updated:
- luci-app-secubox (dashboard, modules views)
- luci-app-network-modes (overview view)
- luci-app-system-hub (overview, services views)

Changes Per Module:
- Replaced module-specific theme imports with 'require secubox-theme/theme as Theme'
- Updated CSS imports to use global secubox-theme.css bundle
- Initialized global theme with Theme.init({ theme: 'dark', language: 'en' })
- Removed redundant theme.getTheme() calls from load() functions
- Added global theme CSS link tags to view renders

Benefits:
- Unified CyberMood design system across all modules
- Access to 100+ CSS variables (colors, spacing, effects)
- Theme switching support (dark, light, cyberpunk)
- Multi-language support (en, fr, de, es) via Theme.t()
- Reduced CSS duplication
- Consistent UI components (cards, buttons, badges)

Deployment:
- Created deploy-modules-with-theme.sh for batch deployment
- All modules successfully deployed to router 192.168.8.191
- Verified HTTP access to updated JavaScript files

Testing:
-  SecuBox dashboard loads with global theme
-  Network-modes overview uses theme CSS
-  System-hub views integrate theme properly
-  All 27 view files deployed successfully

Next Steps:
- Modules can now use Theme.createCard(), createButton(), createBadge()
- Translation keys available for internationalization
- Theme variants switchable via Theme.apply('dark'|'light'|'cyberpunk')

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-28 17:16:37 +01:00

644 lines
20 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 secubox/api as API';
'require secubox-theme/theme as Theme';
'require dom';
'require poll';
// Load global theme CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox-theme/secubox-theme.css')
}));
// Initialize global theme
Theme.init({ theme: 'dark', language: 'en' });
return view.extend({
dashboardData: null,
healthData: null,
alertsData: null,
activeFilter: 'all',
load: function() {
return this.refreshData();
},
refreshData: function() {
var self = this;
return Promise.all([
API.getDashboardData(),
API.getSystemHealth(),
API.getAlerts()
]).then(function(data) {
self.dashboardData = data[0] || {};
self.healthData = data[1] || {};
self.alertsData = data[2] || {};
return data;
});
},
render: function(data) {
var self = this;
var container = E('div', { 'class': 'secubox-dashboard' });
// Header with version
var status = this.dashboardData.status || {};
container.appendChild(this.renderHeader(status));
// Stats Overview Cards
container.appendChild(this.renderStatsOverview());
// Main Content Grid
var mainGrid = E('div', { 'class': 'secubox-main-grid' }, [
// Left column
E('div', { 'class': 'secubox-column-left' }, [
this.renderSystemHealth(this.healthData),
this.renderAlerts(this.alertsData.alerts || [])
]),
// Right column
E('div', { 'class': 'secubox-column-right' }, [
this.renderActiveModules(),
this.renderQuickActions()
])
]);
container.appendChild(mainGrid);
// Start auto-refresh poll
poll.add(function() {
return self.refreshData().then(function() {
self.updateDynamicElements();
});
}, 30); // Refresh every 30 seconds
return container;
},
renderHeader: function(status) {
return E('div', { 'class': 'secubox-header' }, [
E('div', { 'class': 'secubox-header-content' }, [
E('div', {}, [
E('h2', {}, '🚀 SecuBox Control Center'),
E('p', { 'class': 'secubox-subtitle' }, 'Security & Network Management Suite')
]),
E('div', { 'class': 'secubox-header-info' }, [
E('span', { 'class': 'secubox-badge secubox-badge-version' },
'v' + (status.version || '0.1.0')),
E('span', { 'class': 'secubox-badge' },
'⏱️ ' + API.formatUptime(status.uptime)),
E('span', { 'class': 'secubox-badge', 'id': 'secubox-hostname' },
'🖥️ ' + (status.hostname || 'SecuBox'))
])
])
]);
},
renderStatsOverview: function() {
var modules = this.dashboardData.modules || [];
var counts = this.dashboardData.counts || {};
var stats = [
{
label: 'Total Modules',
value: counts.total || modules.length,
icon: '📦',
color: '#6366f1',
id: 'stat-total'
},
{
label: 'Installed',
value: counts.installed || 0,
icon: '✓',
color: '#22c55e',
id: 'stat-installed'
},
{
label: 'Running',
value: counts.running || 0,
icon: '▶',
color: '#00ab44',
id: 'stat-running'
},
{
label: 'Alerts',
value: (this.alertsData.alerts || []).length,
icon: '⚠️',
color: '#f59e0b',
id: 'stat-alerts'
}
];
return E('div', { 'class': 'secubox-stats-grid' },
stats.map(function(stat) {
return E('div', {
'class': 'secubox-stat-card',
'style': 'border-top: 3px solid ' + stat.color
}, [
E('div', { 'class': 'secubox-stat-icon' }, stat.icon),
E('div', { 'class': 'secubox-stat-content' }, [
E('div', {
'class': 'secubox-stat-value',
'id': stat.id,
'style': 'color: ' + stat.color
}, stat.value),
E('div', { 'class': 'secubox-stat-label' }, stat.label)
])
]);
})
);
},
renderSystemHealth: function(health) {
var cpu = health.cpu || {};
var memory = health.memory || {};
var disk = health.disk || {};
return E('div', { 'class': 'secubox-card' }, [
E('h3', { 'class': 'secubox-card-title' }, '📊 System Health'),
E('div', { 'class': 'secubox-health-grid' }, [
this.renderCircularGauge('CPU', cpu.percent || 0,
'Load: ' + (cpu.load_1min || '0.00'), 'cpu', '#6366f1'),
this.renderCircularGauge('Memory', memory.percent || 0,
API.formatBytes((memory.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((memory.total_kb || 0) * 1024), 'memory', '#22c55e'),
this.renderCircularGauge('Disk', disk.percent || 0,
API.formatBytes((disk.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((disk.total_kb || 0) * 1024), 'disk', '#f59e0b')
])
]);
},
renderCircularGauge: function(label, percent, details, id, baseColor) {
var color = percent < 70 ? '#22c55e' : percent < 85 ? '#f59e0b' : '#ef4444';
var radius = 45;
var circumference = 2 * Math.PI * radius;
var offset = circumference - (percent / 100) * circumference;
return E('div', { 'class': 'secubox-gauge-container' }, [
E('div', { 'class': 'secubox-gauge' }, [
E('svg', { 'viewBox': '0 0 120 120', 'class': 'secubox-gauge-svg' }, [
// Background circle
E('circle', {
'cx': '60',
'cy': '60',
'r': radius,
'fill': 'none',
'stroke': '#f1f5f9',
'stroke-width': '10'
}),
// Progress circle
E('circle', {
'id': 'gauge-' + id,
'cx': '60',
'cy': '60',
'r': radius,
'fill': 'none',
'stroke': color,
'stroke-width': '10',
'stroke-linecap': 'round',
'stroke-dasharray': circumference,
'stroke-dashoffset': offset,
'transform': 'rotate(-90 60 60)',
'class': 'secubox-gauge-progress'
})
]),
E('div', { 'class': 'secubox-gauge-content' }, [
E('div', {
'class': 'secubox-gauge-percent',
'id': 'gauge-' + id + '-percent',
'style': 'color: ' + color
}, Math.round(percent) + '%'),
E('div', { 'class': 'secubox-gauge-label' }, label)
])
]),
E('div', {
'class': 'secubox-gauge-details',
'id': 'gauge-' + id + '-details'
}, details)
]);
},
renderActiveModules: function() {
var self = this;
var modules = this.dashboardData.modules || [];
var activeModules = modules.filter(function(m) { return m.installed; });
// Apply category filter
var filteredModules = this.activeFilter === 'all' ? activeModules :
activeModules.filter(function(m) {
return m.category === self.activeFilter;
});
// Map module IDs to their dashboard paths
var modulePaths = {
'crowdsec': 'admin/secubox/crowdsec/overview',
'netdata': 'admin/secubox/netdata/dashboard',
'netifyd': 'admin/secubox/netifyd/overview',
'wireguard': 'admin/secubox/wireguard/overview',
'network_modes': 'admin/secubox/network-modes/overview',
'client_guardian': 'admin/secubox/client-guardian/overview',
'system_hub': 'admin/secubox/system-hub/overview',
'bandwidth_manager': 'admin/secubox/bandwidth-manager/overview',
'auth_guardian': 'admin/secubox/auth-guardian/overview',
'media_flow': 'admin/secubox/mediaflow/dashboard',
'vhost_manager': 'admin/secubox/vhosts/overview',
'traffic_shaper': 'admin/secubox/traffic-shaper/overview',
'cdn_cache': 'admin/secubox/cdn-cache/overview',
'ksm_manager': 'admin/secubox/ksm-manager/overview'
};
var moduleCards = filteredModules.map(function(module) {
var status = module.status || 'unknown';
var statusClass = status;
var dashboardPath = modulePaths[module.id] || ('admin/secubox/' + module.id);
// Status label mapping (v0.3.1)
var statusLabels = {
'active': '✓ Activé',
'disabled': '○ Désactivé',
'error': '⚠️ Erreur',
'unknown': '? Inconnu'
};
var statusLabel = statusLabels[status] || '○ Désactivé';
return E('a', {
'href': L.url(dashboardPath),
'class': 'secubox-module-link secubox-module-' + statusClass
}, [
E('div', {
'class': 'secubox-module-mini-card',
'style': 'border-left: 4px solid ' + (module.color || '#64748b')
}, [
E('div', { 'class': 'secubox-module-mini-header' }, [
E('span', { 'class': 'secubox-module-mini-icon' }, module.icon || '📦'),
E('span', {
'class': 'secubox-status-dot secubox-status-' + statusClass,
'title': statusLabel
})
]),
E('div', { 'class': 'secubox-module-mini-body' }, [
E('div', { 'class': 'secubox-module-mini-name' }, module.name || module.id),
E('div', { 'class': 'secubox-module-mini-status' }, statusLabel)
])
])
]);
});
// Filter tabs
var filters = [
{ id: 'all', label: 'All', icon: '📦' },
{ id: 'security', label: 'Security', icon: '🛡️' },
{ id: 'network', label: 'Network', icon: '🌐' },
{ id: 'system', label: 'System', icon: '⚙️' },
{ id: 'monitoring', label: 'Monitoring', icon: '📊' }
];
var filterTabButtons = filters.map(function(filter) {
var isActive = self.activeFilter === filter.id;
return E('div', {
'class': 'secubox-filter-tab' + (isActive ? ' active' : ''),
'click': function() {
self.activeFilter = filter.id;
self.updateModulesGrid();
}
}, [
E('span', { 'class': 'secubox-tab-icon' }, filter.icon),
E('span', { 'class': 'secubox-tab-label' }, filter.label)
]);
});
filterTabButtons.push(
E('div', {
'class': 'secubox-filter-tab secubox-tab-bonus',
'click': function() {
window.location.href = L.url('admin/secubox/help');
}
}, [
E('span', { 'class': 'secubox-tab-icon' }, '✨'),
E('span', { 'class': 'secubox-tab-label' }, _('Bonus · Help à SecuBox'))
])
);
var filterTabs = E('div', { 'class': 'secubox-filter-tabs' }, filterTabButtons);
return E('div', { 'class': 'secubox-card' }, [
E('h3', { 'class': 'secubox-card-title' }, '🎯 Active Modules (' + activeModules.length + ')'),
filterTabs,
E('div', {
'class': 'secubox-modules-mini-grid',
'id': 'modules-grid'
},
moduleCards.length > 0 ? moduleCards : [
E('p', { 'class': 'secubox-empty-state' },
this.activeFilter === 'all' ? 'No modules installed' :
'No ' + this.activeFilter + ' modules installed')
]
)
]);
},
updateModulesGrid: function() {
var container = document.getElementById('modules-grid');
if (!container) return;
var modules = this.dashboardData.modules || [];
var activeModules = modules.filter(function(m) { return m.installed; });
var self = this;
// Filter definitions (same as in renderActiveModules)
var filters = [
{ id: 'all', label: 'All', icon: '📦' },
{ id: 'security', label: 'Security', icon: '🛡️' },
{ id: 'network', label: 'Network', icon: '🌐' },
{ id: 'system', label: 'System', icon: '⚙️' },
{ id: 'monitoring', label: 'Monitoring', icon: '📊' }
];
// Apply category filter
var filteredModules = this.activeFilter === 'all' ? activeModules :
activeModules.filter(function(m) {
return m.category === self.activeFilter;
});
// Map module IDs to their dashboard paths
var modulePaths = {
'crowdsec': 'admin/secubox/crowdsec/overview',
'netdata': 'admin/secubox/netdata/dashboard',
'netifyd': 'admin/secubox/netifyd/overview',
'wireguard': 'admin/secubox/wireguard/overview',
'network_modes': 'admin/secubox/network-modes/overview',
'client_guardian': 'admin/secubox/client-guardian/overview',
'system_hub': 'admin/secubox/system-hub/overview',
'bandwidth_manager': 'admin/secubox/bandwidth-manager/overview',
'auth_guardian': 'admin/secubox/auth-guardian/overview',
'media_flow': 'admin/secubox/mediaflow/dashboard',
'vhost_manager': 'admin/secubox/vhosts/overview',
'traffic_shaper': 'admin/secubox/traffic-shaper/overview',
'cdn_cache': 'admin/secubox/cdn-cache/overview',
'ksm_manager': 'admin/secubox/ksm-manager/overview'
};
var moduleCards = filteredModules.map(function(module) {
var status = module.status || 'unknown';
var statusClass = status;
var dashboardPath = modulePaths[module.id] || ('admin/secubox/' + module.id);
// Status label mapping (v0.3.1)
var statusLabels = {
'active': '✓ Activé',
'disabled': '○ Désactivé',
'error': '⚠️ Erreur',
'unknown': '? Inconnu'
};
var statusLabel = statusLabels[status] || '○ Désactivé';
return E('a', {
'href': L.url(dashboardPath),
'class': 'secubox-module-link secubox-module-' + statusClass
}, [
E('div', {
'class': 'secubox-module-mini-card',
'style': 'border-left: 4px solid ' + (module.color || '#64748b')
}, [
E('div', { 'class': 'secubox-module-mini-header' }, [
E('span', { 'class': 'secubox-module-mini-icon' }, module.icon || '📦'),
E('span', {
'class': 'secubox-status-dot secubox-status-' + statusClass,
'title': statusLabel
})
]),
E('div', { 'class': 'secubox-module-mini-body' }, [
E('div', { 'class': 'secubox-module-mini-name' }, module.name || module.id),
E('div', { 'class': 'secubox-module-mini-status' }, statusLabel)
])
])
]);
});
// Update filter tab active states
var tabs = container.parentElement.querySelectorAll('.secubox-filter-tab');
tabs.forEach(function(tab) {
var filterMatch = false;
filters.forEach(function(filter) {
if (tab.textContent.includes(filter.label) && self.activeFilter === filter.id) {
filterMatch = true;
}
});
if (filterMatch) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
});
// Update content
dom.content(container, moduleCards.length > 0 ? moduleCards : [
E('p', { 'class': 'secubox-empty-state' },
this.activeFilter === 'all' ? 'No modules installed' :
'No ' + this.activeFilter + ' modules installed')
]);
},
renderQuickActions: function() {
var self = this;
var actions = [
{ name: 'restart_rpcd', label: 'RPCD', icon: '🔄', color: '#6366f1' },
{ name: 'restart_uhttpd', label: 'Web Server', icon: '🌐', color: '#00ab44' },
{ name: 'restart_network', label: 'Network', icon: '📡', color: '#06b6d4' },
{ name: 'restart_firewall', label: 'Firewall', icon: '🛡️', color: '#ef4444' },
{ name: 'clear_cache', label: 'Clear Cache', icon: '🧹', color: '#f59e0b' },
{ name: 'backup_config', label: 'Backup', icon: '💾', color: '#8b5cf6' }
];
var buttons = actions.map(function(action) {
return E('button', {
'class': 'secubox-action-btn',
'style': 'border-color: ' + action.color,
'click': function() {
self.executeQuickAction(action.name, action.label);
}
}, [
E('span', { 'class': 'secubox-action-icon' }, action.icon),
E('span', { 'class': 'secubox-action-label' }, action.label)
]);
});
// Add Fix Permissions button (v0.3.1)
buttons.push(
E('button', {
'class': 'secubox-action-btn',
'style': 'border-color: #f97316',
'click': function() {
self.executeFixPermissions();
}
}, [
E('span', { 'class': 'secubox-action-icon' }, '🔧'),
E('span', { 'class': 'secubox-action-label' }, 'Fix Perms')
])
);
return E('div', { 'class': 'secubox-card' }, [
E('h3', { 'class': 'secubox-card-title' }, '⚡ Quick Actions'),
E('div', { 'class': 'secubox-actions-grid' }, buttons)
]);
},
executeQuickAction: function(action, label) {
var self = this;
ui.showModal(_('Quick Action'), [
E('p', { 'class': 'spinning' }, _('Executing: ') + label + '...')
]);
API.quickAction(action).then(function(result) {
ui.hideModal();
if (result && result.success) {
ui.addNotification(null, E('p', '✓ ' + (result.message || 'Action completed')), 'info');
// Refresh data after action
setTimeout(function() {
self.refreshData().then(function() {
self.updateDynamicElements();
});
}, 2000);
} else {
ui.addNotification(null, E('p', '✗ ' + (result.message || 'Action failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
});
},
executeFixPermissions: function() {
var self = this;
ui.showModal(_('Fixing Permissions'), [
E('p', { 'class': 'spinning' }, _('Fixing file permissions and restarting services...'))
]);
API.fixPermissions().then(function(result) {
ui.hideModal();
if (result && result.success) {
ui.addNotification(null, E('p', '✓ Permissions fixed successfully'), 'info');
// Show output in console
if (result.output) {
console.log('Fix Permissions Output:\n' + result.output);
}
// Refresh data after fixing permissions
setTimeout(function() {
self.refreshData().then(function() {
self.updateDynamicElements();
});
}, 2000);
} else {
ui.addNotification(null, E('p', '✗ ' + (result.message || 'Failed to fix permissions')), 'error');
if (result.output) {
console.error('Fix Permissions Error:\n' + result.output);
}
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
});
},
renderAlerts: function(alerts) {
if (!alerts || alerts.length === 0) {
return E('div', { 'class': 'secubox-card' }, [
E('h3', { 'class': 'secubox-card-title' }, '✓ System Status'),
E('div', { 'class': 'secubox-alert secubox-alert-success' }, [
E('span', {}, '✓ All systems operational')
])
]);
}
var alertItems = alerts.slice(0, 5).map(function(alert) {
var severityClass = 'secubox-alert-' + (alert.severity || 'info');
var severityIcon = alert.severity === 'error' ? '✗' :
alert.severity === 'warning' ? '⚠️' : '';
return E('div', { 'class': 'secubox-alert ' + severityClass }, [
E('span', { 'class': 'secubox-alert-icon' }, severityIcon),
E('div', { 'class': 'secubox-alert-content' }, [
E('strong', {}, alert.module || 'System'),
E('span', {}, ': ' + alert.message)
])
]);
});
return E('div', { 'class': 'secubox-card' }, [
E('h3', { 'class': 'secubox-card-title' },
'⚠️ Alerts (' + alerts.length + ')'),
E('div', { 'class': 'secubox-alerts-list' }, alertItems),
alerts.length > 5 ? E('a', {
'href': '#',
'class': 'secubox-view-all',
'click': function(ev) {
ev.preventDefault();
window.location.hash = '#admin/secubox/alerts';
}
}, 'View all alerts →') : null
]);
},
updateDynamicElements: function() {
// Update stats
var counts = this.dashboardData.counts || {};
var totalEl = document.getElementById('stat-total');
var installedEl = document.getElementById('stat-installed');
var runningEl = document.getElementById('stat-running');
var alertsEl = document.getElementById('stat-alerts');
if (totalEl) totalEl.textContent = counts.total || 0;
if (installedEl) installedEl.textContent = counts.installed || 0;
if (runningEl) runningEl.textContent = counts.running || 0;
if (alertsEl) alertsEl.textContent = (this.alertsData.alerts || []).length;
// Update health bars
var health = this.healthData;
this.updateHealthBar('cpu', health.cpu);
this.updateHealthBar('memory', health.memory);
this.updateHealthBar('disk', health.disk);
},
updateHealthBar: function(type, data) {
if (!data) return;
var percent = data.percent || 0;
var color = percent < 70 ? '#22c55e' : percent < 85 ? '#f59e0b' : '#ef4444';
var percentEl = document.getElementById('gauge-' + type + '-percent');
var gaugeEl = document.getElementById('gauge-' + type);
if (percentEl) {
percentEl.textContent = Math.round(percent) + '%';
percentEl.style.color = color;
}
if (gaugeEl) {
var radius = 45;
var circumference = 2 * Math.PI * radius;
var offset = circumference - (percent / 100) * circumference;
gaugeEl.setAttribute('stroke-dashoffset', offset);
gaugeEl.setAttribute('stroke', color);
}
// Update details
var detailsEl = document.getElementById('gauge-' + type + '-details');
if (detailsEl && type === 'cpu') {
detailsEl.textContent = 'Load: ' + (data.load_1min || '0.00');
} else if (detailsEl && type === 'memory') {
detailsEl.textContent = API.formatBytes((data.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((data.total_kb || 0) * 1024);
} else if (detailsEl && type === 'disk') {
detailsEl.textContent = API.formatBytes((data.used_kb || 0) * 1024) + ' / ' +
API.formatBytes((data.total_kb || 0) * 1024);
}
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});