secubox-openwrt/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/settings.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

486 lines
15 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 ui';
'require system-hub/api as API';
'require secubox-theme/theme as Theme';
'require system-hub/theme-assets as ThemeAssets';
'require system-hub/nav as HubNav';
'require secubox-portal/header as SbHeader';
'require secubox/kiss-theme';
var shLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: shLang });
return view.extend({
settings: null,
fieldRefs: null,
load: function() {
return API.getSettings();
},
render: function(data) {
this.settings = data || {};
this.fieldRefs = {};
var content = [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
ThemeAssets.stylesheet('common.css'),
ThemeAssets.stylesheet('dashboard.css'),
HubNav.renderTabs('settings'),
this.renderHeader(),
this.renderGeneralSection(),
this.renderThemeSection(),
this.renderThresholdSection(),
this.renderSupportSection(),
this.renderActions()
];
return KissTheme.wrap(content, 'admin/system/hub/settings');
},
renderHeader: function() {
var general = this.settings.general || {};
var autoRefresh = this.boolValue(general.auto_refresh, true);
var healthCheck = this.boolValue(general.health_check, true);
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '⚙️'),
_('System Hub Preferences')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Control health checks, refresh cadence, and alert thresholds for every System Hub widget.'))
]),
E('div', { 'class': 'sh-header-meta' }, [
this.renderChip('⏱️', _('Auto refresh'), autoRefresh ? _('Enabled') : _('Disabled')),
this.renderChip('🩺', _('Health monitor'), healthCheck ? _('Active') : _('Paused')),
this.renderChip('🧪', _('Diagnostics'), _('Manual triggers'))
])
]);
},
renderChip: function(icon, label, value) {
return E('div', { 'class': 'sh-header-chip' }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, value || '—')
])
]);
},
renderGeneralSection: function() {
var general = this.settings.general || {};
var refresh = (general.refresh_interval != null) ? String(general.refresh_interval) : '30';
return E('section', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '🛠️'),
_('Automation & Refresh')
])
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'class': 'sh-settings-grid' }, [
this.renderToggle('auto_refresh', _('Auto refresh'), _('Poll services & metrics every few seconds'), this.boolValue(general.auto_refresh, true), '♻️'),
this.renderToggle('health_check', _('Health monitor'), _('Run background probes to populate the Health tab'), this.boolValue(general.health_check, true), '🩺'),
this.renderToggle('debug_mode', _('Debug mode'), _('Surface extra logs and RPC payloads (development only)'), this.boolValue(general.debug_mode, false), '🐛')
]),
E('div', { 'class': 'sh-settings-grid sh-settings-grid--compact', 'style': 'margin-top: 20px;' }, [
this.renderSelect('refresh_interval', _('Refresh cadence'), [
{ value: '15', label: _('Every 15 seconds') },
{ value: '30', label: _('Every 30 seconds') },
{ value: '60', label: _('Every minute') },
{ value: '120', label: _('Every 2 minutes') },
{ value: '0', label: _('Manual refresh only') }
], refresh),
this.renderNumber('log_retention', _('Log retention (days)'), general.log_retention || 30, 1, 365)
])
])
]);
},
renderThresholdSection: function() {
var th = this.settings.thresholds || {};
return E('section', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '🚨'),
_('Alert thresholds')
]),
E('div', { 'class': 'sh-card-subtitle' }, _('Define warning/critical limits used by Health dashboards.'))
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'class': 'sh-threshold-grid' }, [
this.renderThresholdRow('cpu', _('CPU usage (%)'), th.cpu_warning || 80, th.cpu_critical || 95),
this.renderThresholdRow('mem', _('Memory usage (%)'), th.mem_warning || 80, th.mem_critical || 95),
this.renderThresholdRow('disk', _('Disk usage (%)'), th.disk_warning || 80, th.disk_critical || 95),
this.renderThresholdRow('temp', _('Temperature (°C)'), th.temp_warning || 70, th.temp_critical || 85)
])
])
]);
},
renderSupportSection: function() {
var support = this.settings.support || {};
var upload = this.settings.upload || {};
return E('section', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '🤝'),
_('Support & export')
])
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'class': 'sh-support-grid' }, [
E('div', { 'class': 'sh-support-card' }, [
E('div', { 'class': 'sh-support-label' }, _('Provider')),
E('strong', {}, support.provider || _('Unknown')),
E('div', { 'class': 'sh-support-desc' }, support.email || '')
]),
E('div', { 'class': 'sh-support-card' }, [
E('div', { 'class': 'sh-support-label' }, _('Documentation')),
E('a', { 'href': support.docs || '#', 'target': '_blank', 'rel': 'noreferrer' }, support.docs || _('Unavailable'))
]),
E('div', { 'class': 'sh-support-card' }, [
E('div', { 'class': 'sh-support-label' }, _('Auto upload')),
E('strong', {}, this.boolValue(upload.auto_upload, false) ? _('Enabled') : _('Disabled')),
E('div', { 'class': 'sh-support-desc' }, upload.url || _('No endpoint configured'))
])
])
])
]);
},
renderActions: function() {
return E('section', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '💾'),
_('Apply changes')
])
]),
E('div', { 'class': 'sh-card-body sh-btn-group' }, [
E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.saveSettings, this)
}, [ ' ', _('Save preferences') ]),
E('button', {
'class': 'sh-btn sh-btn-secondary',
'click': L.bind(this.resetView, this)
}, [ ' ', _('Reset') ])
])
]);
},
renderToggle: function(key, label, desc, active, icon) {
var self = this;
var switchEl = E('div', {
'class': 'sh-toggle-switch' + (active ? ' active' : ''),
'data-key': key,
'click': function(ev) {
ev.target.classList.toggle('active');
}
});
this.fieldRefs[key] = { type: 'toggle', node: switchEl };
return E('div', { 'class': 'sh-toggle' }, [
E('div', { 'class': 'sh-toggle-info' }, [
E('span', { 'class': 'sh-toggle-icon' }, icon || ''),
E('div', {}, [
E('div', { 'class': 'sh-toggle-label' }, label),
E('div', { 'class': 'sh-toggle-desc' }, desc)
])
]),
switchEl
]);
},
renderThemeSection: function() {
var available = Theme.availableThemes || [];
var preferred = this.readStoredTheme() || Theme.currentTheme || available[0] || 'dark';
var selectEl = E('select', {
'id': 'sh-theme-select',
'class': 'sh-select',
'change': L.bind(function(ev) {
this.previewTheme(ev.target.value);
}, this)
});
if (available.length === 0) {
available = ['dark'];
}
available.forEach(function(themeName) {
selectEl.appendChild(E('option', {
'value': themeName,
'selected': themeName === preferred
}, themeName));
});
this.themeSelect = selectEl;
return E('section', { 'class': 'sh-card' }, [
E('div', { 'class': 'sh-card-header' }, [
E('div', { 'class': 'sh-card-title' }, [
E('span', { 'class': 'sh-card-title-icon' }, '🎨'),
_('SecuBox Theme Preferences')
]),
E('div', { 'class': 'sh-card-subtitle' },
_('Pick a theme, store it locally, and ensure the UI respects your choice.'))
]),
E('div', { 'class': 'sh-card-body' }, [
E('div', { 'class': 'sh-settings-grid' }, [
E('div', {}, [
E('label', { 'for': 'sh-theme-select' }, _('Preferred theme')),
selectEl,
E('small', { 'class': 'sh-muted' },
_('Current applied theme: {theme}', { theme: Theme.currentTheme || 'dark' }))
]),
E('div', { 'class': 'sh-btn-group' }, [
E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.applyThemePreference, this)
}, [ '💾 ', _('Save to browser') ]),
E('button', {
'class': 'sh-btn sh-btn-secondary',
'click': L.bind(this.resetThemePreference, this)
}, [ '🗑 ', _('Reset preference') ])
])
]),
E('div', { 'class': 'sh-support-grid', 'style': 'margin-top: 18px;' }, [
E('div', { 'class': 'sh-support-card' }, [
E('div', { 'class': 'sh-support-label' }, _('localStorage value')),
E('code', {}, this.readStoredTheme() || _('(none)'))
]),
E('div', { 'class': 'sh-support-card' }, [
E('div', { 'class': 'sh-support-label' }, _('Available themes')),
E('span', {}, available.join(', '))
])
]),
E('div', { 'class': 'sh-support-card sh-theme-help' }, [
E('strong', {}, _('Troubleshooting (theme not persisting)')),
E('ol', { 'class': 'sh-theme-checklist' }, [
E('li', {}, _('Check localStorage: `localStorage.getItem(\'secubox.theme\')`')),
E('li', {}, _('Verify the theme exists in Theme.availableThemes')),
E('li', {}, _('Confirm the browser allows localStorage / site data'))
]),
E('p', { 'class': 'sh-muted' },
_('Use the buttons above to re-generate your preference if the UI resets.'))
])
])
]);
},
readStoredTheme: function() {
try {
return window.localStorage.getItem('secubox.theme');
} catch (err) {
return null;
}
},
previewTheme: function(themeName) {
if (Theme && Theme.apply) {
Theme.apply(themeName);
}
},
applyThemePreference: function() {
var theme = this.themeSelect ? this.themeSelect.value : Theme.currentTheme;
if (!theme)
return;
if (Theme && Theme.setPreferredTheme) {
Theme.setPreferredTheme(theme);
} else {
try {
window.localStorage.setItem('secubox.theme', theme);
} catch (err) {
console.warn('Unable to store theme preference:', err);
}
}
ui.addNotification(null,
E('p', {}, _('Theme preference saved to this browser.') + ' [' + theme + ']'),
'info');
},
resetThemePreference: function() {
try {
window.localStorage.removeItem('secubox.theme');
} catch (err) { /* ignore */ }
if (this.themeSelect) {
this.themeSelect.value = Theme.currentTheme || Theme.availableThemes[0] || 'dark';
}
ui.addNotification(null,
E('p', {}, _('Stored preference cleared. The default detection logic will run on next load.')),
'warning');
},
renderSelect: function(key, label, options, current) {
var select = E('select', {
'class': 'sh-input',
'change': function(ev) {
ev.target.setAttribute('data-value', ev.target.value);
}
}, options.map(function(opt) {
return E('option', {
'value': opt.value,
'selected': opt.value === current
}, opt.label);
}));
select.setAttribute('data-value', current);
this.fieldRefs[key] = { type: 'select', node: select };
return E('div', { 'class': 'sh-input-group' }, [
E('label', { 'class': 'sh-input-label' }, label),
select
]);
},
renderNumber: function(key, label, value, min, max) {
var input = E('input', {
'type': 'number',
'class': 'sh-input',
'value': value,
'min': min,
'max': max
});
this.fieldRefs[key] = { type: 'number', node: input };
return E('div', { 'class': 'sh-input-group' }, [
E('label', { 'class': 'sh-input-label' }, label),
input
]);
},
renderThresholdRow: function(prefix, label, warning, critical) {
var warnKey = prefix + '_warning';
var critKey = prefix + '_critical';
var warnInput = E('input', {
'type': 'number',
'class': 'sh-input',
'value': warning,
'min': 0,
'max': 200
});
var critInput = E('input', {
'type': 'number',
'class': 'sh-input',
'value': critical,
'min': 0,
'max': 200
});
this.fieldRefs[warnKey] = { type: 'number', node: warnInput };
this.fieldRefs[critKey] = { type: 'number', node: critInput };
return E('div', { 'class': 'sh-threshold-row' }, [
E('div', { 'class': 'sh-threshold-label' }, label),
E('div', { 'class': 'sh-threshold-inputs' }, [
E('label', {}, [
_('Warning'),
warnInput
]),
E('label', {}, [
_('Critical'),
critInput
])
])
]);
},
boolValue: function(value, fallback) {
if (value === 0 || value === '0')
return false;
if (value === 1 || value === '1')
return true;
return !!fallback;
},
collectPayload: function() {
var payload = {};
var self = this;
function readBool(key) {
var ref = self.fieldRefs[key];
return ref && ref.node.classList.contains('active') ? 1 : 0;
}
function readNumber(key) {
var ref = self.fieldRefs[key];
return ref ? parseInt(ref.node.value, 10) || 0 : 0;
}
function readSelect(key) {
var ref = self.fieldRefs[key];
return ref ? ref.node.getAttribute('data-value') || ref.node.value : '';
}
payload.auto_refresh = readBool('auto_refresh');
payload.health_check = readBool('health_check');
payload.debug_mode = readBool('debug_mode');
payload.refresh_interval = readSelect('refresh_interval');
payload.log_retention = readNumber('log_retention');
['cpu', 'mem', 'disk', 'temp'].forEach(function(prefix) {
payload[prefix + '_warning'] = readNumber(prefix + '_warning');
payload[prefix + '_critical'] = readNumber(prefix + '_critical');
});
return payload;
},
saveSettings: function(ev) {
ev && ev.preventDefault();
var payload = this.collectPayload();
ui.showModal(_('Saving preferences'), [
E('p', {}, _('Applying thresholds and refresh cadence')),
E('div', { 'class': 'spinning' })
]);
API.saveSettings(payload).then(L.bind(function(result) {
ui.hideModal();
if (result && result.success) {
ui.addNotification(null, E('p', {}, _('Preferences saved.')), 'info');
this.reloadView();
} else {
ui.addNotification(null, E('p', {}, (result && result.error) || _('Unable to save settings')), 'error');
}
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
resetView: function(ev) {
ev && ev.preventDefault();
this.reloadView();
},
reloadView: function() {
this.load().then(L.bind(function(data) {
var node = this.render(data);
var root = document.querySelector('.system-hub-dashboard');
if (root && root.parentNode) {
root.parentNode.replaceChild(node, root);
}
}, this));
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});