Add: WM Football Theme für TV Dashboard + Live CVEs aus NVD NIST API
- TVDashboardPage.jsx komplett auf WM Matchday Theme umgestellt (1:1 Design) - Fußballplatz-Hintergrund (PitchBackground mit SVG-Linien), rollender Ball - Scoreboard-Header in allen 8 Slides mit LIVE-Indikator - Jersey-Zahlen (count-up, lime/gold/rot), Confetti bei Clean Sheet / Backup ≥95% - FBTile ersetzt BTile, WM-Metaphern: Anstoss, Aufstellung, Fitnesstest, Fairplay, Zu Null, Spielaufbau, Abwehr, Gegner-Analyse - Neuer Backend-Endpoint GET /api/tv/cves: fetcht live von NVD NIST API, 1h In-Memory-Cache - CVE-Slide zeigt echte aktuelle Sicherheitslücken aus dem Internet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,52 @@
|
||||
const { getDatabase } = require('../config/database');
|
||||
const https = require('https');
|
||||
|
||||
// In-memory CVE cache — refresh every hour
|
||||
let cveCache = { ts: 0, data: [] };
|
||||
|
||||
function fetchCVEs() {
|
||||
return new Promise((resolve) => {
|
||||
const opts = {
|
||||
hostname: 'services.nvd.nist.gov',
|
||||
path: '/rest/json/cves/2.0?resultsPerPage=8&sortBy=published&sortOrder=desc',
|
||||
headers: { 'User-Agent': 'IT-Nexus-TV/1.0' },
|
||||
timeout: 8000,
|
||||
};
|
||||
const req = https.get(opts, (res) => {
|
||||
let raw = '';
|
||||
res.on('data', c => raw += c);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(raw);
|
||||
const items = (json.vulnerabilities || []).map(v => {
|
||||
const cve = v.cve;
|
||||
const m31 = cve.metrics?.cvssMetricV31 || [];
|
||||
const m30 = cve.metrics?.cvssMetricV30 || [];
|
||||
const m = m31[0] || m30[0];
|
||||
const score = m?.cvssData?.baseScore || 0;
|
||||
const sevRaw = (m?.cvssData?.baseSeverity || 'medium').toLowerCase();
|
||||
const sev = ['critical','high','medium','low'].includes(sevRaw) ? sevRaw : 'medium';
|
||||
const desc = cve.descriptions?.find(d => d.lang === 'en')?.value || '';
|
||||
const firstSentence = desc.split(/\.\s/)[0].replace(/\s+/g, ' ').trim();
|
||||
const title = firstSentence.length > 110 ? firstSentence.substring(0, 110) + '…' : firstSentence || cve.id;
|
||||
const pub = new Date(cve.published);
|
||||
const diffH = Math.round((Date.now() - pub.getTime()) / 3600000);
|
||||
const published = diffH < 1 ? '< 1 Std.' : diffH < 24 ? `${diffH} Std.` : `${Math.round(diffH / 24)} Tag${Math.round(diffH / 24) !== 1 ? 'e' : ''}`;
|
||||
// Vendor from CPE or references
|
||||
let vendor = 'NVD';
|
||||
const cpe = cve.configurations?.[0]?.nodes?.[0]?.cpeMatch?.[0]?.criteria || '';
|
||||
const m2 = cpe.match(/cpe:2\.3:[ao]:([^:]+):/);
|
||||
if (m2) vendor = m2[1].charAt(0).toUpperCase() + m2[1].slice(1).replace(/_/g,' ');
|
||||
return { id: cve.id, sev, cvss: score, title, vendor, published };
|
||||
}).filter(c => c.cvss >= 5).slice(0, 5);
|
||||
resolve(items.length >= 3 ? items : null);
|
||||
} catch { resolve(null); }
|
||||
});
|
||||
});
|
||||
req.on('error', () => resolve(null));
|
||||
req.on('timeout', () => { req.destroy(); resolve(null); });
|
||||
});
|
||||
}
|
||||
|
||||
async function getStats(req, res) {
|
||||
try {
|
||||
@@ -73,4 +121,20 @@ async function getStats(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getStats };
|
||||
async function getCVEs(req, res) {
|
||||
const now = Date.now();
|
||||
if (now - cveCache.ts < 3600000 && cveCache.data.length >= 3) {
|
||||
return res.json({ status: 'success', data: cveCache.data, cached: true });
|
||||
}
|
||||
const fresh = await fetchCVEs();
|
||||
if (fresh) {
|
||||
cveCache = { ts: now, data: fresh };
|
||||
return res.json({ status: 'success', data: fresh, cached: false });
|
||||
}
|
||||
if (cveCache.data.length >= 3) {
|
||||
return res.json({ status: 'success', data: cveCache.data, cached: true });
|
||||
}
|
||||
res.status(503).json({ status: 'error', message: 'CVE-Feed nicht erreichbar' });
|
||||
}
|
||||
|
||||
module.exports = { getStats, getCVEs };
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { getStats } = require('../controllers/tv.controller');
|
||||
const { getStats, getCVEs } = require('../controllers/tv.controller');
|
||||
|
||||
router.get('/stats', getStats);
|
||||
router.get('/cves', getCVEs);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user