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
|
||||
from fastapi import Depends, FastAPI, Form, HTTPException, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
@ -26,6 +27,7 @@ OPTIONEN_FELDER = {
|
||||
|
||||
app = FastAPI(title="Tuxflotte Kundenplattform")
|
||||
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET, session_cookie="kundenplattform_session")
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
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>
|
||||
|
||||
{% if fehler %}
|
||||
<p class="fehler">{{ fehler }}</p>
|
||||
<p><mark>{{ fehler }}</mark></p>
|
||||
{% else %}
|
||||
{% for kategorie_name, eintraege in kategorien.items() %}
|
||||
<div class="kategorie">
|
||||
<h2>{{ kategorie_name }}</h2>
|
||||
<section>
|
||||
<h3>{{ kategorie_name }}</h3>
|
||||
<figure>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for eintrag in eintraege %}
|
||||
@ -23,15 +24,18 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if eintrag.state == "present" %}
|
||||
<form class="inline" method="post" action="/geraete/{{ device.id }}/auftragskatalog/{{ eintrag.merkmal }}/deselect">
|
||||
<button type="submit">Abwählen</button>
|
||||
<form method="post" action="/geraete/{{ device.id }}/auftragskatalog/{{ eintrag.merkmal }}/deselect">
|
||||
<button type="submit" class="secondary outline">Abwählen</button>
|
||||
</form>
|
||||
{% 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) %}
|
||||
<div class="optionen">
|
||||
{% 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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -43,7 +47,8 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</figure>
|
||||
</section>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@ -2,31 +2,27 @@
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Tuxflotte{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; max-width: 60rem; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
|
||||
header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2rem; border-bottom: 1px solid #ddd; padding-bottom: 0.5rem; }
|
||||
header a { color: inherit; text-decoration: none; }
|
||||
h1 { font-size: 1.2rem; margin: 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; }
|
||||
.state-present { color: var(--pico-ins-color); font-weight: 600; }
|
||||
.state-absent { color: var(--pico-muted-color); }
|
||||
.optionen label { font-weight: normal; }
|
||||
td form { margin-bottom: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a href="/geraete">Tuxflotte Kundenplattform</a></h1>
|
||||
{% if user %}
|
||||
<span>{{ user.email }} — <a href="/logout">Abmelden</a></span>
|
||||
{% endif %}
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul><li><strong><a href="/geraete">Tuxflotte Kundenplattform</a></strong></li></ul>
|
||||
{% if user %}
|
||||
<ul><li>{{ user.email }}</li><li><a href="/logout" role="button" class="secondary outline">Abmelden</a></li></ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</header>
|
||||
{% block content %}{% endblock %}
|
||||
<main class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
{% if not devices %}
|
||||
<p>Keine Geräte gefunden.</p>
|
||||
{% else %}
|
||||
<figure>
|
||||
<table>
|
||||
<thead>
|
||||
<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><code>{{ device.device_fingerprint }}</code></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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Anmelden — Tuxflotte{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Anmelden</h2>
|
||||
{% if error %}<p class="fehler">{{ error }}</p>{% endif %}
|
||||
<form method="post" action="/login">
|
||||
<p><label>E-Mail<br><input type="email" name="email" required autofocus></label></p>
|
||||
<p><label>Passwort<br><input type="password" name="password" required></label></p>
|
||||
<button type="submit">Anmelden</button>
|
||||
</form>
|
||||
<article style="max-width: 24rem; margin: 3rem auto;">
|
||||
<h2>Anmelden</h2>
|
||||
{% if error %}<p><mark>{{ error }}</mark></p>{% endif %}
|
||||
<form method="post" action="/login">
|
||||
<label for="email">E-Mail</label>
|
||||
<input type="email" id="email" name="email" required autofocus>
|
||||
<label for="password">Passwort</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
<button type="submit">Anmelden</button>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user