secubox-openwrt/package/secubox/luci-app-threat-analyst/htdocs/luci-static/resources/threat-analyst/api.js
CyberMind-FR 93b9636258 fix(threat-analyst): Async AI chat to handle slow LocalAI inference
- LocalAI inference takes 30-60s on ARM64 hardware
- Changed RPCD chat handler to async pattern:
  - Returns poll_id immediately
  - Background process runs AI query (120s timeout)
  - Saves result to /var/lib/threat-analyst/chat_*.json
  - Client polls with poll_id to get result
- Updated api.js with chatAsync() that polls automatically
- Changed default LocalAI port from 8081 to 8091
- Frontend shows "Thinking..." message with spinner during inference
- Uses curl instead of wget (BusyBox wget doesn't support --post-data=-)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 16:41:27 +01:00

171 lines
3.8 KiB
JavaScript

'use strict';
'require baseclass';
'require rpc';
/**
* Threat Analyst API
* Package: luci-app-threat-analyst
* RPCD object: luci.threat-analyst
* Version: 0.1.0
*
* Generative AI-powered threat filtering for:
* - CrowdSec autoban scenarios
* - mitmproxy filter rules
* - WAF rules
*/
var callStatus = rpc.declare({
object: 'luci.threat-analyst',
method: 'status',
expect: { }
});
var callGetThreats = rpc.declare({
object: 'luci.threat-analyst',
method: 'get_threats',
params: ['limit'],
expect: { }
});
var callGetPending = rpc.declare({
object: 'luci.threat-analyst',
method: 'get_pending',
expect: { }
});
var callChat = rpc.declare({
object: 'luci.threat-analyst',
method: 'chat',
params: ['message', 'poll_id'],
expect: { }
});
// Async chat with polling
function chatAsync(message, maxPolls, pollInterval) {
maxPolls = maxPolls || 20;
pollInterval = pollInterval || 3000;
return callChat(message, null).then(function(result) {
if (!result.pending) {
return result;
}
var pollId = result.poll_id;
var polls = 0;
return new Promise(function(resolve, reject) {
function poll() {
polls++;
callChat('poll', pollId).then(function(res) {
if (res.response || res.error) {
resolve(res);
} else if (polls < maxPolls) {
setTimeout(poll, pollInterval);
} else {
resolve({ error: 'AI response timeout (max polls reached)' });
}
}).catch(reject);
}
setTimeout(poll, pollInterval);
});
});
}
var callGenerateRules = rpc.declare({
object: 'luci.threat-analyst',
method: 'generate_rules',
params: ['target'],
expect: { }
});
var callApproveRule = rpc.declare({
object: 'luci.threat-analyst',
method: 'approve_rule',
params: ['id'],
expect: { }
});
var callRejectRule = rpc.declare({
object: 'luci.threat-analyst',
method: 'reject_rule',
params: ['id'],
expect: { }
});
var callRunCycle = rpc.declare({
object: 'luci.threat-analyst',
method: 'run_cycle',
expect: { }
});
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';
return Math.floor(seconds / 86400) + 'd ago';
} catch(e) {
return dateStr;
}
}
function parseScenario(scenario) {
if (!scenario) return 'Unknown';
var parts = scenario.split('/');
var name = parts[parts.length - 1];
return name.split('-').map(function(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
}).join(' ');
}
function getSeverityClass(scenario) {
if (!scenario) return 'medium';
var s = scenario.toLowerCase();
if (s.includes('malware') || s.includes('exploit') || s.includes('cve')) return 'critical';
if (s.includes('bruteforce') || s.includes('scan')) return 'high';
if (s.includes('crawl') || s.includes('http')) return 'low';
return 'medium';
}
function extractCVE(scenario) {
if (!scenario) return null;
// Match CVE patterns: CVE-YYYY-NNNNN
var match = scenario.match(/CVE-\d{4}-\d{4,}/i);
return match ? match[0].toUpperCase() : null;
}
return baseclass.extend({
getStatus: callStatus,
getThreats: callGetThreats,
getPending: callGetPending,
chat: chatAsync,
chatSync: callChat,
generateRules: callGenerateRules,
approveRule: callApproveRule,
rejectRule: callRejectRule,
runCycle: callRunCycle,
formatRelativeTime: formatRelativeTime,
parseScenario: parseScenario,
getSeverityClass: getSeverityClass,
extractCVE: extractCVE,
getOverview: function() {
return Promise.all([
callStatus(),
callGetThreats(20),
callGetPending()
]).then(function(results) {
return {
status: results[0] || {},
threats: (results[1] || {}).threats || [],
pending: (results[2] || {}).pending || []
};
});
}
});