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(),
|
callDecisions(),
|
||||||
callAlerts()
|
callAlerts()
|
||||||
]).then(function(results) {
|
]).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 {
|
return {
|
||||||
status: results[0] || {},
|
status: status,
|
||||||
stats: results[1] || {},
|
stats: (stats.error) ? {} : stats,
|
||||||
decisions: (results[2] && results[2].decisions) || [],
|
decisions: (decisions.error) ? [] : (decisions.decisions || []),
|
||||||
alerts: (results[3] && results[3].alerts) || []
|
alerts: (alerts.error) ? [] : (alerts.alerts || []),
|
||||||
|
error: stats.error || decisions.error || alerts.error || null
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -849,3 +849,48 @@
|
|||||||
border-radius: var(--cs-radius);
|
border-radius: var(--cs-radius);
|
||||||
border: 1px solid var(--cs-border);
|
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 dom';
|
||||||
'require poll';
|
'require poll';
|
||||||
'require ui';
|
'require ui';
|
||||||
|
'require fs';
|
||||||
'require crowdsec-dashboard/api as api';
|
'require crowdsec-dashboard/api as api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -399,9 +400,30 @@ return view.extend({
|
|||||||
var decisions = data.decisions || [];
|
var decisions = data.decisions || [];
|
||||||
var alerts = data.alerts || [];
|
var alerts = data.alerts || [];
|
||||||
var logs = this.logs || [];
|
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', {}, [
|
return E('div', {}, [
|
||||||
this.renderHeader(status),
|
this.renderHeader(status),
|
||||||
|
serviceWarning,
|
||||||
this.renderStatsGrid(stats, decisions),
|
this.renderStatsGrid(stats, decisions),
|
||||||
|
|
||||||
E('div', { 'class': 'cs-charts-row' }, [
|
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) {
|
render: function(payload) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.data = payload[0] || {};
|
this.data = payload[0] || {};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user