added admin functions
This commit is contained in:
parent
aa5b835e44
commit
0a63ceca7f
70
app.py
70
app.py
@ -76,13 +76,81 @@ def control_page():
|
||||
return render_template('control.html', config=current_config)
|
||||
|
||||
|
||||
# ── Admin ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@app.route('/admin')
|
||||
def admin():
|
||||
"""Admin-Übersicht: Configs anzeigen, bearbeiten, Logs etc."""
|
||||
configs = load_configs()
|
||||
return render_template('admin.html', configs=configs)
|
||||
|
||||
|
||||
@app.route('/admin/edit/<filename>', methods=['GET', 'POST'])
|
||||
def admin_edit_config(filename):
|
||||
path = os.path.join(app.config['CONFIG_DIR'], filename)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
new_content = request.form.get('config_content')
|
||||
if not new_content:
|
||||
raise ValueError("Kein Inhalt übermittelt")
|
||||
|
||||
# Validierung: versuche zu parsen
|
||||
json.loads(new_content)
|
||||
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
logger.info(f"Config {filename} erfolgreich gespeichert")
|
||||
return redirect(url_for('admin'))
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Ungültiges JSON in {filename}: {e}")
|
||||
error_msg = f"Ungültiges JSON: {str(e)}"
|
||||
except Exception as e:
|
||||
logger.error(f"Speicherfehler {filename}: {e}")
|
||||
error_msg = f"Fehler beim Speichern: {str(e)}"
|
||||
|
||||
# Bei Fehler: zurück zum Formular mit Fehlermeldung
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
return render_template('admin_edit.html', filename=filename, content=content, error=error_msg)
|
||||
|
||||
# GET: Formular laden
|
||||
if not os.path.exists(path):
|
||||
return "Konfiguration nicht gefunden", 404
|
||||
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
return render_template('admin_edit.html', filename=filename, content=content)
|
||||
|
||||
|
||||
@app.route('/admin/delete/<filename>', methods=['POST'])
|
||||
def admin_delete_config(filename):
|
||||
path = os.path.join(app.config['CONFIG_DIR'], filename)
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
logger.info(f"Config gelöscht: {filename}")
|
||||
return jsonify({"success": True, "message": f"{filename} gelöscht"})
|
||||
except Exception as e:
|
||||
logger.error(f"Löschfehler {filename}: {e}")
|
||||
return jsonify({"success": False, "message": str(e)}), 500
|
||||
return jsonify({"success": False, "message": "Datei nicht gefunden"}), 404
|
||||
|
||||
|
||||
@app.route('/admin/logs')
|
||||
def admin_logs():
|
||||
log_path = 'app.log'
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, 'r', encoding='utf-8') as f:
|
||||
logs = f.read()
|
||||
else:
|
||||
logs = "Keine Logs vorhanden."
|
||||
|
||||
return render_template('admin_logs.html', logs=logs)
|
||||
|
||||
|
||||
@app.route('/configs/<path:filename>')
|
||||
def serve_config_file(filename):
|
||||
return send_from_directory(app.config['CONFIG_DIR'], filename)
|
||||
|
||||
@ -4,27 +4,108 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<h1>Admin-Bereich</h1>
|
||||
<p class="lead mb-4">Konfigurationen verwalten, Logs einsehen, etc.</p>
|
||||
<h1 class="mb-4">Admin-Bereich</h1>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<a href="{{ url_for('admin_logs') }}" class="btn btn-outline-info">
|
||||
<i class="bi bi-journal-text me-2"></i>Logs ansehen
|
||||
</a>
|
||||
</div>
|
||||
<div class="col text-end">
|
||||
<a href="#" class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#newConfigModal">
|
||||
<i class="bi bi-plus-circle me-2"></i>Neue Konfiguration
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="mt-5 mb-3">Vorhandene Konfigurationen</h3>
|
||||
|
||||
<h3>Vorhandene Konfigurationen</h3>
|
||||
{% if configs %}
|
||||
<ul class="list-group">
|
||||
{% for cfg in configs %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ cfg.name }} ({{ cfg.filename }})
|
||||
<span>
|
||||
<a href="#" class="btn btn-sm btn-outline-primary">Bearbeiten</a>
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Hub-ID</th>
|
||||
<th>Datei</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cfg in configs %}
|
||||
<tr>
|
||||
<td>{{ cfg.name }}</td>
|
||||
<td>{{ cfg.hub_id | default('–') }}</td>
|
||||
<td><code>{{ cfg.filename }}</code></td>
|
||||
<td>
|
||||
<a href="{{ url_for('admin_edit_config', filename=cfg.filename) }}"
|
||||
class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-pencil"></i> Bearbeiten
|
||||
</a>
|
||||
<button class="btn btn-sm btn-danger delete-config-btn"
|
||||
data-filename="{{ cfg.filename }}"
|
||||
data-name="{{ cfg.name }}">
|
||||
<i class="bi bi-trash"></i> Löschen
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">Noch keine Konfigurationen vorhanden.</div>
|
||||
<div class="alert alert-info">
|
||||
Noch keine Konfigurationen vorhanden. Erstelle eine neue.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<a href="{{ url_for('index') }}" class="btn btn-secondary">Zurück zur Übersicht</a>
|
||||
<!-- Modal für neue Config (Platzhalter – später ausbauen) -->
|
||||
<div class="modal fade" id="newConfigModal" tabindex="-1" aria-labelledby="newConfigModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="newConfigModalLabel">Neue Konfiguration anlegen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Hier könnte später ein Formular kommen (Name, Hub-ID, Kanäle...).<br>
|
||||
Für den Moment: Erstelle einfach eine .json-Datei im <code>configs/</code>-Ordner.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.delete-config-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const filename = btn.dataset.filename;
|
||||
const name = btn.dataset.name;
|
||||
|
||||
if (!confirm(`Sicher löschen: ${name} (${filename}) ?`)) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/admin/delete/${filename}`, { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
alert('Gelöscht');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Fehler: ' + data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Netzwerkfehler beim Löschen');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
44
templates/admin_edit.html
Normal file
44
templates/admin_edit.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Bearbeite {{ filename }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<h1>Bearbeite Konfiguration</h1>
|
||||
<h4 class="text-muted mb-4">{{ filename }}</h4>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
{{ error }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="config_content" class="form-label">JSON-Inhalt (achte auf gültiges Format):</label>
|
||||
<textarea class="form-control font-monospace" id="config_content" name="config_content" rows="20">{{ content }}</textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-3">
|
||||
<button type="submit" class="btn btn-primary btn-lg px-5">Speichern</button>
|
||||
<a href="{{ url_for('admin') }}" class="btn btn-outline-secondary btn-lg px-5">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-5">
|
||||
<h5>Hilfe – Beispielstruktur</h5>
|
||||
<pre class="bg-light p-3 rounded">
|
||||
{
|
||||
"name": "BR 01 Dampflok",
|
||||
"image": "br01.jpg",
|
||||
"hub_id": 0,
|
||||
"hub_type": "6channel",
|
||||
"channels": [
|
||||
{"port": "A", "type": "motor", "name": "Fahrtrichtung", "invert": false},
|
||||
{"port": "C", "type": "light", "name": "Spitzenlicht", "on_value": 1.0}
|
||||
]
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
17
templates/admin_logs.html
Normal file
17
templates/admin_logs.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Logs – MK Control{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<h1>Log-Datei (app.log)</h1>
|
||||
|
||||
<pre class="bg-dark text-light p-4 rounded" style="max-height: 70vh; overflow-y: auto; font-size: 0.9rem;">
|
||||
{{ logs | safe }}
|
||||
</pre>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="{{ url_for('admin') }}" class="btn btn-secondary">Zurück zum Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user