secubox-openwrt/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/waf.js
CyberMind-FR dcfa5bf799 fix: String concatenation with DOM elements in CrowdSec views
Fixed "[object HTMLElement]" display bugs in CrowdSec dashboard views by properly structuring DOM element children arrays.

## Problem:
Multiple CrowdSec dashboard views were showing "[object HTMLElement]" instead of properly rendered content. This occurred when JavaScript code tried to concatenate strings with E() DOM elements using the + operator.

## Root Cause:
```javascript
// WRONG: String concatenation with DOM elements
E('p', {}, _('Access metrics at: ') + E('code', {}, url))
// Results in: "Access metrics at: [object HTMLElement]"
```

When you concatenate a string with a DOM element, JavaScript converts the DOM element to its string representation "[object HTMLElement]", which then gets rendered as text.

## Solution:
Changed from string concatenation to proper children arrays:

```javascript
// CORRECT: Array of children
E('p', {}, [
    _('Access metrics at: '),
    E('code', {}, url)
])
```

## Files Fixed:

### metrics.js (line 299-302):
- Fixed "Access metrics at: [object HTMLElement]" in metrics export info box
- Changed from concatenation to children array

### settings.js (line 217-220):
- Fixed "Or use: [object HTMLElement]" in collections empty state
- Changed from concatenation to children array

### waf.js (lines 123-126):
- Fixed 4 instances in WAF setup instructions:
  - "Install AppSec collections: [object HTMLElement]"
  - "Restart CrowdSec service: [object HTMLElement]"
  - "Verify status: [object HTMLElement]"
- Changed all from concatenation to children arrays

## Technical Notes:
- The E() helper function expects children to be:
  1. A single string
  2. A single DOM element
  3. An array of strings and/or DOM elements
- String concatenation (+) cannot be used to combine text with DOM elements
- Always use array notation when mixing text and elements

## Testing:
- Deployed to router 192.168.8.191
- Metrics view now displays "Access metrics at: http://127.0.0.1:6060/metrics" correctly
- Settings view shows "Or use: cscli hub update" properly
- WAF view renders all setup instructions with code blocks correctly

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-06 19:24:55 +01:00

175 lines
5.5 KiB
JavaScript

'use strict';
'require view';
'require secubox-theme/theme as Theme';
'require dom';
'require poll';
'require ui';
'require crowdsec-dashboard/api as api';
/**
* CrowdSec Dashboard - WAF/AppSec View
* Web Application Firewall status and configuration
* Copyright (C) 2024 CyberMind.fr - Gandalf
*/
return view.extend({
title: _('WAF/AppSec'),
csApi: null,
wafStatus: {},
load: function() {
var cssLink = document.createElement('link');
cssLink.rel = 'stylesheet';
cssLink.href = L.resource('crowdsec-dashboard/dashboard.css');
document.head.appendChild(cssLink);
this.csApi = api;
return this.csApi.getWAFStatus().then(function(result) {
return {
wafStatus: result || {}
};
});
},
renderWAFStatus: function() {
var self = this;
var enabled = this.wafStatus.waf_enabled === true || this.wafStatus.waf_enabled === 1;
var message = this.wafStatus.message || '';
if (!enabled) {
return E('div', { 'class': 'cs-card' }, [
E('div', { 'class': 'cs-card-header' }, [
E('div', { 'class': 'cs-card-icon' }, '🛡️'),
E('h3', {}, _('WAF Status'))
]),
E('div', { 'class': 'cs-card-body' }, [
E('div', { 'class': 'cs-empty' }, [
E('div', { 'class': 'cs-empty-icon' }, '⚠️'),
E('p', { 'style': 'margin: 16px 0; color: var(--cs-text-secondary);' }, message || _('WAF/AppSec not configured')),
E('p', { 'style': 'font-size: 13px; color: var(--cs-text-muted);' },
_('The Web Application Firewall (WAF) feature requires CrowdSec 1.7.0 or higher. Configure AppSec rules to enable request filtering and blocking.'))
])
])
]);
}
return E('div', { 'class': 'cs-card' }, [
E('div', { 'class': 'cs-card-header' }, [
E('div', { 'class': 'cs-card-icon' }, '🛡️'),
E('h3', {}, _('WAF Status')),
E('span', {
'class': 'cs-action ban',
'style': 'margin-left: auto; background: rgba(0,212,170,0.15); color: var(--cs-accent-green); padding: 6px 12px; border-radius: 6px; font-weight: 600;'
}, _('Enabled'))
]),
E('div', { 'class': 'cs-card-body' }, [
this.renderWAFInfo()
])
]);
},
renderWAFInfo: function() {
var info = [];
if (this.wafStatus.rules_count !== undefined) {
info.push(
E('div', { 'class': 'cs-metric-item' }, [
E('span', { 'class': 'cs-metric-name' }, _('Active Rules')),
E('span', { 'class': 'cs-metric-value' }, String(this.wafStatus.rules_count))
])
);
}
if (this.wafStatus.blocked_requests !== undefined) {
info.push(
E('div', { 'class': 'cs-metric-item' }, [
E('span', { 'class': 'cs-metric-name' }, _('Blocked Requests')),
E('span', { 'class': 'cs-metric-value' }, String(this.wafStatus.blocked_requests))
])
);
}
if (this.wafStatus.engine_version) {
info.push(
E('div', { 'class': 'cs-metric-item' }, [
E('span', { 'class': 'cs-metric-name' }, _('Engine Version')),
E('span', { 'class': 'cs-metric-value' }, String(this.wafStatus.engine_version))
])
);
}
if (info.length === 0) {
return E('p', { 'style': 'color: var(--cs-text-secondary); margin: 8px 0;' },
_('WAF is enabled but no detailed metrics available.'));
}
return E('div', { 'class': 'cs-metric-list' }, info);
},
renderConfigHelp: function() {
return E('div', { 'class': 'cs-card' }, [
E('div', { 'class': 'cs-card-header' }, [
E('div', { 'class': 'cs-card-icon' }, '📖'),
E('h3', {}, _('Configuration Guide'))
]),
E('div', { 'class': 'cs-card-body' }, [
E('div', { 'class': 'cs-info-box' }, [
E('h4', { 'style': 'margin: 0 0 8px 0; color: var(--cs-text-primary);' }, _('Enabling WAF/AppSec')),
E('p', { 'style': 'margin: 0 0 12px 0; color: var(--cs-text-secondary); font-size: 14px;' },
_('To enable the Web Application Firewall, you need to:')),
E('ol', { 'style': 'margin: 0; padding-left: 20px; color: var(--cs-text-secondary); font-size: 14px;' }, [
E('li', {}, [
_('Install AppSec collections: '),
E('code', {}, 'cscli collections install crowdsecurity/appsec-*')
]),
E('li', {}, _('Configure AppSec in your acquis.yaml')),
E('li', {}, [
_('Restart CrowdSec service: '),
E('code', {}, '/etc/init.d/crowdsec restart')
]),
E('li', {}, [
_('Verify status: '),
E('code', {}, 'cscli appsec status')
])
])
]),
E('div', { 'class': 'cs-info-box', 'style': 'margin-top: 16px;' }, [
E('h4', { 'style': 'margin: 0 0 8px 0; color: var(--cs-text-primary);' }, _('Documentation')),
E('p', { 'style': 'margin: 0; color: var(--cs-text-secondary); font-size: 14px;' }, [
_('For detailed configuration, see: '),
E('a', {
'href': 'https://docs.crowdsec.net/docs/appsec/intro',
'target': '_blank',
'style': 'color: var(--cs-accent-cyan);'
}, 'CrowdSec AppSec Documentation')
])
])
])
]);
},
render: function(data) {
this.wafStatus = data.wafStatus || {};
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: lang });
return E('div', { 'class': 'cs-dashboard' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('h2', { 'class': 'cs-page-title' }, _('CrowdSec WAF/AppSec')),
E('div', { 'class': 'cs-grid' }, [
this.renderWAFStatus(),
this.renderConfigHelp()
])
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});