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 <noreply@anthropic.com>
This commit is contained in:
parent
04325c789f
commit
7a9de56ba1
@ -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);
|
||||
},
|
||||
|
||||
|
||||
@ -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'))
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@ -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] || '-'));
|
||||
})
|
||||
);
|
||||
})
|
||||
|
||||
@ -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
|
||||
])
|
||||
]);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user