1453 lines
58 KiB
HTML
1453 lines
58 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ project.name }} - Details{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Header mit Status-Badge -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<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' %}
|
|
<span class="text-success">Läuft</span>
|
|
{% elif project.status in ['exited', 'stopped'] %}
|
|
<span class="text-warning">Gestoppt</span>
|
|
{% else %}
|
|
<span class="text-muted">Unbekannt</span>
|
|
{% endif %}
|
|
</span>
|
|
</h2>
|
|
<p class="text-muted mb-0">
|
|
<i class="fas fa-folder me-1"></i>{{ project.path }}
|
|
</p>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
{% if project.status == 'running' %}
|
|
<a href="http://localhost:8080" target="_blank" class="btn btn-success">
|
|
<i class="fas fa-external-link-alt me-1"></i> App öffnen
|
|
</a>
|
|
{% endif %}
|
|
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-arrow-left"></i> Zurück
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Status Overview -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card text-center h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-center mb-2">
|
|
{% if project.has_dockerfile %}
|
|
<i class="fab fa-docker fa-2x text-primary"></i>
|
|
{% else %}
|
|
<i class="fas fa-times-circle fa-2x text-danger"></i>
|
|
{% endif %}
|
|
</div>
|
|
<h6 class="card-title">Docker</h6>
|
|
<p class="card-text small">
|
|
{% if project.has_dockerfile %}
|
|
<span class="badge bg-success">Verfügbar</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">Nicht verfügbar</span>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-center mb-2">
|
|
{% if project.has_env_example %}
|
|
<i class="fas fa-cog fa-2x text-success"></i>
|
|
{% else %}
|
|
<i class="fas fa-exclamation-triangle fa-2x text-warning"></i>
|
|
{% endif %}
|
|
</div>
|
|
<h6 class="card-title">Konfiguration</h6>
|
|
<p class="card-text small">
|
|
{% if project.has_env_example %}
|
|
<span class="badge bg-success">.env verfügbar</span>
|
|
{% else %}
|
|
<span class="badge bg-warning">Keine .env</span>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-center mb-2">
|
|
{% if project.has_docker_compose %}
|
|
<i class="fas fa-layer-group fa-2x text-info"></i>
|
|
{% else %}
|
|
<i class="fas fa-minus-circle fa-2x text-muted"></i>
|
|
{% endif %}
|
|
</div>
|
|
<h6 class="card-title">Compose</h6>
|
|
<p class="card-text small">
|
|
{% if project.has_docker_compose %}
|
|
<span class="badge bg-info">Verfügbar</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Nicht verfügbar</span>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-center mb-2">
|
|
{% if project.version %}
|
|
<i class="fas fa-tag fa-2x text-primary"></i>
|
|
{% else %}
|
|
<i class="fas fa-question-circle fa-2x text-muted"></i>
|
|
{% endif %}
|
|
</div>
|
|
<h6 class="card-title">Version</h6>
|
|
<p class="card-text small">
|
|
<span class="badge bg-primary">{{ project.version or 'Unbekannt' }}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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 g-3">
|
|
<div class="col-md-6">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-tag me-2 text-primary"></i>
|
|
<div>
|
|
<strong>Name</strong>
|
|
<div class="text-muted">{{ project.name }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-folder me-2 text-warning"></i>
|
|
<div>
|
|
<strong>Pfad</strong>
|
|
<div class="text-muted"><code>{{ project.path }}</code></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-calendar me-2 text-success"></i>
|
|
<div>
|
|
<strong>Installiert</strong>
|
|
<div class="text-muted">{{ project.created }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-code-branch me-2 text-info"></i>
|
|
<div>
|
|
<strong>Version</strong>
|
|
<div class="text-muted">{{ project.version or 'Unbekannt' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if project.readme %}
|
|
<hr>
|
|
<div class="mt-3">
|
|
<h6><i class="fas fa-book me-2"></i>Beschreibung</h6>
|
|
<div class="bg-light p-3 rounded">
|
|
<p class="mb-0">{{ project.readme[:300] }}{% if project.readme|length > 300 %}...{% endif %}</p>
|
|
</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">
|
|
<!-- Container-Steuerung -->
|
|
<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-play-circle me-2"></i>Container-Steuerung
|
|
</h5>
|
|
<span class="badge bg-{{ 'success' if project.status == 'running' else 'secondary' }}">
|
|
{{ 'Aktiv' if project.status == 'running' else 'Inaktiv' }}
|
|
</span>
|
|
</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 %}
|
|
<!-- Start-Modus Auswahl -->
|
|
<div class="mb-3">
|
|
<label for="startModeSelect" class="form-label small text-muted">
|
|
<i class="fas fa-cog me-1"></i>Start-Modus wählen:
|
|
</label>
|
|
<select class="form-select form-select-sm" id="startModeSelect" onchange="updateStartModeUI()">
|
|
{% if project.has_dockerfile %}
|
|
<option value="docker" selected>
|
|
<i class="fab fa-docker"></i> Docker Container (Standard)
|
|
</option>
|
|
{% endif %}
|
|
|
|
{% if project.has_start_bat %}
|
|
<option value="batch" {% if not project.has_dockerfile %}selected{% endif %}>
|
|
<i class="fas fa-terminal"></i> Windows Batch (.bat)
|
|
</option>
|
|
{% endif %}
|
|
|
|
{% if project.has_start_sh %}
|
|
<option value="shell" {% if not project.has_dockerfile and not project.has_start_bat %}selected{% endif %}>
|
|
<i class="fas fa-terminal"></i> Shell Script (.sh)
|
|
</option>
|
|
{% endif %}
|
|
|
|
{% if project.has_package_json %}
|
|
<option value="nodejs" {% if not project.has_dockerfile and not project.has_start_bat and not project.has_start_sh %}selected{% endif %}>
|
|
<i class="fab fa-node-js"></i> Node.js (npm start)
|
|
</option>
|
|
{% endif %}
|
|
|
|
{% if project.has_python_files %}
|
|
<option value="python">
|
|
<i class="fab fa-python"></i> Python (direkt)
|
|
</option>
|
|
{% endif %}
|
|
|
|
<option value="custom">
|
|
<i class="fas fa-wrench"></i> Custom (manuell)
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Docker Start Optionen -->
|
|
<div id="dockerStartOptions" class="start-mode-options">
|
|
<button class="btn btn-success btn-lg" id="quickStartButton" onclick="quickStartContainer()">
|
|
<i class="fab fa-docker me-2"></i>Docker Schnellstart
|
|
</button>
|
|
<button class="btn btn-outline-success" onclick="showPortSelection()">
|
|
<i class="fas fa-cog"></i> Port-Einstellungen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Native Start Optionen -->
|
|
<div id="nativeStartOptions" class="start-mode-options" style="display: none;">
|
|
<button class="btn btn-success btn-lg" onclick="startNativeMode()">
|
|
<i class="fas fa-play me-2"></i><span id="nativeStartText">Nativ starten</span>
|
|
</button>
|
|
<button class="btn btn-outline-info" onclick="showNativeOptions()">
|
|
<i class="fas fa-terminal"></i> Terminal öffnen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Custom Start Optionen -->
|
|
<div id="customStartOptions" class="start-mode-options" style="display: none;">
|
|
<div class="alert alert-info small mb-2">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
Manueller Start - Verwenden Sie das Terminal für eigene Befehle
|
|
</div>
|
|
<button class="btn btn-outline-primary" onclick="openProjectTerminal()">
|
|
<i class="fas fa-terminal"></i> Terminal im Projektordner öffnen
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<hr class="my-2">
|
|
|
|
<button class="btn btn-primary" onclick="window.location.href='{{ url_for('build_project', project_name=project.name) }}'">
|
|
<i class="fas fa-hammer"></i> Image neu bauen
|
|
</button>
|
|
|
|
<button class="btn btn-outline-danger" onclick="removeProjectSafely()">
|
|
<i class="fas fa-trash"></i> Projekt entfernen
|
|
</button>
|
|
</div>
|
|
|
|
{% if project.status == 'running' %}
|
|
<div class="mt-3 p-2 bg-light rounded">
|
|
<small class="text-muted">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
Container läuft seit: <strong>{{ project.created }}</strong>
|
|
</small>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Port-Status und Verbindungen -->
|
|
{% 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 p-2 bg-light rounded mb-2">
|
|
<div>
|
|
<strong>HTTP</strong>
|
|
<small class="text-muted d-block">Haupt-Webanwendung</small>
|
|
</div>
|
|
<div>
|
|
<a href="http://localhost:8080" target="_blank" class="btn btn-primary btn-sm">
|
|
<i class="fas fa-external-link-alt me-1"></i>:8080
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center p-2 bg-light rounded">
|
|
<div>
|
|
<strong>HTTPS</strong>
|
|
<small class="text-muted d-block">Sichere Verbindung</small>
|
|
</div>
|
|
<div>
|
|
<a href="https://localhost:8443" target="_blank" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-lock me-1"></i>:8443
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3 p-2 bg-success bg-opacity-10 rounded">
|
|
<small class="text-success">
|
|
<i class="fas fa-check-circle me-1"></i>
|
|
Alle Verbindungen sind aktiv und erreichbar
|
|
</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 styles %}
|
|
<style>
|
|
.status-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 0.5rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-running {
|
|
background-color: rgba(25, 135, 84, 0.1);
|
|
color: #198754;
|
|
border: 1px solid rgba(25, 135, 84, 0.2);
|
|
}
|
|
|
|
.status-stopped {
|
|
background-color: rgba(255, 193, 7, 0.1);
|
|
color: #ffc107;
|
|
border: 1px solid rgba(255, 193, 7, 0.2);
|
|
}
|
|
|
|
.status-unknown {
|
|
background-color: rgba(108, 117, 125, 0.1);
|
|
color: #6c757d;
|
|
border: 1px solid rgba(108, 117, 125, 0.2);
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
transition: transform 0.2s ease-in-out;
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.btn-lg {
|
|
font-size: 1.1rem;
|
|
padding: 0.75rem 1.5rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.bg-opacity-10 {
|
|
--bs-bg-opacity: 0.1;
|
|
}
|
|
|
|
/* Toast Container */
|
|
#toastContainer {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 1080;
|
|
}
|
|
|
|
/* Responsive Cards */
|
|
@media (max-width: 768px) {
|
|
.card .row .col-md-6 {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.d-flex.gap-2 {
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
}
|
|
|
|
.start-mode-options {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.start-mode-options.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
#startModeSelect {
|
|
border-color: #dee2e6;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
#startModeSelect:focus {
|
|
border-color: #0d6efd;
|
|
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
|
}
|
|
</style>
|
|
{% 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();
|
|
|
|
// Initialisiere Start-Modus basierend auf Installation
|
|
detectAndSetStartMode();
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Start-Modus Management
|
|
function updateStartModeUI() {
|
|
const selectedMode = document.getElementById('startModeSelect').value;
|
|
|
|
// Verstecke alle Start-Optionen
|
|
document.querySelectorAll('.start-mode-options').forEach(el => {
|
|
el.style.display = 'none';
|
|
});
|
|
|
|
// Zeige die ausgewählte Option
|
|
switch(selectedMode) {
|
|
case 'docker':
|
|
document.getElementById('dockerStartOptions').style.display = 'block';
|
|
break;
|
|
case 'batch':
|
|
case 'shell':
|
|
case 'nodejs':
|
|
case 'python':
|
|
document.getElementById('nativeStartOptions').style.display = 'block';
|
|
updateNativeStartText(selectedMode);
|
|
break;
|
|
case 'custom':
|
|
document.getElementById('customStartOptions').style.display = 'block';
|
|
break;
|
|
}
|
|
}
|
|
|
|
function updateNativeStartText(mode) {
|
|
const textElement = document.getElementById('nativeStartText');
|
|
const modeTexts = {
|
|
'batch': 'Windows Batch starten',
|
|
'shell': 'Shell Script starten',
|
|
'nodejs': 'Node.js App starten',
|
|
'python': 'Python App starten'
|
|
};
|
|
textElement.textContent = modeTexts[mode] || 'Nativ starten';
|
|
}
|
|
|
|
function startNativeMode() {
|
|
const selectedMode = document.getElementById('startModeSelect').value;
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Startet...';
|
|
button.disabled = true;
|
|
|
|
fetch(`/api/start_native/${PROJECT_NAME}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ mode: selectedMode })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showSuccessMessage(`${PROJECT_NAME} wurde nativ gestartet!`);
|
|
setTimeout(() => location.reload(), 2000);
|
|
} else {
|
|
showErrorMessage(data.error || 'Fehler beim nativen Start');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showErrorMessage(`Netzwerkfehler: ${error.message}`);
|
|
})
|
|
.finally(() => {
|
|
button.innerHTML = originalText;
|
|
button.disabled = false;
|
|
});
|
|
}
|
|
|
|
function showNativeOptions() {
|
|
const selectedMode = document.getElementById('startModeSelect').value;
|
|
|
|
const modal = `
|
|
<div class="modal fade" id="nativeOptionsModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-terminal me-2"></i>Native Start-Optionen
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Start-Modus:</label>
|
|
<span class="badge bg-primary">${selectedMode}</span>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="nativeWorkDir" class="form-label">Arbeitsverzeichnis:</label>
|
|
<input type="text" class="form-control" id="nativeWorkDir"
|
|
value="${PROJECT_NAME}" readonly>
|
|
</div>
|
|
|
|
${selectedMode === 'nodejs' ? `
|
|
<div class="mb-3">
|
|
<label for="nodeCommand" class="form-label">Node.js Befehl:</label>
|
|
<select class="form-select" id="nodeCommand">
|
|
<option value="npm start">npm start</option>
|
|
<option value="npm run dev">npm run dev</option>
|
|
<option value="node index.js">node index.js</option>
|
|
<option value="node app.js">node app.js</option>
|
|
<option value="node server.js">node server.js</option>
|
|
</select>
|
|
</div>
|
|
` : ''}
|
|
|
|
${selectedMode === 'python' ? `
|
|
<div class="mb-3">
|
|
<label for="pythonCommand" class="form-label">Python Befehl:</label>
|
|
<select class="form-select" id="pythonCommand">
|
|
<option value="python app.py">python app.py</option>
|
|
<option value="python main.py">python main.py</option>
|
|
<option value="python server.py">python server.py</option>
|
|
<option value="flask run">flask run</option>
|
|
<option value="python -m flask run">python -m flask run</option>
|
|
</select>
|
|
</div>
|
|
` : ''}
|
|
|
|
<div class="alert alert-info small">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
Der Prozess wird im Hintergrund gestartet. Logs können im Terminal eingesehen werden.
|
|
</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="startWithNativeOptions()">
|
|
<i class="fas fa-play me-1"></i>Starten
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Entferne existierendes Modal
|
|
const existingModal = document.getElementById('nativeOptionsModal');
|
|
if (existingModal) existingModal.remove();
|
|
|
|
// Füge neues Modal hinzu
|
|
document.body.insertAdjacentHTML('beforeend', modal);
|
|
|
|
// Modal anzeigen
|
|
const modalElement = new bootstrap.Modal(document.getElementById('nativeOptionsModal'));
|
|
modalElement.show();
|
|
}
|
|
|
|
function startWithNativeOptions() {
|
|
const selectedMode = document.getElementById('startModeSelect').value;
|
|
let command = '';
|
|
|
|
switch(selectedMode) {
|
|
case 'nodejs':
|
|
command = document.getElementById('nodeCommand').value;
|
|
break;
|
|
case 'python':
|
|
command = document.getElementById('pythonCommand').value;
|
|
break;
|
|
case 'batch':
|
|
command = 'start.bat';
|
|
break;
|
|
case 'shell':
|
|
command = './start.sh';
|
|
break;
|
|
}
|
|
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('nativeOptionsModal'));
|
|
modal.hide();
|
|
|
|
// Starte mit spezifischem Befehl
|
|
fetch(`/api/start_native/${PROJECT_NAME}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
mode: selectedMode,
|
|
command: command
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showSuccessMessage(`${PROJECT_NAME} wurde gestartet: ${command}`);
|
|
setTimeout(() => location.reload(), 2000);
|
|
} else {
|
|
showErrorMessage(data.error || 'Fehler beim Start');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showErrorMessage(`Netzwerkfehler: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
function openProjectTerminal() {
|
|
fetch(`/api/open_terminal/${PROJECT_NAME}`, {
|
|
method: 'POST'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showSuccessMessage('Terminal wurde im Projektordner geöffnet');
|
|
} else {
|
|
showErrorMessage(data.error || 'Fehler beim Öffnen des Terminals');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showErrorMessage(`Netzwerkfehler: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
// Automatische Start-Modus Erkennung basierend auf Installation
|
|
function detectAndSetStartMode() {
|
|
const installMethod = '{{ project.install_method or "unknown" }}';
|
|
const hasDockerfile = {{ 'true' if project.has_dockerfile else 'false' }};
|
|
const hasBat = {{ 'true' if project.has_start_bat else 'false' }};
|
|
const hasShell = {{ 'true' if project.has_start_sh else 'false' }};
|
|
const hasNodejs = {{ 'true' if project.has_package_json else 'false' }};
|
|
|
|
let recommendedMode = 'docker'; // Standard
|
|
|
|
// Intelligente Auswahl basierend auf Installation
|
|
if (installMethod.includes('native_batch') || installMethod === 'batch') {
|
|
recommendedMode = 'batch';
|
|
} else if (installMethod.includes('native_shell') || installMethod === 'shell') {
|
|
recommendedMode = 'shell';
|
|
} else if (installMethod.includes('native_nodejs') || installMethod === 'nodejs') {
|
|
recommendedMode = 'nodejs';
|
|
} else if (installMethod.includes('native_python') || installMethod === 'python') {
|
|
recommendedMode = 'python';
|
|
} else if (installMethod === 'clone') {
|
|
// Bei Git Clone: Prüfe verfügbare Optionen
|
|
if (hasBat) recommendedMode = 'batch';
|
|
else if (hasShell) recommendedMode = 'shell';
|
|
else if (hasNodejs) recommendedMode = 'nodejs';
|
|
else if (!hasDockerfile) recommendedMode = 'custom';
|
|
}
|
|
|
|
// Setze empfohlenen Modus
|
|
const select = document.getElementById('startModeSelect');
|
|
if (select) {
|
|
const option = select.querySelector(`option[value="${recommendedMode}"]`);
|
|
if (option) {
|
|
select.value = recommendedMode;
|
|
updateStartModeUI();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ...existing code...
|
|
</script>
|
|
{% endblock %}
|