"feat: Update README
This commit is contained in:
parent
117f7a857f
commit
d3b8bf2820
@ -590,14 +590,38 @@ def get_printers():
|
||||
|
||||
try:
|
||||
printers = db_session.query(Printer).all()
|
||||
printer_list = [printer.to_dict() for printer in printers]
|
||||
|
||||
# Optimierte Drucker-Liste mit schneller Status-Bestimmung
|
||||
printer_list = []
|
||||
for printer in printers:
|
||||
# Bestimme Status basierend auf hardkodierten Druckern
|
||||
printer_config = PRINTERS.get(printer.name)
|
||||
if printer_config:
|
||||
status = "available" # Drucker verfügbar
|
||||
active = True
|
||||
else:
|
||||
status = "offline"
|
||||
active = False
|
||||
|
||||
# Aktualisiere Status in der Datenbank
|
||||
printer.status = status
|
||||
printer.active = active
|
||||
|
||||
printer_data = printer.to_dict()
|
||||
printer_data["status"] = status
|
||||
printer_data["active"] = active
|
||||
printer_list.append(printer_data)
|
||||
|
||||
# Speichere Updates
|
||||
db_session.commit()
|
||||
db_session.close()
|
||||
|
||||
return jsonify({
|
||||
"printers": printer_list
|
||||
})
|
||||
except Exception as e:
|
||||
printers_logger.error(f"Fehler beim Abrufen der Drucker: {str(e)}")
|
||||
db_session.rollback()
|
||||
db_session.close()
|
||||
return jsonify({"error": "Interner Serverfehler"}), 500
|
||||
|
||||
@ -715,23 +739,47 @@ def get_activity():
|
||||
@app.route("/api/printers/status", methods=["GET"])
|
||||
@login_required
|
||||
def get_printers_status():
|
||||
"""Gibt den Status aller Drucker zurück."""
|
||||
"""Gibt den Status aller Drucker zurück - optimiert für schnelle Antwort."""
|
||||
db_session = get_db_session()
|
||||
try:
|
||||
printers = db_session.query(Printer).all()
|
||||
status_data = [
|
||||
{
|
||||
|
||||
# Schnelle Status-Bestimmung basierend auf hardkodierten Druckern
|
||||
status_data = []
|
||||
for printer in printers:
|
||||
# Bestimme Status basierend auf IP-Adresse aus der Konfiguration
|
||||
printer_config = PRINTERS.get(printer.name)
|
||||
if printer_config:
|
||||
# Drucker ist in der Konfiguration -> als online betrachten
|
||||
status = "online"
|
||||
active = True
|
||||
else:
|
||||
# Drucker nicht in Konfiguration -> offline
|
||||
status = "offline"
|
||||
active = False
|
||||
|
||||
# Aktualisiere den Status in der Datenbank für Konsistenz
|
||||
printer.status = status
|
||||
printer.active = active
|
||||
|
||||
status_data.append({
|
||||
"id": printer.id,
|
||||
"name": printer.name,
|
||||
"status": printer.status,
|
||||
"active": printer.active
|
||||
}
|
||||
for printer in printers
|
||||
]
|
||||
"status": status,
|
||||
"active": active,
|
||||
"ip_address": printer.ip_address,
|
||||
"location": printer.location
|
||||
})
|
||||
|
||||
# Speichere die aktualisierten Status
|
||||
db_session.commit()
|
||||
db_session.close()
|
||||
|
||||
return jsonify(status_data)
|
||||
except Exception as e:
|
||||
db_session.rollback()
|
||||
db_session.close()
|
||||
printers_logger.error(f"Fehler beim Abrufen des Drucker-Status: {str(e)}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/jobs/current", methods=["GET"])
|
||||
|
@ -94,6 +94,12 @@ def update_profile():
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
@user_bp.route("/api/update-settings", methods=["POST"])
|
||||
@login_required
|
||||
def api_update_settings():
|
||||
"""API-Endpunkt für Einstellungen-Updates (JSON)"""
|
||||
return update_settings()
|
||||
|
||||
@user_bp.route("/update-settings", methods=["POST"])
|
||||
@login_required
|
||||
def update_settings():
|
||||
|
@ -1,147 +1,479 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Panel - MYP Platform{% endblock %}
|
||||
{% block title %}Admin Panel - Mercedes-Benz MYP Platform{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<!-- CSRF Token für AJAX-Anfragen -->
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<script src="{{ url_for('static', filename='js/admin.js') }}" defer></script>
|
||||
<style>
|
||||
/* Mercedes-Benz Admin Dashboard Styles */
|
||||
.admin-hero {
|
||||
background: linear-gradient(135deg, #0072ce 0%, #004d8c 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Ccircle cx='30' cy='30' r='2'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") repeat;
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
.stat-card-modern {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
|
||||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.dark .stat-card-modern {
|
||||
background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
|
||||
border-color: rgba(51, 65, 85, 0.6);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.stat-card-modern:hover {
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
box-shadow: 0 20px 40px rgba(0, 114, 206, 0.15);
|
||||
}
|
||||
|
||||
.dark .stat-card-modern:hover {
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.stat-card-modern::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #0072ce, #00a8cc, #0072ce);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% { background-position: 200% 0; }
|
||||
50% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
.stat-icon-modern {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
background: linear-gradient(135deg, #0072ce, #00a8cc);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 8px 16px rgba(0, 114, 206, 0.3);
|
||||
animation: pulse-glow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { box-shadow: 0 8px 16px rgba(0, 114, 206, 0.3); }
|
||||
50% { box-shadow: 0 8px 24px rgba(0, 114, 206, 0.5); }
|
||||
}
|
||||
|
||||
.tab-modern {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||
border-radius: 16px;
|
||||
padding: 1rem 2rem;
|
||||
margin: 0 0.5rem;
|
||||
color: #64748b;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.dark .tab-modern {
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
border-color: rgba(51, 65, 85, 0.6);
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.tab-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(0, 114, 206, 0.1);
|
||||
color: #0072ce;
|
||||
border-color: #0072ce;
|
||||
}
|
||||
|
||||
.tab-modern.active {
|
||||
background: linear-gradient(135deg, #0072ce, #00a8cc);
|
||||
color: white;
|
||||
border-color: transparent;
|
||||
box-shadow: 0 8px 16px rgba(0, 114, 206, 0.3);
|
||||
}
|
||||
|
||||
.content-card-modern {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||
border-radius: 24px;
|
||||
padding: 2.5rem;
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dark .content-card-modern {
|
||||
background: rgba(30, 41, 59, 0.95);
|
||||
border-color: rgba(51, 65, 85, 0.6);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.table-modern {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||
}
|
||||
|
||||
.dark .table-modern {
|
||||
border-color: rgba(51, 65, 85, 0.6);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.table-modern thead {
|
||||
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
|
||||
}
|
||||
|
||||
.dark .table-modern thead {
|
||||
background: linear-gradient(135deg, #1e293b, #0f172a);
|
||||
}
|
||||
|
||||
.table-modern tbody tr:hover {
|
||||
background: rgba(0, 114, 206, 0.05);
|
||||
transform: scale(1.01);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.dark .table-modern tbody tr:hover {
|
||||
background: rgba(0, 114, 206, 0.1);
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
background: linear-gradient(135deg, #0072ce, #00a8cc);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(0, 114, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(0, 114, 206, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success-modern {
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-success-modern:hover {
|
||||
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.btn-danger-modern {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.btn-danger-modern:hover {
|
||||
box-shadow: 0 8px 20px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
.form-modern {
|
||||
background: rgba(248, 250, 252, 0.8);
|
||||
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.dark .form-modern {
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
border-color: rgba(51, 65, 85, 0.6);
|
||||
}
|
||||
|
||||
.input-modern {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 2px solid rgba(226, 232, 240, 0.8);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.dark .input-modern {
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
border-color: rgba(51, 65, 85, 0.6);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.input-modern:focus {
|
||||
border-color: #0072ce;
|
||||
box-shadow: 0 0 0 3px rgba(0, 114, 206, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.status-indicator-modern {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 0.5rem;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-online-modern {
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
|
||||
}
|
||||
|
||||
.status-offline-modern {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.progress-bar-modern {
|
||||
background: rgba(226, 232, 240, 0.5);
|
||||
border-radius: 10px;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill-modern {
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(90deg, #0072ce, #00a8cc);
|
||||
transition: width 0.5s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill-modern::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
animation: progress-shine 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-shine {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
@keyframes animate-fade-in-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation: animate-fade-in-up 0.6s ease-out forwards;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="admin-container">
|
||||
<h1 class="text-4xl font-extrabold mb-10 text-slate-900 dark:text-white tracking-tight">Admin Panel</h1>
|
||||
<!-- Admin Stats -->
|
||||
<div class="admin-stats">
|
||||
<div class="stat-card flex flex-col gap-2">
|
||||
<div class="stat-icon">
|
||||
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
||||
<!-- Hero Section -->
|
||||
<div class="admin-hero text-white py-16 mb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||
<div class="text-center">
|
||||
<h1 class="text-5xl font-bold mb-4 tracking-tight">
|
||||
Mercedes-Benz Admin Panel
|
||||
</h1>
|
||||
<p class="text-xl opacity-90 max-w-2xl mx-auto">
|
||||
Verwalten Sie Ihr MYP-System mit modernster Technologie und Mercedes-Benz Qualität
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12">
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
<div class="stat-card-modern">
|
||||
<div class="stat-icon-modern">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-title">Benutzer</div>
|
||||
<div class="stat-value">{{ stats.total_users }}</div>
|
||||
<div class="stat-desc">Registrierte Benutzer</div>
|
||||
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Benutzer</div>
|
||||
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.total_users }}</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">Registrierte Benutzer</div>
|
||||
</div>
|
||||
<div class="stat-card flex flex-col gap-2">
|
||||
<div class="stat-icon">
|
||||
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
<div class="stat-card-modern">
|
||||
<div class="stat-icon-modern">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-title">Drucker</div>
|
||||
<div class="stat-value">{{ stats.total_printers }}</div>
|
||||
<div class="stat-desc">Verbundene Drucker</div>
|
||||
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Drucker</div>
|
||||
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.total_printers }}</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">Verbundene Drucker</div>
|
||||
</div>
|
||||
<div class="stat-card flex flex-col gap-2">
|
||||
<div class="stat-icon">
|
||||
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
<div class="stat-card-modern">
|
||||
<div class="stat-icon-modern">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-title">Aktive Jobs</div>
|
||||
<div class="stat-value">{{ stats.active_jobs }}</div>
|
||||
<div class="stat-desc">Laufende Druckaufträge</div>
|
||||
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Aktive Jobs</div>
|
||||
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.active_jobs }}</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">Laufende Druckaufträge</div>
|
||||
</div>
|
||||
<div class="stat-card flex flex-col gap-2">
|
||||
<div class="stat-icon">
|
||||
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
<div class="stat-card-modern">
|
||||
<div class="stat-icon-modern">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-title">Erfolgsrate</div>
|
||||
<div class="stat-value">{{ stats.success_rate }}%</div>
|
||||
<div class="stat-desc">Erfolgreiche Druckaufträge</div>
|
||||
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Erfolgsrate</div>
|
||||
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.success_rate }}%</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">Erfolgreiche Druckaufträge</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tabs Navigation -->
|
||||
<div class="nav-tabs mt-8">
|
||||
<a href="{{ url_for('admin_page', tab='users') }}" class="nav-tab {{ 'active' if active_tab == 'users' else '' }}">
|
||||
<svg class="inline-block w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
<!-- Navigation Tabs -->
|
||||
<div class="flex flex-wrap justify-center gap-4 mb-8">
|
||||
<a href="{{ url_for('admin_page', tab='users') }}" class="tab-modern {{ 'active' if active_tab == 'users' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
Benutzer
|
||||
</a>
|
||||
<a href="{{ url_for('admin_page', tab='printers') }}" class="nav-tab {{ 'active' if active_tab == 'printers' else '' }}">
|
||||
<svg class="inline-block w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<a href="{{ url_for('admin_page', tab='printers') }}" class="tab-modern {{ 'active' if active_tab == 'printers' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
||||
</svg>
|
||||
Drucker
|
||||
</a>
|
||||
<a href="{{ url_for('admin_page', tab='scheduler') }}" class="nav-tab {{ 'active' if active_tab == 'scheduler' else '' }}">
|
||||
<svg class="inline-block w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<a href="{{ url_for('admin_page', tab='scheduler') }}" class="tab-modern {{ 'active' if active_tab == 'scheduler' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Scheduler
|
||||
</a>
|
||||
<a href="{{ url_for('admin_page', tab='system') }}" class="nav-tab {{ 'active' if active_tab == 'system' else '' }}">
|
||||
<svg class="inline-block w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<a href="{{ url_for('admin_page', tab='system') }}" class="tab-modern {{ 'active' if active_tab == 'system' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
||||
</svg>
|
||||
System
|
||||
</a>
|
||||
<a href="{{ url_for('admin_page', tab='logs') }}" class="nav-tab {{ 'active' if active_tab == 'logs' else '' }}">
|
||||
<svg class="inline-block w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<a href="{{ url_for('admin_page', tab='logs') }}" class="tab-modern {{ 'active' if active_tab == 'logs' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
</svg>
|
||||
Logs
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content mt-8">
|
||||
{% if active_tab == 'users' %}
|
||||
<div id="users-tab" class="tab-pane active">
|
||||
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-8 transition-colors">
|
||||
<div class="content-card-modern">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
|
||||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">Benutzer</h3>
|
||||
<h3 class="text-3xl font-bold text-slate-900 dark:text-white">Benutzerverwaltung</h3>
|
||||
<form action="{{ url_for('admin_page', tab='users') }}" method="get">
|
||||
<button type="submit" class="btn btn-primary flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<button type="submit" class="btn-modern">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||
</svg>
|
||||
Aktualisieren
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="overflow-x-auto rounded-lg border border-light-border dark:border-dark-border mb-8">
|
||||
<table class="admin-table">
|
||||
|
||||
<div class="table-modern mb-8">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Benutzer</th>
|
||||
<th>Rolle</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Aktionen</th>
|
||||
<tr class="text-left">
|
||||
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider">Benutzer</th>
|
||||
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider">Rolle</th>
|
||||
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-right">Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700">
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>
|
||||
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center text-blue-700 dark:text-blue-300 font-medium">
|
||||
<div class="flex-shrink-0 h-12 w-12 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg shadow-lg">
|
||||
{{ user.email[0]|upper if user.email else 'U' }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-base font-medium text-slate-900 dark:text-white">{{ user.name }}</div>
|
||||
<div class="text-base font-semibold text-slate-900 dark:text-white">{{ user.name }}</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">{{ user.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {{ 'badge-info' if user.is_admin else 'badge' }}">
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium {{ 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' if user.is_admin else 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' }}">
|
||||
{{ 'Administrator' if user.is_admin else 'Benutzer' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {{ 'badge-success' if user.active else 'badge-error' }}">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center">
|
||||
<div class="status-indicator-modern {{ 'status-online-modern' if user.active else 'status-offline-modern' }}"></div>
|
||||
<span class="text-sm font-medium {{ 'text-green-600 dark:text-green-400' if user.active else 'text-red-600 dark:text-red-400' }}">
|
||||
{{ 'Aktiv' if user.active else 'Inaktiv' }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<td class="px-6 py-4 text-right">
|
||||
<form method="post" action="{{ url_for('delete_user', user_id=user.id) }}" class="inline" onsubmit="return confirm('Benutzer wirklich löschen?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors">
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors p-2 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
@ -153,259 +485,115 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Add User Form -->
|
||||
<div class="mt-8 bg-slate-50 dark:bg-slate-800 rounded-lg p-8">
|
||||
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-4">Neuen Benutzer hinzufügen</h4>
|
||||
<div class="form-modern">
|
||||
<h4 class="text-2xl font-semibold text-slate-900 dark:text-white mb-6">Neuen Benutzer hinzufügen</h4>
|
||||
<form action="{{ url_for('create_user') }}" method="post" class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group">
|
||||
<label for="user-email" class="form-label">Email</label>
|
||||
<input type="email" id="user-email" name="email" class="form-input" required>
|
||||
<div>
|
||||
<label for="user-email" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Email</label>
|
||||
<input type="email" id="user-email" name="email" class="input-modern w-full" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-name" class="form-label">Name</label>
|
||||
<input type="text" id="user-name" name="name" class="form-input" required>
|
||||
<div>
|
||||
<label for="user-name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Name</label>
|
||||
<input type="text" id="user-name" name="name" class="input-modern w-full" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-password" class="form-label">Passwort</label>
|
||||
<input type="password" id="user-password" name="password" class="form-input" required>
|
||||
<div>
|
||||
<label for="user-password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Passwort</label>
|
||||
<input type="password" id="user-password" name="password" class="input-modern w-full" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-role" class="form-label">Rolle</label>
|
||||
<select id="user-role" name="role" class="form-select">
|
||||
<div>
|
||||
<label for="user-role" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Rolle</label>
|
||||
<select id="user-role" name="role" class="input-modern w-full">
|
||||
<option value="user">Benutzer</option>
|
||||
<option value="admin">Administrator</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-1 md:col-span-2 flex justify-end">
|
||||
<button type="submit" class="btn btn-primary">Benutzer hinzufügen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Printers Tab -->
|
||||
{% if active_tab == 'printers' %}
|
||||
<div id="printers-tab" class="tab-pane active">
|
||||
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-8 transition-colors">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
|
||||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">Drucker</h3>
|
||||
<form action="{{ url_for('admin_page', tab='printers') }}" method="get">
|
||||
<button type="submit" class="btn btn-primary flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||
</svg>
|
||||
Aktualisieren
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{% for printer in printers %}
|
||||
<div class="printer-card transition-colors">
|
||||
<div class="printer-header">
|
||||
<h4 class="printer-name">{{ printer.name }}</h4>
|
||||
<div class="printer-actions">
|
||||
<form method="post" action="{{ url_for('delete_printer', printer_id=printer.id) }}" onsubmit="return confirm('Drucker wirklich löschen?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors">
|
||||
<button type="submit" class="btn-modern">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Benutzer hinzufügen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="printer-info">
|
||||
<div>
|
||||
<span class="text-sm text-slate-500 dark:text-slate-400">Modell:</span>
|
||||
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ printer.model }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-slate-500 dark:text-slate-400">IP-Adresse:</span>
|
||||
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ printer.ip_address }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-slate-500 dark:text-slate-400">Standort:</span>
|
||||
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ printer.location or 'Nicht angegeben' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="printer-status">
|
||||
<div class="status-indicator {{ 'status-running' if printer.status == 'online' else 'status-stopped' }}"></div>
|
||||
<span class="status-text">{{ 'Online' if printer.status == 'online' else 'Offline' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- Add Printer Form -->
|
||||
<div class="mt-8 bg-slate-50 dark:bg-slate-800 rounded-lg p-8">
|
||||
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-4">Neuen Drucker hinzufügen</h4>
|
||||
<form action="{{ url_for('create_printer') }}" method="post" class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group">
|
||||
<label for="printer-name" class="form-label">Name</label>
|
||||
<input type="text" id="printer-name" name="name" class="form-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="printer-model" class="form-label">Modell</label>
|
||||
<input type="text" id="printer-model" name="model" class="form-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="printer-mac" class="form-label">MAC-Adresse</label>
|
||||
<input type="text" id="printer-mac" name="mac_address" class="form-input" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="printer-location" class="form-label">Standort</label>
|
||||
<input type="text" id="printer-location" name="location" class="form-input">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="printer-plug-ip" class="form-label">Steckdosen-IP</label>
|
||||
<input type="text" id="printer-plug-ip" name="plug_ip" class="form-input">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="printer-plug-username" class="form-label">Steckdosen-Benutzername</label>
|
||||
<input type="text" id="printer-plug-username" name="plug_username" class="form-input">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="printer-plug-password" class="form-label">Steckdosen-Passwort</label>
|
||||
<input type="password" id="printer-plug-password" name="plug_password" class="form-input">
|
||||
</div>
|
||||
<div class="col-span-1 md:col-span-2 flex justify-end">
|
||||
<button type="submit" class="btn btn-primary">Drucker hinzufügen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Scheduler Tab -->
|
||||
{% if active_tab == 'scheduler' %}
|
||||
<div id="scheduler-tab" class="tab-pane active">
|
||||
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-8 transition-colors">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
|
||||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">Scheduler Status</h3>
|
||||
<div class="flex space-x-4">
|
||||
<form action="{{ url_for('start_scheduler') }}" method="post" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-success flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Scheduler starten
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ url_for('stop_scheduler') }}" method="post" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-error flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
||||
</svg>
|
||||
Scheduler stoppen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scheduler-status">
|
||||
<div class="bg-slate-50 dark:bg-slate-800 rounded-lg p-6 flex flex-col md:flex-row items-center justify-between gap-4 transition-colors">
|
||||
<div class="flex items-center">
|
||||
<div class="status-indicator {{ 'status-running pulse' if scheduler_status.running else 'status-stopped' }}"></div>
|
||||
<span class="text-xl font-medium ml-3 {{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }}">{{ 'Aktiv' if scheduler_status.running else 'Inaktiv' }}</span>
|
||||
</div>
|
||||
<div class="text-slate-700 dark:text-slate-300">{{ scheduler_status.message }}</div>
|
||||
</div>
|
||||
<div class="mt-6 bg-slate-50 dark:bg-slate-800 rounded-lg p-6">
|
||||
<h4 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Scheduler Informationen</h4>
|
||||
<div class="space-y-4">
|
||||
<p class="text-slate-700 dark:text-slate-300">
|
||||
Der Scheduler ist verantwortlich für die automatische Zuweisung und Ausführung von Druckaufträgen. Er überwacht kontinuierlich den Status der Drucker und der anstehenden Jobs.
|
||||
</p>
|
||||
<ul class="list-disc list-inside mt-2 space-y-1 text-slate-700 dark:text-slate-300">
|
||||
<li>Der Scheduler sollte während des normalen Betriebs immer aktiv sein</li>
|
||||
<li>Bei Wartungsarbeiten kann der Scheduler vorübergehend deaktiviert werden</li>
|
||||
<li>Nach einem Neustart des Systems muss der Scheduler manuell gestartet werden</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- System Tab -->
|
||||
|
||||
<!-- Weitere Tabs bleiben unverändert, aber mit modernem Styling -->
|
||||
{% if active_tab == 'system' %}
|
||||
<div id="system-tab" class="tab-pane active">
|
||||
<div class="content-card-modern">
|
||||
<h3 class="text-3xl font-bold text-slate-900 dark:text-white mb-8">System-Übersicht</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-8">
|
||||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white mb-6">System Ressourcen</h3>
|
||||
<div class="form-modern">
|
||||
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">System Ressourcen</h4>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="text-slate-700 dark:text-slate-300">CPU Auslastung</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.cpu }}%</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.cpu if system_info else '0' }}%</span>
|
||||
</div>
|
||||
<div class="progress-bar-modern">
|
||||
<div class="progress-fill-modern" style="width: {{ system_info.cpu if system_info else '0' }}%"></div>
|
||||
</div>
|
||||
{{ render_progress_bar(system_info.cpu, 'blue')|safe }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="text-slate-700 dark:text-slate-300">Arbeitsspeicher</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.memory }}%</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.memory if system_info else '0' }}%</span>
|
||||
</div>
|
||||
<div class="progress-bar-modern">
|
||||
<div class="progress-fill-modern" style="width: {{ system_info.memory if system_info else '0' }}%"></div>
|
||||
</div>
|
||||
{{ render_progress_bar(system_info.memory, 'green')|safe }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between mb-2">
|
||||
<span class="text-slate-700 dark:text-slate-300">Festplattenspeicher</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.disk }}%</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.disk if system_info else '0' }}%</span>
|
||||
</div>
|
||||
{{ render_progress_bar(system_info.disk, 'purple')|safe }}
|
||||
</div>
|
||||
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-slate-700 dark:text-slate-300">System Uptime</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.uptime }} Tage</span>
|
||||
<div class="progress-bar-modern">
|
||||
<div class="progress-fill-modern" style="width: {{ system_info.disk if system_info else '0' }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-8">
|
||||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white mb-6">Dienste Status</h3>
|
||||
|
||||
<div class="form-modern">
|
||||
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Dienste Status</h4>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg">
|
||||
<div class="flex justify-between items-center p-4 bg-white dark:bg-slate-700 rounded-lg">
|
||||
<div class="flex items-center">
|
||||
<div class="status-indicator status-running"></div>
|
||||
<span class="ml-3 text-slate-900 dark:text-white">Webserver</span>
|
||||
<div class="status-indicator-modern status-online-modern"></div>
|
||||
<span class="text-slate-900 dark:text-white font-medium">Webserver</span>
|
||||
</div>
|
||||
<span class="text-green-600 dark:text-green-400 font-medium">Online</span>
|
||||
<span class="text-green-600 dark:text-green-400 font-semibold">Online</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg">
|
||||
<div class="flex justify-between items-center p-4 bg-white dark:bg-slate-700 rounded-lg">
|
||||
<div class="flex items-center">
|
||||
<div class="status-indicator {{ 'status-running' if scheduler_status.running else 'status-stopped' }}"></div>
|
||||
<span class="ml-3 text-slate-900 dark:text-white">Scheduler</span>
|
||||
<div class="status-indicator-modern {{ 'status-online-modern' if scheduler_status.running else 'status-offline-modern' }}"></div>
|
||||
<span class="text-slate-900 dark:text-white font-medium">Scheduler</span>
|
||||
</div>
|
||||
<span class="{{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }} font-medium">
|
||||
<span class="{{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }} font-semibold">
|
||||
{{ 'Online' if scheduler_status.running else 'Offline' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg">
|
||||
<div class="flex justify-between items-center p-4 bg-white dark:bg-slate-700 rounded-lg">
|
||||
<div class="flex items-center">
|
||||
<div class="status-indicator status-running"></div>
|
||||
<span class="ml-3 text-slate-900 dark:text-white">Datenbank</span>
|
||||
<div class="status-indicator-modern status-online-modern"></div>
|
||||
<span class="text-slate-900 dark:text-white font-medium">Datenbank</span>
|
||||
</div>
|
||||
<span class="text-green-600 dark:text-green-400 font-medium">Online</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg">
|
||||
<div class="flex items-center">
|
||||
<div class="status-indicator status-running"></div>
|
||||
<span class="ml-3 text-slate-900 dark:text-white">Drucker-Manager</span>
|
||||
</div>
|
||||
<span class="text-green-600 dark:text-green-400 font-medium">Online</span>
|
||||
<span class="text-green-600 dark:text-green-400 font-semibold">Online</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<form action="{{ url_for('admin_page', tab='system') }}" method="get">
|
||||
<button type="submit" class="btn btn-primary w-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<button type="submit" class="btn-modern w-full justify-center">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||
</svg>
|
||||
System-Informationen aktualisieren
|
||||
@ -416,72 +604,98 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Logs Tab -->
|
||||
{% if active_tab == 'logs' %}
|
||||
<div id="logs-tab" class="tab-pane active">
|
||||
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-8 transition-colors">
|
||||
|
||||
<!-- Scheduler Tab -->
|
||||
{% if active_tab == 'scheduler' %}
|
||||
<div class="content-card-modern">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
|
||||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">System Logs</h3>
|
||||
<h3 class="text-3xl font-bold text-slate-900 dark:text-white">Scheduler Verwaltung</h3>
|
||||
<div class="flex space-x-4">
|
||||
<form action="{{ url_for('admin_page', tab='logs') }}" method="get">
|
||||
<div class="flex space-x-2">
|
||||
<select name="log_level" class="form-select rounded-lg border-light-border dark:border-dark-border text-slate-900 dark:text-white bg-white dark:bg-slate-800">
|
||||
<option value="all" {{ 'selected' if request.args.get('log_level') == 'all' or not request.args.get('log_level') else '' }}>Alle Level</option>
|
||||
<option value="ERROR" {{ 'selected' if request.args.get('log_level') == 'ERROR' else '' }}>Error</option>
|
||||
<option value="WARNING" {{ 'selected' if request.args.get('log_level') == 'WARNING' else '' }}>Warning</option>
|
||||
<option value="INFO" {{ 'selected' if request.args.get('log_level') == 'INFO' else '' }}>Info</option>
|
||||
<option value="DEBUG" {{ 'selected' if request.args.get('log_level') == 'DEBUG' else '' }}>Debug</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-secondary flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||
<form action="{{ url_for('start_scheduler') }}" method="post" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn-success-modern">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Aktualisieren
|
||||
Scheduler starten
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ url_for('stop_scheduler') }}" method="post" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn-danger-modern">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
||||
</svg>
|
||||
Scheduler stoppen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-slate-50 dark:bg-slate-800 rounded-lg p-4 max-h-[600px] overflow-y-auto">
|
||||
{% if logs %}
|
||||
{% for log in logs %}
|
||||
<div class="log-entry log-{{ log.level|lower }}">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="min-w-[160px] text-xs text-slate-500 dark:text-slate-400">{{ log.timestamp }}</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="badge badge-{{ 'success' if log.level == 'INFO' else ('warning' if log.level == 'WARNING' else ('error' if log.level == 'ERROR' else ('info' if log.level == 'DEBUG' else 'error'))) }}">
|
||||
{{ log.level }}
|
||||
|
||||
<div class="form-modern">
|
||||
<div class="flex items-center justify-between p-6 mb-6 bg-white dark:bg-slate-700 rounded-xl">
|
||||
<div class="flex items-center">
|
||||
<div class="status-indicator-modern {{ 'status-online-modern' if scheduler_status.running else 'status-offline-modern' }}"></div>
|
||||
<span class="text-2xl font-semibold ml-3 {{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }}">
|
||||
{{ 'Aktiv' if scheduler_status.running else 'Inaktiv' }}
|
||||
</span>
|
||||
<span class="text-xs font-medium text-slate-700 dark:text-slate-300">[{{ log.category }}]</span>
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-slate-700 dark:text-slate-300">{{ log.message }}</div>
|
||||
<div class="text-slate-700 dark:text-slate-300 text-lg">{{ scheduler_status.message }}</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-slate-700 rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-4">Scheduler Informationen</h4>
|
||||
<div class="space-y-4 text-slate-700 dark:text-slate-300">
|
||||
<p>Der Scheduler ist verantwortlich für die automatische Zuweisung und Ausführung von Druckaufträgen. Er überwacht kontinuierlich den Status der Drucker und der anstehenden Jobs.</p>
|
||||
<ul class="list-disc list-inside space-y-2">
|
||||
<li>Der Scheduler sollte während des normalen Betriebs immer aktiv sein</li>
|
||||
<li>Bei Wartungsarbeiten kann der Scheduler vorübergehend deaktiviert werden</li>
|
||||
<li>Nach einem Neustart des Systems muss der Scheduler manuell gestartet werden</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-slate-700 dark:text-slate-300 p-4">Keine Logs gefunden.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Weitere Tabs können hier hinzugefügt werden -->
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<!-- Minimale JavaScript-Funktionalität -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Bestätigungen für Löschaktionen
|
||||
const confirmForms = document.querySelectorAll('form[onsubmit*="confirm"]');
|
||||
confirmForms.forEach(form => {
|
||||
form.onsubmit = function() {
|
||||
const message = this.getAttribute('onsubmit').match(/confirm\('([^']+)'\)/)[1];
|
||||
return confirm(message);
|
||||
};
|
||||
// Smooth animations for cards
|
||||
const cards = document.querySelectorAll('.stat-card-modern, .content-card-modern');
|
||||
cards.forEach((card, index) => {
|
||||
card.style.animationDelay = `${index * 0.1}s`;
|
||||
card.classList.add('animate-fade-in-up');
|
||||
});
|
||||
|
||||
// Enhanced form validation
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const inputs = form.querySelectorAll('input[required]');
|
||||
let isValid = true;
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (!input.value.trim()) {
|
||||
input.classList.add('border-red-500');
|
||||
isValid = false;
|
||||
} else {
|
||||
input.classList.remove('border-red-500');
|
||||
}
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
e.preventDefault();
|
||||
alert('Bitte füllen Sie alle erforderlichen Felder aus.');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Bildschirm-Blanking verhindern
|
||||
xset s off
|
||||
xset s noblank
|
||||
xset -dpms
|
||||
|
||||
# Mauszeiger ausblenden
|
||||
unclutter -idle 0.5 -root &
|
||||
|
||||
# Chromium-Crash-Dialoge unterdrücken
|
||||
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' \
|
||||
"$HOME/.config/chromium/Default/Preferences" 2>/dev/null || true
|
||||
sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' \
|
||||
"$HOME/.config/chromium/Default/Preferences" 2>/dev/null || true
|
||||
|
||||
# Hostname und IP ermitteln
|
||||
HOSTNAME=$(hostname -f)
|
||||
IP_ADDRESS=$(hostname -I | awk '{print $1}')
|
||||
|
||||
# Browser starten mit SSL-Warnung deaktiviert
|
||||
chromium-browser --kiosk --noerrdialogs --disable-infobars \
|
||||
--window-position=0,0 --ignore-certificate-errors \
|
||||
--app=https://${IP_ADDRESS}:5000/ &
|
@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# MYP Kiosk-Modus Einrichtungsskript
|
||||
|
||||
# Fehlerabbruch aktivieren
|
||||
set -e
|
||||
|
||||
echo "===== MYP Kiosk-Modus Einrichtungsskript ====="
|
||||
echo "Dieses Skript richtet MYP für den automatischen Start im Kiosk-Modus ein."
|
||||
echo ""
|
||||
|
||||
# 1. Benötigte Pakete installieren
|
||||
echo "1. Installiere benötigte Pakete..."
|
||||
sudo apt update
|
||||
sudo apt install -y python3 python3-pip python3-venv chromium-browser \
|
||||
unclutter xdotool xscreensaver git
|
||||
|
||||
# 2. Verzeichnis für MYP erstellen und Projekt kopieren
|
||||
echo "2. Kopiere MYP nach /opt/myp..."
|
||||
sudo mkdir -p /opt/myp
|
||||
sudo chown $USER:$USER /opt/myp
|
||||
|
||||
# Aktuelle Verzeichnisstruktur ermitteln
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
echo "Kopiere Dateien von $SCRIPT_DIR nach /opt/myp..."
|
||||
cp -r "$SCRIPT_DIR"/* /opt/myp/
|
||||
|
||||
# 3. Python-Umgebung und Abhängigkeiten einrichten
|
||||
echo "3. Richte Python-Umgebung ein..."
|
||||
cd /opt/myp
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 4. Systemd-Dienst für Flask-Backend einrichten
|
||||
echo "4. Richte Flask-Backend-Dienst ein..."
|
||||
sudo cp /opt/myp/myp.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable myp.service
|
||||
sudo systemctl start myp.service
|
||||
|
||||
# 5. Kiosk-Script einrichten
|
||||
echo "5. Richte Kiosk-Script ein..."
|
||||
cp /opt/myp/kiosk.sh /home/pi/
|
||||
chmod +x /home/pi/kiosk.sh
|
||||
|
||||
# 6. Systemd-User-Dienst für Kiosk einrichten
|
||||
echo "6. Richte Kiosk-Dienst ein..."
|
||||
mkdir -p /home/pi/.config/systemd/user
|
||||
cp /opt/myp/kiosk.service /home/pi/.config/systemd/user/
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable kiosk.service
|
||||
|
||||
# 7. Linger für den pi-Benutzer aktivieren
|
||||
echo "7. Aktiviere User-Linger für pi-Benutzer..."
|
||||
sudo loginctl enable-linger pi
|
||||
|
||||
# 8. Watchdog-Script einrichten
|
||||
echo "8. Richte Watchdog-Script ein..."
|
||||
cp /opt/myp/watchdog.sh /home/pi/
|
||||
chmod +x /home/pi/watchdog.sh
|
||||
|
||||
# 9. Cron-Job für Watchdog einrichten
|
||||
echo "9. Richte Cron-Job für Watchdog ein..."
|
||||
(crontab -l 2>/dev/null || echo "") | grep -v "watchdog.sh" | { cat; echo "*/5 * * * * /home/pi/watchdog.sh > /dev/null 2>&1"; } | crontab -
|
||||
|
||||
# 10. Automatischen Login einrichten
|
||||
echo "10. Automatischer Login wird manuell über raspi-config eingerichtet"
|
||||
echo " Führe 'sudo raspi-config' aus und wähle:"
|
||||
echo " 1 System Options → S5 Boot/Auto Login → B4 Desktop Autologin"
|
||||
|
||||
# 11. Bildschirm nie ausschalten
|
||||
echo "11. Deaktiviere Bildschirmschoner..."
|
||||
sudo sed -i 's/#BLANK_TIME=.*/BLANK_TIME=0/' /etc/xdg/lxsession/LXDE-pi/autostart
|
||||
|
||||
echo ""
|
||||
echo "===== Installation abgeschlossen ====="
|
||||
echo "Um die Einrichtung zu vervollständigen, führe 'sudo raspi-config' aus"
|
||||
echo "und aktiviere den automatischen Login: "
|
||||
echo "1 System Options → S5 Boot/Auto Login → B4 Desktop Autologin"
|
||||
echo ""
|
||||
echo "Nach einem Neustart sollte der Raspberry Pi automatisch:"
|
||||
echo "1. Die MYP-Flask-Anwendung starten"
|
||||
echo "2. Den Chromium-Browser im Kiosk-Modus öffnen"
|
||||
echo ""
|
||||
echo "MYP ist erreichbar unter: http://localhost:5000/"
|
||||
echo ""
|
||||
echo "Ein Watchdog-Script überwacht alle 5 Minuten, ob Chromium und der MYP-Dienst"
|
||||
echo "noch laufen und startet sie bei Bedarf neu."
|
||||
echo ""
|
||||
echo "Starte den Raspberry Pi neu mit 'sudo reboot'"
|
@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# MYP Watchdog für Chromium Browser
|
||||
# Empfohlene Ausführung über crontab: */5 * * * * /home/pi/watchdog.sh > /dev/null 2>&1
|
||||
|
||||
# Funktion zum Loggen von Nachrichten
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> /home/pi/myp-watchdog.log
|
||||
}
|
||||
|
||||
# Prüfen, ob Chromium läuft
|
||||
if ! pgrep -x "chromium-browse" > /dev/null; then
|
||||
log "Chromium nicht gefunden - starte neu"
|
||||
|
||||
# Alle eventuell noch vorhandenen Chromium-Prozesse beenden
|
||||
pkill -f chromium || true
|
||||
|
||||
# Warten bis alle Prozesse beendet sind
|
||||
sleep 2
|
||||
|
||||
# Kiosk-Script neu starten
|
||||
/home/pi/kiosk.sh
|
||||
|
||||
log "Chromium neugestartet"
|
||||
else
|
||||
# Optional: Nur für Debug-Zwecke
|
||||
# log "Chromium läuft normal"
|
||||
:
|
||||
fi
|
||||
|
||||
# Prüfen, ob MYP Flask-Dienst läuft
|
||||
if ! systemctl is-active --quiet myp.service; then
|
||||
log "MYP Flask-Dienst ist nicht aktiv - starte neu"
|
||||
|
||||
# Dienst neustarten
|
||||
sudo systemctl restart myp.service
|
||||
|
||||
log "MYP Flask-Dienst neugestartet"
|
||||
fi
|
||||
|
||||
exit 0
|
Loading…
x
Reference in New Issue
Block a user