modified: app.py
modified: config.json modified: templates/project_details.html
This commit is contained in:
205
app.py
205
app.py
@@ -377,11 +377,22 @@ class ProjectManager:
|
||||
'has_dockerfile': os.path.exists(os.path.join(project_path, 'Dockerfile')),
|
||||
'has_env_example': os.path.exists(os.path.join(project_path, '.env.example')),
|
||||
'has_docker_compose': os.path.exists(os.path.join(project_path, 'docker-compose.yml')),
|
||||
'has_start_bat': os.path.exists(os.path.join(project_path, 'start.bat')),
|
||||
'has_start_sh': os.path.exists(os.path.join(project_path, 'start.sh')),
|
||||
'has_package_json': os.path.exists(os.path.join(project_path, 'package.json')),
|
||||
'has_python_files': any(os.path.exists(os.path.join(project_path, f)) for f in ['app.py', 'main.py', 'server.py']),
|
||||
'status': self.get_container_status(project_name),
|
||||
'created': datetime.fromtimestamp(os.path.getctime(project_path)).strftime('%Y-%m-%d %H:%M'),
|
||||
'version': self.get_installed_version(project_name)
|
||||
}
|
||||
|
||||
# Bestimme die Installationsmethode aus der Konfiguration
|
||||
config = self.load_config()
|
||||
for project in config.get('projects', []):
|
||||
if project.get('name') == project_name:
|
||||
info['install_method'] = project.get('install_method', 'unknown')
|
||||
break
|
||||
|
||||
# Lese README falls vorhanden
|
||||
readme_files = ['README.md', 'readme.md', 'README.txt', 'readme.txt']
|
||||
for readme in readme_files:
|
||||
@@ -936,7 +947,7 @@ class ProjectManager:
|
||||
if alternative_port:
|
||||
return False, f"Port {blocked_port} ist bereits belegt. Alternativer freier Port: {alternative_port}"
|
||||
else:
|
||||
return False, f"Port {blocked_port} ist bereits belegt und keine Alternative gefunden."
|
||||
return False, f"Port {blocked_blocked} ist bereits belegt und keine Alternative gefunden."
|
||||
else:
|
||||
return False, f"Port-Konflikt: {error_msg}"
|
||||
else:
|
||||
@@ -1895,7 +1906,7 @@ def api_docker_diagnose():
|
||||
'containers': []
|
||||
}
|
||||
else:
|
||||
results['checks']['containers'] = {
|
||||
results['checks']['containers'] = {
|
||||
'success': False,
|
||||
'output': 'Docker nicht verfügbar',
|
||||
'details': 'Docker Client nicht initialisiert',
|
||||
@@ -2029,137 +2040,89 @@ def api_start_project(project_name):
|
||||
'error': f'Unerwarteter Fehler: {str(e)}'
|
||||
})
|
||||
|
||||
@app.route('/api/project_status/<project_name>')
|
||||
def api_project_status(project_name):
|
||||
"""Hole aktuellen Projektstatus"""
|
||||
try:
|
||||
info = project_manager.get_project_info(project_name)
|
||||
if info:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'status': info['status'],
|
||||
'name': info['name'],
|
||||
'has_dockerfile': info['has_dockerfile'],
|
||||
'has_env_example': info['has_env_example']
|
||||
})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': 'Projekt nicht gefunden'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
|
||||
@app.route('/api/analyze_ports/<project_name>')
|
||||
def api_analyze_ports(project_name):
|
||||
"""Analysiere Port-Konfiguration eines Projekts"""
|
||||
@app.route('/api/start_native/<project_name>', methods=['POST'])
|
||||
def api_start_native(project_name):
|
||||
"""Starte Projekt nativ (ohne Docker)"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
mode = data.get('mode', 'custom')
|
||||
command = data.get('command', '')
|
||||
|
||||
project_path = os.path.join(PROJECTS_DIR, project_name)
|
||||
if not os.path.exists(project_path):
|
||||
return jsonify({'success': False, 'error': 'Projekt nicht gefunden'})
|
||||
|
||||
result = {
|
||||
'project_name': project_name,
|
||||
'dockerfile_ports': [],
|
||||
'app_file_ports': [],
|
||||
'image_ports': [],
|
||||
'recommended_mapping': None,
|
||||
'analysis': []
|
||||
}
|
||||
|
||||
# Analysiere Dockerfile
|
||||
if os.path.exists(os.path.join(project_path, 'Dockerfile')):
|
||||
dockerfile_ports = project_manager.analyze_dockerfile_ports(project_path)
|
||||
result['dockerfile_ports'] = dockerfile_ports
|
||||
result['analysis'].append(f"Dockerfile definiert Ports: {', '.join(dockerfile_ports) if dockerfile_ports else 'keine'}")
|
||||
else:
|
||||
result['analysis'].append("Kein Dockerfile gefunden")
|
||||
|
||||
# Analysiere App-Dateien
|
||||
app_ports = project_manager.analyze_app_files_for_ports(project_path)
|
||||
result['app_file_ports'] = app_ports
|
||||
result['analysis'].append(f"App-Dateien verwenden Ports: {', '.join(app_ports) if app_ports else 'keine gefunden'}")
|
||||
|
||||
# Analysiere Image (falls vorhanden)
|
||||
if project_manager.docker_available:
|
||||
try:
|
||||
image_ports = project_manager.detect_container_exposed_ports(project_name)
|
||||
result['image_ports'] = image_ports
|
||||
result['analysis'].append(f"Image exponiert Ports: {', '.join(image_ports) if image_ports else 'keine'}")
|
||||
# Bestimme den Befehl basierend auf dem Modus
|
||||
if mode == 'batch':
|
||||
if os.path.exists(os.path.join(project_path, 'start.bat')):
|
||||
command = 'start.bat'
|
||||
else:
|
||||
return jsonify({'success': False, 'error': 'start.bat nicht gefunden'})
|
||||
|
||||
# Empfohlenes Mapping
|
||||
if image_ports:
|
||||
recommended_port = 8080
|
||||
mapping = project_manager.create_smart_port_mapping(project_name, recommended_port)
|
||||
result['recommended_mapping'] = mapping
|
||||
result['analysis'].append(f"Empfohlenes Port-Mapping: {mapping}")
|
||||
elif mode == 'shell':
|
||||
if os.path.exists(os.path.join(project_path, 'start.sh')):
|
||||
command = './start.sh'
|
||||
else:
|
||||
return jsonify({'success': False, 'error': 'start.sh nicht gefunden'})
|
||||
|
||||
except Exception as e:
|
||||
result['analysis'].append(f"Image-Analyse fehlgeschlagen: {str(e)}")
|
||||
else:
|
||||
result['analysis'].append("Docker nicht verfügbar für Image-Analyse")
|
||||
elif mode == 'nodejs':
|
||||
if not command:
|
||||
command = 'npm start'
|
||||
if not os.path.exists(os.path.join(project_path, 'package.json')):
|
||||
return jsonify({'success': False, 'error': 'package.json nicht gefunden'})
|
||||
|
||||
elif mode == 'python':
|
||||
if not command:
|
||||
# Suche nach Python-Dateien
|
||||
for py_file in ['app.py', 'main.py', 'server.py']:
|
||||
if os.path.exists(os.path.join(project_path, py_file)):
|
||||
command = f'python {py_file}'
|
||||
break
|
||||
if not command:
|
||||
command = 'python app.py'
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'port_analysis': result
|
||||
})
|
||||
if not command:
|
||||
return jsonify({'success': False, 'error': 'Kein Start-Befehl definiert'})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
@app.route('/api/remove_project/<project_name>', methods=['POST'])
|
||||
def api_remove_project(project_name):
|
||||
"""API Endpoint zum Entfernen eines Projekts mit detailliertem Feedback"""
|
||||
try:
|
||||
success, message = project_manager.remove_project(project_name)
|
||||
|
||||
return jsonify({
|
||||
'success': success,
|
||||
'message': message,
|
||||
'project_name': project_name
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Unerwarteter Fehler beim Entfernen: {str(e)}',
|
||||
'project_name': project_name
|
||||
})
|
||||
|
||||
@app.route('/api/docker_reconnect', methods=['POST'])
|
||||
def api_docker_reconnect():
|
||||
"""Versuche Docker-Verbindung wiederherzustellen"""
|
||||
try:
|
||||
success = project_manager.reconnect_docker()
|
||||
|
||||
if success:
|
||||
# Starte Prozess im Hintergrund
|
||||
try:
|
||||
subprocess.Popen(
|
||||
command.split(),
|
||||
cwd=project_path,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=True if os.name == 'nt' else False
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Docker-Verbindung erfolgreich wiederhergestellt',
|
||||
'docker_available': True
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Docker ist nicht verfügbar. Stellen Sie sicher, dass Docker Desktop läuft.',
|
||||
'docker_available': False,
|
||||
'recommendations': [
|
||||
'Docker Desktop starten',
|
||||
'Als Administrator ausführen',
|
||||
'WSL2 oder Hyper-V aktivieren',
|
||||
'Docker Desktop neu installieren'
|
||||
]
|
||||
'success': True,
|
||||
'message': f'Projekt {project_name} nativ gestartet mit: {command}'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': f'Fehler beim Start: {str(e)}'})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Reconnect fehlgeschlagen: {str(e)}',
|
||||
'docker_available': False
|
||||
})
|
||||
return jsonify({'success': False, 'error': f'API-Fehler: {str(e)}'})
|
||||
|
||||
@app.route('/test_icons')
|
||||
def test_icons():
|
||||
"""Icon Test Seite"""
|
||||
return render_template('test_icons.html')
|
||||
@app.route('/api/open_terminal/<project_name>', methods=['POST'])
|
||||
def api_open_terminal(project_name):
|
||||
"""Öffne Terminal im Projektordner"""
|
||||
try:
|
||||
project_path = os.path.join(PROJECTS_DIR, project_name)
|
||||
if not os.path.exists(project_path):
|
||||
return jsonify({'success': False, 'error': 'Projekt nicht gefunden'})
|
||||
|
||||
# Öffne Terminal je nach Betriebssystem
|
||||
if os.name == 'nt': # Windows
|
||||
subprocess.Popen(['cmd', '/c', 'start', 'cmd', '/k', f'cd /d "{project_path}"'])
|
||||
else: # Linux/macOS
|
||||
subprocess.Popen(['gnome-terminal', '--working-directory', project_path])
|
||||
|
||||
return jsonify({'success': True, 'message': 'Terminal geöffnet'})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': f'Fehler beim Öffnen des Terminals: {str(e)}'})
|
||||
|
||||
# Test Icons Template Route
|
||||
@app.route('/templates/test_icons.html')
|
||||
|
||||
20
config.json
20
config.json
@@ -45,14 +45,14 @@
|
||||
"description": "Quellcode klonen und selbst bauen"
|
||||
},
|
||||
"docker_registry": {
|
||||
"available": true,
|
||||
"available": false,
|
||||
"url": "docker.io/simolzimol/quizify:1.3.0",
|
||||
"type": "docker_registry",
|
||||
"description": "Offizielles Docker-Image von Registry"
|
||||
},
|
||||
"docker_file": {
|
||||
"available": true,
|
||||
"url": "https://simolzimol.eu/images/quizify-1.3.0.tar",
|
||||
"available": false,
|
||||
"url": "https://gitea.example.com/user/repo/raw/branch/main/Dockerfile",
|
||||
"type": "docker_file",
|
||||
"description": "Docker-Image-Datei von Webserver"
|
||||
},
|
||||
@@ -60,29 +60,29 @@
|
||||
"available": true,
|
||||
"url": "https://simolzimol.eu/images/quizify-1.3.0.tar",
|
||||
"type": "docker_url",
|
||||
"description": "Docker-Image direkt von URL laden"
|
||||
"description": "Docker-Image direkt von URL laden (tar)"
|
||||
},
|
||||
"native_python": {
|
||||
"available": true,
|
||||
"url": "https://github.com/example/simple-notes",
|
||||
"url": "https://gitea.simolzimol.net/Simon/quizify",
|
||||
"type": "python",
|
||||
"description": "Python: Direkter Start mit Flask"
|
||||
},
|
||||
"native_batch": {
|
||||
"available": true,
|
||||
"url": "https://github.com/example/media-server",
|
||||
"url": "https://gitea.simolzimol.net/Simon/quizify",
|
||||
"type": "batch_script",
|
||||
"description": "Windows: Native Ausführung mit start.bat"
|
||||
},
|
||||
"native_shell": {
|
||||
"available": true,
|
||||
"url": "https://github.com/example/media-server",
|
||||
"available": false,
|
||||
"url": "https://releases.example.com/install.sh",
|
||||
"type": "shell_script",
|
||||
"description": "Linux/macOS: Native Ausführung mit start.sh"
|
||||
},
|
||||
"native_nodejs": {
|
||||
"available": true,
|
||||
"url": "https://github.com/example/media-server",
|
||||
"available": false,
|
||||
"url": "https://registry.npmjs.org/package/-/package-1.0.0.tgz",
|
||||
"type": "nodejs",
|
||||
"description": "Node.js: Direkter Start mit npm start"
|
||||
}
|
||||
|
||||
@@ -3,17 +3,120 @@
|
||||
{% block title %}{{ project.name }} - Details{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Header mit Status-Badge -->
|
||||
<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>
|
||||
<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">
|
||||
@@ -26,51 +129,51 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<strong>Name:</strong> {{ project.name }}
|
||||
<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">
|
||||
<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 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">
|
||||
<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 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">
|
||||
<strong>Installiert:</strong> {{ project.created }}
|
||||
<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 %}
|
||||
<div class="mt-4">
|
||||
<h6>README:</h6>
|
||||
<hr>
|
||||
<div class="mt-3">
|
||||
<h6><i class="fas fa-book me-2"></i>Beschreibung</h6>
|
||||
<div class="bg-light p-3 rounded">
|
||||
<pre class="mb-0 small">{{ project.readme }}</pre>
|
||||
<p class="mb-0">{{ project.readme[:300] }}{% if project.readme|length > 300 %}...{% endif %}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -138,12 +241,15 @@
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<!-- Schnellaktionen -->
|
||||
<!-- Container-Steuerung -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-bolt me-2"></i>Container-Steuerung
|
||||
<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">
|
||||
@@ -155,26 +261,103 @@
|
||||
<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>
|
||||
<!-- 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 %}
|
||||
|
||||
<a href="{{ url_for('build_project', project_name=project.name) }}" class="btn btn-primary">
|
||||
<i class="fas fa-hammer"></i> Image neu bauen
|
||||
</a>
|
||||
<hr class="my-2">
|
||||
|
||||
<button class="btn btn-danger" onclick="removeProjectSafely()">
|
||||
<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 -->
|
||||
<!-- Port-Status und Verbindungen -->
|
||||
{% if project.status == 'running' %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
@@ -184,16 +367,35 @@
|
||||
</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 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">
|
||||
<small class="text-muted">
|
||||
Klicken Sie auf die Links um die Anwendung zu öffnen.
|
||||
|
||||
<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>
|
||||
@@ -306,6 +508,91 @@
|
||||
</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
|
||||
@@ -720,6 +1007,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
refreshLogs();
|
||||
updateMonitoring();
|
||||
|
||||
// Initialisiere Start-Modus basierend auf Installation
|
||||
detectAndSetStartMode();
|
||||
|
||||
// Auto-Update alle 10 Sekunden
|
||||
setInterval(() => {
|
||||
updateMonitoring();
|
||||
@@ -902,5 +1192,261 @@ function closeRemoveModal() {
|
||||
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 %}
|
||||
|
||||
Reference in New Issue
Block a user