mirror of
https://github.com/Jeuners/Logpy-AgentOne.git
synced 2026-09-09 15:02:33 +02:00
feat: Störungswache (pipe.monitor) + Verschlüsselung-at-rest dokumentiert
Dienste-Ampel (Telefon, Verarbeitung, Spracherkennung, Nextcloud) alarmierte bisher nur im offenen Leitstand. pipe/monitor.py laeuft jetzt als eigener Dauerprozess, prueft dieselbe Ampel (neu ausgelagert nach pipe/dienste.py) periodisch und alarmiert bei Stoerung lokal per macOS-Benachrichtigung + Ton (pipe/alarm.py) - unabhaengig davon, ob pipe.watch oder pipe.server laufen. Wird ab jetzt von telefon/starten.sh mitgestartet. Verschluesselung at rest: keine App-eigene Verschluesselung eingebaut, stattdessen im README als Betreiber-Pflicht dokumentiert (FileVault aktivieren) - App verlaesst sich auf Festplattenverschluesselung durch das Betriebssystem. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
This commit is contained in:
parent
be0252c907
commit
6e5fe59c53
8 changed files with 266 additions and 90 deletions
|
|
@ -45,6 +45,14 @@ NEXTCLOUD_ADRESSBUCH=contacts
|
|||
# Anrufe bleiben unangetastet, egal wie alt.
|
||||
LOESCHFRIST_TAGE=0
|
||||
|
||||
# --- Störungswache (pipe.monitor, eigener Prozess, siehe telefon/starten.sh) ---
|
||||
# Prüft die Dienste-Ampel (Telefon, Verarbeitung, Spracherkennung, Nextcloud)
|
||||
# und alarmiert lokal per macOS-Benachrichtigung + Ton, wenn niemand auf den
|
||||
# Leitstand schaut.
|
||||
MONITOR_TAKT_S=60
|
||||
MONITOR_WIEDERHOLUNG_MIN=15
|
||||
MONITOR_TON=Sosumi
|
||||
|
||||
# --- Telefonie (SIP-Zugang der Praxis, leer lassen = keine Telefonannahme) ---
|
||||
SIP_USER=
|
||||
SIP_DOMAIN=sip.plusnet.de
|
||||
|
|
|
|||
46
README.md
46
README.md
|
|
@ -23,8 +23,10 @@ sammelt die Erfahrungswerte, die in dieser Anleitung nicht stehen.
|
|||
1. **Die Pipe** (fertig): Audio → Transkript → Kategorie → Ablage.
|
||||
2. **Telefonie** (steht): FreeSWITCH nimmt über den Plusnet-Trunk an, spielt die
|
||||
Ansage, nimmt auf und legt die Datei im Eingang der Pipe ab.
|
||||
3. **Feinschliff** (teilweise): Löschfristen ✅, Notfall-Alarm ✅ — offen:
|
||||
Monitoring/Alerting bei Diensteausfall, Verschlüsselung at rest.
|
||||
3. **Feinschliff** (teilweise): Löschfristen ✅, Notfall-Alarm ✅,
|
||||
Verschlüsselung at rest ✅ (Betreiber-Pflicht: FileVault, siehe
|
||||
[Datenschutz](#datenschutz-dsgvo)), Monitoring/Alerting bei Diensteausfall ✅
|
||||
(`pipe/monitor.py`, siehe unten).
|
||||
|
||||
## Bausteine
|
||||
|
||||
|
|
@ -37,6 +39,9 @@ sammelt die Erfahrungswerte, die in dieser Anleitung nicht stehen.
|
|||
| `pipe/config.py` | Konfiguration aus `.env` |
|
||||
| `pipe/watch.py` | Beobachtet den Eingang und schiebt Aufnahmen durch die Pipe |
|
||||
| `pipe/server.py` | Leitstand: Live-Ansicht mit Status, Anrufen und Verlauf |
|
||||
| `pipe/dienste.py` | Zustand der beteiligten Dienste (Telefon, Verarbeitung, Spracherkennung, Nextcloud) — geteilt von Leitstand und Störungswache |
|
||||
| `pipe/monitor.py` | Störungswache: alarmiert lokal (macOS-Benachrichtigung + Ton), wenn ein Dienst ausfällt |
|
||||
| `pipe/alarm.py` | Verschickt den lokalen macOS-Alarm (osascript) |
|
||||
| `pipe/protokoll.py` | Ereignisprotokoll (JSONL) für den Leitstand |
|
||||
| `pipe/audit.py` | Unveränderliches Audit-Log (append-only, siehe unten) |
|
||||
| `pipe/loeschen.py` | Löscht erledigte Anrufe nach Ablauf der Aufbewahrungsfrist |
|
||||
|
|
@ -247,6 +252,29 @@ in `telefon/audit.log` — anders als `verlauf.log` (fürs Leitstand-UI, wird au
|
|||
zusätzlich per macOS `chflags uappnd` gegen nachträgliches Ändern/Kürzen
|
||||
abgesichert (auch der eigene Prozess kommt danach nur noch per Anhängen rein).
|
||||
|
||||
## Störungswache (Monitoring/Alerting)
|
||||
|
||||
Die Dienste-Ampel im Leitstand (Telefon, Verarbeitung, Spracherkennung,
|
||||
Nextcloud) zeigt den Zustand nur an, wenn gerade jemand die Seite offen hat.
|
||||
`pipe/monitor.py` läuft deshalb als **eigener Prozess** (von
|
||||
`telefon/starten.sh` mitgestartet, Log `telefon/monitor.log`), fragt dieselbe
|
||||
Ampel (`pipe/dienste.py`) alle `MONITOR_TAKT_S` Sekunden (Default 60) ab und
|
||||
alarmiert bei einer echten Störung (`schlecht`/`aus` — nicht bei `unklar`,
|
||||
z. B. Nextcloud unkonfiguriert) lokal per macOS-Systembenachrichtigung + Ton
|
||||
(`pipe/alarm.py`, `osascript`). Solange die Störung anhält, wird alle
|
||||
`MONITOR_WIEDERHOLUNG_MIN` Minuten (Default 15) erneut erinnert; bei
|
||||
Wiederherstellung einmalig „wieder ok".
|
||||
|
||||
Bewusst ein eigener, dritter Prozess statt in `pipe.watch` oder `pipe.server`
|
||||
eingebaut: fällt einer der beiden aus, kann die Wache das trotzdem noch
|
||||
melden. Wirkt nur lokal auf diesem Mac — für Alarme aufs Handy (z. B. per
|
||||
Push/E-Mail) `pipe/alarm.py` erweitern.
|
||||
|
||||
```bash
|
||||
python3 -m pipe.monitor # dauerhaft beobachten (normalerweise per starten.sh)
|
||||
python3 -m pipe.monitor --einmal # einmal prüfen, für Tests
|
||||
```
|
||||
|
||||
## Bewertung & Trainingsdaten-Export
|
||||
|
||||
Jede Karte im Leitstand hat 👍/👎 — war die Kategorisierung richtig? Bei 👎
|
||||
|
|
@ -275,3 +303,17 @@ Gesundheitsdaten — Pflichten für den Produktivbetrieb (Stufe 2):
|
|||
> beiden Punkte noch nicht (kein Notfall-Hinweis, keine Aufnahme-Einwilligung)
|
||||
> — Text anpassen und `ansage_bauen.sh` neu laufen lassen, bevor die Anlage im
|
||||
> echten Praxisbetrieb ans Netz geht.
|
||||
|
||||
> **Verschlüsselung at rest — Pflicht des Betreibers:** Die Pipe legt
|
||||
> Aufnahmen, Transkripte und Metadaten (`ablage/`, `telefon/`, `training/`)
|
||||
> unverschlüsselt auf der Platte ab und verlässt sich auf
|
||||
> Festplattenverschlüsselung durch das Betriebssystem. **FileVault muss auf
|
||||
> jedem Rechner aktiviert sein, der diese Pipe betreibt**
|
||||
> (Systemeinstellungen → Datenschutz & Sicherheit → FileVault, oder
|
||||
> `sudo fdesetup enable`) — den Recovery-Key sicher verwahren (Passwort-
|
||||
> Tresor), da er das einzige Mittel bei vergessenem Login-Passwort ist.
|
||||
> Ist FileVault auf einem Zielsystem nicht aktivierbar oder nicht zulässig
|
||||
> (z. B. Mehrbenutzer-Rechner, andere Auflagen), reicht das nicht aus — dann
|
||||
> braucht die App eine eigene Verschlüsselung der Ablage, die es aktuell
|
||||
> **nicht** gibt. Das Aktivieren/Prüfen von FileVault ist Aufgabe des
|
||||
> Betreibers vor Ort, nicht Teil dieses Repos.
|
||||
|
|
|
|||
27
pipe/alarm.py
Normal file
27
pipe/alarm.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"""Lokaler macOS-Alarm (Systembenachrichtigung + Ton) für pipe.monitor.
|
||||
|
||||
Wirft nie - eine fehlgeschlagene Benachrichtigung darf die Wache nicht zum
|
||||
Absturz bringen. Nutzt osascript (stdlib-Unterprozess, kein Framework, keine
|
||||
Abhängigkeit von einem laufenden Nextcloud/Cloud-Dienst).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
|
||||
from . import config
|
||||
|
||||
|
||||
def _quote(text: str) -> str:
|
||||
"""AppleScript-String-Literal - Anführungszeichen/Backslashes escapen."""
|
||||
return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
def sende(titel: str, text: str) -> None:
|
||||
skript = (
|
||||
f"display notification {_quote(text)} with title {_quote(titel)} "
|
||||
f"sound name {_quote(config.MONITOR_TON)}"
|
||||
)
|
||||
try:
|
||||
subprocess.run(["osascript", "-e", skript], capture_output=True, timeout=10)
|
||||
except Exception as fehler:
|
||||
print(f" Alarm konnte nicht zugestellt werden: {fehler}")
|
||||
|
|
@ -168,6 +168,19 @@ def nextcloud_aktiv() -> bool:
|
|||
return bool(NEXTCLOUD_URL and NEXTCLOUD_USER and NEXTCLOUD_PASS)
|
||||
|
||||
|
||||
# --- Störungswache (pipe.monitor) --------------------------------------------
|
||||
# Prueft die Dienste-Ampel (pipe.dienste: Telefon, Verarbeitung, Spracherkennung,
|
||||
# Nextcloud) periodisch und alarmiert lokal per macOS-Benachrichtigung + Ton,
|
||||
# wenn niemand auf den Leitstand schaut. Eigener Prozess (siehe
|
||||
# telefon/starten.sh) - faellt pipe.watch oder pipe.server aus, meldet die
|
||||
# Wache das trotzdem noch.
|
||||
MONITOR_TAKT_S = int(os.environ.get("MONITOR_TAKT_S", "60") or "60")
|
||||
# Erinnerungsabstand, waehrend eine Stoerung anhaelt - sonst nur ein einziger
|
||||
# Alarm beim Ausfall, der leicht untergeht.
|
||||
MONITOR_WIEDERHOLUNG_MIN = int(os.environ.get("MONITOR_WIEDERHOLUNG_MIN", "15") or "15")
|
||||
# macOS-Systemton (siehe /System/Library/Sounds), der bei jedem Alarm abgespielt wird.
|
||||
MONITOR_TON = os.environ.get("MONITOR_TON", "Sosumi").strip() or "Sosumi"
|
||||
|
||||
# --- Aufbewahrungsfrist (pipe.loeschen) --------------------------------------
|
||||
# Nach so vielen Tagen wird ein Anruf-Ordner (Aufnahme + Transkript + Meta)
|
||||
# geloescht - lokal und, falls hochgeladen, auch bei Nextcloud. 0 = aus (nichts
|
||||
|
|
|
|||
96
pipe/dienste.py
Normal file
96
pipe/dienste.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
"""Zustand der beteiligten Dienste (Telefon, Verarbeitung, Spracherkennung,
|
||||
Nextcloud) — geteilt zwischen Leitstand (pipe.server) und Störungswache
|
||||
(pipe.monitor), damit beide dieselbe Sicht auf "läuft/steht" haben.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
|
||||
from . import config, store
|
||||
|
||||
# Statusabfragen sind teuer (Netz, Unterprozesse) - kurz zwischenspeichern,
|
||||
# damit haeufiges Nachfragen den Rechner nicht belastet.
|
||||
_CACHE: dict[str, tuple[float, dict]] = {}
|
||||
CACHE_S = 4.0
|
||||
|
||||
|
||||
def _laeuft(muster: str) -> bool:
|
||||
try:
|
||||
return subprocess.run(["pgrep", "-f", muster], capture_output=True).returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _http_ok(url: str, timeout: float = 5.0) -> bool:
|
||||
"""Erreichbarkeitstest. Mit eigenem User-Agent - ein WAF vor Nextcloud
|
||||
blockt den Standardnamen von urllib und meldete fälschlich 'nicht
|
||||
erreichbar'."""
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "praxis-telefon-agent/1.0"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout, context=store._ssl_kontext()):
|
||||
return True
|
||||
except urllib.error.HTTPError:
|
||||
return True # antwortet - reicht als Lebenszeichen
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _trunk() -> tuple[str, str]:
|
||||
"""(zustand, text) des SIP-Trunks."""
|
||||
if not _laeuft("[f]reeswitch"):
|
||||
return "aus", "FreeSWITCH läuft nicht"
|
||||
try:
|
||||
roh = subprocess.run(
|
||||
["fs_cli", "-P", config.FS_PORT, "-x", "sofia status gateway plusnet"],
|
||||
capture_output=True, text=True, timeout=8).stdout
|
||||
for zeile in roh.splitlines():
|
||||
if zeile.startswith("Status"):
|
||||
wert = zeile.split()[-1]
|
||||
return ("gut", "registriert") if wert == "UP" else ("schlecht", f"Trunk {wert}")
|
||||
return "unklar", "Gateway unbekannt"
|
||||
except Exception:
|
||||
return "unklar", "fs_cli nicht erreichbar"
|
||||
|
||||
|
||||
def status() -> dict:
|
||||
jetzt = time.time()
|
||||
gepuffert = _CACHE.get("status")
|
||||
if gepuffert and jetzt - gepuffert[0] < CACHE_S:
|
||||
return gepuffert[1]
|
||||
|
||||
trunk_zustand, trunk_text = _trunk()
|
||||
watcher = _laeuft("[p]ipe.watch")
|
||||
ollama = _http_ok(f"{config.OLLAMA_URL}/api/version")
|
||||
nextcloud = (_http_ok(f"{config.NEXTCLOUD_URL}/status.php")
|
||||
if config.nextcloud_aktiv() else None)
|
||||
|
||||
offen = 0
|
||||
if config.TELEFON_EINGANG.is_dir():
|
||||
offen = sum(1 for d in config.TELEFON_EINGANG.iterdir()
|
||||
if d.is_file() and not d.name.startswith("."))
|
||||
fehler = 0
|
||||
if config.TELEFON_FEHLER.is_dir():
|
||||
fehler = sum(1 for d in config.TELEFON_FEHLER.iterdir() if d.is_file())
|
||||
|
||||
daten = {
|
||||
"dienste": [
|
||||
{"name": "Telefon", "zustand": trunk_zustand, "text": trunk_text},
|
||||
{"name": "Verarbeitung", "zustand": "gut" if watcher else "aus",
|
||||
"text": "beobachtet Eingang" if watcher else "Watcher läuft nicht"},
|
||||
{"name": "Spracherkennung", "zustand": "gut" if ollama else "schlecht",
|
||||
"text": "Ollama bereit" if ollama else "Ollama nicht erreichbar"},
|
||||
{"name": "Nextcloud", "zustand": "unklar" if nextcloud is None
|
||||
else ("gut" if nextcloud else "schlecht"),
|
||||
"text": "nicht konfiguriert" if nextcloud is None
|
||||
else ("erreichbar" if nextcloud else "nicht erreichbar")},
|
||||
],
|
||||
"eingang_offen": offen,
|
||||
"fehler": fehler,
|
||||
"stand": datetime.now().strftime("%H:%M:%S"),
|
||||
}
|
||||
_CACHE["status"] = (jetzt, daten)
|
||||
return daten
|
||||
70
pipe/monitor.py
Normal file
70
pipe/monitor.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"""Störungswache: prüft die Dienste-Ampel (pipe.dienste) periodisch und
|
||||
alarmiert lokal (macOS-Benachrichtigung + Ton), wenn ein Dienst ausfällt -
|
||||
auch wenn gerade niemand auf den Leitstand schaut.
|
||||
|
||||
python3 -m pipe.monitor [--einmal]
|
||||
|
||||
Läuft als eigener Prozess (siehe telefon/starten.sh), unabhängig von
|
||||
pipe.watch und pipe.server - fällt einer der beiden aus, kann die Wache das
|
||||
trotzdem noch melden. Alarmiert nur bei echten Störungen ("schlecht"/"aus"),
|
||||
nicht bei "unklar" (z. B. Nextcloud nicht konfiguriert - das ist gewollt,
|
||||
kein Fehler).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import time
|
||||
|
||||
from . import alarm, config, dienste
|
||||
|
||||
GESTOERT = {"schlecht", "aus"}
|
||||
|
||||
|
||||
def pruefe(letzte_alarme: dict[str, float]) -> None:
|
||||
"""Ein Durchlauf: Ampel abfragen, bei Bedarf alarmieren.
|
||||
|
||||
`letzte_alarme` hält je Dienstname den Zeitpunkt (time.monotonic) des
|
||||
letzten Alarms - fehlt ein Name, gilt der Dienst aktuell als nicht
|
||||
gestört. Der Aufrufer reicht dasselbe dict über alle Durchläufe weiter.
|
||||
"""
|
||||
jetzt = time.monotonic()
|
||||
for dienst in dienste.status()["dienste"]:
|
||||
name, zustand, text = dienst["name"], dienst["zustand"], dienst["text"]
|
||||
letzter_alarm = letzte_alarme.get(name)
|
||||
if zustand in GESTOERT:
|
||||
if letzter_alarm is None:
|
||||
alarm.sende(f"⚠ {name} gestört", text)
|
||||
letzte_alarme[name] = jetzt
|
||||
elif jetzt - letzter_alarm >= config.MONITOR_WIEDERHOLUNG_MIN * 60:
|
||||
alarm.sende(f"⚠ {name} weiterhin gestört", text)
|
||||
letzte_alarme[name] = jetzt
|
||||
elif zustand == "gut" and letzter_alarm is not None:
|
||||
alarm.sende(f"✓ {name} wieder ok", text)
|
||||
del letzte_alarme[name]
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Dienste-Ampel beobachten und bei Störung lokal alarmieren.")
|
||||
p.add_argument("--einmal", action="store_true",
|
||||
help="nur einmal prüfen statt dauerhaft zu beobachten")
|
||||
args = p.parse_args(argv)
|
||||
|
||||
letzte_alarme: dict[str, float] = {}
|
||||
if args.einmal:
|
||||
pruefe(letzte_alarme)
|
||||
return 0
|
||||
|
||||
print(f"Störungswache aktiv - Takt {config.MONITOR_TAKT_S}s, "
|
||||
f"Erinnerung alle {config.MONITOR_WIEDERHOLUNG_MIN} min - Abbruch mit Strg-C")
|
||||
try:
|
||||
while True:
|
||||
pruefe(letzte_alarme)
|
||||
time.sleep(config.MONITOR_TAKT_S)
|
||||
except KeyboardInterrupt:
|
||||
print("\nBeobachtung beendet.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -13,101 +13,15 @@ import base64
|
|||
import hmac
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from http.cookies import SimpleCookie
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from urllib.parse import parse_qs, unquote, urlparse
|
||||
|
||||
from . import bewertung, config, dashboard, export, modellwahl, protokoll, stapel, store, testzugang
|
||||
|
||||
# Statusabfragen sind teuer (Netz, Unterprozesse) - kurz zwischenspeichern,
|
||||
# damit haeufiges Nachfragen den Rechner nicht belastet.
|
||||
_CACHE: dict[str, tuple[float, dict]] = {}
|
||||
CACHE_S = 4.0
|
||||
|
||||
|
||||
def _laeuft(muster: str) -> bool:
|
||||
try:
|
||||
return subprocess.run(["pgrep", "-f", muster], capture_output=True).returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _http_ok(url: str, timeout: float = 5.0) -> bool:
|
||||
"""Erreichbarkeitstest. Mit eigenem User-Agent - ein WAF vor Nextcloud
|
||||
blockt den Standardnamen von urllib und meldete fälschlich 'nicht
|
||||
erreichbar'."""
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "praxis-telefon-agent/1.0"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout, context=store._ssl_kontext()):
|
||||
return True
|
||||
except urllib.error.HTTPError:
|
||||
return True # antwortet - reicht als Lebenszeichen
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _trunk() -> tuple[str, str]:
|
||||
"""(zustand, text) des SIP-Trunks."""
|
||||
if not _laeuft("[f]reeswitch"):
|
||||
return "aus", "FreeSWITCH läuft nicht"
|
||||
try:
|
||||
roh = subprocess.run(
|
||||
["fs_cli", "-P", config.FS_PORT, "-x", "sofia status gateway plusnet"],
|
||||
capture_output=True, text=True, timeout=8).stdout
|
||||
for zeile in roh.splitlines():
|
||||
if zeile.startswith("Status"):
|
||||
wert = zeile.split()[-1]
|
||||
return ("gut", "registriert") if wert == "UP" else ("schlecht", f"Trunk {wert}")
|
||||
return "unklar", "Gateway unbekannt"
|
||||
except Exception:
|
||||
return "unklar", "fs_cli nicht erreichbar"
|
||||
|
||||
|
||||
def status() -> dict:
|
||||
jetzt = time.time()
|
||||
gepuffert = _CACHE.get("status")
|
||||
if gepuffert and jetzt - gepuffert[0] < CACHE_S:
|
||||
return gepuffert[1]
|
||||
|
||||
trunk_zustand, trunk_text = _trunk()
|
||||
watcher = _laeuft("[p]ipe.watch")
|
||||
ollama = _http_ok(f"{config.OLLAMA_URL}/api/version")
|
||||
nextcloud = (_http_ok(f"{config.NEXTCLOUD_URL}/status.php")
|
||||
if config.nextcloud_aktiv() else None)
|
||||
|
||||
offen = 0
|
||||
if config.TELEFON_EINGANG.is_dir():
|
||||
offen = sum(1 for d in config.TELEFON_EINGANG.iterdir()
|
||||
if d.is_file() and not d.name.startswith("."))
|
||||
fehler = 0
|
||||
if config.TELEFON_FEHLER.is_dir():
|
||||
fehler = sum(1 for d in config.TELEFON_FEHLER.iterdir() if d.is_file())
|
||||
|
||||
daten = {
|
||||
"dienste": [
|
||||
{"name": "Telefon", "zustand": trunk_zustand, "text": trunk_text},
|
||||
{"name": "Verarbeitung", "zustand": "gut" if watcher else "aus",
|
||||
"text": "beobachtet Eingang" if watcher else "Watcher läuft nicht"},
|
||||
{"name": "Spracherkennung", "zustand": "gut" if ollama else "schlecht",
|
||||
"text": "Ollama bereit" if ollama else "Ollama nicht erreichbar"},
|
||||
{"name": "Nextcloud", "zustand": "unklar" if nextcloud is None
|
||||
else ("gut" if nextcloud else "schlecht"),
|
||||
"text": "nicht konfiguriert" if nextcloud is None
|
||||
else ("erreichbar" if nextcloud else "nicht erreichbar")},
|
||||
],
|
||||
"eingang_offen": offen,
|
||||
"fehler": fehler,
|
||||
"stand": datetime.now().strftime("%H:%M:%S"),
|
||||
}
|
||||
_CACHE["status"] = (jetzt, daten)
|
||||
return daten
|
||||
from . import bewertung, config, dashboard, dienste, export, modellwahl, protokoll, stapel, testzugang
|
||||
|
||||
|
||||
def anrufe() -> list[dict]:
|
||||
|
|
@ -325,7 +239,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||
if pfad == "/":
|
||||
self._sende(SEITE.encode("utf-8"), "text/html; charset=utf-8")
|
||||
elif pfad == "/api/status":
|
||||
daten = status()
|
||||
daten = dienste.status()
|
||||
# Testzugang (per "agent-one test"-Link) -> Sicher-Modus im
|
||||
# Frontend erzwingen, siehe sicherSetzen() im JS.
|
||||
daten["testzugang"] = getattr(self, "_via_token", False)
|
||||
|
|
|
|||
|
|
@ -109,6 +109,12 @@ if [ "${status:-}" = "UP" ]; then
|
|||
sleep 2
|
||||
head -1 telefon/leitstand.log
|
||||
fi
|
||||
if pgrep -f "[p]ipe.monitor" >/dev/null; then
|
||||
echo "Stoerungswache laeuft bereits."
|
||||
else
|
||||
nohup python3 -u -m pipe.monitor > telefon/monitor.log 2>&1 &
|
||||
echo "Stoerungswache gestartet (Log: telefon/monitor.log)"
|
||||
fi
|
||||
sleep 2
|
||||
echo
|
||||
ip=$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue