diff --git a/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/applications.js b/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/applications.js index ffa3a545..74ebd916 100644 --- a/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/applications.js +++ b/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/applications.js @@ -160,107 +160,106 @@ return view.extend({ var getSortIcon = function(column) { if (this.sortColumn !== column) { - return E('i', { 'class': 'fa fa-sort', 'style': 'opacity: 0.3' }); + return E('i', { 'class': 'fa fa-sort', 'style': 'opacity: 0.3; margin-left: 0.25rem;' }); } return E('i', { 'class': 'fa fa-sort-' + (this.sortDirection === 'asc' ? 'up' : 'down'), - 'style': 'color: #3b82f6' + 'style': 'color: #3b82f6; margin-left: 0.25rem;' }); }.bind(this); - dom.content(container, [ - apps.length > 0 ? E('div', { 'class': 'table', 'style': 'font-size: 0.95em' }, - [ - // Header - E('div', { 'class': 'tr table-titles' }, [ - E('div', { - 'class': 'th left', - 'style': 'width: 30%; cursor: pointer', - 'click': ui.createHandlerFn(this, 'handleSort', 'name') - }, [ - _('Application'), - ' ', - getSortIcon('name') - ]), - E('div', { - 'class': 'th center', - 'style': 'width: 15%; cursor: pointer', - 'click': ui.createHandlerFn(this, 'handleSort', 'flows') - }, [ - _('Flows'), - ' ', - getSortIcon('flows') - ]), - E('div', { - 'class': 'th center', - 'style': 'width: 15%; cursor: pointer', - 'click': ui.createHandlerFn(this, 'handleSort', 'packets') - }, [ - _('Packets'), - ' ', - getSortIcon('packets') - ]), - E('div', { - 'class': 'th right', - 'style': 'width: 20%; cursor: pointer', - 'click': ui.createHandlerFn(this, 'handleSort', 'bytes') - }, [ - _('Total Traffic'), - ' ', - getSortIcon('bytes') - ]), - E('div', { 'class': 'th', 'style': 'width: 20%' }, _('% of Total')) - ]) - ].concat( - // Rows - sortedApps.map(function(app, idx) { - var percentage = totalBytes > 0 ? (app.bytes / totalBytes * 100) : 0; - var color = appColors[idx % appColors.length]; + var tableStyle = 'width: 100%; border-collapse: collapse;'; + var thStyle = 'padding: 0.75rem 1rem; text-align: left; font-weight: 600; background: #f8fafc; border-bottom: 2px solid #e2e8f0; cursor: pointer; user-select: none;'; + var tdStyle = 'padding: 0.75rem 1rem; border-bottom: 1px solid #e2e8f0; vertical-align: middle;'; - return E('div', { - 'class': 'tr', - 'style': idx % 2 === 0 ? 'background: #f9fafb' : '' + if (apps.length === 0) { + dom.content(container, [ + E('div', { + 'style': 'text-align: center; padding: 3rem; background: #f9fafb; border-radius: 8px;' + }, [ + E('i', { 'class': 'fa fa-cubes', 'style': 'font-size: 3em; opacity: 0.3; display: block; margin-bottom: 1rem; color: #6b7280;' }), + E('h4', { 'style': 'margin: 0 0 0.5rem 0; color: #374151;' }, _('No Application Data')), + E('p', { 'style': 'margin: 0 0 0.5rem 0; color: #6b7280;' }, _('No applications have been detected yet')), + E('small', { 'style': 'color: #9ca3af;' }, _('Data will appear once network traffic is analyzed')) + ]) + ]); + return; + } + + var tableContent = E('table', { 'style': tableStyle }, [ + E('thead', {}, [ + E('tr', {}, [ + E('th', { + 'style': thStyle, + 'click': ui.createHandlerFn(this, 'handleSort', 'name') }, [ - E('div', { 'class': 'td left', 'style': 'width: 30%' }, [ - E('div', { 'style': 'display: flex; align-items: center; gap: 0.5rem' }, [ - E('div', { - 'style': 'width: 10px; height: 10px; border-radius: 50%; background: ' + color + '; flex-shrink: 0' - }), - E('strong', app.name || 'Unknown') - ]) - ]), - E('div', { 'class': 'td center', 'style': 'width: 15%' }, - (app.flows || 0).toLocaleString()), - E('div', { 'class': 'td center', 'style': 'width: 15%' }, - (app.packets || 0).toLocaleString()), - E('div', { 'class': 'td right', 'style': 'width: 20%' }, [ - E('strong', { 'style': 'color: ' + color }, - netifydAPI.formatBytes(app.bytes || 0)) - ]), - E('div', { 'class': 'td', 'style': 'width: 20%' }, [ - E('div', { - 'style': 'background: #e5e7eb; border-radius: 10px; height: 24px; position: relative; overflow: hidden' - }, [ - E('div', { - 'style': 'background: ' + color + '; height: 100%; width: ' + percentage + '%; transition: width 0.3s ease; border-radius: 10px' - }), - E('span', { - 'style': 'position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 0.8em; font-weight: bold; color: ' + (percentage > 40 ? '#fff' : '#374151') - }, percentage.toFixed(1) + '%') - ]) + _('Application'), + getSortIcon('name') + ]), + E('th', { + 'style': thStyle + ' text-align: center;', + 'click': ui.createHandlerFn(this, 'handleSort', 'flows') + }, [ + _('Flows'), + getSortIcon('flows') + ]), + E('th', { + 'style': thStyle + ' text-align: center;', + 'click': ui.createHandlerFn(this, 'handleSort', 'packets') + }, [ + _('Packets'), + getSortIcon('packets') + ]), + E('th', { + 'style': thStyle + ' text-align: right;', + 'click': ui.createHandlerFn(this, 'handleSort', 'bytes') + }, [ + _('Total Traffic'), + getSortIcon('bytes') + ]), + E('th', { 'style': thStyle + ' text-align: center; cursor: default;' }, _('% of Total')) + ]) + ]), + E('tbody', {}, sortedApps.map(function(app, idx) { + var percentage = totalBytes > 0 ? (app.bytes / totalBytes * 100) : 0; + var color = appColors[idx % appColors.length]; + var rowBg = idx % 2 === 0 ? '' : 'background: #f9fafb;'; + + return E('tr', { 'style': rowBg }, [ + E('td', { 'style': tdStyle }, [ + E('span', { 'style': 'display: inline-flex; align-items: center; gap: 0.5rem;' }, [ + E('span', { + 'style': 'width: 10px; height: 10px; border-radius: 50%; background: ' + color + '; flex-shrink: 0; display: inline-block;' + }), + E('strong', {}, app.name || 'Unknown') ]) - ]); - }.bind(this)) - ) - ) : E('div', { - 'class': 'alert-message info', - 'style': 'text-align: center; padding: 3rem' - }, [ - E('i', { 'class': 'fa fa-cubes', 'style': 'font-size: 3em; opacity: 0.3; display: block; margin-bottom: 1rem' }), - E('h4', _('No Application Data')), - E('p', { 'class': 'text-muted' }, _('No applications have been detected yet')), - E('small', _('Data will appear once network traffic is analyzed')) - ]) + ]), + E('td', { 'style': tdStyle + ' text-align: center;' }, + (app.flows || 0).toLocaleString()), + E('td', { 'style': tdStyle + ' text-align: center;' }, + (app.packets || 0).toLocaleString()), + E('td', { 'style': tdStyle + ' text-align: right;' }, [ + E('strong', { 'style': 'color: ' + color + ';' }, + netifydAPI.formatBytes(app.bytes || 0)) + ]), + E('td', { 'style': tdStyle + ' text-align: center; min-width: 120px;' }, [ + E('div', { + 'style': 'background: #e5e7eb; border-radius: 12px; height: 24px; position: relative; overflow: hidden;' + }, [ + E('div', { + 'style': 'background: ' + color + '; height: 100%; width: ' + percentage + '%; transition: width 0.3s ease; border-radius: 12px;' + }), + E('span', { + 'style': 'position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 0.8em; font-weight: 600; color: ' + (percentage > 40 ? '#fff' : '#374151') + ';' + }, percentage.toFixed(1) + '%') + ]) + ]) + ]); + }.bind(this))) + ]); + + dom.content(container, [ + E('div', { 'style': 'overflow-x: auto;' }, [tableContent]) ]); }, diff --git a/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/flows.js b/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/flows.js index 20fa7df2..9d516eb1 100644 --- a/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/flows.js +++ b/package/secubox/luci-app-secubox-netifyd/htdocs/luci-static/resources/view/secubox-netifyd/flows.js @@ -143,72 +143,74 @@ return view.extend({ } } + var tableStyle = 'width: 100%; border-collapse: collapse; margin-top: 1rem;'; + var thStyle = 'padding: 0.75rem 1rem; text-align: left; font-weight: 600; background: #f8fafc; border-bottom: 2px solid #e2e8f0;'; + var tdStyle = 'padding: 0.75rem 1rem; border-bottom: 1px solid #e2e8f0; vertical-align: middle;'; + + var tableContent = E('table', { 'style': tableStyle }, [ + E('thead', {}, [ + E('tr', {}, [ + E('th', { 'style': thStyle }, _('Interface')), + E('th', { 'style': thStyle + ' text-align: center;' }, _('TCP')), + E('th', { 'style': thStyle + ' text-align: center;' }, _('UDP')), + E('th', { 'style': thStyle + ' text-align: center;' }, _('ICMP')), + E('th', { 'style': thStyle + ' text-align: right;' }, _('Total Traffic')), + E('th', { 'style': thStyle + ' text-align: center;' }, _('Status')) + ]) + ]), + E('tbody', {}, interfaceList.map(function(iface) { + var totalPackets = iface.tcp + iface.udp + iface.icmp; + var isActive = totalPackets > 0; + + return E('tr', { 'style': isActive ? '' : 'opacity: 0.6;' }, [ + E('td', { 'style': tdStyle }, [ + E('span', { 'style': 'display: inline-flex; align-items: center; gap: 0.5rem;' }, [ + E('i', { 'class': 'fa fa-ethernet', 'style': 'color: ' + (isActive ? '#3b82f6' : '#9ca3af') }), + E('strong', {}, self.formatInterfaceLabel(iface.name)) + ]) + ]), + E('td', { 'style': tdStyle + ' text-align: center;' }, [ + E('span', { + 'style': 'display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; background: #3b82f6; color: white; font-weight: 500; min-width: 3rem;' + }, iface.tcp.toLocaleString()) + ]), + E('td', { 'style': tdStyle + ' text-align: center;' }, [ + E('span', { + 'style': 'display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; background: #10b981; color: white; font-weight: 500; min-width: 3rem;' + }, iface.udp.toLocaleString()) + ]), + E('td', { 'style': tdStyle + ' text-align: center;' }, [ + E('span', { + 'style': 'display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; background: #f59e0b; color: white; font-weight: 500; min-width: 3rem;' + }, iface.icmp.toLocaleString()) + ]), + E('td', { 'style': tdStyle + ' text-align: right; font-weight: 500;' }, + netifydAPI.formatBytes ? netifydAPI.formatBytes(iface.bytes) : (iface.bytes + ' B')), + E('td', { 'style': tdStyle + ' text-align: center;' }, [ + E('span', { + 'style': 'display: inline-flex; align-items: center; gap: 0.5rem;' + }, [ + E('i', { + 'class': 'fa fa-circle', + 'style': 'color: ' + (isActive ? '#10b981' : '#9ca3af'), + 'title': isActive ? _('Active') : _('Idle') + }), + iface.dropped > 0 ? E('span', { + 'style': 'padding: 0.125rem 0.5rem; border-radius: 9999px; background: #ef4444; color: white; font-size: 0.75rem;' + }, iface.dropped + ' dropped') : null + ]) + ]) + ]); + })) + ]); + return E('div', { 'class': 'cbi-section' }, [ - E('h3', [ - E('i', { 'class': 'fa fa-network-wired', 'style': 'margin-right: 0.5rem' }), + E('h3', { 'style': 'display: flex; align-items: center; margin-bottom: 0.5rem;' }, [ + E('i', { 'class': 'fa fa-network-wired', 'style': 'margin-right: 0.5rem; color: #3b82f6;' }), _('Flow Activity by Interface') ]), - E('div', { 'class': 'cbi-section-node' }, [ - E('div', { 'class': 'table', 'style': 'font-size: 0.95em' }, [ - // Header - E('div', { 'class': 'tr table-titles' }, [ - E('div', { 'class': 'th', 'style': 'width: 25%' }, _('Interface')), - E('div', { 'class': 'th center', 'style': 'width: 15%' }, _('TCP')), - E('div', { 'class': 'th center', 'style': 'width: 15%' }, _('UDP')), - E('div', { 'class': 'th center', 'style': 'width: 15%' }, _('ICMP')), - E('div', { 'class': 'th right', 'style': 'width: 20%' }, _('Total Traffic')), - E('div', { 'class': 'th center', 'style': 'width: 10%' }, _('Status')) - ]), - // Rows - interfaceList.map(function(iface) { - var totalPackets = iface.tcp + iface.udp + iface.icmp; - var isActive = totalPackets > 0; - - return E('div', { 'class': 'tr' }, [ - E('div', { 'class': 'td', 'style': 'width: 25%' }, [ - E('div', { 'style': 'display: flex; align-items: center; gap: 0.5rem' }, [ - E('i', { 'class': 'fa fa-ethernet', 'style': 'color: ' + (isActive ? '#3b82f6' : '#9ca3af') }), - E('strong', self.formatInterfaceLabel(iface.name)) - ]) - ]), - E('div', { 'class': 'td center', 'style': 'width: 15%' }, [ - E('span', { - 'class': 'badge', - 'style': 'background: #3b82f6; color: white' - }, iface.tcp.toLocaleString()) - ]), - E('div', { 'class': 'td center', 'style': 'width: 15%' }, [ - E('span', { - 'class': 'badge', - 'style': 'background: #10b981; color: white' - }, iface.udp.toLocaleString()) - ]), - E('div', { 'class': 'td center', 'style': 'width: 15%' }, [ - E('span', { - 'class': 'badge', - 'style': 'background: #f59e0b; color: white' - }, iface.icmp.toLocaleString()) - ]), - E('div', { 'class': 'td right', 'style': 'width: 20%' }, - netifydAPI.formatBytes(iface.bytes)), - E('div', { 'class': 'td center', 'style': 'width: 10%' }, [ - isActive ? E('i', { - 'class': 'fa fa-circle', - 'style': 'color: #10b981', - 'title': _('Active') - }) : E('i', { - 'class': 'fa fa-circle', - 'style': 'color: #9ca3af', - 'title': _('Idle') - }), - iface.dropped > 0 ? E('span', { - 'class': 'badge', - 'style': 'background: #ef4444; color: white; margin-left: 0.5rem; font-size: 0.75em' - }, iface.dropped + ' ⚠') : null - ]) - ]); - }) - ]) + E('div', { 'class': 'cbi-section-node', 'style': 'overflow-x: auto;' }, [ + tableContent ]) ]); },