secubox-openwrt/package/secubox/luci-app-haproxy/htdocs/luci-static/resources/view/haproxy/acls.js
CyberMind-FR 04325c789f style(haproxy): Migrate acls.js and settings.js to KISS theme
ACLs & Routing (acls.js):
- Removed CSS import, replaced cbi- classes with kiss- classes
- Add ACL form with name, type, pattern, backend selector
- Add Redirect form with match host, target, code options
- KISS-styled tables for ACL and redirect rules
- Delete confirmation modals and toast notifications

Settings (settings.js):
- Removed CSS import, replaced cbi- classes with kiss- classes
- Service settings: enable, ports, max connections, memory, log level
- Statistics dashboard: enable, port, username, password
- Timeouts: connect, client, server, HTTP request, keep-alive, retries
- ACME/Let's Encrypt: enable, email, staging, key type, renew days
- KISS-styled form inputs with grid layout

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 12:06:23 +01:00

379 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require dom';
'require ui';
'require haproxy.api as api';
'require secubox/kiss-theme';
/**
* HAProxy ACLs & Routing - KISS Style
* Copyright (C) 2025 CyberMind.fr
*/
return view.extend({
load: function() {
return Promise.all([
api.listAcls(),
api.listRedirects(),
api.listBackends()
]);
},
render: function(data) {
var self = this;
var acls = data[0] || [];
var redirects = data[1] || [];
var backends = data[2] || [];
var K = KissTheme;
var content = K.E('div', {}, [
// Page Header
K.E('div', { 'style': 'margin-bottom: 20px;' }, [
K.E('h2', { 'style': 'margin: 0; font-size: 24px; display: flex; align-items: center; gap: 10px;' }, [
K.E('span', {}, '🔀'),
'ACLs & Routing'
]),
K.E('p', { 'style': 'margin: 4px 0 0; color: var(--kiss-muted, #94a3b8); font-size: 14px;' },
'Configure URL-based routing rules and redirections')
]),
// Add ACL Rule Card
K.E('div', { 'class': 'kiss-card' }, [
K.E('div', { 'class': 'kiss-card-title' }, [' ', 'Add ACL Rule']),
K.E('div', { 'class': 'kiss-grid kiss-grid-2', 'style': 'gap: 16px; margin-bottom: 16px;' }, [
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Name'),
K.E('input', {
'type': 'text',
'id': 'acl-name',
'placeholder': 'is_api',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
})
]),
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Match Type'),
K.E('select', {
'id': 'acl-type',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
}, [
K.E('option', { 'value': 'path_beg' }, 'Path begins with'),
K.E('option', { 'value': 'path_end' }, 'Path ends with'),
K.E('option', { 'value': 'path_reg' }, 'Path regex'),
K.E('option', { 'value': 'hdr(host)' }, 'Host header'),
K.E('option', { 'value': 'hdr_beg(host)' }, 'Host begins with'),
K.E('option', { 'value': 'src' }, 'Source IP'),
K.E('option', { 'value': 'url_param' }, 'URL parameter')
])
]),
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Pattern'),
K.E('input', {
'type': 'text',
'id': 'acl-pattern',
'placeholder': '/api/',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
})
]),
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Route to Backend'),
K.E('select', {
'id': 'acl-backend',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
}, [K.E('option', { 'value': '' }, '-- No routing (ACL only) --')].concat(
backends.map(function(b) {
return K.E('option', { 'value': b.id }, b.name);
})
))
])
]),
K.E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': function() { self.handleAddAcl(); }
}, ' Add ACL Rule')
]),
// ACL Rules Table
K.E('div', { 'class': 'kiss-card' }, [
K.E('div', { 'class': 'kiss-card-title' }, ['📋 ', 'ACL Rules (', String(acls.length), ')']),
this.renderAclsTable(acls, backends)
]),
// Add Redirect Rule Card
K.E('div', { 'class': 'kiss-card' }, [
K.E('div', { 'class': 'kiss-card-title' }, ['↪️ ', 'Add Redirect Rule']),
K.E('div', { 'class': 'kiss-grid kiss-grid-2', 'style': 'gap: 16px; margin-bottom: 16px;' }, [
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Name'),
K.E('input', {
'type': 'text',
'id': 'redirect-name',
'placeholder': 'www-redirect',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
})
]),
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Match Host (Regex)'),
K.E('input', {
'type': 'text',
'id': 'redirect-match',
'placeholder': '^www\\.',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
})
]),
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Target Host'),
K.E('input', {
'type': 'text',
'id': 'redirect-target',
'placeholder': 'Leave empty to strip matched portion',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
})
]),
K.E('div', {}, [
K.E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; display: block; margin-bottom: 6px;' }, 'Redirect Code'),
K.E('select', {
'id': 'redirect-code',
'style': 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'
}, [
K.E('option', { 'value': '301' }, '301 Permanent'),
K.E('option', { 'value': '302' }, '302 Temporary'),
K.E('option', { 'value': '307' }, '307 Temporary Redirect'),
K.E('option', { 'value': '308' }, '308 Permanent Redirect')
])
]),
K.E('div', { 'style': 'grid-column: span 2;' }, [
K.E('label', { 'style': 'display: flex; align-items: center; gap: 8px; cursor: pointer;' }, [
K.E('input', { 'type': 'checkbox', 'id': 'redirect-strip-www' }),
'Strip www prefix automatically'
])
])
]),
K.E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': function() { self.handleAddRedirect(); }
}, '↪️ Add Redirect')
]),
// Redirects Table
K.E('div', { 'class': 'kiss-card' }, [
K.E('div', { 'class': 'kiss-card-title' }, ['↪️ ', 'Redirect Rules (', String(redirects.length), ')']),
this.renderRedirectsTable(redirects)
])
]);
return KissTheme.wrap(content, 'admin/services/haproxy/acls');
},
renderAclsTable: function(acls, backends) {
var self = this;
var K = KissTheme;
if (acls.length === 0) {
return K.E('div', { 'style': 'text-align: center; padding: 30px; color: var(--kiss-muted);' },
'No ACL rules configured');
}
var backendMap = {};
backends.forEach(function(b) { backendMap[b.id] = b.name; });
return 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;' }, 'Name'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Type'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Pattern'),
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;' }, 'Status'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: right; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; width: 80px;' }, 'Actions')
])
]),
K.E('tbody', {}, acls.map(function(acl) {
return K.E('tr', { 'data-id': acl.id, 'style': 'border-bottom: 1px solid var(--kiss-line, #1e293b);' }, [
K.E('td', { 'style': 'padding: 12px;' }, K.E('strong', {}, acl.name)),
K.E('td', { 'style': 'padding: 12px; font-family: monospace; font-size: 12px;' }, acl.type),
K.E('td', { 'style': 'padding: 12px; font-family: monospace; font-size: 12px;' }, acl.pattern),
K.E('td', { 'style': 'padding: 12px;' }, backendMap[acl.backend] || acl.backend || '-'),
K.E('td', { 'style': 'padding: 12px;' }, K.badge(acl.enabled ? 'Enabled' : 'Disabled', acl.enabled ? 'green' : 'red')),
K.E('td', { 'style': 'padding: 12px; text-align: right;' }, [
K.E('button', {
'class': 'kiss-btn kiss-btn-red',
'style': 'padding: 4px 10px; font-size: 12px;',
'click': function() { self.handleDeleteAcl(acl); }
}, '🗑️')
])
]);
}))
]);
},
renderRedirectsTable: function(redirects) {
var self = this;
var K = KissTheme;
if (redirects.length === 0) {
return K.E('div', { 'style': 'text-align: center; padding: 30px; color: var(--kiss-muted);' },
'No redirect rules configured');
}
return 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;' }, 'Name'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Match Host'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Target'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Code'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: left; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Status'),
K.E('th', { 'style': 'padding: 10px 12px; text-align: right; font-size: 12px; color: var(--kiss-muted); text-transform: uppercase; width: 80px;' }, 'Actions')
])
]),
K.E('tbody', {}, redirects.map(function(r) {
return K.E('tr', { 'data-id': r.id, 'style': 'border-bottom: 1px solid var(--kiss-line, #1e293b);' }, [
K.E('td', { 'style': 'padding: 12px;' }, K.E('strong', {}, r.name)),
K.E('td', { 'style': 'padding: 12px; font-family: monospace; font-size: 12px;' }, r.match_host),
K.E('td', { 'style': 'padding: 12px;' }, r.strip_www ? 'Strip www' : (r.target_host || '-')),
K.E('td', { 'style': 'padding: 12px;' }, K.badge(r.code, 'blue')),
K.E('td', { 'style': 'padding: 12px;' }, K.badge(r.enabled ? 'Enabled' : 'Disabled', r.enabled ? 'green' : 'red')),
K.E('td', { 'style': 'padding: 12px; text-align: right;' }, [
K.E('button', {
'class': 'kiss-btn kiss-btn-red',
'style': 'padding: 4px 10px; font-size: 12px;',
'click': function() { self.handleDeleteRedirect(r); }
}, '🗑️')
])
]);
}))
]);
},
handleAddAcl: function() {
var self = this;
var name = document.getElementById('acl-name').value.trim();
var type = document.getElementById('acl-type').value;
var pattern = document.getElementById('acl-pattern').value.trim();
var backend = document.getElementById('acl-backend').value;
if (!name || !type || !pattern) {
self.showToast('Name, type and pattern are required', 'error');
return;
}
return api.createAcl(name, type, pattern, backend, 1).then(function(res) {
if (res.success) {
self.showToast('ACL rule created', 'success');
window.location.reload();
} else {
self.showToast('Failed: ' + (res.error || 'Unknown error'), 'error');
}
});
},
handleDeleteAcl: function(acl) {
var self = this;
var K = KissTheme;
var modalContent = K.E('div', {}, [
K.E('p', { 'style': 'margin: 0 0 12px;' }, 'Are you sure you want to delete this ACL rule?'),
K.E('div', {
'style': 'padding: 12px 16px; background: var(--kiss-bg2, #111827); border-radius: 8px; font-family: monospace; margin-bottom: 20px;'
}, acl.name),
K.E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 12px;' }, [
K.E('button', { 'class': 'kiss-btn', 'click': ui.hideModal }, 'Cancel'),
K.E('button', {
'class': 'kiss-btn kiss-btn-red',
'click': function() {
ui.hideModal();
api.deleteAcl(acl.id).then(function(res) {
if (res.success) {
self.showToast('ACL deleted', 'success');
window.location.reload();
} else {
self.showToast('Failed: ' + (res.error || 'Unknown error'), 'error');
}
});
}
}, '🗑️ Delete')
])
]);
ui.showModal('Delete ACL', [modalContent]);
},
handleAddRedirect: function() {
var self = this;
var name = document.getElementById('redirect-name').value.trim();
var matchHost = document.getElementById('redirect-match').value.trim();
var targetHost = document.getElementById('redirect-target').value.trim();
var stripWww = document.getElementById('redirect-strip-www').checked ? 1 : 0;
var code = parseInt(document.getElementById('redirect-code').value) || 301;
if (!name || !matchHost) {
self.showToast('Name and match host pattern are required', 'error');
return;
}
return api.createRedirect(name, matchHost, targetHost, stripWww, code, 1).then(function(res) {
if (res.success) {
self.showToast('Redirect rule created', 'success');
window.location.reload();
} else {
self.showToast('Failed: ' + (res.error || 'Unknown error'), 'error');
}
});
},
handleDeleteRedirect: function(r) {
var self = this;
var K = KissTheme;
var modalContent = K.E('div', {}, [
K.E('p', { 'style': 'margin: 0 0 12px;' }, 'Are you sure you want to delete this redirect rule?'),
K.E('div', {
'style': 'padding: 12px 16px; background: var(--kiss-bg2, #111827); border-radius: 8px; font-family: monospace; margin-bottom: 20px;'
}, r.name),
K.E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 12px;' }, [
K.E('button', { 'class': 'kiss-btn', 'click': ui.hideModal }, 'Cancel'),
K.E('button', {
'class': 'kiss-btn kiss-btn-red',
'click': function() {
ui.hideModal();
api.deleteRedirect(r.id).then(function(res) {
if (res.success) {
self.showToast('Redirect deleted', 'success');
window.location.reload();
} else {
self.showToast('Failed: ' + (res.error || 'Unknown error'), 'error');
}
});
}
}, '🗑️ Delete')
])
]);
ui.showModal('Delete Redirect', [modalContent]);
},
showToast: function(message, type) {
var existing = document.querySelector('.kiss-toast');
if (existing) existing.remove();
var icons = { success: '✅', error: '❌', warning: '⚠️' };
var colors = {
success: 'var(--kiss-green, #00C853)',
error: 'var(--kiss-red, #FF1744)',
warning: 'var(--kiss-yellow, #fbbf24)'
};
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;
document.body.appendChild(toast);
setTimeout(function() { toast.remove(); }, 4000);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});