feat(luci-app-crowdsec-dashboard): Add graceful error handling when service stopped
Enhanced dashboard UX when CrowdSec service is not running: API module (api.js): - Modified getDashboardData() to handle error responses gracefully - Returns empty arrays/objects for stats when service is stopped - Includes error flag in response data Overview module (overview.js): - Added 'fs' module import for service control - Added startCrowdSec() function to start service from UI - Display warning banner when service is stopped - Provide actionable message with start service link Dashboard CSS (dashboard.css): - Added .cs-warning-banner styles for error messages - Professional warning styling with icon and content layout This resolves XHR timeout errors by showing friendly error messages instead of hanging requests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4e98c03be4
commit
d1788a12ff
@ -198,11 +198,18 @@ return baseclass.extend({
|
||||
callDecisions(),
|
||||
callAlerts()
|
||||
]).then(function(results) {
|
||||
// Check if any result has an error (service not running)
|
||||
var status = results[0] || {};
|
||||
var stats = results[1] || {};
|
||||
var decisions = results[2] || {};
|
||||
var alerts = results[3] || {};
|
||||
|
||||
return {
|
||||
status: results[0] || {},
|
||||
stats: results[1] || {},
|
||||
decisions: (results[2] && results[2].decisions) || [],
|
||||
alerts: (results[3] && results[3].alerts) || []
|
||||
status: status,
|
||||
stats: (stats.error) ? {} : stats,
|
||||
decisions: (decisions.error) ? [] : (decisions.decisions || []),
|
||||
alerts: (alerts.error) ? [] : (alerts.alerts || []),
|
||||
error: stats.error || decisions.error || alerts.error || null
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -849,3 +849,48 @@
|
||||
border-radius: var(--cs-radius);
|
||||
border: 1px solid var(--cs-border);
|
||||
}
|
||||
|
||||
/* Warning banner */
|
||||
.cs-warning-banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.cs-warning-icon {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cs-warning-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cs-warning-title {
|
||||
font-weight: 600;
|
||||
color: #856404;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.cs-warning-message {
|
||||
color: #856404;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cs-warning-message a {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cs-warning-message code {
|
||||
background: #f8f9fa;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
font-family: monospace;
|
||||
color: #d63384;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
'require dom';
|
||||
'require poll';
|
||||
'require ui';
|
||||
'require fs';
|
||||
'require crowdsec-dashboard/api as api';
|
||||
|
||||
/**
|
||||
@ -399,9 +400,30 @@ return view.extend({
|
||||
var decisions = data.decisions || [];
|
||||
var alerts = data.alerts || [];
|
||||
var logs = this.logs || [];
|
||||
|
||||
|
||||
// Check if service is not running
|
||||
var serviceWarning = null;
|
||||
if (data.error && status.crowdsec !== 'running') {
|
||||
serviceWarning = E('div', { 'class': 'cs-warning-banner' }, [
|
||||
E('div', { 'class': 'cs-warning-icon' }, '⚠️'),
|
||||
E('div', { 'class': 'cs-warning-content' }, [
|
||||
E('div', { 'class': 'cs-warning-title' }, 'CrowdSec Service Not Running'),
|
||||
E('div', { 'class': 'cs-warning-message' }, [
|
||||
'The CrowdSec engine is currently stopped. ',
|
||||
E('a', {
|
||||
'href': '#',
|
||||
'click': ui.createHandlerFn(this, 'startCrowdSec')
|
||||
}, 'Click here to start the service'),
|
||||
' or use the command: ',
|
||||
E('code', {}, '/etc/init.d/crowdsec start')
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
return E('div', {}, [
|
||||
this.renderHeader(status),
|
||||
serviceWarning,
|
||||
this.renderStatsGrid(stats, decisions),
|
||||
|
||||
E('div', { 'class': 'cs-charts-row' }, [
|
||||
@ -443,6 +465,37 @@ return view.extend({
|
||||
]);
|
||||
},
|
||||
|
||||
startCrowdSec: function(ev) {
|
||||
var self = this;
|
||||
ev.preventDefault();
|
||||
|
||||
ui.showModal(_('Start CrowdSec'), [
|
||||
E('p', {}, _('Do you want to start the CrowdSec service?')),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-positive',
|
||||
'click': function() {
|
||||
return fs.exec('/etc/init.d/crowdsec', ['start']).then(function() {
|
||||
ui.hideModal();
|
||||
self.showToast('CrowdSec service started', 'success');
|
||||
setTimeout(function() {
|
||||
return self.refreshDashboard();
|
||||
}, 2000);
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
self.showToast('Failed to start service: ' + err, 'error');
|
||||
});
|
||||
}
|
||||
}, _('Start Service'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(payload) {
|
||||
var self = this;
|
||||
this.data = payload[0] || {};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user