diff --git a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/alerts.js b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/alerts.js index 65e61c5c..94dfdd9b 100644 --- a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/alerts.js +++ b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/alerts.js @@ -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; }); } }); diff --git a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/clients.js b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/clients.js index 6cda846b..d48deff0 100644 --- a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/clients.js +++ b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/clients.js @@ -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, diff --git a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/history.js b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/history.js index 55868469..3ce1c4ed 100644 --- a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/history.js +++ b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/history.js @@ -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, diff --git a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/services.js b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/services.js index 5a03fc6a..3dc93853 100644 --- a/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/services.js +++ b/package/secubox/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/services.js @@ -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,