From 7a9de56ba117a3de697c228ed62a697d0457f2ae Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 12 Feb 2026 12:11:42 +0100 Subject: [PATCH] style(device-intel): Migrate dashboard views to KISS theme - dashboard.js: KISS stats grid, source chips, type cards, recent devices table - devices.js: KISS filter bar, device table with inline actions, edit/detail modals - emulators.js: KISS emulator cards with status badges, mini tables - mesh.js: KISS peer cards grid, remote devices table Removes external CSS loading (cssLink pattern) and di-* class prefixes. Uses KissTheme.E(), kiss-* classes, and CSS variables throughout. Co-Authored-By: Claude Opus 4.5 --- .../resources/view/device-intel/dashboard.js | 252 +++++++++--------- .../resources/view/device-intel/devices.js | 208 ++++++++------- .../resources/view/device-intel/emulators.js | 164 ++++++------ .../resources/view/device-intel/mesh.js | 104 ++++---- 4 files changed, 385 insertions(+), 343 deletions(-) diff --git a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/dashboard.js b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/dashboard.js index 71f59d2f..7556af97 100644 --- a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/dashboard.js +++ b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/dashboard.js @@ -5,60 +5,35 @@ 'require device-intel/api as api'; 'require secubox/kiss-theme'; -return view.extend({ - css: null, +/** + * Device Intelligence Dashboard - KISS Style + * Copyright (C) 2025 CyberMind.fr + */ +return view.extend({ load: function() { return Promise.all([ api.getSummary(), api.getDevices(), - api.getDeviceTypes(), - L.require('device-intel/common.css') - .catch(function() { return null; }) + api.getDeviceTypes() ]); }, render: function(data) { + var self = this; + var K = KissTheme; var summary = data[0] || {}; var devResult = data[1] || {}; var typesResult = data[2] || {}; var devices = devResult.devices || []; var types = typesResult.types || []; - // Include CSS - var cssLink = E('link', { - rel: 'stylesheet', - href: L.resource('device-intel/common.css') - }); + // Count at-risk devices + var atRiskCount = devices.filter(function(d) { + return d.risk_score > 0; + }).length; - // ── Summary Stat Cards ── - var stats = E('div', { 'class': 'di-stats' }, [ - this.statCard(summary.total || 0, _('Total Devices'), '#3b82f6'), - this.statCard(summary.online || 0, _('Online'), '#22c55e'), - this.statCard(summary.mesh_peers || 0, _('Mesh Peers'), '#6366f1'), - this.statCard(devices.filter(function(d) { - return d.risk_score > 0; - }).length, _('At Risk'), '#ef4444') - ]); - - // ── Data Source Status ── - var sources = summary.sources || {}; - var sourceBar = E('div', { 'class': 'di-source-bar' }, [ - this.sourceChip('MAC Guardian', sources.mac_guardian), - this.sourceChip('Client Guardian', sources.client_guardian), - this.sourceChip('DHCP Leases', sources.dhcp), - this.sourceChip('P2P Mesh', sources.p2p) - ]); - - // ── Emulator Status ── - var emus = summary.emulators || {}; - var emuBar = E('div', { 'class': 'di-source-bar' }, [ - this.sourceChip('USB', emus.usb), - this.sourceChip('MQTT', emus.mqtt), - this.sourceChip('Zigbee', emus.zigbee) - ]); - - // ── Device Type Distribution ── + // Build type counts var typeCounts = {}; devices.forEach(function(d) { var t = d.device_type || 'unknown'; @@ -68,122 +43,149 @@ return view.extend({ var typeMap = {}; types.forEach(function(t) { typeMap[t.id] = t; }); - var typeCards = Object.keys(typeCounts).sort(function(a, b) { - return typeCounts[b] - typeCounts[a]; - }).map(function(tid) { - var info = typeMap[tid] || { name: tid, color: '#6c757d' }; - return E('div', { - 'class': 'di-type-card', - 'style': '--type-color:' + (info.color || '#6c757d') - }, [ - E('span', { 'class': 'count' }, String(typeCounts[tid])), - E('span', { 'class': 'name' }, info.name || tid) - ]); - }); - - var typeGrid = E('div', { 'class': 'di-type-grid' }, typeCards); - - // ── Zone Distribution ── + // Build zone counts var zoneCounts = {}; devices.forEach(function(d) { var z = d.cg_zone || 'unzoned'; zoneCounts[z] = (zoneCounts[z] || 0) + 1; }); - var zoneItems = Object.keys(zoneCounts).map(function(z) { - return E('span', { - 'class': 'di-source-chip', - 'style': 'background:#e0e7ff; color:#3730a3;' - }, z + ' (' + zoneCounts[z] + ')'); - }); - - // ── Recent Devices (last 5 seen) ── + // Recent devices (last 5 seen) var recent = devices .filter(function(d) { return d.last_seen; }) .sort(function(a, b) { return (b.last_seen || 0) - (a.last_seen || 0); }) .slice(0, 5); - var recentRows = recent.map(function(d) { - return E('tr', {}, [ - E('td', {}, [ - E('span', { - 'class': 'di-online-dot ' + (d.online ? 'online' : 'offline') - }), - d.label || d.hostname || d.mac + var sources = summary.sources || {}; + var emus = summary.emulators || {}; + + var content = K.E('div', {}, [ + // Page Header + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;' }, [ + K.E('div', {}, [ + K.E('h2', { 'style': 'margin: 0; font-size: 24px; display: flex; align-items: center; gap: 10px;' }, [ + K.E('span', {}, '🔍'), + 'Device Intelligence' + ]), + K.E('p', { 'style': 'margin: 4px 0 0; color: var(--kiss-muted, #94a3b8); font-size: 14px;' }, + 'Network device discovery and monitoring') ]), - E('td', {}, d.ip || '-'), - E('td', {}, d.device_type || '-'), - E('td', {}, d.vendor || '-') - ]); - }); + K.E('button', { + 'class': 'kiss-btn kiss-btn-blue', + 'style': 'padding: 10px 16px; font-size: 14px;', + 'click': function() { + api.refresh().then(function() { + window.location.href = window.location.pathname + '?' + Date.now(); + }); + } + }, '🔄 Refresh') + ]), - var recentTable = E('table', { 'class': 'di-device-table' }, [ - E('thead', {}, E('tr', {}, [ - E('th', {}, _('Device')), - E('th', {}, _('IP')), - E('th', {}, _('Type')), - E('th', {}, _('Vendor')) - ])), - E('tbody', {}, recentRows.length > 0 ? recentRows : - [E('tr', {}, E('td', { 'colspan': '4', 'style': 'text-align:center; color:#6c757d;' }, - _('No recent devices')))]) - ]); + // Stats Grid + K.E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'gap: 16px; margin-bottom: 20px;' }, [ + self.statCard(K, summary.total || 0, 'Total Devices', 'var(--kiss-blue, #3b82f6)', '📱'), + self.statCard(K, summary.online || 0, 'Online', 'var(--kiss-green, #22c55e)', '🟢'), + self.statCard(K, summary.mesh_peers || 0, 'Mesh Peers', 'var(--kiss-purple, #6366f1)', '🔗'), + self.statCard(K, atRiskCount, 'At Risk', 'var(--kiss-red, #ef4444)', '⚠️') + ]), - var content = [ - cssLink, - E('h2', {}, _('Device Intelligence')), - - E('div', { 'class': 'cbi-section' }, [ - E('div', { 'style': 'display:flex; justify-content:space-between; align-items:center; margin-bottom:0.5em;' }, [ - E('h3', { 'style': 'margin:0;' }, _('Overview')), - E('button', { - 'class': 'cbi-button', - 'click': function() { - api.refresh().then(function() { - window.location.href = window.location.pathname + '?' + Date.now(); - }); - } - }, _('Refresh')) + // Data Sources Card + K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['📡 ', 'Data Sources']), + K.E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px;' }, [ + self.sourceChip(K, 'MAC Guardian', sources.mac_guardian), + self.sourceChip(K, 'Client Guardian', sources.client_guardian), + self.sourceChip(K, 'DHCP Leases', sources.dhcp), + self.sourceChip(K, 'P2P Mesh', sources.p2p) ]), - stats + K.E('div', { 'style': 'font-size: 13px; color: var(--kiss-muted); margin-bottom: 8px;' }, 'Emulators'), + K.E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 8px;' }, [ + self.sourceChip(K, 'USB', emus.usb), + self.sourceChip(K, 'MQTT', emus.mqtt), + self.sourceChip(K, 'Zigbee', emus.zigbee) + ]) ]), - E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Data Sources')), - sourceBar, - E('h4', { 'style': 'margin-top:0.75em;' }, _('Emulators')), - emuBar + // Device Types Card + K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['🏷️ ', 'Device Types']), + K.E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 12px;' }, + Object.keys(typeCounts).sort(function(a, b) { + return typeCounts[b] - typeCounts[a]; + }).map(function(tid) { + var info = typeMap[tid] || { name: tid, color: '#6c757d' }; + return K.E('div', { + 'style': 'background: var(--kiss-bg2, #111827); border: 1px solid ' + (info.color || '#6c757d') + '; border-radius: 8px; padding: 12px 16px; text-align: center; min-width: 80px;' + }, [ + K.E('div', { 'style': 'font-size: 24px; font-weight: bold; color: ' + (info.color || '#6c757d') }, String(typeCounts[tid])), + K.E('div', { 'style': 'font-size: 12px; color: var(--kiss-muted); margin-top: 4px;' }, info.name || tid) + ]); + }) + ) ]), - E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Device Types')), - typeGrid + // Zones Card + K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['🗺️ ', 'Zones']), + K.E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 8px;' }, + Object.keys(zoneCounts).map(function(z) { + return K.E('span', { + 'style': 'background: var(--kiss-purple, #6366f1); color: #fff; padding: 6px 12px; border-radius: 6px; font-size: 13px;' + }, z + ' (' + zoneCounts[z] + ')'); + }) + ) ]), - E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Zones')), - E('div', { 'class': 'di-source-bar' }, zoneItems) - ]), - - E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Recent Devices')), - recentTable + // Recent Devices Card + K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['🕐 ', 'Recent Devices']), + K.E('table', { 'class': 'kiss-table' }, [ + K.E('thead', {}, K.E('tr', {}, [ + K.E('th', {}, 'Device'), + K.E('th', {}, 'IP'), + K.E('th', {}, 'Type'), + K.E('th', {}, 'Vendor') + ])), + K.E('tbody', {}, + recent.length > 0 + ? recent.map(function(d) { + return K.E('tr', {}, [ + K.E('td', {}, [ + K.E('span', { + 'style': 'display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 8px; background: ' + (d.online ? 'var(--kiss-green, #22c55e)' : 'var(--kiss-muted, #64748b)') + ';' + }), + d.label || d.hostname || d.mac + ]), + K.E('td', {}, d.ip || '-'), + K.E('td', {}, d.device_type || '-'), + K.E('td', {}, d.vendor || '-') + ]); + }) + : [K.E('tr', {}, K.E('td', { 'colspan': '4', 'style': 'text-align: center; color: var(--kiss-muted); padding: 20px;' }, 'No recent devices'))] + ) + ]) ]) - ]; + ]); return KissTheme.wrap(content, 'admin/secubox/services/device-intel'); }, - statCard: function(value, label, color) { - return E('div', { 'class': 'di-stat-card' }, [ - E('div', { 'class': 'value', 'style': 'color:' + color }, String(value)), - E('div', { 'class': 'label' }, label) + statCard: function(K, value, label, color, icon) { + return K.E('div', { + 'style': 'background: var(--kiss-card, #161e2e); border: 1px solid var(--kiss-line, #1e293b); border-radius: 12px; padding: 20px; text-align: center;' + }, [ + K.E('div', { 'style': 'font-size: 20px; margin-bottom: 8px;' }, icon), + K.E('div', { 'style': 'font-size: 32px; font-weight: bold; color: ' + color }, String(value)), + K.E('div', { 'style': 'font-size: 12px; color: var(--kiss-muted); margin-top: 4px;' }, label) ]); }, - sourceChip: function(name, active) { - return E('span', { - 'class': 'di-source-chip ' + (active ? 'active' : 'inactive') + sourceChip: function(K, name, active) { + return K.E('span', { + 'style': 'padding: 6px 12px; border-radius: 6px; font-size: 13px; ' + + (active + ? 'background: var(--kiss-green, #22c55e); color: #000;' + : 'background: var(--kiss-bg2, #111827); color: var(--kiss-muted); border: 1px solid var(--kiss-line, #1e293b);') }, name); }, diff --git a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/devices.js b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/devices.js index 2e7c879c..b1adc399 100644 --- a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/devices.js +++ b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/devices.js @@ -5,6 +5,11 @@ 'require device-intel/api as api'; 'require secubox/kiss-theme'; +/** + * Device Intel - Devices List - KISS Style + * Copyright (C) 2025 CyberMind.fr + */ + return view.extend({ load: function() { return Promise.all([ @@ -19,84 +24,95 @@ return view.extend({ var devices = devResult.devices || []; var types = typesResult.types || []; var self = this; - - var cssLink = E('link', { - rel: 'stylesheet', - href: L.resource('device-intel/common.css') - }); + var K = KissTheme; // Build type lookup var typeMap = {}; types.forEach(function(t) { typeMap[t.id] = t; }); + var inputStyle = 'padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px;'; + // ── Filter Bar ── var filterInput, typeFilter, statusFilter; - var filterBar = E('div', { 'class': 'di-filter-bar' }, [ - filterInput = E('input', { + var filterBar = K.E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; align-items: center;' }, [ + filterInput = K.E('input', { 'type': 'text', - 'placeholder': _('Search MAC, hostname, IP, vendor...'), - 'style': 'width:250px;', + 'placeholder': 'Search MAC, hostname, IP, vendor...', + 'style': inputStyle + ' width: 280px;', 'input': function() { self.applyFilters(devices, typeMap); } }), - typeFilter = E('select', { + typeFilter = K.E('select', { + 'style': inputStyle, 'change': function() { self.applyFilters(devices, typeMap); } }, [ - E('option', { value: '' }, _('All Types')) + K.E('option', { value: '' }, 'All Types') ].concat(types.map(function(t) { - return E('option', { value: t.id }, t.name); + return K.E('option', { value: t.id }, t.name); })).concat([ - E('option', { value: 'unknown' }, _('Unknown')) + K.E('option', { value: 'unknown' }, 'Unknown') ])), - statusFilter = E('select', { + statusFilter = K.E('select', { + 'style': inputStyle, 'change': function() { self.applyFilters(devices, typeMap); } }, [ - E('option', { value: '' }, _('All Status')), - E('option', { value: 'online' }, _('Online')), - E('option', { value: 'offline' }, _('Offline')) + K.E('option', { value: '' }, 'All Status'), + K.E('option', { value: 'online' }, 'Online'), + K.E('option', { value: 'offline' }, 'Offline') ]), - E('button', { - 'class': 'cbi-button', + K.E('button', { + 'class': 'kiss-btn kiss-btn-blue', + 'style': 'padding: 10px 16px;', 'click': function() { api.refresh().then(function() { window.location.href = window.location.pathname + '?' + Date.now(); }); } - }, _('Refresh')) + }, '🔄 Refresh') ]); // Store filter elements for later this._filterInput = filterInput; this._typeFilter = typeFilter; this._statusFilter = statusFilter; + this._typeMap = typeMap; // ── Device Table ── - var tbody = E('tbody', { 'id': 'di-device-tbody' }); + var tbody = K.E('tbody', { 'id': 'di-device-tbody' }); this.renderDeviceRows(tbody, devices, typeMap); - var table = E('table', { 'class': 'di-device-table' }, [ - E('thead', {}, E('tr', {}, [ - E('th', {}, ''), - E('th', {}, _('Device')), - E('th', {}, _('MAC')), - E('th', {}, _('IP')), - E('th', {}, _('Vendor')), - E('th', {}, _('Type')), - E('th', {}, _('Zone')), - E('th', {}, _('Source')), - E('th', {}, _('Actions')) + var table = K.E('table', { 'class': 'kiss-table' }, [ + K.E('thead', {}, K.E('tr', {}, [ + K.E('th', { 'style': 'width: 40px;' }, ''), + K.E('th', {}, 'Device'), + K.E('th', {}, 'MAC'), + K.E('th', {}, 'IP'), + K.E('th', {}, 'Vendor'), + K.E('th', {}, 'Type'), + K.E('th', {}, 'Zone'), + K.E('th', {}, 'Source'), + K.E('th', { 'style': 'width: 140px;' }, 'Actions') ])), tbody ]); - var content = [ - cssLink, - E('h2', {}, _('Devices')), - E('div', { 'class': 'cbi-section' }, [ + var content = K.E('div', {}, [ + // Page Header + K.E('div', { 'style': 'margin-bottom: 20px;' }, [ + K.E('h2', { 'style': 'margin: 0; font-size: 24px; display: flex; align-items: center; gap: 10px;' }, [ + K.E('span', {}, '📱'), + 'Devices' + ]), + K.E('p', { 'style': 'margin: 4px 0 0; color: var(--kiss-muted, #94a3b8); font-size: 14px;' }, + 'Network devices discovered across all data sources') + ]), + + // Filter and Table Card + K.E('div', { 'class': 'kiss-card' }, [ filterBar, table ]) - ]; + ]); return KissTheme.wrap(content, 'admin/secubox/services/device-intel/devices'); }, @@ -135,56 +151,55 @@ return view.extend({ renderDeviceRows: function(tbody, devices, typeMap) { var self = this; + var K = KissTheme; dom.content(tbody, devices.map(function(d) { var typeInfo = typeMap[d.device_type] || {}; - return E('tr', { 'data-mac': d.mac }, [ - E('td', {}, E('span', { - 'class': 'di-online-dot ' + (d.online ? 'online' : 'offline'), - 'title': d.online ? _('Online') : _('Offline') + return K.E('tr', { 'data-mac': d.mac }, [ + K.E('td', {}, K.E('span', { + 'style': 'display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: ' + (d.online ? 'var(--kiss-green, #22c55e)' : 'var(--kiss-muted, #64748b)') + ';', + 'title': d.online ? 'Online' : 'Offline' })), - E('td', {}, [ - E('strong', {}, d.label || d.hostname || '-'), + K.E('td', {}, [ + K.E('strong', {}, d.label || d.hostname || '-'), d.emulator_source - ? E('small', { 'style': 'display:block; color:#6c757d;' }, + ? K.E('small', { 'style': 'display: block; color: var(--kiss-muted); font-size: 11px;' }, d.emulator_source) : null ].filter(Boolean)), - E('td', { 'style': 'font-family:monospace; font-size:0.85em;' }, d.mac || '-'), - E('td', {}, d.ip || '-'), - E('td', {}, (function() { + K.E('td', { 'style': 'font-family: monospace; font-size: 12px;' }, d.mac || '-'), + K.E('td', {}, d.ip || '-'), + K.E('td', {}, (function() { var v = self.vendorDisplay(d); return [v[0], v[1]].join(''); })()), - E('td', {}, [ + K.E('td', {}, [ typeInfo.name - ? E('span', { - 'style': 'border-left:3px solid ' + (typeInfo.color || '#6c757d') + - '; padding-left:0.5em;' + ? K.E('span', { + 'style': 'border-left: 3px solid ' + (typeInfo.color || '#6c757d') + '; padding-left: 8px;' }, typeInfo.name) - : E('span', { 'style': 'color:#6c757d;' }, d.device_type || '-') + : K.E('span', { 'style': 'color: var(--kiss-muted);' }, d.device_type || '-') ]), - E('td', {}, d.cg_zone || '-'), - E('td', {}, d.source_node || 'local'), - E('td', {}, [ - E('button', { - 'class': 'cbi-button cbi-button-action', - 'style': 'padding:0.2em 0.5em; font-size:0.8em;', + K.E('td', {}, d.cg_zone || '-'), + K.E('td', {}, d.source_node || 'local'), + K.E('td', {}, [ + K.E('button', { + 'class': 'kiss-btn kiss-btn-blue', + 'style': 'padding: 4px 10px; font-size: 12px; margin-right: 6px;', 'click': function() { self.handleEditDevice(d, typeMap); } - }, _('Edit')), - ' ', - E('button', { - 'class': 'cbi-button', - 'style': 'padding:0.2em 0.5em; font-size:0.8em;', + }, 'Edit'), + K.E('button', { + 'class': 'kiss-btn', + 'style': 'padding: 4px 10px; font-size: 12px;', 'click': function() { self.handleShowDetail(d); } - }, _('Detail')) + }, 'Detail') ]) ]); })); if (devices.length === 0) { - dom.content(tbody, E('tr', {}, - E('td', { 'colspan': '9', 'style': 'text-align:center; color:#6c757d; padding:2em;' }, - _('No devices found')))); + dom.content(tbody, K.E('tr', {}, + K.E('td', { 'colspan': '9', 'style': 'text-align: center; color: var(--kiss-muted); padding: 40px;' }, + 'No devices found'))); } }, @@ -215,26 +230,23 @@ return view.extend({ handleEditDevice: function(device, typeMap) { var typeSelect, labelInput; var types = Object.keys(typeMap); + var inputStyle = 'width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid var(--kiss-line, #1e293b); background: var(--kiss-bg2, #111827); color: var(--kiss-text, #e2e8f0); font-size: 14px; margin-top: 6px;'; - ui.showModal(_('Edit Device: ') + (device.label || device.hostname || device.mac), [ - E('div', { 'style': 'display:flex; flex-direction:column; gap:0.8em;' }, [ - E('label', {}, [ - E('strong', {}, _('Label')), + ui.showModal('✏️ Edit Device: ' + (device.label || device.hostname || device.mac), [ + E('div', { 'style': 'display: flex; flex-direction: column; gap: 16px;' }, [ + E('div', {}, [ + E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Label'), labelInput = E('input', { 'type': 'text', - 'class': 'cbi-input-text', 'value': device.label || '', 'placeholder': device.hostname || device.mac, - 'style': 'margin-left:0.5em;' + 'style': inputStyle }) ]), - E('label', {}, [ - E('strong', {}, _('Device Type')), - typeSelect = E('select', { - 'class': 'cbi-input-select', - 'style': 'margin-left:0.5em;' - }, [ - E('option', { value: '' }, _('-- Auto --')) + E('div', {}, [ + E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted); text-transform: uppercase;' }, 'Device Type'), + typeSelect = E('select', { 'style': inputStyle }, [ + E('option', { value: '' }, '-- Auto --') ].concat(types.map(function(tid) { var opt = E('option', { value: tid }, typeMap[tid].name); if (device.device_type === tid) opt.selected = true; @@ -242,11 +254,15 @@ return view.extend({ }))) ]) ]), - E('div', { 'class': 'right', 'style': 'margin-top:1em;' }, [ - E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')), - ' ', + E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px;' }, [ E('button', { - 'class': 'cbi-button cbi-button-action', + 'class': 'kiss-btn', + 'style': 'padding: 10px 20px;', + 'click': ui.hideModal + }, 'Cancel'), + E('button', { + 'class': 'kiss-btn kiss-btn-green', + 'style': 'padding: 10px 20px;', 'click': function() { var newType = typeSelect.value; var newLabel = labelInput.value.trim(); @@ -254,15 +270,15 @@ return view.extend({ ui.hideModal(); api.setDeviceMeta(device.mac, newType, newLabel).then(function(res) { if (res && res.success) { - ui.addNotification(null, E('p', {}, _('Device updated.')), 'info'); + ui.addNotification(null, E('p', {}, 'Device updated.'), 'info'); window.location.href = window.location.pathname + '?' + Date.now(); } else { ui.addNotification(null, E('p', {}, - _('Update failed: ') + (res ? res.error : '')), 'danger'); + 'Update failed: ' + (res ? res.error : '')), 'danger'); } }); } - }, _('Save')) + }, '💾 Save') ]) ]); }, @@ -274,7 +290,7 @@ return view.extend({ ['Hostname', device.hostname || '-'], ['Label', device.label || '-'], ['Vendor', device.vendor || '-'], - ['Online', device.online ? 'Yes' : 'No'], + ['Online', device.online ? '🟢 Yes' : '⚫ No'], ['Connection', device.connection_type || '-'], ['Interface', device.iface || '-'], ['Randomized MAC', device.randomized ? 'Yes' : 'No'], @@ -290,17 +306,21 @@ return view.extend({ ['Source Node', device.source_node || 'local'] ]; - ui.showModal(_('Device Detail: ') + (device.label || device.hostname || device.mac), [ - E('table', { 'style': 'width:100%;' }, + ui.showModal('🔍 Device Detail: ' + (device.label || device.hostname || device.mac), [ + E('table', { 'style': 'width: 100%; border-collapse: collapse;' }, rows.map(function(r) { return E('tr', {}, [ - E('td', { 'style': 'font-weight:bold; padding:0.3em 1em 0.3em 0;' }, r[0]), - E('td', { 'style': 'padding:0.3em 0;' }, r[1]) + E('td', { 'style': 'font-weight: bold; padding: 8px 16px 8px 0; color: var(--kiss-muted); border-bottom: 1px solid var(--kiss-line, #1e293b);' }, r[0]), + E('td', { 'style': 'padding: 8px 0; border-bottom: 1px solid var(--kiss-line, #1e293b);' }, r[1]) ]); }) ), - E('div', { 'class': 'right', 'style': 'margin-top:1em;' }, - E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Close'))) + E('div', { 'style': 'text-align: right; margin-top: 20px;' }, + E('button', { + 'class': 'kiss-btn', + 'style': 'padding: 10px 20px;', + 'click': ui.hideModal + }, 'Close')) ]); }, diff --git a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/emulators.js b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/emulators.js index b8dc7553..f82e4f99 100644 --- a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/emulators.js +++ b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/emulators.js @@ -5,6 +5,11 @@ 'require device-intel/api as api'; 'require secubox/kiss-theme'; +/** + * Device Intel - Emulator Modules - KISS Style + * Copyright (C) 2025 CyberMind.fr + */ + return view.extend({ load: function() { return Promise.all([ @@ -14,118 +19,119 @@ return view.extend({ }, render: function(data) { + var self = this; + var K = KissTheme; var emuResult = data[0] || {}; var devResult = data[1] || {}; var devices = devResult.devices || []; - var cssLink = E('link', { - rel: 'stylesheet', - href: L.resource('device-intel/common.css') - }); - // Filter devices by emulator source var usbDevices = devices.filter(function(d) { return d.emulator_source === 'usb'; }); var mqttDevices = devices.filter(function(d) { return d.emulator_source === 'mqtt'; }); var zigbeeDevices = devices.filter(function(d) { return d.emulator_source === 'zigbee'; }); - // ── USB Card ── var usb = emuResult.usb || {}; - var usbCard = this.emuCard('USB Peripherals', usb.enabled, [ - E('div', { 'style': 'display:flex; gap:2em; margin-bottom:0.75em;' }, [ - E('span', {}, _('System USB devices: ') + E('strong', {}, String(usb.device_count || 0)).textContent), - E('span', {}, _('Discovered: ') + E('strong', {}, String(usbDevices.length)).textContent) - ]), - this.deviceMiniTable(usbDevices, ['hostname', 'vendor', 'device_type']) - ]); - - // ── MQTT Card ── var mqtt = emuResult.mqtt || {}; - var mqttCard = this.emuCard('MQTT Broker', mqtt.enabled, [ - E('div', { 'style': 'display:flex; gap:2em; margin-bottom:0.75em; flex-wrap:wrap;' }, [ - E('span', {}, _('Broker: ') + (mqtt.broker_host || '127.0.0.1') + ':' + (mqtt.broker_port || 1883)), - E('span', {}, [ - _('Status: '), - mqtt.broker_running - ? E('span', { 'style': 'color:#22c55e;' }, _('Running')) - : E('span', { 'style': 'color:#ef4444;' }, _('Not Found')) - ]), - E('span', {}, _('Clients discovered: ') + E('strong', {}, String(mqttDevices.length)).textContent) - ]), - this.deviceMiniTable(mqttDevices, ['hostname', 'vendor', 'device_type']) - ]); - - // ── Zigbee Card ── var zigbee = emuResult.zigbee || {}; - var zigbeeCard = this.emuCard('Zigbee Coordinator', zigbee.enabled, [ - E('div', { 'style': 'display:flex; gap:2em; margin-bottom:0.75em; flex-wrap:wrap;' }, [ - E('span', {}, _('Adapter: ') + (zigbee.adapter || 'zigbee2mqtt')), - E('span', {}, _('Dongle: ') + (zigbee.coordinator || '/dev/ttyUSB0')), - E('span', {}, [ - _('Dongle: '), - zigbee.dongle_present - ? E('span', { 'style': 'color:#22c55e;' }, _('Present')) - : E('span', { 'style': 'color:#ef4444;' }, _('Not Found')) - ]), - E('span', {}, _('Paired devices: ') + E('strong', {}, String(zigbeeDevices.length)).textContent) - ]), - this.deviceMiniTable(zigbeeDevices, ['hostname', 'vendor', 'device_type']) - ]); - var content = E('div', {}, [ - cssLink, - E('h2', {}, _('Emulator Modules')), - E('p', { 'style': 'color:#6c757d; margin-bottom:1em;' }, - _('Pluggable device discovery modules for USB, MQTT, and Zigbee peripherals.')), - usbCard, - mqttCard, - zigbeeCard, - E('div', { 'style': 'text-align:right; margin-top:1em;' }, - E('a', { + var content = K.E('div', {}, [ + // Page Header + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;' }, [ + K.E('div', {}, [ + K.E('h2', { 'style': 'margin: 0; font-size: 24px; display: flex; align-items: center; gap: 10px;' }, [ + K.E('span', {}, '🔌'), + 'Emulator Modules' + ]), + K.E('p', { 'style': 'margin: 4px 0 0; color: var(--kiss-muted, #94a3b8); font-size: 14px;' }, + 'Pluggable device discovery modules for USB, MQTT, and Zigbee peripherals') + ]), + K.E('a', { 'href': L.url('admin/secubox/device-intel/settings'), - 'class': 'cbi-button cbi-button-neutral' - }, _('Configure Emulators'))) + 'class': 'kiss-btn kiss-btn-blue', + 'style': 'padding: 10px 16px; text-decoration: none;' + }, '⚙️ Configure') + ]), + + // USB Card + self.emuCard(K, '🔌 USB Peripherals', usb.enabled, [ + K.E('div', { 'style': 'display: flex; gap: 24px; margin-bottom: 16px; flex-wrap: wrap;' }, [ + K.E('span', {}, ['System USB devices: ', K.E('strong', {}, String(usb.device_count || 0))]), + K.E('span', {}, ['Discovered: ', K.E('strong', { 'style': 'color: var(--kiss-green);' }, String(usbDevices.length))]) + ]), + self.deviceMiniTable(K, usbDevices, ['hostname', 'vendor', 'device_type']) + ]), + + // MQTT Card + self.emuCard(K, '📡 MQTT Broker', mqtt.enabled, [ + K.E('div', { 'style': 'display: flex; gap: 24px; margin-bottom: 16px; flex-wrap: wrap;' }, [ + K.E('span', {}, 'Broker: ' + (mqtt.broker_host || '127.0.0.1') + ':' + (mqtt.broker_port || 1883)), + K.E('span', {}, [ + 'Status: ', + mqtt.broker_running + ? K.E('span', { 'style': 'color: var(--kiss-green, #22c55e);' }, '● Running') + : K.E('span', { 'style': 'color: var(--kiss-red, #ef4444);' }, '● Not Found') + ]), + K.E('span', {}, ['Clients discovered: ', K.E('strong', { 'style': 'color: var(--kiss-green);' }, String(mqttDevices.length))]) + ]), + self.deviceMiniTable(K, mqttDevices, ['hostname', 'vendor', 'device_type']) + ]), + + // Zigbee Card + self.emuCard(K, '📻 Zigbee Coordinator', zigbee.enabled, [ + K.E('div', { 'style': 'display: flex; gap: 24px; margin-bottom: 16px; flex-wrap: wrap;' }, [ + K.E('span', {}, 'Adapter: ' + (zigbee.adapter || 'zigbee2mqtt')), + K.E('span', {}, 'Dongle: ' + (zigbee.coordinator || '/dev/ttyUSB0')), + K.E('span', {}, [ + 'Status: ', + zigbee.dongle_present + ? K.E('span', { 'style': 'color: var(--kiss-green, #22c55e);' }, '● Present') + : K.E('span', { 'style': 'color: var(--kiss-red, #ef4444);' }, '● Not Found') + ]), + K.E('span', {}, ['Paired devices: ', K.E('strong', { 'style': 'color: var(--kiss-green);' }, String(zigbeeDevices.length))]) + ]), + self.deviceMiniTable(K, zigbeeDevices, ['hostname', 'vendor', 'device_type']) + ]) ]); return KissTheme.wrap(content, 'admin/secubox/services/device-intel/emulators'); }, - emuCard: function(title, enabled, content) { - return E('div', { 'class': 'di-emu-card' }, [ - E('h4', {}, [ - title, - E('span', { - 'class': 'status ' + (enabled ? 'enabled' : 'disabled'), - 'style': 'margin-left:0.75em;' - }, enabled ? _('Enabled') : _('Disabled')) + emuCard: function(K, title, enabled, contentItems) { + return K.E('div', { 'class': 'kiss-card', 'style': 'margin-bottom: 16px;' }, [ + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;' }, [ + K.E('div', { 'class': 'kiss-card-title', 'style': 'margin: 0;' }, title), + enabled + ? K.E('span', { 'style': 'background: var(--kiss-green, #22c55e); color: #000; padding: 4px 12px; border-radius: 6px; font-size: 12px; font-weight: bold;' }, 'Enabled') + : K.E('span', { 'style': 'background: var(--kiss-muted, #64748b); color: #fff; padding: 4px 12px; border-radius: 6px; font-size: 12px;' }, 'Disabled') ]) - ].concat(content)); + ].concat(contentItems)); }, - deviceMiniTable: function(devices, fields) { + deviceMiniTable: function(K, devices, fields) { if (devices.length === 0) { - return E('p', { 'style': 'color:#6c757d; font-style:italic;' }, - _('No devices discovered from this emulator.')); + return K.E('p', { 'style': 'color: var(--kiss-muted); font-style: italic; text-align: center; padding: 20px;' }, + 'No devices discovered from this emulator.'); } var headers = { - 'hostname': _('Name'), - 'vendor': _('Model/Vendor'), - 'device_type': _('Type'), - 'mac': _('ID'), - 'ip': _('IP') + 'hostname': 'Name', + 'vendor': 'Model/Vendor', + 'device_type': 'Type', + 'mac': 'ID', + 'ip': 'IP' }; - return E('table', { 'class': 'di-device-table' }, [ - E('thead', {}, E('tr', {}, + return K.E('table', { 'class': 'kiss-table' }, [ + K.E('thead', {}, K.E('tr', {}, fields.map(function(f) { - return E('th', {}, headers[f] || f); + return K.E('th', {}, headers[f] || f); }) )), - E('tbody', {}, + K.E('tbody', {}, devices.slice(0, 20).map(function(d) { - return E('tr', {}, + return K.E('tr', {}, fields.map(function(f) { - return E('td', {}, String(d[f] || '-')); + return K.E('td', {}, String(d[f] || '-')); }) ); }) diff --git a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/mesh.js b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/mesh.js index a6228dd1..31eba766 100644 --- a/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/mesh.js +++ b/package/secubox/luci-app-device-intel/htdocs/luci-static/resources/view/device-intel/mesh.js @@ -5,6 +5,11 @@ 'require device-intel/api as api'; 'require secubox/kiss-theme'; +/** + * Device Intel - Mesh Network - KISS Style + * Copyright (C) 2025 CyberMind.fr + */ + return view.extend({ load: function() { return Promise.all([ @@ -14,15 +19,10 @@ return view.extend({ }, render: function(data) { + var K = KissTheme; var meshResult = data[0] || {}; - var summary = data[1] || {}; var meshDevices = meshResult.devices || []; - var cssLink = E('link', { - rel: 'stylesheet', - href: L.resource('device-intel/common.css') - }); - // Separate mesh peers from remote devices var peers = meshDevices.filter(function(d) { return d.device_type === 'mesh_peer'; }); var remoteDevices = meshDevices.filter(function(d) { return d.device_type !== 'mesh_peer'; }); @@ -30,26 +30,29 @@ return view.extend({ // ── Peer Cards ── var peerCards; if (peers.length > 0) { - peerCards = E('div', { 'class': 'di-stats' }, + peerCards = K.E('div', { 'class': 'kiss-grid kiss-grid-3', 'style': 'gap: 16px;' }, peers.map(function(p) { - return E('div', { 'class': 'di-stat-card' }, [ - E('div', { 'style': 'display:flex; align-items:center; gap:0.5em; margin-bottom:0.5em;' }, [ - E('span', { - 'class': 'di-online-dot ' + (p.online ? 'online' : 'offline') + return K.E('div', { + 'style': 'background: var(--kiss-bg2, #111827); border: 1px solid var(--kiss-line, #1e293b); border-radius: 12px; padding: 16px;' + }, [ + K.E('div', { 'style': 'display: flex; align-items: center; gap: 10px; margin-bottom: 8px;' }, [ + K.E('span', { + 'style': 'display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: ' + (p.online ? 'var(--kiss-green, #22c55e)' : 'var(--kiss-muted, #64748b)') + ';' }), - E('strong', {}, p.hostname || p.mac) + K.E('strong', { 'style': 'font-size: 14px;' }, p.hostname || p.mac) ]), - E('div', { 'style': 'font-size:0.85em; color:#6c757d;' }, p.ip || '-') + K.E('div', { 'style': 'font-size: 13px; color: var(--kiss-muted);' }, p.ip || '-') ]); }) ); } else { - peerCards = E('div', { - 'style': 'text-align:center; padding:2em; color:#6c757d;' + peerCards = K.E('div', { + 'style': 'text-align: center; padding: 40px 20px; color: var(--kiss-muted);' }, [ - E('p', {}, _('No mesh peers discovered.')), - E('p', { 'style': 'font-size:0.9em;' }, - _('Ensure SecuBox P2P is running and peers are configured.')) + K.E('div', { 'style': 'font-size: 48px; margin-bottom: 12px;' }, '🔗'), + K.E('p', { 'style': 'margin: 0;' }, 'No mesh peers discovered.'), + K.E('p', { 'style': 'font-size: 13px; margin-top: 8px;' }, + 'Ensure SecuBox P2P is running and peers are configured.') ]); } @@ -57,48 +60,59 @@ return view.extend({ var remoteTable; if (remoteDevices.length > 0) { var rows = remoteDevices.map(function(d) { - return E('tr', {}, [ - E('td', {}, [ - E('span', { 'class': 'di-online-dot ' + (d.online ? 'online' : 'offline') }), + return K.E('tr', {}, [ + K.E('td', {}, [ + K.E('span', { + 'style': 'display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 8px; background: ' + (d.online ? 'var(--kiss-green, #22c55e)' : 'var(--kiss-muted, #64748b)') + ';' + }), d.label || d.hostname || d.mac ]), - E('td', {}, d.ip || '-'), - E('td', {}, d.device_type || '-'), - E('td', {}, d.source_node || '-') + K.E('td', {}, d.ip || '-'), + K.E('td', {}, d.device_type || '-'), + K.E('td', {}, d.source_node || '-') ]); }); - remoteTable = E('table', { 'class': 'di-device-table' }, [ - E('thead', {}, E('tr', {}, [ - E('th', {}, _('Device')), - E('th', {}, _('IP')), - E('th', {}, _('Type')), - E('th', {}, _('Source Node')) + remoteTable = K.E('table', { 'class': 'kiss-table' }, [ + K.E('thead', {}, K.E('tr', {}, [ + K.E('th', {}, 'Device'), + K.E('th', {}, 'IP'), + K.E('th', {}, 'Type'), + K.E('th', {}, 'Source Node') ])), - E('tbody', {}, rows) + K.E('tbody', {}, rows) ]); } else { - remoteTable = E('p', { 'style': 'color:#6c757d; font-style:italic;' }, - _('No remote devices available. Peer device inventory sharing is not yet active.')); + remoteTable = K.E('p', { 'style': 'color: var(--kiss-muted); font-style: italic; text-align: center; padding: 20px;' }, + 'No remote devices available. Peer device inventory sharing is not yet active.'); } - var content = E('div', {}, [ - cssLink, - E('h2', {}, _('Mesh Network')), + var content = K.E('div', {}, [ + // Page Header + K.E('div', { 'style': 'margin-bottom: 20px;' }, [ + K.E('h2', { 'style': 'margin: 0; font-size: 24px; display: flex; align-items: center; gap: 10px;' }, [ + K.E('span', {}, '🌐'), + 'Mesh Network' + ]), + K.E('p', { 'style': 'margin: 4px 0 0; color: var(--kiss-muted, #94a3b8); font-size: 14px;' }, + 'P2P mesh peers and shared device inventory') + ]), - E('div', { 'class': 'cbi-section' }, [ - E('div', { 'style': 'display:flex; justify-content:space-between; align-items:center;' }, [ - E('h3', { 'style': 'margin:0;' }, _('Peers')), - E('span', { 'style': 'color:#6c757d;' }, - String(peers.length) + _(' peer(s) discovered')) + // Peers Card + K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;' }, [ + K.E('div', { 'class': 'kiss-card-title', 'style': 'margin: 0;' }, ['🔗 ', 'Peers']), + K.E('span', { 'style': 'color: var(--kiss-muted); font-size: 13px;' }, + String(peers.length) + ' peer(s) discovered') ]), peerCards ]), - E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Remote Devices')), - E('p', { 'style': 'color:#6c757d; margin-bottom:1em; font-size:0.9em;' }, - _('Devices reported by mesh peers. Requires device-intel on remote nodes.')), + // Remote Devices Card + K.E('div', { 'class': 'kiss-card' }, [ + K.E('div', { 'class': 'kiss-card-title' }, ['📡 ', 'Remote Devices']), + K.E('p', { 'style': 'color: var(--kiss-muted); margin-bottom: 16px; font-size: 13px;' }, + 'Devices reported by mesh peers. Requires device-intel on remote nodes.'), remoteTable ]) ]);