Files
app-installer/templates/index.html
SimolZimol 61acce5338 modified: templates/base.html
modified:   templates/index.html
2025-07-07 23:17:39 +02:00

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 %}