feat: Media-flow UI + security updates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-12 07:57:39 +01:00
parent 2ef130583e
commit a677f8cf49
13 changed files with 245 additions and 630 deletions

File diff suppressed because one or more lines are too long

View File

@ -617,6 +617,40 @@ return view.extend({
}, _('Apply Interface Settings'))
])
])
// Firewall Bouncer quick control
E('div', { 'style': 'margin-top: 1em; padding: 1em; background: #fff; border-radius: 6px; border: 1px solid #e6e6e6;' }, [
E('h4', { 'style': 'margin-bottom: 0.5em; color: var(--cyber-text-secondary, #888);' }, _('Firewall Bouncer')),
E('p', { 'style': 'color: #666; margin-bottom: 0.5em;' }, _('Enable or disable the CrowdSec firewall bouncer (requires nftables and the firewall-bouncer collection).')),
E('div', { 'id': 'bouncer-control', 'style': 'display: flex; gap: 0.5em; align-items: center;' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function(ev) {
ev.target.disabled = true;
ev.target.classList.add('spinning');
API.getFirewallBouncerStatus().then(function(res) {
var enabled = res && res.enabled;
var action = enabled ? 'disable' : 'enable';
API.controlFirewallBouncer(action).then(function(result) {
ev.target.disabled = false;
ev.target.classList.remove('spinning');
if (result && result.success) {
ui.addNotification(null, E('p', {}, enabled ? _('Firewall bouncer disabled') : _('Firewall bouncer enabled')), 'info');
window.setTimeout(function() { location.reload(); }, 1200);
} else {
ui.addNotification(null, E('p', {}, result.error || _('Failed to change bouncer state')), 'error');
}
});
}).catch(function(err) {
ev.target.disabled = false;
ev.target.classList.remove('spinning');
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
}
}, _('Toggle Firewall Bouncer')),
E('div', { 'id': 'bouncer-status', 'style': 'color: #666; font-size: 0.95em;' }, _('Checking status...'))
])
]),
]),
// Configuration Files

View File

@ -4,7 +4,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-media-flow
PKG_VERSION:=0.6.2
PKG_VERSION:=0.6.3
PKG_RELEASE:=1
PKG_ARCH:=all
PKG_LICENSE:=Apache-2.0

View File

@ -0,0 +1,49 @@
'use strict';
'require baseclass';
var tabs = [
{ id: 'dashboard', icon: '📊', label: _('Dashboard'), path: ['admin', 'secubox', 'monitoring', 'mediaflow', 'dashboard'] },
{ id: 'alerts', icon: '🔔', label: _('Alerts'), path: ['admin', 'secubox', 'monitoring', 'mediaflow', 'alerts'] },
{ id: 'clients', icon: '👥', label: _('Clients'), path: ['admin', 'secubox', 'monitoring', 'mediaflow', 'clients'] },
{ id: 'services', icon: '🎬', label: _('Services'), path: ['admin', 'secubox', 'monitoring', 'mediaflow', 'services'] },
{ id: 'history', icon: '📜', label: _('History'), path: ['admin', 'secubox', 'monitoring', 'mediaflow', 'history'] }
];
return baseclass.extend({
getTabs: function() {
return tabs.slice();
},
ensureLuCITabsHidden: function() {
if (typeof document === 'undefined')
return;
if (document.getElementById('media-flow-tabstyle'))
return;
var style = document.createElement('style');
style.id = 'media-flow-tabstyle';
style.textContent = `
body[data-page^="admin-secubox-monitoring-mediaflow"] .tabs,
body[data-page^="admin-secubox-monitoring-mediaflow"] #tabmenu,
body[data-page^="admin-secubox-monitoring-mediaflow"] .cbi-tabmenu,
body[data-page^="admin-secubox-monitoring-mediaflow"] .nav-tabs {
display: none !important;
}
`;
document.head && document.head.appendChild(style);
},
renderTabs: function(active) {
this.ensureLuCITabsHidden();
return E('div', { 'class': 'sh-nav-tabs media-flow-nav-tabs' },
this.getTabs().map(function(tab) {
return E('a', {
'class': 'sh-nav-tab' + (tab.id === active ? ' active' : ''),
'href': L.url.apply(L, tab.path)
}, [
E('span', { 'class': 'sh-tab-icon' }, tab.icon),
E('span', { 'class': 'sh-tab-label' }, tab.label)
]);
})
);
}
});

View File

@ -3,33 +3,9 @@
'require form';
'require ui';
'require media-flow/api as API';
'require media-flow/nav as NavHelper';
'require secubox-portal/header as SbHeader';
var MEDIAFLOW_NAV = [
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
{ id: 'clients', icon: '👥', label: 'Clients' },
{ id: 'services', icon: '🎬', label: 'Services' },
{ id: 'history', icon: '📜', label: 'History' },
{ id: 'alerts', icon: '🔔', label: 'Alerts' }
];
function renderMediaFlowNav(activeId) {
return E('div', {
'class': 'sb-app-nav',
'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;flex-wrap:wrap;'
}, MEDIAFLOW_NAV.map(function(item) {
var isActive = activeId === item.id;
return E('a', {
'href': L.url('admin', 'secubox', 'mediaflow', item.id),
'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' +
(isActive ? 'background:linear-gradient(135deg,#ec4899,#8b5cf6);color:white;' : 'color:#a0a0b0;background:transparent;')
}, [
E('span', {}, item.icon),
E('span', {}, _(item.label))
]);
}));
}
return L.view.extend({
load: function() {
return Promise.all([
@ -53,18 +29,19 @@ return L.view.extend({
o.placeholder = 'Netflix';
o.rmempty = false;
o = s.option(form.Value, 'threshold_hours', _('Threshold (hours)'));
o = s.option(form.Value, 'threshold_hours', _("Daily Usage Limit (hours)"));
o.datatype = 'uinteger';
o.placeholder = '2';
o.placeholder = '4';
o.rmempty = false;
o.description = _('Trigger alert if usage exceeds this many hours per day');
o.description = _('Alert when daily usage exceeds this threshold (e.g., 2-4 hours)');
o = s.option(form.ListValue, 'action', _('Action'));
o.value('notify', _('Notification only'));
o.value('limit', _('Limit bandwidth'));
o.value('block', _('Block service'));
o = s.option(form.ListValue, 'action', _("Alert Action"));
o.value('notify', _('📬 Notify Only'));
o.value('limit', _('🐌 Limit Bandwidth'));
o.value('block', _('🚫 Block Service'));
o.default = 'notify';
o.rmempty = false;
o.description = _('Choose what happens when threshold is exceeded');
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.default = o.enabled;
@ -75,10 +52,20 @@ return L.view.extend({
.mf-header { margin-bottom: 24px; }
.mf-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 8px; display: flex; align-items: center; gap: 12px; }
.mf-subtitle { color: #a1a1aa; font-size: 0.875rem; }
.cbi-section { margin: 0 !important; background: rgba(255,255,255,0.02) !important; border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 12px !important; padding: 20px !important; }
.cbi-section-create { margin-top: 12px !important; }
.cbi-option-compact { padding: 12px 0 !important; border-bottom: 1px solid rgba(255,255,255,0.04) !important; }
.cbi-option-compact:last-child { border-bottom: none !important; }
.form-control { background: rgba(255,255,255,0.05) !important; border-color: rgba(255,255,255,0.1) !important; color: #e4e4e7 !important; border-radius: 6px !important; padding: 8px 12px !important; }
.form-control:focus { border-color: #ec4899 !important; background: rgba(236,72,153,0.08) !important; box-shadow: 0 0 0 3px rgba(236,72,153,0.1) !important; }
.cbi-button { border-radius: 8px !important; font-weight: 500 !important; transition: all 0.2s !important; }
.cbi-button-add { background: linear-gradient(135deg, #ec4899, #8b5cf6) !important; color: white !important; border: none !important; }
.cbi-button-add:hover { opacity: 0.9 !important; transform: translateY(-2px) !important; }
`;
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('alerts'),
E('link', { 'rel': 'stylesheet', 'href': L.resource('media-flow/common.css') }),
NavHelper.renderTabs('alerts'),
E('div', { 'class': 'mf-header' }, [
E('div', { 'class': 'mf-title' }, ['🔔 ', _('Streaming Alerts')]),
E('div', { 'class': 'mf-subtitle' }, _('Configure alerts based on streaming service usage'))

View File

@ -2,33 +2,9 @@
'require view';
'require ui';
'require media-flow/api as API';
'require media-flow/nav as NavHelper';
'require secubox-portal/header as SbHeader';
var MEDIAFLOW_NAV = [
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
{ id: 'clients', icon: '👥', label: 'Clients' },
{ id: 'services', icon: '🎬', label: 'Services' },
{ id: 'history', icon: '📜', label: 'History' },
{ id: 'alerts', icon: '🔔', label: 'Alerts' }
];
function renderMediaFlowNav(activeId) {
return E('div', {
'class': 'sb-app-nav',
'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;flex-wrap:wrap;'
}, MEDIAFLOW_NAV.map(function(item) {
var isActive = activeId === item.id;
return E('a', {
'href': L.url('admin', 'secubox', 'mediaflow', item.id),
'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' +
(isActive ? 'background:linear-gradient(135deg,#ec4899,#8b5cf6);color:white;' : 'color:#a0a0b0;background:transparent;')
}, [
E('span', {}, item.icon),
E('span', {}, _(item.label))
]);
}));
}
return L.view.extend({
load: function() {
return Promise.all([
@ -47,16 +23,27 @@ return L.view.extend({
.mf-subtitle { color: #a1a1aa; font-size: 0.875rem; }
.mf-card { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; overflow: hidden; }
.mf-table { width: 100%; border-collapse: collapse; }
.mf-table th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; color: #71717a; border-bottom: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255,255,255,0.02); }
.mf-table th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; color: #71717a; border-bottom: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255,255,255,0.02); font-weight: 600; }
.mf-table td { padding: 12px 16px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
.mf-table tr:hover td { background: rgba(255, 255, 255, 0.03); }
.mf-empty { text-align: center; padding: 48px 20px; color: #71717a; }
.mf-empty-icon { font-size: 3rem; margin-bottom: 12px; opacity: 0.5; }
@media (max-width: 768px) {
.mf-card { overflow-x: auto; }
.mf-table { font-size: 0.9rem; }
.mf-table th, .mf-table td { padding: 10px 8px; }
}
@media (max-width: 480px) {
.mf-title { font-size: 1.25rem; }
.mf-table { font-size: 0.8rem; }
.mf-table th, .mf-table td { padding: 8px 4px; }
}
`;
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('clients'),
E('link', { 'rel': 'stylesheet', 'href': L.resource('media-flow/common.css') }),
NavHelper.renderTabs('clients'),
E('div', { 'class': 'mf-header' }, [
E('div', { 'class': 'mf-title' }, ['👥 ', _('Clients Statistics')]),
E('div', { 'class': 'mf-subtitle' }, _('Streaming activity per client'))

View File

@ -3,6 +3,7 @@
'require poll';
'require ui';
'require media-flow/api as API';
'require media-flow/nav as NavHelper';
'require secubox-portal/header as SbHeader';
return view.extend({
@ -37,10 +38,6 @@ return view.extend({
var streams = streamsData.streams || [];
var flowCount = streamsData.flow_count || status.active_flows || 0;
// Main wrapper with SecuBox header
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
// Inject CSS
var css = `
.mf-dashboard { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #e4e4e7; }
@ -54,7 +51,9 @@ return view.extend({
.mf-status-badge.stopped { background: rgba(239, 68, 68, 0.2); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.3); }
.mf-status-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.mf-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
.mf-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 24px; }
@media (max-width: 768px) { .mf-stats-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .mf-stats-grid { grid-template-columns: 1fr; } }
.mf-stat-card { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; padding: 20px; text-align: center; transition: all 0.2s; }
.mf-stat-card:hover { background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.12); }
.mf-stat-icon { font-size: 1.5rem; margin-bottom: 8px; }
@ -84,13 +83,20 @@ return view.extend({
.mf-empty-icon { font-size: 3rem; margin-bottom: 12px; opacity: 0.5; }
.mf-empty-text { font-size: 1rem; }
.mf-streams-table { width: 100%; border-collapse: collapse; }
.mf-streams-table { width: 100%; border-collapse: collapse; overflow-x: auto; }
@media (max-width: 768px) { .mf-streams-table { font-size: 0.85rem; } .mf-streams-table th, .mf-streams-table td { padding: 10px 8px; } }
@media (max-width: 480px) { .mf-card-body { padding: 12px; overflow-x: auto; } .mf-streams-table { font-size: 0.75rem; } .mf-streams-table th, .mf-streams-table td { padding: 8px 4px; } }
.mf-streams-table th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; color: #71717a; border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
.mf-streams-table td { padding: 12px 16px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
.mf-streams-table tr:hover td { background: rgba(255, 255, 255, 0.03); }
.mf-quality-badge { padding: 4px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 600; color: white; }
.mf-btn { padding: 10px 20px; border-radius: 8px; font-size: 0.875rem; font-weight: 500; cursor: pointer; border: none; transition: all 0.2s; }
.mf-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.mf-btn.spinning::after { content: ''; animation: spin 1s linear infinite; display: inline-block; margin-left: 8px; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.mf-loading { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
.mf-btn-primary { background: linear-gradient(135deg, #ec4899, #8b5cf6); color: white; }
.mf-btn-primary:hover { opacity: 0.9; transform: translateY(-1px); }
.mf-btn-secondary { background: rgba(255, 255, 255, 0.1); color: #e4e4e7; border: 1px solid rgba(255, 255, 255, 0.2); }
@ -101,6 +107,8 @@ return view.extend({
var view = E('div', { 'class': 'mf-dashboard' }, [
E('style', {}, css),
E('link', { 'rel': 'stylesheet', 'href': L.resource('media-flow/common.css') }),
NavHelper.renderTabs('dashboard'),
// Header
E('div', { 'class': 'mf-header' }, [
@ -246,6 +254,8 @@ return view.extend({
}, this));
}, this), this.pollInterval);
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
wrapper.appendChild(view);
return wrapper;
},

View File

@ -2,33 +2,9 @@
'require view';
'require ui';
'require media-flow/api as API';
'require media-flow/nav as NavHelper';
'require secubox-portal/header as SbHeader';
var MEDIAFLOW_NAV = [
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
{ id: 'clients', icon: '👥', label: 'Clients' },
{ id: 'services', icon: '🎬', label: 'Services' },
{ id: 'history', icon: '📜', label: 'History' },
{ id: 'alerts', icon: '🔔', label: 'Alerts' }
];
function renderMediaFlowNav(activeId) {
return E('div', {
'class': 'sb-app-nav',
'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;flex-wrap:wrap;'
}, MEDIAFLOW_NAV.map(function(item) {
var isActive = activeId === item.id;
return E('a', {
'href': L.url('admin', 'secubox', 'mediaflow', item.id),
'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' +
(isActive ? 'background:linear-gradient(135deg,#ec4899,#8b5cf6);color:white;' : 'color:#a0a0b0;background:transparent;')
}, [
E('span', {}, item.icon),
E('span', {}, _(item.label))
]);
}));
}
return L.view.extend({
load: function() {
return Promise.all([
@ -47,22 +23,37 @@ return L.view.extend({
.mf-subtitle { color: #a1a1aa; font-size: 0.875rem; }
.mf-card { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; overflow: hidden; }
.mf-controls { display: flex; gap: 10px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
.mf-select { padding: 8px 12px; border-radius: 6px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); color: #e4e4e7; }
.mf-select { padding: 8px 12px; border-radius: 6px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); color: #e4e4e7; cursor: pointer; transition: all 0.2s; }
.mf-select:focus { border-color: #ec4899; background: rgba(236,72,153,0.08); }
.mf-btn { padding: 8px 16px; border-radius: 6px; font-size: 0.8rem; cursor: pointer; border: none; background: rgba(255,255,255,0.1); color: #e4e4e7; transition: all 0.2s; }
.mf-btn:hover { background: rgba(255,255,255,0.15); }
.mf-btn:hover { background: rgba(255,255,255,0.15); transform: translateY(-1px); }
.mf-btn-danger { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
.mf-btn-danger:hover { background: rgba(239, 68, 68, 0.3); }
.mf-table { width: 100%; border-collapse: collapse; }
.mf-table th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; color: #71717a; border-bottom: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255,255,255,0.02); }
.mf-table th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; color: #71717a; border-bottom: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255,255,255,0.02); font-weight: 600; }
.mf-table td { padding: 12px 16px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
.mf-table tr:hover td { background: rgba(255, 255, 255, 0.03); }
.mf-empty { text-align: center; padding: 48px 20px; color: #71717a; }
.mf-quality { padding: 4px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 600; color: white; }
@media (max-width: 768px) {
.mf-controls { flex-direction: column; align-items: stretch; }
.mf-select, .mf-btn { width: 100%; }
.mf-card { overflow-x: auto; }
.mf-table { font-size: 0.9rem; }
.mf-table th, .mf-table td { padding: 10px 8px; }
}
@media (max-width: 480px) {
.mf-title { font-size: 1.25rem; }
.mf-table { font-size: 0.8rem; }
.mf-table th, .mf-table td { padding: 8px 4px; }
.mf-btn { padding: 6px 12px; font-size: 0.75rem; }
}
`;
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('history'),
E('link', { 'rel': 'stylesheet', 'href': L.resource('media-flow/common.css') }),
NavHelper.renderTabs('history'),
E('div', { 'class': 'mf-header' }, [
E('div', { 'class': 'mf-title' }, ['📜 ', _('Stream History')]),
E('div', { 'class': 'mf-subtitle' }, _('Historical record of detected streaming sessions'))

View File

@ -2,33 +2,9 @@
'require view';
'require ui';
'require media-flow/api as API';
'require media-flow/nav as NavHelper';
'require secubox-portal/header as SbHeader';
var MEDIAFLOW_NAV = [
{ id: 'dashboard', icon: '📊', label: 'Dashboard' },
{ id: 'clients', icon: '👥', label: 'Clients' },
{ id: 'services', icon: '🎬', label: 'Services' },
{ id: 'history', icon: '📜', label: 'History' },
{ id: 'alerts', icon: '🔔', label: 'Alerts' }
];
function renderMediaFlowNav(activeId) {
return E('div', {
'class': 'sb-app-nav',
'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;flex-wrap:wrap;'
}, MEDIAFLOW_NAV.map(function(item) {
var isActive = activeId === item.id;
return E('a', {
'href': L.url('admin', 'secubox', 'mediaflow', item.id),
'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' +
(isActive ? 'background:linear-gradient(135deg,#ec4899,#8b5cf6);color:white;' : 'color:#a0a0b0;background:transparent;')
}, [
E('span', {}, item.icon),
E('span', {}, _(item.label))
]);
}));
}
return L.view.extend({
load: function() {
return Promise.all([
@ -47,18 +23,30 @@ return L.view.extend({
.mf-subtitle { color: #a1a1aa; font-size: 0.875rem; }
.mf-card { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; overflow: hidden; }
.mf-table { width: 100%; border-collapse: collapse; }
.mf-table th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; color: #71717a; border-bottom: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255,255,255,0.02); }
.mf-table th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; color: #71717a; border-bottom: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255,255,255,0.02); font-weight: 600; }
.mf-table td { padding: 12px 16px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
.mf-table tr:hover td { background: rgba(255, 255, 255, 0.03); }
.mf-empty { text-align: center; padding: 48px 20px; color: #71717a; }
.mf-empty-icon { font-size: 3rem; margin-bottom: 12px; opacity: 0.5; }
.mf-btn { padding: 8px 16px; border-radius: 6px; font-size: 0.8rem; cursor: pointer; border: none; background: rgba(255,255,255,0.1); color: #e4e4e7; transition: all 0.2s; }
.mf-btn:hover { background: rgba(255,255,255,0.15); }
.mf-btn:hover { background: rgba(255,255,255,0.15); transform: translateY(-1px); }
@media (max-width: 768px) {
.mf-card { overflow-x: auto; }
.mf-table { font-size: 0.9rem; }
.mf-table th, .mf-table td { padding: 10px 8px; }
}
@media (max-width: 480px) {
.mf-title { font-size: 1.25rem; }
.mf-table { font-size: 0.8rem; }
.mf-table th, .mf-table td { padding: 8px 4px; }
.mf-btn { padding: 6px 12px; font-size: 0.75rem; }
}
`;
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('services'),
E('link', { 'rel': 'stylesheet', 'href': L.resource('media-flow/common.css') }),
NavHelper.renderTabs('services'),
E('div', { 'class': 'mf-header' }, [
E('div', { 'class': 'mf-title' }, ['🎬 ', _('Services Statistics')]),
E('div', { 'class': 'mf-subtitle' }, _('Detailed statistics per streaming service'))

View File

@ -1,6 +1,6 @@
{
"feed_url": "/secubox-feed",
"generated": "2026-01-11T10:24:11+01:00",
"generated": "2026-01-11T16:58:17+01:00",
"packages": [
{
"name": "luci-app-auth-guardian",

View File

@ -5,23 +5,18 @@
#
# secubox-app-mitmproxy - mitmproxy integration for SecuBox
# Provides init scripts, UCI configuration, and control utilities
# mitmproxy is installed via pip at runtime (with pre-built wheels)
# mitmproxy binary must be installed separately via pip
#
include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-app-mitmproxy
PKG_VERSION:=2.1.0
PKG_VERSION:=2.2.0
PKG_RELEASE:=1
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
PKG_LICENSE:=MIT
# mitmproxy version to install
MITMPROXY_VERSION:=8.1.1
# zstandard version with musllinux aarch64 wheels
ZSTANDARD_VERSION:=0.23.0
include $(INCLUDE_DIR)/package.mk
define Package/secubox-app-mitmproxy
@ -30,16 +25,16 @@ define Package/secubox-app-mitmproxy
SUBMENU:=SecuBox Apps
TITLE:=mitmproxy - Interactive HTTPS Proxy (SecuBox Integration)
URL:=https://mitmproxy.org/
DEPENDS:=+python3 +python3-pip +jq +openssl-util
DEPENDS:=+python3 +jq +openssl-util
PKGARCH:=all
endef
define Package/secubox-app-mitmproxy/description
SecuBox integration package for mitmproxy $(MITMPROXY_VERSION).
SecuBox integration package for mitmproxy.
Provides init scripts, UCI configuration, and control utilities.
mitmproxy is installed via pip during post-install with pre-built
musllinux wheels for optimal compatibility.
NOTE: mitmproxy binary must be installed separately via pip:
pip3 install mitmproxy
Features:
- Intercept and modify HTTP/HTTPS traffic
@ -85,25 +80,9 @@ define Package/secubox-app-mitmproxy/postinst
# Create runtime directories
mkdir -p /var/lib/mitmproxy /tmp/mitmproxy /etc/mitmproxy
# Install mitmproxy via pip if not present
if ! python3 -c "import mitmproxy" 2>/dev/null; then
echo "Installing mitmproxy dependencies..."
# IMPORTANT: Install zstandard first with musllinux wheel
# Older versions don't have musllinux wheels and fail to compile
pip3 install --no-cache-dir zstandard==0.23.0 || {
echo "Warning: zstandard installation failed"
}
echo "Installing mitmproxy 8.1.1..."
pip3 install --no-cache-dir mitmproxy==8.1.1 || {
echo "Error: mitmproxy installation failed"
echo "Try manually: pip3 install zstandard==0.23.0 mitmproxy==8.1.1"
exit 1
}
else
echo "mitmproxy already installed"
fi
# Check if mitmproxy is installed
if python3 -c "import mitmproxy" 2>/dev/null; then
echo "mitmproxy detected"
# Generate CA certificate if needed
if [ ! -f /etc/mitmproxy/mitmproxy-ca.pem ]; then
@ -114,7 +93,12 @@ define Package/secubox-app-mitmproxy/postinst
fi
/etc/init.d/mitmproxy enable
echo "mitmproxy installed. Start with: /etc/init.d/mitmproxy start"
echo "mitmproxy service enabled. Start with: /etc/init.d/mitmproxy start"
else
echo "NOTE: mitmproxy binary not found."
echo "Install via pip: pip3 install mitmproxy"
echo "Then enable service: /etc/init.d/mitmproxy enable"
fi
}
exit 0
endef

View File

@ -0,0 +1,52 @@
#!/bin/sh
# Simple OpenWrt script: detect repeated LuCI failed logins and block offending IPs
# Place this file in /usr/bin or /etc/init.d and run periodically (cron) as root.
THRESHOLD=${THRESHOLD:-5} # attempts
TAIL_LINES=${TAIL_LINES:-1000}
log() { logger -t auto_block_luci "$@"; }
detect_offenders() {
log "Scanning logs for failed LuCI logins..."
# Gather recent failed login lines and extract IPs
logread | tail -n "$TAIL_LINES" | grep -i "luci: failed login" | awk '{for(i=1;i<=NF;i++) if ($i=="from") print $(i+1)}' | sort | uniq -c | while read -r count ip; do
if [ -z "$ip" ]; then continue; fi
if [ "$count" -ge "$THRESHOLD" ]; then
block_ip "$ip" "$count"
fi
done
}
block_ip() {
ip=$1
cnt=$2
log "Detected $cnt failed attempts from $ip; blocking"
# Check if already blocked by searching existing firewall rules
exists=$(uci show firewall 2>/dev/null | grep "src_ip='$ip'" || true)
if [ -n "$exists" ]; then
log "$ip already blocked (uci rule exists)"
return
fi
# Create persistent UCI firewall rule named auto_block_<ip>
# UCI section names cannot contain dots, replace with '_'
name="auto_block_$(echo "$ip" | tr '.' '_')"
uci set firewall.$name=rule
uci set firewall.$name.name="auto_block_$ip"
uci set firewall.$name.src='lan'
uci set firewall.$name.src_ip="$ip"
uci set firewall.$name.family='ipv4'
uci set firewall.$name.target='DROP'
uci commit firewall
/etc/init.d/firewall reload
log "Blocked $ip via UCI firewall rule $name"
}
main() {
detect_offenders
}
main