deleted: templates/project_details_fixed.html
deleted: templates/project_details_new.html deleted: templates/project_details_old.html
This commit is contained in:
@@ -1,696 +0,0 @@
|
||||
{% 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>
|
||||
|
||||
<a href="{{ url_for('remove_project', project_name=project.name) }}"
|
||||
class="btn btn-danger"
|
||||
onclick="return confirmAction('remove', '{{ project.name }}')">
|
||||
<i class="fas fa-trash"></i> Projekt entfernen
|
||||
</a>
|
||||
</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>
|
||||
</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'}`);
|
||||
|
||||
// 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);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,696 +0,0 @@
|
||||
{% 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="10"
|
||||
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">
|
||||
{% if project.status == 'running' %}
|
||||
Logs werden geladen...
|
||||
{% else %}
|
||||
Container ist nicht gestartet. Keine Logs verfügbar.
|
||||
{% endif %}
|
||||
</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-Verwaltung
|
||||
</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>
|
||||
<button class="btn btn-outline-info" onclick="viewContainerDetails()">
|
||||
<i class="fas fa-eye"></i> Container-Details
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-success" onclick="quickStartContainer()" id="quickStartButton">
|
||||
<i class="fas fa-play"></i> Schnellstart (Auto-Port)
|
||||
</button>
|
||||
<button class="btn btn-outline-success" onclick="showPortSelection()" id="customStartButton">
|
||||
<i class="fas fa-cog"></i> Erweiterte Startoptionen
|
||||
</button>
|
||||
{% if not project.has_dockerfile %}
|
||||
<div class="alert alert-warning mt-2">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Kein Dockerfile gefunden! Build ist erforderlich.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
|
||||
<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-outline-info" onclick="openFileExplorer()">
|
||||
<i class="fas fa-folder-open"></i> Dateien öffnen
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-warning" onclick="exportProject()">
|
||||
<i class="fas fa-download"></i> Projekt exportieren
|
||||
</button>
|
||||
|
||||
<a href="{{ url_for('remove_project', project_name=project.name) }}"
|
||||
class="btn btn-danger"
|
||||
onclick="return confirm('Möchten Sie das Projekt wirklich vollständig entfernen?')">
|
||||
<i class="fas fa-trash"></i> Projekt entfernen
|
||||
</a>
|
||||
</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>Hauptzugriff:</span>
|
||||
<a href="http://localhost:8080" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||
<i class="fas fa-external-link-alt"></i> :8080
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Der tatsächliche Port kann variieren je nach Konfiguration.
|
||||
</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>
|
||||
</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}`);
|
||||
|
||||
// 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}`);
|
||||
})
|
||||
.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 => {
|
||||
if (data.success) {
|
||||
// Modal schließen
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('portSelectionModal'));
|
||||
modal.hide();
|
||||
|
||||
showSuccessMessage(`Container erfolgreich gestartet auf Port ${data.port}!`);
|
||||
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
showErrorMessage(`Fehler beim Start: ${data.error}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showErrorMessage(`Netzwerkfehler: ${error.message}`);
|
||||
})
|
||||
.finally(() => {
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 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 && data.stats) {
|
||||
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));
|
||||
}
|
||||
|
||||
// Andere Funktionen (vereinfacht)
|
||||
function resetToExample() {
|
||||
showToast('Diese Funktion wird implementiert', 'info');
|
||||
}
|
||||
|
||||
function validateEnv() {
|
||||
showToast('Validierung wird implementiert', 'info');
|
||||
}
|
||||
|
||||
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>
|
||||
`);
|
||||
}
|
||||
|
||||
function openFileExplorer() {
|
||||
showToast('File Explorer-Integration wird in einer zukünftigen Version verfügbar sein.', 'info');
|
||||
}
|
||||
|
||||
function exportProject() {
|
||||
if (confirm('Projekt als Archiv exportieren?')) {
|
||||
showToast('Export-Funktion wird implementiert', 'info');
|
||||
}
|
||||
}
|
||||
|
||||
function viewContainerDetails() {
|
||||
showToast('Container-Details werden implementiert', 'info');
|
||||
}
|
||||
|
||||
// Initialisierung beim Laden der Seite
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log(`Project Details loaded for: ${PROJECT_NAME}`);
|
||||
|
||||
// Lade Logs wenn Container läuft
|
||||
const status = '{{ project.status }}';
|
||||
if (status === 'running') {
|
||||
refreshLogs();
|
||||
updateMonitoring();
|
||||
|
||||
// Auto-refresh alle 30 Sekunden
|
||||
setInterval(() => {
|
||||
refreshLogs();
|
||||
updateMonitoring();
|
||||
}, 30000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,709 +0,0 @@
|
||||
{% 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>Schnellaktionen
|
||||
</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 %}
|
||||
<div class="btn-group w-100 mb-2" role="group">
|
||||
<button class="btn btn-success" onclick="startWithPort(8080)" id="quickStartButton">
|
||||
<i class="fas fa-play"></i> Schnellstart (Port 8080)
|
||||
</button>
|
||||
<button class="btn btn-outline-success" onclick="showPortSelection()" id="customStartButton">
|
||||
<i class="fas fa-cog"></i> Erweitert
|
||||
</button>
|
||||
</div>
|
||||
<div class="alert alert-info alert-sm p-2">
|
||||
<small><i class="fas fa-info-circle me-1"></i>Port wird automatisch geprüft und angepasst falls belegt</small>
|
||||
</div>
|
||||
{% 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-outline-info" onclick="openFileExplorer()">
|
||||
<i class="fas fa-folder-open"></i> Dateien öffnen
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-warning" onclick="exportProject()">
|
||||
<i class="fas fa-download"></i> Projekt exportieren
|
||||
</button>
|
||||
|
||||
<a href="{{ url_for('remove_project', project_name=project.name) }}"
|
||||
class="btn btn-danger"
|
||||
onclick="return confirmAction('remove', '{{ project.name }}')">
|
||||
<i class="fas fa-trash"></i> Projekt entfernen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Port Konfiguration -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-network-wired me-2"></i>Port Konfiguration
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="httpPort" class="form-label">Gewünschter Port:</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="httpPort" value="8080" min="1" max="65535">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="checkPort()">
|
||||
<i class="fas fa-search"></i> Prüfen
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
<span id="portStatus" class="text-muted">Port-Status wird hier angezeigt</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Häufige Ports:</label>
|
||||
<div class="btn-group-vertical w-100" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="setPort(8080)">8080 - Standard Web</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="setPort(3000)">3000 - Node.js</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="setPort(5000)">5000 - Flask</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="findFreePort()">🔍 Freien Port finden</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="autoPort">
|
||||
<label class="form-check-label" for="autoPort">
|
||||
Automatische Portzuweisung
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if project.status == 'running' %}
|
||||
<div class="mt-3">
|
||||
<h6>Aktive Ports:</h6>
|
||||
<div id="activePorts" class="small">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>HTTP:</span>
|
||||
<a href="http://localhost:8080" target="_blank" class="text-decoration-none">
|
||||
:8080 <i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup & Restore -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-shield-alt me-2"></i>Backup & Wiederherstellung
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-success btn-sm" onclick="createBackup()">
|
||||
<i class="fas fa-save"></i> Backup erstellen
|
||||
</button>
|
||||
<button class="btn btn-outline-info btn-sm" onclick="document.getElementById('restoreFile').click()">
|
||||
<i class="fas fa-upload"></i> Backup wiederherstellen
|
||||
</button>
|
||||
<input type="file" id="restoreFile" style="display: none" accept=".tar.gz,.zip" onchange="restoreBackup(event)">
|
||||
<button class="btn btn-outline-warning btn-sm" onclick="showBackupHistory()">
|
||||
<i class="fas fa-history"></i> Backup Historie
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monitoring -->
|
||||
<div class="card">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Port 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">Container starten</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="startForm">
|
||||
<div class="mb-3">
|
||||
<label for="startPort" class="form-label">Port (Standard: 8080)</label>
|
||||
<input type="number" class="form-control" id="startPort" value="8080" min="1" max="65535">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="detachedMode" checked>
|
||||
<label class="form-check-label" for="detachedMode">
|
||||
Im Hintergrund ausführen
|
||||
</label>
|
||||
</div>
|
||||
</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-success" onclick="startWithCustomPort()">
|
||||
<i class="fas fa-play"></i> Starten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Container starten - Verbesserte Version mit Fehlerbehandlung
|
||||
function startWithPort(port) {
|
||||
const button = event?.target;
|
||||
if (button) {
|
||||
const originalText = button.innerHTML;
|
||||
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Startet...';
|
||||
button.disabled = true;
|
||||
|
||||
// Führe den Start-Request direkt aus
|
||||
fetch(`/start_project/{{ project.name }}?port=${port}`)
|
||||
.then(response => {
|
||||
if (response.redirected) {
|
||||
// Flask hat redirect gemacht, folge dem
|
||||
window.location.href = response.url;
|
||||
} else {
|
||||
// Erwarte JSON-Response
|
||||
return response.json().catch(() => {
|
||||
// Falls kein JSON, behandle als Erfolg und lade Seite neu
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
if (data && !data.success) {
|
||||
// Zeige spezifische Fehlermeldung
|
||||
if (data.message.includes('bereits belegt')) {
|
||||
const portMatch = data.message.match(/Port (\d+)/);
|
||||
if (portMatch) {
|
||||
const altPort = data.message.match(/(\d+)\./);
|
||||
if (altPort && confirm(`Port ${portMatch[1]} ist belegt. Möchten Sie Port ${altPort[1]} verwenden?`)) {
|
||||
startWithPort(altPort[1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
alert('Fehler beim Starten: ' + data.message);
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
} else {
|
||||
// Erfolg - lade Seite neu nach kurzer Verzögerung
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Start-Fehler:', error);
|
||||
// Bei Fehlern einfach zur ursprünglichen URL navigieren
|
||||
window.location.href = `/start_project/{{ project.name }}?port=${port}`;
|
||||
});
|
||||
} else {
|
||||
// Fallback für direkte Aufrufe
|
||||
window.location.href = `/start_project/{{ project.name }}?port=${port}`;
|
||||
}
|
||||
}
|
||||
|
||||
function showPortSelection() {
|
||||
// Verwende den konfigurierten Port aus dem Port-Konfigurations-Panel
|
||||
const configuredPort = document.getElementById('httpPort').value;
|
||||
if (configuredPort && !isNaN(configuredPort) && configuredPort > 0 && configuredPort <= 65535) {
|
||||
startWithPort(configuredPort);
|
||||
} else {
|
||||
// Fallback auf Port-Eingabe
|
||||
let port;
|
||||
do {
|
||||
port = prompt('Welchen Port möchten Sie verwenden?\n\nHäufig verwendete Ports:\n8080 - Standard Web\n3000 - Node.js Apps\n5000 - Flask Apps\n8081-8090 - Alternative Ports', '8080');
|
||||
|
||||
if (port === null) return; // Benutzer hat abgebrochen
|
||||
|
||||
port = parseInt(port);
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
alert('Bitte geben Sie einen gültigen Port zwischen 1 und 65535 ein.');
|
||||
port = null; // Damit die Schleife weitergeht
|
||||
} else if (port < 1024) {
|
||||
if (!confirm(`Port ${port} ist ein System-Port. Dies könnte Probleme verursachen. Trotzdem verwenden?`)) {
|
||||
port = null;
|
||||
}
|
||||
}
|
||||
} while (port === null);
|
||||
|
||||
startWithPort(port);
|
||||
}
|
||||
}
|
||||
|
||||
function checkPortAvailability(port) {
|
||||
return fetch(`/api/check_port/${port}`)
|
||||
.then(response => response.json())
|
||||
.then(data => data.available)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
function showPortModal() {
|
||||
// Fallback auf Port-Auswahl falls Modal nicht funktioniert
|
||||
showPortSelection();
|
||||
}
|
||||
|
||||
function startWithCustomPort() {
|
||||
// Nicht mehr verwendet - für Rückwärtskompatibilität
|
||||
const port = document.getElementById('startPort')?.value || 8080;
|
||||
startWithPort(port);
|
||||
}
|
||||
|
||||
// Container neustarten
|
||||
function restartContainer() {
|
||||
if (confirm('Container wirklich neustarten?')) {
|
||||
fetch(`/api/restart_project/{{ project.name }}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Fehler beim Neustart: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => alert('Netzwerkfehler: ' + 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;
|
||||
} else {
|
||||
alert('Fehler: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => alert('Netzwerkfehler: ' + error));
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
alert('✅ .env Konfiguration ist gültig!');
|
||||
} else {
|
||||
alert('❌ .env Konfiguration hat Probleme:\n' + data.errors.join('\n'));
|
||||
}
|
||||
})
|
||||
.catch(error => alert('Fehler bei der Validierung: ' + error));
|
||||
}
|
||||
|
||||
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>
|
||||
`);
|
||||
}
|
||||
|
||||
// Docker Logs
|
||||
function refreshLogs() {
|
||||
const logsContainer = document.getElementById('dockerLogs');
|
||||
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) {
|
||||
logsContainer.innerHTML = `<pre class="mb-0">${data.logs || 'Keine Logs verfügbar'}</pre>`;
|
||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
||||
} else {
|
||||
logsContainer.innerHTML = `<div class="text-danger">Fehler beim Laden der Logs: ${data.error}</div>`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logsContainer.innerHTML = `<div class="text-danger">Netzwerkfehler: ${error}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
// File Explorer
|
||||
function openFileExplorer() {
|
||||
// In einer realen Implementierung würde hier ein File Manager geöffnet
|
||||
alert('File Explorer-Integration wird in einer zukünftigen Version verfügbar sein.');
|
||||
}
|
||||
|
||||
// Projekt Export
|
||||
function exportProject() {
|
||||
if (confirm('Projekt als Archiv exportieren?')) {
|
||||
window.location.href = `/api/export_project/{{ project.name }}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Backup Funktionen
|
||||
function createBackup() {
|
||||
if (confirm('Backup des Projekts erstellen?')) {
|
||||
fetch(`/api/create_backup/{{ project.name }}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Backup erfolgreich erstellt: ' + data.filename);
|
||||
} else {
|
||||
alert('❌ Backup-Fehler: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => alert('Netzwerkfehler: ' + error));
|
||||
}
|
||||
}
|
||||
|
||||
function restoreBackup(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (confirm('Projekt aus Backup wiederherstellen? Aktuelle Daten werden überschrieben!')) {
|
||||
const formData = new FormData();
|
||||
formData.append('backup_file', file);
|
||||
|
||||
fetch(`/api/restore_backup/{{ project.name }}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Backup erfolgreich wiederhergestellt!');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('❌ Wiederherstellungs-Fehler: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => alert('Netzwerkfehler: ' + error));
|
||||
}
|
||||
}
|
||||
|
||||
function showBackupHistory() {
|
||||
fetch(`/api/backup_history/{{ project.name }}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let historyHtml = '<h5>Backup Historie:</h5><ul>';
|
||||
if (data.backups.length > 0) {
|
||||
data.backups.forEach(backup => {
|
||||
historyHtml += `<li>${backup.name} (${backup.date}) - ${backup.size}</li>`;
|
||||
});
|
||||
} else {
|
||||
historyHtml += '<li>Keine Backups vorhanden</li>';
|
||||
}
|
||||
historyHtml += '</ul>';
|
||||
|
||||
const popup = window.open('', '_blank', 'width=500,height=400');
|
||||
popup.document.write(`
|
||||
<html>
|
||||
<head><title>Backup Historie</title></head>
|
||||
<body style="padding: 20px; font-family: Arial, sans-serif;">
|
||||
${historyHtml}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
})
|
||||
.catch(error => alert('Fehler beim Laden der Historie: ' + error));
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// Port-Management Funktionen
|
||||
function setPort(port) {
|
||||
document.getElementById('httpPort').value = port;
|
||||
checkPort();
|
||||
}
|
||||
|
||||
function checkPort() {
|
||||
const port = document.getElementById('httpPort').value;
|
||||
const statusElement = document.getElementById('portStatus');
|
||||
|
||||
if (!port || port < 1 || port > 65535) {
|
||||
statusElement.innerHTML = '<span class="text-danger">Ungültiger Port</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
statusElement.innerHTML = '<span class="text-info"><i class="fas fa-spinner fa-spin"></i> Prüfe...</span>';
|
||||
|
||||
fetch(`/api/check_port/${port}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.available) {
|
||||
statusElement.innerHTML = `<span class="text-success"><i class="fas fa-check"></i> Port ${port} ist verfügbar</span>`;
|
||||
} else {
|
||||
statusElement.innerHTML = `<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Port ${port} ist belegt</span>`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
statusElement.innerHTML = '<span class="text-danger"><i class="fas fa-times"></i> Fehler bei Port-Prüfung</span>';
|
||||
});
|
||||
}
|
||||
|
||||
function findFreePort() {
|
||||
const statusElement = document.getElementById('portStatus');
|
||||
statusElement.innerHTML = '<span class="text-info"><i class="fas fa-spinner fa-spin"></i> Suche freien Port...</span>';
|
||||
|
||||
fetch('/api/find_available_port?start=8080')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('httpPort').value = data.port;
|
||||
statusElement.innerHTML = `<span class="text-success"><i class="fas fa-check"></i> Freier Port gefunden: ${data.port}</span>`;
|
||||
} else {
|
||||
statusElement.innerHTML = '<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Kein freier Port gefunden</span>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
statusElement.innerHTML = '<span class="text-danger"><i class="fas fa-times"></i> Fehler bei Port-Suche</span>';
|
||||
});
|
||||
}
|
||||
|
||||
// Beim Laden der Seite
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Project Details Page loaded');
|
||||
|
||||
// Prüfe ob alle wichtigen Elemente vorhanden sind
|
||||
const quickStartButton = document.getElementById('quickStartButton');
|
||||
const customStartButton = document.getElementById('customStartButton');
|
||||
|
||||
if (quickStartButton) {
|
||||
console.log('Quick Start Button gefunden');
|
||||
}
|
||||
if (customStartButton) {
|
||||
console.log('Custom Start Button gefunden');
|
||||
}
|
||||
|
||||
refreshLogs();
|
||||
updateMonitoring();
|
||||
|
||||
// Auto-Update alle 10 Sekunden
|
||||
setInterval(() => {
|
||||
updateMonitoring();
|
||||
if (document.getElementById('autoRefreshLogs')?.checked) {
|
||||
refreshLogs();
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user