Compare commits
7 Commits
fcb6cd7995
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 550b5261e1 | |||
| f79fc24b3b | |||
| e092022774 | |||
| 4b58b138c9 | |||
| be89e66261 | |||
| b5e767a265 | |||
| 6e6f58c32d |
49
.dockerignore
Normal file
49
.dockerignore
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
env
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
.tox
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.log
|
||||||
|
.git
|
||||||
|
.mypy_cache
|
||||||
|
.pytest_cache
|
||||||
|
.hypothesis
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Dokumentation
|
||||||
|
README*.md
|
||||||
|
LICENSE.txt
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Lokale Konfiguration und Daten
|
||||||
|
.env
|
||||||
|
*.json
|
||||||
|
*.cmd
|
||||||
19
Dockerfile
19
Dockerfile
@@ -1,11 +1,26 @@
|
|||||||
# Dockerfile für TI-Status2Mattermost
|
# Dockerfile für TI-Status2Mattermost
|
||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Arbeitsverzeichnis setzen
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY requirements.txt ./
|
# System-Abhängigkeiten installieren
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Python-Abhängigkeiten kopieren und installieren
|
||||||
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY . .
|
# Anwendungsdateien kopieren
|
||||||
|
COPY ti_status_checker.py .
|
||||||
|
COPY ti_statistics.py .
|
||||||
|
|
||||||
|
# Nicht-root Benutzer erstellen
|
||||||
|
RUN useradd --create-home --shell /bin/bash appuser && \
|
||||||
|
chown -R appuser:appuser /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Standardbefehl
|
||||||
CMD ["python", "ti_status_checker.py"]
|
CMD ["python", "ti_status_checker.py"]
|
||||||
154
README.md
154
README.md
@@ -13,9 +13,12 @@ Ein Python-Skript, das den TI-Status überwacht und neue Meldungen über Apprise
|
|||||||
- Vermeidet Duplikate durch lokale Statusverfolgung
|
- Vermeidet Duplikate durch lokale Statusverfolgung
|
||||||
- Debug-Ausgaben für bessere Transparenz
|
- Debug-Ausgaben für bessere Transparenz
|
||||||
- Umfassendes Test-Tool
|
- Umfassendes Test-Tool
|
||||||
|
- [Statistische Erfassung der Störungen](./README_STATISTICS.md)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Option 1: Lokale Python-Installation
|
||||||
|
|
||||||
1. Repository klonen:
|
1. Repository klonen:
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitea.medisoftware.org/Markus/TI-Status2Mattermost.git
|
git clone https://gitea.medisoftware.org/Markus/TI-Status2Mattermost.git
|
||||||
@@ -36,6 +39,23 @@ source .venv/bin/activate
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Option 2: Docker (Empfohlen)
|
||||||
|
|
||||||
|
1. Repository klonen:
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.medisoftware.org/Markus/TI-Status2Mattermost.git
|
||||||
|
cd TI-Status2Mattermost
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Docker und Docker Compose installieren (falls noch nicht vorhanden)
|
||||||
|
|
||||||
|
3. Konfiguration einrichten (siehe Abschnitt "Konfiguration")
|
||||||
|
|
||||||
|
4. Container starten:
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
## Konfiguration
|
## Konfiguration
|
||||||
|
|
||||||
1. Kopiere die Beispiel-Konfiguration:
|
1. Kopiere die Beispiel-Konfiguration:
|
||||||
@@ -96,13 +116,112 @@ DEBUG_MODE=true
|
|||||||
|
|
||||||
Weitere Apprise-URLs findest du in der [Apprise-Dokumentation](https://github.com/caronc/apprise#supported-notifications).
|
Weitere Apprise-URLs findest du in der [Apprise-Dokumentation](https://github.com/caronc/apprise#supported-notifications).
|
||||||
|
|
||||||
|
## Docker-Architektur
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
#### ti-status-checker
|
||||||
|
- **Hauptservice** für die kontinuierliche Überwachung der TI-Status-API
|
||||||
|
- Läuft dauerhaft und prüft regelmäßig auf neue Meldungen
|
||||||
|
- Sendet Benachrichtigungen bei Änderungen
|
||||||
|
- Zeichnet Störungen in der Statistik auf
|
||||||
|
|
||||||
|
#### ti-statistics
|
||||||
|
- **Statistik-Service** für periodische Berichte
|
||||||
|
- Kann manuell oder automatisch ausgeführt werden
|
||||||
|
- Generiert und sendet detaillierte Ausfall-Statistiken
|
||||||
|
- Läuft unabhängig vom Hauptservice
|
||||||
|
|
||||||
|
### Volume-Mappings
|
||||||
|
|
||||||
|
Alle wichtigen Dateien werden als lokale Volumes eingebunden:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
# Konfiguration (read-only)
|
||||||
|
- ./.env:/app/.env:ro
|
||||||
|
|
||||||
|
# Persistierte Daten
|
||||||
|
- ./ti_status_state.json:/app/ti_status_state.json
|
||||||
|
- ./ti_outage_statistics.json:/app/ti_outage_statistics.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vorteile:**
|
||||||
|
- ✅ Daten bleiben auf dem lokalen System
|
||||||
|
- ✅ Einfache Updates ohne Datenverlust
|
||||||
|
- ✅ Backup der Konfiguration und Daten möglich
|
||||||
|
- ✅ Debugging und Monitoring von außen
|
||||||
|
|
||||||
|
### Netzwerk
|
||||||
|
|
||||||
|
- Isoliertes Docker-Netzwerk `ti-status-network`
|
||||||
|
- Services können untereinander kommunizieren
|
||||||
|
- Externe Verbindungen nur für API-Calls und Benachrichtigungen
|
||||||
|
|
||||||
|
### Umgebungsvariablen
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- DBG_MODE=false # Kann in .env überschrieben werden
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatischer Restart
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
- Container startet automatisch nach Neustart des Hosts
|
||||||
|
- Bei Fehlern wird der Container neu gestartet
|
||||||
|
- Nur bei manuellem Stopp bleibt der Container gestoppt
|
||||||
|
|
||||||
## Verwendung
|
## Verwendung
|
||||||
|
|
||||||
### Hauptskript ausführen:
|
### Mit lokaler Python-Installation
|
||||||
|
|
||||||
|
#### Hauptskript ausführen:
|
||||||
```bash
|
```bash
|
||||||
python ti_status_checker.py
|
python ti_status_checker.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Mit Docker
|
||||||
|
|
||||||
|
#### Container starten:
|
||||||
|
```bash
|
||||||
|
# Alle Services starten
|
||||||
|
docker-compose up --build
|
||||||
|
|
||||||
|
# Nur den Hauptservice starten
|
||||||
|
docker-compose up ti-status-checker
|
||||||
|
|
||||||
|
# Im Hintergrund laufen lassen
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Container verwalten:
|
||||||
|
```bash
|
||||||
|
# Logs anzeigen
|
||||||
|
docker-compose logs -f ti-status-checker
|
||||||
|
|
||||||
|
# Container stoppen
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Container neu starten
|
||||||
|
docker-compose restart ti-status-checker
|
||||||
|
|
||||||
|
# Status anzeigen
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Statistik-Service:
|
||||||
|
```bash
|
||||||
|
# Nur Statistik-Bericht senden
|
||||||
|
docker-compose run --rm ti-statistics
|
||||||
|
|
||||||
|
# Statistik-Service dauerhaft starten
|
||||||
|
docker-compose up ti-statistics
|
||||||
|
```
|
||||||
|
|
||||||
### Verbindungstest:
|
### Verbindungstest:
|
||||||
```bash
|
```bash
|
||||||
python test_apprise.py
|
python test_apprise.py
|
||||||
@@ -127,12 +246,23 @@ Das Skript gibt dann detaillierte Debug-Informationen aus:
|
|||||||
- Benachrichtigungsregeln-Auswertung
|
- Benachrichtigungsregeln-Auswertung
|
||||||
- Endpunkt-Status
|
- Endpunkt-Status
|
||||||
|
|
||||||
### Für kontinuierliche Überwachung (z.B. mit cron):
|
### Für kontinuierliche Überwachung
|
||||||
|
|
||||||
|
#### Mit lokaler Python-Installation (cron):
|
||||||
```bash
|
```bash
|
||||||
# Alle 5 Minuten ausführen
|
# Alle 5 Minuten ausführen
|
||||||
*/5 * * * * cd /path/to/TI-Status2Mattermost && python ti_status_checker.py
|
*/5 * * * * cd /path/to/TI-Status2Mattermost && python ti_status_checker.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Mit Docker (automatischer Restart):
|
||||||
|
```bash
|
||||||
|
# Container läuft dauerhaft und startet automatisch neu
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Für periodische Ausführung alle 5 Minuten (in docker-compose.yml aktivieren):
|
||||||
|
# command: ["sh", "-c", "while true; do python ti_status_checker.py; sleep 300; done"]
|
||||||
|
```
|
||||||
|
|
||||||
## Benachrichtigungsregeln
|
## Benachrichtigungsregeln
|
||||||
|
|
||||||
### Filter
|
### Filter
|
||||||
@@ -162,15 +292,33 @@ Apprise unterstützt über 80 verschiedene Benachrichtigungsdienste, darunter:
|
|||||||
|
|
||||||
## Dateien
|
## Dateien
|
||||||
|
|
||||||
|
### Anwendung
|
||||||
- `ti_status_checker.py` - Hauptskript mit Multi-Endpoint-Support
|
- `ti_status_checker.py` - Hauptskript mit Multi-Endpoint-Support
|
||||||
|
- `ti_statistics.py` - Statistik-Funktionalität für Störungen
|
||||||
- `test_apprise.py` - Umfassendes Test-Tool für alle Endpunkte
|
- `test_apprise.py` - Umfassendes Test-Tool für alle Endpunkte
|
||||||
|
|
||||||
|
### Konfiguration
|
||||||
- `requirements.txt` - Python-Abhängigkeiten (python-dotenv, apprise)
|
- `requirements.txt` - Python-Abhängigkeiten (python-dotenv, apprise)
|
||||||
- `env.example` - Beispiel-Konfiguration mit allen Optionen
|
- `env.example` - Beispiel-Konfiguration mit allen Optionen
|
||||||
- `ti_status_state.json` - Lokale Statusverfolgung (wird automatisch erstellt)
|
|
||||||
- `.env` - Deine Konfiguration (nicht im Repository)
|
- `.env` - Deine Konfiguration (nicht im Repository)
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
- `Dockerfile` - Container-Image für die Anwendung
|
||||||
|
- `docker-compose.yml` - Multi-Service-Orchestrierung
|
||||||
|
- `.dockerignore` - Dateien die vom Docker Build ausgeschlossen werden
|
||||||
|
|
||||||
|
### Daten
|
||||||
|
- `ti_status_state.json` - Lokale Statusverfolgung (wird automatisch erstellt)
|
||||||
|
- `ti_outage_statistics.json` - Statistik-Daten der Störungen (wird automatisch erstellt)
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### Version 3.1 (Docker-Support)
|
||||||
|
- ✅ Vollständige Docker-Integration mit Dockerfile und docker-compose.yml
|
||||||
|
- ✅ Multi-Service-Architektur (Hauptservice + Statistik-Service)
|
||||||
|
- ✅ Lokale Volume-Mappings für .env und .json Dateien
|
||||||
|
- ✅ Automatischer Restart und Container-Management
|
||||||
|
|
||||||
### Version 3.0 (Multi-Endpoint & Rules)
|
### Version 3.0 (Multi-Endpoint & Rules)
|
||||||
- ✅ Mehrere Apprise-Endpunkte gleichzeitig
|
- ✅ Mehrere Apprise-Endpunkte gleichzeitig
|
||||||
- ✅ Konfigurierbare Benachrichtigungsregeln (Filter, Zeiten, Verzögerungen)
|
- ✅ Konfigurierbare Benachrichtigungsregeln (Filter, Zeiten, Verzögerungen)
|
||||||
|
|||||||
149
README_STATISTICS.md
Normal file
149
README_STATISTICS.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# TI-Status Checker - Statistik-Funktionalität
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Der TI-Status Checker wurde um umfangreiche Statistik-Funktionalität erweitert, die automatisch alle Störungen und deren Dauer aufzeichnet.
|
||||||
|
|
||||||
|
## Neue Features
|
||||||
|
|
||||||
|
### 1. Automatische Störungsaufzeichnung
|
||||||
|
- **Störungsbeginn**: Wird automatisch erkannt und aufgezeichnet
|
||||||
|
- **Störungsende**: Wird automatisch erkannt und die Dauer berechnet
|
||||||
|
- **Störungstypen**: Unterscheidung zwischen "full" und "partial" Störungen
|
||||||
|
|
||||||
|
### 2. Detaillierte Statistiken
|
||||||
|
- **Service-spezifische Statistiken**: Für jeden betroffenen Service
|
||||||
|
- **Gesamtstatistiken**: Über alle Störungen hinweg
|
||||||
|
- **Trend-Analysen**: Durchschnittswerte der letzten 30 Tage
|
||||||
|
- **Zeitreihen**: Chronologische Aufzeichnung aller Störungen
|
||||||
|
|
||||||
|
### 3. Statistik-Berichte
|
||||||
|
- **Automatische Berichte**: Über Apprise versendbar
|
||||||
|
- **Markdown-Format**: Gut lesbar in Chat-Systemen
|
||||||
|
- **Zusammenfassungen**: Übersichtliche Darstellung der wichtigsten Kennzahlen
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
### Normale Ausführung (Störungen überwachen)
|
||||||
|
```bash
|
||||||
|
python ti_status_checker.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Statistik-Bericht senden
|
||||||
|
```bash
|
||||||
|
python ti_status_checker.py --stats
|
||||||
|
# oder
|
||||||
|
python ti_status_checker.py -s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug-Modus aktivieren
|
||||||
|
```bash
|
||||||
|
python ti_status_checker.py --debug
|
||||||
|
# oder
|
||||||
|
python ti_status_checker.py -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Statistik-Daten
|
||||||
|
|
||||||
|
### Gespeicherte Informationen
|
||||||
|
- **Störungsbeginn**: ISO-Zeitstempel
|
||||||
|
- **Störungsende**: ISO-Zeitstempel
|
||||||
|
- **Dauer**: In Minuten
|
||||||
|
- **Service-Name**: Betroffener Dienst
|
||||||
|
- **Störungstyp**: "full" oder "partial"
|
||||||
|
- **Status**: "active" oder "resolved"
|
||||||
|
|
||||||
|
### Berechnete Kennzahlen
|
||||||
|
- Gesamtanzahl Störungen
|
||||||
|
- Gesamtdauer aller Störungen
|
||||||
|
- Durchschnittsdauer pro Störung
|
||||||
|
- Längste Störung
|
||||||
|
- Am stärksten betroffener Service
|
||||||
|
- Tägliche Durchschnittswerte
|
||||||
|
- Trends der letzten 30 Tage
|
||||||
|
|
||||||
|
## Dateien
|
||||||
|
|
||||||
|
### Neue Dateien
|
||||||
|
- `ti_statistics.py`: Statistik-Funktionen und -Klasse
|
||||||
|
- `ti_outage_statistics.json`: Gespeicherte Statistik-Daten
|
||||||
|
|
||||||
|
### Erweiterte Dateien
|
||||||
|
- `ti_status_checker.py`: Hauptscript mit Statistik-Integration
|
||||||
|
|
||||||
|
## Beispiel-Statistik-Bericht
|
||||||
|
|
||||||
|
```
|
||||||
|
📊 **TI-Status Ausfall-Statistiken**
|
||||||
|
==================================================
|
||||||
|
**Zusammenfassung:**
|
||||||
|
• Gesamte Störungen: 15
|
||||||
|
• Aktive Störungen: 2
|
||||||
|
• Gesamtdauer: 1245 Minuten
|
||||||
|
• Durchschnittsdauer: 83.0 Minuten
|
||||||
|
• Längste Störung: 180 Minuten
|
||||||
|
• Am stärksten betroffen: konnektor
|
||||||
|
|
||||||
|
**Service-Statistiken:**
|
||||||
|
• **KONNEKTOR**:
|
||||||
|
- Störungen: 8
|
||||||
|
- Gesamtdauer: 720 Min
|
||||||
|
- Durchschnitt: 90.0 Min
|
||||||
|
- Längste: 180 Min
|
||||||
|
|
||||||
|
**Trends (letzte 30 Tage):**
|
||||||
|
• Durchschnitt Störungen/Tag: 0.5
|
||||||
|
• Durchschnitt Dauer/Tag: 41.5 Min
|
||||||
|
• Tage mit Störungen: 12
|
||||||
|
• Max. Störungen an einem Tag: 3
|
||||||
|
|
||||||
|
**Letzte Störungen (7 Tage):**
|
||||||
|
• **KONNEKTOR** (partial)
|
||||||
|
- 15.01. 14:30 - 15.01. 16:45 (135 Min)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
Die Statistik-Funktionalität verwendet dieselben Apprise-Konfigurationen wie der normale Status-Checker. Stellen Sie sicher, dass in Ihrer `.env` Datei mindestens eine Apprise-URL konfiguriert ist:
|
||||||
|
|
||||||
|
```env
|
||||||
|
APPRISE_URL_MATTERMOST=mattermost://...
|
||||||
|
APPRISE_URL_SLACK=slack://...
|
||||||
|
# etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automatisierung
|
||||||
|
|
||||||
|
### Cron-Job für regelmäßige Statistiken
|
||||||
|
```bash
|
||||||
|
# Täglich um 9:00 Uhr Statistik-Bericht senden
|
||||||
|
0 9 * * * cd /path/to/script && python ti_status_checker.py --stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows Task Scheduler
|
||||||
|
Erstellen Sie eine geplante Aufgabe, die regelmäßig `python ti_status_checker.py --stats` ausführt.
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### Häufige Probleme
|
||||||
|
1. **Keine Apprise-URLs konfiguriert**: Prüfen Sie Ihre `.env` Datei
|
||||||
|
2. **Berechtigungsfehler**: Stellen Sie sicher, dass das Script Schreibrechte im Verzeichnis hat
|
||||||
|
3. **JSON-Fehler**: Bei korrupten Statistik-Daten wird automatisch eine neue Datei erstellt
|
||||||
|
|
||||||
|
### Debug-Modus
|
||||||
|
Verwenden Sie `--debug` für detaillierte Ausgaben und Fehlerdiagnose.
|
||||||
|
|
||||||
|
## Erweiterte Verwendung
|
||||||
|
|
||||||
|
### Statistik-Daten exportieren
|
||||||
|
Die Statistik-Daten werden in `ti_outage_statistics.json` gespeichert und können für weitere Analysen verwendet werden.
|
||||||
|
|
||||||
|
### Benutzerdefinierte Berichte
|
||||||
|
Die `OutageStatistics` Klasse kann in eigene Scripts importiert werden, um maßgeschneiderte Berichte zu erstellen.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Bei Fragen oder Problemen mit der Statistik-Funktionalität:
|
||||||
|
1. Aktivieren Sie den Debug-Modus mit `--debug`
|
||||||
|
2. Prüfen Sie die Logs auf Fehlermeldungen
|
||||||
|
3. Stellen Sie sicher, dass alle Abhängigkeiten installiert sind
|
||||||
46
docker-compose.yml
Normal file
46
docker-compose.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
services:
|
||||||
|
ti-status-checker:
|
||||||
|
build: .
|
||||||
|
container_name: ti-status-checker
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
# .env Datei für Konfiguration
|
||||||
|
- ./.env:/app/.env:ro
|
||||||
|
# JSON-Dateien für Persistierung
|
||||||
|
- ./ti_status_state.json:/app/ti_status_state.json
|
||||||
|
- ./ti_outage_statistics.json:/app/ti_outage_statistics.json
|
||||||
|
environment:
|
||||||
|
# Debug-Modus (kann in .env überschrieben werden)
|
||||||
|
- DBG_MODE=false
|
||||||
|
networks:
|
||||||
|
- ti-status-network
|
||||||
|
# Optional: Cron-ähnliche Ausführung alle 5 Minuten
|
||||||
|
# command: ["sh", "-c", "while true; do python ti_status_checker.py; sleep 300; done"]
|
||||||
|
|
||||||
|
# Optional: Statistik-Service der täglich läuft
|
||||||
|
ti-statistics:
|
||||||
|
build: .
|
||||||
|
container_name: ti-statistics
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./.env:/app/.env:ro
|
||||||
|
- ./ti_outage_statistics.json:/app/ti_outage_statistics.json
|
||||||
|
command: ["python", "ti_status_checker.py", "--stats"]
|
||||||
|
environment:
|
||||||
|
- DBG_MODE=false
|
||||||
|
networks:
|
||||||
|
- ti-status-network
|
||||||
|
# Läuft einmal täglich um 8:00 Uhr
|
||||||
|
# depends_on:
|
||||||
|
# - ti-status-checker
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ti-status-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Lokale Volumes für Persistierung
|
||||||
|
ti-status-state:
|
||||||
|
driver: local
|
||||||
|
ti-outage-statistics:
|
||||||
|
driver: local
|
||||||
11
run_checker_loop.cmd
Normal file
11
run_checker_loop.cmd
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
@echo off
|
||||||
|
REM Setze den Pfad zu deinem venv (ggf. anpassen!)
|
||||||
|
set VENV_PATH=%~dp0.venv
|
||||||
|
|
||||||
|
REM Aktiviere das venv
|
||||||
|
call "%VENV_PATH%\Scripts\activate.bat"
|
||||||
|
|
||||||
|
:loop
|
||||||
|
python ti_status_checker.py
|
||||||
|
timeout /t 120
|
||||||
|
goto loop
|
||||||
240
ti_statistics.py
Normal file
240
ti_statistics.py
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from collections import defaultdict, Counter
|
||||||
|
import statistics
|
||||||
|
|
||||||
|
STATS_FILE = "ti_outage_statistics.json"
|
||||||
|
|
||||||
|
class OutageStatistics:
|
||||||
|
def __init__(self):
|
||||||
|
self.stats_file = STATS_FILE
|
||||||
|
self.stats = self.load_statistics()
|
||||||
|
|
||||||
|
def load_statistics(self):
|
||||||
|
"""Lädt gespeicherte Statistiken aus der JSON-Datei"""
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.stats_file):
|
||||||
|
with open(self.stats_file, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (json.JSONDecodeError, IOError) as e:
|
||||||
|
print(f"Warnung: Konnte Statistiken nicht laden: {e}")
|
||||||
|
|
||||||
|
# Standard-Struktur für neue Statistiken
|
||||||
|
return {
|
||||||
|
"outages": [],
|
||||||
|
"services": {},
|
||||||
|
"summary": {
|
||||||
|
"total_outages": 0,
|
||||||
|
"total_duration_minutes": 0,
|
||||||
|
"average_duration_minutes": 0,
|
||||||
|
"longest_outage_minutes": 0,
|
||||||
|
"most_affected_service": "",
|
||||||
|
"last_updated": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_statistics(self):
|
||||||
|
"""Speichert Statistiken in die JSON-Datei"""
|
||||||
|
try:
|
||||||
|
with open(self.stats_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(self.stats, f, ensure_ascii=False, indent=2)
|
||||||
|
except IOError as e:
|
||||||
|
print(f"Fehler beim Speichern der Statistiken: {e}")
|
||||||
|
|
||||||
|
def record_outage_start(self, service_name, outage_type, timestamp=None):
|
||||||
|
"""Zeichnet den Beginn einer Störung auf"""
|
||||||
|
if timestamp is None:
|
||||||
|
timestamp = datetime.now().isoformat()
|
||||||
|
|
||||||
|
# Prüfe ob bereits eine aktive Störung für diesen Service existiert
|
||||||
|
for outage in self.stats["outages"]:
|
||||||
|
if (outage["service"] == service_name and
|
||||||
|
outage["status"] == "active" and
|
||||||
|
outage["type"] == outage_type):
|
||||||
|
# Störung bereits aktiv, nicht erneut aufzeichnen
|
||||||
|
return
|
||||||
|
|
||||||
|
outage_record = {
|
||||||
|
"service": service_name,
|
||||||
|
"type": outage_type,
|
||||||
|
"start_time": timestamp,
|
||||||
|
"end_time": None,
|
||||||
|
"status": "active",
|
||||||
|
"duration_minutes": None
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stats["outages"].append(outage_record)
|
||||||
|
self.update_service_stats(service_name, "start")
|
||||||
|
self.save_statistics()
|
||||||
|
|
||||||
|
def record_outage_end(self, service_name, timestamp=None):
|
||||||
|
"""Zeichnet das Ende einer Störung auf"""
|
||||||
|
if timestamp is None:
|
||||||
|
timestamp = datetime.now().isoformat()
|
||||||
|
|
||||||
|
# Finde die aktive Störung für diesen Service
|
||||||
|
for outage in self.stats["outages"]:
|
||||||
|
if outage["service"] == service_name and outage["status"] == "active":
|
||||||
|
outage["end_time"] = timestamp
|
||||||
|
outage["status"] = "resolved"
|
||||||
|
|
||||||
|
# Berechne Dauer
|
||||||
|
start_time = datetime.fromisoformat(outage["start_time"])
|
||||||
|
end_time = datetime.fromisoformat(timestamp)
|
||||||
|
duration = end_time - start_time
|
||||||
|
outage["duration_minutes"] = int(duration.total_seconds() / 60)
|
||||||
|
|
||||||
|
self.update_service_stats(service_name, "end", outage["duration_minutes"])
|
||||||
|
break
|
||||||
|
|
||||||
|
self.save_statistics()
|
||||||
|
|
||||||
|
def update_service_stats(self, service_name, event_type, duration_minutes=None):
|
||||||
|
"""Aktualisiert die Service-spezifischen Statistiken"""
|
||||||
|
if service_name not in self.stats["services"]:
|
||||||
|
self.stats["services"][service_name] = {
|
||||||
|
"total_outages": 0,
|
||||||
|
"total_duration_minutes": 0,
|
||||||
|
"average_duration_minutes": 0,
|
||||||
|
"longest_outage_minutes": 0,
|
||||||
|
"last_outage": None
|
||||||
|
}
|
||||||
|
|
||||||
|
service_stats = self.stats["services"][service_name]
|
||||||
|
|
||||||
|
if event_type == "start":
|
||||||
|
service_stats["total_outages"] += 1
|
||||||
|
service_stats["last_outage"] = datetime.now().isoformat()
|
||||||
|
elif event_type == "end" and duration_minutes:
|
||||||
|
service_stats["total_duration_minutes"] += duration_minutes
|
||||||
|
service_stats["average_duration_minutes"] = (
|
||||||
|
service_stats["total_duration_minutes"] / service_stats["total_outages"]
|
||||||
|
)
|
||||||
|
if duration_minutes > service_stats["longest_outage_minutes"]:
|
||||||
|
service_stats["longest_outage_minutes"] = duration_minutes
|
||||||
|
|
||||||
|
def update_summary_stats(self):
|
||||||
|
"""Aktualisiert die Zusammenfassungs-Statistiken"""
|
||||||
|
active_outages = [o for o in self.stats["outages"] if o["status"] == "active"]
|
||||||
|
resolved_outages = [o for o in self.stats["outages"] if o["status"] == "resolved"]
|
||||||
|
|
||||||
|
total_duration = sum(o.get("duration_minutes", 0) for o in resolved_outages)
|
||||||
|
durations = [o.get("duration_minutes", 0) for o in resolved_outages if o.get("duration_minutes")]
|
||||||
|
|
||||||
|
self.stats["summary"] = {
|
||||||
|
"total_outages": len(resolved_outages),
|
||||||
|
"active_outages": len(active_outages),
|
||||||
|
"total_duration_minutes": total_duration,
|
||||||
|
"average_duration_minutes": statistics.mean(durations) if durations else 0,
|
||||||
|
"longest_outage_minutes": max(durations) if durations else 0,
|
||||||
|
"most_affected_service": self.get_most_affected_service(),
|
||||||
|
"last_updated": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_most_affected_service(self):
|
||||||
|
"""Ermittelt den am stärksten betroffenen Service"""
|
||||||
|
if not self.stats["services"]:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
most_affected = max(
|
||||||
|
self.stats["services"].items(),
|
||||||
|
key=lambda x: x[1]["total_outages"]
|
||||||
|
)
|
||||||
|
return most_affected[0]
|
||||||
|
|
||||||
|
def get_recent_outages(self, days=30):
|
||||||
|
"""Gibt Störungen der letzten X Tage zurück"""
|
||||||
|
cutoff_date = datetime.now() - timedelta(days=days)
|
||||||
|
recent_outages = []
|
||||||
|
|
||||||
|
for outage in self.stats["outages"]:
|
||||||
|
if outage["status"] == "resolved" and outage["end_time"]:
|
||||||
|
end_time = datetime.fromisoformat(outage["end_time"])
|
||||||
|
if end_time >= cutoff_date:
|
||||||
|
recent_outages.append(outage)
|
||||||
|
|
||||||
|
return sorted(recent_outages, key=lambda x: x["end_time"], reverse=True)
|
||||||
|
|
||||||
|
def get_outage_trends(self, days=30):
|
||||||
|
"""Analysiert Trends in den Störungen"""
|
||||||
|
recent_outages = self.get_recent_outages(days)
|
||||||
|
|
||||||
|
# Gruppiere nach Datum
|
||||||
|
daily_outages = defaultdict(list)
|
||||||
|
for outage in recent_outages:
|
||||||
|
date = outage["end_time"][:10] # YYYY-MM-DD
|
||||||
|
daily_outages[date].append(outage)
|
||||||
|
|
||||||
|
# Berechne Durchschnitt pro Tag
|
||||||
|
daily_counts = [len(outages) for outages in daily_outages.values()]
|
||||||
|
daily_durations = []
|
||||||
|
for outages in daily_outages.values():
|
||||||
|
daily_duration = sum(o.get("duration_minutes", 0) for o in outages)
|
||||||
|
daily_durations.append(daily_duration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"daily_average_outages": statistics.mean(daily_counts) if daily_counts else 0,
|
||||||
|
"daily_average_duration": statistics.mean(daily_durations) if daily_durations else 0,
|
||||||
|
"total_days_with_outages": len(daily_outages),
|
||||||
|
"most_outages_in_day": max(daily_counts) if daily_counts else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_statistics_report(self):
|
||||||
|
"""Generiert einen detaillierten Statistik-Bericht"""
|
||||||
|
self.update_summary_stats()
|
||||||
|
|
||||||
|
report = []
|
||||||
|
report.append("📊 **TI-Status Ausfall-Statistiken**")
|
||||||
|
report.append("=" * 50)
|
||||||
|
|
||||||
|
# Zusammenfassung
|
||||||
|
summary = self.stats["summary"]
|
||||||
|
report.append(f"**Zusammenfassung:**")
|
||||||
|
report.append(f"• Gesamte Störungen: {summary['total_outages']}")
|
||||||
|
report.append(f"• Aktive Störungen: {summary['active_outages']}")
|
||||||
|
report.append(f"• Gesamtdauer: {summary['total_duration_minutes']} Minuten")
|
||||||
|
report.append(f"• Durchschnittsdauer: {summary['average_duration_minutes']:.1f} Minuten")
|
||||||
|
report.append(f"• Längste Störung: {summary['longest_outage_minutes']} Minuten")
|
||||||
|
report.append(f"• Am stärksten betroffen: {summary['most_affected_service']}")
|
||||||
|
report.append("")
|
||||||
|
|
||||||
|
# Service-spezifische Statistiken
|
||||||
|
if self.stats["services"]:
|
||||||
|
report.append("**Service-Statistiken:**")
|
||||||
|
for service, stats in sorted(
|
||||||
|
self.stats["services"].items(),
|
||||||
|
key=lambda x: x[1]["total_outages"],
|
||||||
|
reverse=True
|
||||||
|
)[:10]: # Top 10 Services
|
||||||
|
report.append(f"• **{service.upper()}**:")
|
||||||
|
report.append(f" - Störungen: {stats['total_outages']}")
|
||||||
|
report.append(f" - Gesamtdauer: {stats['total_duration_minutes']} Min")
|
||||||
|
report.append(f" - Durchschnitt: {stats['average_duration_minutes']:.1f} Min")
|
||||||
|
report.append(f" - Längste: {stats['longest_outage_minutes']} Min")
|
||||||
|
report.append("")
|
||||||
|
|
||||||
|
# Trends der letzten 30 Tage
|
||||||
|
trends = self.get_outage_trends(30)
|
||||||
|
report.append("**Trends (letzte 30 Tage):**")
|
||||||
|
report.append(f"• Durchschnitt Störungen/Tag: {trends['daily_average_outages']:.1f}")
|
||||||
|
report.append(f"• Durchschnitt Dauer/Tag: {trends['daily_average_duration']:.1f} Min")
|
||||||
|
report.append(f"• Tage mit Störungen: {trends['total_days_with_outages']}")
|
||||||
|
report.append(f"• Max. Störungen an einem Tag: {trends['most_outages_in_day']}")
|
||||||
|
report.append("")
|
||||||
|
|
||||||
|
# Letzte Störungen
|
||||||
|
recent_outages = self.get_recent_outages(7) # Letzte 7 Tage
|
||||||
|
if recent_outages:
|
||||||
|
report.append("**Letzte Störungen (7 Tage):**")
|
||||||
|
for outage in recent_outages[:5]: # Top 5
|
||||||
|
duration = outage.get("duration_minutes", 0)
|
||||||
|
start_time = datetime.fromisoformat(outage["start_time"]).strftime("%d.%m. %H:%M")
|
||||||
|
end_time = datetime.fromisoformat(outage["end_time"]).strftime("%d.%m. %H:%M")
|
||||||
|
report.append(f"• **{outage['service'].upper()}** ({outage['type']})")
|
||||||
|
report.append(f" - {start_time} - {end_time} ({duration} Min)")
|
||||||
|
|
||||||
|
report.append("")
|
||||||
|
report.append(f"_Bericht generiert am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}_")
|
||||||
|
|
||||||
|
return "\n".join(report)
|
||||||
@@ -6,6 +6,8 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import apprise
|
import apprise
|
||||||
import time
|
import time
|
||||||
|
import argparse
|
||||||
|
from ti_statistics import OutageStatistics
|
||||||
|
|
||||||
# Lade Umgebungsvariablen aus .env Datei
|
# Lade Umgebungsvariablen aus .env Datei
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -16,8 +18,8 @@ def is_debug_mode():
|
|||||||
|
|
||||||
def get_ti_api_url():
|
def get_ti_api_url():
|
||||||
"""Gibt die API-URL zurück, im Debug-Modus ggf. die lokale Test-API"""
|
"""Gibt die API-URL zurück, im Debug-Modus ggf. die lokale Test-API"""
|
||||||
if is_debug_mode():
|
# if is_debug_mode():
|
||||||
return os.getenv("TI_API_URL_DEBUG", "http://localhost:8000/lageapi/v2/tilage")
|
# return os.getenv("TI_API_URL_DEBUG", "http://localhost:8000/lageapi/v2/tilage")
|
||||||
return os.getenv("TI_API_URL", "https://ti-lage.prod.ccs.gematik.solutions/lageapi/v2/tilage")
|
return os.getenv("TI_API_URL", "https://ti-lage.prod.ccs.gematik.solutions/lageapi/v2/tilage")
|
||||||
|
|
||||||
TI_API_URL = get_ti_api_url()
|
TI_API_URL = get_ti_api_url()
|
||||||
@@ -152,19 +154,20 @@ def fetch_status_messages():
|
|||||||
has_maintenance = status.get("hasMaintenance", False)
|
has_maintenance = status.get("hasMaintenance", False)
|
||||||
sub_maintenance = status.get("hasSubComponentMaintenance", False)
|
sub_maintenance = status.get("hasSubComponentMaintenance", False)
|
||||||
affected = status.get("affectedFunctions", [])
|
affected = status.get("affectedFunctions", [])
|
||||||
# Baue eine verständliche Meldung
|
# Baue eine verständliche Meldung mit Zeitstempel
|
||||||
msg = f"Störung bei {dienst.upper()} ({'Wartung' if has_maintenance else 'Störung'}): Status: {outage}"
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
msg_core = f"Störung bei {dienst.upper()} ({'Wartung' if has_maintenance else 'Störung'}): Status: {outage}"
|
||||||
if affected:
|
if affected:
|
||||||
for func in affected:
|
for func in affected:
|
||||||
func_name = func.get("function", "Unbekannte Funktion")
|
func_name = func.get("function", "Unbekannte Funktion")
|
||||||
impact = func.get("impactDesc", "")
|
impact = func.get("impactDesc", "")
|
||||||
func_outage = func.get("outage", outage)
|
func_outage = func.get("outage", outage)
|
||||||
msg += f"\n- {func_name}: {impact} (Status: {func_outage})"
|
msg_core += f"\n- {func_name}: {impact} (Status: {func_outage})"
|
||||||
else:
|
else:
|
||||||
msg += "\n- Keine weiteren Details."
|
msg_core += "\n- Keine weiteren Details."
|
||||||
messages.append(msg)
|
messages.append(msg_core)
|
||||||
if is_debug_mode():
|
if is_debug_mode():
|
||||||
print(f"Erkannte Störung: {msg[:80]}...")
|
print(f"Erkannte Störung: {msg_core[:80]}...")
|
||||||
if is_debug_mode():
|
if is_debug_mode():
|
||||||
print(f"Insgesamt {len(messages)} Meldungen verarbeitet")
|
print(f"Insgesamt {len(messages)} Meldungen verarbeitet")
|
||||||
return messages
|
return messages
|
||||||
@@ -174,12 +177,21 @@ def load_state():
|
|||||||
with open(STATE_FILE, "r", encoding="utf-8") as f:
|
with open(STATE_FILE, "r", encoding="utf-8") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
return {"messages": []}
|
return {"messages": [], "last_status": {}}
|
||||||
|
|
||||||
def save_state(state):
|
def save_state(state):
|
||||||
with open(STATE_FILE, "w", encoding="utf-8") as f:
|
with open(STATE_FILE, "w", encoding="utf-8") as f:
|
||||||
json.dump(state, f, ensure_ascii=False, indent=2)
|
json.dump(state, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
def extract_outages(app_status):
|
||||||
|
"""Extrahiert alle aktuellen Störungen als Set von Strings"""
|
||||||
|
outages = set()
|
||||||
|
for dienst, status in app_status.items():
|
||||||
|
outage = status.get("outage", "none")
|
||||||
|
if outage in ("partial", "full"):
|
||||||
|
outages.add(dienst)
|
||||||
|
return outages
|
||||||
|
|
||||||
def markdownify_message(message):
|
def markdownify_message(message):
|
||||||
# Datumsangaben fett hervorheben (z.B. 2025-06-23 oder 23.06.2025)
|
# Datumsangaben fett hervorheben (z.B. 2025-06-23 oder 23.06.2025)
|
||||||
message = re.sub(r"(\d{4}-\d{2}-\d{2})", r"**\1**", message)
|
message = re.sub(r"(\d{4}-\d{2}-\d{2})", r"**\1**", message)
|
||||||
@@ -228,8 +240,12 @@ def send_notification(message):
|
|||||||
|
|
||||||
# Erstelle die Nachricht
|
# Erstelle die Nachricht
|
||||||
title = "Neue TI-Status-Meldung"
|
title = "Neue TI-Status-Meldung"
|
||||||
|
# Zeitstempel nur für die Anzeige, nicht für Duplikatserkennung
|
||||||
body = f"{md_message}\n\n[Zur Statusseite](https://fachportal.gematik.de/ti-status)\n_Gemeldet am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}_"
|
body = f"{md_message}\n\n[Zur Statusseite](https://fachportal.gematik.de/ti-status)\n_Gemeldet am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}_"
|
||||||
|
|
||||||
|
# Setze den Absender-Namen
|
||||||
|
sender_name = "TI-Status Bot by medisoftware"
|
||||||
|
|
||||||
if is_debug_mode():
|
if is_debug_mode():
|
||||||
print(f"📤 Sende Benachrichtigung an {len(urls)} Endpunkt(e)")
|
print(f"📤 Sende Benachrichtigung an {len(urls)} Endpunkt(e)")
|
||||||
print(f"Verwendete Apprise-URLs: {urls}")
|
print(f"Verwendete Apprise-URLs: {urls}")
|
||||||
@@ -263,7 +279,66 @@ def send_notification(message):
|
|||||||
print(f"⏳ Warte {delay} Sekunden...")
|
print(f"⏳ Warte {delay} Sekunden...")
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
|
def send_statistics_notification():
|
||||||
|
"""Sendet Statistik-Bericht über Apprise"""
|
||||||
|
print("📊 Generiere und sende Statistik-Bericht...")
|
||||||
|
|
||||||
|
# Erstelle Statistik-Objekt
|
||||||
|
stats = OutageStatistics()
|
||||||
|
|
||||||
|
# Generiere Bericht
|
||||||
|
report = stats.generate_statistics_report()
|
||||||
|
|
||||||
|
# Hole alle konfigurierten URLs
|
||||||
|
urls = get_apprise_urls()
|
||||||
|
if not urls:
|
||||||
|
print("❌ Keine Apprise URLs konfiguriert!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Erstelle Apprise Objekt
|
||||||
|
apobj = apprise.Apprise()
|
||||||
|
|
||||||
|
# Füge alle URLs hinzu
|
||||||
|
for url in urls:
|
||||||
|
apobj.add(url)
|
||||||
|
|
||||||
|
# Erstelle die Nachricht
|
||||||
|
title = "📊 TI-Status Ausfall-Statistiken"
|
||||||
|
body = report
|
||||||
|
|
||||||
|
if is_debug_mode():
|
||||||
|
print(f"📤 Sende Statistik-Bericht an {len(urls)} Endpunkt(e)")
|
||||||
|
print(f"Bericht-Länge: {len(report)} Zeichen")
|
||||||
|
|
||||||
|
# Sende die Nachricht
|
||||||
|
try:
|
||||||
|
result = apobj.notify(
|
||||||
|
title=title,
|
||||||
|
body=body,
|
||||||
|
body_format=apprise.NotifyFormat.MARKDOWN
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
print("✅ Statistik-Bericht erfolgreich gesendet")
|
||||||
|
else:
|
||||||
|
print("❌ Fehler beim Senden des Statistik-Berichts")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Fehler beim Senden des Statistik-Berichts: {e}")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
# Parse Kommandozeilen-Argumente
|
||||||
|
parser = argparse.ArgumentParser(description="TI-Status Checker mit Statistik-Funktionalität")
|
||||||
|
parser.add_argument("--stats", "-s", action="store_true",
|
||||||
|
help="Generiert und sendet Statistik-Bericht über Apprise")
|
||||||
|
parser.add_argument("--debug", "-d", action="store_true",
|
||||||
|
help="Aktiviert Debug-Modus")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Wenn --stats gesetzt ist, sende nur Statistiken
|
||||||
|
if args.stats:
|
||||||
|
send_statistics_notification()
|
||||||
|
return
|
||||||
|
|
||||||
# Prüfe Konfiguration
|
# Prüfe Konfiguration
|
||||||
urls = get_apprise_urls()
|
urls = get_apprise_urls()
|
||||||
if not urls:
|
if not urls:
|
||||||
@@ -271,7 +346,7 @@ def main():
|
|||||||
print("Bitte erstelle eine .env Datei basierend auf env.example")
|
print("Bitte erstelle eine .env Datei basierend auf env.example")
|
||||||
return
|
return
|
||||||
|
|
||||||
if is_debug_mode():
|
if is_debug_mode() or args.debug:
|
||||||
print("🔧 Debug-Modus aktiviert")
|
print("🔧 Debug-Modus aktiviert")
|
||||||
print(f"📋 Benachrichtigungslevel: {get_notification_level()}")
|
print(f"📋 Benachrichtigungslevel: {get_notification_level()}")
|
||||||
print(f"🔍 Filter: {get_notification_filters()}")
|
print(f"🔍 Filter: {get_notification_filters()}")
|
||||||
@@ -280,23 +355,54 @@ def main():
|
|||||||
|
|
||||||
state = load_state()
|
state = load_state()
|
||||||
known_messages = set(state.get("messages", []))
|
known_messages = set(state.get("messages", []))
|
||||||
|
last_status = state.get("last_status", {})
|
||||||
print("Prüfe TI-Status-API auf neue Meldungen...")
|
print("Prüfe TI-Status-API auf neue Meldungen...")
|
||||||
|
|
||||||
|
# Erstelle Statistik-Objekt für die Aufzeichnung von Störungen
|
||||||
|
stats = OutageStatistics()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
messages = fetch_status_messages()
|
messages = fetch_status_messages()
|
||||||
new_messages = [m for m in messages if m not in known_messages]
|
# Extrahiere aktuelle und vorherige Störungen
|
||||||
|
resp = requests.get(TI_API_URL)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
app_status = data.get("appStatus", {})
|
||||||
|
current_outages = extract_outages(app_status)
|
||||||
|
previous_outages = extract_outages(last_status.get("appStatus", {})) if last_status else set()
|
||||||
|
|
||||||
|
# Neue Störungen aufzeichnen
|
||||||
|
new_outages = current_outages - previous_outages
|
||||||
|
for dienst in new_outages:
|
||||||
|
# Bestimme den Störungstyp
|
||||||
|
outage_type = "full" if app_status.get(dienst, {}).get("outage") == "full" else "partial"
|
||||||
|
stats.record_outage_start(dienst, outage_type)
|
||||||
|
if is_debug_mode() or args.debug:
|
||||||
|
print(f"📊 Neue Störung aufgezeichnet: {dienst} ({outage_type})")
|
||||||
|
|
||||||
|
# Entwarnungen erkennen und aufzeichnen
|
||||||
|
resolved = previous_outages - current_outages
|
||||||
|
for dienst in resolved:
|
||||||
|
stats.record_outage_end(dienst)
|
||||||
|
msg = f"✅ Entwarnung: Die Störung bei {dienst.upper()} wurde behoben."
|
||||||
|
print(msg)
|
||||||
|
send_notification(msg)
|
||||||
|
known_messages.add(msg)
|
||||||
|
if is_debug_mode() or args.debug:
|
||||||
|
print(f"📊 Störung beendet aufgezeichnet: {dienst}")
|
||||||
|
|
||||||
|
# Normale neue Meldungen
|
||||||
|
new_messages = [m for m in messages if m not in known_messages]
|
||||||
for msg in new_messages:
|
for msg in new_messages:
|
||||||
print(f"Neue Meldung gefunden: {msg[:100]}...")
|
print(f"Neue Meldung gefunden: {msg[:100]}...")
|
||||||
send_notification(msg)
|
send_notification(msg)
|
||||||
known_messages.add(msg)
|
known_messages.add(msg)
|
||||||
|
|
||||||
if new_messages:
|
if new_messages or resolved:
|
||||||
save_state({"messages": list(known_messages)})
|
save_state({"messages": list(known_messages), "last_status": data})
|
||||||
print(f"✅ {len(new_messages)} neue Meldung(en) verarbeitet")
|
print(f"✅ {len(new_messages)} neue Meldung(en) und {len(resolved)} Entwarnung(en) verarbeitet")
|
||||||
else:
|
else:
|
||||||
print(f"Keine neuen Meldungen ({datetime.now().strftime('%H:%M:%S')})")
|
print(f"Keine neuen Meldungen ({datetime.now().strftime('%H:%M:%S')})")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Fehler: {e}")
|
print(f"Fehler: {e}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user