secubox-openwrt/package/secubox/luci-app-client-guardian/htdocs/luci-static/resources/view/client-guardian/clients.js
CyberMind-FR e58f479cd4 feat(waf): Update WAF scenarios with 2024-2025 CVEs and OWASP threats
Add detection patterns for latest actively exploited vulnerabilities:
- CVE-2025-55182 (React2Shell, CVSS 10.0)
- CVE-2025-8110 (Gogs RCE), CVE-2025-53770 (SharePoint)
- CVE-2025-52691 (SmarterMail), CVE-2025-40551 (SolarWinds)
- CVE-2024-47575 (FortiManager), CVE-2024-21887 (Ivanti)
- CVE-2024-3400, CVE-2024-0012, CVE-2024-9474 (PAN-OS)

New attack categories based on OWASP Top 10 2025:
- HTTP Request Smuggling (TE.CL/CL.TE conflicts)
- AI/LLM Prompt Injection (ChatML, instruction markers)
- WAF Bypass techniques (Unicode normalization, double encoding)
- Supply Chain attacks (CI/CD poisoning, dependency confusion)
- Extended SSTI (Jinja2, Freemarker, Velocity, Thymeleaf)
- API Abuse (BOLA/IDOR, mass assignment)

CrowdSec scenarios split into 11 separate files for reliability.

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

356 lines
13 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.

'use strict';
'require view';
'require dom';
'require poll';
'require ui';
'require rpc';
'require secubox/kiss-theme';
'require client-guardian/nav as CgNav';
'require secubox-portal/header as SbHeader';
var callGetClients = rpc.declare({
object: 'luci.client-guardian',
method: 'clients',
expect: { clients: [] }
});
var callGetZones = rpc.declare({
object: 'luci.client-guardian',
method: 'zones',
expect: { zones: [] }
});
var callApproveClient = rpc.declare({
object: 'luci.client-guardian',
method: 'approve_client',
params: ['mac', 'name', 'zone', 'notes']
});
var callUpdateClient = rpc.declare({
object: 'luci.client-guardian',
method: 'update_client',
params: ['section', 'name', 'zone', 'notes', 'daily_quota', 'static_ip']
});
var callBanClient = rpc.declare({
object: 'luci.client-guardian',
method: 'ban_client',
params: ['mac', 'reason']
});
var callQuarantineClient = rpc.declare({
object: 'luci.client-guardian',
method: 'quarantine_client',
params: ['mac']
});
function formatBytes(bytes) {
if (!bytes || bytes === 0) return '0 B';
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = Math.floor(Math.log(bytes) / Math.log(1024));
i = Math.min(i, units.length - 1);
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + units[i];
}
function getDeviceIcon(hostname, mac) {
hostname = (hostname || '').toLowerCase();
mac = (mac || '').toLowerCase();
if (hostname.match(/android|iphone|ipad|mobile|phone|samsung|xiaomi|huawei/)) return '📱';
if (hostname.match(/pc|laptop|desktop|macbook|imac|windows|linux|ubuntu/)) return '💻';
if (hostname.match(/camera|bulb|switch|sensor|thermostat|doorbell|lock/)) return '📷';
if (hostname.match(/tv|roku|chromecast|firestick|appletv|media/)) return '📺';
if (hostname.match(/playstation|xbox|nintendo|switch|steam/)) return '🎮';
if (hostname.match(/router|switch|ap|access[-_]?point|bridge/)) return '🌐';
if (hostname.match(/printer|print|hp-|canon-|epson-/)) return '🖨';
return '🔌';
}
return view.extend({
load: function() {
return Promise.all([
callGetClients(),
callGetZones()
]);
},
render: function(data) {
var clients = Array.isArray(data[0]) ? data[0] : (data[0].clients || []);
var zones = Array.isArray(data[1]) ? data[1] : (data[1].zones || []);
var self = this;
var content = [
E('link', { 'rel': 'stylesheet', 'href': L.resource('client-guardian/dashboard.css') }),
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;' }, [
E('div', {}, [
E('h2', { 'style': 'margin: 0 0 4px 0;' }, 'Gestion des Clients'),
E('div', { 'style': 'color: var(--kiss-muted);' }, 'Client Guardian')
]),
E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': L.bind(this.handleRefresh, this)
}, 'Actualiser')
]),
CgNav.renderTabs('clients'),
// Filter tabs
E('div', { 'class': 'kiss-grid kiss-grid-auto', 'style': 'margin-bottom: 20px' }, [
this.renderFilterTab('all', 'Tous', clients.length, true),
this.renderFilterTab('online', 'En Ligne', clients.filter(function(c) { return c.online; }).length),
this.renderFilterTab('approved', 'Approuves', clients.filter(function(c) { return c.status === 'approved'; }).length),
this.renderFilterTab('quarantine', 'Quarantaine', clients.filter(function(c) { return c.status === 'unknown'; }).length),
this.renderFilterTab('banned', 'Bannis', clients.filter(function(c) { return c.status === 'banned'; }).length)
]),
// Clients list
E('div', { 'class': 'kiss-card' }, [
E('div', { 'class': 'kiss-card-title' }, [
'Liste des Clients',
E('span', { 'class': 'kiss-badge kiss-badge-blue', 'style': 'margin-left: 12px;' }, clients.length + ' total')
]),
E('div', { 'id': 'clients-list' }, [
E('div', { 'class': 'cg-client-list' },
clients.map(L.bind(this.renderClientRow, this, zones))
)
])
])
];
return KissTheme.wrap(content, 'client-guardian/clients');
},
renderFilterTab: function(filter, label, count, active) {
var tab = E('div', {
'class': 'kiss-stat' + (active ? ' kiss-panel-green' : ''),
'data-filter': filter,
'style': 'cursor: pointer; transition: all 0.2s;'
}, [
E('div', { 'class': 'kiss-stat-value' }, String(count)),
E('div', { 'class': 'kiss-stat-label' }, label)
]);
tab.addEventListener('click', L.bind(this.handleFilter, this));
return tab;
},
renderClientRow: function(zones, client) {
var statusClass = client.online ? 'online' : 'offline';
if (client.status === 'unknown') statusClass += ' quarantine';
if (client.status === 'banned') statusClass += ' banned';
var deviceIcon = getDeviceIcon(client.hostname || client.name, client.mac);
var zoneClass = (client.zone || 'unknown').replace('lan_', '');
var self = this;
return E('div', {
'class': 'cg-client-item ' + statusClass,
'data-status': client.status || 'unknown',
'data-online': client.online ? 'true' : 'false',
'data-mac': client.mac
}, [
E('div', { 'class': 'cg-client-avatar' }, deviceIcon),
E('div', { 'class': 'cg-client-info' }, [
E('div', { 'class': 'cg-client-name' }, [
client.online ? E('span', { 'class': 'online-indicator' }) : E('span'),
client.name || client.hostname || 'Unknown'
]),
E('div', { 'class': 'cg-client-meta' }, [
E('span', {}, client.mac),
E('span', {}, client.ip || 'N/A'),
client.first_seen ? E('span', {}, '📅 ' + client.first_seen.split(' ')[0]) : E('span')
])
]),
E('span', { 'class': 'cg-client-zone ' + zoneClass }, client.zone || 'unknown'),
E('div', { 'class': 'cg-client-traffic' }, [
E('div', { 'class': 'cg-client-traffic-value' }, ' ' + formatBytes(client.rx_bytes || 0)),
E('div', { 'class': 'cg-client-traffic-label' }, ' ' + formatBytes(client.tx_bytes || 0))
]),
E('div', { 'class': 'cg-client-actions' }, [
client.status === 'unknown' ? E('div', {
'class': 'cg-client-action approve',
'title': 'Approuver',
'data-mac': client.mac,
'click': L.bind(this.handleApprove, this, zones)
}, '') : E('span'),
client.status !== 'banned' ? E('div', {
'class': 'cg-client-action',
'title': 'Modifier',
'data-mac': client.mac,
'data-section': client.section,
'click': L.bind(this.handleEdit, this, client, zones)
}, '') : E('span'),
client.status !== 'banned' ? E('div', {
'class': 'cg-client-action ban',
'title': 'Bannir',
'data-mac': client.mac,
'click': L.bind(this.handleBan, this)
}, '🚫') : E('div', {
'class': 'cg-client-action',
'title': 'Débannir',
'data-mac': client.mac,
'click': L.bind(this.handleUnban, this)
}, '🔓')
])
]);
},
handleFilter: function(ev) {
var filter = ev.currentTarget.dataset.filter;
var items = document.querySelectorAll('.cg-client-item');
var tabs = document.querySelectorAll('.kiss-stat');
tabs.forEach(function(t) { t.classList.remove('kiss-panel-green'); });
ev.currentTarget.classList.add('kiss-panel-green');
items.forEach(function(item) {
var status = item.dataset.status;
var online = item.dataset.online === 'true';
var show = false;
switch(filter) {
case 'all': show = true; break;
case 'online': show = online; break;
case 'approved': show = status === 'approved'; break;
case 'quarantine': show = status === 'unknown'; break;
case 'banned': show = status === 'banned'; break;
}
item.style.display = show ? '' : 'none';
});
},
handleApprove: function(zones, ev) {
var mac = ev.currentTarget.dataset.mac;
ui.showModal(_('Approuver le Client'), [
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Nom du client'),
E('input', { 'type': 'text', 'id': 'approve-name', 'class': 'cg-input', 'placeholder': 'Ex: iPhone de Marie' })
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Zone'),
E('select', { 'id': 'approve-zone', 'class': 'cg-input' },
zones.map(function(z) {
return E('option', { 'value': z.id }, z.name);
})
)
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Notes'),
E('textarea', { 'id': 'approve-notes', 'class': 'cg-input', 'rows': '2' })
]),
E('div', { 'class': 'cg-btn-group', 'style': 'justify-content: flex-end' }, [
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
E('button', { 'class': 'cg-btn cg-btn-success', 'click': L.bind(function() {
var name = document.getElementById('approve-name').value;
var zone = document.getElementById('approve-zone').value;
var notes = document.getElementById('approve-notes').value;
callApproveClient(mac, name, zone, notes).then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Client approved successfully')), 'success');
this.handleRefresh();
}, this));
}, this)}, _('Approuver'))
])
]);
},
handleEdit: function(client, zones, ev) {
ui.showModal(_('Modifier le Client'), [
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Nom'),
E('input', { 'type': 'text', 'id': 'edit-name', 'class': 'cg-input', 'value': client.name || '' })
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Zone'),
E('select', { 'id': 'edit-zone', 'class': 'cg-input' },
zones.map(function(z) {
return E('option', { 'value': z.id, 'selected': z.id === client.zone }, z.name);
})
)
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'IP Statique'),
E('input', { 'type': 'text', 'id': 'edit-ip', 'class': 'cg-input', 'value': client.static_ip || '', 'placeholder': '192.168.1.x' })
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Quota journalier (minutes, 0=illimité)'),
E('input', { 'type': 'number', 'id': 'edit-quota', 'class': 'cg-input', 'value': client.daily_quota || '0' })
]),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Notes'),
E('textarea', { 'id': 'edit-notes', 'class': 'cg-input', 'rows': '2' }, client.notes || '')
]),
E('div', { 'class': 'cg-btn-group', 'style': 'justify-content: flex-end' }, [
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
E('button', { 'class': 'cg-btn cg-btn-primary', 'click': L.bind(function() {
callUpdateClient(
client.section,
document.getElementById('edit-name').value,
document.getElementById('edit-zone').value,
document.getElementById('edit-notes').value,
parseInt(document.getElementById('edit-quota').value) || 0,
document.getElementById('edit-ip').value
).then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Client updated successfully')), 'success');
this.handleRefresh();
}, this));
}, this)}, _('Enregistrer'))
])
]);
},
handleBan: function(ev) {
var mac = ev.currentTarget.dataset.mac;
ui.showModal(_('Bannir le Client'), [
E('p', {}, _('Voulez-vous vraiment bannir ce client ?')),
E('p', { 'style': 'font-family: monospace; font-size: 14px' }, mac),
E('div', { 'class': 'cg-form-group' }, [
E('label', { 'class': 'cg-form-label' }, 'Raison'),
E('input', { 'type': 'text', 'id': 'ban-reason', 'class': 'cg-input', 'placeholder': 'Ex: Appareil non autorisé' })
]),
E('div', { 'class': 'cg-btn-group', 'style': 'justify-content: flex-end' }, [
E('button', { 'class': 'cg-btn', 'click': ui.hideModal }, _('Annuler')),
E('button', { 'class': 'cg-btn cg-btn-danger', 'click': L.bind(function() {
var reason = document.getElementById('ban-reason').value || 'Manual ban';
callBanClient(mac, reason).then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('Client banned successfully')), 'info');
this.handleRefresh();
}, this));
}, this)}, _('Bannir'))
])
]);
},
handleUnban: function(ev) {
var mac = ev.currentTarget.dataset.mac;
callQuarantineClient(mac).then(L.bind(function() {
ui.addNotification(null, E('p', _('Client unbanned successfully')), 'success');
this.handleRefresh();
}, this));
},
handleRefresh: function() {
return Promise.all([
callGetClients(),
callGetZones()
]).then(L.bind(function(data) {
var container = document.querySelector('.kiss-main');
if (container) {
var newView = this.render(data);
dom.content(container.parentNode, newView);
}
}, this)).catch(function(err) {
console.error('Failed to refresh clients list:', err);
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});