diff --git a/Node-RED-Flow-Code.json b/Node-RED-Flow-Code.json new file mode 100644 index 0000000..f4926b4 --- /dev/null +++ b/Node-RED-Flow-Code.json @@ -0,0 +1,257 @@ +[ + { + "id": "vpn_final_v4", + "type": "tab", + "label": "VPN Dashboard V4", + "disabled": false, + "info": "" + }, + { + "id": "node_http_in", + "type": "http in", + "z": "vpn_final_v4", + "name": "", + "url": "/vpn-status", + "method": "post", + "upload": false, + "swaggerDoc": "", + "x": 120, + "y": 120, + "wires": [ + [ + "node_logic", + "node_http_res", + "58624bbd43ed4aa5" + ] + ] + }, + { + "id": "node_http_res", + "type": "http response", + "z": "vpn_final_v4", + "name": "", + "statusCode": "200", + "headers": {}, + "x": 340, + "y": 80, + "wires": [] + }, + { + "id": "node_logic", + "type": "function", + "z": "vpn_final_v4", + "name": "Status-Zentrale", + "func": "// Status-Zentrale Function-Node (V7 - No-Crash Edition)\nvar vpnData = global.get('vpnData') || {};\n\n// 1. Reset-Logik (muss ganz oben stehen)\nif (msg.topic === \"reset\") {\n vpnData = {};\n global.set('vpnData', vpnData);\n msg.payload = [];\n return msg;\n}\n\n// 2. Sicherheits-Check: Ist überhaupt eine Payload da?\nif (msg.payload !== null && typeof msg.payload === 'object') {\n\n // Nur verarbeiten, wenn die Pflichtfelder existieren\n if (msg.payload.server && msg.payload.client) {\n\n var s = msg.payload.server;\n var c = msg.payload.client;\n var st = msg.payload.status;\n var hs = msg.payload.handshake || \"\";\n var time = new Date().toLocaleTimeString('de-DE');\n\n let isOnline = (st === true || st === 'true');\n\n // Key generieren (ohne Sonderzeichen)\n var normalizedClient = c.replace(/[^a-zA-Z0-9._-]/g, \"\");\n var key = s + \"_\" + normalizedClient;\n\n // Status-Text zusammenbauen\n let statusText = isOnline ? \"🟢 Online (\" + hs + \")\" : \"🔴 Offline (\" + hs + \")\";\n if (hs === \"never\") { statusText = \"🔴 Offline (Nie)\"; }\n\n // Daten in den Speicher schreiben (Keys exakt wie in deiner ui-table!)\n vpnData[key] = {\n \"Server\": s,\n \"VPN-Client\": c,\n \"Status\": statusText,\n \"Letztes_Update\": time\n };\n\n // Global speichern\n global.set('vpnData', vpnData);\n }\n}\n\n// 3. Ausgabe für die Tabelle (Immer als Array!)\nmsg.payload = Object.values(vpnData);\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 360, + "y": 160, + "wires": [ + [ + "node_ui_table", + "2124bcb8672a7c64" + ] + ] + }, + { + "id": "node_ui_table", + "type": "ui-table", + "z": "vpn_final_v4", + "group": "node_ui_group", + "name": "VPN Übersicht", + "label": "", + "order": 1, + "width": "0", + "height": "0", + "maxrows": "", + "autocols": false, + "showSearch": true, + "deselect": true, + "selectionType": "none", + "columns": [ + { + "title": "Server", + "key": "Server", + "keyType": "key", + "type": "text", + "width": "", + "align": "start" + }, + { + "title": "VPN-Client", + "key": "VPN-Client", + "keyType": "key", + "type": "text", + "width": "", + "align": "start" + }, + { + "title": "Status", + "key": "Status", + "keyType": "key", + "type": "text", + "width": "", + "align": "start" + } + ], + "mobileBreakpoint": "sm", + "mobileBreakpointType": "defaults", + "action": "replace", + "className": "", + "x": 600, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "node_inject_test", + "type": "inject", + "z": "vpn_final_v4", + "name": "Test-Daten", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "{\"server\":\"TestNode\",\"client\":\"vpn-test\",\"status\":\"true\"}", + "x": 130, + "y": 200, + "wires": [ + [ + "node_logic" + ] + ] + }, + { + "id": "2124bcb8672a7c64", + "type": "debug", + "z": "vpn_final_v4", + "name": "debug 1", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 600, + "y": 260, + "wires": [] + }, + { + "id": "58624bbd43ed4aa5", + "type": "debug", + "z": "vpn_final_v4", + "name": "debug 2", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 340, + "y": 40, + "wires": [] + }, + { + "id": "node_ui_group", + "type": "ui-group", + "name": "..:..", + "page": "node_ui_page", + "width": "12", + "height": "1", + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "node_ui_page", + "type": "ui-page", + "name": "NETMAKER VPN Client Status", + "ui": "db2_base", + "path": "/vpn", + "icon": "", + "layout": "grid", + "theme": "b752cb4ed9bda6eb", + "breakpoints": [ + { + "name": "Default", + "px": "0", + "cols": "3" + }, + { + "name": "Tablet", + "px": "576", + "cols": "6" + }, + { + "name": "Small Desktop", + "px": "768", + "cols": "9" + }, + { + "name": "Desktop", + "px": "1024", + "cols": "12" + } + ], + "order": 1, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "db2_base", + "type": "ui-base", + "name": "Dashboard 2.0", + "path": "/dashboard", + "includeClientData": true, + "acceptsClientConfig": [ + "ui-control", + "ui-notification" + ] + }, + { + "id": "b752cb4ed9bda6eb", + "type": "ui-theme", + "name": "Standardthema", + "colors": { + "surface": "#737373", + "primary": "#0094ce", + "bgPage": "#eeeeee", + "groupBg": "#ffffff", + "groupOutline": "#cccccc" + }, + "sizes": { + "density": "comfortable", + "pagePadding": "12px", + "groupGap": "12px", + "groupBorderRadius": "4px", + "widgetGap": "12px" + } + }, + { + "id": "180348698317a147", + "type": "global-config", + "env": [], + "modules": { + "@flowfuse/node-red-dashboard": "1.30.2" + } + } +] \ No newline at end of file