fix(haproxy): Fix NodeList rendering error in overview dashboard
- Add pollRegistered flag to prevent duplicate poll registration - Fix refreshDashboard to use replaceChild instead of dom.content - Build content arrays explicitly to avoid null values in arrays - Fix disabled attribute handling for action buttons Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
500e020809
commit
45c9a4b7dc
@ -11,7 +11,7 @@ LUCI_PKGARCH:=all
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-haproxy
|
PKG_NAME:=luci-app-haproxy
|
||||||
PKG_VERSION:=1.0.0
|
PKG_VERSION:=1.0.0
|
||||||
PKG_RELEASE:=3
|
PKG_RELEASE:=4
|
||||||
|
|
||||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
|
|||||||
@ -15,6 +15,7 @@ return view.extend({
|
|||||||
title: _('HAProxy Dashboard'),
|
title: _('HAProxy Dashboard'),
|
||||||
|
|
||||||
data: null,
|
data: null,
|
||||||
|
pollRegistered: false,
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
// Load CSS
|
// Load CSS
|
||||||
@ -36,47 +37,40 @@ return view.extend({
|
|||||||
var certificates = data.certificates || [];
|
var certificates = data.certificates || [];
|
||||||
|
|
||||||
var containerRunning = status.container_running;
|
var containerRunning = status.container_running;
|
||||||
var haproxyRunning = status.haproxy_running;
|
|
||||||
var enabled = status.enabled;
|
|
||||||
|
|
||||||
// Main wrapper
|
// Build content array, filtering out nulls
|
||||||
var view = E('div', { 'class': 'haproxy-dashboard' }, [
|
var content = [
|
||||||
// Page Header
|
|
||||||
this.renderPageHeader(status),
|
this.renderPageHeader(status),
|
||||||
|
|
||||||
// Warning banner if not running
|
|
||||||
!containerRunning ? this.renderWarningBanner() : null,
|
|
||||||
|
|
||||||
// Stats Grid
|
|
||||||
this.renderStatsGrid(status, vhosts, backends, certificates),
|
this.renderStatsGrid(status, vhosts, backends, certificates),
|
||||||
|
|
||||||
// Health Check Grid
|
|
||||||
this.renderHealthGrid(status),
|
this.renderHealthGrid(status),
|
||||||
|
|
||||||
// Main content grid
|
|
||||||
E('div', { 'class': 'hp-row' }, [
|
E('div', { 'class': 'hp-row' }, [
|
||||||
// Left column - Vhosts preview
|
|
||||||
E('div', { 'style': 'flex: 2' }, [
|
E('div', { 'style': 'flex: 2' }, [
|
||||||
this.renderVhostsCard(vhosts)
|
this.renderVhostsCard(vhosts)
|
||||||
]),
|
]),
|
||||||
// Right column - Backends + Certs
|
|
||||||
E('div', { 'style': 'flex: 1' }, [
|
E('div', { 'style': 'flex: 1' }, [
|
||||||
this.renderBackendsCard(backends),
|
this.renderBackendsCard(backends),
|
||||||
this.renderCertificatesCard(certificates)
|
this.renderCertificatesCard(certificates)
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Quick Actions
|
|
||||||
this.renderQuickActions(status),
|
this.renderQuickActions(status),
|
||||||
|
|
||||||
// Connection Info
|
|
||||||
this.renderConnectionInfo(status)
|
this.renderConnectionInfo(status)
|
||||||
]);
|
];
|
||||||
|
|
||||||
// Setup polling for auto-refresh
|
// Add warning banner if container not running
|
||||||
|
if (!containerRunning) {
|
||||||
|
content.splice(1, 0, this.renderWarningBanner());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main wrapper
|
||||||
|
var view = E('div', { 'class': 'haproxy-dashboard' }, content);
|
||||||
|
|
||||||
|
// Setup polling for auto-refresh (only once)
|
||||||
|
if (!this.pollRegistered) {
|
||||||
|
this.pollRegistered = true;
|
||||||
poll.add(function() {
|
poll.add(function() {
|
||||||
return self.refreshDashboard();
|
return self.refreshDashboard();
|
||||||
}, 30);
|
}, 30);
|
||||||
|
}
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
},
|
},
|
||||||
@ -87,6 +81,17 @@ return view.extend({
|
|||||||
var statusText = haproxyRunning ? 'Running' : (containerRunning ? 'Container Only' : 'Stopped');
|
var statusText = haproxyRunning ? 'Running' : (containerRunning ? 'Container Only' : 'Stopped');
|
||||||
var statusClass = haproxyRunning ? 'running' : (containerRunning ? 'warning' : 'stopped');
|
var statusClass = haproxyRunning ? 'running' : (containerRunning ? 'warning' : 'stopped');
|
||||||
|
|
||||||
|
var badges = [
|
||||||
|
E('div', { 'class': 'hp-header-badge' }, [
|
||||||
|
E('span', { 'class': 'hp-badge-dot ' + statusClass }),
|
||||||
|
statusText
|
||||||
|
])
|
||||||
|
];
|
||||||
|
|
||||||
|
if (status.version) {
|
||||||
|
badges.push(E('div', { 'class': 'hp-header-badge' }, 'v' + status.version));
|
||||||
|
}
|
||||||
|
|
||||||
return E('div', { 'class': 'hp-page-header' }, [
|
return E('div', { 'class': 'hp-page-header' }, [
|
||||||
E('div', {}, [
|
E('div', {}, [
|
||||||
E('h1', { 'class': 'hp-page-title' }, [
|
E('h1', { 'class': 'hp-page-title' }, [
|
||||||
@ -95,13 +100,7 @@ return view.extend({
|
|||||||
]),
|
]),
|
||||||
E('p', { 'class': 'hp-page-subtitle' }, 'High-performance reverse proxy and load balancer')
|
E('p', { 'class': 'hp-page-subtitle' }, 'High-performance reverse proxy and load balancer')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'hp-header-badges' }, [
|
E('div', { 'class': 'hp-header-badges' }, badges)
|
||||||
E('div', { 'class': 'hp-header-badge' }, [
|
|
||||||
E('span', { 'class': 'hp-badge-dot ' + statusClass }),
|
|
||||||
statusText
|
|
||||||
]),
|
|
||||||
status.version ? E('div', { 'class': 'hp-header-badge' }, 'v' + status.version) : null
|
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -122,7 +121,7 @@ return view.extend({
|
|||||||
E('button', {
|
E('button', {
|
||||||
'class': 'hp-btn hp-btn-primary',
|
'class': 'hp-btn hp-btn-primary',
|
||||||
'click': function() { self.handleInstall(); }
|
'click': function() { self.handleInstall(); }
|
||||||
}, ['\u{1F4E6}', ' Install Container'])
|
}, '\u{1F4E6} Install Container')
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
@ -176,10 +175,10 @@ return view.extend({
|
|||||||
status: status.haproxy_running ? 'success' : 'danger'
|
status: status.haproxy_running ? 'success' : 'danger'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: status.config_valid ? '\u2705' : '\u26A0\uFE0F',
|
icon: status.config_valid !== false ? '\u2705' : '\u26A0\uFE0F',
|
||||||
label: 'Config',
|
label: 'Config',
|
||||||
value: status.config_valid ? 'Valid' : 'Check Needed',
|
value: status.config_valid !== false ? 'Valid' : 'Check Needed',
|
||||||
status: status.config_valid ? 'success' : 'warning'
|
status: status.config_valid !== false ? 'success' : 'warning'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '\u{1F4E1}',
|
icon: '\u{1F4E1}',
|
||||||
@ -221,8 +220,6 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderVhostsCard: function(vhosts) {
|
renderVhostsCard: function(vhosts) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (vhosts.length === 0) {
|
if (vhosts.length === 0) {
|
||||||
return E('div', { 'class': 'hp-card' }, [
|
return E('div', { 'class': 'hp-card' }, [
|
||||||
E('div', { 'class': 'hp-card-header' }, [
|
E('div', { 'class': 'hp-card-header' }, [
|
||||||
@ -243,16 +240,28 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return E('div', { 'class': 'hp-card' }, [
|
var tableRows = vhosts.slice(0, 5).map(function(vh) {
|
||||||
E('div', { 'class': 'hp-card-header' }, [
|
var sslBadges = [];
|
||||||
E('div', { 'class': 'hp-card-title' }, [
|
if (vh.ssl) sslBadges.push(E('span', { 'class': 'hp-badge hp-badge-info', 'style': 'margin-right: 4px;' }, 'SSL'));
|
||||||
E('span', { 'class': 'hp-card-title-icon' }, '\u{1F310}'),
|
if (vh.acme) sslBadges.push(E('span', { 'class': 'hp-badge hp-badge-success' }, 'ACME'));
|
||||||
'Virtual Hosts (' + vhosts.length + ')'
|
|
||||||
]),
|
var domainCell = [E('strong', {}, vh.domain)];
|
||||||
E('a', { 'href': L.url('admin/services/haproxy/vhosts'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' },
|
if (vh.ssl_redirect) {
|
||||||
'Manage')
|
domainCell.push(E('small', { 'style': 'display: block; color: var(--hp-text-muted); font-size: 11px;' },
|
||||||
]),
|
'HTTPS redirect enabled'));
|
||||||
E('div', { 'class': 'hp-card-body no-padding' }, [
|
}
|
||||||
|
|
||||||
|
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'))
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var cardContent = [
|
||||||
E('table', { 'class': 'hp-table' }, [
|
E('table', { 'class': 'hp-table' }, [
|
||||||
E('thead', {}, [
|
E('thead', {}, [
|
||||||
E('tr', {}, [
|
E('tr', {}, [
|
||||||
@ -262,52 +271,39 @@ return view.extend({
|
|||||||
E('th', {}, 'Status')
|
E('th', {}, 'Status')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
E('tbody', {}, vhosts.slice(0, 5).map(function(vh) {
|
E('tbody', {}, tableRows)
|
||||||
return E('tr', {}, [
|
|
||||||
E('td', {}, [
|
|
||||||
E('strong', {}, vh.domain),
|
|
||||||
vh.ssl_redirect ? E('small', { 'style': 'display: block; color: var(--hp-text-muted); font-size: 11px;' },
|
|
||||||
'HTTPS redirect enabled') : null
|
|
||||||
]),
|
|
||||||
E('td', {}, E('span', { 'class': 'hp-mono' }, vh.backend || '-')),
|
|
||||||
E('td', {}, [
|
|
||||||
vh.ssl ? E('span', { 'class': 'hp-badge hp-badge-info', 'style': 'margin-right: 4px;' }, 'SSL') : null,
|
|
||||||
vh.acme ? E('span', { 'class': 'hp-badge hp-badge-success' }, 'ACME') : null
|
|
||||||
]),
|
|
||||||
E('td', {}, E('span', {
|
|
||||||
'class': 'hp-badge ' + (vh.enabled ? 'hp-badge-success' : 'hp-badge-danger')
|
|
||||||
}, vh.enabled ? 'Active' : 'Disabled'))
|
|
||||||
]);
|
|
||||||
}))
|
|
||||||
]),
|
|
||||||
vhosts.length > 5 ? 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')
|
|
||||||
) : null
|
|
||||||
])
|
])
|
||||||
]);
|
];
|
||||||
},
|
|
||||||
|
|
||||||
renderBackendsCard: function(backends) {
|
if (vhosts.length > 5) {
|
||||||
var activeCount = backends.filter(function(b) { return b.enabled; }).length;
|
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')));
|
||||||
|
}
|
||||||
|
|
||||||
return E('div', { 'class': 'hp-card' }, [
|
return E('div', { 'class': 'hp-card' }, [
|
||||||
E('div', { 'class': 'hp-card-header' }, [
|
E('div', { 'class': 'hp-card-header' }, [
|
||||||
E('div', { 'class': 'hp-card-title' }, [
|
E('div', { 'class': 'hp-card-title' }, [
|
||||||
E('span', { 'class': 'hp-card-title-icon' }, '\u{1F5A5}\uFE0F'),
|
E('span', { 'class': 'hp-card-title-icon' }, '\u{1F310}'),
|
||||||
'Backends'
|
'Virtual Hosts (' + vhosts.length + ')'
|
||||||
]),
|
]),
|
||||||
E('a', { 'href': L.url('admin/services/haproxy/backends'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' },
|
E('a', { 'href': L.url('admin/services/haproxy/vhosts'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' },
|
||||||
'Manage')
|
'Manage')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'hp-card-body' }, backends.length === 0 ? [
|
E('div', { 'class': 'hp-card-body no-padding' }, cardContent)
|
||||||
E('div', { 'class': 'hp-empty', 'style': 'padding: 20px;' }, [
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderBackendsCard: function(backends) {
|
||||||
|
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-icon', 'style': 'font-size: 32px;' }, '\u{1F5A5}\uFE0F'),
|
||||||
E('div', { 'class': 'hp-empty-text', 'style': 'font-size: 14px;' }, 'No backends configured')
|
E('div', { 'class': 'hp-empty-text', 'style': 'font-size: 14px;' }, 'No backends configured')
|
||||||
])
|
]);
|
||||||
] : [
|
} else {
|
||||||
E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' },
|
var backendItems = backends.slice(0, 4).map(function(b) {
|
||||||
backends.slice(0, 4).map(function(b) {
|
|
||||||
return E('div', {
|
return E('div', {
|
||||||
'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--hp-bg-tertiary); border-radius: 8px;'
|
'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--hp-bg-tertiary); border-radius: 8px;'
|
||||||
}, [
|
}, [
|
||||||
@ -320,13 +316,29 @@ return view.extend({
|
|||||||
'class': 'hp-badge ' + (b.enabled ? 'hp-badge-success' : 'hp-badge-danger')
|
'class': 'hp-badge ' + (b.enabled ? 'hp-badge-success' : 'hp-badge-danger')
|
||||||
}, b.enabled ? 'UP' : 'DOWN')
|
}, b.enabled ? 'UP' : 'DOWN')
|
||||||
]);
|
]);
|
||||||
})
|
});
|
||||||
),
|
|
||||||
backends.length > 4 ? E('div', { 'style': 'text-align: center; margin-top: 12px;' },
|
var content = [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;' },
|
E('a', { 'href': L.url('admin/services/haproxy/backends'), 'style': 'font-size: 13px;' },
|
||||||
'+' + (backends.length - 4) + ' more')
|
'+' + (backends.length - 4) + ' more')));
|
||||||
) : null
|
}
|
||||||
])
|
|
||||||
|
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')
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'hp-card-body' }, Array.isArray(cardBody) ? cardBody : [cardBody])
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -335,6 +347,47 @@ return view.extend({
|
|||||||
return c.days_until_expiry && c.days_until_expiry < 30 && c.days_until_expiry > 0;
|
return c.days_until_expiry && c.days_until_expiry < 30 && c.days_until_expiry > 0;
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
|
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')
|
||||||
|
]);
|
||||||
|
} 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']));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'class': 'hp-mono', 'style': 'font-size: 13px;' }, c.domain),
|
||||||
|
E('span', { 'class': 'hp-badge ' + badgeClass }, badgeText)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
content.push(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;' },
|
||||||
|
'+' + (certificates.length - 3) + ' more')));
|
||||||
|
}
|
||||||
|
|
||||||
|
cardBody = content;
|
||||||
|
}
|
||||||
|
|
||||||
return E('div', { 'class': 'hp-card' }, [
|
return E('div', { 'class': 'hp-card' }, [
|
||||||
E('div', { 'class': 'hp-card-header' }, [
|
E('div', { 'class': 'hp-card-header' }, [
|
||||||
E('div', { 'class': 'hp-card-title' }, [
|
E('div', { 'class': 'hp-card-title' }, [
|
||||||
@ -344,37 +397,7 @@ return view.extend({
|
|||||||
E('a', { 'href': L.url('admin/services/haproxy/certificates'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' },
|
E('a', { 'href': L.url('admin/services/haproxy/certificates'), 'class': 'hp-btn hp-btn-secondary hp-btn-sm' },
|
||||||
'Manage')
|
'Manage')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'hp-card-body' }, certificates.length === 0 ? [
|
E('div', { 'class': 'hp-card-body' }, Array.isArray(cardBody) ? cardBody : [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')
|
|
||||||
])
|
|
||||||
] : [
|
|
||||||
expiringCount > 0 ? 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'
|
|
||||||
]) : null,
|
|
||||||
E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' },
|
|
||||||
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);
|
|
||||||
return E('div', {
|
|
||||||
'style': 'display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--hp-bg-tertiary); border-radius: 8px;'
|
|
||||||
}, [
|
|
||||||
E('div', { 'class': 'hp-mono', 'style': 'font-size: 13px;' }, c.domain),
|
|
||||||
E('span', {
|
|
||||||
'class': 'hp-badge ' + (isExpired ? 'hp-badge-danger' : (isExpiring ? 'hp-badge-warning' : 'hp-badge-success'))
|
|
||||||
}, isExpired ? 'Expired' : (c.acme ? 'ACME' : 'Custom'))
|
|
||||||
]);
|
|
||||||
})
|
|
||||||
),
|
|
||||||
certificates.length > 3 ? E('div', { 'style': 'text-align: center; margin-top: 12px;' },
|
|
||||||
E('a', { 'href': L.url('admin/services/haproxy/certificates'), 'style': 'font-size: 13px;' },
|
|
||||||
'+' + (certificates.length - 3) + ' more')
|
|
||||||
) : null
|
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -435,7 +458,7 @@ return view.extend({
|
|||||||
E('div', { 'class': 'hp-quick-actions' }, actions.map(function(action) {
|
E('div', { 'class': 'hp-quick-actions' }, actions.map(function(action) {
|
||||||
return E('button', {
|
return E('button', {
|
||||||
'class': 'hp-action-btn',
|
'class': 'hp-action-btn',
|
||||||
'disabled': action.disabled,
|
'disabled': action.disabled ? true : null,
|
||||||
'click': action.click
|
'click': action.click
|
||||||
}, [
|
}, [
|
||||||
E('span', { 'class': 'hp-action-icon' }, action.icon),
|
E('span', { 'class': 'hp-action-icon' }, action.icon),
|
||||||
@ -449,15 +472,7 @@ return view.extend({
|
|||||||
renderConnectionInfo: function(status) {
|
renderConnectionInfo: function(status) {
|
||||||
var hostname = window.location.hostname;
|
var hostname = window.location.hostname;
|
||||||
|
|
||||||
return E('div', { 'class': 'hp-card' }, [
|
var items = [
|
||||||
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' }, [
|
|
||||||
E('div', { 'class': 'hp-connection-item' }, [
|
E('div', { 'class': 'hp-connection-item' }, [
|
||||||
E('span', { 'class': 'hp-connection-label' }, 'HTTP Endpoint'),
|
E('span', { 'class': 'hp-connection-label' }, 'HTTP Endpoint'),
|
||||||
E('span', { 'class': 'hp-connection-value' },
|
E('span', { 'class': 'hp-connection-value' },
|
||||||
@ -470,17 +485,30 @@ return view.extend({
|
|||||||
E('a', { 'href': 'https://' + hostname + ':' + (status.https_port || 443), 'target': '_blank' },
|
E('a', { 'href': 'https://' + hostname + ':' + (status.https_port || 443), 'target': '_blank' },
|
||||||
hostname + ':' + (status.https_port || 443)))
|
hostname + ':' + (status.https_port || 443)))
|
||||||
]),
|
]),
|
||||||
status.stats_enabled ? 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'))
|
|
||||||
]) : null,
|
|
||||||
E('div', { 'class': 'hp-connection-item' }, [
|
E('div', { 'class': 'hp-connection-item' }, [
|
||||||
E('span', { 'class': 'hp-connection-label' }, 'Config Path'),
|
E('span', { 'class': 'hp-connection-label' }, 'Config Path'),
|
||||||
E('span', { 'class': 'hp-connection-value' }, '/etc/haproxy/haproxy.cfg')
|
E('span', { 'class': 'hp-connection-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'))
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
@ -565,10 +593,11 @@ return view.extend({
|
|||||||
var self = this;
|
var self = this;
|
||||||
return api.getDashboardData().then(function(data) {
|
return api.getDashboardData().then(function(data) {
|
||||||
self.data = data;
|
self.data = data;
|
||||||
// Re-render dashboard content
|
|
||||||
var container = document.querySelector('.haproxy-dashboard');
|
var container = document.querySelector('.haproxy-dashboard');
|
||||||
if (container) {
|
if (container) {
|
||||||
dom.content(container, self.render(data).childNodes);
|
// Clear and rebuild content
|
||||||
|
var newView = self.render(data);
|
||||||
|
container.parentNode.replaceChild(newView, container);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -585,7 +614,7 @@ return view.extend({
|
|||||||
|
|
||||||
var toast = E('div', { 'class': 'hp-toast ' + (type || '') }, [
|
var toast = E('div', { 'class': 'hp-toast ' + (type || '') }, [
|
||||||
E('span', {}, iconMap[type] || '\u2139\uFE0F'),
|
E('span', {}, iconMap[type] || '\u2139\uFE0F'),
|
||||||
message
|
' ' + message
|
||||||
]);
|
]);
|
||||||
document.body.appendChild(toast);
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user