secubox-openwrt/package/secubox/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/overview.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

505 lines
18 KiB
JavaScript

'use strict';
'require view';
'require secubox-theme/theme as Theme';
'require poll';
'require dom';
'require ui';
'require wireguard-dashboard/api as api';
'require secubox/kiss-theme';
return view.extend({
title: _('WireGuard Dashboard'),
pollInterval: 5,
pollActive: true,
selectedInterface: 'all',
peerDescriptions: {},
bandwidthRates: {},
load: function() {
return api.getAllData();
},
// Interface control actions
handleInterfaceAction: function(iface, action) {
var self = this;
ui.showModal(_('Interface Control'), [
E('p', { 'class': 'spinning' }, _('Executing %s on %s...').format(action, iface))
]);
api.interfaceControl(iface, action).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', result.message || _('Action completed')), 'info');
} else {
ui.addNotification(null, E('p', result.error || _('Action failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
});
},
// Ping peer
handlePingPeer: function(peerIp, peerName) {
var self = this;
if (!peerIp || peerIp === '(none)') {
ui.addNotification(null, E('p', _('No endpoint IP available for this peer')), 'warning');
return;
}
// Extract IP from endpoint (remove port)
var ip = peerIp.split(':')[0];
ui.showModal(_('Ping Peer'), [
E('p', { 'class': 'spinning' }, _('Pinging %s (%s)...').format(peerName, ip))
]);
api.pingPeer(ip).then(function(result) {
ui.hideModal();
if (result.reachable) {
ui.addNotification(null, E('p', _('Peer %s is reachable (RTT: %s ms)').format(peerName, result.rtt_ms)), 'info');
} else {
ui.addNotification(null, E('p', _('Peer %s is not reachable').format(peerName)), 'warning');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Ping failed: %s').format(err.message || err)), 'error');
});
},
// Get peer display name
getPeerName: function(peer) {
if (this.peerDescriptions && this.peerDescriptions[peer.public_key]) {
return this.peerDescriptions[peer.public_key];
}
return 'Peer ' + peer.short_key;
},
// Interface tab filtering
setInterfaceFilter: function(ifaceName) {
this.selectedInterface = ifaceName;
var tabs = document.querySelectorAll('.wg-tab');
tabs.forEach(function(tab) {
tab.classList.toggle('active', tab.dataset.iface === ifaceName);
});
// Filter peer cards
var peerCards = document.querySelectorAll('.wg-peer-card');
peerCards.forEach(function(card) {
if (ifaceName === 'all' || card.dataset.interface === ifaceName) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
// Filter interface cards
var ifaceCards = document.querySelectorAll('.wg-interface-card');
ifaceCards.forEach(function(card) {
if (ifaceName === 'all' || card.dataset.iface === ifaceName) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
},
renderInterfaceTabs: function(interfaces) {
var self = this;
var tabs = [
E('button', {
'class': 'wg-tab active',
'data-iface': 'all',
'click': function() { self.setInterfaceFilter('all'); }
}, 'All Interfaces')
];
interfaces.forEach(function(iface) {
tabs.push(E('button', {
'class': 'wg-tab',
'data-iface': iface.name,
'click': function() { self.setInterfaceFilter(iface.name); }
}, iface.name));
});
return E('div', { 'class': 'wg-interface-tabs' }, tabs);
},
// Update stats without full re-render
updateStats: function(status) {
var updates = [
{ selector: '.wg-stat-interfaces', value: status.interface_count || 0 },
{ selector: '.wg-stat-total-peers', value: status.total_peers || 0 },
{ selector: '.wg-stat-active-peers', value: status.active_peers || 0 },
{ selector: '.wg-stat-rx', value: api.formatBytes(status.total_rx || 0) },
{ selector: '.wg-stat-tx', value: api.formatBytes(status.total_tx || 0) }
];
updates.forEach(function(u) {
var el = document.querySelector(u.selector);
if (el && el.textContent !== String(u.value)) {
el.textContent = u.value;
el.classList.add('wg-value-updated');
setTimeout(function() { el.classList.remove('wg-value-updated'); }, 500);
}
});
// Update status badge
var badge = document.querySelector('.wg-status-badge');
if (badge) {
var isActive = status.interface_count > 0;
badge.classList.toggle('offline', !isActive);
badge.innerHTML = '<span class="wg-status-dot"></span>' + (isActive ? 'VPN Active' : 'No Tunnels');
}
},
// Update peer cards
updatePeers: function(peers) {
var grid = document.querySelector('.wg-peer-grid');
if (!grid) return;
peers.slice(0, 6).forEach(function(peer, idx) {
var card = grid.children[idx];
if (!card) return;
// Update status
var statusEl = card.querySelector('.wg-peer-status');
if (statusEl) {
statusEl.textContent = peer.status;
statusEl.className = 'wg-peer-status ' + api.getPeerStatusClass(peer.status);
}
// Update handshake
var hsEl = card.querySelector('.wg-peer-detail-value[data-field="handshake"]');
if (hsEl) {
hsEl.textContent = api.formatHandshake(peer.handshake_ago);
}
// Update traffic
var rxEl = card.querySelector('.wg-peer-traffic-value.rx');
var txEl = card.querySelector('.wg-peer-traffic-value.tx');
if (rxEl) rxEl.textContent = api.formatBytes(peer.rx_bytes);
if (txEl) txEl.textContent = api.formatBytes(peer.tx_bytes);
// Update active state
card.classList.toggle('active', peer.status === 'active');
});
// Update badge count
var activePeers = peers.filter(function(p) { return p.status === 'active'; }).length;
var badge = document.querySelector('.wg-peers-badge');
if (badge) {
badge.textContent = activePeers + '/' + peers.length + ' active';
}
},
// Update interface cards
updateInterfaces: function(interfaces) {
interfaces.forEach(function(iface) {
var card = document.querySelector('.wg-interface-card[data-iface="' + iface.name + '"]');
if (!card) return;
// Update status
var statusEl = card.querySelector('.wg-interface-status');
if (statusEl) {
statusEl.textContent = iface.state;
statusEl.className = 'wg-interface-status ' + iface.state;
}
// Update traffic
var trafficEl = card.querySelector('.wg-interface-traffic');
if (trafficEl) {
trafficEl.textContent = '↓' + api.formatBytes(iface.rx_bytes) + ' / ↑' + api.formatBytes(iface.tx_bytes);
}
});
},
startPolling: function() {
var self = this;
this.pollActive = true;
poll.add(L.bind(function() {
if (!this.pollActive) return Promise.resolve();
return api.getAllData().then(L.bind(function(data) {
var status = data.status || {};
// Handle RPC expect unwrapping - may be array or object
var interfacesData = data.interfaces || [];
var peersData = data.peers || [];
var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []);
var peers = Array.isArray(peersData) ? peersData : (peersData.peers || []);
this.updateStats(status);
this.updatePeers(peers);
this.updateInterfaces(interfaces);
}, this));
}, this), this.pollInterval);
},
stopPolling: function() {
this.pollActive = false;
poll.stop();
},
render: function(data) {
var self = this;
var status = data.status || {};
// Handle RPC expect unwrapping - may be array or object
var interfacesData = data.interfaces || [];
var peersData = data.peers || [];
var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []);
var peers = Array.isArray(peersData) ? peersData : (peersData.peers || []);
// Store peer descriptions
this.peerDescriptions = data.descriptions || {};
var activePeers = peers.filter(function(p) { return p.status === 'active'; }).length;
var view = E('div', { 'class': 'wireguard-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
// Header
E('div', { 'class': 'wg-header' }, [
E('div', { 'class': 'wg-logo' }, [
E('div', { 'class': 'wg-logo-icon' }, '🔐'),
E('div', { 'class': 'wg-logo-text' }, ['Wire', E('span', {}, 'Guard')])
]),
E('div', { 'class': 'wg-header-info' }, [
E('div', {
'class': 'wg-status-badge ' + (status.interface_count > 0 ? '' : 'offline')
}, [
E('span', { 'class': 'wg-status-dot' }),
status.interface_count > 0 ? 'VPN Active' : 'No Tunnels'
])
])
]),
// Auto-refresh control
E('div', { 'class': 'wg-refresh-control' }, [
E('span', { 'class': 'wg-refresh-status' }, [
E('span', { 'class': 'wg-refresh-indicator active' }),
' Auto-refresh: ',
E('span', { 'class': 'wg-refresh-state' }, 'Active')
]),
E('button', {
'class': 'wg-btn wg-btn-sm',
'id': 'wg-poll-toggle',
'click': L.bind(function(ev) {
var btn = ev.target;
var indicator = document.querySelector('.wg-refresh-indicator');
var state = document.querySelector('.wg-refresh-state');
if (this.pollActive) {
this.stopPolling();
btn.textContent = '▶ Resume';
indicator.classList.remove('active');
state.textContent = 'Paused';
} else {
this.startPolling();
btn.textContent = '⏸ Pause';
indicator.classList.add('active');
state.textContent = 'Active';
}
}, this)
}, '⏸ Pause')
]),
// Interface tabs
interfaces.length > 1 ? this.renderInterfaceTabs(interfaces) : '',
// Quick Stats
E('div', { 'class': 'wg-quick-stats' }, [
E('div', { 'class': 'wg-quick-stat' }, [
E('div', { 'class': 'wg-quick-stat-header' }, [
E('span', { 'class': 'wg-quick-stat-icon' }, '🌐'),
E('span', { 'class': 'wg-quick-stat-label' }, 'Interfaces')
]),
E('div', { 'class': 'wg-quick-stat-value wg-stat-interfaces' }, status.interface_count || 0),
E('div', { 'class': 'wg-quick-stat-sub' }, 'Active tunnels')
]),
E('div', { 'class': 'wg-quick-stat' }, [
E('div', { 'class': 'wg-quick-stat-header' }, [
E('span', { 'class': 'wg-quick-stat-icon' }, '👥'),
E('span', { 'class': 'wg-quick-stat-label' }, 'Total Peers')
]),
E('div', { 'class': 'wg-quick-stat-value wg-stat-total-peers' }, status.total_peers || 0),
E('div', { 'class': 'wg-quick-stat-sub' }, 'Configured')
]),
E('div', { 'class': 'wg-quick-stat', 'style': '--stat-gradient: linear-gradient(135deg, #10b981, #34d399)' }, [
E('div', { 'class': 'wg-quick-stat-header' }, [
E('span', { 'class': 'wg-quick-stat-icon' }, '✅'),
E('span', { 'class': 'wg-quick-stat-label' }, 'Active Peers')
]),
E('div', { 'class': 'wg-quick-stat-value wg-stat-active-peers' }, status.active_peers || 0),
E('div', { 'class': 'wg-quick-stat-sub' }, 'Connected now')
]),
E('div', { 'class': 'wg-quick-stat' }, [
E('div', { 'class': 'wg-quick-stat-header' }, [
E('span', { 'class': 'wg-quick-stat-icon' }, '📥'),
E('span', { 'class': 'wg-quick-stat-label' }, 'Downloaded')
]),
E('div', { 'class': 'wg-quick-stat-value wg-stat-rx' }, api.formatBytes(status.total_rx || 0)),
E('div', { 'class': 'wg-quick-stat-sub' }, 'Total received')
]),
E('div', { 'class': 'wg-quick-stat' }, [
E('div', { 'class': 'wg-quick-stat-header' }, [
E('span', { 'class': 'wg-quick-stat-icon' }, '📤'),
E('span', { 'class': 'wg-quick-stat-label' }, 'Uploaded')
]),
E('div', { 'class': 'wg-quick-stat-value wg-stat-tx' }, api.formatBytes(status.total_tx || 0)),
E('div', { 'class': 'wg-quick-stat-sub' }, 'Total sent')
])
]),
// Interfaces
E('div', { 'class': 'wg-card' }, [
E('div', { 'class': 'wg-card-header' }, [
E('div', { 'class': 'wg-card-title' }, [
E('span', { 'class': 'wg-card-title-icon' }, '🔗'),
'WireGuard Interfaces'
]),
E('div', { 'class': 'wg-card-badge' }, interfaces.length + ' tunnel' + (interfaces.length !== 1 ? 's' : ''))
]),
E('div', { 'class': 'wg-card-body' },
interfaces.length > 0 ?
E('div', { 'class': 'wg-charts-grid' },
interfaces.map(function(iface) {
return E('div', { 'class': 'wg-interface-card', 'data-iface': iface.name }, [
E('div', { 'class': 'wg-interface-header' }, [
E('div', { 'class': 'wg-interface-name' }, [
E('div', { 'class': 'wg-interface-icon' }, '🌐'),
E('div', {}, [
E('h3', {}, iface.name),
E('p', {}, 'Listen port: ' + (iface.listen_port || 'N/A'))
])
]),
E('span', { 'class': 'wg-interface-status ' + iface.state }, iface.state)
]),
E('div', { 'class': 'wg-interface-details' }, [
E('div', { 'class': 'wg-interface-detail' }, [
E('div', { 'class': 'wg-interface-detail-label' }, 'Public Key'),
E('div', { 'class': 'wg-interface-detail-value' }, api.shortenKey(iface.public_key, 12))
]),
E('div', { 'class': 'wg-interface-detail' }, [
E('div', { 'class': 'wg-interface-detail-label' }, 'IPv4 Address'),
E('div', { 'class': 'wg-interface-detail-value' }, iface.ipv4_address || 'N/A')
]),
E('div', { 'class': 'wg-interface-detail' }, [
E('div', { 'class': 'wg-interface-detail-label' }, 'MTU'),
E('div', { 'class': 'wg-interface-detail-value' }, iface.mtu || 1420)
]),
E('div', { 'class': 'wg-interface-detail' }, [
E('div', { 'class': 'wg-interface-detail-label' }, 'Traffic'),
E('div', { 'class': 'wg-interface-detail-value wg-interface-traffic' },
'↓' + api.formatBytes(iface.rx_bytes) + ' / ↑' + api.formatBytes(iface.tx_bytes))
])
]),
// Interface control buttons
E('div', { 'class': 'wg-interface-controls' }, [
iface.state === 'up' ?
E('button', {
'class': 'wg-btn wg-btn-sm wg-btn-warning',
'click': L.bind(self.handleInterfaceAction, self, iface.name, 'down'),
'title': _('Bring interface down')
}, '⏹ Stop') :
E('button', {
'class': 'wg-btn wg-btn-sm wg-btn-success',
'click': L.bind(self.handleInterfaceAction, self, iface.name, 'up'),
'title': _('Bring interface up')
}, '▶ Start'),
E('button', {
'class': 'wg-btn wg-btn-sm',
'click': L.bind(self.handleInterfaceAction, self, iface.name, 'restart'),
'title': _('Restart interface')
}, '🔄 Restart')
])
]);
})
) :
E('div', { 'class': 'wg-empty' }, [
E('div', { 'class': 'wg-empty-icon' }, '🔐'),
E('div', { 'class': 'wg-empty-text' }, 'No WireGuard interfaces configured'),
E('p', {}, 'Configure a WireGuard interface in Network settings')
])
)
]),
// Recent Peers
peers.length > 0 ? E('div', { 'class': 'wg-card' }, [
E('div', { 'class': 'wg-card-header' }, [
E('div', { 'class': 'wg-card-title' }, [
E('span', { 'class': 'wg-card-title-icon' }, '👥'),
'Connected Peers'
]),
E('div', { 'class': 'wg-card-badge wg-peers-badge' }, activePeers + '/' + peers.length + ' active')
]),
E('div', { 'class': 'wg-card-body' }, [
E('div', { 'class': 'wg-peer-grid' },
peers.slice(0, 6).map(function(peer) {
var peerName = self.getPeerName(peer);
return E('div', { 'class': 'wg-peer-card ' + (peer.status === 'active' ? 'active' : ''), 'data-peer': peer.public_key, 'data-interface': peer.interface || '' }, [
E('div', { 'class': 'wg-peer-header' }, [
E('div', { 'class': 'wg-peer-info' }, [
E('div', { 'class': 'wg-peer-icon' }, peer.status === 'active' ? '✅' : '👤'),
E('div', {}, [
E('p', { 'class': 'wg-peer-name' }, peerName),
E('p', { 'class': 'wg-peer-key' }, api.shortenKey(peer.public_key, 16))
])
]),
E('span', { 'class': 'wg-peer-status ' + api.getPeerStatusClass(peer.status) }, peer.status)
]),
E('div', { 'class': 'wg-peer-details' }, [
E('div', { 'class': 'wg-peer-detail' }, [
E('span', { 'class': 'wg-peer-detail-label' }, 'Endpoint'),
E('span', { 'class': 'wg-peer-detail-value' }, peer.endpoint || '(none)')
]),
E('div', { 'class': 'wg-peer-detail' }, [
E('span', { 'class': 'wg-peer-detail-label' }, 'Last Handshake'),
E('span', { 'class': 'wg-peer-detail-value', 'data-field': 'handshake' }, api.formatHandshake(peer.handshake_ago))
]),
E('div', { 'class': 'wg-peer-detail', 'style': 'grid-column: span 2' }, [
E('span', { 'class': 'wg-peer-detail-label' }, 'Allowed IPs'),
E('span', { 'class': 'wg-peer-detail-value' }, peer.allowed_ips || 'N/A')
])
]),
E('div', { 'class': 'wg-peer-traffic' }, [
E('div', { 'class': 'wg-peer-traffic-item' }, [
E('div', { 'class': 'wg-peer-traffic-icon' }, '📥'),
E('div', { 'class': 'wg-peer-traffic-value rx' }, api.formatBytes(peer.rx_bytes)),
E('div', { 'class': 'wg-peer-traffic-label' }, 'Received')
]),
E('div', { 'class': 'wg-peer-traffic-item' }, [
E('div', { 'class': 'wg-peer-traffic-icon' }, '📤'),
E('div', { 'class': 'wg-peer-traffic-value tx' }, api.formatBytes(peer.tx_bytes)),
E('div', { 'class': 'wg-peer-traffic-label' }, 'Sent')
])
]),
// Peer action buttons
peer.endpoint && peer.endpoint !== '(none)' ?
E('div', { 'class': 'wg-peer-actions' }, [
E('button', {
'class': 'wg-btn wg-btn-xs',
'click': L.bind(self.handlePingPeer, self, peer.endpoint, peerName),
'title': _('Ping peer')
}, '📡 Ping')
]) : ''
]);
})
)
])
]) : ''
]);
// Include CSS
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('wireguard-dashboard/dashboard.css') });
document.head.appendChild(cssLink);
// Start auto-refresh
this.startPolling();
return KissTheme.wrap([view], 'admin/network/wireguard-dashboard/overview');
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});