Consolidate multiple dashboard API calls into a single get_overview RPC method to reduce network overhead and improve page load performance. The frontend now transforms the consolidated response to maintain compatibility with existing view logic. Also increases poll interval from 30s to 60s. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
503 lines
12 KiB
JavaScript
503 lines
12 KiB
JavaScript
'use strict';
|
|
'require baseclass';
|
|
'require rpc';
|
|
|
|
/**
|
|
* CrowdSec Dashboard API
|
|
* Package: luci-app-crowdsec-dashboard
|
|
* RPCD object: luci.crowdsec-dashboard
|
|
* CrowdSec Core: 1.7.4+
|
|
*/
|
|
|
|
// Version: 0.7.0
|
|
|
|
var callStatus = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'status',
|
|
expect: { }
|
|
});
|
|
|
|
var callDecisions = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'decisions',
|
|
expect: { alerts: [] }
|
|
});
|
|
|
|
var callAlerts = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'alerts',
|
|
expect: { alerts: [] }
|
|
});
|
|
|
|
var callBouncers = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'bouncers',
|
|
expect: { bouncers: [] }
|
|
});
|
|
|
|
var callMetrics = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'metrics',
|
|
expect: { }
|
|
});
|
|
|
|
var callMachines = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'machines',
|
|
expect: { machines: [] }
|
|
});
|
|
|
|
var callHub = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'hub',
|
|
expect: { }
|
|
});
|
|
|
|
var callStats = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'stats',
|
|
expect: { }
|
|
});
|
|
|
|
var callGetOverview = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'get_overview',
|
|
expect: { }
|
|
});
|
|
|
|
var callSecuboxLogs = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'secubox_logs',
|
|
expect: { }
|
|
});
|
|
|
|
var callCollectDebug = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'collect_debug',
|
|
expect: { }
|
|
});
|
|
|
|
var callBan = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'ban',
|
|
params: ['ip', 'duration', 'reason'],
|
|
expect: { }
|
|
});
|
|
|
|
var callUnban = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'unban',
|
|
params: ['ip'],
|
|
expect: { }
|
|
});
|
|
|
|
// CrowdSec v1.7.4+ features
|
|
var callWAFStatus = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'waf_status',
|
|
expect: { }
|
|
});
|
|
|
|
var callMetricsConfig = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'metrics_config',
|
|
expect: { }
|
|
});
|
|
|
|
var callConfigureMetrics = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'configure_metrics',
|
|
params: ['enable'],
|
|
expect: { }
|
|
});
|
|
|
|
var callCollections = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'collections',
|
|
expect: { collections: [] }
|
|
});
|
|
|
|
var callInstallCollection = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'install_collection',
|
|
params: ['collection'],
|
|
expect: { }
|
|
});
|
|
|
|
var callRemoveCollection = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'remove_collection',
|
|
params: ['collection'],
|
|
expect: { }
|
|
});
|
|
|
|
var callUpdateHub = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'update_hub',
|
|
expect: { }
|
|
});
|
|
|
|
var callRegisterBouncer = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'register_bouncer',
|
|
params: ['bouncer_name'],
|
|
expect: { }
|
|
});
|
|
|
|
var callDeleteBouncer = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'delete_bouncer',
|
|
params: ['bouncer_name'],
|
|
expect: { }
|
|
});
|
|
|
|
// Firewall Bouncer Management
|
|
var callFirewallBouncerStatus = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'firewall_bouncer_status',
|
|
expect: { }
|
|
});
|
|
|
|
var callControlFirewallBouncer = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'control_firewall_bouncer',
|
|
params: ['action'],
|
|
expect: { }
|
|
});
|
|
|
|
var callFirewallBouncerConfig = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'firewall_bouncer_config',
|
|
expect: { }
|
|
});
|
|
|
|
var callUpdateFirewallBouncerConfig = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'update_firewall_bouncer_config',
|
|
params: ['key', 'value'],
|
|
expect: { }
|
|
});
|
|
|
|
var callNftablesStats = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'nftables_stats',
|
|
expect: { }
|
|
});
|
|
|
|
// Wizard Methods
|
|
var callCheckWizardNeeded = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'check_wizard_needed',
|
|
expect: { }
|
|
});
|
|
|
|
var callWizardState = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'wizard_state',
|
|
expect: { }
|
|
});
|
|
|
|
var callRepairLapi = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'repair_lapi',
|
|
expect: { }
|
|
});
|
|
|
|
var callRepairCapi = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'repair_capi',
|
|
expect: { }
|
|
});
|
|
|
|
var callResetWizard = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'reset_wizard',
|
|
expect: { }
|
|
});
|
|
|
|
// Console Methods
|
|
var callConsoleStatus = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'console_status',
|
|
expect: { }
|
|
});
|
|
|
|
var callConsoleEnroll = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'console_enroll',
|
|
params: ['key', 'name'],
|
|
expect: { }
|
|
});
|
|
|
|
var callConsoleDisable = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'console_disable',
|
|
expect: { }
|
|
});
|
|
|
|
var callServiceControl = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'service_control',
|
|
params: ['action'],
|
|
expect: { }
|
|
});
|
|
|
|
// Acquisition Methods
|
|
var callConfigureAcquisition = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'configure_acquisition',
|
|
params: ['syslog_enabled', 'firewall_enabled', 'ssh_enabled', 'http_enabled', 'syslog_path'],
|
|
expect: { }
|
|
});
|
|
|
|
var callAcquisitionConfig = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'acquisition_config',
|
|
expect: { }
|
|
});
|
|
|
|
var callAcquisitionMetrics = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'acquisition_metrics',
|
|
expect: { }
|
|
});
|
|
|
|
// Health Check & CAPI Methods
|
|
var callHealthCheck = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'health_check',
|
|
expect: { }
|
|
});
|
|
|
|
var callCapiMetrics = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'capi_metrics',
|
|
expect: { }
|
|
});
|
|
|
|
var callHubAvailable = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'hub_available',
|
|
expect: { }
|
|
});
|
|
|
|
var callInstallHubItem = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'install_hub_item',
|
|
params: ['item_type', 'item_name'],
|
|
expect: { }
|
|
});
|
|
|
|
var callRemoveHubItem = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'remove_hub_item',
|
|
params: ['item_type', 'item_name'],
|
|
expect: { }
|
|
});
|
|
|
|
var callGetSettings = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'get_settings',
|
|
expect: { }
|
|
});
|
|
|
|
var callSaveSettings = rpc.declare({
|
|
object: 'luci.crowdsec-dashboard',
|
|
method: 'save_settings',
|
|
params: ['enrollment_key', 'machine_name', 'auto_enroll'],
|
|
expect: { }
|
|
});
|
|
|
|
function formatDuration(seconds) {
|
|
if (!seconds) return 'N/A';
|
|
if (seconds < 60) return seconds + 's';
|
|
if (seconds < 3600) return Math.floor(seconds / 60) + 'm';
|
|
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h';
|
|
return Math.floor(seconds / 86400) + 'd';
|
|
}
|
|
|
|
function formatDate(dateStr) {
|
|
if (!dateStr) return 'N/A';
|
|
try {
|
|
var date = new Date(dateStr);
|
|
return date.toLocaleString();
|
|
} catch(e) {
|
|
return dateStr;
|
|
}
|
|
}
|
|
|
|
function isValidIP(ip) {
|
|
if (!ip) return false;
|
|
|
|
// IPv4 regex
|
|
var ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
|
|
// IPv6 regex (simplified)
|
|
var ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
|
|
|
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
|
}
|
|
|
|
function parseScenario(scenario) {
|
|
if (!scenario) return 'N/A';
|
|
|
|
// Extract human-readable part from scenario name
|
|
// e.g., "crowdsecurity/ssh-bruteforce" -> "SSH Bruteforce"
|
|
var parts = scenario.split('/');
|
|
var name = parts[parts.length - 1];
|
|
|
|
// Convert dash-separated to title case
|
|
return name.split('-').map(function(word) {
|
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
}).join(' ');
|
|
}
|
|
|
|
function getCountryFlag(countryCode) {
|
|
if (!countryCode || countryCode === 'N/A') return '';
|
|
|
|
// Convert country code to flag emoji
|
|
// e.g., "US" -> "🇺🇸"
|
|
var code = countryCode.toUpperCase();
|
|
if (code.length !== 2) return '';
|
|
|
|
var codePoints = [];
|
|
for (var i = 0; i < code.length; i++) {
|
|
codePoints.push(0x1F1E6 - 65 + code.charCodeAt(i));
|
|
}
|
|
return String.fromCodePoint.apply(null, codePoints);
|
|
}
|
|
|
|
function formatRelativeTime(dateStr) {
|
|
if (!dateStr) return 'N/A';
|
|
|
|
try {
|
|
var date = new Date(dateStr);
|
|
var now = new Date();
|
|
var seconds = Math.floor((now - date) / 1000);
|
|
|
|
if (seconds < 60) return seconds + 's ago';
|
|
if (seconds < 3600) return Math.floor(seconds / 60) + 'm ago';
|
|
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h ago';
|
|
if (seconds < 2592000) return Math.floor(seconds / 86400) + 'd ago';
|
|
return Math.floor(seconds / 2592000) + 'mo ago';
|
|
} catch(e) {
|
|
return dateStr;
|
|
}
|
|
}
|
|
|
|
return baseclass.extend({
|
|
getStatus: callStatus,
|
|
getDecisions: function() {
|
|
return callDecisions().then(function(result) {
|
|
console.log('[API] getDecisions raw result:', result);
|
|
console.log('[API] getDecisions result type:', typeof result);
|
|
console.log('[API] getDecisions is array:', Array.isArray(result));
|
|
return result;
|
|
});
|
|
},
|
|
getAlerts: callAlerts,
|
|
getBouncers: callBouncers,
|
|
getMetrics: callMetrics,
|
|
getMachines: callMachines,
|
|
getHub: callHub,
|
|
getStats: callStats,
|
|
getOverview: callGetOverview,
|
|
getSecuboxLogs: callSecuboxLogs,
|
|
collectDebugSnapshot: callCollectDebug,
|
|
addBan: callBan,
|
|
removeBan: callUnban,
|
|
|
|
// CrowdSec v1.7.4+ features
|
|
getWAFStatus: callWAFStatus,
|
|
getMetricsConfig: callMetricsConfig,
|
|
configureMetrics: callConfigureMetrics,
|
|
getCollections: callCollections,
|
|
installCollection: callInstallCollection,
|
|
removeCollection: callRemoveCollection,
|
|
updateHub: callUpdateHub,
|
|
registerBouncer: callRegisterBouncer,
|
|
deleteBouncer: callDeleteBouncer,
|
|
|
|
// Firewall Bouncer Management
|
|
getFirewallBouncerStatus: callFirewallBouncerStatus,
|
|
controlFirewallBouncer: callControlFirewallBouncer,
|
|
getFirewallBouncerConfig: callFirewallBouncerConfig,
|
|
updateFirewallBouncerConfig: callUpdateFirewallBouncerConfig,
|
|
getNftablesStats: callNftablesStats,
|
|
|
|
// Wizard Methods
|
|
checkWizardNeeded: callCheckWizardNeeded,
|
|
getWizardState: callWizardState,
|
|
repairLapi: callRepairLapi,
|
|
repairCapi: callRepairCapi,
|
|
resetWizard: callResetWizard,
|
|
|
|
// Console Methods
|
|
getConsoleStatus: callConsoleStatus,
|
|
consoleEnroll: callConsoleEnroll,
|
|
consoleDisable: callConsoleDisable,
|
|
|
|
// Service Control
|
|
serviceControl: callServiceControl,
|
|
|
|
// Acquisition Methods
|
|
configureAcquisition: callConfigureAcquisition,
|
|
getAcquisitionConfig: callAcquisitionConfig,
|
|
getAcquisitionMetrics: callAcquisitionMetrics,
|
|
|
|
// Health Check & CAPI Methods
|
|
getHealthCheck: callHealthCheck,
|
|
getCapiMetrics: callCapiMetrics,
|
|
getHubAvailable: callHubAvailable,
|
|
installHubItem: callInstallHubItem,
|
|
removeHubItem: callRemoveHubItem,
|
|
|
|
// Settings Management
|
|
getSettings: callGetSettings,
|
|
saveSettings: callSaveSettings,
|
|
|
|
formatDuration: formatDuration,
|
|
formatDate: formatDate,
|
|
formatRelativeTime: formatRelativeTime,
|
|
isValidIP: isValidIP,
|
|
parseScenario: parseScenario,
|
|
getCountryFlag: getCountryFlag,
|
|
|
|
// Aliases for compatibility
|
|
banIP: callBan,
|
|
unbanIP: callUnban,
|
|
|
|
getDashboardData: function() {
|
|
return Promise.all([
|
|
callStatus(),
|
|
callStats(),
|
|
callDecisions(),
|
|
callAlerts()
|
|
]).then(function(results) {
|
|
// Check if any result has an error (service not running)
|
|
var status = results[0] || {};
|
|
var stats = results[1] || {};
|
|
var decisionsRaw = results[2] || [];
|
|
var alerts = results[3] || [];
|
|
|
|
// Flatten alerts->decisions structure
|
|
var decisions = [];
|
|
if (Array.isArray(decisionsRaw)) {
|
|
decisionsRaw.forEach(function(alert) {
|
|
if (alert.decisions && Array.isArray(alert.decisions)) {
|
|
decisions = decisions.concat(alert.decisions);
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
status: status,
|
|
stats: (stats.error) ? {} : stats,
|
|
decisions: decisions,
|
|
alerts: alerts,
|
|
error: stats.error || null
|
|
};
|
|
});
|
|
}
|
|
});
|