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:
254
app.py
254
app.py
@@ -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)
|
||||
|
||||
660
config.json
660
config.json
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
166
test_icons.html
Normal 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>
|
||||
Reference in New Issue
Block a user