modified: app.py

modified:   config.json
	modified:   templates/base.html
	modified:   templates/docker_status.html
	new file:   test_icons.html
This commit is contained in:
SimolZimol
2025-07-05 12:43:55 +02:00
parent a25a744940
commit 08e7381667
5 changed files with 1491 additions and 101 deletions

254
app.py
View File

@@ -21,6 +21,14 @@ class ProjectManager:
def __init__(self):
self.docker_client = None
self.docker_available = False
self._init_docker()
# Erstelle notwendige Verzeichnisse
os.makedirs(PROJECTS_DIR, exist_ok=True)
os.makedirs(APPS_DIR, exist_ok=True)
def _init_docker(self):
"""Initialisiere Docker-Verbindung mit Retry-Logik"""
try:
self.docker_client = docker.from_env()
# Teste Docker-Verbindung
@@ -28,15 +36,30 @@ class ProjectManager:
self.docker_available = True
print("✓ Docker erfolgreich verbunden")
except docker.errors.DockerException as e:
print(f"Docker-Verbindungsfehler: {e}")
print(f"Docker-Verbindungsfehler: {e}")
print(" Hinweis: Docker Desktop starten und /api/docker_reconnect aufrufen")
self.docker_client = None
self.docker_available = False
except Exception as e:
print(f"Docker nicht verfügbar: {e}")
print(f"Docker nicht verfügbar: {e}")
print(" Hinweis: Docker Desktop installieren oder starten")
self.docker_client = None
self.docker_available = False
def reconnect_docker(self):
"""Versuche Docker-Verbindung wiederherzustellen"""
print("🔄 Versuche Docker-Reconnect...")
# Erstelle notwendige Verzeichnisse
os.makedirs(PROJECTS_DIR, exist_ok=True)
os.makedirs(APPS_DIR, exist_ok=True)
# Alte Verbindung schließen
if self.docker_client:
try:
self.docker_client.close()
except:
pass
# Neu initialisieren
self._init_docker()
return self.docker_available
def load_config(self):
"""Lade Konfiguration aus config.json"""
@@ -785,20 +808,63 @@ def remove_project(project_name):
@app.route('/config', methods=['GET', 'POST'])
def config():
"""Konfigurationsseite"""
if request.method == 'POST':
config = {
'project_list_url': request.form.get('project_list_url', ''),
'auto_refresh_minutes': int(request.form.get('auto_refresh_minutes', 30)),
'docker_registry': request.form.get('docker_registry', ''),
'projects': project_manager.load_config().get('projects', [])
try:
if request.method == 'POST':
# Sichere Formular-Validierung
project_list_url = request.form.get('project_list_url', '').strip()
auto_refresh_minutes = request.form.get('auto_refresh_minutes', '30')
docker_registry = request.form.get('docker_registry', '').strip()
# Validiere auto_refresh_minutes
try:
auto_refresh_minutes = int(auto_refresh_minutes)
if auto_refresh_minutes < 5 or auto_refresh_minutes > 1440:
auto_refresh_minutes = 30
except (ValueError, TypeError):
auto_refresh_minutes = 30
current_config = project_manager.load_config()
config = {
'project_list_url': project_list_url,
'auto_refresh_minutes': auto_refresh_minutes,
'docker_registry': docker_registry,
'projects': current_config.get('projects', [])
}
project_manager.save_config(config)
flash('Konfiguration gespeichert', 'success')
return redirect(url_for('config'))
# GET request - lade Konfiguration
config = project_manager.load_config()
# Stelle sicher, dass alle erwarteten Konfigurationswerte vorhanden sind
default_config = {
'project_list_url': '',
'auto_refresh_minutes': 30,
'docker_registry': '',
'projects': []
}
project_manager.save_config(config)
flash('Konfiguration gespeichert', 'success')
return redirect(url_for('config'))
config = project_manager.load_config()
return render_template('config.html', config=config)
# Merge mit defaults
for key, default_value in default_config.items():
if key not in config:
config[key] = default_value
return render_template('config.html', config=config)
except Exception as e:
print(f"Config Route Fehler: {e}")
flash(f'Fehler beim Laden der Konfiguration: {str(e)}', 'error')
# Fallback config
fallback_config = {
'project_list_url': '',
'auto_refresh_minutes': 30,
'docker_registry': '',
'projects': []
}
return render_template('config.html', config=fallback_config)
@app.route('/project_details/<project_name>')
def project_details(project_name):
@@ -849,18 +915,25 @@ def api_system_status():
# Docker Status
try:
result = subprocess.run(['docker', '--version'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
version = result.stdout.strip()
# Teste Docker-Daemon
if project_manager.docker_available and project_manager.docker_client:
try:
subprocess.run(['docker', 'ps'], capture_output=True, text=True, timeout=5, check=True)
status['docker'] = {'available': True, 'version': version, 'status': 'running'}
except subprocess.CalledProcessError:
status['docker'] = {'available': True, 'version': version, 'status': 'daemon_stopped'}
project_manager.docker_client.ping()
result = subprocess.run(['docker', '--version'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
version = result.stdout.strip()
status['docker'] = {'available': True, 'version': version, 'status': 'running'}
else:
status['docker'] = {'available': False, 'version': None, 'status': 'version_check_failed'}
except Exception:
status['docker'] = {'available': False, 'version': None, 'status': 'daemon_not_reachable'}
else:
status['docker'] = {'available': False, 'version': None, 'status': 'not_installed'}
# Versuche Docker-Installation zu prüfen
result = subprocess.run(['docker', '--version'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
version = result.stdout.strip()
status['docker'] = {'available': True, 'version': version, 'status': 'daemon_stopped'}
else:
status['docker'] = {'available': False, 'version': None, 'status': 'not_installed'}
except subprocess.TimeoutExpired:
status['docker'] = {'available': False, 'version': None, 'status': 'timeout'}
except FileNotFoundError:
@@ -880,6 +953,19 @@ def api_system_status():
except Exception as e:
status['git'] = {'available': False, 'version': None, 'status': f'error: {str(e)}'}
# Festplattenspeicher hinzufügen
try:
import shutil
total, used, free = shutil.disk_usage(PROJECTS_DIR)
free_gb = round(free / (1024**3), 1)
status['disk_space'] = {
'available': free > (1024**3), # Mehr als 1GB frei
'free': f'{free_gb} GB',
'status': 'ok' if free > (1024**3) else 'low'
}
except Exception:
status['disk_space'] = {'available': False, 'free': 'Unknown', 'status': 'error'}
return jsonify(status)
@app.route('/api/test_connection', methods=['POST'])
@@ -1056,15 +1142,25 @@ def api_reset_config():
@app.route('/docker_status')
def docker_status():
"""Docker Status und Diagnose Seite"""
return render_template('docker_status.html')
try:
# Lade Standard-Konfiguration für Template-Konsistenz
config = project_manager.load_config()
return render_template('docker_status.html', config=config)
except Exception as e:
print(f"Docker Status Route Fehler: {e}")
# Fallback bei Fehlern
fallback_config = {
'project_list_url': '',
'auto_refresh_minutes': 30,
'docker_registry': '',
'projects': []
}
return render_template('docker_status.html', config=fallback_config)
@app.route('/api/docker_diagnose')
def api_docker_diagnose():
"""Docker Diagnose API"""
try:
# Importiere Diagnose-Funktion
from docker_diagnose import diagnose_docker_status
results = {
'timestamp': datetime.now().isoformat(),
'checks': {},
@@ -1083,7 +1179,7 @@ def api_docker_diagnose():
else:
results['checks']['docker_version'] = {
'success': False,
'output': result.stderr.strip(),
'output': result.stderr.strip() if result.stderr else 'Fehler ohne Ausgabe',
'details': 'Docker Kommando fehlgeschlagen'
}
results['recommendations'].append('Docker Desktop installieren')
@@ -1094,9 +1190,15 @@ def api_docker_diagnose():
'details': 'Docker ist nicht installiert'
}
results['recommendations'].append('Docker Desktop von https://docker.com herunterladen')
except Exception as e:
results['checks']['docker_version'] = {
'success': False,
'output': f'Unerwarteter Fehler: {str(e)}',
'details': 'Docker Version Check fehlgeschlagen'
}
# Docker Daemon
if project_manager.docker_available:
if project_manager.docker_available and project_manager.docker_client:
try:
project_manager.docker_client.ping()
results['checks']['docker_daemon'] = {
@@ -1120,24 +1222,47 @@ def api_docker_diagnose():
results['recommendations'].append('Docker Desktop starten')
# Container Status
if project_manager.docker_available:
if project_manager.docker_available and project_manager.docker_client:
try:
containers = project_manager.docker_client.containers.list(all=True)
container_data = []
for c in containers:
try:
container_data.append({
'name': getattr(c, 'name', 'unknown'),
'status': getattr(c, 'status', 'unknown'),
'State': getattr(c, 'status', 'unknown')
})
except Exception:
container_data.append({
'name': 'unknown',
'status': 'unknown',
'State': 'unknown'
})
results['checks']['containers'] = {
'success': True,
'output': f'{len(containers)} Container',
'details': f'Gefunden: {len(containers)} Container',
'containers': [{'name': c.name, 'status': c.status} for c in containers]
'containers': container_data
}
except Exception as e:
results['checks']['containers'] = {
'success': False,
'output': 'Container-Check fehlgeschlagen',
'details': str(e)
'details': str(e),
'containers': []
}
else:
results['checks']['containers'] = {
'success': False,
'output': 'Docker nicht verfügbar',
'details': 'Docker Client nicht initialisiert',
'containers': []
}
# Images
if project_manager.docker_available:
if project_manager.docker_available and project_manager.docker_client:
try:
images = project_manager.docker_client.images.list()
results['checks']['images'] = {
@@ -1151,13 +1276,22 @@ def api_docker_diagnose():
'output': 'Image-Check fehlgeschlagen',
'details': str(e)
}
else:
results['checks']['images'] = {
'success': False,
'output': 'Docker nicht verfügbar',
'details': 'Docker Client nicht initialisiert'
}
return jsonify(results)
except Exception as e:
print(f"Docker Diagnose API Fehler: {e}")
return jsonify({
'error': str(e),
'timestamp': datetime.now().isoformat()
'timestamp': datetime.now().isoformat(),
'checks': {},
'recommendations': ['Systemfehler - bitte versuchen Sie es erneut']
}), 500
@app.route('/api/check_port/<int:port>')
@@ -1348,5 +1482,49 @@ def api_remove_project(project_name):
'project_name': project_name
})
@app.route('/api/docker_reconnect', methods=['POST'])
def api_docker_reconnect():
"""Versuche Docker-Verbindung wiederherzustellen"""
try:
success = project_manager.reconnect_docker()
if success:
return jsonify({
'success': True,
'message': 'Docker-Verbindung erfolgreich wiederhergestellt',
'docker_available': True
})
else:
return jsonify({
'success': False,
'error': 'Docker ist nicht verfügbar. Stellen Sie sicher, dass Docker Desktop läuft.',
'docker_available': False,
'recommendations': [
'Docker Desktop starten',
'Als Administrator ausführen',
'WSL2 oder Hyper-V aktivieren',
'Docker Desktop neu installieren'
]
})
except Exception as e:
return jsonify({
'success': False,
'error': f'Reconnect fehlgeschlagen: {str(e)}',
'docker_available': False
})
@app.route('/test_icons')
def test_icons():
"""Icon Test Seite"""
return render_template('test_icons.html')
# Test Icons Template Route
@app.route('/templates/test_icons.html')
def serve_test_icons():
"""Serve Icon Test Template"""
with open('test_icons.html', 'r', encoding='utf-8') as f:
return f.read(), 200, {'Content-Type': 'text/html; charset=utf-8'}
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)

View File

@@ -2,13 +2,653 @@
"project_list_url": "https://gitea.simolzimol.net/api/v1/repos/search?sort=updated&order=desc&limit=50",
"auto_refresh_minutes": 30,
"docker_registry": "",
"projects": [
{
"url": "https://gitea.simolzimol.net/Simon/quizify",
"name": "quizify",
"description": "Ein interaktives Quiz-System",
"language": "JavaScript",
"tags": ["quiz", "web", "javascript"]
}
]
}
"projects": {
"ok": true,
"data": [
{
"id": 12,
"owner": {
"id": 1,
"login": "Simon",
"login_name": "",
"source_id": 0,
"full_name": "",
"email": "Simon@vp-server.eu",
"avatar_url": "https://gitea.simolzimol.net/avatars/019358ccebd80ad0a19ab87e342d1fbc",
"html_url": "https://gitea.simolzimol.net/Simon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2024-09-01T19:33:46Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "Simon"
},
"name": "app-installer",
"full_name": "Simon/app-installer",
"description": "",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 93,
"language": "HTML",
"languages_url": "https://gitea.simolzimol.net/api/v1/repos/Simon/app-installer/languages",
"html_url": "https://gitea.simolzimol.net/Simon/app-installer",
"url": "https://gitea.simolzimol.net/api/v1/repos/Simon/app-installer",
"link": "",
"ssh_url": "git@gitea.simolzimol.net:Simon/app-installer.git",
"clone_url": "https://gitea.simolzimol.net/Simon/app-installer.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2025-07-04T21:47:47Z",
"updated_at": "2025-07-04T21:50:40Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": true,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_fast_forward_only_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://gitea.simolzimol.net/",
"internal": false,
"mirror_interval": "",
"object_format_name": "sha1",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
},
{
"id": 11,
"owner": {
"id": 1,
"login": "Simon",
"login_name": "",
"source_id": 0,
"full_name": "",
"email": "Simon@vp-server.eu",
"avatar_url": "https://gitea.simolzimol.net/avatars/019358ccebd80ad0a19ab87e342d1fbc",
"html_url": "https://gitea.simolzimol.net/Simon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2024-09-01T19:33:46Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "Simon"
},
"name": "Assistent",
"full_name": "Simon/Assistent",
"description": "",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 136,
"language": "HTML",
"languages_url": "https://gitea.simolzimol.net/api/v1/repos/Simon/Assistent/languages",
"html_url": "https://gitea.simolzimol.net/Simon/Assistent",
"url": "https://gitea.simolzimol.net/api/v1/repos/Simon/Assistent",
"link": "",
"ssh_url": "git@gitea.simolzimol.net:Simon/Assistent.git",
"clone_url": "https://gitea.simolzimol.net/Simon/Assistent.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2025-06-18T12:31:19Z",
"updated_at": "2025-06-19T17:27:02Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": true,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_fast_forward_only_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://gitea.simolzimol.net/",
"internal": false,
"mirror_interval": "",
"object_format_name": "sha1",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
},
{
"id": 6,
"owner": {
"id": 1,
"login": "Simon",
"login_name": "",
"source_id": 0,
"full_name": "",
"email": "Simon@vp-server.eu",
"avatar_url": "https://gitea.simolzimol.net/avatars/019358ccebd80ad0a19ab87e342d1fbc",
"html_url": "https://gitea.simolzimol.net/Simon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2024-09-01T19:33:46Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "Simon"
},
"name": "quizify",
"full_name": "Simon/quizify",
"description": "",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 452,
"language": "HTML",
"languages_url": "https://gitea.simolzimol.net/api/v1/repos/Simon/quizify/languages",
"html_url": "https://gitea.simolzimol.net/Simon/quizify",
"url": "https://gitea.simolzimol.net/api/v1/repos/Simon/quizify",
"link": "",
"ssh_url": "git@gitea.simolzimol.net:Simon/quizify.git",
"clone_url": "https://gitea.simolzimol.net/Simon/quizify.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2025-05-15T19:57:24Z",
"updated_at": "2025-06-07T12:29:23Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": true,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_fast_forward_only_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://gitea.simolzimol.net/",
"internal": false,
"mirror_interval": "",
"object_format_name": "sha1",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
},
{
"id": 8,
"owner": {
"id": 1,
"login": "Simon",
"login_name": "",
"source_id": 0,
"full_name": "",
"email": "Simon@vp-server.eu",
"avatar_url": "https://gitea.simolzimol.net/avatars/019358ccebd80ad0a19ab87e342d1fbc",
"html_url": "https://gitea.simolzimol.net/Simon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2024-09-01T19:33:46Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "Simon"
},
"name": "LevelCraft",
"full_name": "Simon/LevelCraft",
"description": "",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 104,
"language": "Java",
"languages_url": "https://gitea.simolzimol.net/api/v1/repos/Simon/LevelCraft/languages",
"html_url": "https://gitea.simolzimol.net/Simon/LevelCraft",
"url": "https://gitea.simolzimol.net/api/v1/repos/Simon/LevelCraft",
"link": "",
"ssh_url": "git@gitea.simolzimol.net:Simon/LevelCraft.git",
"clone_url": "https://gitea.simolzimol.net/Simon/LevelCraft.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2025-05-23T12:43:51Z",
"updated_at": "2025-05-25T13:18:21Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": true,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_fast_forward_only_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://gitea.simolzimol.net/",
"internal": false,
"mirror_interval": "",
"object_format_name": "sha1",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
},
{
"id": 2,
"owner": {
"id": 1,
"login": "Simon",
"login_name": "",
"source_id": 0,
"full_name": "",
"email": "Simon@vp-server.eu",
"avatar_url": "https://gitea.simolzimol.net/avatars/019358ccebd80ad0a19ab87e342d1fbc",
"html_url": "https://gitea.simolzimol.net/Simon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2024-09-01T19:33:46Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "Simon"
},
"name": "Discord-infobot",
"full_name": "Simon/Discord-infobot",
"description": "",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 33,
"language": "",
"languages_url": "https://gitea.simolzimol.net/api/v1/repos/Simon/Discord-infobot/languages",
"html_url": "https://gitea.simolzimol.net/Simon/Discord-infobot",
"url": "https://gitea.simolzimol.net/api/v1/repos/Simon/Discord-infobot",
"link": "",
"ssh_url": "git@gitea.simolzimol.net:Simon/Discord-infobot.git",
"clone_url": "https://gitea.simolzimol.net/Simon/Discord-infobot.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2024-09-03T12:51:43Z",
"updated_at": "2025-01-17T14:03:30Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": true,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_fast_forward_only_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://gitea.simolzimol.net/",
"internal": false,
"mirror_interval": "",
"object_format_name": "sha1",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
},
{
"id": 1,
"owner": {
"id": 1,
"login": "Simon",
"login_name": "",
"source_id": 0,
"full_name": "",
"email": "Simon@vp-server.eu",
"avatar_url": "https://gitea.simolzimol.net/avatars/019358ccebd80ad0a19ab87e342d1fbc",
"html_url": "https://gitea.simolzimol.net/Simon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2024-09-01T19:33:46Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "Simon"
},
"name": "Discord-ai-chatbot",
"full_name": "Simon/Discord-ai-chatbot",
"description": "",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 239616,
"language": "Python",
"languages_url": "https://gitea.simolzimol.net/api/v1/repos/Simon/Discord-ai-chatbot/languages",
"html_url": "https://gitea.simolzimol.net/Simon/Discord-ai-chatbot",
"url": "https://gitea.simolzimol.net/api/v1/repos/Simon/Discord-ai-chatbot",
"link": "",
"ssh_url": "git@gitea.simolzimol.net:Simon/Discord-ai-chatbot.git",
"clone_url": "https://gitea.simolzimol.net/Simon/Discord-ai-chatbot.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2024-09-01T19:35:29Z",
"updated_at": "2024-12-18T18:46:16Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": true,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_fast_forward_only_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://gitea.simolzimol.net/",
"internal": false,
"mirror_interval": "",
"object_format_name": "sha1",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
},
{
"id": 3,
"owner": {
"id": 1,
"login": "Simon",
"login_name": "",
"source_id": 0,
"full_name": "",
"email": "Simon@vp-server.eu",
"avatar_url": "https://gitea.simolzimol.net/avatars/019358ccebd80ad0a19ab87e342d1fbc",
"html_url": "https://gitea.simolzimol.net/Simon",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2024-09-01T19:33:46Z",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "",
"website": "",
"description": "",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "Simon"
},
"name": "wetterapp",
"full_name": "Simon/wetterapp",
"description": "",
"empty": false,
"private": false,
"fork": false,
"template": false,
"parent": null,
"mirror": false,
"size": 182,
"language": "Python",
"languages_url": "https://gitea.simolzimol.net/api/v1/repos/Simon/wetterapp/languages",
"html_url": "https://gitea.simolzimol.net/Simon/wetterapp",
"url": "https://gitea.simolzimol.net/api/v1/repos/Simon/wetterapp",
"link": "",
"ssh_url": "git@gitea.simolzimol.net:Simon/wetterapp.git",
"clone_url": "https://gitea.simolzimol.net/Simon/wetterapp.git",
"original_url": "",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2024-09-09T08:23:57Z",
"updated_at": "2024-09-10T13:23:35Z",
"archived_at": "1970-01-01T00:00:00Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": true,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": true,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_squash_merge": true,
"allow_fast_forward_only_merge": true,
"allow_rebase_update": true,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://gitea.simolzimol.net/",
"internal": false,
"mirror_interval": "",
"object_format_name": "sha1",
"mirror_updated": "0001-01-01T00:00:00Z",
"repo_transfer": null
}
]
}
}

View File

@@ -4,8 +4,19 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}App Installer & Manager{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<!-- FontAwesome Icons - mit Fallback -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
crossorigin="anonymous" referrerpolicy="no-referrer">
<!-- Fallback für FontAwesome -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -130,7 +141,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('docker_status') }}">
<i class="fas fa-docker"></i> Docker Status
<i class="fas fa-cube"></i> Docker Status
</a>
</li>
<li class="nav-item">
@@ -216,6 +227,133 @@
}
return true;
}
// Icon Fallback System
function checkAndFixIcons() {
// Prüfe ob FontAwesome geladen wurde
const testIcon = document.createElement('i');
testIcon.className = 'fas fa-home';
testIcon.style.visibility = 'hidden';
testIcon.style.position = 'absolute';
document.body.appendChild(testIcon);
setTimeout(() => {
const computed = window.getComputedStyle(testIcon);
const fontFamily = computed.getPropertyValue('font-family');
if (!fontFamily.includes('Font Awesome')) {
console.warn('FontAwesome nicht geladen - verwende Bootstrap Icons als Fallback');
replaceFontAwesome();
}
document.body.removeChild(testIcon);
}, 100);
}
function replaceFontAwesome() {
// FontAwesome zu Bootstrap Icons Mapping
const iconMap = {
'fas fa-home': 'bi bi-house-fill',
'fas fa-plus': 'bi bi-plus',
'fas fa-play': 'bi bi-play-fill',
'fas fa-stop': 'bi bi-stop-fill',
'fas fa-trash': 'bi bi-trash-fill',
'fas fa-cog': 'bi bi-gear-fill',
'fas fa-docker': 'bi bi-app-indicator',
'fas fa-cube': 'bi bi-cube',
'fas fa-check': 'bi bi-check',
'fas fa-times': 'bi bi-x',
'fas fa-spinner fa-spin': 'bi bi-arrow-clockwise',
'fas fa-exclamation-triangle': 'bi bi-exclamation-triangle-fill',
'fas fa-info-circle': 'bi bi-info-circle-fill',
'fas fa-download': 'bi bi-download',
'fas fa-upload': 'bi bi-upload',
'fas fa-sync-alt': 'bi bi-arrow-repeat',
'fas fa-server': 'bi bi-server',
'fas fa-box': 'bi bi-box',
'fas fa-layer-group': 'bi bi-layers',
'fas fa-terminal': 'bi bi-terminal',
'fas fa-rocket': 'bi bi-rocket',
'fas fa-stethoscope': 'bi bi-heart-pulse',
'fas fa-clipboard-list': 'bi bi-clipboard-check',
'fas fa-bolt': 'bi bi-lightning',
'fas fa-redo': 'bi bi-arrow-clockwise',
'fas fa-external-link-alt': 'bi bi-box-arrow-up-right',
'fas fa-clipboard-check': 'bi bi-clipboard-check',
'fas fa-question-circle': 'bi bi-question-circle',
'fas fa-book': 'bi bi-book',
'fas fa-wrench': 'bi bi-wrench',
'fas fa-file-export': 'bi bi-file-earmark-arrow-down',
'fas fa-broom': 'bi bi-brush'
};
// Erweiterte Ersetzung für komplexe Selektoren
const complexSelectors = [
'i[class*="fas fa-"]',
'i[class*="far fa-"]',
'i[class*="fab fa-"]'
];
complexSelectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
const classes = el.className.split(' ');
const faClasses = classes.filter(cls => cls.startsWith('fa'));
const faString = faClasses.join(' ');
if (iconMap[faString]) {
// Ersetze nur die FontAwesome-Klassen, behalte andere bei
const otherClasses = classes.filter(cls => !cls.startsWith('fa'));
const newClasses = [iconMap[faString], ...otherClasses];
el.className = newClasses.join(' ');
} else if (faClasses.length > 0) {
// Fallback für nicht gemappte Icons
console.warn(`Unbekanntes FontAwesome Icon: ${faString}`);
const otherClasses = classes.filter(cls => !cls.startsWith('fa'));
el.className = ['bi bi-question-circle', ...otherClasses].join(' ');
}
});
});
}
// System Status Check für bessere UX
function checkSystemHealth() {
if (window.location.pathname !== '/config') {
return; // Nur auf bestimmten Seiten ausführen
}
fetch('/api/system_status')
.then(response => response.json())
.then(data => {
// Update UI basierend auf System Status
if (!data.docker.available) {
showSystemAlert('Docker ist nicht verfügbar. <a href="/docker_status">Diagnose ausführen</a>');
}
})
.catch(error => {
console.log('System Health Check fehlgeschlagen:', error);
});
}
function showSystemAlert(message) {
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-warning alert-dismissible fade show';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('.container-fluid');
if (container) {
container.insertBefore(alertDiv, container.firstChild);
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
checkAndFixIcons();
checkSystemHealth();
});
</script>
{% block scripts %}{% endblock %}

View File

@@ -6,10 +6,15 @@
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-docker me-2"></i>Docker Status & Diagnose</h2>
<button class="btn btn-primary" onclick="runDiagnose()">
<i class="fas fa-stethoscope"></i> Diagnose ausführen
</button>
<h2><i class="fas fa-cube me-2"></i>Docker Status & Diagnose</h2>
<div>
<button class="btn btn-outline-secondary me-2" onclick="reconnectDocker()">
<i class="fas fa-sync-alt"></i> Neu verbinden
</button>
<button class="btn btn-primary" onclick="runDiagnose()">
<i class="fas fa-stethoscope"></i> Diagnose ausführen
</button>
</div>
</div>
</div>
</div>
@@ -20,7 +25,7 @@
<div class="card">
<div class="card-body text-center">
<div class="stat-item">
<i class="fas fa-docker fa-2x mb-2" id="dockerIcon"></i>
<i class="fas fa-cube fa-2x mb-2" id="dockerIcon"></i>
<div class="stat-number" id="dockerStatus">Prüfung...</div>
<div class="text-muted">Docker Status</div>
</div>
@@ -237,21 +242,43 @@ function runDiagnose() {
button.disabled = true;
fetch('/api/docker_diagnose')
.then(response => response.json())
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
if (data.error) {
throw new Error(data.error);
}
lastDiagnoseData = data;
displayDiagnoseResults(data);
updateStatusCards(data);
document.getElementById('lastUpdate').textContent = new Date(data.timestamp).toLocaleString();
if (data.timestamp) {
try {
const timestamp = new Date(data.timestamp).toLocaleString();
document.getElementById('lastUpdate').textContent = timestamp;
} catch {
document.getElementById('lastUpdate').textContent = 'Gerade eben';
}
}
})
.catch(error => {
console.error('Diagnose-Fehler:', error);
document.getElementById('diagnoseResults').innerHTML = `
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
Fehler bei der Diagnose: ${error}
Fehler bei der Diagnose: ${error.message || error}
<br><small class="text-muted mt-2 d-block">
Stellen Sie sicher, dass Docker läuft und versuchen Sie es erneut.
<a href="#" onclick="reconnectDocker(); return false;">Docker-Verbindung wiederherstellen</a>
</small>
</div>
`;
setDefaultCardStates();
})
.finally(() => {
button.innerHTML = originalText;
@@ -262,19 +289,65 @@ function runDiagnose() {
// Zeige Diagnose-Ergebnisse
function displayDiagnoseResults(data) {
const container = document.getElementById('diagnoseResults');
if (!data || !data.checks || typeof data.checks !== 'object') {
container.innerHTML = `
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
Keine Diagnose-Daten verfügbar.
</div>
`;
return;
}
let html = '';
for (const [checkName, result] of Object.entries(data.checks)) {
const icon = result.success ? 'fa-check text-success' : 'fa-times text-danger';
const title = formatCheckName(checkName);
html += `
<div class="d-flex align-items-start mb-3">
<i class="fas ${icon} me-3 mt-1"></i>
<div class="flex-grow-1">
<h6 class="mb-1">${title}</h6>
<p class="mb-0 text-muted small">${result.output}</p>
try {
for (const [checkName, result] of Object.entries(data.checks)) {
if (!result || typeof result !== 'object') {
continue;
}
const icon = result.success ? 'fa-check text-success' : 'fa-times text-danger';
const title = formatCheckName(checkName);
const output = result.output || 'Keine Ausgabe';
const details = result.details || '';
html += `
<div class="d-flex align-items-start mb-3">
<i class="fas ${icon} me-3 mt-1"></i>
<div class="flex-grow-1">
<h6 class="mb-1">${title}</h6>
<p class="mb-0 text-muted small">${output}</p>
${details ? `<small class="text-muted">${details}</small>` : ''}
</div>
</div>
`;
}
// Zeige Empfehlungen falls vorhanden
if (data.recommendations && Array.isArray(data.recommendations) && data.recommendations.length > 0) {
html += `
<hr>
<h6 class="text-primary"><i class="fas fa-lightbulb me-2"></i>Empfehlungen:</h6>
<ul class="list-unstyled">
`;
data.recommendations.forEach(rec => {
if (typeof rec === 'string' && rec.trim()) {
html += `<li class="text-muted small"><i class="fas fa-arrow-right me-2"></i>${rec}</li>`;
}
});
html += '</ul>';
}
} catch (e) {
console.error('Fehler beim Anzeigen der Diagnose-Ergebnisse:', e);
html = `
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
Fehler beim Verarbeiten der Diagnose-Daten.
</div>
`;
}
@@ -299,47 +372,226 @@ function formatCheckName(name) {
// Update Status-Cards
function updateStatusCards(data) {
// Sicherheitscheck für data.checks
if (!data || !data.checks || typeof data.checks !== 'object') {
console.warn('Fehlende oder ungültige Diagnose-Daten:', data);
setDefaultCardStates();
return;
}
// Docker Status
if (data.checks.docker_version) {
const dockerIcon = document.getElementById('dockerIcon');
const dockerStatus = document.getElementById('dockerStatus');
if (data.checks.docker_version.success) {
dockerIcon.className = 'fas fa-docker fa-2x mb-2 text-success';
dockerStatus.textContent = 'Installiert';
dockerStatus.className = 'stat-number text-success';
} else {
dockerIcon.className = 'fas fa-docker fa-2x mb-2 text-danger';
dockerStatus.textContent = 'Nicht verfügbar';
dockerStatus.className = 'stat-number text-danger';
try {
if (data.checks.docker_version) {
const dockerIcon = document.getElementById('dockerIcon');
const dockerStatus = document.getElementById('dockerStatus');
if (dockerIcon && dockerStatus) {
if (data.checks.docker_version.success) {
dockerIcon.className = 'fas fa-cube fa-2x mb-2 text-success';
dockerStatus.textContent = 'Installiert';
dockerStatus.className = 'stat-number text-success';
} else {
dockerIcon.className = 'fas fa-cube fa-2x mb-2 text-danger';
dockerStatus.textContent = 'Nicht verfügbar';
dockerStatus.className = 'stat-number text-danger';
}
}
}
} catch (e) {
console.error('Fehler beim Update Docker Status:', e);
}
// Daemon Status
if (data.checks.docker_daemon) {
const daemonIcon = document.getElementById('daemonIcon');
const daemonStatus = document.getElementById('daemonStatus');
if (data.checks.docker_daemon.success) {
daemonIcon.className = 'fas fa-server fa-2x mb-2 text-success';
daemonStatus.textContent = 'Läuft';
daemonStatus.className = 'stat-number text-success';
} else {
daemonIcon.className = 'fas fa-server fa-2x mb-2 text-danger';
daemonStatus.textContent = 'Gestoppt';
daemonStatus.className = 'stat-number text-danger';
try {
if (data.checks.docker_daemon) {
const daemonIcon = document.getElementById('daemonIcon');
const daemonStatus = document.getElementById('daemonStatus');
if (daemonIcon && daemonStatus) {
if (data.checks.docker_daemon.success) {
daemonIcon.className = 'fas fa-server fa-2x mb-2 text-success';
daemonStatus.textContent = 'Läuft';
daemonStatus.className = 'stat-number text-success';
} else {
daemonIcon.className = 'fas fa-server fa-2x mb-2 text-danger';
daemonStatus.textContent = 'Gestoppt';
daemonStatus.className = 'stat-number text-danger';
}
}
}
} catch (e) {
console.error('Fehler beim Update Daemon Status:', e);
}
// Container Count
if (data.checks.containers && data.checks.containers.containers) {
const containerCount = document.getElementById('containerCount');
const count = data.checks.containers.containers.length;
containerCount.textContent = count;
const running = data.checks.containers.containers.filter(c => c.State === 'running').length;
containerCount.title = `${running} laufend, ${count - running} gestoppt`;
// Container Count - mit Sicherheitschecks
try {
if (data.checks.containers) {
const containerCount = document.getElementById('containerCount');
const containerIcon = document.getElementById('containerIcon');
if (containerCount && containerIcon) {
if (data.checks.containers.success &&
data.checks.containers.containers &&
Array.isArray(data.checks.containers.containers)) {
const containers = data.checks.containers.containers;
const count = containers.length;
containerCount.textContent = count;
// Zähle laufende Container sicher
const running = containers.filter(c => {
try {
return c && (c.State === 'running' || c.status === 'running');
} catch {
return false;
}
}).length;
containerCount.title = `${running} laufend, ${count - running} gestoppt`;
if (count > 0) {
containerIcon.className = 'fas fa-box fa-2x mb-2 text-success';
} else {
containerIcon.className = 'fas fa-box fa-2x mb-2 text-secondary';
}
} else {
containerCount.textContent = '0';
containerIcon.className = 'fas fa-box fa-2x mb-2 text-danger';
containerCount.title = 'Container können nicht gelesen werden';
}
}
}
} catch (e) {
console.error('Fehler beim Update Container Status:', e);
}
// Image Count - mit Sicherheitschecks
try {
if (data.checks.images) {
const imageCount = document.getElementById('imageCount');
const imageIcon = document.getElementById('imageIcon');
if (imageCount && imageIcon) {
if (data.checks.images.success && data.checks.images.output) {
// Extrahiere Anzahl aus output-Text (z.B. "5 Images")
const match = data.checks.images.output.match(/(\d+)/);
const count = match ? parseInt(match[1], 10) : 0;
imageCount.textContent = count;
if (count > 0) {
imageIcon.className = 'fas fa-layer-group fa-2x mb-2 text-success';
} else {
imageIcon.className = 'fas fa-layer-group fa-2x mb-2 text-warning';
}
} else {
imageCount.textContent = '0';
imageIcon.className = 'fas fa-layer-group fa-2x mb-2 text-danger';
}
}
}
} catch (e) {
console.error('Fehler beim Update Image Status:', e);
}
}
function setDefaultCardStates() {
// Setze alle Cards auf einen sicheren Default-Zustand
try {
const elements = [
{icon: 'dockerIcon', status: 'dockerStatus', iconClass: 'fas fa-cube fa-2x mb-2 text-secondary', text: 'Unbekannt'},
{icon: 'daemonIcon', status: 'daemonStatus', iconClass: 'fas fa-server fa-2x mb-2 text-secondary', text: 'Unbekannt'},
{icon: 'containerIcon', status: 'containerCount', iconClass: 'fas fa-box fa-2x mb-2 text-secondary', text: '-'},
{icon: 'imageIcon', status: 'imageCount', iconClass: 'fas fa-layer-group fa-2x mb-2 text-secondary', text: '-'}
];
elements.forEach(el => {
const iconEl = document.getElementById(el.icon);
const statusEl = document.getElementById(el.status);
if (iconEl) iconEl.className = el.iconClass;
if (statusEl) {
statusEl.textContent = el.text;
statusEl.className = 'stat-number text-muted';
}
});
} catch (e) {
console.error('Fehler beim Setzen der Default-Zustände:', e);
}
}
// Docker Reconnect
function reconnectDocker() {
const button = event.target.closest('button');
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Verbinde...';
button.disabled = true;
fetch('/api/docker_reconnect', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('success', 'Docker-Verbindung wiederhergestellt');
// Diagnose automatisch neu ausführen
setTimeout(() => runDiagnose(), 500);
} else {
showToast('error', `Fehler beim Verbinden: ${data.error}`);
}
})
.catch(error => {
console.error('Docker Reconnect Fehler:', error);
showToast('error', 'Netzwerkfehler beim Reconnect');
})
.finally(() => {
button.innerHTML = originalText;
button.disabled = false;
});
}
// Toast-Funktion für Benachrichtigungen
function showToast(type, message) {
const toastContainer = getOrCreateToastContainer();
const toastId = 'toast-' + Date.now();
const toastClass = type === 'success' ? 'bg-success' : type === 'error' ? 'bg-danger' : 'bg-info';
const iconClass = type === 'success' ? 'fas fa-check' : type === 'error' ? 'fas fa-exclamation-triangle' : 'fas fa-info-circle';
const toastHtml = `
<div id="${toastId}" class="toast ${toastClass} text-white" role="alert" data-bs-delay="5000">
<div class="toast-header ${toastClass} text-white border-0">
<i class="${iconClass} me-2"></i>
<strong class="me-auto">${type === 'success' ? 'Erfolg' : type === 'error' ? 'Fehler' : 'Info'}</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
${message}
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement);
toast.show();
// Automatisch entfernen nach dem Ausblenden
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
function getOrCreateToastContainer() {
let container = document.getElementById('toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'toast-container';
container.className = 'toast-container position-fixed top-0 end-0 p-3';
container.style.zIndex = '9999';
document.body.appendChild(container);
}
return container;
}
// Docker Desktop Aktionen
@@ -418,6 +670,22 @@ function exportDiagnose() {
// Auto-Diagnose beim Laden
document.addEventListener('DOMContentLoaded', function() {
// Führe automatische Icon-Reparatur aus
setTimeout(() => {
const dockerIcon = document.getElementById('dockerIcon');
if (dockerIcon) {
const computed = window.getComputedStyle(dockerIcon);
const content = computed.getPropertyValue('content');
// Wenn Icon nicht lädt, verwende Fallback
if (content === 'none' || content === '"\\f2dc"' || content === '"\\f1c0"') {
console.log('Docker Icon wird repariert...');
dockerIcon.className = 'bi bi-cube fs-1 mb-2';
dockerIcon.style.color = '#007bff';
}
}
}, 500);
runDiagnose();
refreshDockerLogs();
});

166
test_icons.html Normal file
View File

@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Icon Test</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<!-- FontAwesome Icons -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
crossorigin="anonymous" referrerpolicy="no-referrer">
<!-- Bootstrap Icons Fallback -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
</head>
<body class="p-4">
<div class="container">
<h1>Icon Test</h1>
<div class="row">
<div class="col-md-6">
<h3>FontAwesome Icons</h3>
<div class="d-flex flex-wrap gap-3 mb-4">
<i class="fas fa-home fa-2x text-primary" title="Home"></i>
<i class="fas fa-cube fa-2x text-info" title="Docker (Cube)"></i>
<i class="fas fa-server fa-2x text-success" title="Server"></i>
<i class="fas fa-box fa-2x text-warning" title="Container"></i>
<i class="fas fa-layer-group fa-2x text-secondary" title="Images"></i>
<i class="fas fa-cog fa-2x text-muted" title="Settings"></i>
<i class="fas fa-play fa-2x text-success" title="Start"></i>
<i class="fas fa-stop fa-2x text-danger" title="Stop"></i>
<i class="fas fa-sync-alt fa-2x text-primary" title="Refresh"></i>
<i class="fas fa-stethoscope fa-2x text-info" title="Diagnose"></i>
</div>
</div>
<div class="col-md-6">
<h3>Bootstrap Icons</h3>
<div class="d-flex flex-wrap gap-3 mb-4">
<i class="bi bi-house-fill fs-1 text-primary" title="Home"></i>
<i class="bi bi-app-indicator fs-1 text-info" title="Docker"></i>
<i class="bi bi-server fs-1 text-success" title="Server"></i>
<i class="bi bi-box fs-1 text-warning" title="Container"></i>
<i class="bi bi-layers fs-1 text-secondary" title="Images"></i>
<i class="bi bi-gear-fill fs-1 text-muted" title="Settings"></i>
<i class="bi bi-play-fill fs-1 text-success" title="Start"></i>
<i class="bi bi-stop-fill fs-1 text-danger" title="Stop"></i>
<i class="bi bi-arrow-repeat fs-1 text-primary" title="Refresh"></i>
<i class="bi bi-heart-pulse fs-1 text-info" title="Diagnose"></i>
</div>
</div>
</div>
<div class="alert alert-info">
<h5>Icon Test Status:</h5>
<p id="fontAwesomeStatus">FontAwesome: <span class="text-muted">Wird geprüft...</span></p>
<p id="bootstrapIconStatus">Bootstrap Icons: <span class="text-muted">Wird geprüft...</span></p>
</div>
<button class="btn btn-primary" onclick="testIconFallback()">
<i class="fas fa-test"></i> Test Icon Fallback
</button>
<button class="btn btn-secondary" onclick="showIconMapping()">
<i class="fas fa-list"></i> Zeige Icon-Mapping
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
function checkIconLibraries() {
// FontAwesome Test
const faTest = document.createElement('i');
faTest.className = 'fas fa-home';
faTest.style.visibility = 'hidden';
faTest.style.position = 'absolute';
document.body.appendChild(faTest);
setTimeout(() => {
const faComputed = window.getComputedStyle(faTest);
const faFamily = faComputed.getPropertyValue('font-family');
const faLoaded = faFamily.includes('Font Awesome');
document.getElementById('fontAwesomeStatus').innerHTML =
`FontAwesome: <span class="${faLoaded ? 'text-success' : 'text-danger'}">${faLoaded ? 'Geladen' : 'Nicht verfügbar'}</span>`;
document.body.removeChild(faTest);
// Bootstrap Icons Test
const biTest = document.createElement('i');
biTest.className = 'bi bi-house';
biTest.style.visibility = 'hidden';
biTest.style.position = 'absolute';
document.body.appendChild(biTest);
setTimeout(() => {
const biComputed = window.getComputedStyle(biTest);
const biFamily = biComputed.getPropertyValue('font-family');
const biLoaded = biFamily.includes('bootstrap-icons') || biComputed.content !== 'none';
document.getElementById('bootstrapIconStatus').innerHTML =
`Bootstrap Icons: <span class="${biLoaded ? 'text-success' : 'text-danger'}">${biLoaded ? 'Geladen' : 'Nicht verfügbar'}</span>`;
document.body.removeChild(biTest);
}, 100);
}, 100);
}
function testIconFallback() {
// Ersetze alle FontAwesome Icons mit Bootstrap Icons
const iconMap = {
'fas fa-home': 'bi bi-house-fill',
'fas fa-cube': 'bi bi-cube',
'fas fa-server': 'bi bi-server',
'fas fa-box': 'bi bi-box',
'fas fa-layer-group': 'bi bi-layers',
'fas fa-cog': 'bi bi-gear-fill',
'fas fa-play': 'bi bi-play-fill',
'fas fa-stop': 'bi bi-stop-fill',
'fas fa-sync-alt': 'bi bi-arrow-repeat',
'fas fa-stethoscope': 'bi bi-heart-pulse'
};
Object.keys(iconMap).forEach(faClass => {
const elements = document.querySelectorAll(`i.${faClass.replace(/\s+/g, '.')}`);
elements.forEach(el => {
el.className = el.className.replace(faClass, iconMap[faClass]);
});
});
alert('Icon Fallback angewendet!');
}
function showIconMapping() {
const mapping = {
'fas fa-home': 'bi bi-house-fill',
'fas fa-docker': 'bi bi-app-indicator',
'fas fa-server': 'bi bi-server',
'fas fa-box': 'bi bi-box',
'fas fa-layer-group': 'bi bi-layers',
'fas fa-cog': 'bi bi-gear-fill',
'fas fa-play': 'bi bi-play-fill',
'fas fa-stop': 'bi bi-stop-fill',
'fas fa-sync-alt': 'bi bi-arrow-repeat',
'fas fa-stethoscope': 'bi bi-heart-pulse'
};
let mappingText = 'Icon Mapping:\n\n';
Object.entries(mapping).forEach(([fa, bi]) => {
mappingText += `${fa}${bi}\n`;
});
alert(mappingText);
}
// Test beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
checkIconLibraries();
});
</script>
</body>
</html>