feat(ai-insights): Add CVE feed panel to dashboard
- Add get_cve_feed RPCD method fetching from NVD API - Add CVE feed panel showing recent vulnerabilities with CVSS scores - Cache CVE feed for 30 minutes to reduce API calls - Link CVE IDs to NVD detail pages - Color-code severity (critical/high/medium/low) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4f0e7d7cc4
commit
040b69ad1d
@ -47,6 +47,13 @@ var callAnalyze = rpc.declare({
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callGetCVEFeed = rpc.declare({
|
||||
object: 'luci.ai-insights',
|
||||
method: 'get_cve_feed',
|
||||
params: ['limit'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
function getPostureColor(score) {
|
||||
if (score >= 80) return 'success';
|
||||
if (score >= 60) return 'warning';
|
||||
@ -104,6 +111,7 @@ return baseclass.extend({
|
||||
getTimeline: callGetTimeline,
|
||||
runAll: callRunAll,
|
||||
analyze: callAnalyze,
|
||||
getCVEFeed: callGetCVEFeed,
|
||||
|
||||
getPostureColor: getPostureColor,
|
||||
getPostureLabel: getPostureLabel,
|
||||
|
||||
@ -12,12 +12,21 @@
|
||||
*/
|
||||
|
||||
return view.extend({
|
||||
cveCache: [],
|
||||
|
||||
load: function() {
|
||||
var self = this;
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = L.resource('ai-insights/dashboard.css');
|
||||
document.head.appendChild(link);
|
||||
return api.getOverview().catch(function() { return {}; });
|
||||
return Promise.all([
|
||||
api.getOverview().catch(function() { return {}; }),
|
||||
api.getCVEFeed(10).catch(function() { return { cves: [] }; })
|
||||
]).then(function(results) {
|
||||
self.cveCache = (results[1] || {}).cves || [];
|
||||
return results[0];
|
||||
});
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
@ -68,6 +77,15 @@ return view.extend({
|
||||
E('span', { 'class': 'ai-badge' }, String(alerts.length))
|
||||
]),
|
||||
E('div', { 'class': 'ai-card-body', 'id': 'ai-alerts' }, this.renderAlerts(alerts))
|
||||
]),
|
||||
|
||||
// CVE Feed card
|
||||
E('div', { 'class': 'ai-card' }, [
|
||||
E('div', { 'class': 'ai-card-header' }, [
|
||||
'⚠️ CVE Feed (Recent)',
|
||||
E('span', { 'class': 'ai-badge warning' }, String(this.cveCache.length))
|
||||
]),
|
||||
E('div', { 'class': 'ai-card-body', 'id': 'ai-cves' }, this.renderCVEs(this.cveCache))
|
||||
])
|
||||
]);
|
||||
|
||||
@ -159,6 +177,32 @@ return view.extend({
|
||||
}));
|
||||
},
|
||||
|
||||
renderCVEs: function(cves) {
|
||||
if (!cves || !cves.length) {
|
||||
return E('div', { 'class': 'ai-empty' }, 'Loading CVE feed...');
|
||||
}
|
||||
|
||||
return E('div', { 'class': 'ai-cve-list' }, cves.slice(0, 10).map(function(cve) {
|
||||
var score = cve.score || 0;
|
||||
var severity = score >= 9.0 ? 'critical' : score >= 7.0 ? 'high' : score >= 4.0 ? 'medium' : 'low';
|
||||
var severityClass = score >= 9.0 ? 'danger' : score >= 7.0 ? 'warning' : score >= 4.0 ? 'caution' : 'success';
|
||||
|
||||
return E('div', { 'class': 'ai-cve-item' }, [
|
||||
E('div', { 'class': 'ai-cve-score ' + severityClass }, [
|
||||
E('span', { 'class': 'score-value' }, score.toFixed(1)),
|
||||
E('span', { 'class': 'score-label' }, severity.toUpperCase())
|
||||
]),
|
||||
E('div', { 'class': 'ai-cve-content' }, [
|
||||
E('div', { 'class': 'ai-cve-id' }, [
|
||||
E('a', { 'href': 'https://nvd.nist.gov/vuln/detail/' + cve.id, 'target': '_blank' }, cve.id)
|
||||
]),
|
||||
E('div', { 'class': 'ai-cve-desc' }, (cve.description || '').substring(0, 120) + '...')
|
||||
]),
|
||||
E('div', { 'class': 'ai-cve-date' }, cve.published || '')
|
||||
]);
|
||||
}));
|
||||
},
|
||||
|
||||
runAllAgents: function() {
|
||||
ui.showModal('Running Agents', [
|
||||
E('p', { 'class': 'spinning' }, 'Starting all AI agents...')
|
||||
|
||||
@ -104,6 +104,7 @@ case "$1" in
|
||||
"get_alerts": {"limit": 50},
|
||||
"get_posture": {},
|
||||
"get_timeline": {"hours": 24},
|
||||
"get_cve_feed": {"limit": 10},
|
||||
"run_all": {},
|
||||
"analyze": {}
|
||||
}
|
||||
@ -268,6 +269,60 @@ EOF
|
||||
echo "$results"
|
||||
;;
|
||||
|
||||
get_cve_feed)
|
||||
read -r input
|
||||
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
|
||||
[ -z "$limit" ] && limit=10
|
||||
|
||||
# Cache file for CVE feed (refresh every 30 min)
|
||||
cache_file="/tmp/cve_feed_cache.json"
|
||||
cache_age=1800
|
||||
|
||||
# Check cache
|
||||
if [ -f "$cache_file" ]; then
|
||||
age=$(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0)))
|
||||
if [ "$age" -lt "$cache_age" ]; then
|
||||
cat "$cache_file"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fetch from NVD API (last 3 days)
|
||||
start_date=$(date -d "3 days ago" +%Y-%m-%dT00:00:00.000 2>/dev/null || date -u +%Y-%m-%dT00:00:00.000)
|
||||
end_date=$(date +%Y-%m-%dT23:59:59.999 2>/dev/null || date -u +%Y-%m-%dT23:59:59.999)
|
||||
|
||||
response=$(curl -s --max-time 15 \
|
||||
"https://services.nvd.nist.gov/rest/json/cves/2.0?pubStartDate=${start_date}&pubEndDate=${end_date}&resultsPerPage=${limit}" 2>/dev/null)
|
||||
|
||||
if [ -n "$response" ]; then
|
||||
# Parse and format CVEs
|
||||
json_init
|
||||
json_add_array "cves"
|
||||
|
||||
echo "$response" | jsonfilter -e '@.vulnerabilities[*]' 2>/dev/null | while read -r vuln; do
|
||||
cve_id=$(echo "$vuln" | jsonfilter -e '@.cve.id' 2>/dev/null)
|
||||
desc=$(echo "$vuln" | jsonfilter -e '@.cve.descriptions[0].value' 2>/dev/null | head -c 200 | sed 's/"/\\"/g')
|
||||
score=$(echo "$vuln" | jsonfilter -e '@.cve.metrics.cvssMetricV31[0].cvssData.baseScore' 2>/dev/null)
|
||||
[ -z "$score" ] && score=$(echo "$vuln" | jsonfilter -e '@.cve.metrics.cvssMetricV2[0].cvssData.baseScore' 2>/dev/null)
|
||||
[ -z "$score" ] && score="0"
|
||||
published=$(echo "$vuln" | jsonfilter -e '@.cve.published' 2>/dev/null | cut -c1-10)
|
||||
|
||||
json_add_object ""
|
||||
json_add_string "id" "$cve_id"
|
||||
json_add_string "description" "$desc"
|
||||
json_add_double "score" "$score"
|
||||
json_add_string "published" "$published"
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
json_dump > "$cache_file"
|
||||
cat "$cache_file"
|
||||
else
|
||||
echo '{"cves":[],"error":"Failed to fetch CVE feed"}'
|
||||
fi
|
||||
;;
|
||||
|
||||
analyze)
|
||||
# Get AI security analysis
|
||||
localai_url=$(uci -q get localrecall.main.localai_url || echo "http://127.0.0.1:8091")
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Grant access to AI Insights Dashboard",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.ai-insights": ["status", "get_alerts", "get_posture", "get_timeline"]
|
||||
"luci.ai-insights": ["status", "get_alerts", "get_posture", "get_timeline", "get_cve_feed"]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user