const esc = (value) => String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') // Numeric entities keep German text intact in every supported mail client. .replace(/[^\x20-\x7e]/g, (character) => `&#${character.codePointAt(0)};`); const formatDate = (value) => new Date(value).toLocaleString('de-DE', { timeZone: 'Europe/Berlin', dateStyle: 'medium', timeStyle: 'short' }); const now = new Date(); const periodEnd = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); periodEnd.setUTCDate(periodEnd.getUTCDate() - ((periodEnd.getUTCDay() + 6) % 7)); const periodStart = new Date(periodEnd.getTime() - 7 * 86400000); const groups = new Map(); for (const item of items) { const payload = item.json.payload || {}; const ninja = payload.NinjaOne || payload.ninjaOne || {}; const organizationId = String(ninja.OrganizationId || ninja.organizationId || 'unknown'); const organizationName = String(ninja.OrganizationName || ninja.organizationName || `Organisation ${organizationId}`); if (!groups.has(organizationId)) { groups.set(organizationId, { organizationId, organizationName, devices: [] }); } groups.get(organizationId).devices.push({ machineName: item.json.machine_name || payload.MachineName || 'Unbekannt', state: String(item.json.alert_state || payload.AlertState || 'unknown').toLowerCase(), payload }); } const reportItems = []; for (const group of groups.values()) { let warningCount = 0; let criticalCount = 0; let totalEvents = 0; let cveTotal = 0; let cveCritical = 0; const uniqueIps = new Set(); const alertRows = []; const cleanRows = []; for (const device of group.devices.sort((left, right) => left.machineName.localeCompare(right.machineName))) { if (device.state === 'warning') warningCount++; if (device.state === 'critical') criticalCount++; const vulnerabilities = device.payload.VulnerabilityCorrelation || {}; cveTotal += Number(vulnerabilities.TotalCount || 0); cveCritical += Number(vulnerabilities.CriticalCount || 0); const events = (device.payload.Events || []).filter((event) => { const timestamp = new Date(event.Timestamp); return !Number.isNaN(timestamp) && timestamp >= periodStart && timestamp < periodEnd; }); totalEvents += events.length; if (events.length === 0) { cleanRows.push(`${esc(device.machineName)}Keine sicherheitsrelevanten Ereignisse im Berichtszeitraum.Sauber`); continue; } const groupedEvents = new Map(); for (const event of events) { const sourceIp = event.SourceIp || '-'; const account = event.Username || '-'; const type = event.Target || 'Sicherheitsereignis'; const key = [type, account, sourceIp].join('|'); const row = groupedEvents.get(key) || { type, account, sourceIp, count: 0, latest: event.Timestamp }; row.count++; if (new Date(event.Timestamp) > new Date(row.latest)) row.latest = event.Timestamp; groupedEvents.set(key, row); if (sourceIp !== '-') uniqueIps.add(sourceIp); } for (const event of groupedEvents.values()) { const severity = device.state === 'critical' ? 'Kritisch' : 'Pruefen'; const badge = device.state === 'critical' ? 'badge-critical' : 'badge-warning'; alertRows.push(`${esc(device.machineName)}${esc(event.type)}${esc(event.account)}${esc(formatDate(event.latest))}${event.count}${esc(event.sourceIp)}${severity}`); } } const summary = { deviceCount: group.devices.length, warningCount, criticalCount, totalEvents, uniqueIps: uniqueIps.size, cveTotal, cveCritical }; const riskLabel = criticalCount > 0 ? 'Kritisch' : warningCount > 0 ? 'Beobachten' : 'Unauffaellig'; const riskClass = criticalCount > 0 ? 'risk-critical' : warningCount > 0 ? 'risk-warning' : 'risk-ok'; const rows = alertRows.length > 0 ? `${alertRows.join('')}${cleanRows.join('')}` : cleanRows.join(''); const reportHtml = `
${riskLabel}

OfficeCom Sentinel Sicherheitsbericht

${esc(group.organizationName)} | ${esc(periodStart.toLocaleDateString('de-DE'))} bis ${esc(periodEnd.toLocaleDateString('de-DE'))}
${summary.deviceCount}Geräte${summary.criticalCount}Kritisch${summary.warningCount}Warnungen${summary.totalEvents}Ereignisse${summary.uniqueIps}Quell-IP-Adressen${summary.cveCritical}Kritische CVEs
${rows}
SystemVorfallKontoLetzter ZeitpunktAnzahlQuell-IPBewertung
`; reportItems.push({ json: { organizationId: group.organizationId, organizationName: group.organizationName, periodStartUtc: periodStart.toISOString(), periodEndUtc: periodEnd.toISOString(), ...summary, reportHtml, summaryJson: JSON.stringify(summary) } }); } return reportItems;