feat: Pico.css für Dark/Light-Mode und Responsivität
Klassenloses CSS-Framework (self-hosted unter static/, kein CDN), passt zum bestehenden Ansatz ohne JS-Build-Schritt. Bringt automatisches Dark/Light-Mode (prefers-color-scheme) und Responsivität ohne Zusatzaufwand. Templates auf Picos semantische Konventionen umgestellt (label/input-Paare, article für die Login-Karte, figure um Tabellen für horizontales Scrollen). Eigene CSS-Regeln auf die zwei Farb-Utility- Klassen (state-present/state-absent) reduziert, Rest kommt von Pico. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
21c6210869
commit
c35145ef8e
2
app.py
2
app.py
@ -6,6 +6,7 @@ import httpx
|
|||||||
import psycopg
|
import psycopg
|
||||||
from fastapi import Depends, FastAPI, Form, HTTPException, Request
|
from fastapi import Depends, FastAPI, Form, HTTPException, Request
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ OPTIONEN_FELDER = {
|
|||||||
|
|
||||||
app = FastAPI(title="Tuxflotte Kundenplattform")
|
app = FastAPI(title="Tuxflotte Kundenplattform")
|
||||||
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET, session_cookie="kundenplattform_session")
|
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET, session_cookie="kundenplattform_session")
|
||||||
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
static/pico.min.css
vendored
Normal file
4
static/pico.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -5,11 +5,12 @@
|
|||||||
<h2>Auftragskatalog: {{ device.hostname or device.device_fingerprint }}</h2>
|
<h2>Auftragskatalog: {{ device.hostname or device.device_fingerprint }}</h2>
|
||||||
|
|
||||||
{% if fehler %}
|
{% if fehler %}
|
||||||
<p class="fehler">{{ fehler }}</p>
|
<p><mark>{{ fehler }}</mark></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for kategorie_name, eintraege in kategorien.items() %}
|
{% for kategorie_name, eintraege in kategorien.items() %}
|
||||||
<div class="kategorie">
|
<section>
|
||||||
<h2>{{ kategorie_name }}</h2>
|
<h3>{{ kategorie_name }}</h3>
|
||||||
|
<figure>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for eintrag in eintraege %}
|
{% for eintrag in eintraege %}
|
||||||
@ -23,15 +24,18 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if eintrag.state == "present" %}
|
{% if eintrag.state == "present" %}
|
||||||
<form class="inline" method="post" action="/geraete/{{ device.id }}/auftragskatalog/{{ eintrag.merkmal }}/deselect">
|
<form method="post" action="/geraete/{{ device.id }}/auftragskatalog/{{ eintrag.merkmal }}/deselect">
|
||||||
<button type="submit">Abwählen</button>
|
<button type="submit" class="secondary outline">Abwählen</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form class="inline" method="post" action="/geraete/{{ device.id }}/auftragskatalog/{{ eintrag.merkmal }}/select">
|
<form method="post" action="/geraete/{{ device.id }}/auftragskatalog/{{ eintrag.merkmal }}/select">
|
||||||
{% if optionen_felder.get(eintrag.merkmal) %}
|
{% if optionen_felder.get(eintrag.merkmal) %}
|
||||||
<div class="optionen">
|
<div class="optionen">
|
||||||
{% for feld in optionen_felder[eintrag.merkmal] %}
|
{% for feld in optionen_felder[eintrag.merkmal] %}
|
||||||
<label><input type="checkbox" name="optionen.{{ feld.key }}"> {{ feld.label }}</label>
|
<label>
|
||||||
|
<input type="checkbox" name="optionen.{{ feld.key }}">
|
||||||
|
{{ feld.label }}
|
||||||
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -43,7 +47,8 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</figure>
|
||||||
|
</section>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -2,31 +2,27 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}Tuxflotte{% endblock %}</title>
|
<title>{% block title %}Tuxflotte{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="/static/pico.min.css">
|
||||||
<style>
|
<style>
|
||||||
body { font-family: system-ui, sans-serif; max-width: 60rem; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
|
.state-present { color: var(--pico-ins-color); font-weight: 600; }
|
||||||
header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2rem; border-bottom: 1px solid #ddd; padding-bottom: 0.5rem; }
|
.state-absent { color: var(--pico-muted-color); }
|
||||||
header a { color: inherit; text-decoration: none; }
|
.optionen label { font-weight: normal; }
|
||||||
h1 { font-size: 1.2rem; margin: 0; }
|
td form { margin-bottom: 0; }
|
||||||
table { width: 100%; border-collapse: collapse; }
|
|
||||||
th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
|
|
||||||
.kategorie { margin-top: 1.5rem; }
|
|
||||||
.kategorie h2 { font-size: 1rem; color: #555; margin-bottom: 0.3rem; }
|
|
||||||
.state-present { color: #1a7f37; font-weight: 600; }
|
|
||||||
.state-absent { color: #999; }
|
|
||||||
form.inline { display: inline; }
|
|
||||||
button { cursor: pointer; padding: 0.3rem 0.8rem; }
|
|
||||||
.fehler { background: #fee; border: 1px solid #f99; padding: 0.6rem; border-radius: 4px; margin-bottom: 1rem; }
|
|
||||||
.optionen label { font-weight: normal; font-size: 0.9rem; color: #555; display: block; margin: 0.2rem 0; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header class="container">
|
||||||
<h1><a href="/geraete">Tuxflotte Kundenplattform</a></h1>
|
<nav>
|
||||||
{% if user %}
|
<ul><li><strong><a href="/geraete">Tuxflotte Kundenplattform</a></strong></li></ul>
|
||||||
<span>{{ user.email }} — <a href="/logout">Abmelden</a></span>
|
{% if user %}
|
||||||
{% endif %}
|
<ul><li>{{ user.email }}</li><li><a href="/logout" role="button" class="secondary outline">Abmelden</a></li></ul>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
{% block content %}{% endblock %}
|
<main class="container">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
{% if not devices %}
|
{% if not devices %}
|
||||||
<p>Keine Geräte gefunden.</p>
|
<p>Keine Geräte gefunden.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<figure>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Hostname</th><th>Kennung</th><th>Letzter Check-in</th><th></th></tr>
|
<tr><th>Hostname</th><th>Kennung</th><th>Letzter Check-in</th><th></th></tr>
|
||||||
@ -15,10 +16,11 @@
|
|||||||
<td>{{ device.hostname or "—" }}</td>
|
<td>{{ device.hostname or "—" }}</td>
|
||||||
<td><code>{{ device.device_fingerprint }}</code></td>
|
<td><code>{{ device.device_fingerprint }}</code></td>
|
||||||
<td>{{ device.agent_last_checkin or "noch nie" }}</td>
|
<td>{{ device.agent_last_checkin or "noch nie" }}</td>
|
||||||
<td><a href="/geraete/{{ device.id }}">Auftragskatalog →</a></td>
|
<td><a href="/geraete/{{ device.id }}" role="button" class="outline">Auftragskatalog →</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</figure>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}Anmelden — Tuxflotte{% endblock %}
|
{% block title %}Anmelden — Tuxflotte{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Anmelden</h2>
|
<article style="max-width: 24rem; margin: 3rem auto;">
|
||||||
{% if error %}<p class="fehler">{{ error }}</p>{% endif %}
|
<h2>Anmelden</h2>
|
||||||
<form method="post" action="/login">
|
{% if error %}<p><mark>{{ error }}</mark></p>{% endif %}
|
||||||
<p><label>E-Mail<br><input type="email" name="email" required autofocus></label></p>
|
<form method="post" action="/login">
|
||||||
<p><label>Passwort<br><input type="password" name="password" required></label></p>
|
<label for="email">E-Mail</label>
|
||||||
<button type="submit">Anmelden</button>
|
<input type="email" id="email" name="email" required autofocus>
|
||||||
</form>
|
<label for="password">Passwort</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
<button type="submit">Anmelden</button>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user