Files
app-installer/templates/project_details.html
SimolZimol 0f83f15588 new file: README.md
new file:   app.py
	new file:   config.example.json
	new file:   config.json
	new file:   docker_diagnose.py
	new file:   requirements.txt
	new file:   start.bat
	new file:   templates/available_projects.html
	new file:   templates/base.html
	new file:   templates/config.html
	new file:   templates/docker_status.html
	new file:   templates/index.html
	new file:   templates/project_details.html
	new file:   templates/project_details_fixed.html
	new file:   templates/project_details_new.html
	new file:   templates/project_details_old.html
	.gitignore
2025-07-04 23:50:04 +02:00

907 lines
37 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ project.name }} - Details{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>
<i class="fas fa-cube me-2"></i>{{ project.name }}
<span class="status-badge status-{{ 'running' if project.status == 'running' else 'stopped' if project.status in ['exited', 'stopped'] else 'unknown' }} ms-2">
<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>
</h2>
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Zurück
</a>
</div>
<div class="row">
<div class="col-lg-8">
<!-- Projektinformationen -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-info-circle me-2"></i>Projektinformationen
</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<strong>Name:</strong> {{ project.name }}
</div>
<div class="col-md-6">
<strong>Pfad:</strong> <code>{{ project.path }}</code>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Docker verfügbar:</strong>
{% if project.has_dockerfile %}
<i class="fas fa-check text-success"></i> Ja
{% else %}
<i class="fas fa-times text-danger"></i> Nein
{% endif %}
</div>
<div class="col-md-6">
<strong>Umgebungskonfiguration:</strong>
{% if project.has_env_example %}
<i class="fas fa-check text-success"></i> .env.example vorhanden
{% else %}
<i class="fas fa-minus text-warning"></i> Keine .env.example
{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Docker Compose:</strong>
{% if project.has_docker_compose %}
<i class="fas fa-check text-success"></i> Verfügbar
{% else %}
<i class="fas fa-times text-muted"></i> Nicht verfügbar
{% endif %}
</div>
<div class="col-md-6">
<strong>Installiert:</strong> {{ project.created }}
</div>
</div>
{% if project.readme %}
<div class="mt-4">
<h6>README:</h6>
<div class="bg-light p-3 rounded">
<pre class="mb-0 small">{{ project.readme }}</pre>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Umgebungskonfiguration -->
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
<i class="fas fa-edit me-2"></i>Umgebungskonfiguration (.env)
</h5>
<div>
<button class="btn btn-outline-info btn-sm" onclick="resetToExample()">
<i class="fas fa-undo"></i> Beispiel wiederherstellen
</button>
<button class="btn btn-outline-warning btn-sm" onclick="validateEnv()">
<i class="fas fa-check-circle"></i> Validieren
</button>
</div>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('save_env', project_name=project.name) }}">
<div class="mb-3">
<textarea class="form-control font-monospace"
name="env_content"
id="envContent"
rows="15"
placeholder="Hier können Sie die Umgebungsvariablen für das Projekt konfigurieren...">{{ env_content }}</textarea>
<div class="form-text">
Konfigurieren Sie hier die Umgebungsvariablen für Ihr Projekt.
Diese werden in die .env Datei gespeichert.
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button type="button" class="btn btn-outline-secondary me-md-2" onclick="previewChanges()">
<i class="fas fa-eye"></i> Vorschau
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> .env speichern
</button>
</div>
</form>
</div>
</div>
<!-- Docker Logs -->
<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-terminal me-2"></i>Container Logs
</h5>
<button class="btn btn-outline-info btn-sm" onclick="refreshLogs()">
<i class="fas fa-sync-alt"></i> Aktualisieren
</button>
</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">
Logs werden geladen...
</div>
</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>Container-Steuerung
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if project.status == 'running' %}
<a href="{{ url_for('stop_project', project_name=project.name) }}" class="btn btn-warning">
<i class="fas fa-stop"></i> Container stoppen
</a>
<button class="btn btn-info" onclick="restartContainer()">
<i class="fas fa-redo"></i> Container neustarten
</button>
{% else %}
<button class="btn btn-success" id="quickStartButton" onclick="quickStartContainer()">
<i class="fas fa-play"></i> Schnellstart (Auto-Port)
</button>
<button class="btn btn-outline-success" onclick="showPortSelection()">
<i class="fas fa-cog"></i> Erweiterte Start-Optionen
</button>
{% endif %}
<a href="{{ url_for('build_project', project_name=project.name) }}" class="btn btn-primary">
<i class="fas fa-hammer"></i> Image neu bauen
</a>
<button class="btn btn-danger" onclick="removeProjectSafely()">
<i class="fas fa-trash"></i> Projekt entfernen
</button>
</div>
</div>
</div>
<!-- Port-Status -->
{% if project.status == 'running' %}
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-network-wired me-2"></i>Aktive Verbindungen
</h5>
</div>
<div class="card-body">
<div id="activeConnections">
<div class="d-flex justify-content-between align-items-center">
<span>HTTP:</span>
<a href="http://localhost:8080" target="_blank" class="btn btn-outline-primary btn-sm">
:8080 <i class="fas fa-external-link-alt"></i>
</a>
</div>
</div>
<div class="mt-3">
<small class="text-muted">
Klicken Sie auf die Links um die Anwendung zu öffnen.
</small>
</div>
</div>
</div>
{% endif %}
<!-- Container-Statistiken -->
{% if project.status == 'running' %}
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-line me-2"></i>Monitoring
</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6">
<div class="stat-item">
<span class="stat-number small" id="cpuUsage">-</span>
<span class="small text-muted">CPU %</span>
</div>
</div>
<div class="col-6">
<div class="stat-item">
<span class="stat-number small" id="memUsage">-</span>
<span class="small text-muted">RAM MB</span>
</div>
</div>
</div>
<div class="row text-center mt-2">
<div class="col-6">
<div class="stat-item">
<span class="stat-number small" id="networkIn">-</span>
<span class="small text-muted">Net In</span>
</div>
</div>
<div class="col-6">
<div class="stat-item">
<span class="stat-number small" id="networkOut">-</span>
<span class="small text-muted">Net Out</span>
</div>
</div>
</div>
<div class="mt-3">
<button class="btn btn-outline-info btn-sm w-100" onclick="updateMonitoring()">
<i class="fas fa-sync-alt"></i> Aktualisieren
</button>
</div>
</div>
</div>
{% endif %}
<!-- Hilfe -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-question-circle me-2"></i>Hilfe & Tipps
</h5>
</div>
<div class="card-body">
<div class="small">
{% if not project.has_dockerfile %}
<div class="alert alert-warning">
<strong>Kein Dockerfile:</strong> Erstellen Sie ein Dockerfile in Ihrem Projektverzeichnis.
</div>
{% endif %}
{% if not project.has_env_example %}
<div class="alert alert-info">
<strong>Tipp:</strong> Erstellen Sie eine .env.example Datei für Umgebungsvariablen.
</div>
{% endif %}
<ul class="list-unstyled mb-0">
<li><i class="fas fa-check text-success"></i> Bauen Sie das Image vor dem ersten Start</li>
<li><i class="fas fa-check text-success"></i> Überprüfen Sie die .env Konfiguration</li>
<li><i class="fas fa-check text-success"></i> Beachten Sie die Container-Logs bei Problemen</li>
</ul>
</div>
</div>
</div>
<!-- Debug-Panel -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-bug me-2"></i>Debug-Informationen
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button class="btn btn-outline-secondary btn-sm" onclick="showContainerLogs()">
<i class="fas fa-file-alt"></i> Container-Logs anzeigen
</button>
<button class="btn btn-outline-info btn-sm" onclick="inspectContainer()">
<i class="fas fa-search"></i> Container inspizieren
</button>
<button class="btn btn-outline-warning btn-sm" onclick="checkPortStatus()">
<i class="fas fa-network-wired"></i> Port-Status prüfen
</button>
</div>
<div id="debugOutput" class="mt-3 small" style="display: none;">
<div class="bg-light p-2 rounded">
<pre id="debugContent"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Globale Variable für Projekt-Name
const PROJECT_NAME = '{{ project.name }}';
// Container-Verwaltung - Verbesserte Version
function quickStartContainer() {
const button = document.getElementById('quickStartButton');
if (!button) return;
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Startet...';
button.disabled = true;
console.log('Starting container with auto-port selection...');
// Verwende API-Endpoint für bessere Kontrolle
fetch(`/api/start_project/${PROJECT_NAME}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({})
})
.then(response => response.json())
.then(data => {
console.log('Start response:', data);
if (data.success) {
// Erfolg - zeige Erfolgsmeldung und aktualisiere UI
showSuccessMessage(`Container erfolgreich gestartet auf Port ${data.port}!`);
// Aktualisiere Button-Status nach 2 Sekunden
setTimeout(() => {
location.reload();
}, 2000);
} else {
// Fehler behandeln
console.error('Start error:', data.error);
showErrorMessage(`Fehler beim Start: ${data.error || 'Unbekannter Fehler'}`);
// Automatisch Debug-Informationen anzeigen bei Container-Problemen
if (data.error && data.error.includes('läuft nicht')) {
setTimeout(() => {
console.log('Container läuft nicht - zeige Debug-Informationen...');
showContainerLogs();
}, 1000);
}
// Prüfe ob es ein Port-Problem ist
if (data.error && (data.error.includes('Port') || data.error.includes('port'))) {
setTimeout(() => {
showPortSelectionWithError(data.error);
}, 1000);
}
}
})
.catch(error => {
console.error('Network error:', error);
showErrorMessage(`Netzwerkfehler: ${error.message || 'Unbekannter Netzwerkfehler'}`);
})
.finally(() => {
// Button zurücksetzen
button.innerHTML = originalText;
button.disabled = false;
});
}
function showPortSelection() {
const portSelection = `
<div class="modal fade" id="portSelectionModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Port-Auswahl für ${PROJECT_NAME}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="customPort" class="form-label">Port auswählen:</label>
<input type="number" class="form-control" id="customPort" value="8080" min="1" max="65535">
<div class="form-text">
Beliebte Ports: 8080 (Standard), 3000 (Node.js), 5000 (Flask), 8081-8090 (Alternative)
</div>
</div>
<div id="portStatus" class="mb-3"></div>
<div class="d-grid gap-2">
<button class="btn btn-outline-info" onclick="checkPortAvailability()">
<i class="fas fa-search"></i> Port prüfen
</button>
<button class="btn btn-outline-warning" onclick="findFreePortForStart()">
<i class="fas fa-magic"></i> Automatisch freien Port finden
</button>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-success" onclick="startWithSelectedPort()">
<i class="fas fa-play"></i> Container starten
</button>
</div>
</div>
</div>
</div>
`;
// Entferne existierendes Modal falls vorhanden
const existingModal = document.getElementById('portSelectionModal');
if (existingModal) {
existingModal.remove();
}
// Füge neues Modal hinzu
document.body.insertAdjacentHTML('beforeend', portSelection);
// Modal anzeigen
const modal = new bootstrap.Modal(document.getElementById('portSelectionModal'));
modal.show();
// Port direkt prüfen
setTimeout(checkPortAvailability, 500);
}
function showPortSelectionWithError(errorMessage) {
showPortSelection();
setTimeout(() => {
const statusElement = document.getElementById('portStatus');
if (statusElement) {
statusElement.innerHTML = `<div class="alert alert-warning"><i class="fas fa-exclamation-triangle"></i> ${errorMessage}</div>`;
}
}, 600);
}
function checkPortAvailability() {
const port = document.getElementById('customPort').value;
const statusElement = document.getElementById('portStatus');
if (!port || port < 1 || port > 65535) {
statusElement.innerHTML = '<div class="alert alert-danger">Ungültiger Port (1-65535)</div>';
return;
}
statusElement.innerHTML = '<div class="alert alert-info"><i class="fas fa-spinner fa-spin"></i> Prüfe Port verfügbarkeit...</div>';
fetch(`/api/check_port/${port}`)
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.available) {
statusElement.innerHTML = `<div class="alert alert-success"><i class="fas fa-check"></i> Port ${port} ist verfügbar!</div>`;
} else {
statusElement.innerHTML = `<div class="alert alert-warning"><i class="fas fa-times"></i> Port ${port} ist bereits belegt</div>`;
}
} else {
statusElement.innerHTML = `<div class="alert alert-danger">Fehler: ${data.error}</div>`;
}
})
.catch(error => {
statusElement.innerHTML = `<div class="alert alert-danger">Netzwerkfehler: ${error.message}</div>`;
});
}
function findFreePortForStart() {
const statusElement = document.getElementById('portStatus');
statusElement.innerHTML = '<div class="alert alert-info"><i class="fas fa-spinner fa-spin"></i> Suche freien Port...</div>';
fetch('/api/find_available_port?start=8080')
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('customPort').value = data.port;
statusElement.innerHTML = `<div class="alert alert-success"><i class="fas fa-check"></i> Freier Port gefunden: ${data.port}</div>`;
} else {
statusElement.innerHTML = `<div class="alert alert-danger">Kein freier Port gefunden: ${data.error}</div>`;
}
})
.catch(error => {
statusElement.innerHTML = `<div class="alert alert-danger">Fehler bei Port-Suche: ${error.message}</div>`;
});
}
function startWithSelectedPort() {
const port = document.getElementById('customPort').value;
const button = event.target;
const originalText = button.innerHTML;
if (!port || port < 1 || port > 65535) {
showErrorMessage('Bitte geben Sie einen gültigen Port ein (1-65535)');
return;
}
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Startet...';
button.disabled = true;
fetch(`/api/start_project/${PROJECT_NAME}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({port: parseInt(port)})
})
.then(response => response.json())
.then(data => {
console.log('Start response:', data);
if (data.success) {
// Erfolg - schließe Modal und zeige Erfolg
bootstrap.Modal.getInstance(document.getElementById('portSelectionModal')).hide();
showSuccessMessage(`Container erfolgreich gestartet auf Port ${data.port}!`);
setTimeout(() => {
location.reload();
}, 2000);
} else {
// Fehler - zeige Fehlermeldung aber behalte Modal offen
const statusElement = document.getElementById('portStatus');
statusElement.innerHTML = `<div class="alert alert-danger"><i class="fas fa-times"></i> ${data.error}</div>`;
// Bei Port-Konflikt, schlage Alternative vor
if (data.alternative_port) {
statusElement.innerHTML += `<button class="btn btn-warning btn-sm mt-2" onclick="useAlternativePort(${data.alternative_port})">
<i class="fas fa-arrow-right"></i> Port ${data.alternative_port} verwenden
</button>`;
}
}
})
.catch(error => {
console.error('Network error:', error);
const statusElement = document.getElementById('portStatus');
statusElement.innerHTML = `<div class="alert alert-danger">Netzwerkfehler: ${error.message}</div>`;
})
.finally(() => {
button.innerHTML = originalText;
button.disabled = false;
});
}
function useAlternativePort(port) {
document.getElementById('customPort').value = port;
checkPortAvailability();
}
// Hilfsfunktionen für Nachrichten
function showSuccessMessage(message) {
showToast(message, 'success');
}
function showErrorMessage(message) {
showToast(message, 'error');
}
function showToast(message, type) {
const toastHtml = `
<div class="toast align-items-center text-white bg-${type === 'success' ? 'success' : 'danger'} border-0" role="alert">
<div class="d-flex">
<div class="toast-body">
<i class="fas fa-${type === 'success' ? 'check' : 'exclamation-triangle'} me-2"></i>
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
`;
// Toast Container erstellen falls nicht vorhanden
let toastContainer = document.getElementById('toastContainer');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toastContainer';
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
toastContainer.style.zIndex = '9999';
document.body.appendChild(toastContainer);
}
// Toast hinzufügen
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
// Toast anzeigen
const toastElement = toastContainer.lastElementChild;
const toast = new bootstrap.Toast(toastElement, {delay: 5000});
toast.show();
// Toast nach dem Verstecken entfernen
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
// Container neustarten - verbessert
function restartContainer() {
if (!confirm('Container wirklich neustarten?')) return;
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Startet neu...';
button.disabled = true;
fetch(`/api/restart_project/${PROJECT_NAME}`, {method: 'POST'})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message);
setTimeout(() => location.reload(), 2000);
} else {
showErrorMessage(data.error || 'Fehler beim Neustart');
}
})
.catch(error => {
showErrorMessage(`Netzwerkfehler: ${error.message}`);
})
.finally(() => {
button.innerHTML = originalText;
button.disabled = false;
});
}
// Docker Logs - verbessert
function refreshLogs() {
const logsContainer = document.getElementById('dockerLogs');
if (!logsContainer) return;
logsContainer.innerHTML = '<div class="text-center text-muted">Logs werden geladen...</div>';
fetch(`/api/container_logs/${PROJECT_NAME}`)
.then(response => response.json())
.then(data => {
if (data.success) {
const logs = data.logs || 'Keine Logs verfügbar';
logsContainer.innerHTML = `<pre class="mb-0">${logs}</pre>`;
logsContainer.scrollTop = logsContainer.scrollHeight;
} else {
logsContainer.innerHTML = `<div class="text-warning">Logs nicht verfügbar: ${data.error}</div>`;
}
})
.catch(error => {
logsContainer.innerHTML = `<div class="text-danger">Fehler beim Laden der Logs: ${error.message}</div>`;
});
}
// Monitoring Update
function updateMonitoring() {
fetch(`/api/container_stats/${PROJECT_NAME}`)
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('cpuUsage').textContent = data.stats.cpu || '-';
document.getElementById('memUsage').textContent = data.stats.memory || '-';
document.getElementById('networkIn').textContent = data.stats.network_in || '-';
document.getElementById('networkOut').textContent = data.stats.network_out || '-';
}
})
.catch(error => console.log('Monitoring update failed:', error));
}
// .env Funktionen
function resetToExample() {
if (confirm('Möchten Sie die aktuelle .env mit der .env.example überschreiben?')) {
fetch(`/api/reset_env/${PROJECT_NAME}`, {method: 'POST'})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('envContent').value = data.content;
showSuccessMessage('.env auf Beispiel zurückgesetzt');
} else {
showErrorMessage('Fehler: ' + data.message);
}
})
.catch(error => showErrorMessage('Netzwerkfehler: ' + error.message));
}
}
function validateEnv() {
const envContent = document.getElementById('envContent').value;
fetch(`/api/validate_env/${PROJECT_NAME}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({content: envContent})
})
.then(response => response.json())
.then(data => {
if (data.valid) {
showSuccessMessage('✅ .env Konfiguration ist gültig!');
} else {
showErrorMessage('❌ .env Konfiguration hat Probleme:\n' + data.errors.join('\n'));
}
})
.catch(error => showErrorMessage('Fehler bei der Validierung: ' + error.message));
}
function previewChanges() {
const envContent = document.getElementById('envContent').value;
const preview = window.open('', '_blank', 'width=600,height=400');
preview.document.write(`
<html>
<head><title>Umgebungskonfiguration Vorschau</title></head>
<body style="font-family: monospace; padding: 20px;">
<h3>Vorschau der .env Datei:</h3>
<pre style="background: #f5f5f5; padding: 10px; border-radius: 5px;">${envContent}</pre>
</body>
</html>
`);
}
// Beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
refreshLogs();
updateMonitoring();
// Auto-Update alle 10 Sekunden
setInterval(() => {
updateMonitoring();
}, 10000);
});
// Debug-Funktionen
function showContainerLogs() {
const debugOutput = document.getElementById('debugOutput');
const debugContent = document.getElementById('debugContent');
fetch(`/api/container_logs/${PROJECT_NAME}`)
.then(response => response.json())
.then(data => {
debugOutput.style.display = 'block';
if (data.success) {
debugContent.textContent = data.logs || 'Keine Logs verfügbar';
} else {
debugContent.textContent = 'Fehler beim Laden der Logs: ' + data.error;
}
})
.catch(error => {
debugOutput.style.display = 'block';
debugContent.textContent = 'Netzwerkfehler: ' + error.message;
});
}
function inspectContainer() {
const debugOutput = document.getElementById('debugOutput');
const debugContent = document.getElementById('debugContent');
fetch(`/api/container_inspect/${PROJECT_NAME}`)
.then(response => response.json())
.then(data => {
debugOutput.style.display = 'block';
if (data.success) {
debugContent.textContent = JSON.stringify(data.container_info, null, 2);
} else {
debugContent.textContent = 'Fehler beim Inspizieren: ' + data.error;
}
})
.catch(error => {
debugOutput.style.display = 'block';
debugContent.textContent = 'Netzwerkfehler: ' + error.message;
});
}
function checkPortStatus() {
const debugOutput = document.getElementById('debugOutput');
const debugContent = document.getElementById('debugContent');
fetch(`/api/find_available_port`)
.then(response => response.json())
.then(data => {
debugOutput.style.display = 'block';
if (data.success) {
debugContent.textContent = `Nächster freier Port: ${data.port}\n\nPort-Check-Details:\n`;
// Prüfe mehrere Ports
for (let port = 8080; port <= 8090; port++) {
fetch(`/api/check_port/${port}`)
.then(resp => resp.json())
.then(portData => {
debugContent.textContent += `Port ${port}: ${portData.available ? 'FREI' : 'BELEGT'}\n`;
});
}
} else {
debugContent.textContent = 'Fehler beim Port-Check: ' + data.error;
}
})
.catch(error => {
debugOutput.style.display = 'block';
debugContent.textContent = 'Netzwerkfehler: ' + error.message;
});
}
// Erweiterte Projekt-Entfernung mit Ajax
function removeProjectSafely() {
if (!confirm(`⚠️ ACHTUNG: Möchten Sie das Projekt "${PROJECT_NAME}" wirklich vollständig entfernen?\n\nDies wird:\n✗ Container stoppen und entfernen\n✗ Docker Image löschen\n✗ Alle Projektdateien löschen\n\nDiese Aktion kann NICHT rückgängig gemacht werden!`)) {
return;
}
// Erstelle Progress Modal
const progressModal = `
<div class="modal fade" id="removeProgressModal" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-trash me-2"></i>Projekt entfernen
</h5>
</div>
<div class="modal-body">
<div class="progress mb-3">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%" id="removeProgress"></div>
</div>
<div id="removeStatus">Beginne Entfernung...</div>
<div id="removeDetails" class="mt-3 small text-muted"></div>
</div>
<div class="modal-footer" id="removeModalFooter" style="display: none;">
<button type="button" class="btn btn-secondary" onclick="closeRemoveModal()">Schließen</button>
</div>
</div>
</div>
</div>
`;
// Modal hinzufügen und anzeigen
document.body.insertAdjacentHTML('beforeend', progressModal);
const modal = new bootstrap.Modal(document.getElementById('removeProgressModal'));
modal.show();
// Start removal process
updateRemovalProgress(25, 'Container wird gestoppt...');
fetch(`/api/remove_project/${PROJECT_NAME}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
updateRemovalProgress(100, '✅ Projekt erfolgreich entfernt!');
document.getElementById('removeDetails').innerHTML = `
<div class="alert alert-success">
<strong>Erfolgreich!</strong><br>
${data.message}
</div>
`;
// Nach 2 Sekunden zur Hauptseite
setTimeout(() => {
window.location.href = '/';
}, 2000);
} else {
updateRemovalProgress(100, '❌ Fehler beim Entfernen');
document.getElementById('removeDetails').innerHTML = `
<div class="alert alert-danger">
<strong>Fehler:</strong><br>
${data.message || data.error}
${data.message && data.message.includes('Zugriff verweigert') ? `
<hr>
<strong>Lösungsvorschläge:</strong>
<ul class="mb-0">
<li>Alle Git-Clients (VS Code, GitHub Desktop, etc.) schließen</li>
<li>Als Administrator ausführen</li>
<li>Antivirus temporär deaktivieren</li>
<li>Manuell löschen: <code>projects/${PROJECT_NAME}</code></li>
</ul>
` : ''}
</div>
`;
document.getElementById('removeModalFooter').style.display = 'block';
}
})
.catch(error => {
updateRemovalProgress(100, '❌ Netzwerkfehler');
document.getElementById('removeDetails').innerHTML = `
<div class="alert alert-danger">
<strong>Netzwerkfehler:</strong><br>
${error.message}
</div>
`;
document.getElementById('removeModalFooter').style.display = 'block';
});
}
function updateRemovalProgress(percent, status) {
document.getElementById('removeProgress').style.width = `${percent}%`;
document.getElementById('removeStatus').textContent = status;
}
function closeRemoveModal() {
const modal = bootstrap.Modal.getInstance(document.getElementById('removeProgressModal'));
modal.hide();
document.getElementById('removeProgressModal').remove();
}
</script>
{% endblock %}