New packages: - secubox-threat-analyst: AI-powered threat analysis with CrowdSec integration - luci-app-threat-analyst: LuCI dashboard for threat intelligence - secubox-dns-guard: DNS security monitoring and blocking - secubox-mcp-server: Model Context Protocol server for AI assistant integration Enhancements: - dns-provider: Add DynDNS support (dyndns, get, update, domains commands) - gandi.sh: Full DynDNS with WAN IP detection and record updates - luci-app-dnsguard: Upgrade to v1.1.0 with improved dashboard Infrastructure: - BIND9 DNS setup for secubox.in with CAA records - Wildcard SSL certificates via DNS-01 challenge - HAProxy config fixes for secubox.in subdomains - Mail server setup with Roundcube webmail Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
276 lines
9.9 KiB
JavaScript
276 lines
9.9 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require poll';
|
|
'require ui';
|
|
'require threat-analyst/api as api';
|
|
|
|
return view.extend({
|
|
chatHistory: [],
|
|
|
|
load: function() {
|
|
return Promise.all([
|
|
api.status(),
|
|
api.getThreats(20),
|
|
api.getPending()
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var status = data[0] || {};
|
|
var threats = (data[1] || {}).threats || [];
|
|
var pending = (data[2] || {}).pending || [];
|
|
var self = this;
|
|
|
|
// Add CSS
|
|
var style = E('style', {}, `
|
|
.ta-dashboard { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
|
.ta-card { background: var(--bg-alt, #f8f9fa); border-radius: 8px; padding: 16px; }
|
|
.ta-card h3 { margin: 0 0 12px 0; font-size: 14px; text-transform: uppercase; opacity: 0.7; }
|
|
.ta-status-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
|
.ta-stat { text-align: center; padding: 12px; background: var(--bg, #fff); border-radius: 6px; }
|
|
.ta-stat-value { font-size: 24px; font-weight: bold; color: var(--primary, #2196f3); }
|
|
.ta-stat-label { font-size: 11px; opacity: 0.6; margin-top: 4px; }
|
|
.ta-stat.warning .ta-stat-value { color: #ff9800; }
|
|
.ta-stat.danger .ta-stat-value { color: #f44336; }
|
|
.ta-stat.success .ta-stat-value { color: #4caf50; }
|
|
|
|
.ta-chat { grid-column: span 2; }
|
|
.ta-chat-messages { height: 300px; overflow-y: auto; background: var(--bg, #fff); border-radius: 6px; padding: 12px; margin-bottom: 12px; }
|
|
.ta-message { margin-bottom: 12px; }
|
|
.ta-message.user { text-align: right; }
|
|
.ta-message-bubble { display: inline-block; max-width: 80%; padding: 8px 12px; border-radius: 12px; }
|
|
.ta-message.user .ta-message-bubble { background: #2196f3; color: white; }
|
|
.ta-message.ai .ta-message-bubble { background: var(--bg-alt, #e3e3e3); }
|
|
.ta-message-time { font-size: 10px; opacity: 0.5; margin-top: 4px; }
|
|
.ta-chat-input { display: flex; gap: 8px; }
|
|
.ta-chat-input input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 6px; }
|
|
.ta-chat-input button { padding: 10px 20px; background: #2196f3; color: white; border: none; border-radius: 6px; cursor: pointer; }
|
|
|
|
.ta-threats { grid-column: span 2; }
|
|
.ta-threats-table { width: 100%; border-collapse: collapse; }
|
|
.ta-threats-table th, .ta-threats-table td { padding: 8px; text-align: left; border-bottom: 1px solid #eee; }
|
|
.ta-threats-table th { font-size: 11px; text-transform: uppercase; opacity: 0.6; }
|
|
.ta-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; }
|
|
.ta-badge.critical { background: #f44336; color: white; }
|
|
.ta-badge.high { background: #ff9800; color: white; }
|
|
.ta-badge.medium { background: #ffc107; color: black; }
|
|
.ta-badge.low { background: #4caf50; color: white; }
|
|
|
|
.ta-actions { display: flex; gap: 8px; margin-top: 16px; }
|
|
.ta-btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
|
.ta-btn-primary { background: #2196f3; color: white; }
|
|
.ta-btn-success { background: #4caf50; color: white; }
|
|
.ta-btn-warning { background: #ff9800; color: white; }
|
|
|
|
.ta-pending { margin-top: 20px; }
|
|
.ta-pending-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; background: var(--bg, #fff); border-radius: 6px; margin-bottom: 8px; }
|
|
`);
|
|
|
|
var statusCard = E('div', { 'class': 'ta-card' }, [
|
|
E('h3', {}, 'Agent Status'),
|
|
E('div', { 'class': 'ta-status-grid' }, [
|
|
E('div', { 'class': 'ta-stat ' + (status.daemon_running ? 'success' : 'warning') }, [
|
|
E('div', { 'class': 'ta-stat-value' }, status.daemon_running ? 'ON' : 'OFF'),
|
|
E('div', { 'class': 'ta-stat-label' }, 'Daemon')
|
|
]),
|
|
E('div', { 'class': 'ta-stat ' + (status.localai_status === 'online' ? 'success' : 'danger') }, [
|
|
E('div', { 'class': 'ta-stat-value' }, status.localai_status === 'online' ? 'OK' : 'OFF'),
|
|
E('div', { 'class': 'ta-stat-label' }, 'LocalAI')
|
|
]),
|
|
E('div', { 'class': 'ta-stat ' + (status.recent_threats > 10 ? 'danger' : status.recent_threats > 0 ? 'warning' : 'success') }, [
|
|
E('div', { 'class': 'ta-stat-value' }, status.recent_threats || 0),
|
|
E('div', { 'class': 'ta-stat-label' }, 'Threats (1h)')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'ta-actions' }, [
|
|
E('button', {
|
|
'class': 'ta-btn ta-btn-primary',
|
|
'click': function() { self.runCycle(); }
|
|
}, 'Run Analysis'),
|
|
E('button', {
|
|
'class': 'ta-btn ta-btn-success',
|
|
'click': function() { self.generateRules('all'); }
|
|
}, 'Generate Rules')
|
|
])
|
|
]);
|
|
|
|
var pendingCard = E('div', { 'class': 'ta-card' }, [
|
|
E('h3', {}, 'Pending Rules (' + pending.length + ')'),
|
|
E('div', { 'class': 'ta-pending', 'id': 'pending-rules' },
|
|
pending.length === 0 ? E('em', {}, 'No pending rules') :
|
|
pending.map(function(rule) {
|
|
return E('div', { 'class': 'ta-pending-item' }, [
|
|
E('span', {}, rule.type + ' - ' + (rule.created || '').substring(0, 10)),
|
|
E('div', {}, [
|
|
E('button', {
|
|
'class': 'ta-btn ta-btn-success',
|
|
'style': 'padding: 4px 8px; margin-right: 4px;',
|
|
'click': function() { self.approveRule(rule.id); }
|
|
}, 'Approve'),
|
|
E('button', {
|
|
'class': 'ta-btn ta-btn-warning',
|
|
'style': 'padding: 4px 8px;',
|
|
'click': function() { self.rejectRule(rule.id); }
|
|
}, 'Reject')
|
|
])
|
|
]);
|
|
})
|
|
)
|
|
]);
|
|
|
|
var chatCard = E('div', { 'class': 'ta-card ta-chat' }, [
|
|
E('h3', {}, 'AI Security Chat'),
|
|
E('div', { 'class': 'ta-chat-messages', 'id': 'chat-messages' },
|
|
E('div', { 'class': 'ta-message ai' }, [
|
|
E('div', { 'class': 'ta-message-bubble' }, 'Hello! I\'m your SecuBox Threat Analyst. Ask me about security threats, or request filter rules for mitmproxy, CrowdSec, or WAF.')
|
|
])
|
|
),
|
|
E('div', { 'class': 'ta-chat-input' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'chat-input',
|
|
'placeholder': 'Ask about threats or request rules...',
|
|
'keypress': function(e) { if (e.key === 'Enter') self.sendChat(); }
|
|
}),
|
|
E('button', { 'click': function() { self.sendChat(); } }, 'Send')
|
|
])
|
|
]);
|
|
|
|
var threatsCard = E('div', { 'class': 'ta-card ta-threats' }, [
|
|
E('h3', {}, 'Recent Threats'),
|
|
E('table', { 'class': 'ta-threats-table' }, [
|
|
E('thead', {}, [
|
|
E('tr', {}, [
|
|
E('th', {}, 'Time'),
|
|
E('th', {}, 'Source'),
|
|
E('th', {}, 'Scenario'),
|
|
E('th', {}, 'IP'),
|
|
E('th', {}, 'Severity')
|
|
])
|
|
]),
|
|
E('tbody', { 'id': 'threats-body' },
|
|
threats.slice(0, 10).map(function(t) {
|
|
var severity = 'medium';
|
|
if (t.scenario && (t.scenario.includes('malware') || t.scenario.includes('exploit'))) severity = 'critical';
|
|
else if (t.scenario && t.scenario.includes('scan')) severity = 'high';
|
|
else if (t.scenario && t.scenario.includes('http')) severity = 'low';
|
|
|
|
return E('tr', {}, [
|
|
E('td', {}, (t.created_at || '').substring(11, 19)),
|
|
E('td', {}, (t.source || {}).ip || '-'),
|
|
E('td', {}, t.scenario || '-'),
|
|
E('td', {}, (t.source || {}).ip || '-'),
|
|
E('td', {}, E('span', { 'class': 'ta-badge ' + severity }, severity))
|
|
]);
|
|
})
|
|
)
|
|
])
|
|
]);
|
|
|
|
return E('div', {}, [
|
|
style,
|
|
E('h2', {}, 'Threat Analyst'),
|
|
E('div', { 'class': 'ta-dashboard' }, [
|
|
statusCard,
|
|
pendingCard,
|
|
chatCard,
|
|
threatsCard
|
|
])
|
|
]);
|
|
},
|
|
|
|
sendChat: function() {
|
|
var input = document.getElementById('chat-input');
|
|
var messages = document.getElementById('chat-messages');
|
|
var message = input.value.trim();
|
|
var self = this;
|
|
|
|
if (!message) return;
|
|
|
|
// Add user message
|
|
messages.appendChild(E('div', { 'class': 'ta-message user' }, [
|
|
E('div', { 'class': 'ta-message-bubble' }, message),
|
|
E('div', { 'class': 'ta-message-time' }, new Date().toLocaleTimeString())
|
|
]));
|
|
|
|
input.value = '';
|
|
messages.scrollTop = messages.scrollHeight;
|
|
|
|
// Add loading indicator
|
|
var loading = E('div', { 'class': 'ta-message ai', 'id': 'chat-loading' }, [
|
|
E('div', { 'class': 'ta-message-bubble' }, 'Analyzing...')
|
|
]);
|
|
messages.appendChild(loading);
|
|
|
|
// Call API
|
|
api.chat(message).then(function(result) {
|
|
var loadingEl = document.getElementById('chat-loading');
|
|
if (loadingEl) loadingEl.remove();
|
|
|
|
var response = result.response || result.error || 'No response';
|
|
|
|
messages.appendChild(E('div', { 'class': 'ta-message ai' }, [
|
|
E('div', { 'class': 'ta-message-bubble' }, response),
|
|
E('div', { 'class': 'ta-message-time' }, new Date().toLocaleTimeString())
|
|
]));
|
|
messages.scrollTop = messages.scrollHeight;
|
|
}).catch(function(err) {
|
|
var loadingEl = document.getElementById('chat-loading');
|
|
if (loadingEl) loadingEl.remove();
|
|
|
|
messages.appendChild(E('div', { 'class': 'ta-message ai' }, [
|
|
E('div', { 'class': 'ta-message-bubble' }, 'Error: ' + (err.message || 'Request failed'))
|
|
]));
|
|
});
|
|
},
|
|
|
|
runCycle: function() {
|
|
ui.showModal('Running Analysis', [
|
|
E('p', { 'class': 'spinning' }, 'Running threat analysis cycle...')
|
|
]);
|
|
|
|
api.runCycle().then(function() {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', {}, 'Analysis cycle started'), 'success');
|
|
});
|
|
},
|
|
|
|
generateRules: function(target) {
|
|
ui.showModal('Generating Rules', [
|
|
E('p', { 'class': 'spinning' }, 'Generating ' + target + ' rules with AI...')
|
|
]);
|
|
|
|
api.generateRules(target).then(function(result) {
|
|
ui.hideModal();
|
|
if (result.rules) {
|
|
ui.addNotification(null, E('p', {}, 'Rules generated. Check pending queue.'), 'success');
|
|
window.location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', {}, 'Failed to generate rules'), 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
approveRule: function(id) {
|
|
var self = this;
|
|
api.approveRule(id).then(function(result) {
|
|
if (result.success) {
|
|
ui.addNotification(null, E('p', {}, 'Rule approved and applied'), 'success');
|
|
window.location.reload();
|
|
}
|
|
});
|
|
},
|
|
|
|
rejectRule: function(id) {
|
|
api.rejectRule(id).then(function() {
|
|
ui.addNotification(null, E('p', {}, 'Rule rejected'), 'info');
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|