sync: Windmill-State übernehmen + neue Reporting-Flows
- Dateien nach Windmill-Naming-Konvention umbenannt (ssh-key_aus_db_testen, flow-fehler_per_nextcloud_talk_melden, bitwarden_(fallback)) - testpause-Schritt aus flow.yaml entfernt (Debugging abgeschlossen) - Neue Flows: f/Reporting/exchange_logins, f/Reporting/run_sql_events - mail_to_talk: Dateinamen nach Windmill-Konvention synchronisiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,18 +49,19 @@ value:
|
||||
summary: SSH-Key aus DB testen
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline ssh_key_versuch.py'
|
||||
content: '!inline ssh-key_aus_db_testen.py'
|
||||
input_transforms:
|
||||
prev:
|
||||
type: javascript
|
||||
expr: results.b
|
||||
lock: '!inline ssh_key_versuch.lock'
|
||||
lock: '!inline ssh-key_aus_db_testen.lock'
|
||||
language: python3
|
||||
- id: g
|
||||
summary: SSH-Credentials fuer alle Restore-Server aus Bitwarden (Fallback)
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline ssh-credentials_fuer_alle_restore-server_aus_bitwarden.py'
|
||||
content: '!inline
|
||||
ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).py'
|
||||
input_transforms:
|
||||
bw_url:
|
||||
type: static
|
||||
@@ -68,7 +69,8 @@ value:
|
||||
prev:
|
||||
type: javascript
|
||||
expr: results.i
|
||||
lock: '!inline ssh-credentials_fuer_alle_restore-server_aus_bitwarden.lock'
|
||||
lock: '!inline
|
||||
ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).lock'
|
||||
language: python3
|
||||
- id: c
|
||||
summary: Script deployen & PBS-Datastores auf allen Servern registrieren
|
||||
@@ -130,7 +132,7 @@ value:
|
||||
summary: Flow-Fehler per Nextcloud Talk melden
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline flow_fehler_handler.py'
|
||||
content: '!inline flow-fehler_per_nextcloud_talk_melden.py'
|
||||
input_transforms:
|
||||
error:
|
||||
type: javascript
|
||||
@@ -138,7 +140,7 @@ value:
|
||||
flow_input:
|
||||
type: javascript
|
||||
expr: flow_input
|
||||
lock: '!inline flow_fehler_handler.lock'
|
||||
lock: '!inline flow-fehler_per_nextcloud_talk_melden.lock'
|
||||
language: python3
|
||||
schema:
|
||||
$schema: https://json-schema.org/draft/2020-12/schema
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# py: 3.12
|
||||
anyio==4.12.1
|
||||
bcrypt==5.0.0
|
||||
certifi==2026.2.25
|
||||
cffi==2.0.0
|
||||
cryptography==46.0.5
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httpx==0.28.1
|
||||
idna==3.11
|
||||
invoke==2.2.1
|
||||
mysql-connector-python==9.6.0
|
||||
paramiko==4.0.0
|
||||
pycparser==3.0
|
||||
pynacl==1.6.2
|
||||
typing-extensions==4.15.0
|
||||
wmill==1.657.2
|
||||
@@ -1,23 +0,0 @@
|
||||
def main(prev: dict):
|
||||
print("=" * 60)
|
||||
print("TESTPAUSE — Flow stoppt hier. Ergebnisse:")
|
||||
print("=" * 60)
|
||||
|
||||
server_creds = prev.get("server_creds", {})
|
||||
needs_bitwarden = prev.get("needs_bitwarden", [])
|
||||
servers = prev.get("target_servers", [])
|
||||
|
||||
print(f"\nServer gesamt: {[s['hostname'] for s in servers]}")
|
||||
print(f"needs_bitwarden: {needs_bitwarden}")
|
||||
print(f"\nAuthentifizierung:")
|
||||
for hostname, creds in server_creds.items():
|
||||
method = creds.get("auth_method", "password")
|
||||
user = creds.get("username", "?")
|
||||
print(f" {hostname}: {method} ({user})")
|
||||
|
||||
for hostname in needs_bitwarden:
|
||||
if hostname not in server_creds:
|
||||
print(f" {hostname}: FEHLT — weder Key noch Bitwarden!")
|
||||
|
||||
print("=" * 60)
|
||||
return prev
|
||||
@@ -1,2 +1,6 @@
|
||||
summary: Proxmox
|
||||
description: ''
|
||||
display_name: Proxmox
|
||||
extra_perms:
|
||||
sebastianserfling@stines.de: true
|
||||
owners:
|
||||
- sebastianserfling@stines.de
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"mysql2": "latest"
|
||||
}
|
||||
}
|
||||
//bun.lock
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"mysql2": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="],
|
||||
|
||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="],
|
||||
|
||||
"mysql2": ["mysql2@3.21.0", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-CYNKIuhnalXHTa4gonZ+KhzLESKllvo1qQIDYUVuatpN4NgMk+lsA3WwHYno5AS4PACUiD2qEmiVD9pr3bXWOw=="],
|
||||
|
||||
"named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"mysql2": "latest"
|
||||
}
|
||||
}
|
||||
//bun.lock
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"mysql2": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="],
|
||||
|
||||
"mysql2": ["mysql2@3.22.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA=="],
|
||||
|
||||
"named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
summary: ''
|
||||
description: ''
|
||||
lock: '!inline
|
||||
f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen.script.lock'
|
||||
kind: script
|
||||
schema:
|
||||
$schema: https://json-schema.org/draft/2020-12/schema
|
||||
type: object
|
||||
properties:
|
||||
database:
|
||||
type: object
|
||||
description: ''
|
||||
default: null
|
||||
format: resource-mysql
|
||||
properties:
|
||||
database:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
host:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
password:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
port:
|
||||
type: number
|
||||
description: ''
|
||||
user:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
record:
|
||||
type: object
|
||||
description: ''
|
||||
default: null
|
||||
format: resource-login_record
|
||||
properties:
|
||||
ipaddress:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
lastlogon:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
memberof:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
username:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
required:
|
||||
- database
|
||||
- record
|
||||
@@ -0,0 +1,39 @@
|
||||
type Mysql = {
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
password: string;
|
||||
database: string;
|
||||
};
|
||||
|
||||
type LoginRecord = {
|
||||
username: string;
|
||||
lastaccess: string;
|
||||
ipaddress: string;
|
||||
memberof: string;
|
||||
};
|
||||
|
||||
export async function main(
|
||||
database: Mysql,
|
||||
record: LoginRecord
|
||||
): Promise<{ inserted: boolean }> {
|
||||
const mysql2 = await import("mysql2/promise");
|
||||
|
||||
const conn = await mysql2.createConnection({
|
||||
host: database.host,
|
||||
port: database.port,
|
||||
user: database.user,
|
||||
password: database.password,
|
||||
database: database.database,
|
||||
});
|
||||
|
||||
try {
|
||||
await conn.execute(
|
||||
"INSERT INTO `bronze.services.reporting` (username, lastaccess, ipaddress, add_date, memberof) VALUES (?, ?, ?, NOW(), ?)",
|
||||
[record.username, record.lastaccess, record.ipaddress, record.memberof]
|
||||
);
|
||||
return { inserted: true };
|
||||
} finally {
|
||||
await conn.end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"mysql2": "latest"
|
||||
}
|
||||
}
|
||||
//bun.lock
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"mysql2": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="],
|
||||
|
||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="],
|
||||
|
||||
"mysql2": ["mysql2@3.21.0", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-CYNKIuhnalXHTa4gonZ+KhzLESKllvo1qQIDYUVuatpN4NgMk+lsA3WwHYno5AS4PACUiD2qEmiVD9pr3bXWOw=="],
|
||||
|
||||
"named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
type Mysql = {
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
password: string;
|
||||
database: string;
|
||||
};
|
||||
|
||||
type RportClient = {
|
||||
rport_client_id: string;
|
||||
hostname: string;
|
||||
ipaddress: string;
|
||||
};
|
||||
|
||||
export async function main(
|
||||
database: Mysql,
|
||||
rportio_base_url: string,
|
||||
rportio_username: string,
|
||||
rportio_api_token: string
|
||||
): Promise<RportClient[]> {
|
||||
const mysql2 = await import("mysql2/promise");
|
||||
|
||||
// 1. Query MySQL for all active Exchange servers
|
||||
const conn = await mysql2.createConnection({
|
||||
host: database.host,
|
||||
port: database.port,
|
||||
user: database.user,
|
||||
password: database.password,
|
||||
database: database.database,
|
||||
});
|
||||
|
||||
const [rows] = await conn.execute(
|
||||
"SELECT hostname, privat_ipaddress FROM `bronze.server` WHERE services LIKE '%EX%' AND (disable_date IS NULL OR disable_date > NOW())"
|
||||
);
|
||||
await conn.end();
|
||||
|
||||
const dbServers = rows as Array<{ hostname: string; privat_ipaddress: string }>;
|
||||
if (dbServers.length === 0) return [];
|
||||
|
||||
// Build lookup maps for fast matching (hostname → DB row)
|
||||
const byHostname = new Map(dbServers.map((s) => [s.hostname.toLowerCase(), s]));
|
||||
const byIp = new Map(dbServers.map((s) => [s.privat_ipaddress, s]));
|
||||
|
||||
// 2. Query rport.io for all connected clients
|
||||
const auth = Buffer.from(`${rportio_username}:${rportio_api_token}`).toString("base64");
|
||||
const headers = {
|
||||
Authorization: `Basic ${auth}`,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
// @ts-ignore - Bun-specific TLS option to allow self-signed certificates
|
||||
const resp = await fetch(
|
||||
`${rportio_base_url}/api/v1/clients?filter[connection_state]=connected&fields[clients]=id,name,hostname,ipv4&page[limit]=500`,
|
||||
{ headers, tls: { rejectUnauthorized: false } }
|
||||
);
|
||||
|
||||
if (!resp.ok) {
|
||||
const err = await resp.text();
|
||||
throw new Error(`rport.io clients list failed [${resp.status}]: ${err}`);
|
||||
}
|
||||
|
||||
const data = await resp.json();
|
||||
const clients = data?.data ?? [];
|
||||
|
||||
// 3. Match rport.io clients against DB server list (hostname or IP)
|
||||
const matched: RportClient[] = [];
|
||||
for (const client of clients) {
|
||||
const rportHostname = (client.hostname ?? "").toLowerCase();
|
||||
const rportIps: string[] = client.ipv4 ?? [];
|
||||
|
||||
const dbRow = byHostname.get(rportHostname)
|
||||
?? rportIps.map((ip) => byIp.get(ip)).find(Boolean);
|
||||
|
||||
if (dbRow) {
|
||||
matched.push({
|
||||
rport_client_id: client.id,
|
||||
hostname: client.hostname ?? client.name,
|
||||
ipaddress: dbRow.privat_ipaddress,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Found ${dbServers.length} EX servers in DB, ${clients.length} connected rport.io clients, ${matched.length} matched`
|
||||
);
|
||||
|
||||
return matched;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"mysql2": "latest"
|
||||
}
|
||||
}
|
||||
//bun.lock
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"mysql2": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="],
|
||||
|
||||
"mysql2": ["mysql2@3.22.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.2", "long": "^5.3.2", "lru.min": "^1.1.4", "named-placeholders": "^1.1.6", "sql-escaper": "^1.3.3" }, "peerDependencies": { "@types/node": ">= 8" } }, "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA=="],
|
||||
|
||||
"named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sql-escaper": ["sql-escaper@1.3.3", "", {}, "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
summary: ''
|
||||
description: ''
|
||||
lock: '!inline f/Reporting/exchange_logins__flow/ex-server_mit_rport.script.lock'
|
||||
kind: script
|
||||
schema:
|
||||
$schema: https://json-schema.org/draft/2020-12/schema
|
||||
type: object
|
||||
properties:
|
||||
database:
|
||||
type: object
|
||||
description: ''
|
||||
default: null
|
||||
format: resource-mysql
|
||||
properties:
|
||||
database:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
host:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
password:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
port:
|
||||
type: number
|
||||
description: ''
|
||||
user:
|
||||
type: string
|
||||
description: ''
|
||||
originalType: string
|
||||
rportio_api_token:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
rportio_base_url:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
rportio_username:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
required:
|
||||
- database
|
||||
- rportio_base_url
|
||||
- rportio_username
|
||||
- rportio_api_token
|
||||
@@ -0,0 +1,91 @@
|
||||
summary: Exchange Logins Collector
|
||||
description: >
|
||||
Lädt alle aktiven EX-Server aus bronze.server, gleicht sie mit den
|
||||
verbundenen rport.io Clients ab (per Hostname oder IP), fragt auf jedem
|
||||
Server per PowerShell die AD-Gruppe G-Exchange-User ab (username ohne
|
||||
E-Mail, lastlogon, ipaddress vom Server) und schreibt die Ergebnisse in
|
||||
bronze.services.reporting.
|
||||
value:
|
||||
modules:
|
||||
- id: find_ex_clients
|
||||
summary: EX-Server mit rport.io Clients abgleichen
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline ex-server_mit_rport.io_clients_abgleichen.ts'
|
||||
input_transforms:
|
||||
database:
|
||||
type: static
|
||||
value: $res:u/sebastianserfling/fascinating_mysql
|
||||
rportio_api_token:
|
||||
type: static
|
||||
value: $var:f/Reporting/rportio_api_token
|
||||
rportio_base_url:
|
||||
type: static
|
||||
value: $var:f/Reporting/rportio_base_url
|
||||
rportio_username:
|
||||
type: static
|
||||
value: $var:f/Reporting/rportio_username
|
||||
lock: '!inline ex-server_mit_rport.io_clients_abgleichen.lock'
|
||||
language: bun
|
||||
- id: process_servers
|
||||
summary: Pro Server Logins sammeln und speichern
|
||||
value:
|
||||
type: forloopflow
|
||||
modules:
|
||||
- id: execute_ps
|
||||
summary: PowerShell via rport.io ausführen
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline powershell_via_rport.io_ausführen.ts'
|
||||
input_transforms:
|
||||
client_id:
|
||||
type: javascript
|
||||
expr: flow_input.iter.value.rport_client_id
|
||||
rportio_api_token:
|
||||
type: static
|
||||
value: $var:f/Reporting/rportio_api_token
|
||||
rportio_base_url:
|
||||
type: static
|
||||
value: $var:f/Reporting/rportio_base_url
|
||||
rportio_username:
|
||||
type: static
|
||||
value: $var:f/Reporting/rportio_username
|
||||
server_ip:
|
||||
type: javascript
|
||||
expr: flow_input.iter.value.ipaddress
|
||||
lock: '!inline powershell_via_rport.io_ausführen.lock'
|
||||
language: bun
|
||||
- id: insert_logins
|
||||
summary: Login-Einträge in MySQL speichern
|
||||
value:
|
||||
type: forloopflow
|
||||
modules:
|
||||
- id: insert_login
|
||||
summary: Einzelnen Login-Eintrag einfügen
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline einzelnen_login-eintrag_einfügen.ts'
|
||||
input_transforms:
|
||||
database:
|
||||
type: static
|
||||
value: $res:u/sebastianserfling/fascinating_mysql
|
||||
record:
|
||||
type: javascript
|
||||
expr: flow_input.iter.value
|
||||
lock: '!inline einzelnen_login-eintrag_einfügen.lock'
|
||||
language: bun
|
||||
iterator:
|
||||
type: javascript
|
||||
expr: results.execute_ps
|
||||
parallel: false
|
||||
skip_failures: false
|
||||
iterator:
|
||||
type: javascript
|
||||
expr: results.find_ex_clients
|
||||
parallel: false
|
||||
skip_failures: true
|
||||
schema:
|
||||
$schema: https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties: {}
|
||||
required: []
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {}
|
||||
}
|
||||
//bun.lock
|
||||
<empty>
|
||||
@@ -0,0 +1,119 @@
|
||||
type LoginRecord = {
|
||||
username: string;
|
||||
lastaccess: string;
|
||||
ipaddress: string;
|
||||
memberof: string;
|
||||
};
|
||||
|
||||
export async function main(
|
||||
rportio_base_url: string,
|
||||
rportio_username: string,
|
||||
rportio_api_token: string,
|
||||
client_id: string,
|
||||
server_ip: string
|
||||
): Promise<LoginRecord[]> {
|
||||
const psScript = `
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$groupName = 'G-Exchange-User'
|
||||
$serverIp = '${server_ip}'
|
||||
$firstOfMonth = (Get-Date -Day 1).ToString('yyyy-MM-dd 00:00:00')
|
||||
|
||||
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction SilentlyContinue
|
||||
|
||||
$members = @()
|
||||
try {
|
||||
$members = Get-ADGroupMember -Identity $groupName -Recursive -ErrorAction Stop |
|
||||
Where-Object { $_.objectClass -eq 'user' }
|
||||
} catch {}
|
||||
|
||||
$result = [System.Collections.Generic.List[object]]::new()
|
||||
foreach ($m in $members) {
|
||||
$mailbox = $null
|
||||
try {
|
||||
$mailbox = Get-Mailbox -Identity $m.SamAccountName -RecipientTypeDetails UserMailbox -ErrorAction Stop
|
||||
} catch { continue }
|
||||
|
||||
$lastaccess = $firstOfMonth
|
||||
try {
|
||||
$stats = Get-MailboxStatistics -Identity $mailbox.Identity -ErrorAction Stop
|
||||
if ($stats.LastLogonTime) {
|
||||
$lastaccess = $stats.LastLogonTime.ToString('yyyy-MM-dd HH:mm:ss')
|
||||
}
|
||||
} catch {}
|
||||
|
||||
$result.Add([PSCustomObject]@{
|
||||
username = $mailbox.SamAccountName
|
||||
lastaccess = $lastaccess
|
||||
ipaddress = $serverIp
|
||||
memberof = $groupName
|
||||
})
|
||||
}
|
||||
if ($result.Count -eq 0) { Write-Output '[]' } else { $result | ConvertTo-Json -Depth 3 -Compress }
|
||||
`.trim();
|
||||
|
||||
// rport.io powershell interpreter executes the script directly as PS code
|
||||
const command = psScript;
|
||||
|
||||
const auth = Buffer.from(`${rportio_username}:${rportio_api_token}`).toString("base64");
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Basic ${auth}`,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const tlsOpts = { tls: { rejectUnauthorized: false } };
|
||||
|
||||
// Submit command to rport.io
|
||||
// @ts-ignore - Bun-specific TLS option for self-signed certificates
|
||||
const execResp = await fetch(
|
||||
`${rportio_base_url}/api/v1/clients/${client_id}/commands`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ command, interpreter: "powershell", timeout_sec: 120 }),
|
||||
...tlsOpts,
|
||||
}
|
||||
);
|
||||
if (!execResp.ok) {
|
||||
const text = await execResp.text();
|
||||
throw new Error(`rport.io execute failed [${execResp.status}]: ${text}`);
|
||||
}
|
||||
const execData = await execResp.json();
|
||||
const jid: string = execData?.data?.jid;
|
||||
if (!jid) throw new Error(`No job ID from rport.io: ${JSON.stringify(execData)}`);
|
||||
|
||||
// Poll until finished (max 120s)
|
||||
let cmdResult: Record<string, unknown> | null = null;
|
||||
for (let i = 0; i < 60; i++) {
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
// @ts-ignore - Bun-specific TLS option
|
||||
const statusResp = await fetch(
|
||||
`${rportio_base_url}/api/v1/clients/${client_id}/commands/${jid}`,
|
||||
{ headers, ...tlsOpts }
|
||||
);
|
||||
if (!statusResp.ok) continue;
|
||||
const statusData = await statusResp.json();
|
||||
const cmd = statusData?.data as Record<string, unknown>;
|
||||
if (cmd?.finished_at) {
|
||||
cmdResult = cmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cmdResult) throw new Error("Timeout waiting for rport.io command result");
|
||||
|
||||
const status = cmdResult.status as string;
|
||||
if (status === "failed" || status === "unknown") {
|
||||
const result = cmdResult.result as Record<string, string> ?? {};
|
||||
throw new Error(`PowerShell failed [${status}]: ${cmdResult.error ?? result.stderr ?? ""}`);
|
||||
}
|
||||
|
||||
const result = cmdResult.result as Record<string, string> ?? {};
|
||||
const stdout = (result.stdout ?? "").trim();
|
||||
if (!stdout || stdout === "[]") return [];
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(stdout);
|
||||
return Array.isArray(parsed) ? parsed : [parsed];
|
||||
} catch {
|
||||
throw new Error(`Failed to parse PowerShell JSON output: ${stdout}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {}
|
||||
}
|
||||
//bun.lock
|
||||
<empty>
|
||||
@@ -0,0 +1,39 @@
|
||||
summary: ''
|
||||
description: ''
|
||||
lock: '!inline f/Reporting/exchange_logins__flow/powershell_via_rport.script.lock'
|
||||
kind: script
|
||||
schema:
|
||||
$schema: https://json-schema.org/draft/2020-12/schema
|
||||
type: object
|
||||
properties:
|
||||
client_id:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
rportio_api_token:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
rportio_base_url:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
rportio_username:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
server_ip:
|
||||
type: string
|
||||
description: ''
|
||||
default: null
|
||||
originalType: string
|
||||
required:
|
||||
- rportio_base_url
|
||||
- rportio_username
|
||||
- rportio_api_token
|
||||
- client_id
|
||||
- server_ip
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {}
|
||||
}
|
||||
//bun.lock
|
||||
<empty>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
export async function main(results_array: any[], event_name: string = 'all_create', subject_prefix: string = '') {
|
||||
const total = Array.isArray(results_array) ? results_array.length : 0
|
||||
const failed = Array.isArray(results_array) ? results_array.filter((r: any) => r && r.success === false).length : 0
|
||||
const succeeded = total - failed
|
||||
const subject = `${subject_prefix ? subject_prefix + ' ' : ''}Ergebnisse für Event ${event_name}: ${succeeded}/${total} erfolgreich`
|
||||
const lines: string[] = []
|
||||
lines.push(`Event: ${event_name}`)
|
||||
lines.push(`Erfolgreich: ${succeeded}/${total}`)
|
||||
lines.push('')
|
||||
for (let i = 0; i < total; i++) {
|
||||
const r: any = results_array[i]
|
||||
if (!r) continue
|
||||
lines.push(`CALL ${i + 1}: ${r.call}`)
|
||||
if (r.success) {
|
||||
let snippet = ''
|
||||
try {
|
||||
snippet = JSON.stringify(r.result)
|
||||
if (snippet.length > 1000) snippet = snippet.slice(0, 1000) + '...'
|
||||
} catch { snippet = '[unserialisierbar]' }
|
||||
lines.push(' Status: OK')
|
||||
lines.push(` Result: ${snippet}`)
|
||||
} else {
|
||||
lines.push(' Status: FEHLER')
|
||||
lines.push(` Error: ${r.error}`)
|
||||
}
|
||||
lines.push('')
|
||||
}
|
||||
const body = lines.join('\n')
|
||||
return { subject, body }
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {}
|
||||
}
|
||||
//bun.lock
|
||||
<empty>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
export async function main(event_definition: string) {
|
||||
if (!event_definition || typeof event_definition !== 'string') return []
|
||||
// Some MySQL store event body as full CREATE EVENT ... DO ...; we only need inside DO ...
|
||||
let def = event_definition
|
||||
const doMatch = def.match(/\bDO\s+(BEGIN[\s\S]*END|[^;]+);?/i)
|
||||
if (doMatch) {
|
||||
def = doMatch[1]
|
||||
}
|
||||
let body = def
|
||||
.replace(/DELIMITER\s+[^\n]+/gi, ' ')
|
||||
.replace(/\bBEGIN\b/gi, ' ')
|
||||
.replace(/\bEND\b/gi, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
|
||||
const regex = /CALL\s+[^;]+;/gi
|
||||
const matches = body.match(regex) || []
|
||||
return matches.map((s) => s.trim())
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {}
|
||||
}
|
||||
//bun.lock
|
||||
<empty>
|
||||
@@ -0,0 +1,21 @@
|
||||
export async function main(rows: any) {
|
||||
// Normalize possible hub script return shapes
|
||||
let arr: any[] = []
|
||||
if (Array.isArray(rows)) arr = rows
|
||||
else if (rows && Array.isArray(rows.rows)) arr = rows.rows
|
||||
else if (rows && Array.isArray(rows.result)) arr = rows.result
|
||||
else if (rows && Array.isArray(rows.data)) arr = rows.data
|
||||
|
||||
let def = ''
|
||||
try {
|
||||
if (arr.length > 0) {
|
||||
const r = arr[0]
|
||||
def = r.EVENT_DEFINITION || r.event_definition || ''
|
||||
if (!def && r.EVENT_BODY) {
|
||||
// Some MySQL variants expose body differently; just in case
|
||||
def = r.EVENT_BODY
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return { event_definition: def, row_count: arr.length }
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
summary: Run SQL Events for Reporting
|
||||
description: ''
|
||||
value:
|
||||
modules:
|
||||
- id: get_event_def_rows
|
||||
summary: Liest EVENT_DEFINITION für das MySQL-Event (Zeilen vom Hubskript)
|
||||
value:
|
||||
type: script
|
||||
input_transforms:
|
||||
mysql_conn:
|
||||
type: static
|
||||
value: $res:u/sebastianserfling/fascinating_mysql
|
||||
query:
|
||||
type: javascript
|
||||
expr: "`SELECT EVENT_DEFINITION FROM information_schema.EVENTS WHERE EVENT_NAME
|
||||
= '${flow_input.event_name ?? 'all_create'}' ORDER BY
|
||||
(EVENT_SCHEMA = DATABASE()) DESC LIMIT 1`"
|
||||
path: hub/17540/mysql/execute_query
|
||||
- id: extract_event_def
|
||||
summary: Extrahiert EVENT_DEFINITION-String aus den Zeilen
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline extrahiert_event_definition-string_aus_den_zeilen.ts'
|
||||
input_transforms:
|
||||
rows:
|
||||
type: javascript
|
||||
expr: results.get_event_def_rows
|
||||
lock: '!inline extrahiert_event_definition-string_aus_den_zeilen.lock'
|
||||
language: bun
|
||||
- id: parse_calls
|
||||
summary: Extrahiert alle CALL-Statements aus dem EVENT_DEFINITION-Body
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline
|
||||
extrahiert_alle_call-statements_aus_dem_event_definition-body.ts'
|
||||
input_transforms:
|
||||
event_definition:
|
||||
type: javascript
|
||||
expr: results.extract_event_def.event_definition
|
||||
lock: '!inline
|
||||
extrahiert_alle_call-statements_aus_dem_event_definition-body.lock'
|
||||
language: bun
|
||||
- id: loop_calls
|
||||
summary: Führt jede CALL einzeln aus und sendet bei Fehler Pushover
|
||||
value:
|
||||
type: forloopflow
|
||||
modules:
|
||||
- id: run_call
|
||||
summary: Führt eine einzelne CALL aus, sendet bei Fehler Pushover und liefert
|
||||
Ergebnis
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline
|
||||
führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.ts'
|
||||
input_transforms:
|
||||
call_sql:
|
||||
type: javascript
|
||||
expr: flow_input.iter.value
|
||||
database_resource_path:
|
||||
type: static
|
||||
value: $res:u/sebastianserfling/fascinating_mysql
|
||||
pushover_token:
|
||||
type: javascript
|
||||
expr: flow_input.pushover_token
|
||||
pushover_user:
|
||||
type: javascript
|
||||
expr: flow_input.pushover_user
|
||||
lock: '!inline
|
||||
führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.lock'
|
||||
language: bun
|
||||
iterator:
|
||||
type: javascript
|
||||
expr: results.parse_calls || []
|
||||
parallel: false
|
||||
skip_failures: true
|
||||
squash: false
|
||||
- id: compose_email
|
||||
summary: Erstellt Betreff und Body mit allen Ergebnissen der CALLs
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.ts'
|
||||
input_transforms:
|
||||
event_name:
|
||||
type: javascript
|
||||
expr: flow_input.event_name ?? 'all_create'
|
||||
results_array:
|
||||
type: javascript
|
||||
expr: results.loop_calls
|
||||
subject_prefix:
|
||||
type: javascript
|
||||
expr: flow_input.email_subject_prefix ?? ''
|
||||
lock: '!inline erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.lock'
|
||||
language: bun
|
||||
groups: []
|
||||
schema:
|
||||
$schema: https://json-schema.org/draft/2020-12/schema
|
||||
type: object
|
||||
order:
|
||||
- event_name
|
||||
properties:
|
||||
event_name:
|
||||
type: string
|
||||
description: Name des MySQL Events
|
||||
default: all_create
|
||||
required: []
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"windmill-client": "latest"
|
||||
}
|
||||
}
|
||||
//bun.lock
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"windmill-client": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"windmill-client": ["windmill-client@1.614.0", "", {}, "sha512-skpKI9mJXhUdwc9gZbounVclw22HTpXlhCAkGnkEAqys3xkRzA7v8/NY2Xt2WQrWV7GIN4ZGkOAgv2rk1r36hg=="],
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import * as wmill from 'windmill-client'
|
||||
|
||||
export async function main(database_resource_path: string, call_sql: string, pushover_token?: string, pushover_user?: string) {
|
||||
let success = false
|
||||
let result: any = null
|
||||
let errorMsg = ''
|
||||
try {
|
||||
// Pass the resource path string directly so the hub script can resolve it itself
|
||||
const rows = await wmill.runScriptByPath('hub/17540/mysql/execute_query', {
|
||||
mysql_conn: database_resource_path,
|
||||
query: call_sql
|
||||
})
|
||||
success = true
|
||||
result = rows
|
||||
} catch (err: any) {
|
||||
errorMsg = err?.message || String(err)
|
||||
if (pushover_token && pushover_user) {
|
||||
try {
|
||||
const body = new URLSearchParams({
|
||||
token: String(pushover_token),
|
||||
user: String(pushover_user),
|
||||
title: 'MySQL CALL fehlgeschlagen',
|
||||
message: `Fehler bei CALL: ${call_sql}\nFehlermeldung: ${errorMsg}`
|
||||
})
|
||||
await fetch('https://api.pushover.net/1/messages.json', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body
|
||||
})
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return { call: call_sql, success, result, error: success ? null : errorMsg }
|
||||
}
|
||||
@@ -1,73 +1,49 @@
|
||||
summary: E-Mails zu Nextcloud Talk
|
||||
description: >
|
||||
Liest ungelesene E-Mails von zwei IMAP-Konten und sendet Benachrichtigungen
|
||||
an einen Nextcloud Talk Raum. E-Mails werden als gelesen markiert.
|
||||
schema:
|
||||
$schema: "http://json-schema.org/draft-07/schema"
|
||||
type: object
|
||||
properties:
|
||||
imap_config_1:
|
||||
type: object
|
||||
format: resource-imap
|
||||
description: "IMAP Konto 1 (Ressource: f/mail_to_talk/imap_config)"
|
||||
imap_config_2:
|
||||
type: object
|
||||
format: resource-imap
|
||||
description: "IMAP Konto 2 (Ressource: f/mail_to_talk/imap_config_2)"
|
||||
nextcloud_config:
|
||||
type: object
|
||||
format: resource-nextcloud
|
||||
description: "Nextcloud Zugangsdaten (Ressource: f/mail_to_talk/nextcloud_talk_config)"
|
||||
required:
|
||||
- imap_config_1
|
||||
- imap_config_2
|
||||
- nextcloud_config
|
||||
Liest ungelesene E-Mails von zwei IMAP-Konten und sendet Benachrichtigungen an
|
||||
einen Nextcloud Talk Raum. E-Mails werden als gelesen markiert.
|
||||
value:
|
||||
modules:
|
||||
- id: fetch_emails_1
|
||||
summary: Ungelesene E-Mails Konto 1 abrufen
|
||||
value:
|
||||
type: rawscript
|
||||
language: python3
|
||||
content: "!inline fetch_emails.py"
|
||||
content: '!inline ungelesene_e-mails_konto_1_abrufen.py'
|
||||
input_transforms:
|
||||
imap_config:
|
||||
type: javascript
|
||||
expr: flow_input.imap_config_1
|
||||
lock: '!inline ungelesene_e-mails_konto_1_abrufen.lock'
|
||||
language: python3
|
||||
stop_after_if:
|
||||
expr: "false"
|
||||
error_message: null
|
||||
expr: 'false'
|
||||
skip_if_stopped: false
|
||||
|
||||
- id: fetch_emails_2
|
||||
summary: Ungelesene E-Mails Konto 2 abrufen
|
||||
value:
|
||||
type: rawscript
|
||||
language: python3
|
||||
content: "!inline fetch_emails.py"
|
||||
content: '!inline ungelesene_e-mails_konto_2_abrufen.py'
|
||||
input_transforms:
|
||||
imap_config:
|
||||
type: javascript
|
||||
expr: flow_input.imap_config_2
|
||||
lock: '!inline ungelesene_e-mails_konto_2_abrufen.lock'
|
||||
language: python3
|
||||
stop_after_if:
|
||||
expr: "false"
|
||||
error_message: null
|
||||
expr: 'false'
|
||||
skip_if_stopped: false
|
||||
|
||||
- id: send_to_talk
|
||||
summary: Jede E-Mail an Nextcloud Talk senden
|
||||
value:
|
||||
type: forloopflow
|
||||
iterator:
|
||||
type: javascript
|
||||
expr: "[...results.fetch_emails_1, ...results.fetch_emails_2]"
|
||||
skip_failures: false
|
||||
parallel: false
|
||||
modules:
|
||||
- id: send_message
|
||||
summary: Nachricht an Nextcloud Talk senden
|
||||
value:
|
||||
type: rawscript
|
||||
language: python3
|
||||
content: "!inline send_message.py"
|
||||
content: '!inline nachricht_an_nextcloud_talk_senden.py'
|
||||
input_transforms:
|
||||
email:
|
||||
type: javascript
|
||||
@@ -75,3 +51,31 @@ value:
|
||||
nextcloud_config:
|
||||
type: javascript
|
||||
expr: flow_input.nextcloud_config
|
||||
lock: '!inline nachricht_an_nextcloud_talk_senden.lock'
|
||||
language: python3
|
||||
iterator:
|
||||
type: javascript
|
||||
expr: '[...results.fetch_emails_1, ...results.fetch_emails_2]'
|
||||
parallel: false
|
||||
skip_failures: false
|
||||
schema:
|
||||
$schema: http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
imap_config_1:
|
||||
type: object
|
||||
description: 'IMAP Konto 1 (Ressource: f/mail_to_talk/imap_config)'
|
||||
format: resource-imap
|
||||
imap_config_2:
|
||||
type: object
|
||||
description: 'IMAP Konto 2 (Ressource: f/mail_to_talk/imap_config_2)'
|
||||
format: resource-imap
|
||||
nextcloud_config:
|
||||
type: object
|
||||
description: 'Nextcloud Zugangsdaten (Ressource:
|
||||
f/mail_to_talk/nextcloud_talk_config)'
|
||||
format: resource-nextcloud
|
||||
required:
|
||||
- imap_config_1
|
||||
- imap_config_2
|
||||
- nextcloud_config
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# py: 3.12
|
||||
@@ -0,0 +1 @@
|
||||
# py: 3.12
|
||||
@@ -0,0 +1 @@
|
||||
# py: 3.12
|
||||
@@ -0,0 +1,85 @@
|
||||
import imaplib
|
||||
import email
|
||||
from email.header import decode_header
|
||||
from typing import TypedDict, Optional, List
|
||||
from datetime import datetime
|
||||
import email.utils
|
||||
|
||||
|
||||
class imap(TypedDict):
|
||||
host: str
|
||||
port: int
|
||||
user: str
|
||||
password: str
|
||||
mailbox: Optional[str]
|
||||
|
||||
|
||||
def decode_str(value) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
parts = decode_header(value)
|
||||
result = []
|
||||
for part, charset in parts:
|
||||
if isinstance(part, bytes):
|
||||
result.append(part.decode(charset or "utf-8", errors="replace"))
|
||||
else:
|
||||
result.append(part)
|
||||
return "".join(result)
|
||||
|
||||
|
||||
def main(imap_config: imap) -> List[dict]:
|
||||
host = imap_config["host"]
|
||||
port = imap_config["port"]
|
||||
user = imap_config["user"]
|
||||
password = imap_config["password"]
|
||||
mailbox = imap_config.get("mailbox") or "INBOX"
|
||||
|
||||
if port == 993:
|
||||
client = imaplib.IMAP4_SSL(host, port)
|
||||
else:
|
||||
client = imaplib.IMAP4(host, port)
|
||||
|
||||
client.login(user, password)
|
||||
client.select(mailbox, readonly=False)
|
||||
|
||||
# Nur ungelesene E-Mails suchen
|
||||
status, data = client.search(None, "UNSEEN")
|
||||
if status != "OK" or not data[0]:
|
||||
client.logout()
|
||||
return []
|
||||
|
||||
uids = data[0].split()
|
||||
emails = []
|
||||
|
||||
for uid in uids:
|
||||
status, msg_data = client.fetch(uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])")
|
||||
if status != "OK":
|
||||
continue
|
||||
|
||||
raw = msg_data[0][1]
|
||||
msg = email.message_from_bytes(raw)
|
||||
|
||||
subject = decode_str(msg.get("Subject", "(Kein Betreff)"))
|
||||
from_raw = msg.get("From", "Unbekannt")
|
||||
from_addr = decode_str(from_raw)
|
||||
|
||||
date_raw = msg.get("Date", "")
|
||||
try:
|
||||
parsed_date = email.utils.parsedate_to_datetime(date_raw)
|
||||
date_str = parsed_date.strftime("%d.%m.%Y %H:%M:%S")
|
||||
except Exception:
|
||||
date_str = date_raw or "Unbekanntes Datum"
|
||||
|
||||
# Als gelesen markieren
|
||||
client.store(uid, "+FLAGS", "\\Seen")
|
||||
|
||||
emails.append({
|
||||
"subject": subject,
|
||||
"from": from_addr,
|
||||
"date": date_str,
|
||||
"uid": int(uid),
|
||||
"account": user,
|
||||
})
|
||||
|
||||
client.logout()
|
||||
return emails
|
||||
+19
-5
@@ -1,24 +1,38 @@
|
||||
version: v2
|
||||
locks:
|
||||
f/Backup/backup_restore_orchestrator__flow+__flow_hash: 6579d1cc5b707758f5c529c9c0b453405a99cfa90aa14dc5f9fdd4495005be10
|
||||
f/Backup/backup_restore_orchestrator__flow+__flow_hash: 04867048ce98eccbc211d3c2f7309b1a1d17b32cdae32db0ca75edfb11eeb64e
|
||||
f/Backup/backup_restore_orchestrator__flow+aktive_datastores_aus_db_holen.my.sql: 7565dc4d025614f12cc4e54ca92a74d331d920dbd7b6ebee29d4ce3d5a5fe4dc
|
||||
f/Backup/backup_restore_orchestrator__flow+alle_freien_restore-server_holen.py: 2e1b0c1cabe1a48ec69f1633c75aa381bf0484a0f06ec7720652bc4dd0a1c8bb
|
||||
f/Backup/backup_restore_orchestrator__flow+alte_restore-ordner_auf_backup-server_loeschen.py: cc49da8a46103d3efb447cd6171212e95047311ebd350217034b8e87b65b8d09
|
||||
f/Backup/backup_restore_orchestrator__flow+ersten_restore_pro_server_starten.py: b809e5fafa560de53fcbd73fa18801f33920460e83d7affe572809e1cbc45974
|
||||
f/Backup/backup_restore_orchestrator__flow+ersten_restore_pro_server_starten.py: a971ed9dcf02683b291532cf4077a14335472ef50bef9d7debe61a1838ff4e3a
|
||||
f/Backup/backup_restore_orchestrator__flow+flow-fehler_per_nextcloud_talk_melden.py: 990b913603c30753e21e9e501cc6395c6eef207f92a198b86b02436e20fd2795
|
||||
f/Backup/backup_restore_orchestrator__flow+job_initialisieren_&_backup-queue_aus_pbs_aufbauen.py: 31bbfac68741bf617a8ce3fe501e3cb3d74512db8bb7e72055f29e9e0af70be2
|
||||
f/Backup/backup_restore_orchestrator__flow+script_deployen_&_pbs-datastores_auf_allen_servern_registrieren.py: 72cd9c759e2716cb13a0df0f4867af6bbbde1dcd353f25cb2f3dd48d703cd7f1
|
||||
f/Backup/backup_restore_orchestrator__flow+ssh-credentials_fuer_alle_restore-server_aus_bitwarden.py: 231782a10f9e9379f3b11ca2df1db30eecbcc211618ec4cc58bf10b36fd7d870
|
||||
f/Backup/backup_restore_orchestrator__flow+webhook_verarbeiten_&_naechsten_restore_auf_demselben_server_starten.py: 086c41014a56f4aa24abe0233793614005646227e4564e7c310a2ca17331fe28
|
||||
f/Backup/backup_restore_orchestrator__flow+script_deployen_&_pbs-datastores_auf_allen_servern_registrieren.py: b94953d1ba007149e2fb185ba4aad3405e569e66614a59003a91a2044d17f224
|
||||
f/Backup/backup_restore_orchestrator__flow+ssh-credentials_fuer_alle_restore-server_aus_bitwarden_(fallback).py: be5de491d2857f1e2bf5c93d35f59e1e56d6755b02650e163349ae30c89114cf
|
||||
f/Backup/backup_restore_orchestrator__flow+ssh-key_aus_db_testen.py: 14a06d11eee684888f0b80b16b1b73a4487c46ec620abe843fb874deced92a02
|
||||
f/Backup/backup_restore_orchestrator__flow+webhook_verarbeiten_&_naechsten_restore_auf_demselben_server_starten.py: 33075530a84d944744a40476214c7b10549c069320a1af26186c406afb4f20e1
|
||||
f/Backup/backup_restore_report___nextcloud_talk__flow+__flow_hash: f3c1a6a92392d6a7217b07f74799698b6be048e4706f2114f905cdbcf336a176
|
||||
f/Backup/backup_restore_report___nextcloud_talk__flow+letzten_job_aus_db_holen_&_report_zusammenbauen.py: 10fce98d17bacb4c3314e50b9bbd321ce4a407ed27304f52f108088601f12ff5
|
||||
f/Backup/backup_restore_report___nextcloud_talk__flow+nachricht_an_nextcloud_talk_senden.py: eb69eafe6fc5e3012774e1c8ce37b00540c6662e9c7302f915f6562d3dae0c0a
|
||||
f/mail_to_talk/mail_to_talk__flow+__flow_hash: f280641696d7fbf1ec71f348c9d45725814c0b7297aeaf893eea045bcfe23fbb
|
||||
f/mail_to_talk/mail_to_talk__flow+nachricht_an_nextcloud_talk_senden.py: abcc3d48bff32ac2103190fad1c4deb7b40eb3b312ac51bd940559e42e31b78a
|
||||
f/mail_to_talk/mail_to_talk__flow+ungelesene_e-mails_konto_1_abrufen.py: c9ea5af563459e188485d97b47c37719c9f7fa194454b4c3ebee380d180eada7
|
||||
f/mail_to_talk/mail_to_talk__flow+ungelesene_e-mails_konto_2_abrufen.py: c9ea5af563459e188485d97b47c37719c9f7fa194454b4c3ebee380d180eada7
|
||||
f/Proxmox/proxmox_backup_webhook__flow+__flow_hash: 47428b1bccf563b4b976bf2c36410fc018e429711b5d0c2cf77829c26ffea5fa
|
||||
f/Proxmox/proxmox_backup_webhook__flow+in_mysql_speichern.py: 6d2538c237800143595937eb64dcfdc1483e84a888e8c00dbd8820ae4aff95dc
|
||||
f/Proxmox/proxmox_backup_webhook__flow+nachricht_an_nextcloud_talk.py: f2993562a0f827bc8720a49b3246d34f6a0135c0f28845e5495eed5514d2476c
|
||||
f/Proxmox/proxmox_backup_webhook__flow+payload_parsen_&_aufbereiten.py: 563813669ff3fdef34311459bed488fbfd3725ce645ad70b483b4731c05f4e78
|
||||
f/Reporting/exchange_logins__flow/einzelnen_login-eintrag_einfügen: 54ed4946e9da375bbc8876731879af7acc10ecb4bedd381776ee07b681f49689
|
||||
f/Reporting/exchange_logins__flow/ex-server_mit_rport: 1993acb6da2d88cc99cd30e9b98088f21df113d3041de6827897ce527661f39b
|
||||
f/Reporting/exchange_logins__flow/powershell_via_rport: 2bb1e3959830fa8880ed5ebe053253de4da5df4a5aeac4b510fce722d39c9817
|
||||
f/Reporting/rdp_logins__flow+__flow_hash: 08fa7852bd83a57bb43363c7398c43dea022b8a5cdc639cc34456281459a6034
|
||||
f/Reporting/rdp_logins__flow+einzelnen_login-eintrag_einfügen.ts: 37bbfc68c31c7ebf6046b87c66a05996ef5ae38ad24dcb4de152879d969ab6f9
|
||||
f/Reporting/rdp_logins__flow+powershell_via_rport.io_ausführen.ts: fec88de017a5f9566e9cdbd7b4b49ff9c96447629ef211b51d047026aa051726
|
||||
f/Reporting/rdp_logins__flow+rds-server_mit_rport.io_clients_abgleichen.ts: dea12a10507f9dc7357d4ba06b306b921381bbab9a5ed7f95682e6e49787c224
|
||||
f/Reporting/run_sql_events__flow+__flow_hash: ad83045810a5f0c184ddbf066afb3fa80d693473ef3f0f05bb29f6fb27f0522f
|
||||
f/Reporting/run_sql_events__flow+erstellt_betreff_und_body_mit_allen_ergebnissen_der_calls.ts: 532dbb2974448436d6540b670a9bb2683ee953dc3b7038d12ee9cfb09c956442
|
||||
f/Reporting/run_sql_events__flow+extrahiert_alle_call-statements_aus_dem_event_definition-body.ts: e57324d04d0e43fb603ff5ac404acd7c78f517035214b806c396609768a20ea4
|
||||
f/Reporting/run_sql_events__flow+extrahiert_event_definition-string_aus_den_zeilen.ts: 1041df7e4d2c5b17fb2470ee6b865a470e94f20a0aaabb74336f657f0e37d8ab
|
||||
f/Reporting/run_sql_events__flow+führt_eine_einzelne_call_aus,_sendet_bei_fehler_pushover_und_liefert_ergebnis.ts: 06d9872fe89a38571603e6fcfd6597625f15dda3cc688a448be92a7adba78f23
|
||||
f/Server/Bitwarden_Data_Export__flow+__flow_hash: 72c83e1fd73a374808b1be09a46a286ecc4b4eee806141a2570fc1703942cbbc
|
||||
f/Server/Bitwarden_Data_Export__flow+c.py: 90f7b8c407a4632f882bc1051c6b3827d97df336d787d2d1c3b4bcd5f0063db5
|
||||
|
||||
Reference in New Issue
Block a user