From 1ff116e93de8163c9c88a1b0c8c6e2558f086eda Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 12 Feb 2026 12:03:55 +0100 Subject: [PATCH] style(haproxy): Migrate overview.js to KISS theme Rewrote HAProxy Overview dashboard to use KissTheme: - Removed external dashboard.css loading - Replaced all hp- classes with kiss- classes - Emergency banner with service status and controls - Stats grid with vhosts, backends, certs counts - System health grid with container/haproxy/config status - Virtual hosts table preview - Backends and certificates cards - Quick actions grid (start/stop/reload/validate/regenerate/stats) - Connection details with endpoints - KISS toast notifications Co-Authored-By: Claude Opus 4.5 --- .../resources/view/haproxy/overview.js | 585 +++++++----------- 1 file changed, 240 insertions(+), 345 deletions(-) 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 02bb432e..87265f43 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 @@ -7,7 +7,7 @@ 'require secubox/kiss-theme'; /** - * HAProxy Dashboard - Overview + * HAProxy Dashboard - Overview - KISS Style * Enhanced dashboard with stats, health monitoring, and quick actions * Copyright (C) 2025 CyberMind.fr */ @@ -19,17 +19,12 @@ return view.extend({ pollRegistered: false, load: function() { - // Load CSS - var cssLink = document.createElement('link'); - cssLink.rel = 'stylesheet'; - cssLink.href = L.resource('haproxy/dashboard.css'); - document.head.appendChild(cssLink); - return api.getDashboardData(); }, render: function(data) { var self = this; + var K = KissTheme; this.data = data; var status = data.status || {}; @@ -45,11 +40,11 @@ return view.extend({ this.renderPageHeader(status), this.renderStatsGrid(status, vhosts, backends, certificates), this.renderHealthGrid(status), - E('div', { 'class': 'hp-row' }, [ - E('div', { 'style': 'flex: 2' }, [ + K.E('div', { 'style': 'display: flex; gap: 20px; margin-bottom: 20px;' }, [ + K.E('div', { 'style': 'flex: 2;' }, [ this.renderVhostsCard(vhosts) ]), - E('div', { 'style': 'flex: 1' }, [ + K.E('div', { 'style': 'flex: 1; display: flex; flex-direction: column; gap: 20px;' }, [ this.renderBackendsCard(backends), this.renderCertificatesCard(certificates) ]) @@ -64,7 +59,7 @@ return view.extend({ } // Main wrapper - var view = E('div', { 'class': 'haproxy-dashboard' }, content); + var view = K.E('div', { 'class': 'kiss-dashboard' }, content); // Setup polling for auto-refresh (only once) if (!this.pollRegistered) { @@ -78,110 +73,109 @@ return view.extend({ }, renderPageHeader: function(status) { + var K = KissTheme; var haproxyRunning = status.haproxy_running; var containerRunning = status.container_running; var statusText = haproxyRunning ? 'Running' : (containerRunning ? 'Container Only' : 'Stopped'); - var statusClass = haproxyRunning ? 'running' : (containerRunning ? 'warning' : 'stopped'); + var statusColor = haproxyRunning ? 'green' : (containerRunning ? 'yellow' : 'red'); - var badges = [ - E('div', { 'class': 'hp-header-badge' }, [ - E('span', { 'class': 'hp-badge-dot ' + statusClass }), - statusText - ]) - ]; + var badges = [K.badge(statusText, statusColor)]; if (status.version) { - badges.push(E('div', { 'class': 'hp-header-badge' }, 'v' + status.version)); + badges.push(K.badge('v' + status.version, 'blue')); } - return E('div', { 'class': 'hp-page-header' }, [ - E('div', {}, [ - E('h1', { 'class': 'hp-page-title' }, [ - E('span', { 'class': 'hp-page-title-icon' }, '\u2696\uFE0F'), + return K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;' }, [ + K.E('div', {}, [ + K.E('h2', { 'style': 'margin: 0; font-size: 24px; display: flex; align-items: center; gap: 10px;' }, [ + K.E('span', {}, 'âš–ī¸'), 'HAProxy Load Balancer' ]), - E('p', { 'class': 'hp-page-subtitle' }, 'High-performance reverse proxy and load balancer') + K.E('p', { 'style': 'margin: 4px 0 0; color: var(--kiss-muted, #94a3b8); font-size: 14px;' }, + 'High-performance reverse proxy and load balancer') ]), - E('div', { 'class': 'hp-header-badges' }, badges) + K.E('div', { 'style': 'display: flex; gap: 8px;' }, badges) ]); }, renderWarningBanner: function() { var self = this; - return E('div', { - 'class': 'hp-card', - 'style': 'border-left: 4px solid var(--hp-warning); margin-bottom: 24px;' + var K = KissTheme; + + return K.E('div', { + 'class': 'kiss-card', + 'style': 'border-left: 4px solid var(--kiss-yellow, #fbbf24); margin-bottom: 20px;' }, [ - E('div', { 'class': 'hp-card-body', 'style': 'display: flex; align-items: center; gap: 16px;' }, [ - E('span', { 'style': 'font-size: 32px;' }, '\u26A0\uFE0F'), - E('div', { 'style': 'flex: 1;' }, [ - E('div', { 'style': 'font-weight: 600; font-size: 16px; margin-bottom: 4px;' }, + K.E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [ + K.E('span', { 'style': 'font-size: 32px;' }, 'âš ī¸'), + K.E('div', { 'style': 'flex: 1;' }, [ + K.E('div', { 'style': 'font-weight: 600; font-size: 16px; margin-bottom: 4px;' }, 'HAProxy Container Not Running'), - E('div', { 'style': 'color: var(--hp-text-secondary);' }, + K.E('div', { 'style': 'color: var(--kiss-muted);' }, 'The HAProxy container needs to be installed and started to use load balancing features.') ]), - E('button', { - 'class': 'hp-btn hp-btn-primary', + K.E('button', { + 'class': 'kiss-btn kiss-btn-blue', 'click': function() { self.handleInstall(); } - }, '\u{1F4E6} Install Container') + }, 'đŸ“Ļ Install Container') ]) ]); }, renderEmergencyBanner: function(status) { var self = this; + var K = KissTheme; var haproxyRunning = status.haproxy_running; var containerRunning = status.container_running; - var statusColor = haproxyRunning ? '#22c55e' : (containerRunning ? '#f97316' : '#ef4444'); + var statusColor = haproxyRunning ? 'var(--kiss-green, #22c55e)' : (containerRunning ? 'var(--kiss-yellow, #f97316)' : 'var(--kiss-red, #ef4444)'); var statusText = haproxyRunning ? 'HEALTHY' : (containerRunning ? 'DEGRADED' : 'DOWN'); - var statusIcon = haproxyRunning ? '\u2705' : (containerRunning ? '\u26A0\uFE0F' : '\u274C'); + var statusIcon = haproxyRunning ? '✅' : (containerRunning ? 'âš ī¸' : '❌'); - 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;' + return K.E('div', { + 'style': 'background: linear-gradient(135deg, var(--kiss-bg, #0f172a) 0%, var(--kiss-bg2, #1e293b) 100%); border: 1px solid ' + statusColor + '; border-radius: 12px; padding: 20px; margin-bottom: 20px; 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', { + K.E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [ + K.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;' }, + K.E('div', {}, [ + K.E('div', { 'style': 'font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--kiss-muted); margin-bottom: 4px;' }, 'Service Status'), + K.E('div', { 'style': 'font-size: 24px; font-weight: 700; color: ' + statusColor + ';' }, statusText), + K.E('div', { 'style': 'font-size: 13px; color: var(--kiss-muted); margin-top: 4px;' }, 'Container: ' + (containerRunning ? 'Running' : 'Stopped') + - ' \u2022 HAProxy: ' + (haproxyRunning ? 'Active' : 'Inactive')) + ' â€ĸ 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') + K.E('div', { 'style': 'display: flex; gap: 16px;' }, [ + K.E('div', { 'style': 'text-align: center; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 8px;' }, [ + K.E('div', { 'style': 'font-size: 20px;' }, containerRunning ? '✅' : '❌'), + K.E('div', { 'style': 'font-size: 11px; color: var(--kiss-muted); 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') + K.E('div', { 'style': 'text-align: center; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 8px;' }, [ + K.E('div', { 'style': 'font-size: 20px;' }, haproxyRunning ? '✅' : '❌'), + K.E('div', { 'style': 'font-size: 11px; color: var(--kiss-muted); 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') + K.E('div', { 'style': 'text-align: center; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 8px;' }, [ + K.E('div', { 'style': 'font-size: 20px;' }, status.config_valid !== false ? '✅' : 'âš ī¸'), + K.E('div', { 'style': 'font-size: 11px; color: var(--kiss-muted); 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;', + K.E('div', { 'style': 'display: flex; gap: 12px;' }, [ + K.E('button', { + 'class': 'kiss-btn kiss-btn-blue', + 'style': 'padding: 12px 20px;', '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;', + }, '🔄 Restart'), + K.E('button', { + 'class': 'kiss-btn ' + (haproxyRunning ? 'kiss-btn-red' : 'kiss-btn-green'), + 'style': 'padding: 12px 20px;', 'click': function() { if (haproxyRunning) { self.handleStop(); @@ -189,228 +183,174 @@ return view.extend({ self.handleStart(); } } - }, haproxyRunning ? ['\u23F9\uFE0F', ' Stop'] : ['\u25B6\uFE0F', ' Start']) + }, haproxyRunning ? 'âšī¸ Stop' : 'â–ļī¸ Start') ]) ]); }, renderStatsGrid: function(status, vhosts, backends, certificates) { + var K = KissTheme; var activeVhosts = vhosts.filter(function(v) { return v.enabled; }).length; var activeBackends = backends.filter(function(b) { return b.enabled; }).length; var validCerts = certificates.filter(function(c) { return !c.expired; }).length; - return E('div', { 'class': 'hp-stats-grid' }, [ - E('div', { 'class': 'hp-stat-card' }, [ - E('div', { 'class': 'hp-stat-icon' }, '\u{1F310}'), - E('div', { 'class': 'hp-stat-value' }, String(vhosts.length)), - E('div', { 'class': 'hp-stat-label' }, 'Virtual Hosts'), - E('div', { 'class': 'hp-stat-trend' }, activeVhosts + ' active') - ]), - E('div', { 'class': 'hp-stat-card' }, [ - E('div', { 'class': 'hp-stat-icon' }, '\u{1F5A5}\uFE0F'), - E('div', { 'class': 'hp-stat-value' }, String(backends.length)), - E('div', { 'class': 'hp-stat-label' }, 'Backends'), - E('div', { 'class': 'hp-stat-trend' }, activeBackends + ' active') - ]), - E('div', { 'class': 'hp-stat-card' }, [ - E('div', { 'class': 'hp-stat-icon' }, '\u{1F512}'), - E('div', { 'class': 'hp-stat-value' }, String(certificates.length)), - E('div', { 'class': 'hp-stat-label' }, 'SSL Certificates'), - E('div', { 'class': 'hp-stat-trend' }, validCerts + ' valid') - ]), - E('div', { 'class': 'hp-stat-card' }, [ - E('div', { 'class': 'hp-stat-icon' }, '\u{1F4CA}'), - E('div', { 'class': 'hp-stat-value ' + (status.haproxy_running ? 'success' : 'danger') }, - status.haproxy_running ? 'UP' : 'DOWN'), - E('div', { 'class': 'hp-stat-label' }, 'Service Status'), - E('div', { 'class': 'hp-stat-trend' }, status.enabled ? 'Auto-start enabled' : 'Manual start') - ]) - ]); + var stats = [ + { icon: '🌐', value: vhosts.length, label: 'Virtual Hosts', trend: activeVhosts + ' active' }, + { icon: 'đŸ–Ĩī¸', value: backends.length, label: 'Backends', trend: activeBackends + ' active' }, + { icon: '🔒', value: certificates.length, label: 'SSL Certificates', trend: validCerts + ' valid' }, + { icon: '📊', value: status.haproxy_running ? 'UP' : 'DOWN', label: 'Service Status', trend: status.enabled ? 'Auto-start enabled' : 'Manual start', color: status.haproxy_running ? 'var(--kiss-green)' : 'var(--kiss-red)' } + ]; + + return K.E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin-bottom: 20px;' }, + stats.map(function(stat) { + return K.E('div', { + 'style': 'background: var(--kiss-card, #161e2e); border: 1px solid var(--kiss-line, #1e293b); border-radius: 12px; padding: 20px; text-align: center;' + }, [ + K.E('div', { 'style': 'font-size: 28px; margin-bottom: 8px;' }, stat.icon), + K.E('div', { 'style': 'font-size: 28px; font-weight: 700;' + (stat.color ? ' color: ' + stat.color + ';' : '') }, String(stat.value)), + K.E('div', { 'style': 'font-size: 13px; color: var(--kiss-muted); margin-top: 4px;' }, stat.label), + K.E('div', { 'style': 'font-size: 11px; color: var(--kiss-muted); margin-top: 4px; opacity: 0.7;' }, stat.trend) + ]); + }) + ); }, renderHealthGrid: function(status) { + var K = KissTheme; var items = [ - { - icon: status.container_running ? '\u2705' : '\u274C', - label: 'Container', - value: status.container_running ? 'Running' : 'Stopped', - status: status.container_running ? 'success' : 'danger' - }, - { - icon: status.haproxy_running ? '\u2705' : '\u274C', - label: 'HAProxy', - value: status.haproxy_running ? 'Active' : 'Inactive', - status: status.haproxy_running ? 'success' : 'danger' - }, - { - icon: status.config_valid !== false ? '\u2705' : '\u26A0\uFE0F', - label: 'Config', - value: status.config_valid !== false ? 'Valid' : 'Check Needed', - status: status.config_valid !== false ? 'success' : 'warning' - }, - { - icon: '\u{1F4E1}', - label: 'HTTP Port', - value: String(status.http_port || 80), - status: '' - }, - { - icon: '\u{1F510}', - label: 'HTTPS Port', - value: String(status.https_port || 443), - status: '' - }, - { - icon: status.stats_enabled ? '\u{1F4CA}' : '\u26AA', - label: 'Stats Page', - value: status.stats_enabled ? 'Enabled' : 'Disabled', - status: status.stats_enabled ? 'success' : '' - } + { icon: status.container_running ? '✅' : '❌', label: 'Container', value: status.container_running ? 'Running' : 'Stopped', ok: status.container_running }, + { icon: status.haproxy_running ? '✅' : '❌', label: 'HAProxy', value: status.haproxy_running ? 'Active' : 'Inactive', ok: status.haproxy_running }, + { icon: status.config_valid !== false ? '✅' : 'âš ī¸', label: 'Config', value: status.config_valid !== false ? 'Valid' : 'Check Needed', ok: status.config_valid !== false }, + { icon: '📡', label: 'HTTP Port', value: String(status.http_port || 80) }, + { icon: '🔐', label: 'HTTPS Port', value: String(status.https_port || 443) }, + { icon: status.stats_enabled ? '📊' : 'âšĒ', label: 'Stats Page', value: status.stats_enabled ? 'Enabled' : 'Disabled', ok: status.stats_enabled } ]; - return E('div', { 'class': 'hp-card', 'style': 'margin-bottom: 24px;' }, [ - E('div', { 'class': 'hp-card-header' }, [ - E('div', { 'class': 'hp-card-title' }, [ - E('span', { 'class': 'hp-card-title-icon' }, '\u{1F3E5}'), - 'System Health' - ]) - ]), - E('div', { 'class': 'hp-card-body' }, [ - E('div', { 'class': 'hp-health-grid' }, items.map(function(item) { - return E('div', { 'class': 'hp-health-item' }, [ - E('div', { 'class': 'hp-health-icon' }, item.icon), - E('div', { 'class': 'hp-health-label' }, item.label), - E('div', { 'class': 'hp-health-value ' + item.status }, item.value) + return K.E('div', { 'class': 'kiss-card', 'style': 'margin-bottom: 20px;' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['đŸĨ ', 'System Health']), + K.E('div', { 'style': 'display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px;' }, + items.map(function(item) { + return K.E('div', { 'style': 'text-align: center; padding: 12px; background: var(--kiss-bg2, #111827); border-radius: 8px;' }, [ + K.E('div', { 'style': 'font-size: 20px; margin-bottom: 6px;' }, item.icon), + K.E('div', { 'style': 'font-size: 11px; color: var(--kiss-muted); text-transform: uppercase;' }, item.label), + K.E('div', { + 'style': 'font-size: 13px; font-weight: 500; margin-top: 4px;' + + (item.ok === true ? ' color: var(--kiss-green);' : (item.ok === false ? ' color: var(--kiss-red);' : '')) + }, item.value) ]); - })) - ]) + }) + ) ]); }, renderVhostsCard: function(vhosts) { + var K = KissTheme; + if (vhosts.length === 0) { - return E('div', { 'class': 'hp-card' }, [ - E('div', { 'class': 'hp-card-header' }, [ - E('div', { 'class': 'hp-card-title' }, [ - E('span', { 'class': 'hp-card-title-icon' }, '\u{1F310}'), - 'Virtual Hosts' - ]), - E('a', { 'href': L.url('admin/services/haproxy/vhosts'), 'class': 'hp-btn hp-btn-primary hp-btn-sm' }, - '+ Add Host') + return K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;' }, [ + K.E('div', { 'class': 'kiss-card-title', 'style': 'margin: 0;' }, ['🌐 ', 'Virtual Hosts']), + K.E('a', { 'href': L.url('admin/services/haproxy/vhosts'), 'class': 'kiss-btn kiss-btn-green', 'style': 'font-size: 12px;' }, '+ Add Host') ]), - E('div', { 'class': 'hp-card-body' }, [ - E('div', { 'class': 'hp-empty' }, [ - E('div', { 'class': 'hp-empty-icon' }, '\u{1F310}'), - E('div', { 'class': 'hp-empty-text' }, 'No virtual hosts configured'), - E('div', { 'class': 'hp-empty-hint' }, 'Add a virtual host to start routing traffic') - ]) + K.E('div', { 'style': 'text-align: center; padding: 40px 20px; color: var(--kiss-muted);' }, [ + K.E('div', { 'style': 'font-size: 48px; margin-bottom: 12px;' }, '🌐'), + K.E('div', {}, 'No virtual hosts configured'), + K.E('div', { 'style': 'font-size: 13px; margin-top: 6px;' }, 'Add a virtual host to start routing traffic') ]) ]); } var tableRows = vhosts.slice(0, 5).map(function(vh) { var sslBadges = []; - if (vh.ssl) sslBadges.push(E('span', { 'class': 'hp-badge hp-badge-info', 'style': 'margin-right: 4px;' }, 'SSL')); - if (vh.acme) sslBadges.push(E('span', { 'class': 'hp-badge hp-badge-success' }, 'ACME')); + if (vh.ssl) sslBadges.push(K.badge('SSL', 'blue')); + if (vh.acme) sslBadges.push(K.badge('ACME', 'green')); - var domainCell = [E('strong', {}, vh.domain)]; - if (vh.ssl_redirect) { - domainCell.push(E('small', { 'style': 'display: block; color: var(--hp-text-muted); font-size: 11px;' }, - 'HTTPS redirect enabled')); - } - - return E('tr', {}, [ - E('td', {}, domainCell), - E('td', {}, E('span', { 'class': 'hp-mono' }, vh.backend || '-')), - E('td', {}, sslBadges.length > 0 ? sslBadges : '-'), - E('td', {}, E('span', { - 'class': 'hp-badge ' + (vh.enabled ? 'hp-badge-success' : 'hp-badge-danger') - }, vh.enabled ? 'Active' : 'Disabled')) + return K.E('tr', {}, [ + K.E('td', { 'style': 'padding: 10px 12px;' }, [ + K.E('strong', {}, vh.domain), + vh.ssl_redirect ? K.E('small', { 'style': 'display: block; color: var(--kiss-muted); font-size: 11px;' }, 'HTTPS redirect enabled') : null + ]), + K.E('td', { 'style': 'padding: 10px 12px; font-family: monospace; font-size: 13px;' }, vh.backend || '-'), + K.E('td', { 'style': 'padding: 10px 12px;' }, sslBadges.length > 0 ? K.E('span', { 'style': 'display: flex; gap: 4px;' }, sslBadges) : '-'), + K.E('td', { 'style': 'padding: 10px 12px;' }, K.badge(vh.enabled ? 'Active' : 'Disabled', vh.enabled ? 'green' : 'red')) ]); }); var cardContent = [ - E('table', { 'class': 'hp-table' }, [ - E('thead', {}, [ - E('tr', {}, [ - E('th', {}, 'Domain'), - E('th', {}, 'Backend'), - E('th', {}, 'SSL'), - E('th', {}, 'Status') + K.E('table', { 'style': 'width: 100%; border-collapse: collapse;' }, [ + K.E('thead', {}, [ + K.E('tr', { 'style': 'border-bottom: 1px solid var(--kiss-line, #1e293b);' }, [ + K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Domain'), + K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Backend'), + K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'SSL'), + K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Status') ]) ]), - E('tbody', {}, tableRows) + K.E('tbody', {}, tableRows) ]) ]; if (vhosts.length > 5) { - cardContent.push(E('div', { 'style': 'padding: 12px 16px; text-align: center; border-top: 1px solid var(--hp-border);' }, - E('a', { 'href': L.url('admin/services/haproxy/vhosts') }, - 'View all ' + vhosts.length + ' virtual hosts \u2192'))); + cardContent.push(K.E('div', { 'style': 'padding: 12px 16px; text-align: center; border-top: 1px solid var(--kiss-line);' }, + K.E('a', { 'href': L.url('admin/services/haproxy/vhosts'), 'style': 'color: var(--kiss-blue); text-decoration: none;' }, + 'View all ' + vhosts.length + ' virtual hosts →'))); } - return E('div', { 'class': 'hp-card' }, [ - E('div', { 'class': 'hp-card-header' }, [ - E('div', { 'class': 'hp-card-title' }, [ - E('span', { 'class': 'hp-card-title-icon' }, '\u{1F310}'), - 'Virtual Hosts (' + vhosts.length + ')' - ]), - E('a', { 'href': L.url('admin/services/haproxy/vhosts'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' }, - 'Manage') + return K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;' }, [ + K.E('div', { 'class': 'kiss-card-title', 'style': 'margin: 0;' }, ['🌐 ', 'Virtual Hosts (', String(vhosts.length), ')']), + K.E('a', { 'href': L.url('admin/services/haproxy/vhosts'), 'class': 'kiss-btn', 'style': 'font-size: 12px;' }, 'Manage') ]), - E('div', { 'class': 'hp-card-body no-padding' }, cardContent) + K.E('div', {}, cardContent) ]); }, renderBackendsCard: function(backends) { + var K = KissTheme; var cardBody; if (backends.length === 0) { - cardBody = E('div', { 'class': 'hp-empty', 'style': 'padding: 20px;' }, [ - E('div', { 'class': 'hp-empty-icon', 'style': 'font-size: 32px;' }, '\u{1F5A5}\uFE0F'), - E('div', { 'class': 'hp-empty-text', 'style': 'font-size: 14px;' }, 'No backends configured') + cardBody = K.E('div', { 'style': 'text-align: center; padding: 20px; color: var(--kiss-muted);' }, [ + K.E('div', { 'style': 'font-size: 32px; margin-bottom: 8px;' }, 'đŸ–Ĩī¸'), + K.E('div', {}, 'No backends configured') ]); } else { var backendItems = backends.slice(0, 4).map(function(b) { - return E('div', { - 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--hp-bg-tertiary); border-radius: 8px;' + return K.E('div', { + 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--kiss-bg, #0f172a); border-radius: 8px;' }, [ - E('div', {}, [ - E('div', { 'style': 'font-weight: 500;' }, b.name), - E('div', { 'style': 'font-size: 12px; color: var(--hp-text-muted);' }, + K.E('div', {}, [ + K.E('div', { 'style': 'font-weight: 500;' }, b.name), + K.E('div', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, (b.mode || 'http').toUpperCase() + ' / ' + (b.balance || 'roundrobin')) ]), - E('span', { - 'class': 'hp-badge ' + (b.enabled ? 'hp-badge-success' : 'hp-badge-danger') - }, b.enabled ? 'UP' : 'DOWN') + K.badge(b.enabled ? 'UP' : 'DOWN', b.enabled ? 'green' : 'red') ]); }); - var content = [E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, backendItems)]; + cardBody = K.E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, backendItems); if (backends.length > 4) { - content.push(E('div', { 'style': 'text-align: center; margin-top: 12px;' }, - E('a', { 'href': L.url('admin/services/haproxy/backends'), 'style': 'font-size: 13px;' }, - '+' + (backends.length - 4) + ' more'))); + cardBody = K.E('div', {}, [ + cardBody, + K.E('div', { 'style': 'text-align: center; margin-top: 12px;' }, + K.E('a', { 'href': L.url('admin/services/haproxy/backends'), 'style': 'font-size: 13px; color: var(--kiss-blue);' }, + '+' + (backends.length - 4) + ' more')) + ]); } - - cardBody = content; } - return E('div', { 'class': 'hp-card' }, [ - E('div', { 'class': 'hp-card-header' }, [ - E('div', { 'class': 'hp-card-title' }, [ - E('span', { 'class': 'hp-card-title-icon' }, '\u{1F5A5}\uFE0F'), - 'Backends' - ]), - E('a', { 'href': L.url('admin/services/haproxy/backends'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' }, - 'Manage') + return K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;' }, [ + K.E('div', { 'class': 'kiss-card-title', 'style': 'margin: 0;' }, ['đŸ–Ĩī¸ ', 'Backends']), + K.E('a', { 'href': L.url('admin/services/haproxy/backends'), 'class': 'kiss-btn', 'style': 'font-size: 12px;' }, 'Manage') ]), - E('div', { 'class': 'hp-card-body' }, Array.isArray(cardBody) ? cardBody : [cardBody]) + cardBody ]); }, renderCertificatesCard: function(certificates) { + var K = KissTheme; var expiringCount = certificates.filter(function(c) { return c.days_until_expiry && c.days_until_expiry < 30 && c.days_until_expiry > 0; }).length; @@ -418,166 +358,118 @@ return view.extend({ var cardBody; if (certificates.length === 0) { - cardBody = E('div', { 'class': 'hp-empty', 'style': 'padding: 20px;' }, [ - E('div', { 'class': 'hp-empty-icon', 'style': 'font-size: 32px;' }, '\u{1F512}'), - E('div', { 'class': 'hp-empty-text', 'style': 'font-size: 14px;' }, 'No certificates') + cardBody = K.E('div', { 'style': 'text-align: center; padding: 20px; color: var(--kiss-muted);' }, [ + K.E('div', { 'style': 'font-size: 32px; margin-bottom: 8px;' }, '🔒'), + K.E('div', {}, 'No certificates') ]); } else { var content = []; if (expiringCount > 0) { - content.push(E('div', { - 'style': 'display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--hp-warning-soft); border-radius: 8px; margin-bottom: 12px; font-size: 13px; color: var(--hp-warning);' - }, ['\u26A0\uFE0F ', expiringCount + ' certificate(s) expiring soon'])); + content.push(K.E('div', { + 'style': 'display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: rgba(251,191,36,0.1); border: 1px solid var(--kiss-yellow); border-radius: 8px; margin-bottom: 12px; font-size: 13px; color: var(--kiss-yellow);' + }, 'âš ī¸ ' + expiringCount + ' certificate(s) expiring soon')); } var certItems = certificates.slice(0, 3).map(function(c) { var isExpiring = c.days_until_expiry && c.days_until_expiry < 30; var isExpired = c.expired || (c.days_until_expiry && c.days_until_expiry <= 0); - var badgeClass = isExpired ? 'hp-badge-danger' : (isExpiring ? 'hp-badge-warning' : 'hp-badge-success'); + var badgeColor = isExpired ? 'red' : (isExpiring ? 'yellow' : 'green'); var badgeText = isExpired ? 'Expired' : (c.acme ? 'ACME' : 'Custom'); - return E('div', { - 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--hp-bg-tertiary); border-radius: 8px;' + return K.E('div', { + 'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--kiss-bg, #0f172a); border-radius: 8px;' }, [ - E('div', { 'class': 'hp-mono', 'style': 'font-size: 13px;' }, c.domain), - E('span', { 'class': 'hp-badge ' + badgeClass }, badgeText) + K.E('div', { 'style': 'font-family: monospace; font-size: 13px;' }, c.domain), + K.badge(badgeText, badgeColor) ]); }); - content.push(E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, certItems)); + content.push(K.E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, certItems)); if (certificates.length > 3) { - content.push(E('div', { 'style': 'text-align: center; margin-top: 12px;' }, - E('a', { 'href': L.url('admin/services/haproxy/certificates'), 'style': 'font-size: 13px;' }, + content.push(K.E('div', { 'style': 'text-align: center; margin-top: 12px;' }, + K.E('a', { 'href': L.url('admin/services/haproxy/certificates'), 'style': 'font-size: 13px; color: var(--kiss-blue);' }, '+' + (certificates.length - 3) + ' more'))); } - cardBody = content; + cardBody = K.E('div', {}, content); } - return E('div', { 'class': 'hp-card' }, [ - E('div', { 'class': 'hp-card-header' }, [ - E('div', { 'class': 'hp-card-title' }, [ - E('span', { 'class': 'hp-card-title-icon' }, '\u{1F512}'), - 'Certificates' - ]), - E('a', { 'href': L.url('admin/services/haproxy/certificates'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' }, - 'Manage') + return K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;' }, [ + K.E('div', { 'class': 'kiss-card-title', 'style': 'margin: 0;' }, ['🔒 ', 'Certificates']), + K.E('a', { 'href': L.url('admin/services/haproxy/certificates'), 'class': 'kiss-btn', 'style': 'font-size: 12px;' }, 'Manage') ]), - E('div', { 'class': 'hp-card-body' }, Array.isArray(cardBody) ? cardBody : [cardBody]) + cardBody ]); }, renderQuickActions: function(status) { var self = this; + var K = KissTheme; var haproxyRunning = status.haproxy_running; var containerRunning = status.container_running; var actions = [ - { - icon: '\u25B6\uFE0F', - label: 'Start', - disabled: haproxyRunning, - click: function() { self.handleStart(); } - }, - { - icon: '\u23F9\uFE0F', - label: 'Stop', - disabled: !haproxyRunning, - click: function() { self.handleStop(); } - }, - { - icon: '\u{1F504}', - label: 'Reload', - disabled: !haproxyRunning, - click: function() { self.handleReload(); } - }, - { - icon: '\u2705', - label: 'Validate', - disabled: !containerRunning, - click: function() { self.handleValidate(); } - }, - { - icon: '\u{1F4DD}', - label: 'Regenerate', - disabled: !containerRunning, - click: function() { self.handleGenerate(); } - }, - { - icon: '\u{1F4CA}', - label: 'Stats', - disabled: !status.stats_enabled, - click: function() { - window.open('http://' + window.location.hostname + ':' + (status.stats_port || 8404) + '/stats', '_blank'); - } - } + { icon: 'â–ļī¸', label: 'Start', disabled: haproxyRunning, click: function() { self.handleStart(); } }, + { icon: 'âšī¸', label: 'Stop', disabled: !haproxyRunning, click: function() { self.handleStop(); } }, + { icon: '🔄', label: 'Reload', disabled: !haproxyRunning, click: function() { self.handleReload(); } }, + { icon: '✅', label: 'Validate', disabled: !containerRunning, click: function() { self.handleValidate(); } }, + { icon: '📝', label: 'Regenerate', disabled: !containerRunning, click: function() { self.handleGenerate(); } }, + { icon: '📊', label: 'Stats', disabled: !status.stats_enabled, click: function() { + window.open('http://' + window.location.hostname + ':' + (status.stats_port || 8404) + '/stats', '_blank'); + }} ]; - return E('div', { 'class': 'hp-card', 'style': 'margin-bottom: 24px;' }, [ - E('div', { 'class': 'hp-card-header' }, [ - E('div', { 'class': 'hp-card-title' }, [ - E('span', { 'class': 'hp-card-title-icon' }, '\u26A1'), - 'Quick Actions' - ]) - ]), - E('div', { 'class': 'hp-card-body' }, [ - E('div', { 'class': 'hp-quick-actions' }, actions.map(function(action) { - return E('button', { - 'class': 'hp-action-btn', + return K.E('div', { 'class': 'kiss-card', 'style': 'margin-bottom: 20px;' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['⚡ ', 'Quick Actions']), + K.E('div', { 'style': 'display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px;' }, + actions.map(function(action) { + return K.E('button', { + 'class': 'kiss-btn', + 'style': 'display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 16px 12px; font-size: 12px;' + (action.disabled ? ' opacity: 0.5; cursor: not-allowed;' : ''), 'disabled': action.disabled ? true : null, 'click': action.click }, [ - E('span', { 'class': 'hp-action-icon' }, action.icon), - E('span', { 'class': 'hp-action-label' }, action.label) + K.E('span', { 'style': 'font-size: 24px;' }, action.icon), + K.E('span', {}, action.label) ]); - })) - ]) + }) + ) ]); }, renderConnectionInfo: function(status) { + var K = KissTheme; var hostname = window.location.hostname; var items = [ - E('div', { 'class': 'hp-connection-item' }, [ - E('span', { 'class': 'hp-connection-label' }, 'HTTP Endpoint'), - E('span', { 'class': 'hp-connection-value' }, - E('a', { 'href': 'http://' + hostname + ':' + (status.http_port || 80), 'target': '_blank' }, - hostname + ':' + (status.http_port || 80))) - ]), - E('div', { 'class': 'hp-connection-item' }, [ - E('span', { 'class': 'hp-connection-label' }, 'HTTPS Endpoint'), - E('span', { 'class': 'hp-connection-value' }, - E('a', { 'href': 'https://' + hostname + ':' + (status.https_port || 443), 'target': '_blank' }, - hostname + ':' + (status.https_port || 443))) - ]), - E('div', { 'class': 'hp-connection-item' }, [ - E('span', { 'class': 'hp-connection-label' }, 'Config Path'), - E('span', { 'class': 'hp-connection-value' }, '/etc/haproxy/haproxy.cfg') - ]) + { label: 'HTTP Endpoint', value: hostname + ':' + (status.http_port || 80), url: 'http://' + hostname + ':' + (status.http_port || 80) }, + { label: 'HTTPS Endpoint', value: hostname + ':' + (status.https_port || 443), url: 'https://' + hostname + ':' + (status.https_port || 443) }, + { label: 'Config Path', value: '/etc/haproxy/haproxy.cfg' } ]; if (status.stats_enabled) { - items.splice(2, 0, E('div', { 'class': 'hp-connection-item' }, [ - E('span', { 'class': 'hp-connection-label' }, 'Stats Dashboard'), - E('span', { 'class': 'hp-connection-value' }, - E('a', { 'href': 'http://' + hostname + ':' + (status.stats_port || 8404) + '/stats', 'target': '_blank' }, - hostname + ':' + (status.stats_port || 8404) + '/stats')) - ])); + items.splice(2, 0, { + label: 'Stats Dashboard', + value: hostname + ':' + (status.stats_port || 8404) + '/stats', + url: 'http://' + hostname + ':' + (status.stats_port || 8404) + '/stats' + }); } - return E('div', { 'class': 'hp-card' }, [ - E('div', { 'class': 'hp-card-header' }, [ - E('div', { 'class': 'hp-card-title' }, [ - E('span', { 'class': 'hp-card-title-icon' }, '\u{1F4E1}'), - 'Connection Details' - ]) - ]), - E('div', { 'class': 'hp-card-body' }, [ - E('div', { 'class': 'hp-connection-grid' }, items) - ]) + return K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['📡 ', 'Connection Details']), + K.E('div', { 'style': 'display: grid; grid-template-columns: repeat(' + items.length + ', 1fr); gap: 16px;' }, + items.map(function(item) { + return K.E('div', { 'style': 'padding: 12px; background: var(--kiss-bg2, #111827); border-radius: 8px;' }, [ + K.E('div', { 'style': 'font-size: 11px; color: var(--kiss-muted); text-transform: uppercase; margin-bottom: 6px;' }, item.label), + item.url + ? K.E('a', { 'href': item.url, 'target': '_blank', 'style': 'color: var(--kiss-blue); font-size: 13px; text-decoration: none;' }, item.value) + : K.E('span', { 'style': 'font-family: monospace; font-size: 13px;' }, item.value) + ]); + }) + ) ]); }, @@ -674,34 +566,37 @@ return view.extend({ var self = this; return api.getDashboardData().then(function(data) { self.data = data; - var container = document.querySelector('.haproxy-dashboard'); + var container = document.querySelector('.kiss-dashboard'); if (container) { - // Clear and rebuild content + var parent = container.parentNode; var newView = self.render(data); - container.parentNode.replaceChild(newView, container); + // Find the kiss-dashboard inside the wrapped view + var newContainer = newView.querySelector ? newView.querySelector('.kiss-dashboard') : newView; + if (newContainer && parent) { + parent.replaceChild(newView, container.closest('.kiss-page') || container); + } } }); }, showToast: function(message, type) { - var existing = document.querySelector('.hp-toast'); + var existing = document.querySelector('.kiss-toast'); if (existing) existing.remove(); - var iconMap = { - 'success': '\u2705', - 'error': '\u274C', - 'warning': '\u26A0\uFE0F' + var icons = { success: '✅', error: '❌', warning: 'âš ī¸' }; + var colors = { + success: 'var(--kiss-green, #00C853)', + error: 'var(--kiss-red, #FF1744)', + warning: 'var(--kiss-yellow, #fbbf24)' }; - var toast = E('div', { 'class': 'hp-toast ' + (type || '') }, [ - E('span', {}, iconMap[type] || '\u2139\uFE0F'), - ' ' + message - ]); - document.body.appendChild(toast); + var toast = document.createElement('div'); + toast.className = 'kiss-toast'; + toast.style.cssText = 'position: fixed; bottom: 80px; right: 20px; padding: 12px 20px; border-radius: 8px; background: var(--kiss-card, #161e2e); border: 1px solid ' + (colors[type] || 'var(--kiss-line)') + '; color: var(--kiss-text, #e2e8f0); font-size: 14px; display: flex; align-items: center; gap: 10px; z-index: 9999; box-shadow: 0 4px 20px rgba(0,0,0,0.3);'; + toast.innerHTML = (icons[type] || 'â„šī¸') + ' ' + message; - setTimeout(function() { - toast.remove(); - }, 4000); + document.body.appendChild(toast); + setTimeout(function() { toast.remove(); }, 4000); }, handleSaveApply: null,