Compare commits

..
2 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
18 changed files with 184 additions and 22 deletions
+5
View File
@@ -17,6 +17,11 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
### Sicherheit ### 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 ## [0.12.0] – 2026-09-17
### Hinzugefügt ### Hinzugefügt
+4 -2
View File
@@ -2,7 +2,7 @@
## Zeitübersicht ## Zeitübersicht
**Gesamt:** 7 h 08 min **Gesamt:** 7 h 23 min
| Issue | Zeit | | Issue | Zeit |
|---|---| |---|---|
@@ -23,6 +23,7 @@
| #014 Deployment | 0 h 40 min | | #014 Deployment | 0 h 40 min |
| #015 Feinschliff | 0 h 35 min | | #015 Feinschliff | 0 h 35 min |
| #016 Handbuch | 0 h 30 min | | #016 Handbuch | 0 h 30 min |
| #017 Favicons | 0 h 15 min |
| Meilenstein | Zeit | | Meilenstein | Zeit |
|---|---| |---|---|
@@ -45,7 +46,7 @@
## 2026-09-17 · Sitzung 1 · 19:36–laufend ## 2026-09-17 · Sitzung 1 · 19:36–laufend
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0) **Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
**Version:** 0.12.0 **Version:** 0.12.1
### Erledigt ### Erledigt
- Auftrag gelesen, Rückfragen gestellt - Auftrag gelesen, Rückfragen gestellt
@@ -88,6 +89,7 @@
- Als v0.11.0 veröffentlicht statt 1.0.0: Feldtest, Handbuch, Favicons, CI und Verschlüsselung der Zugangsdaten stehen noch aus - 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) - 0.11.1: Anmeldefehler behoben (Secure-Cookie über http)
- #016 abgeschlossen: Handbuch mit elf Kapiteln und 16 Bildern, dazu `npm run screenshots` zum Auffrischen - #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 ### Entscheidungen
- **Warnungen sammelweise anlegen:** Einzelne Upserts kosteten bei 737 neuen Warnungen 3,8 s; mit `createMany` sind es 0,3 s. - **Warnungen sammelweise anlegen:** Einzelne Upserts kosteten bei 737 neuen Warnungen 3,8 s; mit `createMany` sind es 0,3 s.
+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

+20 -10
View File
@@ -1,23 +1,23 @@
# #017 Favicon-Dateien erzeugen # #017 Favicon-Dateien erzeugen
**Status:** offen **Status:** erledigt
**Meilenstein:** v0.6.0 **Meilenstein:** v0.12.1
**Labels:** ui **Labels:** ui
**Geschätzt:** 1 h **Tatsächlich:** – **Geschätzt:** 1 h **Tatsächlich:** 0 h 15 min
## Ziel ## Ziel
Das Monogramm liegt als React-Komponente vor. Es fehlen die abgeleiteten Dateien für Browser-Tab, Das Monogramm liegt als React-Komponente vor. Es fehlen die abgeleiteten Dateien für Browser-Tab,
Startbildschirm und E-Mails. Startbildschirm und E-Mails.
## Aufgaben ## Aufgaben
- [ ] `favicon.svg` (32-px-Variante mit einer Linie) unter `src/app/icon.svg` - [x] `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 - [x] `favicon.ico` und PNG-Größen (16, 32, 180, 192, 512) erzeugen
- [ ] `apple-touch-icon` über `apple-icon.tsx` (ImageResponse) oder als PNG - [x] `apple-touch-icon` über `apple-icon.tsx` (ImageResponse) oder als PNG
- [ ] Erzeugung als Skript (`npm run icons`) festhalten, damit Änderungen am Logo nachziehbar sind - [x] Erzeugung als Skript (`npm run icons`) festhalten, damit Änderungen am Logo nachziehbar sind
- [ ] Einfarbige Variante für PDF-Export und Rechnungen bereitstellen - [x] Einfarbige Variante für PDF-Export und Rechnungen bereitstellen
## Akzeptanzkriterien ## Akzeptanzkriterien
- [ ] Browser-Tab zeigt das Monogramm in Hell und Dunkel - [x] Browser-Tab zeigt das Monogramm in Hell und Dunkel
- [ ] Beim Ablegen auf dem iOS-Startbildschirm erscheint das Logo ohne weißen Rand - [x] Beim Ablegen auf dem iOS-Startbildschirm erscheint das Logo ohne weißen Rand
## Abhängigkeiten ## Abhängigkeiten
#002 #002
@@ -25,3 +25,13 @@ Startbildschirm und E-Mails.
## Notizen / Entscheidungen ## Notizen / Entscheidungen
- Auf dem Entwicklungsrechner ist kein SVG-Konverter installiert (kein rsvg-convert, kein ImageMagick). - Auf dem Entwicklungsrechner ist kein SVG-Konverter installiert (kein rsvg-convert, kein ImageMagick).
Entweder `sharp` als Entwicklungsabhängigkeit aufnehmen oder Next.js `ImageResponse` nutzen. 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`).
+3 -3
View File
@@ -20,9 +20,9 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
| [#014](014-deployment.md) | Deployment und Dokumentation | erledigt | v0.10.0 | 8 h | 0 h 40 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 | | [#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 | erledigt | v0.12.0 | 5 h | 0 h 30 min | | [#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 | in Arbeit | v0.6.0 | 1 h | – | | [#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 | – | | [#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 | | [#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 | – | | [#021](021-geheimnisse-verschluesseln.md) | Zugangsdaten verschlüsselt speichern | offen | v0.9.0 | 2 h | – |
| | **Summe** | | | **116 h** | **7 h 08 min** | | | **Summe** | | | **116 h** | **7 h 23 min** |
+6 -5
View File
@@ -1,12 +1,12 @@
{ {
"name": "proxledger", "name": "proxledger",
"version": "0.9.0", "version": "0.12.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "proxledger", "name": "proxledger",
"version": "0.9.0", "version": "0.12.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@node-rs/argon2": "^2.2.1", "@node-rs/argon2": "^2.2.1",
@@ -37,6 +37,7 @@
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prettier": "^3.9.7", "prettier": "^3.9.7",
"prisma": "^7.10.0", "prisma": "^7.10.0",
"sharp": "^0.35.4",
"tsx": "^4.23.13", "tsx": "^4.23.13",
"typescript": "^5", "typescript": "^5",
"vitest": "^5.0.1" "vitest": "^5.0.1"
@@ -1013,8 +1014,8 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"optional": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -9117,8 +9118,8 @@
"version": "0.35.4", "version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true,
"dependencies": { "dependencies": {
"@img/colour": "^1.1.0", "@img/colour": "^1.1.0",
"detect-libc": "^2.1.2", "detect-libc": "^2.1.2",
@@ -9167,8 +9168,8 @@
"version": "7.8.5", "version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"optional": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
}, },
+4 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "proxledger", "name": "proxledger",
"version": "0.12.0", "version": "0.12.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "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: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", "build:all": "npm run build && npm run build:worker",
"lasttest": "tsx scripts/lasttest.ts", "lasttest": "tsx scripts/lasttest.ts",
"screenshots": "node scripts/screenshots.mjs" "screenshots": "node scripts/screenshots.mjs",
"icons": "node scripts/icons.mjs"
}, },
"dependencies": { "dependencies": {
"@node-rs/argon2": "^2.2.1", "@node-rs/argon2": "^2.2.1",
@@ -55,6 +56,7 @@
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prettier": "^3.9.7", "prettier": "^3.9.7",
"prisma": "^7.10.0", "prisma": "^7.10.0",
"sharp": "^0.35.4",
"tsx": "^4.23.13", "tsx": "^4.23.13",
"typescript": "^5", "typescript": "^5",
"vitest": "^5.0.1" "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", "/api/gesundheit",
"/install.sh", "/install.sh",
"/agent.sh", "/agent.sh",
// Symbole und Manifest: Der Browser holt sie auch vor der Anmeldung.
"/favicon.ico", "/favicon.ico",
"/icon.svg", "/icon.svg",
"/apple-icon.png",
"/icon-192.png",
"/icon-512.png",
"/manifest.webmanifest",
]; ];
export async function proxy(request: NextRequest) { export async function proxy(request: NextRequest) {