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 = `
| ${summary.deviceCount}Geräte | ${summary.criticalCount}Kritisch | ${summary.warningCount}Warnungen | ${summary.totalEvents}Ereignisse | ${summary.uniqueIps}Quell-IP-Adressen | ${summary.cveCritical}Kritische CVEs |
| System | Vorfall | Konto | Letzter Zeitpunkt | Anzahl | Quell-IP | Bewertung |
${rows}
`;
reportItems.push({
json: {
organizationId: group.organizationId,
organizationName: group.organizationName,
periodStartUtc: periodStart.toISOString(),
periodEndUtc: periodEnd.toISOString(),
...summary,
reportHtml,
summaryJson: JSON.stringify(summary)
}
});
}
return reportItems;