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