Compare commits

..
4 Commits
Author SHA1 Message Date
florian.wilde af38a4efce Merge branch 'phase/017-favicons' 2026-09-17 23:46:21 +02:00
florian.wildeandClaude Opus 5 9c0cefcfe8 feat(ui): Symbole fuer Tab, Startbildschirm und Manifest (#017)
npm run icons erzeugt SVG, ICO und PNG aus den Vorlagen in assets/. Fuer kleine
Groessen kommen die vereinfachten Fassungen zum Einsatz.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:46:21 +02:00
florian.wilde a459d3f770 Merge branch 'phase/016-handbuch' 2026-09-17 23:43:19 +02:00
florian.wildeandClaude Opus 5 4423d17b15 docs: Handbuch mit Bildern und Skript zum Auffrischen (#016)
Elf Kapitel von der Installation bis zur Fehlersuche, 16 Bilder. Das Skript
meldet sich fuer die Aufnahmen selbst an, ohne Hintertuer im Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:43:19 +02:00
25 changed files with 584 additions and 35 deletions
+11
View File
@@ -17,6 +17,17 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
### Sicherheit
## [0.12.1] – 2026-09-17
### Hinzugefügt
- Symbole für Browser-Tab (SVG und ICO), iOS-Startbildschirm und Web-Manifest, erzeugt mit `npm run icons` aus den SVG-Vorlagen in `assets/` (#017)
## [0.12.0] – 2026-09-17
### Hinzugefügt
- Handbuch mit Bildern: Installation, Agent-Rollout, Zuweisung, Rechnungen, Warnungen, Benachrichtigungen, Exporte, Sicherung und Fehlersuche (#016)
- `npm run screenshots` erzeugt alle Bilder des Handbuchs neu, auch im dunklen Design und in der Mobilansicht (#016)
## [0.11.1] – 2026-09-17
### Behoben
+8 -2
View File
@@ -2,7 +2,7 @@
## Zeitübersicht
**Gesamt:** 6 h 38 min
**Gesamt:** 7 h 23 min
| Issue | Zeit |
|---|---|
@@ -22,6 +22,8 @@
| #013 Authentifizierung | 0 h 30 min |
| #014 Deployment | 0 h 40 min |
| #015 Feinschliff | 0 h 35 min |
| #016 Handbuch | 0 h 30 min |
| #017 Favicons | 0 h 15 min |
| Meilenstein | Zeit |
|---|---|
@@ -38,12 +40,13 @@
| v0.9.0 | 0 h 30 min | **abgeschlossen**
| v0.10.0 | 0 h 40 min | **abgeschlossen**
| v0.11.0 | 0 h 35 min | **abgeschlossen**
| v0.12.0 | 0 h 30 min | **abgeschlossen**
---
## 2026-09-17 · Sitzung 1 · 19:36–laufend
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
**Version:** 0.11.1
**Version:** 0.12.1
### Erledigt
- Auftrag gelesen, Rückfragen gestellt
@@ -84,6 +87,9 @@
- Meilenstein v0.10.0 abgeschlossen
- #015 abgeschlossen: Dark-Mode geprüft (Logo war unsichtbar – behoben), Mobilansicht ohne Querscrollen, Lasttest mit 200 Hosts und 365.000 Reports, Auswertung der Warnregeln von 3813 auf 293 ms beschleunigt, Sicherheitsdurchsicht
- Als v0.11.0 veröffentlicht statt 1.0.0: Feldtest, Handbuch, Favicons, CI und Verschlüsselung der Zugangsdaten stehen noch aus
- 0.11.1: Anmeldefehler behoben (Secure-Cookie über http)
- #016 abgeschlossen: Handbuch mit elf Kapiteln und 16 Bildern, dazu `npm run screenshots` zum Auffrischen
- #017 abgeschlossen: Symbole für Tab, Startbildschirm und Manifest, erzeugt mit `npm run icons` aus SVG-Vorlagen
### Entscheidungen
- **Warnungen sammelweise anlegen:** Einzelne Upserts kosteten bei 737 neuen Warnungen 3,8 s; mit `createMany` sind es 0,3 s.
+1
View File
@@ -173,6 +173,7 @@ SMTP und Webhook liegen (noch) unverschlüsselt in der Datenbank, eine Sicherung
## Dokumentation
- **[Handbuch mit Bildern](docs/anleitung/README.md)** – Installation, Agent-Rollout, Rechnungen, Warnungen, Fehlersuche
- [Architektur- und Umsetzungsplan](docs/architektur.md)
- [Issues](docs/issues/README.md)
- [Changelog](CHANGELOG.md) · [Devlog](DEVLOG.md)
+4
View File
@@ -0,0 +1,4 @@
<!-- Fassung für 16 px: nur das P. -->
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M6 2h21a14 14 0 0 1 0 28H18v16H6zM18 13h8a3 3 0 0 1 0 6h-8z" fill="#1E2A44"/>
</svg>

After

Width:  |  Height:  |  Size: 217 B

+5
View File
@@ -0,0 +1,5 @@
<!-- Fassung für 32 px: eine Linie, kräftigere Formen. -->
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M7 3h20a13 13 0 0 1 0 26H17v16H7zM17 12h9a4 4 0 0 1 0 8h-9z" fill="#1E2A44"/>
<path d="M24 38h19" stroke="#17A2B8" stroke-width="5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

+8
View File
@@ -0,0 +1,8 @@
<!-- Fassung für Startbildschirm-Symbole: Monogramm auf dunklem Grund mit Rand. -->
<svg viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" rx="34" fill="#1E2A44"/>
<g transform="translate(34 30) scale(2.3)">
<path fill-rule="evenodd" d="M8 4h19a12 12 0 0 1 0 24H16v16H8zM16 11h10a5 5 0 0 1 0 10H16z" fill="#FFFFFF"/>
<path d="M22 36h20M22 43h14" stroke="#17A2B8" stroke-width="3.5" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 473 B

+5
View File
@@ -0,0 +1,5 @@
<!-- ProxLedger-Monogramm, Standardfassung (heller Grund). Quelle für alle abgeleiteten Dateien. -->
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8 4h19a12 12 0 0 1 0 24H16v16H8zM16 11h10a5 5 0 0 1 0 10H16z" fill="#1E2A44"/>
<path d="M22 36h20M22 43h14" stroke="#17A2B8" stroke-width="3.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

+370
View File
@@ -0,0 +1,370 @@
# ProxLedger – Handbuch
ProxLedger zeigt auf einen Blick, welche Proxmox-Subscription zu welchem Host und zu welchem Kunden
gehört, wann sie ausläuft und ob die Rechnungen dazu erledigt sind. Auf jedem Host meldet ein kleiner
Agent einmal täglich seinen Status – das Dashboard fragt die Hosts nie aktiv ab und funktioniert deshalb
auch mit Hosts hinter NAT.
**Inhalt**
1. [Installation](#1-installation)
2. [Erste Anmeldung](#2-erste-anmeldung)
3. [Agent auf einem Host ausrollen](#3-agent-auf-einem-host-ausrollen)
4. [Hosts einem Kunden zuweisen](#4-hosts-einem-kunden-zuweisen)
5. [Kunden und Standorte pflegen](#5-kunden-und-standorte-pflegen)
6. [Laufzeiten und Rechnungen](#6-laufzeiten-und-rechnungen)
7. [Warnungen](#7-warnungen)
8. [Benachrichtigungen einrichten](#8-benachrichtigungen-einrichten)
9. [Exporte](#9-exporte)
10. [Sicherung und Aktualisierung](#10-sicherung-und-aktualisierung)
11. [Fehlersuche](#11-fehlersuche)
---
## 1. Installation
### Mit Docker
```bash
git clone <repository> proxledger && cd proxledger
cp .env.example .env
```
In der `.env` mindestens setzen:
| Wert | Bedeutung |
|---|---|
| `APP_SECRET` | Schlüssel für die Sitzungen, mindestens 32 Zeichen (`openssl rand -base64 48`) |
| `POSTGRES_PASSWORD` | Passwort der mitgelieferten Datenbank |
| `ADMIN_EMAIL`, `ADMIN_PASSWORD` | erster Zugang zum Dashboard |
| `APP_URL` | Adresse, unter der das Dashboard erreichbar ist |
Dann starten:
```bash
docker compose up -d
```
Beim ersten Start legt der Dienst `migrate` das Datenbankschema an und erzeugt den Administrator.
> **Wichtig:** `APP_URL` muss zur tatsächlichen Adresse passen. Davon hängt ab, ob das Sitzungs-Cookie als
> `Secure` markiert wird – bei falscher Angabe funktioniert die Anmeldung nicht.
### Auf einem Proxmox-Host als LXC-Container
```bash
bash deploy/lxc-installer.sh
```
Das Skript fragt Container-ID, Ressourcen und Netzwerk ab, legt einen Debian-Container an, installiert
Docker, holt den Quelltext, erzeugt Zufallspasswörter und startet alles. Am Ende stehen Adresse,
Benutzername und Passwort auf dem Bildschirm; das Passwort liegt zusätzlich in
`/root/proxledger-admin-passwort.txt` im Container.
Für den dauerhaften Betrieb gehört ein Reverse Proxy mit HTTPS davor – Beispiele für Caddy, nginx und
Traefik liegen in [`deploy/reverse-proxy/`](../../deploy/reverse-proxy/).
---
## 2. Erste Anmeldung
![Anmeldeseite](bilder/login.png)
Anmelden mit der E-Mail-Adresse und dem Passwort aus der `.env`. Ist ein Anmeldedienst über OIDC
eingerichtet (`OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`), erscheint zusätzlich ein Knopf
„Mit authentik anmelden“. Der lokale Zugang bleibt absichtlich immer nutzbar – falls der Anmeldedienst
einmal ausfällt, kommst du trotzdem hinein.
Nach der Anmeldung landest du auf dem Dashboard:
![Dashboard](bilder/dashboard.png)
Von oben nach unten:
- **Vier Kacheln** mit den Kennzahlen: gemeldete Hosts, aktiv lizenziert, Ablauf innerhalb von 30 Tagen,
offene Rechnungspunkte. Jede Kachel führt auf die passende gefilterte Liste.
- **Subscriptions nach Ablaufdatum:** die nächste Fälligkeit zuerst, mit Kunde, Key und beiden
Rechnungsständen. Rot bedeutet weniger als 14 Tage, gelb weniger als 30.
- **Aufgaben:** was konkret zu tun ist. Das Häkchen setzt den Rechnungsstatus direkt.
- **Rechnungsstatus je Kunde:** wie viele Punkte je Kunde erledigt sind.
---
## 3. Agent auf einem Host ausrollen
Zuerst im Dashboard unter *Einstellungen → Agent-Tokens* einen Enrollment-Token erzeugen:
![Agent-Tokens](bilder/tokens.png)
- **Bezeichnung:** wofür der Token gedacht ist, etwa „Rollout Brandl“.
- **Hosts direkt zuordnen zu:** Wird hier ein Kunde gewählt, gehören alle mit diesem Token registrierten
Hosts sofort zu diesem Kunden – die spätere Zuweisung entfällt.
- **Gültigkeit und Nutzungen** begrenzen den Schaden, falls der Token verloren geht.
Der Token wird **nur einmal angezeigt**. Direkt darunter steht der fertige Befehl. Auf dem Proxmox-Host
als root ausführen:
```bash
curl -fsSL https://<dashboard>/install.sh | bash -s -- --token plx_e_…
```
Der Installer erkennt selbst, ob es sich um Proxmox VE, Backup Server oder Mail Gateway handelt,
registriert den Host, legt die Konfiguration unter `/etc/proxledger/agent.conf` (Rechte 600) ab,
richtet einen systemd-Timer ein und schickt sofort den ersten Report.
Nützliche Befehle auf dem Host:
| Befehl | Zweck |
|---|---|
| `proxledger-agent --test` | Verbindung zum Dashboard prüfen |
| `proxledger-agent --dry-run` | Report anzeigen, ohne ihn zu senden |
| `proxledger-agent` | Report sofort senden |
| `journalctl -t proxledger -n 50` | Protokoll ansehen |
| `systemctl list-timers proxledger.timer` | nächsten Lauf anzeigen |
Der Timer läuft täglich mit bis zu vier Stunden Zufallsversatz, damit nicht alle Hosts gleichzeitig melden.
Ist das Dashboard nicht erreichbar, versucht es der Agent dreimal mit wachsendem Abstand und legt den
Report danach in `/var/lib/proxledger/spool/` ab; beim nächsten Lauf wird er nachgesendet.
**Entfernen:**
```bash
curl -fsSL https://<dashboard>/install.sh | bash -s -- --uninstall
```
---
## 4. Hosts einem Kunden zuweisen
Neu registrierte Hosts ohne Kundenbindung erscheinen als Hinweis auf dem Dashboard und unter
*Hosts → Nicht zugeordnete*:
![Nicht zugeordnete Hosts](bilder/nicht-zugeordnet.png)
Mehrere Hosts auswählen, Kunde und optional Standort wählen, zuweisen – fertig. Die zugehörige Warnung
schließt sich dabei von selbst.
In der Host-Tabelle findest du jeden Host wieder:
![Host-Tabelle](bilder/hosts.png)
Die Suche greift auf Hostname, FQDN, Subscription-Key, Server-ID, IP-Adresse, Kundenname **und
Rechnungsnummer** zu. Die Eingabe von `pve1c-a6bd21fcb6` führt also direkt zum passenden Host samt Kunde –
genau die Zuordnung, die im Proxmox-Shop fehlt.
Alle Filter stehen in der Adresse: Eine gefilterte Liste lässt sich als Lesezeichen speichern oder
weitergeben, und der Zurück-Knopf funktioniert wie erwartet.
---
## 5. Kunden und Standorte pflegen
![Kundenübersicht](bilder/kunden.png)
Die Übersicht zeigt je Kunde die Anzahl der Hosts, wie viele davon aktiv lizenziert sind, den nächsten
Ablauf, offene Warnungen und den Rechnungsstand.
![Kunden-Detail](bilder/kunde-detail.png)
In der Detailansicht stehen Stammdaten, Standorte, alle Hosts, alle Laufzeiten und die Summen pro Jahr
mit Marge. Die vier Kästen oben fassen Hosts, Einkauf, Verkauf und Marge zusammen.
Standorte sind optional und lohnen sich bei Kunden mit mehreren Filialen: Sie erscheinen in der
Host-Tabelle und lassen sich bei der Zuweisung mit angeben.
---
## 6. Laufzeiten und Rechnungen
Eine **Laufzeit** ist ein Lizenzjahr eines Hosts, zum Beispiel 24.09.2026 bis 23.09.2027. Dazu gehören
zwei Rechnungen: die **Proxmox-Rechnung** (Einkauf) und die **Kundenrechnung** (Verkauf). Die Differenz
ist die Marge.
Meldet ein Agent ein neues Ablaufdatum, legt ProxLedger die passende Laufzeit automatisch als Vorschlag
an – du trägst nur noch die Rechnungsdaten ein.
![Rechnungen](bilder/rechnungen.png)
In der Tabelle lassen sich die drei Häkchen direkt setzen:
- **Proxmox bezahlt** – setzt zugleich das Bezahldatum.
- **Kundenrechnung erstellt** – gibt es noch keine Rechnung, legt das Häkchen eine ohne Nummer an; Nummer
und Betrag trägst du später nach.
- **Vom Kunden bezahlt**
Für Details die Laufzeit öffnen:
![Laufzeit mit beiden Rechnungen](bilder/laufzeit-detail.png)
Dort stehen beide Rechnungen mit Nummer, Datum, Fälligkeit, Beträgen, Zahlungsart und PDF-Anhang. Ein
Vorschlag lässt sich hier bestätigen oder verwerfen; mit dem Erfassen einer Proxmox-Rechnung gilt er
automatisch als bestätigt.
### Sammelrechnungen
Eine Proxmox-Bestellung umfasst oft mehrere Keys, eine Kundenrechnung oft mehrere Hosts. Dafür braucht es
keinen eigenen Dialog: **Trägst du bei einer zweiten Laufzeit dieselbe Rechnungsnummer ein, wird sie als
weitere Position an die bestehende Rechnung gehängt.** Der Rechnungsbetrag ist immer die Summe der
Positionen und wird automatisch nachgeführt.
### Jahresbahn
![Jahresbahn](bilder/jahresbahn.png)
Die Jahresbahn zeigt zwölf Monate mit einer Linie für „heute“, gruppiert nach Kunde. Darunter stehen die
Abläufe der nächsten 90 Tage und die bereits abgelaufenen Subscriptions.
---
## 7. Warnungen
![Warnungen](bilder/warnungen.png)
Zehn Regeln laufen stündlich und zusätzlich nach jedem eingehenden Report:
| Regel | Meldet |
|---|---|
| Ablauf | Subscription läuft in 30, 14 oder 7 Tagen ab |
| Status | Subscription ist abgelaufen, ungültig, ausgesetzt oder fehlt |
| Sockets | lizenzierte und physische Sockets weichen ab |
| Keine Meldung | Host meldet sich seit über 48 Stunden nicht |
| Server-ID | Server-ID hat sich geändert (Hardwaretausch oder Neuinstallation) |
| Key mehrfach | derselbe Key wird von mehreren Hosts gemeldet |
| Ohne Kunde | Host ist keinem Kunden zugewiesen |
| Proxmox-Rechnung | unbezahlt und in sieben Tagen fällig oder überfällig |
| Kundenrechnung fehlt | Einkauf erfasst, Verkauf noch nicht erstellt |
| Kundenrechnung offen | seit über 30 Tagen nicht bezahlt |
Eine Warnung wird genau einmal geöffnet und **schließt sich selbst**, sobald die Ursache behoben ist.
*Quittieren* markiert sie als gesehen, *Schließen* beendet sie von Hand – besteht die Ursache weiter,
kommt sie beim nächsten Durchlauf zurück.
Schwellwerte und Ein-/Ausschalter stehen unter *Einstellungen → Warnregeln*:
![Warnregeln](bilder/warnregeln.png)
Mehrere Stufen werden mit Komma angegeben, etwa `30, 14, 7`. Nach dem Speichern wird sofort neu
ausgewertet.
---
## 8. Benachrichtigungen einrichten
![Benachrichtigungen](bilder/benachrichtigungen.png)
**E-Mail über Microsoft 365:** Server `smtp.office365.com`, Port 587, Benutzer und Absender identisch mit
dem Postfach. Das Passwort wird gespeichert und nie wieder angezeigt; ein leeres Feld beim Speichern lässt
den bisherigen Wert stehen.
**Webhook:** beliebige Adresse, die JSON entgegennimmt – damit lassen sich Matrix, Zabbix oder Home
Assistant anbinden. Mit hinterlegtem Geheimnis kommt die Signatur `X-ProxLedger-Signatur: sha256=…` mit,
damit der Empfänger die Echtheit prüfen kann.
**Zusammenfassung:** täglich oder wöchentlich um 07:00 Uhr mit offenen Warnungen, Rechnungspunkten und
bald ablaufenden Subscriptions.
Über *Test senden* lässt sich der Weg sofort prüfen; das Ergebnis steht im Versandprotokoll darunter.
Zu jeder Warnung wird nur einmal benachrichtigt. Schlägt der Versand fehl, wird er beim nächsten Durchlauf
erneut versucht.
---
## 9. Exporte
| Ansicht | CSV | PDF |
|---|---|---|
| Hosts | ✓ | ✓ |
| Kunden | ✓ | ✓ |
| Einzelner Kunde | – | ✓ (Anlage für Kundenunterlagen) |
| Rechnungen | ✓ | ✓ |
Alle Exporte übernehmen die gerade aktiven Filter und enthalten sämtliche Treffer, nicht nur die
angezeigte Seite. CSV kommt als UTF-8 mit BOM und Semikolon – Excel öffnet die Datei ohne Umwege, Umlaute
und Beträge stimmen.
Die PDF-Ausgaben tragen das einfarbige Logo und eignen sich als Anlage für Kundenunterlagen oder als
Jahresliste für die Buchhaltung.
---
## 10. Sicherung und Aktualisierung
**Sichern:**
```bash
./deploy/backup.sh /var/backups/proxledger
```
Das Skript sichert Datenbank, hochgeladene PDFs und die `.env`. Für regelmäßige Sicherungen in die
Crontab eintragen:
```
30 2 * * * /opt/proxledger/deploy/backup.sh /var/backups/proxledger
```
**Zurückspielen:**
```bash
gunzip -c datenbank-20260917-023000.sql.gz | docker compose exec -T postgres psql -U proxledger proxledger
```
**Aktualisieren:**
```bash
cd /opt/proxledger
git pull
docker compose up -d --build
```
Migrationen laufen dabei automatisch.
> `APP_SECRET` unbedingt mitsichern: Ohne diesen Wert sind alle Sitzungen ungültig. Und beachte, dass eine
> Sicherung die Zugangsdaten für SMTP und Webhook im Klartext enthält – sie gehört entsprechend geschützt.
---
## 11. Fehlersuche
**Ein Host meldet sich nicht.**
Auf dem Host `proxledger-agent --test` ausführen. Kommt keine Antwort, prüfen, ob das Dashboard vom Host
aus erreichbar ist (Firewall, DNS) und ob der Host-Token noch gültig ist – unter *Einstellungen →
Agent-Tokens* steht, wann er zuletzt benutzt wurde. Liegen Dateien in `/var/lib/proxledger/spool/`,
wartet der Agent auf eine Verbindung und sendet später nach.
**Nach der Anmeldung lande ich wieder auf der Anmeldeseite.**
Dann passt `APP_URL` nicht zur tatsächlichen Adresse: Steht dort `https://`, das Dashboard läuft aber über
`http://`, verwirft der Browser das Sitzungs-Cookie. `APP_URL` korrigieren und neu starten.
**Eine Warnung taucht immer wieder auf.**
Das ist beabsichtigt: Warnungen beschreiben einen Zustand. Solange die Ursache besteht, öffnet die nächste
Auswertung sie erneut. Passt der Schwellwert nicht, lässt er sich unter *Einstellungen → Warnregeln*
ändern, oder die Regel wird abgeschaltet.
**Server-ID hat sich geändert.**
Nach Hardwaretausch oder Neuinstallation vergibt Proxmox eine neue Server-ID. Der Key muss dann im
Proxmox-Shop neu zugewiesen werden. ProxLedger behält den Host samt Historie – die alte und die neue
Server-ID stehen in der Detailansicht unter *Subscription-Historie*.
**Was ist wann passiert?**
*Einstellungen → Protokoll* zeigt jede Änderung mit Zeitpunkt, Aktion und Benutzer.
![Protokoll](bilder/audit.png)
---
## Anhang: dunkles Design und Mobilansicht
Das Dashboard folgt der Einstellung des Betriebssystems und lässt sich über das Mondsymbol in der
Kopfleiste umschalten.
![Dashboard im dunklen Design](bilder/dashboard-dunkel.png)
Auf dem Handy klappt die Seitenleiste ein, und die großen Tabellen blenden gestaffelt Spalten aus, sodass
Host, Kunde, Status und Ablauf ohne seitliches Scrollen sichtbar bleiben.
![Host-Tabelle auf dem Handy](bilder/mobil-hosts.png)
---
*Die Bilder in diesem Handbuch entstehen mit `npm run screenshots -- --alle` aus einer laufenden
Installation mit Beispieldaten und sollten vor jeder Veröffentlichung aufgefrischt werden.*
Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

+18 -12
View File
@@ -1,8 +1,8 @@
# #016 Benutzerhandbuch mit Screenshots
**Status:** offen
**Meilenstein:** v0.10.0
**Status:** erledigt
**Meilenstein:** v0.12.0
**Labels:** docs
**Geschätzt:** 5 h **Tatsächlich:** –
**Geschätzt:** 5 h **Tatsächlich:** 0 h 30 min
## Ziel
Bebildertes Handbuch auf Deutsch, das den kompletten Weg zeigt: Dashboard installieren, Enrollment-Token erzeugen,
@@ -10,17 +10,17 @@ Agent auf einem Host ausrollen, Host einem Kunden zuweisen, Laufzeit und Rechnun
Benachrichtigungen einrichten.
## Aufgaben
- [ ] Gliederung `docs/anleitung/README.md` (Installation, Erste Schritte, Agent ausrollen, Kunden und Hosts, Rechnungen, Warnungen, Benachrichtigungen, Backup)
- [ ] Screenshots automatisiert erzeugen (`npm run screenshots`): Seiten anfahren, Aufnahmen nach `docs/anleitung/bilder/` schreiben
- [ ] Aufnahmen in Hell und Dunkel, dazu eine Mobilansicht
- [ ] Bilder mit Bildunterschriften und Alternativtexten einbinden
- [ ] Screenshots vor jedem Release auffrischen (Hinweis in der Release-Checkliste)
- [ ] Verweise aus README und Dashboard-Hilfe auf das Handbuch
- [x] Gliederung `docs/anleitung/README.md` (Installation, Erste Schritte, Agent ausrollen, Kunden und Hosts, Rechnungen, Warnungen, Benachrichtigungen, Backup)
- [x] Screenshots automatisiert erzeugen (`npm run screenshots`): Seiten anfahren, Aufnahmen nach `docs/anleitung/bilder/` schreiben
- [x] Aufnahmen in Hell und Dunkel, dazu eine Mobilansicht
- [x] Bilder mit Bildunterschriften und Alternativtexten einbinden
- [x] Screenshots vor jedem Release auffrischen (Hinweis in der Release-Checkliste)
- [x] Verweise aus README und Dashboard-Hilfe auf das Handbuch
## Akzeptanzkriterien
- [ ] Jemand ohne Vorkenntnisse kann anhand des Handbuchs Dashboard und Agent installieren
- [ ] Jeder Arbeitsschritt hat mindestens ein Bild
- [ ] Bilder zeigen Beispieldaten, keine echten Kundendaten
- [x] Jemand ohne Vorkenntnisse kann anhand des Handbuchs Dashboard und Agent installieren
- [x] Jeder Arbeitsschritt hat mindestens ein Bild
- [x] Bilder zeigen Beispieldaten, keine echten Kundendaten
## Abhängigkeiten
#014
@@ -31,3 +31,9 @@ Benachrichtigungen einrichten.
Das Profil braucht `user_pref("layout.css.prefers-color-scheme.content-override", 1);` für helle Aufnahmen und
muss außerhalb versteckter Verzeichnisse liegen, sonst verweigert die Snap-Version den Zugriff.
- Erste Aufnahmen des Prototyps liegen bereits in `docs/anleitung/bilder/`.
- Das Handbuch liegt unter `docs/anleitung/README.md` mit elf Kapiteln und 16 Bildern.
- `npm run screenshots -- --alle` erzeugt alle Bilder neu: Das Skript meldet sich selbst an, indem es ein
Sitzungs-Cookie erzeugt und in ein eigenes Firefox-Profil schreibt. Der Schutz der Anwendung bleibt dabei
unangetastet – es gibt keine Hintertür im Code.
- Ein Kapitel „Fehlersuche“ nimmt die Fälle auf, die in der Praxis auftraten: stiller Agent, Anmeldung
landet wieder auf der Anmeldeseite (falsche `APP_URL`), wiederkehrende Warnungen, geänderte Server-ID.
+20 -10
View File
@@ -1,23 +1,23 @@
# #017 Favicon-Dateien erzeugen
**Status:** offen
**Meilenstein:** v0.6.0
**Status:** erledigt
**Meilenstein:** v0.12.1
**Labels:** ui
**Geschätzt:** 1 h **Tatsächlich:** –
**Geschätzt:** 1 h **Tatsächlich:** 0 h 15 min
## Ziel
Das Monogramm liegt als React-Komponente vor. Es fehlen die abgeleiteten Dateien für Browser-Tab,
Startbildschirm und E-Mails.
## Aufgaben
- [ ] `favicon.svg` (32-px-Variante mit einer Linie) unter `src/app/icon.svg`
- [ ] `favicon.ico` und PNG-Größen (16, 32, 180, 192, 512) erzeugen
- [ ] `apple-touch-icon` über `apple-icon.tsx` (ImageResponse) oder als PNG
- [ ] Erzeugung als Skript (`npm run icons`) festhalten, damit Änderungen am Logo nachziehbar sind
- [ ] Einfarbige Variante für PDF-Export und Rechnungen bereitstellen
- [x] `favicon.svg` (32-px-Variante mit einer Linie) unter `src/app/icon.svg`
- [x] `favicon.ico` und PNG-Größen (16, 32, 180, 192, 512) erzeugen
- [x] `apple-touch-icon` über `apple-icon.tsx` (ImageResponse) oder als PNG
- [x] Erzeugung als Skript (`npm run icons`) festhalten, damit Änderungen am Logo nachziehbar sind
- [x] Einfarbige Variante für PDF-Export und Rechnungen bereitstellen
## Akzeptanzkriterien
- [ ] Browser-Tab zeigt das Monogramm in Hell und Dunkel
- [ ] Beim Ablegen auf dem iOS-Startbildschirm erscheint das Logo ohne weißen Rand
- [x] Browser-Tab zeigt das Monogramm in Hell und Dunkel
- [x] Beim Ablegen auf dem iOS-Startbildschirm erscheint das Logo ohne weißen Rand
## Abhängigkeiten
#002
@@ -25,3 +25,13 @@ Startbildschirm und E-Mails.
## Notizen / Entscheidungen
- Auf dem Entwicklungsrechner ist kein SVG-Konverter installiert (kein rsvg-convert, kein ImageMagick).
Entweder `sharp` als Entwicklungsabhängigkeit aufnehmen oder Next.js `ImageResponse` nutzen.
- Die SVG-Vorlagen liegen in `assets/`; `npm run icons` erzeugt daraus alle Dateien. Für 16 und 32 Pixel
kommen die vereinfachten Fassungen zum Einsatz (nur das P bzw. eine Linie), sonst verschwimmen die feinen
Linien.
- `favicon.ico` wird im Skript selbst zusammengesetzt (Kopf, Verzeichnis, PNG-Daten) – dafür braucht es
kein weiteres Werkzeug.
- Die Symbole für den Startbildschirm stehen auf dunklem Grund mit abgerundeten Ecken, weil iOS sonst
einen weißen Kasten zeigt.
- Dazu ein `manifest.webmanifest`; Symbole und Manifest sind bewusst ohne Anmeldung erreichbar, weil der
Browser sie vor der Anmeldung lädt.
- Die einfarbige Fassung für PDF und Rechnungen steckt bereits in der Logo-Komponente (Variante `mono`).
+4 -4
View File
@@ -19,10 +19,10 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
| [#013](013-authentifizierung.md) | Authentifizierung und Audit-Log | erledigt | v0.9.0 | 6 h | 0 h 30 min |
| [#014](014-deployment.md) | Deployment und Dokumentation | erledigt | v0.10.0 | 8 h | 0 h 40 min |
| [#015](015-feinschliff-v1.md) | Feinschliff für 1.0 | erledigt | v0.11.0 | 6 h | 0 h 35 min |
| [#016](016-anleitung-screenshots.md) | Benutzerhandbuch mit Screenshots | offen | v0.10.0 | 5 h | – |
| [#017](017-favicon-dateien.md) | Favicon-Dateien erzeugen | offen | v0.6.0 | 1 h | – |
| [#016](016-anleitung-screenshots.md) | Benutzerhandbuch mit Screenshots | erledigt | v0.12.0 | 5 h | 0 h 30 min |
| [#017](017-favicon-dateien.md) | Favicon-Dateien erzeugen | erledigt | v0.12.1 | 1 h | 0 h 15 min |
| [#018](018-agent-feldtest.md) | Agent auf echten Proxmox-Hosts prüfen | offen | v0.2.1 | 2 h | – |
| [#019](019-ci-pipeline.md) | CI-Pipeline | offen | v0.10.0 | 3 h | – |
| [#019](019-ci-pipeline.md) | CI-Pipeline | in Arbeit | v0.10.0 | 3 h | – |
| [#020](020-gitea-issue-abgleich.md) | Issues nach Gitea übertragen | erledigt | v0.4.1 | 2 h | 0 h 10 min |
| [#021](021-geheimnisse-verschluesseln.md) | Zugangsdaten verschlüsselt speichern | offen | v0.9.0 | 2 h | – |
| | **Summe** | | | **116 h** | **6 h 38 min** |
| | **Summe** | | | **116 h** | **7 h 23 min** |
+6 -5
View File
@@ -1,12 +1,12 @@
{
"name": "proxledger",
"version": "0.9.0",
"version": "0.12.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "proxledger",
"version": "0.9.0",
"version": "0.12.0",
"hasInstallScript": true,
"dependencies": {
"@node-rs/argon2": "^2.2.1",
@@ -37,6 +37,7 @@
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.9.7",
"prisma": "^7.10.0",
"sharp": "^0.35.4",
"tsx": "^4.23.13",
"typescript": "^5",
"vitest": "^5.0.1"
@@ -1013,8 +1014,8 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=18"
}
@@ -9117,8 +9118,8 @@
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
"devOptional": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/colour": "^1.1.0",
"detect-libc": "^2.1.2",
@@ -9167,8 +9168,8 @@
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"devOptional": true,
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver.js"
},
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "proxledger",
"version": "0.11.1",
"version": "0.12.1",
"private": true,
"scripts": {
"dev": "next dev",
@@ -24,7 +24,8 @@
"build:worker": "esbuild src/worker/index.ts --bundle --platform=node --target=node24 --format=esm --outfile=dist/worker.mjs --banner:js=\"$(cat deploy/worker-banner.js)\" --external:@prisma/client --external:@node-rs/argon2 --external:pg-native",
"build:all": "npm run build && npm run build:worker",
"lasttest": "tsx scripts/lasttest.ts",
"screenshots": "node scripts/screenshots.mjs"
"screenshots": "node scripts/screenshots.mjs",
"icons": "node scripts/icons.mjs"
},
"dependencies": {
"@node-rs/argon2": "^2.2.1",
@@ -55,6 +56,7 @@
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.9.7",
"prisma": "^7.10.0",
"sharp": "^0.35.4",
"tsx": "^4.23.13",
"typescript": "^5",
"vitest": "^5.0.1"
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env node
/**
* Erzeugt alle Symboldateien aus den SVG-Vorlagen in `assets/`.
*
* npm run icons
*
* Ergebnis:
* src/app/icon.svg – Symbol im Browser-Tab (moderne Browser)
* src/app/favicon.ico – Rückfall für ältere Browser (16, 32, 48 px)
* public/icon-192.png – Startbildschirm und Fortschrittsanzeigen
* public/icon-512.png
* src/app/apple-icon.png – iOS-Startbildschirm (180 px, mit Hintergrund)
*
* Die Größen 16 und 32 nutzen vereinfachte Vorlagen, weil die feinen Linien sonst verschwimmen.
*/
import { readFile, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
const WURZEL = process.cwd();
const ASSETS = path.join(WURZEL, "assets");
async function png(vorlage, groesse) {
const svg = await readFile(path.join(ASSETS, vorlage));
return sharp(svg, { density: 384 }).resize(groesse, groesse).png({ compressionLevel: 9 }).toBuffer();
}
/**
* Baut eine ICO-Datei aus PNG-Bildern. Das Format ist schlicht: ein Kopf, je Bild ein
* Verzeichniseintrag, danach die Bilddaten – PNG als Inhalt ist seit Vista erlaubt.
*/
function icoBauen(bilder) {
const kopf = Buffer.alloc(6);
kopf.writeUInt16LE(0, 0); // reserviert
kopf.writeUInt16LE(1, 2); // Typ 1 = Symbol
kopf.writeUInt16LE(bilder.length, 4);
let versatz = 6 + bilder.length * 16;
const eintraege = [];
for (const bild of bilder) {
const eintrag = Buffer.alloc(16);
eintrag.writeUInt8(bild.groesse >= 256 ? 0 : bild.groesse, 0); // Breite (0 = 256)
eintrag.writeUInt8(bild.groesse >= 256 ? 0 : bild.groesse, 1); // Höhe
eintrag.writeUInt8(0, 2); // Farben in der Palette
eintrag.writeUInt8(0, 3); // reserviert
eintrag.writeUInt16LE(1, 4); // Farbebenen
eintrag.writeUInt16LE(32, 6); // Bits je Bildpunkt
eintrag.writeUInt32LE(bild.daten.length, 8);
eintrag.writeUInt32LE(versatz, 12);
eintraege.push(eintrag);
versatz += bild.daten.length;
}
return Buffer.concat([kopf, ...eintraege, ...bilder.map((b) => b.daten)]);
}
async function main() {
await mkdir(path.join(WURZEL, "public"), { recursive: true });
// Tab-Symbol: das SVG selbst, damit es in jeder Größe scharf bleibt.
const svg = await readFile(path.join(ASSETS, "logo-32.svg"));
await writeFile(path.join(WURZEL, "src", "app", "icon.svg"), svg);
console.log(" src/app/icon.svg");
const ico = icoBauen([
{ groesse: 16, daten: await png("logo-16.svg", 16) },
{ groesse: 32, daten: await png("logo-32.svg", 32) },
{ groesse: 48, daten: await png("logo-32.svg", 48) },
]);
await writeFile(path.join(WURZEL, "src", "app", "favicon.ico"), ico);
console.log(` src/app/favicon.ico (${(ico.length / 1024).toFixed(1)} KB)`);
for (const groesse of [192, 512]) {
const daten = await png("logo-kachel.svg", groesse);
await writeFile(path.join(WURZEL, "public", `icon-${groesse}.png`), daten);
console.log(` public/icon-${groesse}.png`);
}
const apple = await png("logo-kachel.svg", 180);
await writeFile(path.join(WURZEL, "src", "app", "apple-icon.png"), apple);
console.log(" src/app/apple-icon.png");
}
console.log("Erzeuge Symbole aus assets/:");
main()
.then(() => console.log("Fertig."))
.catch((fehler) => {
console.error(fehler.message ?? fehler);
process.exit(1);
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+5
View File
@@ -0,0 +1,5 @@
<!-- Fassung für 32 px: eine Linie, kräftigere Formen. -->
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M7 3h20a13 13 0 0 1 0 26H17v16H7zM17 12h9a4 4 0 0 1 0 8h-9z" fill="#1E2A44"/>
<path d="M24 38h19" stroke="#17A2B8" stroke-width="5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

+19
View File
@@ -0,0 +1,19 @@
import type { MetadataRoute } from "next";
/** Angaben für den Startbildschirm, wenn das Dashboard als Verknüpfung abgelegt wird. */
export default function manifest(): MetadataRoute.Manifest {
return {
name: "ProxLedger – Proxmox Subscription Dashboard",
short_name: "ProxLedger",
description: "Subscription-Status aller Proxmox-Hosts im Blick",
lang: "de",
start_url: "/",
display: "standalone",
background_color: "#F4F6F9",
theme_color: "#1E2A44",
icons: [
{ src: "/icon-192.png", sizes: "192x192", type: "image/png" },
{ src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "any" },
],
};
}
+5
View File
@@ -14,8 +14,13 @@ const OEFFENTLICH = [
"/api/gesundheit",
"/install.sh",
"/agent.sh",
// Symbole und Manifest: Der Browser holt sie auch vor der Anmeldung.
"/favicon.ico",
"/icon.svg",
"/apple-icon.png",
"/icon-192.png",
"/icon-512.png",
"/manifest.webmanifest",
];
export async function proxy(request: NextRequest) {