new file: app.py new file: config.example.json new file: config.json new file: docker_diagnose.py new file: requirements.txt new file: start.bat new file: templates/available_projects.html new file: templates/base.html new file: templates/config.html new file: templates/docker_status.html new file: templates/index.html new file: templates/project_details.html new file: templates/project_details_fixed.html new file: templates/project_details_new.html new file: templates/project_details_old.html .gitignore
393 lines
16 KiB
HTML
393 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Konfiguration - {{ super() }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-cog me-2"></i>Systemkonfiguration
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST">
|
|
<div class="mb-4">
|
|
<label for="project_list_url" class="form-label">
|
|
<i class="fas fa-link me-1"></i>Projektliste URL
|
|
</label>
|
|
<input type="url"
|
|
class="form-control"
|
|
id="project_list_url"
|
|
name="project_list_url"
|
|
value="{{ config.project_list_url or '' }}"
|
|
placeholder="https://example.com/projects.json">
|
|
<div class="form-text">
|
|
URL zu einer JSON-Datei mit Projektinformationen oder einer Webseite mit Git-URLs.
|
|
<br>JSON Format: <code>[{"url": "https://git.example.com/repo", "name": "Project Name", "description": "..."}]</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="auto_refresh_minutes" class="form-label">
|
|
<i class="fas fa-clock me-1"></i>Automatische Aktualisierung (Minuten)
|
|
</label>
|
|
<input type="number"
|
|
class="form-control"
|
|
id="auto_refresh_minutes"
|
|
name="auto_refresh_minutes"
|
|
value="{{ config.auto_refresh_minutes or 30 }}"
|
|
min="5"
|
|
max="1440">
|
|
<div class="form-text">
|
|
Wie oft soll die Projektliste automatisch aktualisiert werden? (5-1440 Minuten)
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="docker_registry" class="form-label">
|
|
<i class="fas fa-server me-1"></i>Docker Registry (Optional)
|
|
</label>
|
|
<input type="text"
|
|
class="form-control"
|
|
id="docker_registry"
|
|
name="docker_registry"
|
|
value="{{ config.docker_registry or '' }}"
|
|
placeholder="registry.example.com">
|
|
<div class="form-text">
|
|
Private Docker Registry für das Pushen von Images (optional).
|
|
</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="testConnection()">
|
|
<i class="fas fa-wifi"></i> Verbindung testen
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i> Speichern
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-4">
|
|
<!-- System Status -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-info-circle me-2"></i>Systemstatus
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="status-item mb-3">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Docker:</span>
|
|
<span id="dockerStatus" class="badge bg-secondary">Prüfung...</span>
|
|
</div>
|
|
</div>
|
|
<div class="status-item mb-3">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Git:</span>
|
|
<span id="gitStatus" class="badge bg-secondary">Prüfung...</span>
|
|
</div>
|
|
</div>
|
|
<div class="status-item mb-3">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Projektverzeichnis:</span>
|
|
<span id="projectDirStatus" class="badge bg-secondary">Prüfung...</span>
|
|
</div>
|
|
</div>
|
|
<div class="status-item">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Festplattenspeicher:</span>
|
|
<span id="diskSpaceStatus" class="badge bg-secondary">Prüfung...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Erweiterte Einstellungen -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-tools me-2"></i>Erweiterte Einstellungen
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<button class="btn btn-outline-warning btn-sm w-100" onclick="clearCache()">
|
|
<i class="fas fa-broom"></i> Cache leeren
|
|
</button>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button class="btn btn-outline-info btn-sm w-100" onclick="exportConfig()">
|
|
<i class="fas fa-download"></i> Konfiguration exportieren
|
|
</button>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button class="btn btn-outline-success btn-sm w-100" onclick="document.getElementById('importFile').click()">
|
|
<i class="fas fa-upload"></i> Konfiguration importieren
|
|
</button>
|
|
<input type="file" id="importFile" style="display: none" accept=".json" onchange="importConfig(event)">
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-outline-danger btn-sm w-100" onclick="resetConfig()">
|
|
<i class="fas fa-undo"></i> Auf Standard zurücksetzen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Projektliste Vorschau -->
|
|
{% if config.projects %}
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<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-list me-2"></i>Aktuelle Projektliste ({{ config.projects|length }} Projekte)
|
|
</h5>
|
|
<small class="text-muted">Letzte Aktualisierung vor {{ last_update or 'unbekannt' }}</small>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>URL</th>
|
|
<th>Beschreibung</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for project in config.projects[:10] %}
|
|
<tr>
|
|
<td><strong>{{ project.name or 'Unbekannt' }}</strong></td>
|
|
<td>
|
|
<a href="{{ project.url }}" target="_blank" class="text-decoration-none">
|
|
{{ project.url[:50] }}{% if project.url|length > 50 %}...{% endif %}
|
|
</a>
|
|
</td>
|
|
<td>{{ (project.description or 'Keine Beschreibung')[:60] }}{% if (project.description or '')|length > 60 %}...{% endif %}</td>
|
|
<td>
|
|
{% if project.name in installed_projects %}
|
|
<span class="badge bg-success">Installiert</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Verfügbar</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% if config.projects|length > 10 %}
|
|
<p class="text-muted text-center">... und {{ config.projects|length - 10 }} weitere Projekte</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Debug Informationen -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<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="row">
|
|
<div class="col-md-6">
|
|
<h6>Systeminfo:</h6>
|
|
<ul class="list-unstyled small">
|
|
<li><strong>Arbeitsverzeichnis:</strong> <code id="workingDir">Lade...</code></li>
|
|
<li><strong>Projektverzeichnis:</strong> <code>./projects/</code></li>
|
|
<li><strong>App-Verzeichnis:</strong> <code>./apps/</code></li>
|
|
<li><strong>Konfigurationsdatei:</strong> <code>./config.json</code></li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Laufzeit-Info:</h6>
|
|
<ul class="list-unstyled small">
|
|
<li><strong>Flask Debug:</strong> <span id="flaskDebug">An</span></li>
|
|
<li><strong>Host:</strong> <code>0.0.0.0:5000</code></li>
|
|
<li><strong>Uptime:</strong> <span id="uptime">Lade...</span></li>
|
|
<li><strong>Speicherverbrauch:</strong> <span id="memUsage">Lade...</span></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Systemstatus prüfen
|
|
function checkSystemStatus() {
|
|
// Docker Status
|
|
fetch('/api/system_status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
updateStatusBadge('dockerStatus', data.docker, 'Docker');
|
|
updateStatusBadge('gitStatus', data.git, 'Git');
|
|
updateStatusBadge('projectDirStatus', data.project_dir, 'Projektverzeichnis');
|
|
updateStatusBadge('diskSpaceStatus', data.disk_space, 'Festplattenspeicher');
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler beim Prüfen des Systemstatus:', error);
|
|
});
|
|
}
|
|
|
|
function updateStatusBadge(elementId, status, name) {
|
|
const element = document.getElementById(elementId);
|
|
if (status.available) {
|
|
element.textContent = status.version || 'Verfügbar';
|
|
element.className = 'badge bg-success';
|
|
} else {
|
|
element.textContent = 'Nicht verfügbar';
|
|
element.className = 'badge bg-danger';
|
|
}
|
|
}
|
|
|
|
// Verbindung testen
|
|
function testConnection() {
|
|
const url = document.getElementById('project_list_url').value;
|
|
if (!url) {
|
|
alert('Bitte geben Sie eine URL ein.');
|
|
return;
|
|
}
|
|
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Teste...';
|
|
button.disabled = true;
|
|
|
|
fetch('/api/test_connection', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({url: url})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`Verbindung erfolgreich! ${data.projects_found} Projekte gefunden.`);
|
|
} else {
|
|
alert(`Verbindungsfehler: ${data.error}`);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert(`Netzwerkfehler: ${error}`);
|
|
})
|
|
.finally(() => {
|
|
button.innerHTML = originalText;
|
|
button.disabled = false;
|
|
});
|
|
}
|
|
|
|
// Cache leeren
|
|
function clearCache() {
|
|
if (confirm('Möchten Sie wirklich den Cache leeren?')) {
|
|
fetch('/api/clear_cache', {method: 'POST'})
|
|
.then(response => response.json())
|
|
.then(data => alert(data.message))
|
|
.catch(error => alert('Fehler: ' + error));
|
|
}
|
|
}
|
|
|
|
// Konfiguration exportieren
|
|
function exportConfig() {
|
|
fetch('/api/export_config')
|
|
.then(response => response.blob())
|
|
.then(blob => {
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'app_installer_config.json';
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
})
|
|
.catch(error => alert('Fehler beim Export: ' + error));
|
|
}
|
|
|
|
// Konfiguration importieren
|
|
function importConfig(event) {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
try {
|
|
const config = JSON.parse(e.target.result);
|
|
|
|
if (confirm('Möchten Sie die aktuelle Konfiguration mit der importierten ersetzen?')) {
|
|
fetch('/api/import_config', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(config)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Konfiguration erfolgreich importiert!');
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler beim Import: ' + data.error);
|
|
}
|
|
})
|
|
.catch(error => alert('Netzwerkfehler: ' + error));
|
|
}
|
|
} catch (error) {
|
|
alert('Ungültige JSON-Datei: ' + error.message);
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
// Konfiguration zurücksetzen
|
|
function resetConfig() {
|
|
if (confirm('Möchten Sie wirklich alle Einstellungen auf die Standardwerte zurücksetzen?')) {
|
|
fetch('/api/reset_config', {method: 'POST'})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Konfiguration zurückgesetzt!');
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler beim Zurücksetzen: ' + data.error);
|
|
}
|
|
})
|
|
.catch(error => alert('Netzwerkfehler: ' + error));
|
|
}
|
|
}
|
|
|
|
// System-Info laden
|
|
function loadSystemInfo() {
|
|
document.getElementById('workingDir').textContent = window.location.origin;
|
|
document.getElementById('uptime').textContent = 'Läuft seit Start';
|
|
document.getElementById('memUsage').textContent = 'Nicht verfügbar';
|
|
}
|
|
|
|
// Beim Laden der Seite
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
checkSystemStatus();
|
|
loadSystemInfo();
|
|
});
|
|
</script>
|
|
{% endblock %}
|