secubox-openwrt/package/secubox/luci-app-haproxy/htdocs/luci-static/resources/view/haproxy/overview.js
CyberMind-FR f3fd676ad1 feat(haproxy): Add HAProxy load balancer packages for OpenWrt
- Add secubox-app-haproxy: LXC-containerized HAProxy service
  - Alpine Linux container with HAProxy
  - Multi-certificate SSL/TLS termination with SNI routing
  - ACME/Let's Encrypt auto-renewal
  - Virtual hosts management
  - Backend health checks and load balancing

- Add luci-app-haproxy: Full LuCI web interface
  - Overview dashboard with service status
  - Virtual hosts management with SSL options
  - Backends and servers configuration
  - SSL certificate management (ACME + import)
  - ACLs and URL-based routing rules
  - Statistics dashboard and logs
  - Settings for ports, timeouts, ACME

- Update luci-app-secubox-portal:
  - Add Services category with HAProxy, HexoJS, PicoBrew,
    Tor Shield, Jellyfin, Home Assistant, AdGuard Home, Nextcloud
  - Make portal dynamic - only shows installed apps
  - Add empty state UI for sections with no apps
  - Remove 404 errors for uninstalled apps

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 20:09:32 +01:00

243 lines
7.3 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require ui';
'require haproxy.api as api';
return view.extend({
load: function() {
return api.getDashboardData();
},
render: function(data) {
var self = this;
var status = data.status || {};
var vhosts = data.vhosts || [];
var backends = data.backends || [];
var certificates = data.certificates || [];
var containerRunning = status.container_running;
var haproxyRunning = status.haproxy_running;
var enabled = status.enabled;
var statusText = haproxyRunning ? 'Running' : (containerRunning ? 'Container Running' : 'Stopped');
var statusClass = haproxyRunning ? 'running' : (containerRunning ? 'unknown' : 'stopped');
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'HAProxy Load Balancer'),
// Dashboard cards
E('div', { 'class': 'haproxy-dashboard' }, [
// Status card
E('div', { 'class': 'haproxy-card' }, [
E('h3', {}, 'Service Status'),
E('div', { 'class': 'haproxy-status' }, [
E('span', { 'class': 'haproxy-status-indicator ' + statusClass }),
E('span', { 'class': 'stat-value' }, statusText)
]),
E('div', { 'class': 'haproxy-actions' }, [
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': function() { self.handleStart(); },
'disabled': haproxyRunning
}, 'Start'),
E('button', {
'class': 'cbi-button cbi-button-reset',
'click': function() { self.handleStop(); },
'disabled': !haproxyRunning
}, 'Stop'),
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.handleReload(); },
'disabled': !haproxyRunning
}, 'Reload')
])
]),
// Vhosts card
E('div', { 'class': 'haproxy-card' }, [
E('h3', {}, 'Virtual Hosts'),
E('div', { 'class': 'stat-value' }, String(vhosts.length)),
E('div', { 'class': 'stat-label' }, 'configured domains')
]),
// Backends card
E('div', { 'class': 'haproxy-card' }, [
E('h3', {}, 'Backends'),
E('div', { 'class': 'stat-value' }, String(backends.length)),
E('div', { 'class': 'stat-label' }, 'backend pools')
]),
// Certificates card
E('div', { 'class': 'haproxy-card' }, [
E('h3', {}, 'SSL Certificates'),
E('div', { 'class': 'stat-value' }, String(certificates.length)),
E('div', { 'class': 'stat-label' }, 'certificates')
])
]),
// Quick info section
E('div', { 'class': 'haproxy-form-section' }, [
E('h3', {}, 'Connection Details'),
E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'style': 'width: 200px' }, 'HTTP Port'),
E('td', { 'class': 'td' }, String(status.http_port || 80))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'HTTPS Port'),
E('td', { 'class': 'td' }, String(status.https_port || 443))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Stats Dashboard'),
E('td', { 'class': 'td' }, status.stats_enabled ?
E('a', { 'href': 'http://' + window.location.hostname + ':' + (status.stats_port || 8404) + '/stats', 'target': '_blank' },
'http://' + window.location.hostname + ':' + (status.stats_port || 8404) + '/stats')
: 'Disabled')
])
])
]),
// Recent vhosts
E('div', { 'class': 'haproxy-form-section' }, [
E('h3', {}, 'Virtual Hosts'),
this.renderVhostsTable(vhosts.slice(0, 5)),
vhosts.length > 5 ? E('p', {},
E('a', { 'href': L.url('admin/services/haproxy/vhosts') }, 'View all ' + vhosts.length + ' virtual hosts')
) : null
]),
// Quick actions
E('div', { 'class': 'haproxy-form-section' }, [
E('h3', {}, 'Quick Actions'),
E('div', { 'class': 'haproxy-actions' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.handleValidate(); }
}, 'Validate Config'),
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.handleGenerate(); }
}, 'Regenerate Config'),
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': function() { self.handleInstall(); },
'disabled': containerRunning
}, 'Install Container')
])
])
]);
// Add CSS
var style = E('style', {}, `
@import url('/luci-static/resources/haproxy/dashboard.css');
`);
view.insertBefore(style, view.firstChild);
return view;
},
renderVhostsTable: function(vhosts) {
if (vhosts.length === 0) {
return E('p', { 'style': 'color: var(--text-color-medium, #666)' },
'No virtual hosts configured. Add one in the Virtual Hosts tab.');
}
return E('table', { 'class': 'haproxy-vhosts-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, 'Domain'),
E('th', {}, 'Backend'),
E('th', {}, 'SSL'),
E('th', {}, 'Status')
])
]),
E('tbody', {}, vhosts.map(function(vh) {
return E('tr', {}, [
E('td', {}, vh.domain),
E('td', {}, vh.backend || '-'),
E('td', {}, [
vh.ssl ? E('span', { 'class': 'haproxy-badge ssl' }, 'SSL') : null,
vh.acme ? E('span', { 'class': 'haproxy-badge acme' }, 'ACME') : null
]),
E('td', {}, E('span', {
'class': 'haproxy-badge ' + (vh.enabled ? 'enabled' : 'disabled')
}, vh.enabled ? 'Enabled' : 'Disabled'))
]);
}))
]);
},
handleStart: function() {
return api.start().then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'HAProxy service started'));
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, 'Failed to start: ' + (res.error || 'Unknown error')), 'error');
}
});
},
handleStop: function() {
return api.stop().then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'HAProxy service stopped'));
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, 'Failed to stop: ' + (res.error || 'Unknown error')), 'error');
}
});
},
handleReload: function() {
return api.reload().then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'HAProxy configuration reloaded'));
} else {
ui.addNotification(null, E('p', {}, 'Failed to reload: ' + (res.error || 'Unknown error')), 'error');
}
});
},
handleValidate: function() {
return api.validate().then(function(res) {
if (res.valid) {
ui.addNotification(null, E('p', {}, 'Configuration is valid'));
} else {
ui.addNotification(null, E('p', {}, 'Configuration error: ' + (res.error || 'Unknown error')), 'error');
}
});
},
handleGenerate: function() {
return api.generate().then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'Configuration regenerated'));
} else {
ui.addNotification(null, E('p', {}, 'Failed to generate: ' + (res.error || 'Unknown error')), 'error');
}
});
},
handleInstall: function() {
ui.showModal('Installing HAProxy Container', [
E('p', { 'class': 'spinning' }, 'Installing HAProxy container...')
]);
return api.install().then(function(res) {
ui.hideModal();
if (res.success) {
ui.addNotification(null, E('p', {}, 'HAProxy container installed successfully'));
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, 'Installation failed: ' + (res.error || 'Unknown error')), 'error');
}
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});