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 { 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) {
|
async function getStats(req, res) {
|
||||||
try {
|
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 express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { getStats } = require('../controllers/tv.controller');
|
const { getStats, getCVEs } = require('../controllers/tv.controller');
|
||||||
|
|
||||||
router.get('/stats', getStats);
|
router.get('/stats', getStats);
|
||||||
|
router.get('/cves', getCVEs);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user