From 62cf871eeb43452ea97382e45e128af0157da43d Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Mon, 26 Jan 2026 08:13:40 +0100 Subject: [PATCH] feat(haproxy): Add emergency health banner and quick restart buttons - HAProxy overview: Add prominent emergency banner showing service status with quick health indicators (Container/HAProxy/Config) and one-click Restart/Start/Stop buttons - SecuBox dashboard: Add Critical Services Quick Restart section with buttons for HAProxy, CrowdSec, Tor Shield, and Gitea - Metabolizer config: Fix portal_path to /www/blog Co-Authored-By: Claude Opus 4.5 --- package/secubox/luci-app-haproxy/Makefile | 2 +- .../resources/view/haproxy/overview.js | 80 +++++++++++++++++ .../resources/view/secubox/dashboard.js | 86 +++++++++++++++++++ .../files/etc/config/metabolizer | 6 +- 4 files changed, 170 insertions(+), 4 deletions(-) diff --git a/package/secubox/luci-app-haproxy/Makefile b/package/secubox/luci-app-haproxy/Makefile index 9f30b18..c2e1d53 100644 --- a/package/secubox/luci-app-haproxy/Makefile +++ b/package/secubox/luci-app-haproxy/Makefile @@ -11,7 +11,7 @@ LUCI_PKGARCH:=all PKG_NAME:=luci-app-haproxy PKG_VERSION:=1.0.0 -PKG_RELEASE:=6 +PKG_RELEASE:=7 PKG_MAINTAINER:=CyberMind PKG_LICENSE:=MIT diff --git a/package/secubox/luci-app-haproxy/htdocs/luci-static/resources/view/haproxy/overview.js b/package/secubox/luci-app-haproxy/htdocs/luci-static/resources/view/haproxy/overview.js index cab5824..94e3b3c 100644 --- a/package/secubox/luci-app-haproxy/htdocs/luci-static/resources/view/haproxy/overview.js +++ b/package/secubox/luci-app-haproxy/htdocs/luci-static/resources/view/haproxy/overview.js @@ -40,6 +40,7 @@ return view.extend({ // Build content array, filtering out nulls var content = [ + this.renderEmergencyBanner(status), this.renderPageHeader(status), this.renderStatsGrid(status, vhosts, backends, certificates), this.renderHealthGrid(status), @@ -126,6 +127,72 @@ return view.extend({ ]); }, + renderEmergencyBanner: function(status) { + var self = this; + var haproxyRunning = status.haproxy_running; + var containerRunning = status.container_running; + + var statusColor = haproxyRunning ? '#22c55e' : (containerRunning ? '#f97316' : '#ef4444'); + var statusText = haproxyRunning ? 'HEALTHY' : (containerRunning ? 'DEGRADED' : 'DOWN'); + var statusIcon = haproxyRunning ? '\u2705' : (containerRunning ? '\u26A0\uFE0F' : '\u274C'); + + return E('div', { + 'class': 'hp-emergency-banner', + 'style': 'background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); border: 1px solid ' + statusColor + '; border-radius: 12px; padding: 20px; margin-bottom: 24px; display: flex; align-items: center; justify-content: space-between; gap: 24px;' + }, [ + // Status indicator + E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [ + E('div', { + 'style': 'width: 64px; height: 64px; border-radius: 50%; background: ' + statusColor + '22; display: flex; align-items: center; justify-content: center; font-size: 32px; border: 3px solid ' + statusColor + ';' + }, statusIcon), + E('div', {}, [ + E('div', { 'style': 'font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: #888; margin-bottom: 4px;' }, 'Service Status'), + E('div', { 'style': 'font-size: 24px; font-weight: 700; color: ' + statusColor + ';' }, statusText), + E('div', { 'style': 'font-size: 13px; color: #888; margin-top: 4px;' }, + 'Container: ' + (containerRunning ? 'Running' : 'Stopped') + + ' \u2022 HAProxy: ' + (haproxyRunning ? 'Active' : 'Inactive')) + ]) + ]), + + // Quick health checks + E('div', { 'style': 'display: flex; gap: 16px;' }, [ + E('div', { 'style': 'text-align: center; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 8px;' }, [ + E('div', { 'style': 'font-size: 20px;' }, containerRunning ? '\u2705' : '\u274C'), + E('div', { 'style': 'font-size: 11px; color: #888; margin-top: 4px;' }, 'Container') + ]), + E('div', { 'style': 'text-align: center; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 8px;' }, [ + E('div', { 'style': 'font-size: 20px;' }, haproxyRunning ? '\u2705' : '\u274C'), + E('div', { 'style': 'font-size: 11px; color: #888; margin-top: 4px;' }, 'HAProxy') + ]), + E('div', { 'style': 'text-align: center; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 8px;' }, [ + E('div', { 'style': 'font-size: 20px;' }, status.config_valid !== false ? '\u2705' : '\u26A0\uFE0F'), + E('div', { 'style': 'font-size: 11px; color: #888; margin-top: 4px;' }, 'Config') + ]) + ]), + + // Emergency actions + E('div', { 'style': 'display: flex; gap: 12px;' }, [ + E('button', { + 'class': 'hp-btn', + 'style': 'background: #3b82f6; color: white; padding: 12px 20px; font-size: 14px; font-weight: 600; border: none; border-radius: 8px; cursor: pointer; display: flex; align-items: center; gap: 8px;', + 'click': function() { self.handleRestart(); }, + 'disabled': !containerRunning ? true : null + }, ['\u{1F504}', ' Restart']), + E('button', { + 'class': 'hp-btn', + 'style': 'background: ' + (haproxyRunning ? '#ef4444' : '#22c55e') + '; color: white; padding: 12px 20px; font-size: 14px; font-weight: 600; border: none; border-radius: 8px; cursor: pointer; display: flex; align-items: center; gap: 8px;', + 'click': function() { + if (haproxyRunning) { + self.handleStop(); + } else { + self.handleStart(); + } + } + }, haproxyRunning ? ['\u23F9\uFE0F', ' Stop'] : ['\u25B6\uFE0F', ' Start']) + ]) + ]); + }, + renderStatsGrid: function(status, vhosts, backends, certificates) { var activeVhosts = vhosts.filter(function(v) { return v.enabled; }).length; var activeBackends = backends.filter(function(b) { return b.enabled; }).length; @@ -539,6 +606,19 @@ return view.extend({ }); }, + handleRestart: function() { + var self = this; + self.showToast('Restarting HAProxy...', 'warning'); + return api.restart().then(function(res) { + if (res.success) { + self.showToast('HAProxy service restarted', 'success'); + return self.refreshDashboard(); + } else { + self.showToast('Failed to restart: ' + (res.error || 'Unknown error'), 'error'); + } + }); + }, + handleReload: function() { var self = this; return api.reload().then(function(res) { diff --git a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js index 419b972..8d23201 100644 --- a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js +++ b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js @@ -3,6 +3,7 @@ 'require ui'; 'require dom'; 'require poll'; +'require fs'; 'require secubox/api as API'; 'require secubox-theme/theme as Theme'; 'require secubox/nav as SecuNav'; @@ -393,6 +394,14 @@ return view.extend({ { id: 'export_config', label: _('Export Configuration'), icon: '📦', variant: 'green' } ]; + // Critical services quick restart + var criticalServices = [ + { id: 'haproxy', label: 'HAProxy', icon: '⚖️' }, + { id: 'crowdsec', label: 'CrowdSec', icon: '🛡️' }, + { id: 'tor', label: 'Tor Shield', icon: '🧅' }, + { id: 'gitea', label: 'Gitea', icon: '🦊' } + ]; + return E('section', { 'class': 'sb-card' }, [ E('div', { 'class': 'sb-card-header' }, [ E('h2', {}, _('Quick Actions')), @@ -410,10 +419,87 @@ return view.extend({ E('span', { 'class': 'sb-action-icon' }, action.icon), E('span', { 'class': 'sb-action-label' }, action.label) ]); + })), + + // Critical Services Quick Restart Section + E('div', { 'class': 'sb-card-header', 'style': 'margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(255,255,255,0.1);' }, [ + E('h3', { 'style': 'font-size: 14px; margin: 0;' }, _('Critical Services Quick Restart')), + E('p', { 'class': 'sb-card-subtitle', 'style': 'font-size: 12px;' }, _('One-click restart for essential services')) + ]), + E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap; padding: 0 16px 16px;' }, + criticalServices.map(function(svc) { + return E('button', { + 'class': 'sb-service-restart-btn', + 'type': 'button', + 'style': 'display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: rgba(59, 130, 246, 0.15); border: 1px solid rgba(59, 130, 246, 0.3); border-radius: 8px; color: #3b82f6; cursor: pointer; font-size: 13px; transition: all 0.2s;', + 'click': function(ev) { + self.restartService(svc.id, ev.target); + }, + 'onmouseover': function(ev) { + ev.target.style.background = 'rgba(59, 130, 246, 0.25)'; + ev.target.style.borderColor = '#3b82f6'; + }, + 'onmouseout': function(ev) { + ev.target.style.background = 'rgba(59, 130, 246, 0.15)'; + ev.target.style.borderColor = 'rgba(59, 130, 246, 0.3)'; + } + }, [ + E('span', {}, svc.icon), + E('span', {}, svc.label), + E('span', { 'style': 'opacity: 0.7;' }, '🔄') + ]); })) ]); }, + restartService: function(serviceId, btnElement) { + var self = this; + + // Visual feedback + if (btnElement) { + btnElement.style.opacity = '0.6'; + btnElement.disabled = true; + } + + ui.showModal(_('Restarting Service'), [ + E('p', { 'class': 'spinning' }, _('Restarting ') + serviceId + '...') + ]); + + // Map service to init.d script + var serviceMap = { + 'haproxy': 'haproxy', + 'crowdsec': 'crowdsec', + 'tor': 'tor', + 'gitea': 'gitea' + }; + + var initScript = serviceMap[serviceId] || serviceId; + + return L.resolveDefault( + L.Request.post(L.env.cgi_base + '/cgi-exec', 'command=/etc/init.d/' + initScript + ' restart'), + {} + ).then(function() { + // Also try the standard approach via fs + return fs.exec('/etc/init.d/' + initScript, ['restart']); + }).then(function() { + ui.hideModal(); + ui.addNotification(null, E('p', {}, serviceId + ' ' + _('restarted successfully')), 'info'); + }).catch(function(err) { + ui.hideModal(); + // Fallback: try via API if available + return API.quickAction('restart_' + serviceId).then(function() { + ui.addNotification(null, E('p', {}, serviceId + ' ' + _('restarted successfully')), 'info'); + }).catch(function() { + ui.addNotification(null, E('p', {}, _('Failed to restart ') + serviceId + ': ' + (err.message || err)), 'error'); + }); + }).finally(function() { + if (btnElement) { + btnElement.style.opacity = '1'; + btnElement.disabled = false; + } + }); + }, + runQuickAction: function(actionId) { ui.showModal(_('Executing action...'), [ E('p', { 'class': 'spinning' }, _('Running ') + actionId + ' ...') diff --git a/package/secubox/secubox-app-metabolizer/files/etc/config/metabolizer b/package/secubox/secubox-app-metabolizer/files/etc/config/metabolizer index 9c003da..f68d197 100644 --- a/package/secubox/secubox-app-metabolizer/files/etc/config/metabolizer +++ b/package/secubox/secubox-app-metabolizer/files/etc/config/metabolizer @@ -18,10 +18,10 @@ config cms 'cms' config hexo 'hexo' option source_path '/srv/hexojs/site/source/_posts' option public_path '/srv/hexojs/site/public' - option portal_path '/www' - option auto_publish '0' + option portal_path '/www/blog' + option auto_publish '1' config portal 'portal' option enabled '1' - option url_path '/' + option url_path '/blog' option title 'SecuBox Blog'