Add web-based GUI with frontend and backend

Co-authored-by: Kenearos <86194771+Kenearos@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-11-17 20:14:28 +00:00
parent c269cb0ef0
commit be9ed433b6
7 changed files with 1732 additions and 1 deletions

48
.gitignore vendored Normal file
View 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
View 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
View file

@ -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
View 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
View 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
View file

@ -0,0 +1,2 @@
Flask==3.0.0
requests==2.31.0

578
templates/index.html Normal file
View 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>