From 9c0cefcfe81929f88337df2d5cd83ec88769e610 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Thu, 17 Sep 2026 23:46:21 +0200 Subject: [PATCH] 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) --- CHANGELOG.md | 5 ++ DEVLOG.md | 6 +- assets/logo-16.svg | 4 ++ assets/logo-32.svg | 5 ++ assets/logo-kachel.svg | 8 +++ assets/logo.svg | 5 ++ docs/issues/017-favicon-dateien.md | 30 ++++++---- docs/issues/README.md | 6 +- package-lock.json | 11 ++-- package.json | 6 +- public/icon-192.png | Bin 0 -> 2802 bytes public/icon-512.png | Bin 0 -> 8744 bytes scripts/icons.mjs | 91 +++++++++++++++++++++++++++++ src/app/apple-icon.png | Bin 0 -> 2546 bytes src/app/favicon.ico | Bin 0 -> 1167 bytes src/app/icon.svg | 5 ++ src/app/manifest.ts | 19 ++++++ src/proxy.ts | 5 ++ 18 files changed, 184 insertions(+), 22 deletions(-) create mode 100644 assets/logo-16.svg create mode 100644 assets/logo-32.svg create mode 100644 assets/logo-kachel.svg create mode 100644 assets/logo.svg create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 scripts/icons.mjs create mode 100644 src/app/apple-icon.png create mode 100644 src/app/favicon.ico create mode 100644 src/app/icon.svg create mode 100644 src/app/manifest.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ffd3fb..e9f7c15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ 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 diff --git a/DEVLOG.md b/DEVLOG.md index a7fe2f5..6533345 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -2,7 +2,7 @@ ## Zeitübersicht -**Gesamt:** 7 h 08 min +**Gesamt:** 7 h 23 min | Issue | Zeit | |---|---| @@ -23,6 +23,7 @@ | #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 | |---|---| @@ -45,7 +46,7 @@ ## 2026-09-17 · Sitzung 1 · 19:36–laufend **Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0) -**Version:** 0.12.0 +**Version:** 0.12.1 ### Erledigt - 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 - 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. diff --git a/assets/logo-16.svg b/assets/logo-16.svg new file mode 100644 index 0000000..9af2ead --- /dev/null +++ b/assets/logo-16.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/logo-32.svg b/assets/logo-32.svg new file mode 100644 index 0000000..6527c00 --- /dev/null +++ b/assets/logo-32.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/logo-kachel.svg b/assets/logo-kachel.svg new file mode 100644 index 0000000..bb61aa1 --- /dev/null +++ b/assets/logo-kachel.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..b805a6b --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/issues/017-favicon-dateien.md b/docs/issues/017-favicon-dateien.md index 7900508..6e70bc7 100644 --- a/docs/issues/017-favicon-dateien.md +++ b/docs/issues/017-favicon-dateien.md @@ -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`). diff --git a/docs/issues/README.md b/docs/issues/README.md index 6a9b768..959b30b 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -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 | | [#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 | -| [#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 | – | -| [#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** | **7 h 08 min** | +| | **Summe** | | | **116 h** | **7 h 23 min** | diff --git a/package-lock.json b/package-lock.json index e6210bb..e3faa79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" }, diff --git a/package.json b/package.json index 510d08f..7865aff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxledger", - "version": "0.12.0", + "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" diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..f2ec82c27e981f6e9f5aa5952f5ca5a38448d628 GIT binary patch literal 2802 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 VFCs{K0&ZLZz}no-tj>gV{onH1+qeJ# literal 0 HcmV?d00001 diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..c352ac4c0cdd2b0ee63aab85dcbcc72399cca560 GIT binary patch literal 8744 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 Nf5zZ+rk3^H{{q++3OoP+ literal 0 HcmV?d00001 diff --git a/scripts/icons.mjs b/scripts/icons.mjs new file mode 100644 index 0000000..1f000c4 --- /dev/null +++ b/scripts/icons.mjs @@ -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); + }); diff --git a/src/app/apple-icon.png b/src/app/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8d2bb8e07602307e95223dc2a4e44323523344de GIT binary patch literal 2546 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 P6US$VLR;4$Zzlc)AeO`1 literal 0 HcmV?d00001 diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..56ca26ba617dc1b33c644509e447da6b112ab13f GIT binary patch literal 1167 zcmZQzU}Rus5D;Jh(h3Yu7#JALfLK8R!e7P6z#s+01_luRX(0Ou69YqMfS)@rmlPLJ zDX*u8OAwF-n!q5y!3HGdKmShzQk(@Ik;M!Q)_fq$cxKzeW}x6SPZ!4!i_^&o4NR80 z*8^fA;x3;I4mumx!YDuCa?#Iz@$6lHrd=(Wxn$9!gMRZE19(hQcwN~XVjugoFbe!X zonhh1U@GG_wRoPx1%?#M1)Gfn_AGq5X9kD3#d@a1NZ;pMH$~~mUwD#rlHW!5x%{VB z@3>N=9{h2SyKLF{CE`NKPo3p2_c*&esuq3`v~Yl z22WQ%mvv4FO<4S>0F+h$`?0vH>I;yA&5w*RAU{SNPEL?u&2aELZ5rZt`l@4s=i?YUqjm|W!6&nuZ_hFEcd5e-!3lU!Oq}( zXlc2FtitN2f1LfUM^E75E8so4%suA()M&rcnk*}xnda?1chRJn!LX_^a!dO&zJQ>j zhsT9^yEET0RQjen+b9~eF5{bUu}GLHs))s(LnP=8L&D24o*6U37A7zFVV1D)Ghe*l zgi8fs(g%tTR6cK*%6#C$PrbE|mzX>775vj){!+>~VCzHnghgMLSLbT1e))+h;@fx6 z6%`#p`)wK4geZKP0gk`0%Ym~S7tZT!(^dMzDkwWEWig*aX~U02bDs%yZ)=~_?Q+DeY?KS`?Y2xD=_f!#S}2TG8lkkDxB$OJCK7l zrfNYkm2nRgQ;QqQfN}NIK6K-<&*H#@^m@s6BJA?h6?3+&{yO0+ zgUEm8V`oH?lasG4e8m`%^JenpuVONr@l#}^%#7gcBqu{8s3_rygu1DbBnXFL9EyFz0O;_U;YuFlk?_n zWLwhmy6tn?CSMegyixR%$4BkDgQ?DnwfbvcF0#^K+t&8y!lM=JZng%m8WUeI&b(>e z;v)X`gh>3B#dD%0Qshp=%&KNc>AZ1BmcL=zzvX6ZGrhN{pZMBvnN v4{Y#rIx#aM=ixWLti)ZZk1kdvTwy+ZlzDN2hyWWyaeuz9-U&v1 + + + + diff --git a/src/app/manifest.ts b/src/app/manifest.ts new file mode 100644 index 0000000..6ab25d4 --- /dev/null +++ b/src/app/manifest.ts @@ -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" }, + ], + }; +} diff --git a/src/proxy.ts b/src/proxy.ts index 95c747c..1e21023 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -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) {