admin_mandanten ergänzt
This commit is contained in:
parent
5a322aa403
commit
1d0256d81a
@ -5,6 +5,7 @@ from functools import wraps
|
|||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask,
|
Flask,
|
||||||
redirect,
|
redirect,
|
||||||
@ -13,6 +14,7 @@ from flask import (
|
|||||||
send_from_directory,
|
send_from_directory,
|
||||||
session,
|
session,
|
||||||
url_for,
|
url_for,
|
||||||
|
abort,
|
||||||
)
|
)
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
@ -145,15 +147,46 @@ def ensure_default_admin():
|
|||||||
""", ("KOLB", "Kolb Compliance", "info@kolb.cc", 0))
|
""", ("KOLB", "Kolb Compliance", "info@kolb.cc", 0))
|
||||||
mandant_id = cur.fetchone()[0]
|
mandant_id = cur.fetchone()[0]
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT id FROM app_group
|
||||||
|
WHERE mandant_id = %s AND group_name = %s
|
||||||
|
""", (1, "Administratoren"))
|
||||||
|
group_row = cur.fetchone()
|
||||||
|
|
||||||
|
if group_row:
|
||||||
|
admin_group_id = group_row[0]
|
||||||
|
else:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO app_group (mandant_id, group_name)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
RETURNING id
|
||||||
|
""", (1, "Administratoren"))
|
||||||
|
admin_group_id = cur.fetchone()[0]
|
||||||
|
|
||||||
cur.execute("SELECT id FROM app_user WHERE email = %s", ("admin@kolb.cc",))
|
cur.execute("SELECT id FROM app_user WHERE email = %s", ("admin@kolb.cc",))
|
||||||
user_row = cur.fetchone()
|
user_row = cur.fetchone()
|
||||||
|
|
||||||
if not user_row:
|
if user_row:
|
||||||
|
admin_user_id = user_row[0]
|
||||||
|
else:
|
||||||
password_hash = generate_password_hash("topsecret")
|
password_hash = generate_password_hash("topsecret")
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
INSERT INTO app_user (email, name, mandant_id, password_hash, status)
|
INSERT INTO app_user (email, name, mandant_id, password_hash, status)
|
||||||
VALUES (%s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s)
|
||||||
""", ("admin@kolb.cc", "Admin", mandant_id, password_hash, 1))
|
RETURNING id
|
||||||
|
""", ("admin@kolb.cc", "Admin", 1, password_hash, 1))
|
||||||
|
admin_user_id = cur.fetchone()[0]
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT 1 FROM user_group
|
||||||
|
WHERE user_id = %s AND group_id = %s AND mandant_id = %s
|
||||||
|
""", (admin_user_id, admin_group_id, 1))
|
||||||
|
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO user_group (user_id, group_id, mandant_id)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (admin_user_id, admin_group_id, 1))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cur.close()
|
cur.close()
|
||||||
@ -186,6 +219,68 @@ def get_current_user():
|
|||||||
"is_logged_in": bool(session.get("user_id")),
|
"is_logged_in": bool(session.get("user_id")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def fetchone_dict(cur):
|
||||||
|
row = cur.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
|
columns = [desc[0] for desc in cur.description]
|
||||||
|
return dict(zip(columns, row))
|
||||||
|
|
||||||
|
|
||||||
|
def fetchall_dict(cur):
|
||||||
|
rows = cur.fetchall()
|
||||||
|
columns = [desc[0] for desc in cur.description]
|
||||||
|
return [dict(zip(columns, row)) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def user_is_admin():
|
||||||
|
user_id = session.get("user_id")
|
||||||
|
if not user_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT 1
|
||||||
|
FROM app_user u
|
||||||
|
JOIN user_group ug ON ug.user_id = u.id
|
||||||
|
JOIN app_group g ON g.id = ug.group_id
|
||||||
|
WHERE u.id = %s
|
||||||
|
AND ug.mandant_id = 1
|
||||||
|
AND g.mandant_id = 1
|
||||||
|
AND g.group_name = 'Administratoren'
|
||||||
|
LIMIT 1
|
||||||
|
""", (user_id,))
|
||||||
|
|
||||||
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return result is not None
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_user():
|
||||||
|
return {
|
||||||
|
"user_id": session.get("user_id"),
|
||||||
|
"user_name": session.get("user_name"),
|
||||||
|
"user_email": session.get("user_email"),
|
||||||
|
"is_logged_in": bool(session.get("user_id")),
|
||||||
|
"is_admin": user_is_admin() if session.get("user_id") else False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def admin_required(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if not session.get("user_id"):
|
||||||
|
return redirect(url_for("login", next=request.path))
|
||||||
|
if not user_is_admin():
|
||||||
|
abort(403)
|
||||||
|
return view_func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def login_required(view_func):
|
def login_required(view_func):
|
||||||
@wraps(view_func)
|
@wraps(view_func)
|
||||||
@ -324,4 +419,113 @@ def serve_style(filename):
|
|||||||
|
|
||||||
@app.route("/files/<path:filename>")
|
@app.route("/files/<path:filename>")
|
||||||
def serve_file(filename):
|
def serve_file(filename):
|
||||||
return send_from_directory("/app/files", filename)
|
return send_from_directory("/app/files", filename)
|
||||||
|
|
||||||
|
#temporär
|
||||||
|
@app.route("/pwd/<password>/<key>")
|
||||||
|
def generate_pwd_hash(password, key):
|
||||||
|
if key != "geheim":
|
||||||
|
return "Forbidden", 403
|
||||||
|
|
||||||
|
return f"<pre>{generate_password_hash(password)}</pre>"
|
||||||
|
|
||||||
|
@app.route("/profil")
|
||||||
|
@login_required
|
||||||
|
def profil():
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT u.id, u.email, u.name, u.mandant_id, u.last_login, u.status,
|
||||||
|
m.name AS mandant_name, m.kuerzel AS mandant_kuerzel
|
||||||
|
FROM app_user u
|
||||||
|
JOIN mandant m ON m.id = u.mandant_id
|
||||||
|
WHERE u.id = %s
|
||||||
|
""", (session["user_id"],))
|
||||||
|
|
||||||
|
user_data = fetchone_dict(cur)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"profil.html",
|
||||||
|
page_title="Profil",
|
||||||
|
active_page="profil",
|
||||||
|
profile=user_data,
|
||||||
|
**get_current_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route("/admin/mandanten", methods=["GET", "POST"])
|
||||||
|
@admin_required
|
||||||
|
def admin_mandanten():
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
action = request.form.get("action")
|
||||||
|
|
||||||
|
if action == "create":
|
||||||
|
kuerzel = request.form.get("kuerzel", "").strip()
|
||||||
|
name = request.form.get("name", "").strip()
|
||||||
|
kontakt_email = request.form.get("kontakt_email", "").strip()
|
||||||
|
level = request.form.get("level", "0").strip()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO mandant (kuerzel, name, kontakt_email, level)
|
||||||
|
VALUES (%s, %s, %s, %s)
|
||||||
|
""", (kuerzel, name, kontakt_email or None, int(level or 0)))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
elif action == "update":
|
||||||
|
mandant_id = request.form.get("id")
|
||||||
|
kuerzel = request.form.get("kuerzel", "").strip()
|
||||||
|
name = request.form.get("name", "").strip()
|
||||||
|
kontakt_email = request.form.get("kontakt_email", "").strip()
|
||||||
|
level = request.form.get("level", "0").strip()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE mandant
|
||||||
|
SET kuerzel = %s,
|
||||||
|
name = %s,
|
||||||
|
kontakt_email = %s,
|
||||||
|
level = %s
|
||||||
|
WHERE id = %s
|
||||||
|
""", (kuerzel, name, kontakt_email or None, int(level or 0), int(mandant_id)))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
elif action == "delete":
|
||||||
|
mandant_id = request.form.get("id")
|
||||||
|
cur.execute("DELETE FROM mandant WHERE id = %s", (int(mandant_id),))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return redirect(url_for("admin_mandanten"))
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT id, kuerzel, name, kontakt_email, level
|
||||||
|
FROM mandant
|
||||||
|
ORDER BY id
|
||||||
|
""")
|
||||||
|
mandanten = fetchall_dict(cur)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"admin_mandanten.html",
|
||||||
|
page_title="Admin - Mandanten",
|
||||||
|
active_page="admin",
|
||||||
|
mandanten=mandanten,
|
||||||
|
**get_current_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.errorhandler(403)
|
||||||
|
def forbidden(_error):
|
||||||
|
return render_template(
|
||||||
|
"403.html",
|
||||||
|
page_title="Kein Zugriff",
|
||||||
|
active_page="",
|
||||||
|
**get_current_user()
|
||||||
|
), 403
|
||||||
18
app/flask-postgres/app/templates/403.html
Normal file
18
app/flask-postgres/app/templates/403.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ page_title }}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="content-area">
|
||||||
|
<section class="content-box">
|
||||||
|
<h1>Kein Zugriff</h1>
|
||||||
|
<p>Sie haben keine Berechtigung für diese Seite.</p>
|
||||||
|
<p><a href="/home">Zurück zur Startseite</a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
116
app/flask-postgres/app/templates/admin_mandanten.html
Normal file
116
app/flask-postgres/app/templates/admin_mandanten.html
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ page_title }}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner">
|
||||||
|
<div class="logo-area">
|
||||||
|
<a href="/home">
|
||||||
|
<img src="/images/Logo-Compliance-Verification-bg-1.png" alt="Logo" class="site-logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a href="/home">Home</a>
|
||||||
|
<a href="/preise">Preise</a>
|
||||||
|
<a href="/allgemein">Allgemein</a>
|
||||||
|
|
||||||
|
{% if is_logged_in %}
|
||||||
|
<div class="user-menu">
|
||||||
|
<button class="user-menu-toggle" type="button">{{ user_name }} ▾</button>
|
||||||
|
<div class="user-menu-dropdown">
|
||||||
|
<a href="/profil">Profil</a>
|
||||||
|
{% if is_admin %}
|
||||||
|
<a href="/admin/mandanten">Admin</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="content-area">
|
||||||
|
<section class="content-box">
|
||||||
|
<h1>Mandantenverwaltung</h1>
|
||||||
|
|
||||||
|
<h2>Neuen Mandanten anlegen</h2>
|
||||||
|
<form method="post" class="admin-form">
|
||||||
|
<input type="hidden" name="action" value="create">
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Kürzel</label>
|
||||||
|
<input type="text" name="kuerzel" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Name</label>
|
||||||
|
<input type="text" name="name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Kontakt E-Mail</label>
|
||||||
|
<input type="email" name="kontakt_email">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Level</label>
|
||||||
|
<input type="number" name="level" value="0">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<button type="submit" class="btn-primary">Mandant anlegen</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Mandanten</h2>
|
||||||
|
|
||||||
|
{% for mandant in mandanten %}
|
||||||
|
<form method="post" class="admin-card-form">
|
||||||
|
<input type="hidden" name="id" value="{{ mandant.id }}">
|
||||||
|
|
||||||
|
<div class="admin-card">
|
||||||
|
<div class="form-row">
|
||||||
|
<label>ID</label>
|
||||||
|
<input type="text" value="{{ mandant.id }}" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Kürzel</label>
|
||||||
|
<input type="text" name="kuerzel" value="{{ mandant.kuerzel }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Name</label>
|
||||||
|
<input type="text" name="name" value="{{ mandant.name }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Kontakt E-Mail</label>
|
||||||
|
<input type="email" name="kontakt_email" value="{{ mandant.kontakt_email or '' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Level</label>
|
||||||
|
<input type="number" name="level" value="{{ mandant.level }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-actions">
|
||||||
|
<button type="submit" name="action" value="update" class="btn-primary">Speichern</button>
|
||||||
|
<button type="submit" name="action" value="delete" class="btn-danger" onclick="return confirm('Mandant wirklich löschen?')">Löschen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -15,18 +15,26 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="top-nav">
|
<nav class="top-nav">
|
||||||
<a href="/home" class="{% if active_page == 'home' %}active{% endif %}">Home</a>
|
<a href="/home" class="{% if active_page == 'home' %}active{% endif %}">Home</a>
|
||||||
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
|
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
|
||||||
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
|
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
|
||||||
|
|
||||||
{% if is_logged_in %}
|
{% if is_logged_in %}
|
||||||
<span class="user-box">{{ user_name }}</span>
|
<div class="user-menu">
|
||||||
<a href="/logout">Logout</a>
|
<button class="user-menu-toggle" type="button">{{ user_name }} ▾</button>
|
||||||
|
<div class="user-menu-dropdown">
|
||||||
|
<a href="/profil">Profil</a>
|
||||||
|
{% if is_admin %}
|
||||||
|
<a href="/admin/mandanten">Admin</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/login" class="{% if active_page == 'login' %}active{% endif %}">Login</a>
|
<a href="/login" class="{% if active_page == 'login' %}active{% endif %}">Login</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@ -16,15 +16,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="top-nav">
|
<nav class="top-nav">
|
||||||
<a href="/home">Home</a>
|
<a href="/home" class="{% if active_page == 'home' %}active{% endif %}">Home</a>
|
||||||
<a href="/preise">Preise</a>
|
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
|
||||||
<a href="/allgemein">Allgemein</a>
|
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
|
||||||
|
|
||||||
{% if is_logged_in %}
|
{% if is_logged_in %}
|
||||||
<span class="user-box">{{ user_name }}</span>
|
<div class="user-menu">
|
||||||
<a href="/logout">Logout</a>
|
<button class="user-menu-toggle" type="button">{{ user_name }} ▾</button>
|
||||||
|
<div class="user-menu-dropdown">
|
||||||
|
<a href="/profil">Profil</a>
|
||||||
|
{% if is_admin %}
|
||||||
|
<a href="/admin/mandanten">Admin</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/login" class="active">Login</a>
|
<a href="/login" class="{% if active_page == 'login' %}active{% endif %}">Login</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
56
app/flask-postgres/app/templates/profil.html
Normal file
56
app/flask-postgres/app/templates/profil.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ page_title }}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-inner">
|
||||||
|
<div class="logo-area">
|
||||||
|
<a href="/home">
|
||||||
|
<img src="/images/Logo-Compliance-Verification-bg-1.png" alt="Logo" class="site-logo">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="top-nav">
|
||||||
|
<a href="/home">Home</a>
|
||||||
|
<a href="/preise">Preise</a>
|
||||||
|
<a href="/allgemein">Allgemein</a>
|
||||||
|
|
||||||
|
{% if is_logged_in %}
|
||||||
|
<div class="user-menu">
|
||||||
|
<button class="user-menu-toggle" type="button">{{ user_name }} ▾</button>
|
||||||
|
<div class="user-menu-dropdown">
|
||||||
|
<a href="/profil">Profil</a>
|
||||||
|
{% if is_admin %}
|
||||||
|
<a href="/admin/mandanten">Admin</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="content-area">
|
||||||
|
<section class="content-box">
|
||||||
|
<h1>Profil</h1>
|
||||||
|
|
||||||
|
<table class="admin-table">
|
||||||
|
<tr><th>ID</th><td>{{ profile.id }}</td></tr>
|
||||||
|
<tr><th>Name</th><td>{{ profile.name }}</td></tr>
|
||||||
|
<tr><th>E-Mail</th><td>{{ profile.email }}</td></tr>
|
||||||
|
<tr><th>Mandant</th><td>{{ profile.mandant_name }} ({{ profile.mandant_kuerzel }})</td></tr>
|
||||||
|
<tr><th>Status</th><td>{{ profile.status }}</td></tr>
|
||||||
|
<tr><th>Letzter Login</th><td>{{ profile.last_login }}</td></tr>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
51
app/flask-postgres/deploy_flask.sh
Normal file
51
app/flask-postgres/deploy_flask.sh
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SRC_ROOT="/Volumes/MacBook SD/Projekte/compliance-verification/app/flask-postgres"
|
||||||
|
DST_ROOT="/Volumes/docker/flask-postgres"
|
||||||
|
|
||||||
|
NAS_USER="BKolb"
|
||||||
|
NAS_HOST="192.168.0.10"
|
||||||
|
CONTAINER_NAME="flask_web"
|
||||||
|
|
||||||
|
echo "Starte Deployment..."
|
||||||
|
|
||||||
|
[ -d "$SRC_ROOT/app" ] || { echo "Quelle app fehlt: $SRC_ROOT/app"; exit 1; }
|
||||||
|
[ -d "$SRC_ROOT/images" ] || { echo "Quelle images fehlt: $SRC_ROOT/images"; exit 1; }
|
||||||
|
[ -d "$SRC_ROOT/styles" ] || { echo "Quelle styles fehlt: $SRC_ROOT/styles"; exit 1; }
|
||||||
|
|
||||||
|
[ -d "$DST_ROOT/app" ] || { echo "Ziel app fehlt: $DST_ROOT/app"; exit 1; }
|
||||||
|
[ -d "$DST_ROOT/images" ] || { echo "Ziel images fehlt: $DST_ROOT/images"; exit 1; }
|
||||||
|
[ -d "$DST_ROOT/styles" ] || { echo "Ziel styles fehlt: $DST_ROOT/styles"; exit 1; }
|
||||||
|
|
||||||
|
echo "Synchronisiere app/ ..."
|
||||||
|
rsync -av --delete \
|
||||||
|
--exclude '.DS_Store' \
|
||||||
|
--exclude '._*' \
|
||||||
|
--exclude '__pycache__/' \
|
||||||
|
--exclude '*.pyc' \
|
||||||
|
--exclude 'images/' \
|
||||||
|
--exclude 'styles/' \
|
||||||
|
--exclude 'files/' \
|
||||||
|
--exclude 'Dockerfile.txt' \
|
||||||
|
"$SRC_ROOT/app/" "$DST_ROOT/app/"
|
||||||
|
|
||||||
|
echo "Synchronisiere images/ ..."
|
||||||
|
rsync -av --delete \
|
||||||
|
--exclude '.DS_Store' \
|
||||||
|
--exclude '._*' \
|
||||||
|
--exclude 'videos/' \
|
||||||
|
"$SRC_ROOT/images/" "$DST_ROOT/images/"
|
||||||
|
|
||||||
|
echo "Synchronisiere styles/ ..."
|
||||||
|
rsync -av --delete \
|
||||||
|
--exclude '.DS_Store' \
|
||||||
|
--exclude '._*' \
|
||||||
|
"$SRC_ROOT/styles/" "$DST_ROOT/styles/"
|
||||||
|
|
||||||
|
echo "files/ wird bewusst nicht angefasst."
|
||||||
|
|
||||||
|
echo "Starte Container manuell neu ..."
|
||||||
|
#ssh "${NAS_USER}@${NAS_HOST}" "/usr/bin/docker restart ${CONTAINER_NAME}"
|
||||||
|
|
||||||
|
echo "Deployment abgeschlossen."
|
||||||
@ -323,4 +323,99 @@ p {
|
|||||||
|
|
||||||
.check-list li {
|
.check-list li {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu-toggle {
|
||||||
|
background: #376da6;
|
||||||
|
color: #fff;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu-dropdown {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 44px;
|
||||||
|
min-width: 180px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu-dropdown a {
|
||||||
|
display: block;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: #1b2430;
|
||||||
|
text-decoration: none;
|
||||||
|
min-width: unset;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu-dropdown a:hover {
|
||||||
|
background: #eef4fb;
|
||||||
|
color: #0d2f57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu:hover .user-menu-dropdown {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-form,
|
||||||
|
.admin-card-form {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-card {
|
||||||
|
background: #f8fbff;
|
||||||
|
border: 1px solid #dce3ea;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #b62323;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-table th,
|
||||||
|
.admin-table td {
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid #dce3ea;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-table th {
|
||||||
|
width: 220px;
|
||||||
|
color: #0d2f57;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user