feat(crowdsec-dashboard): Add dropbear-logs parser and Hub Stats card
Wizard Step 5 improvements: - Add OpenWrt Parsers section (dropbear-logs, syslog-logs) - Auto-select dropbear-logs when SSH logging is enabled - Install parsers via installHubItem API alongside collections Overview Dashboard: - Add Hub Components stats card showing: - Installed Parsers count (green) - Installed Scenarios count (blue) - Installed Collections count (purple) - List of installed parser names - Load hub data in dashboard for component counts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2cfeb682d8
commit
921db872ed
@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-crowdsec-dashboard
|
||||
PKG_VERSION:=0.7.0
|
||||
PKG_RELEASE:=17
|
||||
PKG_RELEASE:=18
|
||||
PKG_ARCH:=all
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
@ -38,7 +38,8 @@ return view.extend({
|
||||
this.csApi.getHealthCheck().catch(function() { return {}; }),
|
||||
this.csApi.getCapiMetrics().catch(function() { return {}; }),
|
||||
this.csApi.getCollections().catch(function() { return { collections: [] }; }),
|
||||
this.csApi.getNftablesStats().catch(function() { return {}; })
|
||||
this.csApi.getNftablesStats().catch(function() { return {}; }),
|
||||
this.csApi.getHub().catch(function() { return {}; })
|
||||
]);
|
||||
},
|
||||
|
||||
@ -450,7 +451,8 @@ return view.extend({
|
||||
|
||||
E('div', { 'class': 'cs-charts-row' }, [
|
||||
this.renderCapiBlocklist(),
|
||||
this.renderCollectionsCard()
|
||||
this.renderCollectionsCard(),
|
||||
this.renderHubStatsCard()
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cs-charts-row' }, [
|
||||
@ -520,6 +522,7 @@ return view.extend({
|
||||
this.capiMetrics = payload[3] || {};
|
||||
this.collections = (payload[4] && payload[4].collections) || [];
|
||||
this.nftablesStats = payload[5] || {};
|
||||
this.hubData = payload[6] || {};
|
||||
|
||||
// Main wrapper with SecuBox header
|
||||
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
|
||||
@ -548,7 +551,8 @@ refreshDashboard: function() {
|
||||
self.csApi.getHealthCheck().catch(function() { return {}; }),
|
||||
self.csApi.getCapiMetrics().catch(function() { return {}; }),
|
||||
self.csApi.getCollections().catch(function() { return { collections: [] }; }),
|
||||
self.csApi.getNftablesStats().catch(function() { return {}; })
|
||||
self.csApi.getNftablesStats().catch(function() { return {}; }),
|
||||
self.csApi.getHub().catch(function() { return {}; })
|
||||
]).then(function(results) {
|
||||
self.data = results[0];
|
||||
self.logs = (results[1] && results[1].entries) || [];
|
||||
@ -556,6 +560,7 @@ refreshDashboard: function() {
|
||||
self.capiMetrics = results[3] || {};
|
||||
self.collections = (results[4] && results[4].collections) || [];
|
||||
self.nftablesStats = results[5] || {};
|
||||
self.hubData = results[6] || {};
|
||||
self.updateView();
|
||||
});
|
||||
},
|
||||
@ -739,6 +744,63 @@ refreshDashboard: function() {
|
||||
});
|
||||
},
|
||||
|
||||
// Hub Stats Card - Shows installed parsers, scenarios, collections counts
|
||||
renderHubStatsCard: function() {
|
||||
var hub = this.hubData || {};
|
||||
|
||||
// Count installed items by type
|
||||
var parsers = hub.parsers || [];
|
||||
var scenarios = hub.scenarios || [];
|
||||
var collections = hub.collections || [];
|
||||
var postoverflows = hub.postoverflows || [];
|
||||
|
||||
var installedParsers = parsers.filter(function(p) {
|
||||
return p.installed || (p.status && p.status.toLowerCase().indexOf('enabled') >= 0);
|
||||
});
|
||||
var installedScenarios = scenarios.filter(function(s) {
|
||||
return s.installed || (s.status && s.status.toLowerCase().indexOf('enabled') >= 0);
|
||||
});
|
||||
var installedCollections = collections.filter(function(c) {
|
||||
return c.installed || (c.status && c.status.toLowerCase().indexOf('enabled') >= 0);
|
||||
});
|
||||
|
||||
// Get parser details for display
|
||||
var parserList = installedParsers.slice(0, 8).map(function(p) {
|
||||
var name = p.name || p.Name || 'unknown';
|
||||
var shortName = name.split('/').pop();
|
||||
return E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em; padding: 0.35em 0; border-bottom: 1px solid rgba(255,255,255,0.05);' }, [
|
||||
E('span', { 'style': 'color: #22c55e;' }, '✓'),
|
||||
E('span', { 'style': 'font-size: 0.85em;' }, shortName)
|
||||
]);
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'cs-card' }, [
|
||||
E('div', { 'class': 'cs-card-header' }, [
|
||||
E('div', { 'class': 'cs-card-title' }, _('Hub Components'))
|
||||
]),
|
||||
E('div', { 'class': 'cs-card-body' }, [
|
||||
// Mini stats row
|
||||
E('div', { 'style': 'display: flex; gap: 1em; margin-bottom: 1em;' }, [
|
||||
E('div', { 'style': 'flex: 1; text-align: center; padding: 0.75em; background: rgba(34,197,94,0.1); border-radius: 8px;' }, [
|
||||
E('div', { 'style': 'font-size: 1.5em; font-weight: 700; color: #22c55e;' }, String(installedParsers.length)),
|
||||
E('div', { 'style': 'font-size: 0.75em; color: #888;' }, _('Parsers'))
|
||||
]),
|
||||
E('div', { 'style': 'flex: 1; text-align: center; padding: 0.75em; background: rgba(59,130,246,0.1); border-radius: 8px;' }, [
|
||||
E('div', { 'style': 'font-size: 1.5em; font-weight: 700; color: #3b82f6;' }, String(installedScenarios.length)),
|
||||
E('div', { 'style': 'font-size: 0.75em; color: #888;' }, _('Scenarios'))
|
||||
]),
|
||||
E('div', { 'style': 'flex: 1; text-align: center; padding: 0.75em; background: rgba(168,85,247,0.1); border-radius: 8px;' }, [
|
||||
E('div', { 'style': 'font-size: 1.5em; font-weight: 700; color: #a855f7;' }, String(installedCollections.length)),
|
||||
E('div', { 'style': 'font-size: 0.75em; color: #888;' }, _('Collections'))
|
||||
])
|
||||
]),
|
||||
// Parser list
|
||||
E('div', { 'style': 'font-size: 0.8em; font-weight: 600; color: #94a3b8; margin-bottom: 0.5em;' }, _('Installed Parsers')),
|
||||
parserList.length > 0 ? E('div', {}, parserList) : E('div', { 'style': 'color: #666; font-size: 0.85em;' }, _('No parsers installed'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
// Firewall Blocks - Shows IPs blocked in nftables
|
||||
renderFirewallBlocks: function() {
|
||||
var self = this;
|
||||
|
||||
@ -637,50 +637,69 @@ return view.extend({
|
||||
{ name: 'crowdsecurity/whitelist-good-actors', description: 'Whitelist known good bots', preselected: false }
|
||||
];
|
||||
|
||||
// OpenWrt-specific parsers for log sources
|
||||
var recommendedParsers = [
|
||||
{ name: 'crowdsecurity/dropbear-logs', description: 'Dropbear SSH daemon logs (OpenWrt)', preselected: this.wizardData.sshEnabled },
|
||||
{ name: 'crowdsecurity/syslog-logs', description: 'Generic syslog parser', preselected: this.wizardData.syslogEnabled }
|
||||
];
|
||||
|
||||
var renderCheckboxItem = function(item, type) {
|
||||
return E('div', {
|
||||
'class': 'collection-item',
|
||||
'data-' + type: item.name,
|
||||
'data-type': type,
|
||||
'data-checked': item.preselected ? '1' : '0',
|
||||
'style': 'display: flex; align-items: center; cursor: pointer;',
|
||||
'click': function(ev) {
|
||||
var el = ev.currentTarget;
|
||||
var currentState = el.getAttribute('data-checked') === '1';
|
||||
var newState = !currentState;
|
||||
el.setAttribute('data-checked', newState ? '1' : '0');
|
||||
var checkbox = el.querySelector('.checkbox-indicator');
|
||||
if (checkbox) {
|
||||
checkbox.textContent = newState ? '☑' : '☐';
|
||||
checkbox.style.color = newState ? '#22c55e' : '#94a3b8';
|
||||
}
|
||||
}
|
||||
}, [
|
||||
E('span', {
|
||||
'class': 'checkbox-indicator',
|
||||
'style': 'display: inline-block; font-size: 28px; margin-right: 16px; user-select: none; color: ' + (item.preselected ? '#22c55e' : '#94a3b8') + '; line-height: 1; min-width: 28px;'
|
||||
}, item.preselected ? '☑' : '☐'),
|
||||
E('div', { 'class': 'collection-info', 'style': 'flex: 1;' }, [
|
||||
E('strong', {}, item.name),
|
||||
E('div', { 'class': 'collection-desc' }, item.description)
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
return E('div', { 'class': 'wizard-step' }, [
|
||||
E('h2', {}, _('Install Security Collections')),
|
||||
E('p', {}, _('Select collections to install. Recommended collections are pre-selected.')),
|
||||
E('h2', {}, _('Install Security Collections & Parsers')),
|
||||
E('p', {}, _('Select collections and parsers to install. Recommended items are pre-selected based on your log configuration.')),
|
||||
|
||||
// Collections section
|
||||
E('h3', { 'style': 'margin-top: 20px; margin-bottom: 12px; font-size: 1.1em; color: #94a3b8;' }, [
|
||||
E('span', { 'style': 'margin-right: 8px;' }, '📦'),
|
||||
_('Collections')
|
||||
]),
|
||||
E('div', { 'class': 'collections-list' },
|
||||
recommendedCollections.map(L.bind(function(collection) {
|
||||
var checkboxId = 'collection-' + collection.name.replace('/', '-');
|
||||
recommendedCollections.map(function(c) { return renderCheckboxItem(c, 'collection'); })
|
||||
),
|
||||
|
||||
return E('div', {
|
||||
'class': 'collection-item',
|
||||
'data-collection': collection.name,
|
||||
'data-checked': collection.preselected ? '1' : '0',
|
||||
'style': 'display: flex; align-items: center; cursor: pointer;',
|
||||
'click': function(ev) {
|
||||
var item = ev.currentTarget;
|
||||
var currentState = item.getAttribute('data-checked') === '1';
|
||||
var newState = !currentState;
|
||||
item.setAttribute('data-checked', newState ? '1' : '0');
|
||||
|
||||
// Update visual indicator
|
||||
var checkbox = item.querySelector('.checkbox-indicator');
|
||||
if (checkbox) {
|
||||
checkbox.textContent = newState ? '☑' : '☐';
|
||||
checkbox.style.color = newState ? '#22c55e' : '#94a3b8';
|
||||
}
|
||||
}
|
||||
}, [
|
||||
E('span', {
|
||||
'class': 'checkbox-indicator',
|
||||
'style': 'display: inline-block; font-size: 28px; margin-right: 16px; user-select: none; color: ' + (collection.preselected ? '#22c55e' : '#94a3b8') + '; line-height: 1; min-width: 28px;'
|
||||
}, collection.preselected ? '☑' : '☐'),
|
||||
E('div', { 'class': 'collection-info', 'style': 'flex: 1;' }, [
|
||||
E('strong', {}, collection.name),
|
||||
E('div', { 'class': 'collection-desc' }, collection.description)
|
||||
])
|
||||
]);
|
||||
}, this))
|
||||
// Parsers section
|
||||
E('h3', { 'style': 'margin-top: 24px; margin-bottom: 12px; font-size: 1.1em; color: #94a3b8;' }, [
|
||||
E('span', { 'style': 'margin-right: 8px;' }, '📝'),
|
||||
_('OpenWrt Parsers')
|
||||
]),
|
||||
E('div', { 'class': 'collections-list' },
|
||||
recommendedParsers.map(function(p) { return renderCheckboxItem(p, 'parser'); })
|
||||
),
|
||||
|
||||
// Install progress
|
||||
this.wizardData.installing ?
|
||||
E('div', { 'class': 'install-progress' }, [
|
||||
E('div', { 'class': 'spinning' }),
|
||||
E('p', {}, _('Installing collections...')),
|
||||
E('p', {}, _('Installing...')),
|
||||
E('div', { 'id': 'install-status' }, this.wizardData.installStatus || '')
|
||||
]) : E([]),
|
||||
|
||||
@ -1123,37 +1142,60 @@ return view.extend({
|
||||
},
|
||||
|
||||
handleInstallCollections: function() {
|
||||
// Read from data-checked attributes (Unicode checkbox approach)
|
||||
var items = document.querySelectorAll('.collection-item[data-collection]');
|
||||
var selected = Array.from(items)
|
||||
// Read collections from data-checked attributes
|
||||
var collectionItems = document.querySelectorAll('.collection-item[data-collection]');
|
||||
var selectedCollections = Array.from(collectionItems)
|
||||
.filter(function(item) { return item.getAttribute('data-checked') === '1'; })
|
||||
.map(function(item) { return item.getAttribute('data-collection'); });
|
||||
|
||||
console.log('[Wizard] Selected collections:', selected);
|
||||
// Read parsers from data-checked attributes
|
||||
var parserItems = document.querySelectorAll('.collection-item[data-parser]');
|
||||
var selectedParsers = Array.from(parserItems)
|
||||
.filter(function(item) { return item.getAttribute('data-checked') === '1'; })
|
||||
.map(function(item) { return item.getAttribute('data-parser'); });
|
||||
|
||||
if (selected.length === 0) {
|
||||
console.log('[Wizard] Selected collections:', selectedCollections);
|
||||
console.log('[Wizard] Selected parsers:', selectedParsers);
|
||||
|
||||
var totalItems = selectedCollections.length + selectedParsers.length;
|
||||
|
||||
if (totalItems === 0) {
|
||||
this.goToStep(6);
|
||||
return;
|
||||
}
|
||||
|
||||
this.wizardData.installing = true;
|
||||
this.wizardData.installStatus = _('Installing 0 of %d collections...').format(selected.length);
|
||||
this.wizardData.installStatus = _('Installing 0 of %d items...').format(totalItems);
|
||||
this.refreshView();
|
||||
|
||||
var currentIndex = 0;
|
||||
var self = this;
|
||||
|
||||
// Install collections sequentially
|
||||
var installPromises = selected.reduce(L.bind(function(promise, collection, index) {
|
||||
return promise.then(L.bind(function() {
|
||||
this.wizardData.installStatus = _('Installing %d of %d: %s').format(index + 1, selected.length, collection);
|
||||
this.refreshView();
|
||||
var installPromises = selectedCollections.reduce(function(promise, collection) {
|
||||
return promise.then(function() {
|
||||
currentIndex++;
|
||||
self.wizardData.installStatus = _('Installing %d of %d: %s').format(currentIndex, totalItems, collection);
|
||||
self.refreshView();
|
||||
return API.installCollection(collection);
|
||||
}, this));
|
||||
}, this), Promise.resolve());
|
||||
});
|
||||
}, Promise.resolve());
|
||||
|
||||
// Then install parsers sequentially
|
||||
installPromises = selectedParsers.reduce(function(promise, parser) {
|
||||
return promise.then(function() {
|
||||
currentIndex++;
|
||||
self.wizardData.installStatus = _('Installing %d of %d: %s').format(currentIndex, totalItems, parser);
|
||||
self.refreshView();
|
||||
return API.installHubItem('parser', parser);
|
||||
});
|
||||
}, installPromises);
|
||||
|
||||
return installPromises.then(L.bind(function() {
|
||||
this.wizardData.installing = false;
|
||||
this.wizardData.installed = true;
|
||||
this.wizardData.installedCount = selected.length;
|
||||
ui.addNotification(null, E('p', _('Installed %d collections').format(selected.length)), 'info');
|
||||
this.wizardData.installedCount = totalItems;
|
||||
ui.addNotification(null, E('p', _('Installed %d collections and %d parsers').format(selectedCollections.length, selectedParsers.length)), 'info');
|
||||
this.refreshView();
|
||||
|
||||
// Auto-advance to Step 6 (Configure Bouncer) after 2 seconds
|
||||
|
||||
Loading…
Reference in New Issue
Block a user