924 lines
42 KiB
HTML
924 lines
42 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<!-- Haupt-Statistiken -->
|
|
<div class="header-stats">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<i class="fas fa-box text-white mb-2" style="font-size: 1.5em;"></i>
|
|
<span class="stat-number">{{ projects|length }}</span>
|
|
<span>Installierte Apps</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<i class="fas fa-play-circle text-white mb-2" style="font-size: 1.5em;"></i>
|
|
<span class="stat-number">{{ projects|selectattr('status', 'equalto', 'running')|list|length }}</span>
|
|
<span>Laufende Apps</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<i class="fas fa-cloud-download-alt text-white mb-2" style="font-size: 1.5em;"></i>
|
|
<span class="stat-number">{{ config.projects|length if config.projects else 0 }}</span>
|
|
<span>Verfügbare Apps</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<i class="fab fa-docker text-white mb-2" style="font-size: 1.5em;"></i>
|
|
<span class="stat-number">{{ projects|selectattr('has_dockerfile', 'equalto', true)|list|length }}</span>
|
|
<span>Docker-fähig</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Erweiterte Kategorie-Statistiken -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-chart-pie me-2"></i>App-Kategorien & Typen
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<!-- Web Applications -->
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="category-stat text-center p-3 rounded" style="background: linear-gradient(45deg, #667eea, #764ba2); color: white;">
|
|
<i class="fas fa-globe fa-2x mb-2"></i>
|
|
<div class="stat-number-cat">
|
|
{% set web_apps = config.projects|selectattr('category', 'equalto', 'Web Application')|list if config.projects else [] %}
|
|
{{ web_apps|length }}
|
|
</div>
|
|
<small>Web Applications</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Productivity -->
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="category-stat text-center p-3 rounded" style="background: linear-gradient(45deg, #f093fb, #f5576c); color: white;">
|
|
<i class="fas fa-tasks fa-2x mb-2"></i>
|
|
<div class="stat-number-cat">
|
|
{% set productivity_apps = config.projects|selectattr('category', 'equalto', 'Productivity')|list if config.projects else [] %}
|
|
{{ productivity_apps|length }}
|
|
</div>
|
|
<small>Productivity</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Media & Entertainment -->
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="category-stat text-center p-3 rounded" style="background: linear-gradient(45deg, #4facfe, #00f2fe); color: white;">
|
|
<i class="fas fa-play fa-2x mb-2"></i>
|
|
<div class="stat-number-cat">
|
|
{% set media_apps = config.projects|selectattr('category', 'equalto', 'Media & Entertainment')|list if config.projects else [] %}
|
|
{{ media_apps|length }}
|
|
</div>
|
|
<small>Media & Entertainment</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Web Server -->
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="category-stat text-center p-3 rounded" style="background: linear-gradient(45deg, #fa709a, #fee140); color: white;">
|
|
<i class="fas fa-server fa-2x mb-2"></i>
|
|
<div class="stat-number-cat">
|
|
{% set server_apps = config.projects|selectattr('category', 'equalto', 'Web Server')|list if config.projects else [] %}
|
|
{{ server_apps|length }}
|
|
</div>
|
|
<small>Web Server</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Zusätzliche Technologie-Statistiken -->
|
|
<div class="row mt-3">
|
|
<div class="col-md-4 col-sm-6 mb-2">
|
|
<div class="tech-stat d-flex align-items-center p-2 rounded" style="background: #e3f2fd;">
|
|
<i class="fab fa-js-square text-warning me-2 fa-lg"></i>
|
|
<span class="me-auto"><strong>JavaScript/Node.js:</strong></span>
|
|
<span class="badge bg-warning">
|
|
{% set js_apps = config.projects|selectattr('language', 'equalto', 'JavaScript')|list if config.projects else [] %}
|
|
{{ js_apps|length }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 col-sm-6 mb-2">
|
|
<div class="tech-stat d-flex align-items-center p-2 rounded" style="background: #f3e5f5;">
|
|
<i class="fab fa-python text-success me-2 fa-lg"></i>
|
|
<span class="me-auto"><strong>Python:</strong></span>
|
|
<span class="badge bg-success">
|
|
{% set python_apps = config.projects|selectattr('language', 'equalto', 'Python')|list if config.projects else [] %}
|
|
{{ python_apps|length }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 col-sm-6 mb-2">
|
|
<div class="tech-stat d-flex align-items-center p-2 rounded" style="background: #fce4ec;">
|
|
<i class="fas fa-code text-primary me-2 fa-lg"></i>
|
|
<span class="me-auto"><strong>Andere:</strong></span>
|
|
<span class="badge bg-primary">
|
|
{% set other_apps = config.projects|rejectattr('language', 'in', ['JavaScript', 'Python'])|list if config.projects else [] %}
|
|
{{ other_apps|length }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if not projects %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-rocket fa-3x text-muted mb-3"></i>
|
|
<h3 class="text-muted">Noch keine Apps installiert</h3>
|
|
<p class="text-muted mb-4">Beginnen Sie mit der Installation Ihrer ersten App!</p>
|
|
<a href="{{ url_for('available_projects') }}" class="btn btn-primary btn-lg">
|
|
<i class="fas fa-download"></i> Verfügbare Apps anzeigen
|
|
</a>
|
|
</div>
|
|
{% else %}
|
|
<!-- Apps nach Kategorien gruppiert -->
|
|
{% set category_mapping = {
|
|
'web': {'name': 'Web Applications', 'icon': 'fas fa-globe', 'color': 'primary'},
|
|
'productivity': {'name': 'Productivity', 'icon': 'fas fa-tasks', 'color': 'success'},
|
|
'media': {'name': 'Media & Entertainment', 'icon': 'fas fa-play', 'color': 'info'},
|
|
'server': {'name': 'Web Server', 'icon': 'fas fa-server', 'color': 'warning'},
|
|
'other': {'name': 'Andere', 'icon': 'fas fa-cube', 'color': 'secondary'}
|
|
} %}
|
|
|
|
<!-- Kategorisierte App-Ansicht -->
|
|
<div class="mb-4">
|
|
<nav class="nav nav-pills nav-fill">
|
|
<a class="nav-link active" data-bs-toggle="tab" href="#all-apps">
|
|
<i class="fas fa-th-large me-2"></i>Alle Apps ({{ projects|length }})
|
|
</a>
|
|
<a class="nav-link" data-bs-toggle="tab" href="#by-category">
|
|
<i class="fas fa-layer-group me-2"></i>Nach Kategorie
|
|
</a>
|
|
<a class="nav-link" data-bs-toggle="tab" href="#by-status">
|
|
<i class="fas fa-traffic-light me-2"></i>Nach Status
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<div class="tab-content">
|
|
<!-- Alle Apps -->
|
|
<div class="tab-pane fade show active" id="all-apps">
|
|
<div class="row">
|
|
{% for project in projects %}
|
|
<div class="col-lg-6 col-xl-4 mb-4">
|
|
{% set app_category = project.get('category', 'other')|lower|replace(' ', '')|replace('&', '')|replace('application', '') %}
|
|
{% set cat_info = category_mapping.get(app_category, category_mapping['other']) %}
|
|
|
|
<div class="card project-card h-100 border-{{ cat_info.color }}">
|
|
<div class="card-header bg-{{ cat_info.color }} text-white d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">
|
|
<i class="{{ cat_info.icon }} me-2"></i>{{ project.name }}
|
|
</h5>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<span class="badge bg-light text-dark">{{ project.get('category', 'Unbekannt') }}</span>
|
|
<span class="status-badge status-{{ 'running' if project.status == 'running' else 'stopped' if project.status in ['exited', 'stopped'] else 'unknown' }}">
|
|
<i class="fas fa-circle me-1"></i>
|
|
{% if project.status == 'running' %}Läuft{% elif project.status in ['exited', 'stopped'] %}Gestoppt{% else %}Unbekannt{% endif %}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Projektdetails -->
|
|
<div class="row mb-3">
|
|
<div class="col-6">
|
|
<small class="text-muted"><i class="fab fa-docker me-1"></i>Docker:</small><br>
|
|
{% if project.has_dockerfile %}
|
|
<span class="badge bg-success"><i class="fas fa-check me-1"></i>Verfügbar</span>
|
|
{% else %}
|
|
<span class="badge bg-danger"><i class="fas fa-times me-1"></i>Nicht verfügbar</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-6">
|
|
<small class="text-muted"><i class="fas fa-cog me-1"></i>Konfiguration:</small><br>
|
|
{% if project.has_env_example %}
|
|
<span class="badge bg-success"><i class="fas fa-file-alt me-1"></i>.env vorhanden</span>
|
|
{% else %}
|
|
<span class="badge bg-warning"><i class="fas fa-minus me-1"></i>Keine .env</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if project.readme %}
|
|
<div class="mb-3">
|
|
<small class="text-muted"><i class="fas fa-book me-1"></i>Beschreibung:</small>
|
|
<p class="card-text small text-muted">{{ project.readme[:120] }}{% if project.readme|length > 120 %}...{% endif %}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Zusätzliche Informationen -->
|
|
<div class="row text-center mb-3">
|
|
<div class="col-4">
|
|
<small class="text-muted d-block">Typ</small>
|
|
<i class="{{ cat_info.icon }} text-{{ cat_info.color }}"></i>
|
|
</div>
|
|
<div class="col-4">
|
|
<small class="text-muted d-block">Version</small>
|
|
<span class="badge bg-light text-dark">{{ project.version or 'unbekannt' }}</span>
|
|
</div>
|
|
<div class="col-4">
|
|
<small class="text-muted d-block">Compose</small>
|
|
{% if project.has_docker_compose %}
|
|
<i class="fas fa-check text-success"></i>
|
|
{% else %}
|
|
<i class="fas fa-times text-muted"></i>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<small class="text-muted"><i class="fas fa-calendar me-1"></i>Installiert: {{ project.created }}</small>
|
|
</div>
|
|
<div class="card-footer bg-light">
|
|
<div class="action-buttons d-flex flex-wrap gap-1">
|
|
{% if project.status == 'running' %}
|
|
<a href="{{ url_for('stop_project', project_name=project.name) }}" class="btn btn-warning btn-sm">
|
|
<i class="fas fa-stop"></i> Stoppen
|
|
</a>
|
|
{% else %}
|
|
<a href="{{ url_for('start_project', project_name=project.name) }}" class="btn btn-success btn-sm">
|
|
<i class="fas fa-play"></i> Starten
|
|
</a>
|
|
{% endif %}
|
|
|
|
{% if project.has_dockerfile %}
|
|
<a href="{{ url_for('build_project', project_name=project.name) }}" class="btn btn-info btn-sm">
|
|
<i class="fas fa-hammer"></i> Build
|
|
</a>
|
|
{% endif %}
|
|
|
|
<a href="{{ url_for('project_details', project_name=project.name) }}" class="btn btn-primary btn-sm">
|
|
<i class="fas fa-cog"></i> Details
|
|
</a>
|
|
|
|
<a href="{{ url_for('remove_project', project_name=project.name) }}"
|
|
class="btn btn-danger btn-sm"
|
|
onclick="return confirmAction('remove', '{{ project.name }}')">
|
|
<i class="fas fa-trash"></i> Entfernen
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nach Kategorie -->
|
|
<div class="tab-pane fade" id="by-category">
|
|
{% for category_key, category_info in category_mapping.items() %}
|
|
{% set category_projects = [] %}
|
|
{% for project in projects %}
|
|
{% set proj_cat = project.get('category', 'other')|lower|replace(' ', '')|replace('&', '')|replace('application', '') %}
|
|
{% if proj_cat == category_key %}
|
|
{% set _ = category_projects.append(project) %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if category_projects %}
|
|
<div class="mb-4">
|
|
<h4 class="text-{{ category_info.color }}">
|
|
<i class="{{ category_info.icon }} me-2"></i>{{ category_info.name }} ({{ category_projects|length }})
|
|
</h4>
|
|
<div class="row">
|
|
{% for project in category_projects %}
|
|
<div class="col-lg-6 col-xl-4 mb-3">
|
|
<div class="card project-card border-{{ category_info.color }}">
|
|
<div class="card-header bg-{{ category_info.color }} text-white">
|
|
<h6 class="card-title mb-0">{{ project.name }}</h6>
|
|
<span class="status-badge status-{{ 'running' if project.status == 'running' else 'stopped' if project.status in ['exited', 'stopped'] else 'unknown' }}">
|
|
<i class="fas fa-circle me-1"></i>
|
|
{% if project.status == 'running' %}Läuft{% elif project.status in ['exited', 'stopped'] %}Gestoppt{% else %}Unbekannt{% endif %}
|
|
</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
{% if project.has_dockerfile %}
|
|
<i class="fab fa-docker text-primary me-1"></i>
|
|
{% endif %}
|
|
{% if project.has_env_example %}
|
|
<i class="fas fa-file-alt text-success me-1"></i>
|
|
{% endif %}
|
|
{% if project.has_docker_compose %}
|
|
<i class="fas fa-layer-group text-info me-1"></i>
|
|
{% endif %}
|
|
</div>
|
|
<small class="text-muted">{{ project.created }}</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="btn-group w-100" role="group">
|
|
{% if project.status == 'running' %}
|
|
<a href="{{ url_for('stop_project', project_name=project.name) }}" class="btn btn-warning btn-sm">
|
|
<i class="fas fa-stop"></i>
|
|
</a>
|
|
{% else %}
|
|
<a href="{{ url_for('start_project', project_name=project.name) }}" class="btn btn-success btn-sm">
|
|
<i class="fas fa-play"></i>
|
|
</a>
|
|
{% endif %}
|
|
<a href="{{ url_for('project_details', project_name=project.name) }}" class="btn btn-primary btn-sm">
|
|
<i class="fas fa-cog"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Nach Status -->
|
|
<div class="tab-pane fade" id="by-status">
|
|
{% set running_apps = projects|selectattr('status', 'equalto', 'running')|list %}
|
|
{% set stopped_apps = projects|selectattr('status', 'in', ['exited', 'stopped'])|list %}
|
|
{% set unknown_apps = projects|rejectattr('status', 'in', ['running', 'exited', 'stopped'])|list %}
|
|
|
|
<!-- Laufende Apps -->
|
|
{% if running_apps %}
|
|
<div class="mb-4">
|
|
<h4 class="text-success">
|
|
<i class="fas fa-play-circle me-2"></i>Laufende Apps ({{ running_apps|length }})
|
|
</h4>
|
|
<div class="row">
|
|
{% for project in running_apps %}
|
|
<div class="col-lg-4 col-md-6 mb-3">
|
|
<div class="card border-success">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{{ project.name }}</h6>
|
|
<p class="card-text small text-muted">{{ project.get('category', 'Unbekannt') }}</p>
|
|
<div class="d-flex gap-1">
|
|
<a href="{{ url_for('stop_project', project_name=project.name) }}" class="btn btn-warning btn-sm">
|
|
<i class="fas fa-stop"></i> Stoppen
|
|
</a>
|
|
<a href="{{ url_for('project_details', project_name=project.name) }}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-cog"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Gestoppte Apps -->
|
|
{% if stopped_apps %}
|
|
<div class="mb-4">
|
|
<h4 class="text-warning">
|
|
<i class="fas fa-stop-circle me-2"></i>Gestoppte Apps ({{ stopped_apps|length }})
|
|
</h4>
|
|
<div class="row">
|
|
{% for project in stopped_apps %}
|
|
<div class="col-lg-4 col-md-6 mb-3">
|
|
<div class="card border-warning">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{{ project.name }}</h6>
|
|
<p class="card-text small text-muted">{{ project.get('category', 'Unbekannt') }}</p>
|
|
<div class="d-flex gap-1">
|
|
<a href="{{ url_for('start_project', project_name=project.name) }}" class="btn btn-success btn-sm">
|
|
<i class="fas fa-play"></i> Starten
|
|
</a>
|
|
<a href="{{ url_for('project_details', project_name=project.name) }}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-cog"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Unbekannte Apps -->
|
|
{% if unknown_apps %}
|
|
<div class="mb-4">
|
|
<h4 class="text-danger">
|
|
<i class="fas fa-question-circle me-2"></i>Status unbekannt ({{ unknown_apps|length }})
|
|
</h4>
|
|
<div class="row">
|
|
{% for project in unknown_apps %}
|
|
<div class="col-lg-4 col-md-6 mb-3">
|
|
<div class="card border-danger">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{{ project.name }}</h6>
|
|
<p class="card-text small text-muted">{{ project.get('category', 'Unbekannt') }}</p>
|
|
<div class="d-flex gap-1">
|
|
<a href="{{ url_for('project_details', project_name=project.name) }}" class="btn btn-primary btn-sm">
|
|
<i class="fas fa-cog"></i> Details
|
|
</a>
|
|
{% if project.has_dockerfile %}
|
|
<a href="{{ url_for('build_project', project_name=project.name) }}" class="btn btn-info btn-sm">
|
|
<i class="fas fa-hammer"></i> Build
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-bolt me-2"></i>Schnellaktionen
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-2 col-sm-4 mb-2">
|
|
<a href="{{ url_for('available_projects') }}" class="btn btn-outline-primary w-100">
|
|
<i class="fas fa-download"></i>
|
|
<span class="d-none d-md-inline"> Neue App</span>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 mb-2">
|
|
<a href="{{ url_for('refresh_projects') }}" class="btn btn-outline-info w-100">
|
|
<i class="fas fa-sync-alt"></i>
|
|
<span class="d-none d-md-inline"> Aktualisieren</span>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 mb-2">
|
|
<a href="{{ url_for('config') }}" class="btn btn-outline-secondary w-100">
|
|
<i class="fas fa-cog"></i>
|
|
<span class="d-none d-md-inline"> Einstellungen</span>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 mb-2">
|
|
<button class="btn btn-outline-success w-100" onclick="startAllStoppedApps()">
|
|
<i class="fas fa-play"></i>
|
|
<span class="d-none d-md-inline"> Alle starten</span>
|
|
</button>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 mb-2">
|
|
<button class="btn btn-outline-warning w-100" onclick="stopAllRunningApps()">
|
|
<i class="fas fa-stop"></i>
|
|
<span class="d-none d-md-inline"> Alle stoppen</span>
|
|
</button>
|
|
</div>
|
|
<div class="col-md-2 col-sm-4 mb-2">
|
|
<a href="{{ url_for('docker_status') }}" class="btn btn-outline-info w-100">
|
|
<i class="fab fa-docker"></i>
|
|
<span class="d-none d-md-inline"> Docker Status</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Erweiterte Aktionen -->
|
|
<div class="mt-3">
|
|
<h6 class="text-muted">Erweiterte Aktionen:</h6>
|
|
<div class="row">
|
|
<div class="col-md-3 col-sm-6 mb-2">
|
|
<button class="btn btn-outline-dark w-100 btn-sm" onclick="buildAllApps()">
|
|
<i class="fas fa-hammer"></i> Alle Docker-Apps bauen
|
|
</button>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-2">
|
|
<button class="btn btn-outline-danger w-100 btn-sm" onclick="cleanupContainers()">
|
|
<i class="fas fa-trash-alt"></i> Container bereinigen
|
|
</button>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-2">
|
|
<button class="btn btn-outline-warning w-100 btn-sm" onclick="showSystemInfo()">
|
|
<i class="fas fa-info-circle"></i> System-Info
|
|
</button>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-2">
|
|
<button class="btn btn-outline-success w-100 btn-sm" onclick="exportProjects()">
|
|
<i class="fas fa-file-export"></i> Projekte exportieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Port Management Modal -->
|
|
<div class="modal fade" id="portModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Port auswählen</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="portForm">
|
|
<div class="mb-3">
|
|
<label for="portInput" class="form-label">Port (Standard: 8080)</label>
|
|
<input type="number" class="form-control" id="portInput" value="8080" min="1" max="65535">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-primary" onclick="startWithPort()">Starten</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
let currentProject = '';
|
|
|
|
// Confirm Action Helper
|
|
function confirmAction(action, projectName) {
|
|
const actionText = {
|
|
'remove': 'entfernen',
|
|
'stop': 'stoppen',
|
|
'start': 'starten',
|
|
'build': 'neu bauen'
|
|
};
|
|
|
|
return confirm(`Möchten Sie wirklich "${projectName}" ${actionText[action] || action}?`);
|
|
}
|
|
|
|
// Port Management
|
|
function startWithCustomPort(projectName) {
|
|
currentProject = projectName;
|
|
const modal = new bootstrap.Modal(document.getElementById('portModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function startWithPort() {
|
|
const port = document.getElementById('portInput').value;
|
|
window.location.href = `/start_project/${currentProject}?port=${port}`;
|
|
}
|
|
|
|
// Bulk Actions
|
|
function startAllStoppedApps() {
|
|
const stoppedApps = document.querySelectorAll('.status-stopped').length;
|
|
if (stoppedApps > 0) {
|
|
if (confirm(`Möchten Sie alle ${stoppedApps} gestoppten Apps starten?`)) {
|
|
showProgress('Starte alle gestoppten Apps...');
|
|
|
|
// Sammle alle gestoppten Apps
|
|
const stoppedProjects = [];
|
|
document.querySelectorAll('.status-stopped').forEach(element => {
|
|
const projectCard = element.closest('.project-card');
|
|
if (projectCard) {
|
|
const projectName = projectCard.querySelector('.card-title').textContent.trim();
|
|
stoppedProjects.push(projectName);
|
|
}
|
|
});
|
|
|
|
// Starte Apps nacheinander
|
|
startAppsSequentially(stoppedProjects, 0);
|
|
}
|
|
} else {
|
|
alert('Alle Apps laufen bereits oder es sind keine Apps installiert.');
|
|
}
|
|
}
|
|
|
|
function stopAllRunningApps() {
|
|
const runningApps = document.querySelectorAll('.status-running').length;
|
|
if (runningApps > 0) {
|
|
if (confirm(`Möchten Sie alle ${runningApps} laufenden Apps stoppen?`)) {
|
|
showProgress('Stoppe alle laufenden Apps...');
|
|
|
|
// Sammle alle laufenden Apps
|
|
const runningProjects = [];
|
|
document.querySelectorAll('.status-running').forEach(element => {
|
|
const projectCard = element.closest('.project-card');
|
|
if (projectCard) {
|
|
const projectName = projectCard.querySelector('.card-title').textContent.trim();
|
|
runningProjects.push(projectName);
|
|
}
|
|
});
|
|
|
|
// Stoppe Apps nacheinander
|
|
stopAppsSequentially(runningProjects, 0);
|
|
}
|
|
} else {
|
|
alert('Keine Apps laufen derzeit.');
|
|
}
|
|
}
|
|
|
|
function buildAllApps() {
|
|
const dockerApps = document.querySelectorAll('.fa-docker').length;
|
|
if (dockerApps > 0) {
|
|
if (confirm(`Möchten Sie alle ${dockerApps} Docker-fähigen Apps neu bauen?`)) {
|
|
showProgress('Baue alle Docker-Apps...');
|
|
|
|
// Sammle alle Docker-Apps
|
|
const dockerProjects = [];
|
|
document.querySelectorAll('.fa-docker').forEach(element => {
|
|
const projectCard = element.closest('.project-card');
|
|
if (projectCard) {
|
|
const projectName = projectCard.querySelector('.card-title').textContent.trim();
|
|
dockerProjects.push(projectName);
|
|
}
|
|
});
|
|
|
|
// Baue Apps nacheinander
|
|
buildAppsSequentially(dockerProjects, 0);
|
|
}
|
|
} else {
|
|
alert('Keine Docker-fähigen Apps gefunden.');
|
|
}
|
|
}
|
|
|
|
// Sequential Operations
|
|
function startAppsSequentially(projects, index) {
|
|
if (index >= projects.length) {
|
|
hideProgress();
|
|
showNotification('Alle Apps wurden gestartet!', 'success');
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
return;
|
|
}
|
|
|
|
const projectName = projects[index];
|
|
updateProgress(`Starte ${projectName}... (${index + 1}/${projects.length})`);
|
|
|
|
fetch(`/start_project/${projectName}`, {method: 'POST'})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateProgress(`${projectName} gestartet. Weiter mit nächster App...`);
|
|
setTimeout(() => startAppsSequentially(projects, index + 1), 1000);
|
|
} else {
|
|
showNotification(`Fehler beim Starten von ${projectName}: ${data.message}`, 'error');
|
|
setTimeout(() => startAppsSequentially(projects, index + 1), 1000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler:', error);
|
|
setTimeout(() => startAppsSequentially(projects, index + 1), 1000);
|
|
});
|
|
}
|
|
|
|
function stopAppsSequentially(projects, index) {
|
|
if (index >= projects.length) {
|
|
hideProgress();
|
|
showNotification('Alle Apps wurden gestoppt!', 'success');
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
return;
|
|
}
|
|
|
|
const projectName = projects[index];
|
|
updateProgress(`Stoppe ${projectName}... (${index + 1}/${projects.length})`);
|
|
|
|
fetch(`/stop_project/${projectName}`, {method: 'POST'})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateProgress(`${projectName} gestoppt. Weiter mit nächster App...`);
|
|
setTimeout(() => stopAppsSequentially(projects, index + 1), 1000);
|
|
} else {
|
|
showNotification(`Fehler beim Stoppen von ${projectName}: ${data.message}`, 'error');
|
|
setTimeout(() => stopAppsSequentially(projects, index + 1), 1000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler:', error);
|
|
setTimeout(() => stopAppsSequentially(projects, index + 1), 1000);
|
|
});
|
|
}
|
|
|
|
function buildAppsSequentially(projects, index) {
|
|
if (index >= projects.length) {
|
|
hideProgress();
|
|
showNotification('Alle Apps wurden gebaut!', 'success');
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
return;
|
|
}
|
|
|
|
const projectName = projects[index];
|
|
updateProgress(`Baue ${projectName}... (${index + 1}/${projects.length})`);
|
|
|
|
fetch(`/build_project/${projectName}`, {method: 'POST'})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateProgress(`${projectName} gebaut. Weiter mit nächster App...`);
|
|
setTimeout(() => buildAppsSequentially(projects, index + 1), 2000);
|
|
} else {
|
|
showNotification(`Fehler beim Bauen von ${projectName}: ${data.message}`, 'error');
|
|
setTimeout(() => buildAppsSequentially(projects, index + 1), 1000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler:', error);
|
|
setTimeout(() => buildAppsSequentially(projects, index + 1), 1000);
|
|
});
|
|
}
|
|
|
|
// Additional Actions
|
|
function cleanupContainers() {
|
|
if (confirm('Möchten Sie alle gestoppten Container und nicht verwendeten Images entfernen?')) {
|
|
showProgress('Bereinige Container und Images...');
|
|
|
|
fetch('/api/cleanup_docker', {method: 'POST'})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
hideProgress();
|
|
if (data.success) {
|
|
showNotification('Container-Bereinigung abgeschlossen!', 'success');
|
|
} else {
|
|
showNotification(`Fehler bei der Bereinigung: ${data.message}`, 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
hideProgress();
|
|
showNotification('Fehler bei der Bereinigung', 'error');
|
|
});
|
|
}
|
|
}
|
|
|
|
function showSystemInfo() {
|
|
fetch('/api/system_info')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const info = `
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>System</h6>
|
|
<p><strong>Docker Status:</strong> ${data.docker_status ? 'Verfügbar' : 'Nicht verfügbar'}</p>
|
|
<p><strong>Installierte Apps:</strong> ${data.installed_apps}</p>
|
|
<p><strong>Laufende Apps:</strong> ${data.running_apps}</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Ressourcen</h6>
|
|
<p><strong>Speicher:</strong> ${data.disk_usage || 'Unbekannt'}</p>
|
|
<p><strong>Container:</strong> ${data.container_count || 0}</p>
|
|
<p><strong>Images:</strong> ${data.image_count || 0}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
showModal('System-Information', info);
|
|
})
|
|
.catch(error => {
|
|
showNotification('Fehler beim Laden der System-Informationen', 'error');
|
|
});
|
|
}
|
|
|
|
function exportProjects() {
|
|
if (confirm('Möchten Sie eine Liste aller installierten Projekte exportieren?')) {
|
|
window.location.href = '/api/export_projects';
|
|
}
|
|
}
|
|
|
|
// Progress and Notification Functions
|
|
function showProgress(message) {
|
|
const progressHtml = `
|
|
<div id="progress-modal" class="modal fade" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-body text-center">
|
|
<div class="spinner-border text-primary mb-3" role="status">
|
|
<span class="visually-hidden">Lädt...</span>
|
|
</div>
|
|
<h5 id="progress-message">${message}</h5>
|
|
<div class="progress mt-3">
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
|
role="progressbar" style="width: 100%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', progressHtml);
|
|
const modal = new bootstrap.Modal(document.getElementById('progress-modal'));
|
|
modal.show();
|
|
}
|
|
|
|
function updateProgress(message) {
|
|
const progressMessage = document.getElementById('progress-message');
|
|
if (progressMessage) {
|
|
progressMessage.textContent = message;
|
|
}
|
|
}
|
|
|
|
function hideProgress() {
|
|
const progressModal = document.getElementById('progress-modal');
|
|
if (progressModal) {
|
|
const modal = bootstrap.Modal.getInstance(progressModal);
|
|
if (modal) {
|
|
modal.hide();
|
|
}
|
|
setTimeout(() => progressModal.remove(), 500);
|
|
}
|
|
}
|
|
|
|
function showNotification(message, type = 'info') {
|
|
const alertClass = type === 'success' ? 'alert-success' :
|
|
type === 'error' ? 'alert-danger' : 'alert-info';
|
|
|
|
const notificationHtml = `
|
|
<div class="alert ${alertClass} alert-dismissible fade show position-fixed"
|
|
style="top: 20px; right: 20px; z-index: 9999;" role="alert">
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', notificationHtml);
|
|
|
|
// Auto-remove nach 5 Sekunden
|
|
setTimeout(() => {
|
|
const alerts = document.querySelectorAll('.alert');
|
|
alerts.forEach(alert => {
|
|
if (alert.textContent.includes(message)) {
|
|
alert.remove();
|
|
}
|
|
});
|
|
}, 5000);
|
|
}
|
|
|
|
function showModal(title, content) {
|
|
const modalHtml = `
|
|
<div class="modal fade" id="info-modal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">${title}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
${content}
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
const modal = new bootstrap.Modal(document.getElementById('info-modal'));
|
|
modal.show();
|
|
|
|
// Modal nach dem Schließen entfernen
|
|
document.getElementById('info-modal').addEventListener('hidden.bs.modal', function () {
|
|
this.remove();
|
|
});
|
|
}
|
|
|
|
// Auto-update status badges
|
|
function updateStatus() {
|
|
fetch('/api/status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Update status badges
|
|
if (data.projects) {
|
|
data.projects.forEach(project => {
|
|
const statusBadges = document.querySelectorAll(`[data-project="${project.name}"]`);
|
|
statusBadges.forEach(badge => {
|
|
badge.className = `status-badge status-${project.status}`;
|
|
badge.innerHTML = `<i class="fas fa-circle me-1"></i>${project.status_text}`;
|
|
});
|
|
});
|
|
}
|
|
})
|
|
.catch(error => console.log('Status update failed:', error));
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Update status every 30 seconds
|
|
setInterval(updateStatus, 30000);
|
|
|
|
// Add project data attributes to status badges
|
|
document.querySelectorAll('.status-badge').forEach(badge => {
|
|
const projectCard = badge.closest('.project-card');
|
|
if (projectCard) {
|
|
const projectName = projectCard.querySelector('.card-title').textContent.trim();
|
|
badge.setAttribute('data-project', projectName);
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|