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 <noreply@anthropic.com>
This commit is contained in:
parent
c5e4d5a903
commit
1ff116e93d
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user