Login-Seite überarbeitet, TESM-Logo als echter Hintergrund; SSH-MOTD-Banner mit Live-Daten
- Login-Seite: WiS-/TESM-Logo und "Melde dich an, um fortzufahren" aus der Box entfernt; die TESM-Wortmarke mit Funktionszeile ist jetzt der tatsächliche Seitenhintergrund (absolut positioniert, hinter allem), die Box liegt zentriert davor. Nebenbei zwei echte Bugs gefunden und behoben: fehlendes Cache-Busting auf login.html (asset_url() wie in base.html, sonst hält nginx' 7-Tage-Cache eine alte style.css/app.js fest) und ein durch den fehlenden align-items-Wert auf die volle Höhe gestrecktes Login-Formular. - Neuer SSH-Login-Banner (dynamisches MOTD) mit Live-Flottenstatus statt der Standard-Ubuntu-Willkommensmeldung: srv/tesm/motd_banner.py liest Online/Offline- und DHCP-Status aus denselben Quellen wie das Web-Dashboard (get_device_status()/_dhcp_read_leases()), eingebunden über etc/update-motd.d/50-tesm. install.sh deaktiviert dabei die Standard- Ubuntu-MOTD-Skripte (chmod -x, reversibel) und installiert den neuen Hook.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Live-Flotten-Banner für die SSH-Login-Message (MOTD).
|
||||
|
||||
Liest denselben Live-Status wie das Web-Dashboard, direkt aus den
|
||||
gleichen Quellen (siehe app.py):
|
||||
- Online/Offline: get_device_status() parst dieselbe Check-Log-Datei
|
||||
(live.log) wie das Dashboard, statt selbst zu pingen -- ein Login
|
||||
soll niemals eine eigene Prüfung anstoßen.
|
||||
- DHCP: _detect_dhcp_status()/_dhcp_read_leases() lesen Dienststatus
|
||||
bzw. Kea-Lease-CSV direkt, kein eigener Zustand.
|
||||
|
||||
Eingebunden über /etc/update-motd.d/50-tesm, das dieses Skript per
|
||||
venv/bin/python3 aufruft -- app.py importiert Flask u.a., die nur in
|
||||
der venv installiert sind (dasselbe Muster wie generate_ips.py, das
|
||||
poe.sh auf demselben Weg konsumiert).
|
||||
|
||||
Bewusst NICHT enthalten: PXE-Status. Anders als überwachen/booten/
|
||||
DHCP ist PXE (noch) keine echte, mit Daten hinterlegte Funktion der
|
||||
App (bislang nur Teil der Tagline) -- ein erfundener "PXE: idle"-Wert
|
||||
wäre keine Live-Daten, sondern eine Behauptung.
|
||||
"""
|
||||
from app import _detect_dhcp_status, _dhcp_read_leases, get_db_connection, get_device_status
|
||||
|
||||
AMBER = "\033[38;2;226;166;60m"
|
||||
DIM = "\033[2m"
|
||||
BOLD = "\033[1m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
|
||||
def _fleet_line(amber, dim, bold, reset):
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
devices = conn.execute(
|
||||
"SELECT mac, name, is_active FROM devices WHERE deleted_at IS NULL"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
status, _, _, _ = get_device_status(devices)
|
||||
active = [d for d in devices if d["is_active"]]
|
||||
online = sum(1 for d in active if status.get(d["mac"]) == "online")
|
||||
down = len(active) - online
|
||||
return f"{bold}{online}{reset} Clients online · {bold}{down}{reset} down"
|
||||
except Exception:
|
||||
return f"{dim}Geräte-Status nicht verfügbar{reset}"
|
||||
|
||||
|
||||
def _dhcp_line():
|
||||
try:
|
||||
dhcp = _detect_dhcp_status()
|
||||
if not dhcp["installed"]:
|
||||
return "nicht installiert"
|
||||
if not dhcp["active"]:
|
||||
return "installiert, gestoppt"
|
||||
leases = _dhcp_read_leases()
|
||||
return f"{len(leases)} Leases aktiv" if leases is not None else "läuft, keine Lease-Datei"
|
||||
except Exception:
|
||||
return "Status nicht verfügbar"
|
||||
|
||||
|
||||
def main():
|
||||
# KEIN sys.stdout.isatty()-Check: dieses Skript schreibt beim Login
|
||||
# nicht direkt auf ein Terminal, sondern über run-parts in
|
||||
# /run/motd.dynamic (eine normale Datei -- isatty() wäre hier immer
|
||||
# False). Der eigentliche Betrachter ist trotzdem ein echtes Terminal
|
||||
# (die SSH-Session, der PAM den Inhalt danach ausgibt), daher immer
|
||||
# Farbe ausgeben.
|
||||
amber, dim, bold, reset = AMBER, DIM, BOLD, RESET
|
||||
|
||||
print(f"{bold}{amber}TESM{reset} · TimEShepManager")
|
||||
print(f"{dim}überwachen · booten · anbinden (DHCP) · Betriebssystem geben (PXE){reset}")
|
||||
print()
|
||||
print(f"{_fleet_line(amber, dim, bold, reset)} · DHCP: {_dhcp_line()}")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
# Ein MOTD-Banner darf einen SSH-Login niemals stören -- lieber
|
||||
# gar keine Ausgabe als ein Traceback vor dem Shell-Prompt.
|
||||
pass
|
||||
@@ -999,28 +999,41 @@ select {
|
||||
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5vw;
|
||||
padding: 40px 20px;
|
||||
background:
|
||||
radial-gradient(circle at 15% 15%, rgba(255, 113, 0, 0.14), transparent 45%),
|
||||
radial-gradient(circle at 85% 85%, rgba(255, 113, 0, 0.08), transparent 40%),
|
||||
var(--bg);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* TESM-Wortmarke (mit Funktionszeile) als eigenes Panel neben der Login-Karte
|
||||
-- nebeneinander statt überlagert, damit beides gleichzeitig sichtbar
|
||||
bleibt. Auf schmalen Bildschirmen ausgeblendet (Platz reicht sonst nicht). */
|
||||
.login-branding { display: none; pointer-events: none; }
|
||||
.login-branding img { width: min(34vw, 520px); height: auto; }
|
||||
/* TESM-Wortmarke mit Funktionszeile als tatsächlicher Seitenhintergrund --
|
||||
absolut positioniert und hinter allem (z-index 0), kein eigenes Panel und
|
||||
kein bloßes "Logo über der Box"-Stacking mehr. Die Login-Box (z-index 1)
|
||||
liegt bewusst weit oben auf der Seite, dadurch sichtbar VOR dem oberen
|
||||
Teil des Hintergrundmotivs statt klassisch vertikal zentriert. */
|
||||
.login-bg {
|
||||
position: absolute;
|
||||
top: 4vh;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: min(72vw, 880px);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.login-bg img { width: 100%; height: auto; display: block; }
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.login-branding { display: block; }
|
||||
@media (max-width: 640px) {
|
||||
.login-bg { width: min(90vw, 460px); }
|
||||
}
|
||||
|
||||
.login-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
background: var(--bg-card);
|
||||
@@ -1030,11 +1043,7 @@ select {
|
||||
padding: 34px 32px 28px;
|
||||
}
|
||||
|
||||
.login-logo { display: flex; justify-content: center; margin-bottom: 22px; }
|
||||
.login-logo img { height: 34px; }
|
||||
|
||||
.login-card h1 { font-size: 18px; text-align: center; margin-bottom: 4px; }
|
||||
.login-card .login-sub { text-align: center; color: var(--text-faint); font-size: 12.5px; margin-bottom: 24px; }
|
||||
|
||||
/* ==========================================================================
|
||||
Logs page
|
||||
|
||||
@@ -5,23 +5,15 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Login · TESM</title>
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='images/icon-dark.svg') }}" id="app-favicon">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<link rel="stylesheet" href="{{ asset_url('css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="login-page">
|
||||
<div class="login-branding">
|
||||
<div class="login-bg">
|
||||
<img src="{{ url_for('static', filename='images/logo-dark-subline.svg') }}" alt="TESM" id="login-bg-logo">
|
||||
</div>
|
||||
<div class="login-card">
|
||||
<div class="login-logo">
|
||||
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="WiS">
|
||||
</div>
|
||||
<div style="display:flex; justify-content:center; margin-bottom:4px;">
|
||||
<img id="sidebar-logo" src="{{ url_for('static', filename='images/logo-dark.svg') }}" alt="TESM" style="height:32px; width:auto;">
|
||||
</div>
|
||||
<p class="login-sub">Melde dich an, um fortzufahren</p>
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="field" style="margin-bottom:18px;">
|
||||
@@ -46,6 +38,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||
<script src="{{ asset_url('js/app.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user