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>
237 lines
6.3 KiB
JavaScript
237 lines
6.3 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require rpc';
|
|
'require ui';
|
|
'require poll';
|
|
'require secubox/kiss-theme';
|
|
|
|
var callStatus = rpc.declare({
|
|
object: 'luci.backup',
|
|
method: 'status',
|
|
expect: {}
|
|
});
|
|
|
|
var callList = rpc.declare({
|
|
object: 'luci.backup',
|
|
method: 'list',
|
|
params: ['type'],
|
|
expect: {}
|
|
});
|
|
|
|
var callContainerList = rpc.declare({
|
|
object: 'luci.backup',
|
|
method: 'container_list',
|
|
expect: {}
|
|
});
|
|
|
|
var callCreate = rpc.declare({
|
|
object: 'luci.backup',
|
|
method: 'create',
|
|
params: ['type'],
|
|
expect: {}
|
|
});
|
|
|
|
var callContainerBackup = rpc.declare({
|
|
object: 'luci.backup',
|
|
method: 'container_backup',
|
|
params: ['name'],
|
|
expect: {}
|
|
});
|
|
|
|
function formatDate(ts) {
|
|
if (!ts || ts === 0) return '-';
|
|
var d = new Date(ts * 1000);
|
|
return d.toLocaleString();
|
|
}
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return Promise.all([
|
|
callStatus(),
|
|
callList('all'),
|
|
callContainerList()
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var status = data[0] || {};
|
|
var backups = (data[1] || {}).backups || [];
|
|
var containers = (data[2] || {}).containers || [];
|
|
|
|
var view = E('div', { 'class': 'cbi-map' }, [
|
|
E('h2', {}, 'Backup Manager'),
|
|
|
|
// Status Card
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'Status'),
|
|
E('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td', 'style': 'width:200px' }, 'Storage Path'),
|
|
E('td', { 'class': 'td' }, status.storage_path || '/srv/backups')
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'Storage Used'),
|
|
E('td', { 'class': 'td' }, status.storage_used || '0')
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'Containers'),
|
|
E('td', { 'class': 'td' }, String(status.container_count || 0))
|
|
])
|
|
])
|
|
]),
|
|
|
|
// Quick Actions
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'Quick Actions'),
|
|
E('div', { 'style': 'display:flex;gap:10px;flex-wrap:wrap' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button-action',
|
|
'click': ui.createHandlerFn(this, function() {
|
|
return this.doBackup('full');
|
|
})
|
|
}, 'Full Backup'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, function() {
|
|
return this.doBackup('config');
|
|
})
|
|
}, 'Config Only'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, function() {
|
|
return this.doBackup('containers');
|
|
})
|
|
}, 'Containers Only')
|
|
])
|
|
]),
|
|
|
|
// Containers Section
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'LXC Containers'),
|
|
this.renderContainerTable(containers)
|
|
]),
|
|
|
|
// Backup History
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'Backup History'),
|
|
this.renderBackupTable(backups)
|
|
])
|
|
]);
|
|
|
|
return KissTheme.wrap([view], 'admin/system/backup');
|
|
},
|
|
|
|
renderContainerTable: function(containers) {
|
|
if (!containers || containers.length === 0) {
|
|
return E('p', { 'class': 'cbi-value-description' }, 'No LXC containers found.');
|
|
}
|
|
|
|
var rows = containers.map(L.bind(function(c) {
|
|
var stateClass = c.state === 'running' ? 'badge success' : 'badge';
|
|
return E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, c.name),
|
|
E('td', { 'class': 'td' }, [
|
|
E('span', { 'style': c.state === 'running' ? 'color:green' : 'color:gray' },
|
|
c.state === 'running' ? '● Running' : '○ Stopped')
|
|
]),
|
|
E('td', { 'class': 'td' }, c.size || '-'),
|
|
E('td', { 'class': 'td' }, String(c.backups || 0)),
|
|
E('td', { 'class': 'td' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button-action',
|
|
'style': 'padding:2px 8px;font-size:12px',
|
|
'click': ui.createHandlerFn(this, function(name) {
|
|
return this.doContainerBackup(name);
|
|
}, c.name)
|
|
}, 'Backup')
|
|
])
|
|
]);
|
|
}, this));
|
|
|
|
return E('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, 'Name'),
|
|
E('th', { 'class': 'th' }, 'State'),
|
|
E('th', { 'class': 'th' }, 'Size'),
|
|
E('th', { 'class': 'th' }, 'Backups'),
|
|
E('th', { 'class': 'th' }, 'Actions')
|
|
])
|
|
].concat(rows));
|
|
},
|
|
|
|
renderBackupTable: function(backups) {
|
|
if (!backups || backups.length === 0) {
|
|
return E('p', { 'class': 'cbi-value-description' }, 'No backups found.');
|
|
}
|
|
|
|
// Sort by timestamp descending
|
|
backups.sort(function(a, b) { return (b.timestamp || 0) - (a.timestamp || 0); });
|
|
|
|
var rows = backups.slice(0, 20).map(function(b) {
|
|
var typeLabel = {
|
|
'config': 'Config',
|
|
'container': 'Container',
|
|
'service': 'Service'
|
|
}[b.type] || b.type;
|
|
|
|
return E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, b.file),
|
|
E('td', { 'class': 'td' }, typeLabel),
|
|
E('td', { 'class': 'td' }, b.size || '-'),
|
|
E('td', { 'class': 'td' }, formatDate(b.timestamp))
|
|
]);
|
|
});
|
|
|
|
return E('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, 'File'),
|
|
E('th', { 'class': 'th' }, 'Type'),
|
|
E('th', { 'class': 'th' }, 'Size'),
|
|
E('th', { 'class': 'th' }, 'Date')
|
|
])
|
|
].concat(rows));
|
|
},
|
|
|
|
doBackup: function(type) {
|
|
ui.showModal('Creating Backup', [
|
|
E('p', { 'class': 'spinning' }, 'Creating ' + type + ' backup...')
|
|
]);
|
|
|
|
return callCreate(type).then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'Backup created successfully.'), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', 'Backup failed: ' + (res.output || 'Unknown error')), 'error');
|
|
}
|
|
window.location.reload();
|
|
}).catch(function(err) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
|
|
});
|
|
},
|
|
|
|
doContainerBackup: function(name) {
|
|
ui.showModal('Backing Up Container', [
|
|
E('p', { 'class': 'spinning' }, 'Backing up container: ' + name + '...')
|
|
]);
|
|
|
|
return callContainerBackup(name).then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'Container backup created.'), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', 'Backup failed: ' + (res.output || 'Unknown error')), 'error');
|
|
}
|
|
window.location.reload();
|
|
}).catch(function(err) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
|
|
});
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|