fix: Array nesting issue in devices and applications table rendering

Fixed "[object HTMLDivElement]" display bug in device and application list views.

## Problem:
- Device list showed "[object HTMLDivElement],[object HTMLDivElement],..." instead of table rows
- Applications list had the same issue
- Root cause: `sortedDevices.map()` and `sortedApps.map()` return arrays, but these arrays were being nested incorrectly in the E() children array

## Solution:
Changed table row structure from:
```javascript
E('div', { 'class': 'table' }, [
    E('div', { 'class': 'tr table-titles' }, [...]),  // header
    sortedDevices.map(function(device) {              // array nested wrong!
        return E('div', {...});
    })
])
```

To:
```javascript
E('div', { 'class': 'table' },
    [
        E('div', { 'class': 'tr table-titles' }, [...])  // header
    ].concat(
        sortedDevices.map(function(device) {             // properly flattened!
            return E('div', {...});
        })
    )
)
```

## Technical Details:
- The E() helper expects children to be individual DOM elements, not nested arrays
- Using `.concat()` properly flattens the array of row elements
- Applied fix to both devices.js and applications.js views

## Testing:
- Deployed to router
- Device list now displays all 6 detected devices with IP, MAC, traffic stats
- Applications list displays all 4 application categories correctly
- Table formatting and styling render properly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-06 19:17:56 +01:00
parent faf9ee3265
commit f65bc8c4ca
2 changed files with 95 additions and 91 deletions

View File

@ -169,50 +169,51 @@ return view.extend({
}.bind(this); }.bind(this);
dom.content(container, [ dom.content(container, [
apps.length > 0 ? E('div', { 'class': 'table', 'style': 'font-size: 0.95em' }, [ apps.length > 0 ? E('div', { 'class': 'table', 'style': 'font-size: 0.95em' },
// Header [
E('div', { 'class': 'tr table-titles' }, [ // Header
E('div', { E('div', { 'class': 'tr table-titles' }, [
'class': 'th left', E('div', {
'style': 'width: 30%; cursor: pointer', 'class': 'th left',
'click': ui.createHandlerFn(this, 'handleSort', 'name') 'style': 'width: 30%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'name')
_('Application'), }, [
' ', _('Application'),
getSortIcon('name') ' ',
]), getSortIcon('name')
E('div', { ]),
'class': 'th center', E('div', {
'style': 'width: 15%; cursor: pointer', 'class': 'th center',
'click': ui.createHandlerFn(this, 'handleSort', 'flows') 'style': 'width: 15%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'flows')
_('Flows'), }, [
' ', _('Flows'),
getSortIcon('flows') ' ',
]), getSortIcon('flows')
E('div', { ]),
'class': 'th center', E('div', {
'style': 'width: 15%; cursor: pointer', 'class': 'th center',
'click': ui.createHandlerFn(this, 'handleSort', 'packets') 'style': 'width: 15%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'packets')
_('Packets'), }, [
' ', _('Packets'),
getSortIcon('packets') ' ',
]), getSortIcon('packets')
E('div', { ]),
'class': 'th right', E('div', {
'style': 'width: 20%; cursor: pointer', 'class': 'th right',
'click': ui.createHandlerFn(this, 'handleSort', 'bytes') 'style': 'width: 20%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'bytes')
_('Total Traffic'), }, [
' ', _('Total Traffic'),
getSortIcon('bytes') ' ',
]), getSortIcon('bytes')
E('div', { 'class': 'th', 'style': 'width: 20%' }, _('% of Total')) ]),
]), E('div', { 'class': 'th', 'style': 'width: 20%' }, _('% of Total'))
])
// Rows ].concat(
sortedApps.map(function(app, idx) { // Rows
sortedApps.map(function(app, idx) {
var percentage = totalBytes > 0 ? (app.bytes / totalBytes * 100) : 0; var percentage = totalBytes > 0 ? (app.bytes / totalBytes * 100) : 0;
var color = appColors[idx % appColors.length]; var color = appColors[idx % appColors.length];
@ -250,7 +251,8 @@ return view.extend({
]) ])
]); ]);
}.bind(this)) }.bind(this))
]) : E('div', { )
) : E('div', {
'class': 'alert-message info', 'class': 'alert-message info',
'style': 'text-align: center; padding: 3rem' 'style': 'text-align: center; padding: 3rem'
}, [ }, [

View File

@ -172,51 +172,52 @@ return view.extend({
}.bind(this); }.bind(this);
dom.content(container, [ dom.content(container, [
devices.length > 0 ? E('div', { 'class': 'table', 'style': 'font-size: 0.95em' }, [ devices.length > 0 ? E('div', { 'class': 'table', 'style': 'font-size: 0.95em' },
// Header [
E('div', { 'class': 'tr table-titles' }, [ // Header
E('div', { E('div', { 'class': 'tr table-titles' }, [
'class': 'th left', E('div', {
'style': 'width: 20%; cursor: pointer', 'class': 'th left',
'click': ui.createHandlerFn(this, 'handleSort', 'ip') 'style': 'width: 20%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'ip')
_('IP Address'), }, [
' ', _('IP Address'),
getSortIcon('ip') ' ',
]), getSortIcon('ip')
E('div', { 'class': 'th left', 'style': 'width: 20%' }, _('MAC Address')), ]),
E('div', { E('div', { 'class': 'th left', 'style': 'width: 20%' }, _('MAC Address')),
'class': 'th center', E('div', {
'style': 'width: 10%; cursor: pointer', 'class': 'th center',
'click': ui.createHandlerFn(this, 'handleSort', 'flows') 'style': 'width: 10%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'flows')
_('Flows'), }, [
' ', _('Flows'),
getSortIcon('flows') ' ',
]), getSortIcon('flows')
E('div', { ]),
'class': 'th right', E('div', {
'style': 'width: 15%; cursor: pointer', 'class': 'th right',
'click': ui.createHandlerFn(this, 'handleSort', 'bytes_sent') 'style': 'width: 15%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'bytes_sent')
_('Sent'), }, [
' ', _('Sent'),
getSortIcon('bytes_sent') ' ',
]), getSortIcon('bytes_sent')
E('div', { ]),
'class': 'th right', E('div', {
'style': 'width: 15%; cursor: pointer', 'class': 'th right',
'click': ui.createHandlerFn(this, 'handleSort', 'bytes_received') 'style': 'width: 15%; cursor: pointer',
}, [ 'click': ui.createHandlerFn(this, 'handleSort', 'bytes_received')
_('Received'), }, [
' ', _('Received'),
getSortIcon('bytes_received') ' ',
]), getSortIcon('bytes_received')
E('div', { 'class': 'th', 'style': 'width: 20%' }, _('Traffic Distribution')) ]),
]), E('div', { 'class': 'th', 'style': 'width: 20%' }, _('Traffic Distribution'))
])
// Rows ].concat(
sortedDevices.map(function(device, idx) { // Rows
sortedDevices.map(function(device, idx) {
var lastSeen = device.last_seen || 0; var lastSeen = device.last_seen || 0;
var now = Math.floor(Date.now() / 1000); var now = Math.floor(Date.now() / 1000);
var ago = now - lastSeen; var ago = now - lastSeen;
@ -278,7 +279,8 @@ return view.extend({
]) ])
]); ]);
}.bind(this)) }.bind(this))
]) : E('div', { )
) : E('div', {
'class': 'alert-message info', 'class': 'alert-message info',
'style': 'text-align: center; padding: 3rem' 'style': 'text-align: center; padding: 3rem'
}, [ }, [