diff --git a/infra/n8n/ocsentinel-weekly-organization-reports-outlook.json b/infra/n8n/ocsentinel-weekly-organization-reports-outlook.json
index e19e50e..a7df6d3 100644
--- a/infra/n8n/ocsentinel-weekly-organization-reports-outlook.json
+++ b/infra/n8n/ocsentinel-weekly-organization-reports-outlook.json
@@ -49,7 +49,7 @@
},
{
"parameters": {
- "jsCode": "const esc = (value) => String(value ?? '')\n .replace(/&/g, '&').replace(//g, '>')\n .replace(/\"/g, '"').replace(/'/g, ''')\n .replace(/[^\\x20-\\x7e]/g, (character) => `${character.codePointAt(0)};`);\nconst now = new Date();\nconst end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));\nend.setUTCDate(end.getUTCDate() - ((end.getUTCDay() + 6) % 7));\nconst start = new Date(end.getTime() - 7 * 86400000);\nconst formatDate = (value) => new Date(value).toLocaleString('de-DE', { timeZone: 'Europe/Berlin', dateStyle: 'medium', timeStyle: 'short' });\nconst groups = new Map();\nfor (const item of items) {\n const payload = item.json.payload || {}, ninja = payload.NinjaOne || payload.ninjaOne || {};\n const id = String(ninja.OrganizationId || ninja.organizationId || 'unknown');\n if (!groups.has(id)) groups.set(id, { id, name: String(ninja.OrganizationName || ninja.organizationName || `Organisation ${id}`), devices: [] });\n groups.get(id).devices.push({ name: item.json.machine_name || payload.MachineName || 'Unbekannt', state: String(item.json.alert_state || payload.AlertState || 'unknown').toLowerCase(), payload });\n}\nconst output = [];\nfor (const group of groups.values()) {\n let warnings = 0, criticals = 0, totalEvents = 0, cveCritical = 0;\n const ips = new Set(), rows = [];\n for (const device of group.devices.sort((a, b) => a.name.localeCompare(b.name))) {\n if (device.state === 'warning') warnings++;\n if (device.state === 'critical') criticals++;\n cveCritical += Number((device.payload.VulnerabilityCorrelation || {}).CriticalCount || 0);\n const events = (device.payload.Events || []).filter((event) => { const timestamp = new Date(event.Timestamp); return !Number.isNaN(timestamp) && timestamp >= start && timestamp < end; });\n totalEvents += events.length;\n if (!events.length) { rows.push(`
| ${esc(device.name)} | Keine sicherheitsrelevanten Ereignisse im Berichtszeitraum. | Sauber |
`); continue; }\n const grouped = new Map();\n for (const event of events) {\n const ip = event.SourceIp || '-', account = event.Username || '-', type = event.Target || 'Sicherheitsereignis', key = [type, account, ip].join('|');\n const entry = grouped.get(key) || { ip, account, type, count: 0, latest: event.Timestamp };\n entry.count++; if (new Date(event.Timestamp) > new Date(entry.latest)) entry.latest = event.Timestamp; grouped.set(key, entry); if (ip !== '-') ips.add(ip);\n }\n for (const event of grouped.values()) { const critical = device.state === 'critical'; rows.push(`| ${esc(device.name)} | ${esc(event.type)} | ${esc(event.account)} | ${esc(formatDate(event.latest))} | ${event.count} | ${esc(event.ip)} | ${critical ? 'Kritisch' : 'Pruefen'} |
`); }\n }\n const risk = criticals ? ['Kritisch', '#991b1b', '#fee2e2'] : warnings ? ['Beobachten', '#92400e', '#fef3c7'] : ['Unauffaellig', '#166534', '#dcfce7'];\n const metric = (value, label) => `${value}${label} | `;\n const html = `${risk[0]}OfficeCom Sentinel Sicherheitsbericht${esc(group.name)} | ${esc(start.toLocaleDateString('de-DE'))} bis ${esc(end.toLocaleDateString('de-DE'))} |
${metric(group.devices.length, 'Geräte')}${metric(criticals, 'Kritisch')}${metric(warnings, 'Warnungen')}${metric(totalEvents, 'Ereignisse')}${metric(ips.size, 'Quell-IP-Adressen')}${metric(cveCritical, 'Kritische CVEs')}
| System | Vorfall | Konto | Letzter Zeitpunkt | Anzahl | Quell-IP | Bewertung | ${rows.join('')}
Automatisch erstellt am ${esc(formatDate(now))} durch OfficeCom Sentinel. |
`;\n output.push({ json: { organizationId: group.id, organizationName: group.name, periodStartUtc: start.toISOString(), periodEndUtc: end.toISOString(), deviceCount: group.devices.length, warningCount: warnings, criticalCount: criticals, totalEvents, uniqueIps: ips.size, cveTotal: 0, cveCritical, reportHtml: html, summaryJson: JSON.stringify({ deviceCount: group.devices.length, warningCount: warnings, criticalCount: criticals, totalEvents, uniqueIps: ips.size, cveCritical }) } });\n}\nreturn output;\n"
+ "jsCode": "const esc = (value) => String(value ?? '')\n .replace(/&/g, '&').replace(//g, '>')\n .replace(/\"/g, '"').replace(/'/g, ''')\n .replace(/[^\\x20-\\x7e]/g, (character) => `${character.codePointAt(0)};`);\nconst now = new Date();\nconst end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));\nend.setUTCDate(end.getUTCDate() - ((end.getUTCDay() + 6) % 7));\nconst start = new Date(end.getTime() - 7 * 86400000);\nconst formatDate = (value) => new Date(value).toLocaleString('de-DE', { timeZone: 'Europe/Berlin', dateStyle: 'medium', timeStyle: 'short' });\nconst groups = new Map();\nfor (const item of items) {\n const payload = item.json.payload || {}, ninja = payload.NinjaOne || payload.ninjaOne || {};\n const id = String(ninja.OrganizationId || ninja.organizationId || 'unknown');\n if (!groups.has(id)) groups.set(id, { id, name: String(ninja.OrganizationName || ninja.organizationName || `Organisation ${id}`), devices: [] });\n groups.get(id).devices.push({ name: item.json.machine_name || payload.MachineName || 'Unbekannt', state: String(item.json.alert_state || payload.AlertState || 'unknown').toLowerCase(), payload });\n}\nconst output = [];\nfor (const group of groups.values()) {\n let warnings = 0, criticals = 0, totalEvents = 0, cveCritical = 0;\n const ips = new Set(), rows = [];\n for (const device of group.devices.sort((a, b) => a.name.localeCompare(b.name))) {\n if (device.state === 'warning') warnings++;\n if (device.state === 'critical') criticals++;\n cveCritical += Number((device.payload.VulnerabilityCorrelation || {}).CriticalCount || 0);\n const events = (device.payload.Events || []).filter((event) => { const timestamp = new Date(event.Timestamp); return !Number.isNaN(timestamp) && timestamp >= start && timestamp < end; });\n totalEvents += events.length;\n if (!events.length) { rows.push(`| ${esc(device.name)} | Keine sicherheitsrelevanten Ereignisse im Berichtszeitraum. | Sauber |
`); continue; }\n const grouped = new Map();\n for (const event of events) {\n const ip = event.SourceIp || '-', account = event.Username || '-', type = event.Target || 'Sicherheitsereignis', key = [type, account, ip].join('|');\n const entry = grouped.get(key) || { ip, account, type, count: 0, latest: event.Timestamp };\n entry.count++; if (new Date(event.Timestamp) > new Date(entry.latest)) entry.latest = event.Timestamp; grouped.set(key, entry); if (ip !== '-') ips.add(ip);\n }\n for (const event of grouped.values()) { const critical = device.state === 'critical'; rows.push(`| ${esc(device.name)} | ${esc(event.type)} | ${esc(event.account)} | ${esc(formatDate(event.latest))} | ${event.count} | ${esc(event.ip)} | ${critical ? 'Kritisch' : 'Pruefen'} |
`); }\n }\n const risk = criticals ? ['Kritisch', '#991b1b', '#fee2e2'] : warnings ? ['Beobachten', '#92400e', '#fef3c7'] : ['Unauffaellig', '#166534', '#dcfce7'];\n const metric = (value, label) => `${value} ${label} | `;\n const html = `OfficeCom Sentinel Sicherheitsbericht${esc(group.name)} | ${esc(start.toLocaleDateString('de-DE'))} bis ${esc(end.toLocaleDateString('de-DE'))} | ${risk[0]} |
|
${metric(group.devices.length, 'Geräte')}${metric(criticals, 'Kritisch')}${metric(warnings, 'Warnungen')}${metric(totalEvents, 'Ereignisse')}${metric(ips.size, 'Quell-IP-Adressen')}${metric(cveCritical, 'Kritische CVEs')}
| System | Vorfall | Konto | Letzter Zeitpunkt | Anzahl | Quell-IP | Bewertung | ${rows.join('')}
Automatisch erstellt am ${esc(formatDate(now))} durch OfficeCom Sentinel. |
`;\n output.push({ json: { organizationId: group.id, organizationName: group.name, periodStartUtc: start.toISOString(), periodEndUtc: end.toISOString(), deviceCount: group.devices.length, warningCount: warnings, criticalCount: criticals, totalEvents, uniqueIps: ips.size, cveTotal: 0, cveCritical, reportHtml: html, summaryJson: JSON.stringify({ deviceCount: group.devices.length, warningCount: warnings, criticalCount: criticals, totalEvents, uniqueIps: ips.size, cveCritical }) } });\n}\nreturn output;\n"
},
"id": "build-weekly-reports",
"name": "Build Organization HTML Reports",
diff --git a/infra/n8n/weekly-organization-report-outlook.js b/infra/n8n/weekly-organization-report-outlook.js
index bd84e8b..f31a93c 100644
--- a/infra/n8n/weekly-organization-report-outlook.js
+++ b/infra/n8n/weekly-organization-report-outlook.js
@@ -34,8 +34,8 @@ for (const group of groups.values()) {
for (const event of grouped.values()) { const critical = device.state === 'critical'; rows.push(`| ${esc(device.name)} | ${esc(event.type)} | ${esc(event.account)} | ${esc(formatDate(event.latest))} | ${event.count} | ${esc(event.ip)} | ${critical ? 'Kritisch' : 'Pruefen'} |
`); }
}
const risk = criticals ? ['Kritisch', '#991b1b', '#fee2e2'] : warnings ? ['Beobachten', '#92400e', '#fef3c7'] : ['Unauffaellig', '#166534', '#dcfce7'];
- const metric = (value, label) => `${value}${label} | `;
- const html = `${risk[0]}OfficeCom Sentinel Sicherheitsbericht${esc(group.name)} | ${esc(start.toLocaleDateString('de-DE'))} bis ${esc(end.toLocaleDateString('de-DE'))} |
${metric(group.devices.length, 'Geräte')}${metric(criticals, 'Kritisch')}${metric(warnings, 'Warnungen')}${metric(totalEvents, 'Ereignisse')}${metric(ips.size, 'Quell-IP-Adressen')}${metric(cveCritical, 'Kritische CVEs')}
| System | Vorfall | Konto | Letzter Zeitpunkt | Anzahl | Quell-IP | Bewertung | ${rows.join('')}
Automatisch erstellt am ${esc(formatDate(now))} durch OfficeCom Sentinel. |
`;
+ const metric = (value, label) => `${value} ${label} | `;
+ const html = `OfficeCom Sentinel Sicherheitsbericht${esc(group.name)} | ${esc(start.toLocaleDateString('de-DE'))} bis ${esc(end.toLocaleDateString('de-DE'))} | ${risk[0]} |
|
${metric(group.devices.length, 'Geräte')}${metric(criticals, 'Kritisch')}${metric(warnings, 'Warnungen')}${metric(totalEvents, 'Ereignisse')}${metric(ips.size, 'Quell-IP-Adressen')}${metric(cveCritical, 'Kritische CVEs')}
| System | Vorfall | Konto | Letzter Zeitpunkt | Anzahl | Quell-IP | Bewertung | ${rows.join('')}
Automatisch erstellt am ${esc(formatDate(now))} durch OfficeCom Sentinel. |
`;
output.push({ json: { organizationId: group.id, organizationName: group.name, periodStartUtc: start.toISOString(), periodEndUtc: end.toISOString(), deviceCount: group.devices.length, warningCount: warnings, criticalCount: criticals, totalEvents, uniqueIps: ips.size, cveTotal: 0, cveCritical, reportHtml: html, summaryJson: JSON.stringify({ deviceCount: group.devices.length, warningCount: warnings, criticalCount: criticals, totalEvents, uniqueIps: ips.size, cveCritical }) } });
}
return output;