Add web-based GUI with frontend and backend
Co-authored-by: Kenearos <86194771+Kenearos@users.noreply.github.com>
This commit is contained in:
parent
c269cb0ef0
commit
be9ed433b6
7 changed files with 1732 additions and 1 deletions
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Flask
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Config files with sensitive data
|
||||||
|
config.json
|
||||||
|
|
||||||
|
# Downloads directory
|
||||||
|
downloads/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
340
ANLEITUNG.md
Normal file
340
ANLEITUNG.md
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
# Anleitung - Home Assistant Overview Tool
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Dieses Tool bietet eine webbasierte Benutzeroberfläche (GUI) mit Frontend und Backend, um einen vollständigen Überblick über Ihre Home Assistant Installation zu erhalten.
|
||||||
|
|
||||||
|
## Was macht dieses Tool?
|
||||||
|
|
||||||
|
Das Tool erstellt eine vollständige Dokumentation von:
|
||||||
|
- Alle installierten Integrationen/Komponenten
|
||||||
|
- Alle Entitäten sortiert nach Domain (z.B. light, switch, sensor, etc.)
|
||||||
|
- Verfügbare Services
|
||||||
|
- System-Informationen
|
||||||
|
- Detaillierte Statistiken
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
1. **Python 3.7 oder höher** installiert
|
||||||
|
2. **Home Assistant** läuft und ist erreichbar
|
||||||
|
3. **Long-Lived Access Token** von Home Assistant
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Schritt 1: Repository klonen oder herunterladen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Kenearos/Homeassistant.git
|
||||||
|
cd Homeassistant
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 2: Abhängigkeiten installieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Oder manuell:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install Flask requests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Home Assistant Token erstellen
|
||||||
|
|
||||||
|
### Schritt-für-Schritt:
|
||||||
|
|
||||||
|
1. **Öffnen Sie Ihre Home Assistant Web-Oberfläche**
|
||||||
|
- Im Browser: `http://homeassistant.local:8123` (oder Ihre IP-Adresse)
|
||||||
|
|
||||||
|
2. **Melden Sie sich an**
|
||||||
|
- Mit Ihren Home Assistant Zugangsdaten
|
||||||
|
|
||||||
|
3. **Öffnen Sie Ihr Profil**
|
||||||
|
- Klicken Sie auf das Profilsymbol (unten links in der Seitenleiste)
|
||||||
|
- Oder navigieren Sie zu: Einstellungen → Personen → Ihr Name
|
||||||
|
|
||||||
|
4. **Erstellen Sie einen Long-Lived Access Token**
|
||||||
|
- Scrollen Sie nach unten zum Abschnitt "Long-Lived Access Tokens"
|
||||||
|
- Klicken Sie auf "TOKEN ERSTELLEN"
|
||||||
|
- Geben Sie einen Namen ein (z.B. "Overview Tool")
|
||||||
|
- Klicken Sie auf "OK"
|
||||||
|
|
||||||
|
5. **Token kopieren**
|
||||||
|
- **WICHTIG:** Der Token wird nur EINMAL angezeigt!
|
||||||
|
- Kopieren Sie den Token sofort
|
||||||
|
- Speichern Sie ihn sicher (z.B. in einem Passwort-Manager)
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
### Option 1: Web-GUI (Empfohlen) 🌐
|
||||||
|
|
||||||
|
#### Server starten:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Server startet auf `http://localhost:5000`
|
||||||
|
|
||||||
|
#### Im Browser öffnen:
|
||||||
|
|
||||||
|
1. Öffnen Sie Ihren Browser
|
||||||
|
2. Gehen Sie zu: `http://localhost:5000`
|
||||||
|
3. Sie sehen die Web-Oberfläche mit integrierter Anleitung
|
||||||
|
|
||||||
|
#### Bericht erstellen:
|
||||||
|
|
||||||
|
1. **Home Assistant URL eingeben**
|
||||||
|
- Beispiel: `http://homeassistant.local:8123`
|
||||||
|
- Oder: `http://192.168.1.100:8123`
|
||||||
|
|
||||||
|
2. **Token eingeben**
|
||||||
|
- Fügen Sie Ihren Long-Lived Access Token ein
|
||||||
|
|
||||||
|
3. **Optional: Konfiguration speichern**
|
||||||
|
- Aktivieren Sie die Checkbox "Konfiguration speichern"
|
||||||
|
- Beim nächsten Start werden die Daten automatisch geladen
|
||||||
|
|
||||||
|
4. **Verbindung testen**
|
||||||
|
- Klicken Sie auf "Verbindung testen"
|
||||||
|
- Warten Sie auf die Bestätigung
|
||||||
|
|
||||||
|
5. **Bericht generieren**
|
||||||
|
- Klicken Sie auf "Bericht generieren"
|
||||||
|
- Der Bericht wird erstellt und angezeigt
|
||||||
|
|
||||||
|
6. **Bericht herunterladen (optional)**
|
||||||
|
- Klicken Sie auf "Als JSON herunterladen" oder "Als Text herunterladen"
|
||||||
|
- Die Datei wird automatisch heruntergeladen
|
||||||
|
|
||||||
|
### Option 2: Kommandozeile (Original-Skript)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python ha-overview.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Sie werden nach URL und Token gefragt. Der Bericht wird als JSON, TXT und HTML gespeichert.
|
||||||
|
|
||||||
|
## Ausgabeformate
|
||||||
|
|
||||||
|
### JSON
|
||||||
|
- Maschinenlesbar
|
||||||
|
- Enthält alle Details
|
||||||
|
- Ideal für Weiterverarbeitung
|
||||||
|
|
||||||
|
### TXT
|
||||||
|
- Menschenlesbar
|
||||||
|
- Strukturierte Textausgabe
|
||||||
|
- Ideal für Dokumentation
|
||||||
|
|
||||||
|
### HTML (nur bei Kommandozeilen-Tool)
|
||||||
|
- Webseite mit schöner Darstellung
|
||||||
|
- Alle Informationen übersichtlich
|
||||||
|
- Kann im Browser geöffnet werden
|
||||||
|
|
||||||
|
## Beispiel-Workflow
|
||||||
|
|
||||||
|
### Szenario: Erste Verwendung
|
||||||
|
|
||||||
|
1. **Installation durchführen**
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Token in Home Assistant erstellen**
|
||||||
|
- Profil → Long-Lived Access Tokens → TOKEN ERSTELLEN
|
||||||
|
|
||||||
|
3. **Web-GUI starten**
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Browser öffnen**
|
||||||
|
- Gehe zu `http://localhost:5000`
|
||||||
|
|
||||||
|
5. **Daten eingeben**
|
||||||
|
- URL: `http://homeassistant.local:8123`
|
||||||
|
- Token: (Ihr Token)
|
||||||
|
- ✓ Konfiguration speichern
|
||||||
|
|
||||||
|
6. **Verbindung testen**
|
||||||
|
- Klick auf "Verbindung testen"
|
||||||
|
- Warte auf grüne Bestätigung
|
||||||
|
|
||||||
|
7. **Bericht erstellen**
|
||||||
|
- Klick auf "Bericht generieren"
|
||||||
|
- Warte bis Statistiken angezeigt werden
|
||||||
|
|
||||||
|
8. **Download (optional)**
|
||||||
|
- Klick auf "Als JSON herunterladen"
|
||||||
|
|
||||||
|
### Szenario: Regelmäßige Nutzung
|
||||||
|
|
||||||
|
Wenn Sie die Konfiguration gespeichert haben:
|
||||||
|
|
||||||
|
1. **Server starten**
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Browser öffnen**
|
||||||
|
- Gehe zu `http://localhost:5000`
|
||||||
|
- URL ist bereits ausgefüllt!
|
||||||
|
|
||||||
|
3. **Token eingeben**
|
||||||
|
- Fügen Sie nur noch Ihren Token ein
|
||||||
|
|
||||||
|
4. **Bericht generieren**
|
||||||
|
- Direkt auf "Bericht generieren" klicken
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
### Problem: "Verbindung fehlgeschlagen"
|
||||||
|
|
||||||
|
**Mögliche Ursachen:**
|
||||||
|
1. Home Assistant läuft nicht
|
||||||
|
2. Falsche URL
|
||||||
|
3. Ungültiger Token
|
||||||
|
4. Firewall blockiert die Verbindung
|
||||||
|
|
||||||
|
**Lösungen:**
|
||||||
|
- Überprüfen Sie, ob Home Assistant erreichbar ist
|
||||||
|
- Testen Sie die URL im Browser
|
||||||
|
- Erstellen Sie einen neuen Token
|
||||||
|
- Überprüfen Sie Firewall-Einstellungen
|
||||||
|
|
||||||
|
### Problem: "ModuleNotFoundError: No module named 'flask'"
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
```bash
|
||||||
|
pip install Flask
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: "Port bereits in Verwendung"
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
Ändern Sie den Port in `app.py`:
|
||||||
|
```python
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=5001) # Statt 5000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: Token wird nicht akzeptiert
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
1. Erstellen Sie einen neuen Token in Home Assistant
|
||||||
|
2. Kopieren Sie den gesamten Token (ohne Leerzeichen)
|
||||||
|
3. Probieren Sie es erneut
|
||||||
|
|
||||||
|
## Sicherheitshinweise
|
||||||
|
|
||||||
|
⚠️ **WICHTIG:**
|
||||||
|
|
||||||
|
1. **Token niemals teilen**
|
||||||
|
- Der Token gibt vollen Zugriff auf Home Assistant
|
||||||
|
- Behandeln Sie ihn wie ein Passwort
|
||||||
|
|
||||||
|
2. **Token sicher speichern**
|
||||||
|
- Verwenden Sie einen Passwort-Manager
|
||||||
|
- Speichern Sie ihn nicht in öffentlichen Repositories
|
||||||
|
|
||||||
|
3. **HTTPS verwenden (in Produktion)**
|
||||||
|
- Für lokale Tests ist HTTP OK
|
||||||
|
- Bei Remote-Zugriff: Verwenden Sie HTTPS
|
||||||
|
|
||||||
|
4. **Regelmäßig Token erneuern**
|
||||||
|
- Alte Token deaktivieren
|
||||||
|
- Neue Token erstellen
|
||||||
|
|
||||||
|
## Erweiterte Optionen
|
||||||
|
|
||||||
|
### Remote-Zugriff aktivieren
|
||||||
|
|
||||||
|
Standardmäßig läuft der Server nur lokal. Für Zugriff aus dem Netzwerk:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# In app.py:
|
||||||
|
app.run(debug=False, host='0.0.0.0', port=5000)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Achtung:** Nur in vertrauenswürdigen Netzwerken!
|
||||||
|
|
||||||
|
### Automatischer Start beim Systemstart
|
||||||
|
|
||||||
|
Erstellen Sie einen Systemd-Service (Linux):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/systemd/system/ha-overview.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Inhalt:
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Home Assistant Overview Web GUI
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=IhrBenutzer
|
||||||
|
WorkingDirectory=/pfad/zum/Homeassistant
|
||||||
|
ExecStart=/usr/bin/python3 /pfad/zum/Homeassistant/app.py
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Aktivieren:
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable ha-overview.service
|
||||||
|
sudo systemctl start ha-overview.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### F: Kann ich das Tool auf einem anderen Computer nutzen?
|
||||||
|
|
||||||
|
**A:** Ja! Sie können den Server auf einem Computer starten und von einem anderen im selben Netzwerk darauf zugreifen. Verwenden Sie die IP-Adresse des Servers.
|
||||||
|
|
||||||
|
### F: Werden meine Daten gespeichert?
|
||||||
|
|
||||||
|
**A:** Nur wenn Sie "Konfiguration speichern" aktivieren. Dann werden URL und Token in `config.json` gespeichert. Berichte werden nur temporär erstellt.
|
||||||
|
|
||||||
|
### F: Kann ich mehrere Home Assistant Instanzen verwalten?
|
||||||
|
|
||||||
|
**A:** Ja, indem Sie für jede Instanz andere URL und Token eingeben. Die Konfiguration speichert nur eine Instanz, aber Sie können manuell wechseln.
|
||||||
|
|
||||||
|
### F: Funktioniert das mit Home Assistant OS?
|
||||||
|
|
||||||
|
**A:** Ja! Das Tool läuft unabhängig von Home Assistant und benötigt nur Netzwerkzugriff zur API.
|
||||||
|
|
||||||
|
### F: Wie oft sollte ich den Bericht aktualisieren?
|
||||||
|
|
||||||
|
**A:** Nach Bedarf. Bei Änderungen an Ihrer Installation (neue Geräte, Integrationen) generieren Sie einen neuen Bericht.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Bei Problemen:
|
||||||
|
1. Überprüfen Sie die Fehlerbehebung oben
|
||||||
|
2. Erstellen Sie ein Issue auf GitHub
|
||||||
|
3. Geben Sie Details an:
|
||||||
|
- Fehlermeldung
|
||||||
|
- Python-Version
|
||||||
|
- Home Assistant Version
|
||||||
|
- Betriebssystem
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
MIT License - Frei verwendbar
|
||||||
|
|
||||||
|
## Danksagungen
|
||||||
|
|
||||||
|
- Home Assistant Community
|
||||||
|
- Flask Framework
|
||||||
|
- Alle Mitwirkenden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Letzte Aktualisierung:** November 2024
|
||||||
|
**Autor:** Kenearos
|
||||||
128
README.md
128
README.md
|
|
@ -1 +1,127 @@
|
||||||
# Homeassistant
|
# Home Assistant Overview Tool
|
||||||
|
|
||||||
|
Ein webbasiertes Tool mit Frontend und Backend zur Erstellung einer vollständigen Übersicht über Ihre Home Assistant Installation.
|
||||||
|
|
||||||
|
## 🌟 Features
|
||||||
|
|
||||||
|
- 🌐 **Web-GUI** - Moderne, benutzerfreundliche Weboberfläche
|
||||||
|
- 📖 **Integrierte Anleitung** - Schritt-für-Schritt Anweisungen direkt im Frontend
|
||||||
|
- 🔄 **Echtzeit-Verbindungstest** - Testen Sie Ihre Verbindung vor der Berichtserstellung
|
||||||
|
- 💾 **Konfigurationsspeicherung** - Speichern Sie URL und Token für spätere Verwendung
|
||||||
|
- 📊 **Detaillierte Statistiken** - Komponenten, Entitäten, Services, Domains
|
||||||
|
- 📥 **Export-Funktionen** - Download als JSON oder TXT
|
||||||
|
- 🎨 **Responsive Design** - Funktioniert auf Desktop und Mobile
|
||||||
|
|
||||||
|
## 🚀 Schnellstart
|
||||||
|
|
||||||
|
### 1. Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Kenearos/Homeassistant.git
|
||||||
|
cd Homeassistant
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Server starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Browser öffnen
|
||||||
|
|
||||||
|
Öffnen Sie `http://localhost:5000` in Ihrem Browser.
|
||||||
|
|
||||||
|
## 📚 Dokumentation
|
||||||
|
|
||||||
|
Für eine ausführliche Anleitung auf Deutsch, siehe [ANLEITUNG.md](ANLEITUNG.md).
|
||||||
|
|
||||||
|
Die Anleitung enthält:
|
||||||
|
- Schritt-für-Schritt Installation
|
||||||
|
- Home Assistant Token erstellen
|
||||||
|
- Verwendung der Web-GUI
|
||||||
|
- Kommandozeilen-Tool
|
||||||
|
- Fehlerbehebung
|
||||||
|
- Sicherheitshinweise
|
||||||
|
- FAQ
|
||||||
|
|
||||||
|
## 🔑 Home Assistant Token erstellen
|
||||||
|
|
||||||
|
1. Öffnen Sie Home Assistant
|
||||||
|
2. Klicken Sie auf Ihr Profil (unten links)
|
||||||
|
3. Scrollen Sie zu "Long-Lived Access Tokens"
|
||||||
|
4. Klicken Sie auf "TOKEN ERSTELLEN"
|
||||||
|
5. Geben Sie einen Namen ein
|
||||||
|
6. Kopieren Sie den Token (wird nur einmal angezeigt!)
|
||||||
|
|
||||||
|
## 💻 Verwendung
|
||||||
|
|
||||||
|
### Web-GUI (Empfohlen)
|
||||||
|
|
||||||
|
1. Server starten: `python app.py`
|
||||||
|
2. Browser öffnen: `http://localhost:5000`
|
||||||
|
3. URL und Token eingeben
|
||||||
|
4. "Bericht generieren" klicken
|
||||||
|
5. Optional: Bericht herunterladen
|
||||||
|
|
||||||
|
### Kommandozeile
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python ha-overview.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Sie werden interaktiv nach URL und Token gefragt.
|
||||||
|
|
||||||
|
## 📋 Anforderungen
|
||||||
|
|
||||||
|
- Python 3.7+
|
||||||
|
- Flask
|
||||||
|
- requests
|
||||||
|
- Zugriff auf Home Assistant API
|
||||||
|
- Long-Lived Access Token
|
||||||
|
|
||||||
|
## 🛠️ Technische Details
|
||||||
|
|
||||||
|
### Architektur
|
||||||
|
|
||||||
|
- **Backend:** Flask (Python)
|
||||||
|
- **Frontend:** HTML/CSS/JavaScript
|
||||||
|
- **API-Kommunikation:** REST API mit Home Assistant
|
||||||
|
- **Datenspeicherung:** JSON (optional)
|
||||||
|
|
||||||
|
### Verzeichnisstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
Homeassistant/
|
||||||
|
├── app.py # Flask Backend-Server
|
||||||
|
├── ha-overview.py # Original Kommandozeilen-Tool
|
||||||
|
├── ha_overview.py # Modul-Version
|
||||||
|
├── templates/
|
||||||
|
│ └── index.html # Frontend Web-GUI
|
||||||
|
├── requirements.txt # Python-Abhängigkeiten
|
||||||
|
├── ANLEITUNG.md # Ausführliche deutsche Anleitung
|
||||||
|
└── README.md # Diese Datei
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Sicherheit
|
||||||
|
|
||||||
|
- ⚠️ Token niemals teilen oder in öffentlichen Repositories speichern
|
||||||
|
- 🔐 Verwenden Sie HTTPS für Remote-Zugriff
|
||||||
|
- 🔄 Erneuern Sie Token regelmäßig
|
||||||
|
- 🏠 Verwenden Sie das Tool nur in vertrauenswürdigen Netzwerken
|
||||||
|
|
||||||
|
## 🤝 Mitwirken
|
||||||
|
|
||||||
|
Beiträge sind willkommen! Bitte erstellen Sie ein Issue oder Pull Request.
|
||||||
|
|
||||||
|
## 📄 Lizenz
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## 👤 Autor
|
||||||
|
|
||||||
|
Kenearos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Für die vollständige Anleitung auf Deutsch, siehe [ANLEITUNG.md](ANLEITUNG.md)**
|
||||||
149
app.py
Normal file
149
app.py
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Home Assistant Overview - Web Application
|
||||||
|
Flask-basierte Web-Anwendung mit Frontend und Backend
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Flask, render_template, request, jsonify, send_file
|
||||||
|
from ha_overview import HomeAssistantOverview
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Konfigurationsdatei
|
||||||
|
CONFIG_FILE = 'config.json'
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
"""Lade gespeicherte Konfiguration"""
|
||||||
|
if os.path.exists(CONFIG_FILE):
|
||||||
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_config(url, token):
|
||||||
|
"""Speichere Konfiguration"""
|
||||||
|
config = {'url': url, 'token': token}
|
||||||
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
|
json.dump(config, f)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
"""Hauptseite mit Anleitung und Formular"""
|
||||||
|
config = load_config()
|
||||||
|
return render_template('index.html',
|
||||||
|
saved_url=config.get('url', ''),
|
||||||
|
has_config=bool(config))
|
||||||
|
|
||||||
|
@app.route('/api/test-connection', methods=['POST'])
|
||||||
|
def test_connection():
|
||||||
|
"""Teste die Verbindung zu Home Assistant"""
|
||||||
|
data = request.json
|
||||||
|
url = data.get('url')
|
||||||
|
token = data.get('token')
|
||||||
|
|
||||||
|
if not url or not token:
|
||||||
|
return jsonify({'success': False, 'error': 'URL und Token sind erforderlich'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
ha = HomeAssistantOverview(url, token)
|
||||||
|
success = ha.test_connection()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Speichere Konfiguration wenn gewünscht
|
||||||
|
if data.get('save_config'):
|
||||||
|
save_config(url, token)
|
||||||
|
|
||||||
|
return jsonify({'success': True, 'message': 'Verbindung erfolgreich!'})
|
||||||
|
else:
|
||||||
|
return jsonify({'success': False, 'error': 'Verbindung fehlgeschlagen'})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@app.route('/api/generate-report', methods=['POST'])
|
||||||
|
def generate_report():
|
||||||
|
"""Generiere vollständigen Bericht"""
|
||||||
|
data = request.json
|
||||||
|
url = data.get('url')
|
||||||
|
token = data.get('token')
|
||||||
|
|
||||||
|
if not url or not token:
|
||||||
|
return jsonify({'success': False, 'error': 'URL und Token sind erforderlich'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
ha = HomeAssistantOverview(url, token)
|
||||||
|
report = ha.generate_report()
|
||||||
|
|
||||||
|
if report:
|
||||||
|
# Speichere Konfiguration wenn gewünscht
|
||||||
|
if data.get('save_config'):
|
||||||
|
save_config(url, token)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'report': report
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({'success': False, 'error': 'Report-Generierung fehlgeschlagen'})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@app.route('/api/download-report', methods=['POST'])
|
||||||
|
def download_report():
|
||||||
|
"""Erstelle und lade Report-Datei herunter"""
|
||||||
|
data = request.json
|
||||||
|
url = data.get('url')
|
||||||
|
token = data.get('token')
|
||||||
|
format_type = data.get('format', 'json')
|
||||||
|
|
||||||
|
if not url or not token:
|
||||||
|
return jsonify({'success': False, 'error': 'URL und Token sind erforderlich'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
ha = HomeAssistantOverview(url, token)
|
||||||
|
report = ha.generate_report()
|
||||||
|
|
||||||
|
if report:
|
||||||
|
# Erstelle temporäres Verzeichnis falls nicht vorhanden
|
||||||
|
os.makedirs('downloads', exist_ok=True)
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
|
||||||
|
if format_type == 'json':
|
||||||
|
filename = f'downloads/ha_overview_{timestamp}.json'
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(report, f, indent=2, ensure_ascii=False)
|
||||||
|
elif format_type == 'txt':
|
||||||
|
filename = f'downloads/ha_overview_{timestamp}.txt'
|
||||||
|
# Vereinfachte Textausgabe
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
f.write("="*80 + "\n")
|
||||||
|
f.write("HOME ASSISTANT - VOLLSTÄNDIGER ÜBERBLICK\n")
|
||||||
|
f.write("="*80 + "\n")
|
||||||
|
f.write(f"Generiert am: {datetime.now().strftime('%d.%m.%Y um %H:%M:%S')}\n")
|
||||||
|
f.write("="*80 + "\n\n")
|
||||||
|
f.write(f"System: {report['system_info']['version']}\n")
|
||||||
|
f.write(f"Entitäten: {report['statistics']['total_entities']}\n")
|
||||||
|
f.write(f"Komponenten: {report['statistics']['total_components']}\n")
|
||||||
|
else:
|
||||||
|
return jsonify({'success': False, 'error': 'Ungültiges Format'})
|
||||||
|
|
||||||
|
return send_file(filename, as_attachment=True)
|
||||||
|
else:
|
||||||
|
return jsonify({'success': False, 'error': 'Report-Generierung fehlgeschlagen'})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Erstelle templates-Verzeichnis falls nicht vorhanden
|
||||||
|
os.makedirs('templates', exist_ok=True)
|
||||||
|
os.makedirs('static', exist_ok=True)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("HOME ASSISTANT OVERVIEW - WEB APPLICATION")
|
||||||
|
print("="*80)
|
||||||
|
print("\nStarte Server auf http://localhost:5000")
|
||||||
|
print("Drücke CTRL+C zum Beenden\n")
|
||||||
|
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||||
488
ha_overview.py
Normal file
488
ha_overview.py
Normal file
|
|
@ -0,0 +1,488 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Home Assistant Complete Overview Tool
|
||||||
|
Erstellt eine vollständige Dokumentation aller Integrationen, Entitäten und Dienste
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
class HomeAssistantOverview:
|
||||||
|
def __init__(self, url, token):
|
||||||
|
"""
|
||||||
|
Initialize Home Assistant connection
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: Home Assistant URL (z.B. http://homeassistant.local:8123)
|
||||||
|
token: Long-Lived Access Token
|
||||||
|
"""
|
||||||
|
self.url = url.rstrip('/')
|
||||||
|
self.headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_connection(self):
|
||||||
|
"""Teste die Verbindung zu Home Assistant"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{self.url}/api/", headers=self.headers, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✓ Verbindung erfolgreich!")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"✗ Fehler: Status Code {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Verbindungsfehler: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
"""Hole die Konfiguration"""
|
||||||
|
response = requests.get(f"{self.url}/api/config", headers=self.headers)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def get_components(self):
|
||||||
|
"""Hole alle installierten Komponenten/Integrationen"""
|
||||||
|
response = requests.get(f"{self.url}/api/config/core", headers=self.headers)
|
||||||
|
data = response.json()
|
||||||
|
return data.get('components', [])
|
||||||
|
|
||||||
|
def get_states(self):
|
||||||
|
"""Hole alle Entitäten mit ihren Zuständen"""
|
||||||
|
response = requests.get(f"{self.url}/api/states", headers=self.headers)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def get_services(self):
|
||||||
|
"""Hole alle verfügbaren Services"""
|
||||||
|
response = requests.get(f"{self.url}/api/services", headers=self.headers)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def get_events(self):
|
||||||
|
"""Hole alle verfügbaren Events"""
|
||||||
|
response = requests.get(f"{self.url}/api/events", headers=self.headers)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def analyze_entities(self, states):
|
||||||
|
"""Analysiere Entitäten nach Domains"""
|
||||||
|
by_domain = defaultdict(list)
|
||||||
|
for entity in states:
|
||||||
|
domain = entity['entity_id'].split('.')[0]
|
||||||
|
by_domain[domain].append(entity)
|
||||||
|
return dict(by_domain)
|
||||||
|
|
||||||
|
def generate_report(self):
|
||||||
|
"""Erstelle einen vollständigen Bericht"""
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("HOME ASSISTANT - VOLLSTÄNDIGER ÜBERBLICK")
|
||||||
|
print("="*80)
|
||||||
|
print(f"Generiert am: {datetime.now().strftime('%d.%m.%Y um %H:%M:%S')}")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
# Test connection
|
||||||
|
if not self.test_connection():
|
||||||
|
return None
|
||||||
|
|
||||||
|
print("\n📊 Sammle Daten...\n")
|
||||||
|
|
||||||
|
# Sammle alle Daten
|
||||||
|
config = self.get_config()
|
||||||
|
components = self.get_components()
|
||||||
|
states = self.get_states()
|
||||||
|
services = self.get_services()
|
||||||
|
events = self.get_events()
|
||||||
|
entities_by_domain = self.analyze_entities(states)
|
||||||
|
|
||||||
|
# Erstelle Report-Struktur
|
||||||
|
report = {
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"system_info": {
|
||||||
|
"version": config.get('version'),
|
||||||
|
"location_name": config.get('location_name'),
|
||||||
|
"timezone": config.get('time_zone'),
|
||||||
|
"unit_system": config.get('unit_system'),
|
||||||
|
"latitude": config.get('latitude'),
|
||||||
|
"longitude": config.get('longitude')
|
||||||
|
},
|
||||||
|
"statistics": {
|
||||||
|
"total_components": len(components),
|
||||||
|
"total_entities": len(states),
|
||||||
|
"total_services": sum(len(domain['services']) for domain in services),
|
||||||
|
"total_domains": len(entities_by_domain),
|
||||||
|
"total_events": len(events)
|
||||||
|
},
|
||||||
|
"components": sorted(components),
|
||||||
|
"entities_by_domain": {
|
||||||
|
domain: len(entities) for domain, entities in sorted(entities_by_domain.items())
|
||||||
|
},
|
||||||
|
"detailed_entities": entities_by_domain,
|
||||||
|
"services": services,
|
||||||
|
"events": events,
|
||||||
|
"all_states": states
|
||||||
|
}
|
||||||
|
|
||||||
|
return report
|
||||||
|
|
||||||
|
def print_summary(self, report):
|
||||||
|
"""Drucke eine Zusammenfassung auf die Konsole"""
|
||||||
|
if not report:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("SYSTEM INFORMATIONEN")
|
||||||
|
print("="*80)
|
||||||
|
info = report['system_info']
|
||||||
|
print(f"Version: {info['version']}")
|
||||||
|
print(f"Standort: {info['location_name']}")
|
||||||
|
print(f"Zeitzone: {info['timezone']}")
|
||||||
|
print(f"Einheitensystem: {info['unit_system']}")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("STATISTIKEN")
|
||||||
|
print("="*80)
|
||||||
|
stats = report['statistics']
|
||||||
|
print(f"Komponenten: {stats['total_components']}")
|
||||||
|
print(f"Entitäten: {stats['total_entities']}")
|
||||||
|
print(f"Services: {stats['total_services']}")
|
||||||
|
print(f"Domains: {stats['total_domains']}")
|
||||||
|
print(f"Events: {stats['total_events']}")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("ENTITÄTEN NACH DOMAIN")
|
||||||
|
print("="*80)
|
||||||
|
for domain, count in sorted(report['entities_by_domain'].items(), key=lambda x: x[1], reverse=True):
|
||||||
|
print(f"{domain:.<30} {count:>4}")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("TOP 20 KOMPONENTEN")
|
||||||
|
print("="*80)
|
||||||
|
for i, component in enumerate(report['components'][:20], 1):
|
||||||
|
print(f"{i:2}. {component}")
|
||||||
|
if len(report['components']) > 20:
|
||||||
|
print(f"... und {len(report['components']) - 20} weitere")
|
||||||
|
|
||||||
|
def save_report(self, report, format='json'):
|
||||||
|
"""Speichere den Report in verschiedenen Formaten"""
|
||||||
|
if not report:
|
||||||
|
return None
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
|
||||||
|
if format == 'json':
|
||||||
|
filename = f"/mnt/user-data/outputs/ha_overview_{timestamp}.json"
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(report, f, indent=2, ensure_ascii=False)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
elif format == 'txt':
|
||||||
|
filename = f"/mnt/user-data/outputs/ha_overview_{timestamp}.txt"
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
f.write("="*80 + "\n")
|
||||||
|
f.write("HOME ASSISTANT - VOLLSTÄNDIGER ÜBERBLICK\n")
|
||||||
|
f.write("="*80 + "\n")
|
||||||
|
f.write(f"Generiert am: {datetime.now().strftime('%d.%m.%Y um %H:%M:%S')}\n")
|
||||||
|
f.write("="*80 + "\n\n")
|
||||||
|
|
||||||
|
# System Info
|
||||||
|
f.write("SYSTEM INFORMATIONEN\n")
|
||||||
|
f.write("-"*80 + "\n")
|
||||||
|
info = report['system_info']
|
||||||
|
for key, value in info.items():
|
||||||
|
f.write(f"{key:20}: {value}\n")
|
||||||
|
|
||||||
|
# Statistiken
|
||||||
|
f.write("\n" + "="*80 + "\n")
|
||||||
|
f.write("STATISTIKEN\n")
|
||||||
|
f.write("-"*80 + "\n")
|
||||||
|
stats = report['statistics']
|
||||||
|
for key, value in stats.items():
|
||||||
|
f.write(f"{key:20}: {value}\n")
|
||||||
|
|
||||||
|
# Komponenten
|
||||||
|
f.write("\n" + "="*80 + "\n")
|
||||||
|
f.write(f"ALLE KOMPONENTEN ({len(report['components'])})\n")
|
||||||
|
f.write("-"*80 + "\n")
|
||||||
|
for component in sorted(report['components']):
|
||||||
|
f.write(f" • {component}\n")
|
||||||
|
|
||||||
|
# Entitäten nach Domain
|
||||||
|
f.write("\n" + "="*80 + "\n")
|
||||||
|
f.write("ENTITÄTEN NACH DOMAIN\n")
|
||||||
|
f.write("-"*80 + "\n")
|
||||||
|
for domain, count in sorted(report['entities_by_domain'].items(), key=lambda x: x[1], reverse=True):
|
||||||
|
f.write(f"{domain:.<30} {count:>4}\n")
|
||||||
|
|
||||||
|
# Detaillierte Entitäten
|
||||||
|
f.write("\n" + "="*80 + "\n")
|
||||||
|
f.write("ALLE ENTITÄTEN (DETAILLIERT)\n")
|
||||||
|
f.write("-"*80 + "\n")
|
||||||
|
for domain, entities in sorted(report['detailed_entities'].items()):
|
||||||
|
f.write(f"\n### {domain.upper()} ({len(entities)} Entitäten) ###\n")
|
||||||
|
for entity in entities:
|
||||||
|
f.write(f"\n Entity ID: {entity['entity_id']}\n")
|
||||||
|
f.write(f" Name: {entity['attributes'].get('friendly_name', 'N/A')}\n")
|
||||||
|
f.write(f" Zustand: {entity['state']}\n")
|
||||||
|
if entity['attributes'].get('device_class'):
|
||||||
|
f.write(f" Klasse: {entity['attributes']['device_class']}\n")
|
||||||
|
f.write(f" Letzte Änderung: {entity['last_changed']}\n")
|
||||||
|
|
||||||
|
# Services
|
||||||
|
f.write("\n" + "="*80 + "\n")
|
||||||
|
f.write("VERFÜGBARE SERVICES\n")
|
||||||
|
f.write("-"*80 + "\n")
|
||||||
|
for domain_services in report['services']:
|
||||||
|
domain = domain_services['domain']
|
||||||
|
f.write(f"\n### {domain.upper()} ###\n")
|
||||||
|
for service_name, service_data in domain_services['services'].items():
|
||||||
|
f.write(f" • {domain}.{service_name}\n")
|
||||||
|
if service_data.get('description'):
|
||||||
|
f.write(f" {service_data['description']}\n")
|
||||||
|
|
||||||
|
# Events
|
||||||
|
f.write("\n" + "="*80 + "\n")
|
||||||
|
f.write("VERFÜGBARE EVENTS\n")
|
||||||
|
f.write("-"*80 + "\n")
|
||||||
|
for event in report['events']:
|
||||||
|
f.write(f" • {event['event']}\n")
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
elif format == 'html':
|
||||||
|
filename = f"/mnt/user-data/outputs/ha_overview_{timestamp}.html"
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
f.write("""<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Home Assistant Überblick</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #03a9f4;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #03a9f4 0%, #0288d1 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.stat-card {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-card h3 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.stat-card .number {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #03a9f4;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.entity-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.entity-card {
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.entity-card .id {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #0288d1;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.entity-card .state {
|
||||||
|
color: #4caf50;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #03a9f4;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.component-tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: #e3f2fd;
|
||||||
|
color: #0288d1;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
""")
|
||||||
|
|
||||||
|
f.write(f"""
|
||||||
|
<div class="header">
|
||||||
|
<h1>🏠 Home Assistant Überblick</h1>
|
||||||
|
<p>Generiert am: {datetime.now().strftime('%d.%m.%Y um %H:%M:%S')}</p>
|
||||||
|
<p>Version: {report['system_info']['version']} | Standort: {report['system_info']['location_name']}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Komponenten</h3>
|
||||||
|
<div class="number">{report['statistics']['total_components']}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Entitäten</h3>
|
||||||
|
<div class="number">{report['statistics']['total_entities']}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Services</h3>
|
||||||
|
<div class="number">{report['statistics']['total_services']}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Domains</h3>
|
||||||
|
<div class="number">{report['statistics']['total_domains']}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Events</h3>
|
||||||
|
<div class="number">{report['statistics']['total_events']}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>📦 Installierte Komponenten</h2>
|
||||||
|
""")
|
||||||
|
|
||||||
|
for component in sorted(report['components']):
|
||||||
|
f.write(f' <span class="component-tag">{component}</span>\n')
|
||||||
|
|
||||||
|
f.write("""
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>📊 Entitäten nach Domain</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Anzahl</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
""")
|
||||||
|
|
||||||
|
for domain, count in sorted(report['entities_by_domain'].items(), key=lambda x: x[1], reverse=True):
|
||||||
|
f.write(f"""
|
||||||
|
<tr>
|
||||||
|
<td><strong>{domain}</strong></td>
|
||||||
|
<td>{count}</td>
|
||||||
|
</tr>
|
||||||
|
""")
|
||||||
|
|
||||||
|
f.write("""
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>🔧 Alle Entitäten</h2>
|
||||||
|
""")
|
||||||
|
|
||||||
|
for domain, entities in sorted(report['detailed_entities'].items()):
|
||||||
|
f.write(f"""
|
||||||
|
<h3>{domain.upper()} ({len(entities)} Entitäten)</h3>
|
||||||
|
<div class="entity-list">
|
||||||
|
""")
|
||||||
|
for entity in entities:
|
||||||
|
name = entity['attributes'].get('friendly_name', entity['entity_id'])
|
||||||
|
f.write(f"""
|
||||||
|
<div class="entity-card">
|
||||||
|
<div class="id">{entity['entity_id']}</div>
|
||||||
|
<div>{name}</div>
|
||||||
|
<div class="state">Zustand: {entity['state']}</div>
|
||||||
|
</div>
|
||||||
|
""")
|
||||||
|
f.write(" </div>\n")
|
||||||
|
|
||||||
|
f.write("""
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""")
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Hauptfunktion"""
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("HOME ASSISTANT OVERVIEW TOOL")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
# Eingaben
|
||||||
|
url = input("Home Assistant URL (z.B. http://homeassistant.local:8123): ").strip()
|
||||||
|
token = input("Long-Lived Access Token: ").strip()
|
||||||
|
|
||||||
|
# Erstelle Overview-Objekt
|
||||||
|
ha = HomeAssistantOverview(url, token)
|
||||||
|
|
||||||
|
# Generiere Report
|
||||||
|
report = ha.generate_report()
|
||||||
|
|
||||||
|
if report:
|
||||||
|
# Zeige Zusammenfassung
|
||||||
|
ha.print_summary(report)
|
||||||
|
|
||||||
|
# Speichere in allen Formaten
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("SPEICHERE BERICHTE...")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
json_file = ha.save_report(report, 'json')
|
||||||
|
txt_file = ha.save_report(report, 'txt')
|
||||||
|
html_file = ha.save_report(report, 'html')
|
||||||
|
|
||||||
|
print(f"\n✓ JSON-Bericht: {json_file}")
|
||||||
|
print(f"✓ Text-Bericht: {txt_file}")
|
||||||
|
print(f"✓ HTML-Bericht: {html_file}")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("FERTIG!")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Flask==3.0.0
|
||||||
|
requests==2.31.0
|
||||||
578
templates/index.html
Normal file
578
templates/index.html
Normal file
|
|
@ -0,0 +1,578 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Home Assistant Overview - Web GUI</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-content {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2::before {
|
||||||
|
content: "📖";
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step h3 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step p {
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step code {
|
||||||
|
background: #e9ecef;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group input {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group label {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #5568d3;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(108, 117, 125, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background: #218838;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background: #f8d7da;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-summary {
|
||||||
|
display: none;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-box {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-box .number {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-box .label {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #e7f3ff;
|
||||||
|
border-left: 4px solid #2196F3;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box strong {
|
||||||
|
color: #1976D2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🏠 Home Assistant Overview - Web GUI</h1>
|
||||||
|
<p>Vollständige Übersicht über Ihre Home Assistant Installation mit integrierter Schritt-für-Schritt Anleitung</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<!-- Anleitung / Instructions -->
|
||||||
|
<div class="card instructions">
|
||||||
|
<h2>Schritt-für-Schritt Anleitung</h2>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>Schritt 1: Home Assistant Long-Lived Access Token erstellen</h3>
|
||||||
|
<p>
|
||||||
|
1. Öffnen Sie Ihre Home Assistant Web-Oberfläche<br>
|
||||||
|
2. Klicken Sie auf Ihr Profil (unten links)<br>
|
||||||
|
3. Scrollen Sie nach unten zu "Long-Lived Access Tokens"<br>
|
||||||
|
4. Klicken Sie auf "Create Token"<br>
|
||||||
|
5. Geben Sie einen Namen ein (z.B. "Overview Tool")<br>
|
||||||
|
6. Kopieren Sie den generierten Token (wird nur einmal angezeigt!)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>Schritt 2: Verbindungsdaten eingeben</h3>
|
||||||
|
<p>
|
||||||
|
Geben Sie in den Feldern rechts Ihre Home Assistant URL und den Token ein:<br>
|
||||||
|
- <strong>URL:</strong> <code>http://homeassistant.local:8123</code> oder Ihre IP-Adresse<br>
|
||||||
|
- <strong>Token:</strong> Der zuvor erstellte Long-Lived Access Token<br>
|
||||||
|
- Optional: Aktivieren Sie "Konfiguration speichern" um die Daten für später zu speichern
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>Schritt 3: Verbindung testen</h3>
|
||||||
|
<p>
|
||||||
|
Klicken Sie auf "Verbindung testen" um sicherzustellen, dass die Verbindung funktioniert.
|
||||||
|
Bei Erfolg erscheint eine grüne Bestätigung.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>Schritt 4: Bericht erstellen</h3>
|
||||||
|
<p>
|
||||||
|
Klicken Sie auf "Bericht generieren" um eine vollständige Übersicht zu erstellen.
|
||||||
|
Der Bericht enthält:<br>
|
||||||
|
- Alle Entitäten sortiert nach Domain<br>
|
||||||
|
- Installierte Komponenten<br>
|
||||||
|
- Verfügbare Services<br>
|
||||||
|
- System-Informationen<br>
|
||||||
|
- Statistiken
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<h3>Schritt 5: Bericht herunterladen (optional)</h3>
|
||||||
|
<p>
|
||||||
|
Nach der Generierung können Sie den Bericht als JSON- oder Text-Datei herunterladen
|
||||||
|
und für Ihre Dokumentation verwenden.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
|
<div class="card">
|
||||||
|
<h2 style="margin-bottom: 10px;">Verbindung einrichten</h2>
|
||||||
|
|
||||||
|
{% if has_config %}
|
||||||
|
<div class="info-box">
|
||||||
|
<strong>ℹ️ Gespeicherte Konfiguration gefunden!</strong><br>
|
||||||
|
Die vorherigen Einstellungen wurden automatisch geladen.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="alerts"></div>
|
||||||
|
|
||||||
|
<form id="connectionForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="url">Home Assistant URL</label>
|
||||||
|
<input type="text" id="url" name="url"
|
||||||
|
placeholder="http://homeassistant.local:8123"
|
||||||
|
value="{{ saved_url }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="token">Long-Lived Access Token</label>
|
||||||
|
<input type="password" id="token" name="token"
|
||||||
|
placeholder="Ihr Token..." required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<input type="checkbox" id="saveConfig" name="saveConfig">
|
||||||
|
<label for="saveConfig">Konfiguration speichern (für spätere Verwendung)</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="testConnection()">
|
||||||
|
Verbindung testen
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="generateReport()">
|
||||||
|
Bericht generieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="loading" id="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p style="margin-top: 10px;">Bitte warten...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="report-summary" id="reportSummary">
|
||||||
|
<h3 style="color: #667eea; margin-bottom: 15px;">Bericht Zusammenfassung</h3>
|
||||||
|
<div class="stat-grid" id="statsGrid"></div>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px;">
|
||||||
|
<button class="btn btn-success" onclick="downloadReport('json')">
|
||||||
|
📥 Als JSON herunterladen
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" onclick="downloadReport('txt')">
|
||||||
|
📥 Als Text herunterladen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let currentReport = null;
|
||||||
|
|
||||||
|
function showAlert(message, type) {
|
||||||
|
const alertsDiv = document.getElementById('alerts');
|
||||||
|
const alertClass = type === 'success' ? 'alert-success' :
|
||||||
|
type === 'error' ? 'alert-error' : 'alert-info';
|
||||||
|
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = `alert ${alertClass}`;
|
||||||
|
alert.textContent = message;
|
||||||
|
alert.style.display = 'block';
|
||||||
|
|
||||||
|
alertsDiv.innerHTML = '';
|
||||||
|
alertsDiv.appendChild(alert);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
alert.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading(show) {
|
||||||
|
document.getElementById('loading').style.display = show ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testConnection() {
|
||||||
|
const url = document.getElementById('url').value;
|
||||||
|
const token = document.getElementById('token').value;
|
||||||
|
const saveConfig = document.getElementById('saveConfig').checked;
|
||||||
|
|
||||||
|
if (!url || !token) {
|
||||||
|
showAlert('Bitte füllen Sie alle Felder aus!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/test-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url, token, save_config: saveConfig })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showAlert('✓ ' + result.message, 'success');
|
||||||
|
} else {
|
||||||
|
showAlert('✗ ' + result.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showAlert('Fehler: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateReport() {
|
||||||
|
const url = document.getElementById('url').value;
|
||||||
|
const token = document.getElementById('token').value;
|
||||||
|
const saveConfig = document.getElementById('saveConfig').checked;
|
||||||
|
|
||||||
|
if (!url || !token) {
|
||||||
|
showAlert('Bitte füllen Sie alle Felder aus!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
document.getElementById('reportSummary').style.display = 'none';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/generate-report', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url, token, save_config: saveConfig })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
currentReport = result.report;
|
||||||
|
displayReport(result.report);
|
||||||
|
showAlert('✓ Bericht erfolgreich erstellt!', 'success');
|
||||||
|
} else {
|
||||||
|
showAlert('✗ ' + result.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showAlert('Fehler: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayReport(report) {
|
||||||
|
const stats = report.statistics;
|
||||||
|
const statsGrid = document.getElementById('statsGrid');
|
||||||
|
|
||||||
|
statsGrid.innerHTML = `
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="number">${stats.total_components}</div>
|
||||||
|
<div class="label">Komponenten</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="number">${stats.total_entities}</div>
|
||||||
|
<div class="label">Entitäten</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="number">${stats.total_services}</div>
|
||||||
|
<div class="label">Services</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="number">${stats.total_domains}</div>
|
||||||
|
<div class="label">Domains</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('reportSummary').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadReport(format) {
|
||||||
|
if (!currentReport) {
|
||||||
|
showAlert('Bitte generieren Sie zuerst einen Bericht!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = document.getElementById('url').value;
|
||||||
|
const token = document.getElementById('token').value;
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/download-report', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url, token, format })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const blob = await response.blob();
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = downloadUrl;
|
||||||
|
a.download = `ha_overview.${format}`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
|
||||||
|
showAlert('✓ Download gestartet!', 'success');
|
||||||
|
} else {
|
||||||
|
showAlert('✗ Download fehlgeschlagen', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showAlert('Fehler: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue