secubox-openwrt/package/secubox/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/schedules.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

501 lines
20 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require ui';
'require form';
'require dom';
'require poll';
'require secubox/kiss-theme';
var callGetSchedules = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'get_schedules',
expect: { schedules: [] }
});
var callCreateSchedule = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'create_schedule',
params: ['name', 'enabled', 'days', 'start_time', 'end_time', 'limit_down', 'limit_up', 'priority', 'target', 'target_type'],
expect: { success: false }
});
var callUpdateSchedule = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'update_schedule',
params: ['id', 'name', 'enabled', 'days', 'start_time', 'end_time', 'limit_down', 'limit_up', 'priority', 'target', 'target_type'],
expect: { success: false }
});
var callDeleteSchedule = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'delete_schedule',
params: ['id'],
expect: { success: false }
});
var callToggleSchedule = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'toggle_schedule',
params: ['id', 'enabled'],
expect: { success: false }
});
var callGetDevices = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'get_clients',
expect: { clients: [] }
});
var callGetGroups = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'get_groups',
expect: { groups: [] }
});
var DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
var DAY_VALUES = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
var CSS = '\
.schedules-container { background: linear-gradient(135deg, #0a0a0f 0%, #050508 100%); border-radius: 12px; padding: 24px; margin: 16px 0; }\
.schedules-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 2px solid #25252f; }\
.schedules-title { font-size: 24px; font-weight: 700; background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\
.schedules-list { display: grid; gap: 12px; }\
.schedule-card { background: #15151a; border: 1px solid #25252f; border-radius: 8px; padding: 16px; display: flex; align-items: center; gap: 16px; transition: all 0.2s; }\
.schedule-card:hover { border-color: #8b5cf6; }\
.schedule-card.disabled { opacity: 0.5; }\
.schedule-icon { font-size: 32px; width: 56px; height: 56px; display: flex; align-items: center; justify-content: center; background: #0a0a0f; border-radius: 8px; }\
.schedule-info { flex: 1; }\
.schedule-name { font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px; }\
.schedule-time { font-size: 14px; color: #06b6d4; font-family: monospace; margin-bottom: 4px; }\
.schedule-details { font-size: 12px; color: #666; }\
.schedule-days { display: flex; gap: 4px; margin-top: 8px; }\
.day-badge { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; background: #0a0a0f; color: #666; border: 1px solid #25252f; }\
.day-badge.active { background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); color: white; border-color: transparent; }\
.schedule-limits { text-align: right; min-width: 100px; }\
.limit-value { font-size: 14px; font-weight: 600; color: #10b981; }\
.limit-label { font-size: 10px; color: #666; text-transform: uppercase; }\
.schedule-actions { display: flex; gap: 8px; align-items: center; }\
.toggle-switch { position: relative; width: 48px; height: 24px; }\
.toggle-switch input { opacity: 0; width: 0; height: 0; }\
.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #25252f; border-radius: 24px; transition: 0.3s; }\
.toggle-slider:before { content: ""; position: absolute; height: 18px; width: 18px; left: 3px; bottom: 3px; background: #666; border-radius: 50%; transition: 0.3s; }\
.toggle-switch input:checked + .toggle-slider { background: #8b5cf6; }\
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(24px); background: white; }\
.btn { padding: 8px 16px; border-radius: 6px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s; display: inline-flex; align-items: center; gap: 6px; }\
.btn-primary { background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); color: white; }\
.btn-primary:hover { box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4); }\
.btn-secondary { background: #15151a; color: #ccc; border: 1px solid #25252f; }\
.btn-secondary:hover { background: #25252f; }\
.btn-danger { background: rgba(244, 63, 94, 0.2); color: #f43f5e; }\
.btn-danger:hover { background: rgba(244, 63, 94, 0.3); }\
.btn-icon { width: 32px; height: 32px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 14px; }\
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(4px); }\
.modal { background: #0a0a0f; border: 1px solid #25252f; border-radius: 12px; width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; }\
.modal-header { padding: 20px; border-bottom: 1px solid #25252f; display: flex; justify-content: space-between; align-items: center; }\
.modal-title { font-size: 18px; font-weight: 600; color: #fff; }\
.modal-close { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #15151a; color: #999; cursor: pointer; border: none; }\
.modal-body { padding: 20px; }\
.modal-footer { padding: 16px 20px; border-top: 1px solid #25252f; display: flex; justify-content: flex-end; gap: 12px; }\
.form-group { margin-bottom: 16px; }\
.form-label { display: block; font-size: 13px; font-weight: 600; color: #999; margin-bottom: 8px; text-transform: uppercase; }\
.form-input, .form-select { width: 100%; background: #15151a; border: 1px solid #25252f; border-radius: 6px; padding: 10px 14px; color: #fff; font-size: 14px; box-sizing: border-box; }\
.form-input:focus, .form-select:focus { border-color: #8b5cf6; outline: none; }\
.form-row { display: flex; gap: 16px; }\
.form-row .form-group { flex: 1; }\
.day-selector { display: flex; gap: 8px; flex-wrap: wrap; }\
.day-btn { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; background: #15151a; color: #666; border: 2px solid #25252f; cursor: pointer; transition: all 0.2s; }\
.day-btn:hover { border-color: #8b5cf6; }\
.day-btn.selected { background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); color: white; border-color: transparent; }\
.empty-state { text-align: center; padding: 48px; color: #666; }\
.empty-icon { font-size: 64px; margin-bottom: 16px; opacity: 0.5; }\
.quick-presets { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }\
.preset-btn { padding: 6px 12px; background: #15151a; border: 1px solid #25252f; border-radius: 16px; color: #999; font-size: 12px; cursor: pointer; transition: all 0.2s; }\
.preset-btn:hover { border-color: #8b5cf6; color: #fff; }\
';
return view.extend({
schedules: [],
devices: [],
groups: [],
load: function() {
return Promise.all([
callGetSchedules(),
callGetDevices(),
callGetGroups()
]);
},
render: function(data) {
var self = this;
this.schedules = data[0].schedules || [];
this.devices = data[1].clients || [];
this.groups = data[2].groups || [];
var container = E('div', { 'class': 'cbi-map' }, [
E('style', {}, CSS),
E('div', { 'class': 'schedules-container' }, [
E('div', { 'class': 'schedules-header' }, [
E('span', { 'class': 'schedules-title' }, 'Time-Based Schedules'),
E('button', {
'class': 'btn btn-primary',
'click': function() { self.showScheduleModal(); }
}, ['+ New Schedule'])
]),
E('div', { 'class': 'schedules-list', 'id': 'schedules-list' },
this.schedules.length > 0
? this.schedules.map(function(s) { return self.renderScheduleCard(s); })
: [this.renderEmptyState()]
)
])
]);
return KissTheme.wrap([container], 'admin/services/bandwidth-manager/schedules');
},
renderScheduleCard: function(schedule) {
var self = this;
var days = schedule.days || [];
var isEnabled = schedule.enabled === '1' || schedule.enabled === true;
return E('div', {
'class': 'schedule-card' + (isEnabled ? '' : ' disabled'),
'data-id': schedule['.name'] || schedule.id
}, [
E('div', { 'class': 'schedule-icon' }, this.getScheduleIcon(schedule)),
E('div', { 'class': 'schedule-info' }, [
E('div', { 'class': 'schedule-name' }, schedule.name || 'Unnamed Schedule'),
E('div', { 'class': 'schedule-time' }, (schedule.start_time || '00:00') + ' - ' + (schedule.end_time || '23:59')),
E('div', { 'class': 'schedule-details' },
schedule.target_type === 'group' ? 'Group: ' + (schedule.target || 'All') :
schedule.target_type === 'device' ? 'Device: ' + (schedule.target || 'All') : 'All devices'
),
E('div', { 'class': 'schedule-days' },
DAY_VALUES.map(function(d, i) {
return E('span', {
'class': 'day-badge' + (days.indexOf(d) >= 0 ? ' active' : '')
}, DAYS[i].charAt(0));
})
)
]),
E('div', { 'class': 'schedule-limits' }, [
E('div', { 'class': 'limit-value' }, this.formatSpeed(schedule.limit_down || 0)),
E('div', { 'class': 'limit-label' }, 'Download'),
E('div', { 'class': 'limit-value', 'style': 'margin-top:8px;color:#06b6d4' }, this.formatSpeed(schedule.limit_up || 0)),
E('div', { 'class': 'limit-label' }, 'Upload')
]),
E('div', { 'class': 'schedule-actions' }, [
E('label', { 'class': 'toggle-switch' }, [
E('input', {
'type': 'checkbox',
'checked': isEnabled,
'change': function(ev) {
self.toggleSchedule(schedule['.name'] || schedule.id, ev.target.checked);
}
}),
E('span', { 'class': 'toggle-slider' })
]),
E('button', {
'class': 'btn btn-icon btn-secondary',
'title': 'Edit',
'click': function() { self.showScheduleModal(schedule); }
}, '\u270E'),
E('button', {
'class': 'btn btn-icon btn-danger',
'title': 'Delete',
'click': function() { self.deleteSchedule(schedule['.name'] || schedule.id); }
}, '\u2715')
])
]);
},
renderEmptyState: function() {
return E('div', { 'class': 'empty-state' }, [
E('div', { 'class': 'empty-icon' }, '\u23F0'),
E('div', { 'style': 'font-size:16px;margin-bottom:8px;color:#999' }, 'No schedules configured'),
E('div', { 'style': 'font-size:13px' }, 'Create a schedule to apply bandwidth limits at specific times')
]);
},
getScheduleIcon: function(schedule) {
var startHour = parseInt((schedule.start_time || '00:00').split(':')[0]);
if (startHour >= 6 && startHour < 12) return '\u2600\uFE0F'; // Morning
if (startHour >= 12 && startHour < 18) return '\u26C5'; // Afternoon
if (startHour >= 18 && startHour < 22) return '\uD83C\uDF19'; // Evening
return '\uD83C\uDF19'; // Night
},
formatSpeed: function(kbps) {
if (!kbps || kbps === 0) return 'Unlimited';
if (kbps >= 1000) return (kbps / 1000).toFixed(1) + ' Mbps';
return kbps + ' Kbps';
},
showScheduleModal: function(schedule) {
var self = this;
var isEdit = !!schedule;
var selectedDays = (schedule && schedule.days) ? schedule.days.slice() : [];
var overlay = E('div', { 'class': 'modal-overlay' });
var modal = E('div', { 'class': 'modal' }, [
E('div', { 'class': 'modal-header' }, [
E('span', { 'class': 'modal-title' }, isEdit ? 'Edit Schedule' : 'New Schedule'),
E('button', { 'class': 'modal-close', 'click': function() { overlay.remove(); } }, '\u2715')
]),
E('div', { 'class': 'modal-body' }, [
E('div', { 'class': 'quick-presets' }, [
E('button', { 'class': 'preset-btn', 'click': function() {
document.getElementById('sched-start').value = '22:00';
document.getElementById('sched-end').value = '06:00';
document.getElementById('sched-name').value = 'Night Hours';
}}, 'Night (22:00-06:00)'),
E('button', { 'class': 'preset-btn', 'click': function() {
document.getElementById('sched-start').value = '18:00';
document.getElementById('sched-end').value = '23:00';
document.getElementById('sched-name').value = 'Peak Hours';
}}, 'Peak (18:00-23:00)'),
E('button', { 'class': 'preset-btn', 'click': function() {
document.getElementById('sched-start').value = '08:00';
document.getElementById('sched-end').value = '16:00';
document.getElementById('sched-name').value = 'Work Hours';
}}, 'Work (08:00-16:00)')
]),
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Schedule Name'),
E('input', {
'type': 'text',
'class': 'form-input',
'id': 'sched-name',
'value': schedule ? schedule.name : '',
'placeholder': 'e.g., Evening Limit'
})
]),
E('div', { 'class': 'form-row' }, [
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Start Time'),
E('input', {
'type': 'time',
'class': 'form-input',
'id': 'sched-start',
'value': schedule ? schedule.start_time : '00:00'
})
]),
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'End Time'),
E('input', {
'type': 'time',
'class': 'form-input',
'id': 'sched-end',
'value': schedule ? schedule.end_time : '23:59'
})
])
]),
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Active Days'),
E('div', { 'class': 'day-selector', 'id': 'day-selector' },
DAY_VALUES.map(function(d, i) {
return E('button', {
'type': 'button',
'class': 'day-btn' + (selectedDays.indexOf(d) >= 0 ? ' selected' : ''),
'data-day': d,
'click': function(ev) {
ev.target.classList.toggle('selected');
}
}, DAYS[i]);
})
)
]),
E('div', { 'class': 'form-row' }, [
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Download Limit (Kbps)'),
E('input', {
'type': 'number',
'class': 'form-input',
'id': 'sched-down',
'value': schedule ? schedule.limit_down : '',
'placeholder': '0 = Unlimited'
})
]),
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Upload Limit (Kbps)'),
E('input', {
'type': 'number',
'class': 'form-input',
'id': 'sched-up',
'value': schedule ? schedule.limit_up : '',
'placeholder': '0 = Unlimited'
})
])
]),
E('div', { 'class': 'form-row' }, [
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Apply To'),
E('select', {
'class': 'form-select',
'id': 'sched-target-type',
'change': function(ev) {
var targetSelect = document.getElementById('sched-target');
targetSelect.innerHTML = '';
if (ev.target.value === 'all') {
targetSelect.style.display = 'none';
} else if (ev.target.value === 'device') {
targetSelect.style.display = '';
self.devices.forEach(function(d) {
targetSelect.appendChild(E('option', { 'value': d.mac }, d.hostname || d.mac));
});
} else if (ev.target.value === 'group') {
targetSelect.style.display = '';
self.groups.forEach(function(g) {
targetSelect.appendChild(E('option', { 'value': g['.name'] || g.name }, g.name));
});
}
}
}, [
E('option', { 'value': 'all', 'selected': !schedule || !schedule.target_type || schedule.target_type === 'all' }, 'All Devices'),
E('option', { 'value': 'device', 'selected': schedule && schedule.target_type === 'device' }, 'Specific Device'),
E('option', { 'value': 'group', 'selected': schedule && schedule.target_type === 'group' }, 'Device Group')
])
]),
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Target'),
E('select', {
'class': 'form-select',
'id': 'sched-target',
'style': (!schedule || !schedule.target_type || schedule.target_type === 'all') ? 'display:none' : ''
})
])
]),
E('div', { 'class': 'form-group' }, [
E('label', { 'class': 'form-label' }, 'Priority'),
E('select', { 'class': 'form-select', 'id': 'sched-priority' }, [
E('option', { 'value': '1', 'selected': schedule && schedule.priority === '1' }, 'High'),
E('option', { 'value': '2', 'selected': !schedule || schedule.priority === '2' || !schedule.priority }, 'Normal'),
E('option', { 'value': '3', 'selected': schedule && schedule.priority === '3' }, 'Low'),
E('option', { 'value': '4', 'selected': schedule && schedule.priority === '4' }, 'Bulk')
])
])
]),
E('div', { 'class': 'modal-footer' }, [
E('button', { 'class': 'btn btn-secondary', 'click': function() { overlay.remove(); } }, 'Cancel'),
E('button', { 'class': 'btn btn-primary', 'click': function() { self.saveSchedule(schedule, overlay); } },
isEdit ? 'Update' : 'Create')
])
]);
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Trigger target type change to populate target dropdown
if (schedule && schedule.target_type && schedule.target_type !== 'all') {
var event = new Event('change');
document.getElementById('sched-target-type').dispatchEvent(event);
if (schedule.target) {
setTimeout(function() {
document.getElementById('sched-target').value = schedule.target;
}, 50);
}
}
},
saveSchedule: function(existingSchedule, overlay) {
var self = this;
var name = document.getElementById('sched-name').value;
var startTime = document.getElementById('sched-start').value;
var endTime = document.getElementById('sched-end').value;
var limitDown = document.getElementById('sched-down').value || '0';
var limitUp = document.getElementById('sched-up').value || '0';
var targetType = document.getElementById('sched-target-type').value;
var target = targetType !== 'all' ? document.getElementById('sched-target').value : '';
var priority = document.getElementById('sched-priority').value;
var days = [];
document.querySelectorAll('#day-selector .day-btn.selected').forEach(function(btn) {
days.push(btn.dataset.day);
});
if (!name) {
ui.addNotification(null, E('p', 'Please enter a schedule name'), 'warning');
return;
}
if (days.length === 0) {
ui.addNotification(null, E('p', 'Please select at least one day'), 'warning');
return;
}
var promise;
if (existingSchedule) {
promise = callUpdateSchedule(
existingSchedule['.name'] || existingSchedule.id,
name, '1', days, startTime, endTime,
limitDown, limitUp, priority, target, targetType
);
} else {
promise = callCreateSchedule(
name, '1', days, startTime, endTime,
limitDown, limitUp, priority, target, targetType
);
}
promise.then(function(res) {
if (res.success) {
overlay.remove();
self.refreshSchedules();
ui.addNotification(null, E('p', existingSchedule ? 'Schedule updated' : 'Schedule created'), 'success');
} else {
ui.addNotification(null, E('p', res.error || 'Failed to save schedule'), 'error');
}
}).catch(function(err) {
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
});
},
toggleSchedule: function(id, enabled) {
var self = this;
callToggleSchedule(id, enabled ? '1' : '0').then(function(res) {
if (res.success) {
self.refreshSchedules();
} else {
ui.addNotification(null, E('p', 'Failed to toggle schedule'), 'error');
}
});
},
deleteSchedule: function(id) {
var self = this;
if (!confirm('Delete this schedule?')) return;
callDeleteSchedule(id).then(function(res) {
if (res.success) {
self.refreshSchedules();
ui.addNotification(null, E('p', 'Schedule deleted'), 'success');
} else {
ui.addNotification(null, E('p', 'Failed to delete schedule'), 'error');
}
});
},
refreshSchedules: function() {
var self = this;
callGetSchedules().then(function(data) {
self.schedules = data.schedules || [];
var list = document.getElementById('schedules-list');
if (list) {
list.innerHTML = '';
if (self.schedules.length > 0) {
self.schedules.forEach(function(s) {
list.appendChild(self.renderScheduleCard(s));
});
} else {
list.appendChild(self.renderEmptyState());
}
}
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});