Files
app-installer/templates/docker_status.html
SimolZimol d66ce43838 modified: app.py
modified:   config.json
	new file:   projects_webserver.json
	new file:   public_projects_list.json
	modified:   templates/available_projects.html
	new file:   templates/available_projects_new.html
	modified:   templates/docker_status.html
2025-07-05 16:43:16 +02:00

694 lines
27 KiB
HTML

{% extends "base.html" %}
{% block title %}Docker Status - {{ super() }}{% endblock %}
{% block content %}
<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>
<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>
<!-- Docker Status Cards -->
<div class="row mb-4">
<div class="col-md-3">
<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>
<div class="stat-number" id="dockerStatus">Prüfung...</div>
<div class="text-muted">Docker Status</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body text-center">
<div class="stat-item">
<i class="fas fa-server fa-2x mb-2" id="daemonIcon"></i>
<div class="stat-number" id="daemonStatus">Prüfung...</div>
<div class="text-muted">Daemon Status</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body text-center">
<div class="stat-item">
<i class="fas fa-box fa-2x mb-2" id="containerIcon"></i>
<div class="stat-number" id="containerCount">-</div>
<div class="text-muted">Container</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body text-center">
<div class="stat-item">
<i class="fas fa-layer-group fa-2x mb-2" id="imageIcon"></i>
<div class="stat-number" id="imageCount">-</div>
<div class="text-muted">Images</div>
</div>
</div>
</div>
</div>
</div>
<!-- Diagnose Ergebnisse -->
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
<i class="fas fa-clipboard-list me-2"></i>Diagnose Ergebnisse
</h5>
<span class="badge bg-secondary" id="lastUpdate">Noch nicht ausgeführt</span>
</div>
<div class="card-body">
<div id="diagnoseResults">
<div class="text-center text-muted py-4">
<i class="fas fa-info-circle fa-2x mb-3"></i>
<p>Klicken Sie auf "Diagnose ausführen" um Docker zu überprüfen.</p>
</div>
</div>
</div>
</div>
<!-- Docker Logs -->
<div class="card mt-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-terminal me-2"></i>Docker System Logs
</h5>
</div>
<div class="card-body">
<div id="dockerLogs" class="bg-dark text-light p-3 rounded font-monospace small" style="height: 300px; overflow-y: auto;">
<div class="text-center text-muted">
Docker Logs werden geladen...
</div>
</div>
<div class="mt-2">
<button class="btn btn-outline-info btn-sm" onclick="refreshDockerLogs()">
<i class="fas fa-sync-alt"></i> Logs aktualisieren
</button>
<button class="btn btn-outline-warning btn-sm" onclick="clearDockerLogs()">
<i class="fas fa-broom"></i> Logs leeren
</button>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Schnellaktionen -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-bolt me-2"></i>Docker Aktionen
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button class="btn btn-success" onclick="startDockerDesktop()">
<i class="fas fa-play"></i> Docker Desktop starten
</button>
<button class="btn btn-warning" onclick="restartDockerDesktop()">
<i class="fas fa-redo"></i> Docker Desktop neustarten
</button>
<button class="btn btn-info" onclick="pullHelloWorld()">
<i class="fas fa-download"></i> Test Image herunterladen
</button>
<button class="btn btn-outline-secondary" onclick="openDockerDesktop()">
<i class="fas fa-external-link-alt"></i> Docker Desktop öffnen
</button>
</div>
</div>
</div>
<!-- Systemanforderungen -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-clipboard-check me-2"></i>Systemanforderungen
</h5>
</div>
<div class="card-body">
<div class="requirements-list">
<div class="requirement-item mb-2">
<i class="fas fa-check text-success me-2"></i>
<span>Windows 10/11 (64-bit)</span>
</div>
<div class="requirement-item mb-2">
<i class="fas fa-check text-success me-2"></i>
<span>Hyper-V oder WSL2</span>
</div>
<div class="requirement-item mb-2">
<i class="fas fa-question text-warning me-2" id="ramCheck"></i>
<span>4GB+ RAM empfohlen</span>
</div>
<div class="requirement-item mb-2">
<i class="fas fa-question text-warning me-2" id="diskCheck"></i>
<span>20GB+ freier Speicher</span>
</div>
</div>
</div>
</div>
<!-- Hilfe & Ressourcen -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-question-circle me-2"></i>Hilfe & Ressourcen
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="https://docs.docker.com/desktop/windows/" target="_blank" class="btn btn-outline-primary btn-sm">
<i class="fas fa-book"></i> Docker Desktop Docs
</a>
<a href="https://docs.docker.com/desktop/troubleshoot/" target="_blank" class="btn btn-outline-info btn-sm">
<i class="fas fa-wrench"></i> Troubleshooting Guide
</a>
<a href="https://www.docker.com/products/docker-desktop/" target="_blank" class="btn btn-outline-success btn-sm">
<i class="fas fa-download"></i> Docker Desktop Download
</a>
<button class="btn btn-outline-warning btn-sm" onclick="exportDiagnose()">
<i class="fas fa-file-export"></i> Diagnose exportieren
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Installation Modal -->
<div class="modal fade" id="installModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Docker Desktop Installation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<h6>Installationsschritte:</h6>
<ol>
<li>Laden Sie Docker Desktop von <a href="https://www.docker.com/products/docker-desktop/" target="_blank">docker.com</a> herunter</li>
<li>Führen Sie die Installationsdatei als Administrator aus</li>
<li>Folgen Sie dem Installationsassistenten</li>
<li>Starten Sie Ihren Computer neu</li>
<li>Starten Sie Docker Desktop</li>
<li>Warten Sie bis Docker vollständig geladen ist</li>
</ol>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Hinweis:</strong> Docker Desktop erfordert Hyper-V oder WSL2.
Diese werden automatisch aktiviert falls nötig.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
<a href="https://www.docker.com/products/docker-desktop/" target="_blank" class="btn btn-primary">
<i class="fas fa-download"></i> Docker Desktop herunterladen
</a>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let lastDiagnoseData = null;
// Führe Diagnose aus
function runDiagnose() {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Läuft...';
button.disabled = true;
fetch('/api/docker_diagnose')
.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);
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.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;
button.disabled = false;
});
}
// 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 = '';
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>
`;
}
if (!html) {
html = '<div class="text-center text-muted">Keine Diagnose-Daten verfügbar.</div>';
}
container.innerHTML = html;
}
// Formatiere Check-Namen
function formatCheckName(name) {
const names = {
'docker_version': 'Docker Version',
'docker_daemon': 'Docker Daemon',
'containers': 'Container Status',
'images': 'Docker Images'
};
return names[name] || 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
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
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 - 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
function startDockerDesktop() {
alert('Bitte starten Sie Docker Desktop manuell über das Startmenü.');
}
function restartDockerDesktop() {
if (confirm('Docker Desktop neustarten? Dies kann einige Minuten dauern.')) {
alert('Bitte starten Sie Docker Desktop manuell neu:\n1. Docker Desktop schließen\n2. Docker Desktop wieder öffnen\n3. Warten bis vollständig geladen');
}
}
function pullHelloWorld() {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Lädt...';
button.disabled = true;
// Simuliere Docker Pull (in echter Implementation würde hier eine API-Call erfolgen)
setTimeout(() => {
alert('Test abgeschlossen. Prüfen Sie die Diagnose für Details.');
button.innerHTML = originalText;
button.disabled = false;
runDiagnose();
}, 3000);
}
function openDockerDesktop() {
// Versuche Docker Desktop zu öffnen
alert('Suchen Sie nach "Docker Desktop" im Startmenü oder klicken Sie auf das Docker-Symbol in der Taskleiste.');
}
// Docker Logs
function refreshDockerLogs() {
const logsContainer = document.getElementById('dockerLogs');
logsContainer.innerHTML = '<div class="text-center text-muted">Docker Logs werden geladen...</div>';
// Simuliere Log-Abruf
setTimeout(() => {
const logs = `[${new Date().toISOString()}] Docker Desktop starting...
[${new Date().toISOString()}] Hyper-V backend initialized
[${new Date().toISOString()}] WSL2 engine started
[${new Date().toISOString()}] Docker daemon started
[${new Date().toISOString()}] Ready for connections`;
logsContainer.innerHTML = `<pre class="mb-0">${logs}</pre>`;
logsContainer.scrollTop = logsContainer.scrollHeight;
}, 1000);
}
function clearDockerLogs() {
if (confirm('Docker Logs leeren?')) {
document.getElementById('dockerLogs').innerHTML = '<div class="text-center text-muted">Logs geleert</div>';
}
}
// Export Diagnose
function exportDiagnose() {
if (!lastDiagnoseData) {
alert('Bitte führen Sie zuerst eine Diagnose aus.');
return;
}
const dataStr = JSON.stringify(lastDiagnoseData, null, 2);
const blob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `docker_diagnose_${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}
// 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();
});
</script>
{% endblock %}