feat: Add SecuBox portal header to Media Flow views

Add portal header and dark theme styling to all Media Flow subviews:
- clients.js: Client statistics with portal header
- services.js: Service statistics with portal header
- history.js: Stream history with portal header
- alerts.js: Streaming alerts with portal header

Each view now includes:
- SecuBox global header with Hub, Admin, Security, Network, Monitoring, System navigation
- Internal Media Flow navigation (Dashboard, Clients, Services, History, Alerts)
- Consistent dark theme styling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-09 16:40:00 +01:00
parent 48da7d71ad
commit 3e86952d48
4 changed files with 381 additions and 248 deletions

View File

@ -3,6 +3,32 @@
'require form';
'require ui';
'require media-flow/api as API';
'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() {
@ -14,10 +40,9 @@ return L.view.extend({
render: function(data) {
var alerts = data[0] || [];
var m = new form.Map('media_flow', _('Streaming Alerts'),
_('Configure alerts based on streaming service usage'));
var m = new form.Map('media_flow', null, null);
var s = m.section(form.TypedSection, 'alert', _('Alerts'));
var s = m.section(form.TypedSection, 'alert', _('Streaming Alerts'));
s.anonymous = false;
s.addremove = true;
s.sortable = true;
@ -44,32 +69,27 @@ return L.view.extend({
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.default = o.enabled;
// Custom add button handler
s.addModalOptions = function(s, section_id, ev) {
var serviceName = this.section.getUIElement(section_id, 'service');
var thresholdInput = this.section.getUIElement(section_id, 'threshold_hours');
var actionInput = this.section.getUIElement(section_id, 'action');
if (serviceName && thresholdInput && actionInput) {
var service = serviceName.getValue();
var threshold = parseInt(thresholdInput.getValue());
var action = actionInput.getValue();
if (service && threshold) {
API.setAlert(service, threshold, action).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('Alert created successfully')), 'info');
}
});
}
}
};
return m.render().then(function(rendered) {
return E('div', {}, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
var css = `
.mf-page { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #e4e4e7; }
.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; }
`;
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('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'))
]),
rendered
]);
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
wrapper.appendChild(container);
return wrapper;
});
}
});

View File

@ -2,6 +2,32 @@
'require view';
'require ui';
'require media-flow/api as API';
'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() {
@ -14,55 +40,76 @@ return L.view.extend({
var statsByClient = data[0] || {};
var clients = statsByClient.clients || {};
var v = E('div', { 'class': 'cbi-map' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('h2', {}, _('Clients Statistics')),
E('div', { 'class': 'cbi-map-descr' }, _('Streaming activity per client'))
var css = `
.mf-page { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #e4e4e7; }
.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; }
.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 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; }
`;
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('clients'),
E('div', { 'class': 'mf-header' }, [
E('div', { 'class': 'mf-title' }, ['👥 ', _('Clients Statistics')]),
E('div', { 'class': 'mf-subtitle' }, _('Streaming activity per client'))
])
]);
var clientsList = Object.keys(clients);
if (clientsList.length === 0) {
v.appendChild(E('div', { 'class': 'cbi-section' }, [
E('p', { 'style': 'font-style: italic; text-align: center; padding: 20px' },
_('No client data available yet'))
container.appendChild(E('div', { 'class': 'mf-card' }, [
E('div', { 'class': 'mf-empty' }, [
E('div', { 'class': 'mf-empty-icon' }, '👥'),
E('div', {}, _('No client data available yet'))
])
]));
return v;
} else {
clientsList.sort(function(a, b) {
return (clients[b].total_duration_seconds || 0) - (clients[a].total_duration_seconds || 0);
});
var table = E('table', { 'class': 'mf-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('Client IP')),
E('th', {}, _('Sessions')),
E('th', {}, _('Total Duration')),
E('th', {}, _('Total Bandwidth')),
E('th', {}, _('Top Service'))
])
]),
E('tbody', {}, clientsList.map(function(client) {
var stats = clients[client];
var duration = stats.total_duration_seconds || 0;
var hours = Math.floor(duration / 3600);
var minutes = Math.floor((duration % 3600) / 60);
return E('tr', {}, [
E('td', {}, E('strong', {}, client)),
E('td', {}, String(stats.sessions)),
E('td', {}, hours + 'h ' + minutes + 'm'),
E('td', {}, Math.round(stats.total_bandwidth_kbps) + ' kbps'),
E('td', {}, stats.top_service || 'N/A')
]);
}))
]);
container.appendChild(E('div', { 'class': 'mf-card' }, table));
}
// Sort by total duration
clientsList.sort(function(a, b) {
return (clients[b].total_duration_seconds || 0) - (clients[a].total_duration_seconds || 0);
});
var table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Client IP')),
E('th', { 'class': 'th' }, _('Sessions')),
E('th', { 'class': 'th' }, _('Total Duration')),
E('th', { 'class': 'th' }, _('Total Bandwidth')),
E('th', { 'class': 'th' }, _('Top Service'))
])
]);
clientsList.forEach(function(client) {
var stats = clients[client];
var duration = stats.total_duration_seconds || 0;
var hours = Math.floor(duration / 3600);
var minutes = Math.floor((duration % 3600) / 60);
table.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, client),
E('td', { 'class': 'td' }, String(stats.sessions)),
E('td', { 'class': 'td' }, hours + 'h ' + minutes + 'm'),
E('td', { 'class': 'td' }, Math.round(stats.total_bandwidth_kbps) + ' kbps'),
E('td', { 'class': 'td' }, stats.top_service || 'N/A')
]));
});
v.appendChild(E('div', { 'class': 'cbi-section' }, table));
return v;
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
wrapper.appendChild(container);
return wrapper;
},
handleSaveApply: null,

View File

@ -2,6 +2,32 @@
'require view';
'require ui';
'require media-flow/api as API';
'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() {
@ -14,119 +40,128 @@ return L.view.extend({
var historyData = data[0] || {};
var history = historyData.history || [];
var v = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Stream History')),
E('div', { 'class': 'cbi-map-descr' }, _('Historical record of detected streaming sessions'))
]);
var css = `
.mf-page { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #e4e4e7; }
.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; }
.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-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-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 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; }
`;
// Time period filter
var filterSection = E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'display: flex; gap: 10px; align-items: center; margin-bottom: 15px;' }, [
E('label', {}, _('Time Period: ')),
E('select', { 'id': 'time-filter', 'class': 'cbi-input-select' }, [
E('option', { 'value': '1' }, _('Last 1 hour')),
E('option', { 'value': '6' }, _('Last 6 hours')),
E('option', { 'value': '24', 'selected': 'selected' }, _('Last 24 hours')),
E('option', { 'value': '168' }, _('Last 7 days'))
]),
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() {
var hours = document.getElementById('time-filter').value;
API.getStreamHistory(parseInt(hours)).then(function(data) {
updateHistoryTable(data.history || []);
});
}
}, _('Refresh')),
E('button', {
'class': 'cbi-button cbi-button-negative',
'style': 'margin-left: auto;',
'click': function() {
if (confirm(_('Clear all history data?'))) {
API.clearHistory().then(function() {
ui.addNotification(null, E('p', _('History cleared')), 'info');
updateHistoryTable([]);
});
}
}
}, _('Clear History'))
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('history'),
E('div', { 'class': 'mf-header' }, [
E('div', { 'class': 'mf-title' }, ['📜 ', _('Stream History')]),
E('div', { 'class': 'mf-subtitle' }, _('Historical record of detected streaming sessions'))
])
]);
v.appendChild(filterSection);
// History table
var tableContainer = E('div', { 'id': 'history-table-container', 'class': 'cbi-section' });
v.appendChild(tableContainer);
// Controls
var controls = E('div', { 'class': 'mf-controls' }, [
E('label', {}, _('Time Period: ')),
E('select', { 'id': 'time-filter', 'class': 'mf-select' }, [
E('option', { 'value': '1' }, _('Last 1 hour')),
E('option', { 'value': '6' }, _('Last 6 hours')),
E('option', { 'value': '24', 'selected': 'selected' }, _('Last 24 hours')),
E('option', { 'value': '168' }, _('Last 7 days'))
]),
E('button', {
'class': 'mf-btn',
'click': function() {
var hours = document.getElementById('time-filter').value;
API.getStreamHistory(parseInt(hours)).then(function(data) {
updateHistoryTable(data.history || []);
});
}
}, _('Refresh')),
E('button', {
'class': 'mf-btn mf-btn-danger',
'style': 'margin-left: auto;',
'click': function() {
if (confirm(_('Clear all history data?'))) {
API.clearHistory().then(function() {
ui.addNotification(null, E('p', _('History cleared')), 'info');
updateHistoryTable([]);
});
}
}
}, _('Clear History'))
]);
container.appendChild(controls);
var tableContainer = E('div', { 'id': 'history-table-container', 'class': 'mf-card' });
container.appendChild(tableContainer);
var updateHistoryTable = function(history) {
var container = document.getElementById('history-table-container');
if (!container) return;
var el = document.getElementById('history-table-container');
if (!el) return;
var table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Time')),
E('th', { 'class': 'th' }, _('Service')),
E('th', { 'class': 'th' }, _('Category')),
E('th', { 'class': 'th' }, _('Client')),
E('th', { 'class': 'th' }, _('Quality')),
E('th', { 'class': 'th' }, _('Duration')),
E('th', { 'class': 'th' }, _('Bandwidth'))
])
]);
if (!history || history.length === 0) {
el.innerHTML = '';
el.appendChild(E('div', { 'class': 'mf-empty' }, _('No historical data available.')));
return;
}
if (history && history.length > 0) {
// Sort by timestamp descending
history.sort(function(a, b) {
return new Date(b.timestamp) - new Date(a.timestamp);
});
history.sort(function(a, b) {
return new Date(b.timestamp) - new Date(a.timestamp);
});
var categoryIcons = {
'video': '🎬',
'audio': '🎵',
'visio': '📹',
'other': '📊'
};
var qualityColors = { 'SD': '#6b7280', 'HD': '#059669', 'FHD': '#2563eb', '4K': '#9333ea' };
var categoryIcons = { 'video': '🎬', 'audio': '🎵', 'visio': '📹', 'other': '📊' };
var qualityColors = {
'SD': '#999',
'HD': '#0088cc',
'FHD': '#00cc00',
'4K': '#cc0000'
};
history.slice(0, 100).forEach(function(entry) {
var table = E('table', { 'class': 'mf-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('Time')),
E('th', {}, _('Service')),
E('th', {}, _('Category')),
E('th', {}, _('Client')),
E('th', {}, _('Quality')),
E('th', {}, _('Duration')),
E('th', {}, _('Bandwidth'))
])
]),
E('tbody', {}, history.slice(0, 100).map(function(entry) {
var time = new Date(entry.timestamp).toLocaleString();
var duration = Math.floor((entry.duration || 0) / 60);
var categoryIcon = categoryIcons[entry.category] || '📊';
var qualityColor = qualityColors[entry.quality] || '#666';
var qualityColor = qualityColors[entry.quality] || '#6b7280';
table.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, time),
E('td', { 'class': 'td' }, entry.app || 'unknown'),
E('td', { 'class': 'td' }, categoryIcon + ' ' + (entry.category || 'other')),
E('td', { 'class': 'td' }, entry.client || 'unknown'),
E('td', { 'class': 'td' },
E('span', { 'style': 'color: ' + qualityColor + '; font-weight: bold' }, entry.quality || 'N/A')
),
E('td', { 'class': 'td' }, duration + ' min'),
E('td', { 'class': 'td' }, (entry.bandwidth || 0) + ' kbps')
]));
});
} else {
table.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'colspan': '7', 'style': 'text-align: center; font-style: italic; padding: 20px;' },
_('No historical data available. Streaming sessions will appear here once detected.'))
]));
}
return E('tr', {}, [
E('td', {}, time),
E('td', {}, E('strong', {}, entry.app || 'unknown')),
E('td', {}, categoryIcon + ' ' + (entry.category || 'other')),
E('td', {}, entry.client || 'unknown'),
E('td', {}, entry.quality ? E('span', { 'class': 'mf-quality', 'style': 'background:' + qualityColor }, entry.quality) : '-'),
E('td', {}, duration + ' min'),
E('td', {}, (entry.bandwidth || 0) + ' kbps')
]);
}))
]);
container.innerHTML = '';
container.appendChild(table);
el.innerHTML = '';
el.appendChild(table);
};
// Initial render
updateHistoryTable(history);
return v;
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
wrapper.appendChild(container);
return wrapper;
},
handleSaveApply: null,

View File

@ -2,6 +2,32 @@
'require view';
'require ui';
'require media-flow/api as API';
'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() {
@ -14,102 +40,107 @@ return L.view.extend({
var statsByService = data[0] || {};
var services = statsByService.services || {};
var v = E('div', { 'class': 'cbi-map' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('h2', {}, _('Services Statistics')),
E('div', { 'class': 'cbi-map-descr' }, _('Detailed statistics per streaming service'))
var css = `
.mf-page { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #e4e4e7; }
.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; }
.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 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); }
`;
var container = E('div', { 'class': 'mf-page' }, [
E('style', {}, css),
renderMediaFlowNav('services'),
E('div', { 'class': 'mf-header' }, [
E('div', { 'class': 'mf-title' }, ['🎬 ', _('Services Statistics')]),
E('div', { 'class': 'mf-subtitle' }, _('Detailed statistics per streaming service'))
])
]);
var servicesList = Object.keys(services);
if (servicesList.length === 0) {
v.appendChild(E('div', { 'class': 'cbi-section' }, [
E('p', { 'style': 'font-style: italic; text-align: center; padding: 20px' },
_('No service data available yet. Streaming services will appear here once detected.'))
]));
return v;
}
// Sort by total duration
servicesList.sort(function(a, b) {
return (services[b].total_duration_seconds || 0) - (services[a].total_duration_seconds || 0);
});
var table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Service')),
E('th', { 'class': 'th' }, _('Category')),
E('th', { 'class': 'th' }, _('Sessions')),
E('th', { 'class': 'th' }, _('Total Duration')),
E('th', { 'class': 'th' }, _('Avg Bandwidth')),
E('th', { 'class': 'th' }, _('Actions'))
])
]);
servicesList.forEach(function(service) {
var stats = services[service];
var duration = stats.total_duration_seconds || 0;
var hours = Math.floor(duration / 3600);
var minutes = Math.floor((duration % 3600) / 60);
var avgBandwidth = stats.total_bandwidth_kbps / stats.sessions || 0;
var categoryIcon = {
'video': '🎬',
'audio': '🎵',
'visio': '📹'
}[stats.category] || '📊';
table.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, categoryIcon + ' ' + service),
E('td', { 'class': 'td' }, stats.category),
E('td', { 'class': 'td' }, String(stats.sessions)),
E('td', { 'class': 'td' }, hours + 'h ' + minutes + 'm'),
E('td', { 'class': 'td' }, Math.round(avgBandwidth) + ' kbps'),
E('td', { 'class': 'td' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function(ev) {
API.getServiceDetails(service).then(function(details) {
ui.showModal(_('Service Details: ') + service, [
E('div', { 'class': 'cbi-section' }, [
E('p', {}, [
E('strong', {}, _('Category: ')),
E('span', {}, details.category || 'unknown')
]),
E('p', {}, [
E('strong', {}, _('Total Sessions: ')),
E('span', {}, String(details.total_sessions || 0))
]),
E('p', {}, [
E('strong', {}, _('Average Bandwidth: ')),
E('span', {}, Math.round(details.avg_bandwidth_kbps || 0) + ' kbps')
]),
E('p', {}, [
E('strong', {}, _('Typical Quality: ')),
E('span', {}, details.typical_quality || 'unknown')
]),
E('p', {}, [
E('strong', {}, _('Total Duration: ')),
E('span', {}, Math.floor((details.total_duration_seconds || 0) / 3600) + 'h')
])
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'cbi-button cbi-button-neutral',
'click': ui.hideModal
}, _('Close'))
])
]);
});
}
}, _('Details'))
container.appendChild(E('div', { 'class': 'mf-card' }, [
E('div', { 'class': 'mf-empty' }, [
E('div', { 'class': 'mf-empty-icon' }, '🎬'),
E('div', {}, _('No service data available yet. Streaming services will appear here once detected.'))
])
]));
});
} else {
servicesList.sort(function(a, b) {
return (services[b].total_duration_seconds || 0) - (services[a].total_duration_seconds || 0);
});
v.appendChild(E('div', { 'class': 'cbi-section' }, table));
var table = E('table', { 'class': 'mf-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('Service')),
E('th', {}, _('Category')),
E('th', {}, _('Sessions')),
E('th', {}, _('Total Duration')),
E('th', {}, _('Avg Bandwidth')),
E('th', {}, _('Actions'))
])
]),
E('tbody', {}, servicesList.map(function(service) {
var stats = services[service];
var duration = stats.total_duration_seconds || 0;
var hours = Math.floor(duration / 3600);
var minutes = Math.floor((duration % 3600) / 60);
var avgBandwidth = stats.total_bandwidth_kbps / stats.sessions || 0;
return v;
var categoryIcon = {
'video': '🎬',
'audio': '🎵',
'visio': '📹'
}[stats.category] || '📊';
return E('tr', {}, [
E('td', {}, E('strong', {}, categoryIcon + ' ' + service)),
E('td', {}, stats.category || 'other'),
E('td', {}, String(stats.sessions)),
E('td', {}, hours + 'h ' + minutes + 'm'),
E('td', {}, Math.round(avgBandwidth) + ' kbps'),
E('td', {}, [
E('button', {
'class': 'mf-btn',
'click': function() {
API.getServiceDetails(service).then(function(details) {
ui.showModal(_('Service Details: ') + service, [
E('div', { 'style': 'padding: 20px; color: #e4e4e7;' }, [
E('p', {}, [E('strong', {}, _('Category: ')), details.category || 'unknown']),
E('p', {}, [E('strong', {}, _('Total Sessions: ')), String(details.total_sessions || 0)]),
E('p', {}, [E('strong', {}, _('Average Bandwidth: ')), Math.round(details.avg_bandwidth_kbps || 0) + ' kbps']),
E('p', {}, [E('strong', {}, _('Typical Quality: ')), details.typical_quality || 'unknown']),
E('p', {}, [E('strong', {}, _('Total Duration: ')), Math.floor((details.total_duration_seconds || 0) / 3600) + 'h'])
]),
E('div', { 'style': 'text-align: right; padding: 10px;' }, [
E('button', { 'class': 'mf-btn', 'click': ui.hideModal }, _('Close'))
])
]);
});
}
}, _('Details'))
])
]);
}))
]);
container.appendChild(E('div', { 'class': 'mf-card' }, table));
}
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
wrapper.appendChild(container);
return wrapper;
},
handleSaveApply: null,