{ "id": "OCwRpt7eK3mQ2xL9", "name": "OCSentinel - Weekly Organization Reports", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "weeks", "weeksInterval": 1, "triggerAtDay": [ 1 ], "triggerAtHour": 7, "triggerAtMinute": 20 } ] } }, "id": "schedule-weekly-reports", "name": "Every Monday 07:20", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.3, "position": [ 300, 300 ] }, { "parameters": { "operation": "executeQuery", "query": "WITH latest AS (\n SELECT DISTINCT ON (d.id) d.machine_name, r.alert_state, r.payload\n FROM ocsentinel.scan_report AS r\n JOIN ocsentinel.device AS d ON d.id = r.device_id\n WHERE r.received_at >= now() - interval '8 days'\n ORDER BY d.id, r.generated_at_utc DESC, r.received_at DESC\n)\nSELECT machine_name, alert_state, payload\nFROM latest\nORDER BY payload #>> '{NinjaOne,OrganizationName}', machine_name;" }, "id": "load-weekly-data", "name": "Load Latest Device Reports", "type": "n8n-nodes-base.postgres", "typeVersion": 2.5, "position": [ 560, 300 ], "credentials": { "postgres": { "id": "WkjY0kIF3kHvREys", "name": "OCSentinel PostgreSQL" } } }, { "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(`
${risk[0]}OfficeCom Sentinel Sicherheitsbericht${esc(group.name)} | ${esc(start.toLocaleDateString('de-DE'))} bis ${esc(end.toLocaleDateString('de-DE'))} | |||||||
Automatisch erstellt am ${esc(formatDate(now))} durch OfficeCom Sentinel. |