feat: Projektgerüst, gehärteten Betriebsstand und Prozess aufsetzen
Legt die Grundlage, auf der alle weiteren Meilensteine nur noch Fachlichkeit ergänzen: Next 16 mit App Router und TypeScript strict, Tailwind 4, Prisma 7, Vitest 5, Playwright, ESLint, Prettier und die Hooks für Lint und Commits. Zwei Dinge sind bewusst so gebaut und durch Tests festgehalten: Die Datenbank ist im Betriebsstand von außen nicht erreichbar. Sie hat kein ports:-Mapping und hängt allein im Netz "intern", das ohne Gateway läuft; nur "web" kommt zusätzlich nach draußen. tests/betriebsstand.test.ts schlägt an, falls ein Port-Mapping zurückkehrt. Geprüft, nicht behauptet: docker compose up startet den Stack, 5432 ist vom Host aus zu, aus "web" offen. pnpm pruefe:doku hält Dokumentation und Code zusammen — Version im Changelog, Issue-Dateien gegen ihre Übersicht, keine Verweise auf fehlende Dateien, keine fehlenden Bilder. Das Skript wurde gegen vier eingebaute Fehler gegengeprüft. Drei Versions-Obergrenzen sind nötig und in CLAUDE.md begründet: eslint bleibt auf 9.x, weil eslint-plugin-react keinen Nachfolger für eslint 10 hat und das Regelwerk sonst beim Laden abbricht; typescript bleibt auf 6.0.x, weil typescript-eslint TypeScript 7 noch nicht unterstützt; prisma ist exakt auf 7.10.0 gepinnt, weil sein latest-Tag auf einen 8.0-RC zeigt, der von @prisma/client aber nicht. Auth.js entfällt entgegen der ursprünglichen Vorgabe: v5 steht dauerhaft auf Beta, das Projekt ist im Wartungsmodus und sein Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen openid-client und jose, wie in ProxLedger. Dieser erste Commit geht direkt auf main, weil es noch keinen Branch gibt, von dem abzuzweigen wäre. Ab #006 läuft jedes Issue über einen eigenen Branch. Closes #5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# #001 Phase 1: MVP
|
||||
|
||||
**Status:** in Arbeit
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1
|
||||
**Geschätzt:** – **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Eine selbst gehostete Checklisten-App für wiederkehrende IT-Arbeiten: versionierte Vorlagen, daraus gestartete Läufe, im Team abgearbeitet, abgeschlossen und unveränderlich archiviert. Ein Lauf hängt optional an einem Mitarbeiter und optional an einem Gerät, woraus im Archiv eine Historie je Gerät und je Mitarbeiter entsteht. Einstieg über QR-Aufkleber mit unveränderlichem `tagCode`. Zwei Betriebsmodi (`INTERN`, `DIENSTLEISTUNG`) in einem Codepfad, zwei Betriebswege (gehärteter Compose-Stack, nativer LXC per One-Liner).
|
||||
|
||||
## Untergeordnete Issues
|
||||
|
||||
- [ ] #005 M0 – Gerüst, Prozess, Design-Token
|
||||
- [ ] #006 M1 – Datenmodell
|
||||
- [ ] #007 M2 – Auth, Rollen, Benutzer
|
||||
- [ ] #008 M3 – Betriebsmodus und Kunden
|
||||
- [ ] #009 M4 – Vorlagen-Versionierung im Service
|
||||
- [ ] #010 M5 – Vorlagen-Editor
|
||||
- [ ] #011 M6 – Mitarbeiter, Abteilungen, Geräte
|
||||
- [ ] #012 M7 – Platzhalter, Namensregeln, Escaping
|
||||
- [ ] #013 M8 – Läufe: Service und Abarbeitung
|
||||
- [ ] #014 M9 – QR-Tags, Geräteseite, Etiketten, Scanner
|
||||
- [ ] #015 M10 – Dashboard und Archiv
|
||||
- [ ] #016 M11 – Verzeichnis und LDAP-Abgleich
|
||||
- [ ] #017 M12 – Seed der sieben Vorlagen
|
||||
- [ ] #018 M13 – E2E-Tests, Anleitung, PDF
|
||||
- [ ] #019 M14 – Betriebswege und Release v0.1.0
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
Die vollständige Liste steht in `docs/abnahme-mvp.md` und wird von #019 abschließend geprüft. Kern:
|
||||
|
||||
- [ ] `docker compose up` startet App und Datenbank, Migrationen laufen automatisch, Datenbank von außen nicht erreichbar
|
||||
- [ ] Proxmox-One-Liner rollt einen lauffähigen LXC aus, erneuter Aufruf aktualisiert
|
||||
- [ ] Lokaler Login funktioniert, OIDC für authentik dokumentiert
|
||||
- [ ] Lauf ohne Mitarbeiter und ohne Gerät: starten, abschließen, archivieren
|
||||
- [ ] Hauptablauf in beiden Betriebsmodi, Moduswechsel in beiden Richtungen nach Regel
|
||||
- [ ] LDAP-Abgleich gegen Testcontainer: anlegen, aktualisieren, deaktivieren
|
||||
- [ ] Veröffentlichte Vorlagenversion unveränderlich, Archiv schreibgeschützt
|
||||
- [ ] `{{generiertes_passwort}}` nirgends persistiert
|
||||
- [ ] Am Handy kein horizontales Scrollen
|
||||
- [ ] `v0.1.0` getaggt, Anleitung mit Screenshots und als PDF
|
||||
|
||||
## Entscheidungen
|
||||
|
||||
- **Deutsch im Code.** Auch Prisma-Modelle, Felder und Enum-Werte sind deutsch, Umlaute ASCII-gefaltet. Abweichung vom Auftragstext, bewusst zugunsten der Einheitlichkeit mit ProxLedger. Verbindliche Namensabbildung in `CLAUDE.md`.
|
||||
- **Kein Auth.js.** v5 ist dauerhaft Beta und im Wartungsmodus, der Prisma-Adapter deckt Prisma 7 nicht ab. Stattdessen `openid-client` + `jose` wie in ProxLedger.
|
||||
@@ -0,0 +1,27 @@
|
||||
# #002 Phase 2: Verzeichnis-Alternativen, Bausteine, Anhänge
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.2.0
|
||||
**Labels:** phase-2
|
||||
**Geschätzt:** – **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Nicht im MVP umsetzen. Hier gesammelt, damit das Datenmodell nichts verbaut.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] SCIM-Endpunkt als Alternative zum Pull-Abgleich
|
||||
- [ ] Microsoft-Graph-Quelle für reine Entra-ID-Umgebungen ohne LDAP
|
||||
- [ ] Wiederverwendbare Bausteine: Abschnitte, die mehrere Vorlagen teilen
|
||||
- [ ] Abteilungsspezifische Zusatzabschnitte (Fachsoftware je Abteilung)
|
||||
- [ ] Anhänge und Fotos an Schritten
|
||||
- [ ] Live-Aktualisierung per SSE statt Polling
|
||||
- [ ] Benachrichtigungen per Mail und Webhook
|
||||
- [ ] Mitarbeiter-Selbstauskunft: ein Mitarbeiter sieht die Läufe zu seinen Geräten, nur lesend
|
||||
|
||||
## Vorleistung im MVP
|
||||
|
||||
- `Verzeichnisquelle` ist bereits mehrfach anlegbar und trägt einen Typ-fähigen Aufbau, damit Graph und SCIM daneben passen
|
||||
- `VorlagenAbschnitt` hängt an der Version, nicht an der Vorlage — ein geteilter Baustein kann später als eigene Entität referenziert werden
|
||||
- Polling ist in einem Route Handler gekapselt, der sich gegen SSE tauschen lässt
|
||||
@@ -0,0 +1,23 @@
|
||||
# #003 Phase 3: Protokoll, bedingte Schritte, Freigabe
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.3.0
|
||||
**Labels:** phase-3
|
||||
**Geschätzt:** – **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Nicht im MVP umsetzen. Hier gesammelt, damit das Datenmodell nichts verbaut.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] PDF-Übergabeprotokoll mit Unterschrift
|
||||
- [ ] Bedingte Schritte und Abschnitte
|
||||
- [ ] Vier-Augen-Freigabe
|
||||
- [ ] Weitere Schritt-Typen
|
||||
|
||||
## Vorleistung im MVP
|
||||
|
||||
- Weil bedingte Abschnitte fehlen, enthält die Onboarding-Vorlage beide Konto-Abschnitte (AD und Entra ID); der nicht zutreffende wird per Abschnitts-Aktion als Ganzes auf `NICHT_ZUTREFFEND` gesetzt. Diese Aktion ist Teil des MVP (#013)
|
||||
- `SchrittTyp` ist ein Enum, `VorlagenSchritt.konfig` ein Json — neue Typen brauchen keine Migration der bestehenden
|
||||
- Die Druckansicht (#015) ist bereits die Vorstufe des PDF-Protokolls
|
||||
@@ -0,0 +1,31 @@
|
||||
# #004 Phase 4: Aktionen, Connectors, REST-API
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.4.0
|
||||
**Labels:** phase-4
|
||||
**Geschätzt:** – **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Nicht im MVP umsetzen. Der Weg dorthin wird im MVP aber bewusst freigehalten: Die in den Vorlagen vorbereiteten PowerShell-Befehle sind die Vorstufe von Schritten, die die App selbst ausführt.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Schritt-Typ `AKTION` mit Connector und Parametern aus Variablen und Person
|
||||
- [ ] Connectors: LDAP/AD schreibend über eigenes Dienstkonto, Microsoft Graph (Entra ID, Exchange Online, Intune), authentik-API, später Ticketsystem und RMM
|
||||
- [ ] Onboarding als Aktion: Konto anlegen, Initialpasswort erzeugen und sicher übergeben, Gruppen laut Abteilung, Postfach und Lizenz
|
||||
- [ ] Offboarding als Aktion: Konto sperren, Sitzungen widerrufen, Postfach in Shared Mailbox wandeln
|
||||
- [ ] Vorschau „das wird passieren", ausdrückliche Bestätigung, Ergebnis-Log am Schritt, Rollback-Hinweis
|
||||
- [ ] Fehler setzen den Schritt auf `FEHLGESCHLAGEN` mit Meldung
|
||||
- [ ] Berechtigung je Connector und Rolle, Geheimnisse verschlüsselt, alle Aufrufe im `Protokoll`
|
||||
- [ ] Wiederkehrende Läufe (v0.5.0)
|
||||
- [ ] REST-API und Webhooks, Start aus Ticketsystem und Wartungsplaner (v0.5.0)
|
||||
- [ ] Zeiterfassung je Lauf, automatische Prüfschritte per RMM-API, Auswertungen, Aufbewahrungsfristen, NFC-Tags (v0.5.0)
|
||||
|
||||
## Vorleistung im MVP
|
||||
|
||||
- `SchrittTyp` ist ein Enum, dem `AKTION` ohne Migration bestehender Daten hinzugefügt werden kann; `konfig` (Json) trägt dann Connector und Parameter
|
||||
- `Schrittergebnis.wert` ist ein Json, das ein Aktionsergebnis mit Log, IDs und Zeitstempeln aufnimmt
|
||||
- Kein Codepfad nimmt an, dass ein Schritt nur von einem Menschen erledigt wird — `aktualisiertVonId` ist optional und ein Systemlauf ist darstellbar
|
||||
- Das Krypto-Modul für das LDAP-Bind-Passwort (#006) ist schon der Platz für Connector-Geheimnisse
|
||||
- NFC nutzt dieselbe URL `{APP_URL}/t/{tagCode}` wie der QR-Code
|
||||
@@ -0,0 +1,68 @@
|
||||
# #005 M0 – Gerüst, Prozess, Design-Token
|
||||
|
||||
**Status:** erledigt
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, infra, docs
|
||||
**Geschätzt:** 6 h **Tatsächlich:** 1 h 25 min
|
||||
|
||||
## Ziel
|
||||
|
||||
Ein lauffähiges, leeres Projekt mit allen Qualitätsschranken, dem gehärteten Betriebsstand und dem Design-Fundament. Danach ist jeder weitere Meilenstein reine Fachlichkeit.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [x] Lokalen Branch `master` auf `main` umbenennen (Gitea-Default ist `main`), Remote unverändert lassen
|
||||
- [x] `.nvmrc` = `24`, `packageManager: "pnpm@10.34.5"` über corepack, `engines.node >= 24`
|
||||
- [x] Next 16.3.6 (App Router, TypeScript strict) mit `output: "standalone"`, `src/`-Layout
|
||||
- [x] `tsconfig.json`: `strict`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`, `forceConsistentCasingInFileNames`, Alias `@/*` → `./src/*`
|
||||
- [x] Tailwind 4 (`@import "tailwindcss"` + `@theme`, `@tailwindcss/postcss`), shadcn/ui initialisieren, `lucide-react`
|
||||
- [x] ESLint flat config nach ProxLedger-Muster (`eslint-config-next/core-web-vitals` + `/typescript` + `eslint-config-prettier/flat`, `globalIgnores` inkl. `src/generated/**`), Prettier (`printWidth: 110`, `singleQuote: false`, `trailingComma: "all"`)
|
||||
- [x] husky + lint-staged + commitlint (`@commitlint/config-conventional`) als Pre-Commit- und Commit-Msg-Hook
|
||||
- [x] Vitest 5 (`vitest.config.mts`, `environment: "node"`, `fileParallelism: false`) und `vite@^7` als Peer; Playwright 1.63 (`playwright.config.ts`)
|
||||
- [x] `Dockerfile` multi-stage (Basis → Abhängigkeiten → Bauen → Laufzeit), `node:24-alpine`, `tini`, Nutzer 1001, non-root
|
||||
- [x] `docker-compose.yml` gehärtet: `postgres` **ohne** `ports:`, Netze `intern` (`internal: true`) und `extern`, `web` auf `${BIND_ADDR:-127.0.0.1}:3000`, `migrate` als One-Shot aus der Bau-Stufe, Healthchecks, `${VAR:?… fehlt in der .env}`
|
||||
- [x] `docker-compose.dev.yml` (nur Postgres, Port 5433) und `docker-compose.test.yml` (Postgres; der OpenLDAP-Dienst kommt mit #016, wo er tatsächlich benutzt wird)
|
||||
- [x] `.env.example` mit allen Variablen und Kommentar je Variable
|
||||
- [x] `GET /api/gesundheit` → `SELECT 1` → 200/503
|
||||
- [x] Sicherheitskopfzeilen in `next.config.ts`, CSP-Nonce in `proxy.ts` (Next 16: nicht `middleware.ts`)
|
||||
- [x] `scripts/pruefe-doku.mjs` und `pnpm pruefe:doku`: Version hat Changelog-Abschnitt, Issue-Dateien und Übersicht stimmen überein, verlinkte Bilder existieren
|
||||
- [x] `scripts/gitea-issues.ts` und `pnpm issues:sync`: spiegelt `docs/issues/*.md` in den Gitea-Tracker
|
||||
- [x] `.gitea/workflows/ci.yml` nach ProxLedger-Muster, erweitert um Playwright und `shellcheck`
|
||||
- [x] Design-Token als CSS-Variablen (Grund `#0B0D10`, Flächen `#14171C`/`#1B1F26`, Linien `#262B33`, Text `#F3F5F7`/`#98A2B3`, Akzent `#C6F135`, Status `#3DDC97`/`#F5B84A`/`#F87171`), heller Modus optional
|
||||
- [x] Schriften selbst gehostet: `@fontsource-variable/space-grotesk`, `-manrope`, `-jetbrains-mono`
|
||||
- [x] Wortmarke `laufzettel` (Space Grotesk 700, Laufweite −0.04em, Akzentpunkt) und Bildmarke `[ ✓ ]` als SVG, Favicon, PWA-Icon, `manifest.ts`
|
||||
- [x] App-Shell: dunkle Sidebar mit „Scannen"-Knopf direkt unter dem Logo, Pillen-Buttons (40 px, am Handy 44–46 px), Karten mit Radius 16–18 px und 1-px-Linie ohne Schatten
|
||||
- [x] Versionsanzeige unten links aus `NEXT_PUBLIC_APP_VERSION` und `NEXT_PUBLIC_GIT_SHA`
|
||||
- [x] `README.md`, `CHANGELOG.md` (Keep a Changelog, deutsch), `docs/devlog.md`, `CLAUDE.md` mit Konventionen und Namensabbildung, `docs/architektur.md`, `docs/abnahme-mvp.md`
|
||||
- [x] Gitea: Labels, Milestones `v0.1.0`…`v0.5.0`, Epic-Issues #001–#004 und Meilenstein-Issues #005–#019
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] `docker compose up` startet App und Datenbank, Migrationen laufen automatisch
|
||||
- [x] `nc -z localhost 5432` schlägt fehl, `docker compose exec web nc -z postgres 5432` gelingt
|
||||
- [x] Ein Vitest-Test liest `docker-compose.yml` und schlägt an, wenn unter `postgres` ein `ports:` auftaucht
|
||||
- [x] `pnpm lint && pnpm typecheck && pnpm test && pnpm pruefe:doku` grün
|
||||
- [x] Ein Commit ohne Conventional-Commits-Betreff wird vom Hook abgelehnt
|
||||
- [x] Die App zeigt Wortmarke, Bildmarke und Version unten links im vorgegebenen Farbschema
|
||||
|
||||
## Beim Bauen aufgefallen
|
||||
|
||||
- **pnpm 12 hat die Freigabe von Build-Skripten umbenannt.** `onlyBuiltDependencies` wird von
|
||||
`pnpm config list` noch angezeigt, aber nicht mehr angewandt; wirksam ist `allowBuilds` als
|
||||
Zuordnung in `pnpm-workspace.yaml`. Das `pnpm`-Feld in `package.json` liest pnpm 12 gar nicht
|
||||
mehr und warnt darüber.
|
||||
- **pnpm 12 prüft die Lieferkette.** Pakete, die jünger als 24 Stunden sind, werden abgelehnt.
|
||||
`vite@8.3.1` und `lucide-react@1.48.0` erschienen am selben Tag und mussten auf `8.3.0` bzw.
|
||||
`1.47.0` zurück. Die Richtlinie bleibt an — sie ist sinnvoll.
|
||||
- **eslint 10 bricht mit `eslint-config-next`.** `eslint-plugin-react@7.37.5` unterstützt nur bis
|
||||
`eslint ^9.7` und hat keinen Nachfolger. Festgestellt, nicht geraten:
|
||||
`TypeError: contextOrFilename.getFilename is not a function`.
|
||||
- **TypeScript 7 geht noch nicht.** `typescript-eslint@8` erlaubt `>=4.8.4 <6.1.0`.
|
||||
- **`shadcn init` ist nicht skriptfähig** — es fragt nach einem Preset, auch mit `--yes`, und
|
||||
bleibt hinter einer Pipe hängen. `shadcn add <bauteil> --yes --overwrite < /dev/null`
|
||||
funktioniert dagegen. Die Registry importiert den Helfer als blanken Modulnamen `cn`, dafür
|
||||
braucht `tsconfig.json` den Pfad `"cn": ["./src/lib/utils"]`, und die Bauteile brauchen das
|
||||
Paket `radix-ui`.
|
||||
- **Port 3000 war von ProxLedger belegt**, die Abnahme lief deshalb auf 3100 bzw. 3200.
|
||||
- Die Migrationen laufen im Compose-Stand nachweislich automatisch; eingespielt wird ab #006
|
||||
etwas, vorher meldet der Dienst korrekt „No pending migrations to apply".
|
||||
@@ -0,0 +1,33 @@
|
||||
# #006 M1 – Datenmodell
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend
|
||||
**Geschätzt:** 5 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Das vollständige Schema in deutscher Benennung, sodass alle folgenden Meilensteine nur noch Verhalten ergänzen und keine Strukturänderung mehr brauchen.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Prisma 7: Generator `prisma-client` mit `output = "../src/generated/prisma"`, `datasource` ohne `url`, `@prisma/adapter-pg`
|
||||
- [ ] `prisma.config.ts` mit `migrations.seed = "tsx prisma/seed.ts"`
|
||||
- [ ] `src/lib/db.ts`: Lazy-Singleton-Proxy, damit `next build` ohne `DATABASE_URL` läuft, Dev-Instanz an `globalThis`
|
||||
- [ ] Modelle: `Benutzer`, `Kunde`, `Einstellung`, `Verzeichnisquelle`, `Mitarbeiter`, `Abteilung`, `Geraet`, `GeraetetypVorlage`, `Vorlage`, `VorlagenVersion`, `VorlagenVariable`, `VorlagenAbschnitt`, `VorlagenSchritt`, `Lauf`, `Schrittergebnis`, `Kommentar`, `Protokoll`, `AbgleichSperre`
|
||||
- [ ] Enums: `Rolle`, `Betriebsmodus`, `LaufStatus`, `SchrittZustand`, `SchrittTyp`, `GeraeteTyp`, `VersionStatus`, `Herkunft`
|
||||
- [ ] Unique-Constraints: `Geraet.tagCode`, `(laufId, vorlagenSchrittId)` auf `Schrittergebnis`, `(quelleId, externeId)` auf `Mitarbeiter`, `(kundeId, name)` auf `Abteilung`, `(kundeId)` partiell für `isteigeneOrganisation`
|
||||
- [ ] `pg_trgm`-Erweiterung und GIN-Indizes für die Archivsuche über `Lauf.titel`, `Schrittergebnis.notiz`, `Kommentar.rumpf`
|
||||
- [ ] Jede kundenbezogene Zeile trägt `kundeId` als Pflichtfeld: `Geraet`, `Mitarbeiter`, `Abteilung`, `Verzeichnisquelle`, `Lauf`
|
||||
- [ ] `src/lib/tagcode.ts`: Alphabet `ABCDEFGHJKLMNPQRSTUVWXYZ23456789`, Präfix `LZ-`, 6 Zeichen, Kollisionsprüfung serverseitig
|
||||
- [ ] `src/lib/geheimnisse.ts`: AES-256-GCM aus `APP_SECRET` (scrypt-abgeleitet), für `bindPasswortVerschluesselt`
|
||||
- [ ] Erste Migration, `prisma/seed.ts` als idempotenter Rumpf: Admin aus `SEED_ADMIN_EMAIL`/`SEED_ADMIN_PASSWORD` mit `@node-rs/argon2`, eigene Organisation, `Einstellung` mit `Betriebsmodus.INTERN`
|
||||
- [ ] `tests/setup-env.ts`, `tests/global-setup.ts`, `tests/helpers/db.ts` nach ProxLedger-Muster
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] `prisma migrate deploy` läuft auf leerer Datenbank durch
|
||||
- [ ] `pnpm db:seed` zweimal hintereinander ohne Fehler und ohne Datenänderung
|
||||
- [ ] Tests: `tagCode` benutzt nur das erlaubte Alphabet, enthält nie `0`, `O`, `1`, `I` oder `L`, ist eindeutig über 10 000 Ziehungen
|
||||
- [ ] Tests: verschlüsseln und entschlüsseln ist verlustfrei, zwei Verschlüsselungen desselben Klartexts unterscheiden sich (eigener IV), ein manipulierter Geheimtext wirft
|
||||
- [ ] `next build` gelingt ohne gesetzte `DATABASE_URL`
|
||||
@@ -0,0 +1,33 @@
|
||||
# #007 M2 – Auth, Rollen, Benutzer
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, frontend, security
|
||||
**Geschätzt:** 8 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Anmeldung per generischem OIDC (Ziel authentik) und als Notfallzugang lokal per E-Mail und Passwort, dazu die serverseitige Rechteprüfung, auf der alle weiteren Services aufsetzen.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] `src/server/auth/oidc.ts`: `openid-client` 6.x mit Discovery, Umgebungsvariablen `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_NAME`; Rücksprung-URL aus `APP_URL` abgeleitet, damit es hinter dem Reverse Proxy stimmt
|
||||
- [ ] `src/lib/sitzung.ts`: `jose`-signiertes Sitzungscookie (HS256, `APP_SECRET` mit mindestens 32 Zeichen erzwungen, Hinweis auf `openssl rand -base64 48`), Nutzlast `{benutzerId, email, name, herkunft}`, Laufzeit 12 h
|
||||
- [ ] Lokaler Login mit `@node-rs/argon2`, nur aktiv bei `AUTH_LOCAL_ENABLED=true`
|
||||
- [ ] Rolle bei jedem OIDC-Login aus dem `groups`-Claim über `OIDC_GROUP_ADMIN`, `OIDC_GROUP_EDITOR`, `OIDC_GROUP_TECHNICIAN`, `OIDC_GROUP_VIEWER` aktualisieren; JIT-Provisioning unbekannter Benutzer beim ersten Login
|
||||
- [ ] `src/server/auth/rollen.ts`: `RECHTE` als eine Tabelle (`satisfies Record<string, readonly Rolle[]>`) plus `darf()` und `ROLLEN_TEXT`
|
||||
- [ ] `src/server/auth/sitzungspflicht.ts`: `angemeldet()` (React-`cache`, ohne Weiterleitung) und `rechtErforderlich(recht)`; Rolle und Aktiv-Status pro Request **aus der Datenbank**, nicht aus dem Token; `sitzungenUngueltigAb` für Widerruf; reine, testbare `sitzungBewerten()`
|
||||
- [ ] `src/proxy.ts`: Allowlist öffentlicher Pfade, Weiterleitung auf `/anmelden?ziel=…`
|
||||
- [ ] Login-Seite im Design, OIDC-Knopf und lokales Formular, Rücksprung auf die ursprünglich angeforderte URL (nötig für `/t/[tagCode]`)
|
||||
- [ ] Admin → Benutzer: Liste mit Rolle und Aktiv-Status, lokale Benutzer anlegen wenn lokaler Login aktiv
|
||||
- [ ] Screenshots Login und Benutzerliste in `docs/screenshots/m2/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Anmeldung mit dem Seed-Admin gelingt, Abmelden beendet die Sitzung
|
||||
- [ ] Bei `AUTH_LOCAL_ENABLED=false` ist das lokale Formular nicht vorhanden und der Endpunkt lehnt ab
|
||||
- [ ] Tests fahren jede Rolle gegen jedes Recht: `LESER` darf nichts schreiben, `TECHNIKER` darf keine Verwaltung, `REDAKTEUR` darf Vorlagen, `ADMIN` alles
|
||||
- [ ] Eine Rollenänderung in der Datenbank greift ohne neue Anmeldung
|
||||
- [ ] Ein deaktivierter Benutzer wird mit bestehendem Cookie abgewiesen
|
||||
- [ ] Aufruf von `/t/LZ-7K3F9Q` ohne Anmeldung führt nach dem Login genau dorthin zurück
|
||||
- [ ] Passwörter stehen nur als argon2-Hash in der Datenbank, nie im Klartext und nie in einer API-Antwort
|
||||
@@ -0,0 +1,33 @@
|
||||
# #008 M3 – Betriebsmodus und Kunden
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, frontend
|
||||
**Geschätzt:** 6 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Ein Codepfad für beide Betriebsmodi. Im Modus `INTERN` ist die Kunden-Ebene vollständig unsichtbar, ohne dass irgendwo Daten fehlen — dadurch ist der Wechsel später eine Datenänderung und keine Migration.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] `betriebsmodus()` als zentraler, gecachter Zugriff auf `Einstellung.modus`; alle Listen und Formulare fragen ihn ab
|
||||
- [ ] `kundeErmitteln(eingabe)`: im Modus `INTERN` immer die eigene Organisation, eine mitgeschickte `kundeId` wird **verworfen**
|
||||
- [ ] Moduswechsel-Service: `INTERN → DIENSTLEISTUNG` jederzeit, die eigene Organisation wird zum ersten Kunden (Name und Kürzel werden abgefragt), alle Daten bleiben daran hängen
|
||||
- [ ] Moduswechsel-Service: `DIENSTLEISTUNG → INTERN` nur bei genau einem nicht archivierten Kunden, sonst Ablehnung mit Hinweis
|
||||
- [ ] Beide Richtungen mit Bestätigungsdialog und `Protokoll`-Eintrag, nur `ADMIN`
|
||||
- [ ] Kundenverwaltung im Modus `DIENSTLEISTUNG`: anlegen, `kuerzel` eindeutig 2–6 Zeichen, archivieren statt löschen, Notizen
|
||||
- [ ] Navigation und Beschriftungen je Modus: `INTERN` = Übersicht, Läufe, Vorlagen, Mitarbeiter & Geräte, Archiv, Admin; `DIENSTLEISTUNG` = Übersicht, Läufe, Vorlagen, Kunden, Archiv, Admin und „Mitarbeiter" heißt „Personen"
|
||||
- [ ] Optionale Kundenbindung über `OIDC_CUSTOMER_GROUP_PREFIX` (z. B. `lz-kunde-MUS`): `TECHNIKER`/`LESER` sehen nur ihre Kunden, `ADMIN`/`REDAKTEUR` alles; ohne die Variable keine Einschränkung
|
||||
- [ ] Admin → Einstellungen: Modus mit Wechsel-Dialog, Name der eigenen Organisation, globale Namensregeln, Sperr-OU, Anzeige von `APP_URL` für die QR-Codes
|
||||
- [ ] Screenshots Einstellungen, Wechsel-Dialog, Kundenliste in `docs/screenshots/m3/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Im Modus `INTERN` erscheint die eigene Organisation in keiner Liste, keinem Filter, keinem Formular und auf keinem Etikett
|
||||
- [ ] Test: eine Server Action mit mitgeschickter fremder `kundeId` schreibt im Modus `INTERN` trotzdem auf die eigene Organisation
|
||||
- [ ] Test: Wechsel `INTERN → DIENSTLEISTUNG` erhält alle Geräte, Mitarbeiter, Abteilungen und Läufe am ersten Kunden
|
||||
- [ ] Test: Wechsel `DIENSTLEISTUNG → INTERN` wird bei zwei nicht archivierten Kunden mit Hinweis abgelehnt, bei einem gelingt er
|
||||
- [ ] Test: ein Wechsel schreibt einen `Protokoll`-Eintrag mit Vorher- und Nachher-Zustand
|
||||
- [ ] Test: `LESER` und `TECHNIKER` dürfen den Modus nicht wechseln
|
||||
- [ ] Der Modus liegt in der Datenbank und ist ohne Neustart umschaltbar
|
||||
@@ -0,0 +1,32 @@
|
||||
# #009 M4 – Vorlagen-Versionierung im Service
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend
|
||||
**Geschätzt:** 6 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Die harte Regel der Anwendung: eine veröffentlichte Version ist für immer unveränderlich, damit ein Archiv-Lauf für immer nachvollziehbar bleibt. Diese Regel sitzt in der Service-Schicht, nicht in der Oberfläche.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Services für `Vorlage`, `VorlagenVersion`, `VorlagenAbschnitt`, `VorlagenSchritt`, `VorlagenVariable`
|
||||
- [ ] `vorlageVeroeffentlichenService`: setzt `status = VEROEFFENTLICHT`, `veroeffentlichtAm`, `veroeffentlichtVonId`, Änderungsnotiz, `Protokoll`-Eintrag
|
||||
- [ ] `vorlageBearbeitenService`: tiefe Kopie der letzten veröffentlichten Version als neuer Entwurf mit fortlaufender `version`; kopiert Variablen, Abschnitte und Schritte samt `konfig`
|
||||
- [ ] Höchstens ein Entwurf je Vorlage — zweiter Versuch wirft `VorlagenFehler` mit Verweis auf den bestehenden Entwurf
|
||||
- [ ] Jede Schreiboperation auf einer veröffentlichten Version wirft; zentral in `versionMussEntwurfSein()`
|
||||
- [ ] Läufe verweisen ausschließlich auf veröffentlichte Versionen — beim Anlegen geprüft
|
||||
- [ ] `vorlageDuplizierenService`: neue Vorlage mit Entwurf als Kopie
|
||||
- [ ] JSON-Export und -Import mit Zod-Schema, Format in `docs/vorlagen-format.md` dokumentiert (Version des Formats, Abschnitte, Schritte, Variablen, `konfig`)
|
||||
- [ ] Rechte: `REDAKTEUR` und `ADMIN` dürfen anlegen, bearbeiten, veröffentlichen; `TECHNIKER` und `LESER` nicht
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: nach dem Veröffentlichen schlägt jede Änderung an Version, Abschnitten, Schritten und Variablen fehl
|
||||
- [ ] Test: „Bearbeiten" erzeugt eine tiefe Kopie — Änderungen am Entwurf lassen die veröffentlichte Version unberührt, auch in verschachtelten `konfig`-Feldern
|
||||
- [ ] Test: ein zweiter Entwurf wird abgelehnt
|
||||
- [ ] Test: ein Lauf auf Version 1 bleibt nach Veröffentlichen von Version 2 unverändert und zeigt weiter die Schritte aus Version 1
|
||||
- [ ] Test: Export und anschließender Import ergeben eine inhaltsgleiche Vorlage
|
||||
- [ ] Test: `TECHNIKER` darf nicht veröffentlichen
|
||||
- [ ] Test: Veröffentlichen schreibt einen `Protokoll`-Eintrag
|
||||
@@ -0,0 +1,31 @@
|
||||
# #010 M5 – Vorlagen-Editor
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, frontend
|
||||
**Geschätzt:** 10 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Der Editor, in dem ein Redakteur eine Vorlage baut, ohne den Kopf über Versionen zu verlieren.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Vorlagenliste mit Suche, Filter nach Kategorie und Tag, Kennzeichnung „Entwurf vorhanden"
|
||||
- [ ] Editor: Abschnitte und Schritte anlegen, bearbeiten, löschen; Abschnitte mit zweistelliger Nummer (`01 · Vorbereitung`)
|
||||
- [ ] Sortieren per Drag & Drop mit `@dnd-kit`, auch am Handy mit dem Finger bedienbar
|
||||
- [ ] Je Schritt: Titel, Typ (`HAKEN`, `TEXT`, `ZAHL`, `AUSWAHL`, `DATUM`, `BEFEHL`), Pflicht (roter Stern), Hilfetext als Markdown, Doku-Link
|
||||
- [ ] Typabhängige `konfig`-Formulare: Auswahloptionen, Min/Max/Einheit, bei `BEFEHL` die Shell (`powershell`/`bash`/`cmd`), `multiline` und der Befehlstext
|
||||
- [ ] Variablen definieren: `key`, Label, Typ, Optionen, Pflicht, Position
|
||||
- [ ] Vorschau der Vorlage wie im Lauf, mit Beispielwerten
|
||||
- [ ] Veröffentlichen mit Änderungsnotiz, Versionshistorie ansehen, Vorlage duplizieren, JSON-Export und -Import
|
||||
- [ ] Warnung im Editor bei unbekannten Platzhaltern in Titel, Hilfetext und Befehlstext
|
||||
- [ ] Screenshots Liste, Editor, Historie in `docs/screenshots/m5/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Eine Vorlage lässt sich vollständig ohne Umweg über die Datenbank anlegen, sortieren, veröffentlichen
|
||||
- [ ] Eine veröffentlichte Version ist im Editor nur lesend; „Bearbeiten" erzeugt sichtbar einen Entwurf
|
||||
- [ ] Unbekannte Platzhalter werden als Warnung angezeigt, blockieren aber das Speichern nicht
|
||||
- [ ] Sortieren funktioniert am Handy bei 390 px Breite ohne horizontales Scrollen
|
||||
- [ ] Der Markdown-Hilfetext rendert ohne rohes HTML
|
||||
@@ -0,0 +1,33 @@
|
||||
# #011 M6 – Mitarbeiter, Abteilungen, Geräte
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, frontend
|
||||
**Geschätzt:** 9 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Die Stammdaten, an denen die Historie hängt. Mitarbeiter und Geräte werden nie gelöscht, damit archivierte Läufe für immer auf sie zeigen können.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Mitarbeiterliste: Suche über Anzeigename, Mail, UPN und `sam`, Filter nach Abteilung und Aktiv-Status
|
||||
- [ ] Mitarbeitersuche als wiederverwendbare Komponente: standardmäßig nur aktive, Umschalter „auch deaktivierte" fürs Offboarding
|
||||
- [ ] Mitarbeiterseite: Stammdaten, Abteilung, Vorgesetzter, seine Geräte, alle Läufe chronologisch (Onboarding, Übergaben, Offboarding)
|
||||
- [ ] Mitarbeiter manuell anlegen und bearbeiten (`herkunft = MANUELL`); synchronisierte Felder sind bei `herkunft = ABGLEICH` gesperrt
|
||||
- [ ] Deaktivieren statt löschen (`aktiv = false`)
|
||||
- [ ] Abteilungen: Name (eindeutig je Kunde), Ziel-OU, Standardgruppen (`String[]`), Mail-Domain, Notizen
|
||||
- [ ] Geräte: Stammdaten nach Vorgabe, Pflichtregel „`hostname` oder `bezeichnung` muss gesetzt sein", Besitzer optional
|
||||
- [ ] Geräteliste mit Suche über Hostname, Bezeichnung, Seriennummer und `tagCode`, Filter nach Typ
|
||||
- [ ] CSV-Import für Geräte (`hostname`, `label`, `serialNumber`, `inventoryNumber`, `type`, `model`), `tagCode` wird beim Import erzeugt; eigener `src/lib/csv.ts`
|
||||
- [ ] Archivieren statt Löschen, sobald Läufe an einem Gerät oder einer Abteilung hängen
|
||||
- [ ] Screenshots Mitarbeiterliste, Mitarbeiterseite, Geräteliste, Abteilungen in `docs/screenshots/m6/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: Mitarbeitersuche liefert standardmäßig nur `aktiv = true`; mit Umschalter auch deaktivierte
|
||||
- [ ] Test: ein Gerät ohne `hostname` und ohne `bezeichnung` wird abgelehnt
|
||||
- [ ] Test: ein Gerät mit Läufen lässt sich nicht löschen, nur archivieren
|
||||
- [ ] Test: `tagCode` eines bestehenden Geräts lässt sich nicht ändern
|
||||
- [ ] Test: CSV-Import erzeugt je Zeile einen `tagCode`, meldet fehlerhafte Zeilen einzeln und bricht nicht ab
|
||||
- [ ] Test: bei `herkunft = ABGLEICH` lehnt der Service Änderungen an synchronisierten Feldern ab
|
||||
@@ -0,0 +1,36 @@
|
||||
# #012 M7 – Platzhalter, Namensregeln, Escaping
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, security
|
||||
**Geschätzt:** 8 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Reine, framework-freie Service-Schicht mit dichter Testabdeckung. Hier entstehen die fertigen PowerShell-Befehle, die der Techniker kopiert — und hier darf nichts schiefgehen, weder beim Escaping noch beim generierten Passwort.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Platzhalter-Auflösung `{{key}}` in Schritt-Titel, Hilfetext und Befehlstext
|
||||
- [ ] Eingebaut aus Gerät und Lauf: `{{hostname}}`, `{{seriennummer}}`, `{{inventarnummer}}`, `{{ticket}}`, `{{datum}}`, `{{techniker}}`; im Modus `DIENSTLEISTUNG` zusätzlich `{{kunde}}` und `{{kundenkuerzel}}`, im Modus `INTERN` leer
|
||||
- [ ] Eingebaut aus dem Mitarbeiter: `{{mitarbeiter}}` (Alias `{{benutzer}}`), `{{vorname}}`, `{{nachname}}`, `{{upn}}`, `{{sam}}`, `{{mail}}`, `{{abteilung}}`, `{{titel}}`, `{{vorgesetzter}}`, `{{vorgesetzter_sam}}`, `{{dn}}`, `{{ou}}`
|
||||
- [ ] Aus der gewählten Abteilung: `{{ou}}` falls nicht vom Mitarbeiter, `{{gruppen}}` kommagetrennt
|
||||
- [ ] `{{ou}}` eines synchronisierten Mitarbeiters ist der Eltern-Container seines `dn` (alles nach dem ersten Komma)
|
||||
- [ ] Dazu die in der Vorlage definierten Variablen; unbekannte Platzhalter bleiben sichtbar stehen
|
||||
- [ ] `{{generiertes_passwort}}`: 20 Zeichen aus Groß, Klein, Ziffern und Sonderzeichen ohne verwechselbare Zeichen, je Anzeige neu erzeugt, **nie** gespeichert; Hinweis „Passwort sofort in den Passwortmanager übernehmen"; in der Druckansicht `[Passwort – nicht gespeichert]`
|
||||
- [ ] Namensregeln (`Einstellung.namensregeln` global, im Modus `DIENSTLEISTUNG` zusätzlich `Kunde.namensregeln` mit Fallback): `upn`, `sam` (höchstens 20 Zeichen), `mail`, `hostname`
|
||||
- [ ] Transliteration (ä→ae, ö→oe, ü→ue, ß→ss, Akzente entfernt), Kleinschreibung, Leerzeichen entfernt, `{nnn}`-Zähler, `{v}` für den ersten Buchstaben des Vornamens, `{kundenkuerzel}`
|
||||
- [ ] Shell-Escaping je Shell: PowerShell (doppelte Anführungszeichen verdoppeln, Backtick maskieren), Bash, Cmd
|
||||
- [ ] HTML-Escaping ersetzter Werte; Markdown im Hilfetext sicher gerendert, kein rohes HTML
|
||||
- [ ] „Alle Befehle als Skript kopieren" je Abschnitt: alle `BEFEHL`-Schritte in Reihenfolge, getrennt durch Leerzeilen, je mit `# Schritt: <Titel>` davor, Zeilenumbruch passend zur Shell
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: „Jörg Müller-Lüdenscheid" ergibt `joerg.mueller-luedenscheid@firma.de` und `jmuellerluedensch` (auf 20 Zeichen gekürzt)
|
||||
- [ ] Test: `{{generiertes_passwort}}` erscheint im angezeigten Befehl, steht aber nicht in `Schrittergebnis`, `Kommentar`, `Protokoll` oder der Druckansicht; zwei Aufrufe liefern verschiedene Passwörter
|
||||
- [ ] Test: ein Wert mit `"` und `` ` `` wird für PowerShell korrekt maskiert und bricht den Befehl nicht
|
||||
- [ ] Test: unbekannte Platzhalter bleiben als `{{key}}` stehen
|
||||
- [ ] Test: `{{ou}}` wird aus `CN=Maria Huber,OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local` zu `OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local`
|
||||
- [ ] Test: Skript-Kopie liefert die Befehle in Abschnittsreihenfolge mit Kommentarzeilen
|
||||
- [ ] Test: im Modus `INTERN` sind `{{kunde}}` und `{{kundenkuerzel}}` leer
|
||||
- [ ] Test: ein Wert mit `<script>` landet HTML-escaped in der Anzeige
|
||||
@@ -0,0 +1,47 @@
|
||||
# #013 M8 – Läufe: Service und Abarbeitung
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, frontend
|
||||
**Geschätzt:** 14 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Das Herz der Anwendung: ein Lauf wird vorbereitet, abgearbeitet, abgeschlossen und archiviert. Der Archiv-Schreibschutz sitzt an genau einer Stelle.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] `laufAnlegenService`: aus veröffentlichter Version, Kunde per `kundeErmitteln()`, optional Mitarbeiter (Verzeichnis oder Freitext `mitarbeiterName`), optional Gerät, Ticket, Variablen, Zuweisung, Fälligkeit
|
||||
- [ ] Ein Lauf kann als `OFFEN` angelegt werden, ohne ihn zu starten — das ist der Zuweisungs-Mechanismus, keine eigene Tabelle
|
||||
- [ ] `gestartetAm` und `IN_ARBEIT` erst beim ersten Öffnen zur Bearbeitung
|
||||
- [ ] Titelvorschlag `<Vorlage> – <Hostname oder Mitarbeiter> – <Datum>`
|
||||
- [ ] `schrittergebnisSetzenService`: einzeln je Schritt (kein Gesamtformular), `zustand`, `wert`, `notiz`, `aktualisiertVonId`, `Protokoll`-Eintrag
|
||||
- [ ] `NICHT_ZUTREFFEND` und `FEHLGESCHLAGEN` verlangen eine Notiz, sonst `LaufFehler`
|
||||
- [ ] `abschnittNichtZutreffendService`: setzt alle Schritte eines Abschnitts mit einer Notiz auf `NICHT_ZUTREFFEND` (für die beiden Konto-Abschnitte im Onboarding)
|
||||
- [ ] `laufAbschliessenService`: nur wenn alle Pflichtschritte `ERLEDIGT` oder `NICHT_ZUTREFFEND` sind; setzt `abgeschlossenAm`, `abgeschlossenVonId`
|
||||
- [ ] `laufArchivierenService` und `laufZurueckholenService` (nur `ADMIN`), beide mit `Protokoll`-Eintrag
|
||||
- [ ] `laufMussVeraenderbarSein(lauf)` zentral, aufgerufen vor jeder Änderung an Kopfdaten, `Schrittergebnis` und `Kommentar`
|
||||
- [ ] Kommentare je Lauf und je Schrittergebnis
|
||||
- [ ] „Mit AD-Konto verknüpfen": ein Lauf mit `mitarbeiterName` wird nachträglich über UPN oder Name mit einem `Mitarbeiter` verbunden
|
||||
- [ ] Start-Dialog: Vorlage → Mitarbeiter → Gerät (wählen, neu anlegen oder scannen) → Ticket, Variablen, Zuweisung, Fälligkeit; Vorbelegung je Aufrufort, Besitzer des Geräts als Mitarbeiter vorgeschlagen; Vorschläge aus den Namensregeln für `upn`, `sam`, `mail`
|
||||
- [ ] Abarbeitungsansicht: einklappbare Abschnitte mit `01 ·`-Nummerierung, Fortschrittsbalken in Lime, je Schritt Zustand, Wert, Notiz, Kommentare und „wer wann"
|
||||
- [ ] Erledigte Schritte durchgestrichen in Nebentext-Farbe, Checkbox erledigt als Lime-Kachel mit dunklem Haken, Pflichtschritte mit rotem Stern
|
||||
- [ ] `BEFEHL`-Schritte als Codeblock auf `#0B0D10` mit Lime-Schrift, Kopier-Knopf, Warnung wenn ein Platzhalter unaufgelöst ist; je Abschnitt „Alle Befehle als Skript kopieren"
|
||||
- [ ] `ZAHL` außerhalb von Min/Max wird markiert, aber nicht blockiert
|
||||
- [ ] Route Handler `GET /api/laeufe/[id]/zustand`, Client pollt alle 10 s und pausiert bei verstecktem Tab
|
||||
- [ ] Umschalter „Kompakt / Ausführlich", ausführlich klappt alle Hilfetexte auf
|
||||
- [ ] Abschließen und Archivieren als getrennte Aktionen
|
||||
- [ ] Screenshots Start-Dialog, Lauf Desktop, Lauf Handy, Befehlsschritt in `docs/screenshots/m8/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: ein Lauf ohne Mitarbeiter und ohne Gerät lässt sich anlegen, abschließen und archivieren
|
||||
- [ ] Test: Abschließen scheitert, solange ein Pflichtschritt `OFFEN` ist
|
||||
- [ ] Test: `NICHT_ZUTREFFEND` ohne Notiz wird abgelehnt, mit Notiz akzeptiert
|
||||
- [ ] Test: an einem archivierten Lauf schlagen Änderung eines Schrittergebnisses, eines Kommentars und der Kopfdaten fehl
|
||||
- [ ] Test: nur `ADMIN` holt einen archivierten Lauf auf `ABGESCHLOSSEN` zurück, mit `Protokoll`-Eintrag
|
||||
- [ ] Test: `gestartetAm` bleibt leer, solange der Lauf nur `OFFEN` ist
|
||||
- [ ] Test: jede Änderung an einem Schrittergebnis, jeder Statuswechsel und jedes Veröffentlichen steht im `Protokoll`
|
||||
- [ ] Test: die Abschnitts-Aktion setzt alle Schritte des Abschnitts mit derselben Notiz
|
||||
- [ ] Zwei Browser auf demselben Lauf sehen die Änderung des anderen nach höchstens 10 s
|
||||
- [ ] Am Handy bei 390 px kein horizontales Scrollen
|
||||
@@ -0,0 +1,38 @@
|
||||
# #014 M9 – QR-Tags, Geräteseite, Etiketten, Scanner
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, frontend
|
||||
**Geschätzt:** 12 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Der Einstieg für den Techniker, der am Gerät steht: scannen, sehen was zu tun ist, loslegen.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Stapel-Erzeugung von N unregistrierten Tags (Standard 50) für vorgedruckte Aufkleber
|
||||
- [ ] Route `/t/[tagCode]` mit Anmeldepflicht und Rücksprung; drei Fälle: registriert → Geräteseite; existiert unregistriert → Registrierformular; unbekannt → Hinweis „Kein Laufzettel-Tag" mit Link zur Suche
|
||||
- [ ] Registrierformular: Typ, Hostname oder Bezeichnung, Seriennummer, Inventarnummer, optional Besitzer per Mitarbeitersuche; danach Geräteseite
|
||||
- [ ] Geräteseite `/geraete/[id]`, handyoptimiert: Kopf mit Hostname bzw. Bezeichnung, Typ, Besitzer mit Abteilung, Seriennummer, `tagCode` in JetBrains Mono
|
||||
- [ ] Abschnitt „Zugewiesen": alle Läufe zu diesem Gerät in `OFFEN` und `IN_ARBEIT` mit Fortschritt, Zuweisung und Fälligkeit, Knopf „Starten" bzw. „Weiter"
|
||||
- [ ] Abschnitt „Schnellstart": Vorlagen aus `GeraetetypVorlage` für diesen Gerätetyp, ein Tipp startet den Lauf mit vorbelegtem Gerät und Besitzer
|
||||
- [ ] Abschnitt „Vorlage zuweisen": Vorlage wählen, optional Techniker und Fälligkeit, erzeugt einen Lauf in `OFFEN`
|
||||
- [ ] Abschnitt „Historie": abgeschlossene und archivierte Läufe chronologisch
|
||||
- [ ] QR-Erzeugung serverseitig als SVG mit `qrcode`, Fehlerkorrektur M, Inhalt ausschließlich `{APP_URL}/t/{tagCode}`, keine Personendaten, keine externe API
|
||||
- [ ] Etikettendruck mit Print-CSS in zwei Layouts: Etikettendrucker 62 × 29 mm (ein Etikett pro Seite) und A4-Bogen 3 × 8 à 70 × 37 mm; je Etikett QR, `tagCode` lesbar darunter, optional Hostname und Besitzer, kleines Laufzettel-Logo; immer hell
|
||||
- [ ] Im Modus `DIENSTLEISTUNG` zeigt das Etikett zusätzlich das Kundenkürzel, im Modus `INTERN` nie
|
||||
- [ ] Knopf „Etikett drucken" auf der Geräteseite, Stapeldruck für unregistrierte Tags
|
||||
- [ ] „Scannen" in der App: `barcode-detector` (native BarcodeDetector-API mit Polyfill-Fallback), Kamera im Browser, direkter Sprung zur Geräteseite
|
||||
- [ ] Screenshots Geräteseite Handy, Registrierformular, Etikett 62 × 29, A4-Bogen, Scanner in `docs/screenshots/m9/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: `/t/LZ-7K3F9Q` ohne Anmeldung leitet zum Login und danach auf die Geräteseite von `PC-001`
|
||||
- [ ] Test: „Vorlage zuweisen" erzeugt einen Lauf in `OFFEN`, der unter „Zugewiesen" erscheint; „Starten" öffnet ihn und setzt `IN_ARBEIT`
|
||||
- [ ] Test: unregistrierter Tag führt zum Registrierformular, danach zur Geräteseite; unbekannter Tag zeigt den Hinweis
|
||||
- [ ] Test: mehrere Zuweisungen je Gerät sind gleichzeitig möglich
|
||||
- [ ] Test: der QR-Inhalt ist genau `{APP_URL}/t/{tagCode}` und enthält keine Personendaten
|
||||
- [ ] Test: ein bei 300 dpi gerastertes 62 × 29-mm-Etikett wird von einem QR-Decoder korrekt zurückgelesen
|
||||
- [ ] Beide Druckbilder sind sauber, ohne Sidebar, ohne dunkle Flächen, ohne abgeschnittene Etiketten
|
||||
- [ ] Die Geräteseite ist bei 390 px ohne horizontales Scrollen bedienbar
|
||||
@@ -0,0 +1,28 @@
|
||||
# #015 M10 – Dashboard und Archiv
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, frontend, backend
|
||||
**Geschätzt:** 8 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Der Einstieg am Schreibtisch und das Gedächtnis der Anwendung.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Dashboard: meine offenen Läufe, überfällige Läufe, zuletzt bearbeitet, Schnellstart der häufigsten Vorlagen
|
||||
- [ ] Archiv-Volltextsuche über Titel, Notizen, Werte und Kommentare in einem gemeinsamen Query-Modul mit `pg_trgm` und `ILIKE`
|
||||
- [ ] Filter nach Vorlage, Mitarbeiter, Abteilung, Gerät, Zeitraum und Bearbeiter; im Modus `DIENSTLEISTUNG` zusätzlich nach Kunde
|
||||
- [ ] Druckansicht eines Laufs: Print-CSS, Kopfdaten, alle Abschnitte und Schritte, Werte, Notizen, Namen, Zeitstempel; ersetzt im MVP den PDF-Export
|
||||
- [ ] In der Druckansicht erscheinen vom Mitarbeiter nur Name, UPN und Abteilung, sonst nichts
|
||||
- [ ] Screenshots Übersicht, Archiv, Druckansicht in `docs/screenshots/m10/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: ein archivierter Lauf wird über ein Wort aus einer Schritt-Notiz gefunden
|
||||
- [ ] Test: ein archivierter Lauf wird über ein Wort aus einem Kommentar gefunden
|
||||
- [ ] Test: die Filter kombinieren sich (Vorlage und Zeitraum und Bearbeiter)
|
||||
- [ ] Test: `{{generiertes_passwort}}` erscheint nicht in der Druckansicht
|
||||
- [ ] Test: überfällige Läufe sind genau die mit `faelligAm < heute` und Status `OFFEN` oder `IN_ARBEIT`
|
||||
- [ ] Die Druckansicht ist hell, ohne Navigation, und passt auf A4
|
||||
@@ -0,0 +1,39 @@
|
||||
# #016 M11 – Verzeichnis und LDAP-Abgleich
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend, frontend, security
|
||||
**Geschätzt:** 12 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Die Mitarbeiter der eigenen Firma kommen aus dem Active Directory, damit beim Start eines Laufs nichts abgetippt werden muss. Gilt nur für die eigene Organisation; im Modus `DIENSTLEISTUNG` ist das Verzeichnis-Modul ausgeblendet.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] `ldapts` 9.x: `ldaps` bevorzugt, StartTLS optional, eigenes CA-Zertifikat hinterlegbar (`caCertPem`), `minVersion: TLSv1.2`
|
||||
- [ ] `searchPaginated` mit `pageSize: 1000` — **nicht** `search` mit `paged`, das puffert alles im Speicher
|
||||
- [ ] `explicitBufferAttributes: ['objectGUID']` und korrekte Umsortierung der gemischt-endianen Bytes zur GUID-Zeichenkette als `externeId`
|
||||
- [ ] Standardfilter `(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))`
|
||||
- [ ] Attribut-Mapping mit AD-Vorgaben: `givenName`, `sn`, `displayName`, `mail`, `userPrincipalName` → `upn`, `sAMAccountName` → `sam`, `department`, `title`, `manager` → `vorgesetzterExterneId`, `distinguishedName` → `dn`
|
||||
- [ ] „Verbindung testen": zeigt die ersten 10 Treffer mit gemappten Feldern, ohne etwas zu schreiben
|
||||
- [ ] `abgleichService`: anlegen, aktualisieren, verschwundene oder deaktivierte Konten auf `aktiv = false` — **nie löschen**; Ergebnis (angelegt/aktualisiert/deaktiviert/Fehler) in `letzterAbgleichStatus`, `letzterAbgleichFehler`, `letzterAbgleichAm` und im `Protokoll`
|
||||
- [ ] Zeitplan mit `node-cron` im selben Prozess, Intervall je Quelle (Standard 60 min), Sperre über `AbgleichSperre` in der Datenbank, damit nur eine Instanz läuft; zusätzlich „Jetzt abgleichen"
|
||||
- [ ] Bind-Passwort verschlüsselt über `src/lib/geheimnisse.ts`, Serializer entfernt `bindPasswortVerschluesselt` aus jeder Antwort
|
||||
- [ ] Admin → Verzeichnis: Quelle anlegen und bearbeiten, Verbindung testen, Abgleich-Status und -Log, „Jetzt abgleichen"
|
||||
- [ ] Manuelle Mitarbeiter und CSV-Import (`givenName`, `sn`, `displayName`, `mail`, `upn`, `sam`, `department`, `title`)
|
||||
- [ ] Abteilungsvorschläge aus den `department`-Werten des Abgleichs, vom Admin in `Abteilung`-Einträge übernehmbar
|
||||
- [ ] Nur die gemappten Attribute speichern: keine Passwörter, keine Gruppenmitgliedschaften des Mitarbeiters
|
||||
- [ ] `docker-compose.test.yml` um OpenLDAP erweitern, mit AD-ähnlichem Fixture-Schema (`sAMAccountName`, `userPrincipalName`, binäres `objectGUID`) — aus #005 hierher verschoben, weil der Dienst erst hier benutzt wird
|
||||
- [ ] Screenshots Verzeichnis, Verbindungstest, Abgleich-Log, CSV-Import in `docs/screenshots/m11/`
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Integrationstest gegen den Container: erster Lauf legt Mitarbeiter an, zweiter Lauf aktualisiert sie ohne Duplikate
|
||||
- [ ] Integrationstest: ein aus dem Verzeichnis entferntes Konto wird `aktiv = false` und bleibt in der Datenbank
|
||||
- [ ] Integrationstest: ein per `userAccountControl` deaktiviertes Konto wird `aktiv = false`
|
||||
- [ ] Test: `{{ou}}` wird korrekt aus dem `dn` abgeleitet
|
||||
- [ ] Test: `objectGUID` als Buffer ergibt reproduzierbar dieselbe GUID-Zeichenkette (bekannter Byte-Vektor als Fixture)
|
||||
- [ ] Test: keine API-Antwort und kein Server-Log enthält das Bind-Passwort im Klartext
|
||||
- [ ] Test: zwei gleichzeitig gestartete Abgleiche derselben Quelle — der zweite wird von der Sperre abgewiesen
|
||||
- [ ] Im Modus `DIENSTLEISTUNG` ist Admin → Verzeichnis nicht erreichbar
|
||||
@@ -0,0 +1,37 @@
|
||||
# #017 M12 – Seed der sieben Vorlagen
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, backend
|
||||
**Geschätzt:** 8 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Die App ist nach dem Seed fachlich sofort brauchbar: sieben vollständige, fachlich korrekte Vorlagen als veröffentlichte Version 1.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Vorlage 1 „PC Neuinstallation" (Client), Variablen `os_version`, `join_typ`; Abschnitte Vorbereitung, Installation, Einbindung, Software, Sicherheit, Benutzer, Abschluss
|
||||
- [ ] Vorlage 2 „Mitarbeiter-Onboarding" (Benutzer), Variablen `konto_typ`, `vorname`, `nachname`, `upn`, `sam`, `mail`, `abteilung`, `ou`, `gruppen`, `eintrittsdatum`, `vorgesetzter`; **beide** Konto-Abschnitte (AD und Entra ID), weil bedingte Abschnitte erst Phase 3 sind
|
||||
- [ ] Vorlage 3 „Mitarbeiter-Offboarding" (Benutzer), Variablen `austrittsdatum`, `ou_gesperrt`, `postfach_verbleib`, `vertreter`
|
||||
- [ ] Vorlage 4 „Server-Inbetriebnahme Windows-VM" (Server), Variablen `vmid`, `ip_adresse`, `vlan`, `rolle`, `os_version`
|
||||
- [ ] Vorlage 5 „Firewall-Rollout OPNsense" (Netzwerk), Variablen `wan_typ`, `lan_netz`, `vpn_typ`
|
||||
- [ ] Vorlage 6 „Monatlicher Patchday" (Wartung), Variable `monat`
|
||||
- [ ] Vorlage 7 „Backup-Restore-Test" (Wartung), Variablen `system`, `restore_typ`
|
||||
- [ ] Alle Schritte wortgetreu nach Auftrag mit Typ, Pflichtkennzeichen und `konfig`; die im Auftrag genannten Hilfetexte übernehmen, die übrigen fachlich korrekt und kurz ergänzen
|
||||
- [ ] 5 Mitarbeiter, darunter „Maria Huber", `m.huber@firma.example`, `sam` `mhuber`, Abteilung Buchhaltung, Vorgesetzte „Petra Klein"
|
||||
- [ ] 3 Abteilungen: Buchhaltung → `OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local` mit Gruppen `G-Buchhaltung, G-Alle`; Vertrieb; IT
|
||||
- [ ] Eine **deaktivierte** `Verzeichnisquelle` als Beispielkonfiguration
|
||||
- [ ] Geräte `PC-001` (PC, `LZ-7K3F9Q`, Besitzerin Maria Huber) und `NB-001` (Notebook, `LZ-M2XH4T`), dazu 5 unregistrierte Tags
|
||||
- [ ] `GeraetetypVorlage`: PC und NOTEBOOK → „PC Neuinstallation"; SERVER → „Server-Inbetriebnahme Windows-VM" und „Backup-Restore-Test"; FIREWALL → „Firewall-Rollout OPNsense"
|
||||
- [ ] `SEED_MODE=DIENSTLEISTUNG` (Alias `SERVICE`) erzeugt stattdessen „Musterfirma GmbH" (`MUS`) und „Bäckerei Reiter" (`REI`) mit je einer Person, einer Abteilung und einem Gerät
|
||||
- [ ] Alles idempotent über `upsert` mit stabilen Schlüsseln
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] `pnpm db:seed` zweimal hintereinander: keine Fehler, keine Duplikate, keine geänderten Zeilen
|
||||
- [ ] Alle sieben Vorlagen liegen als `VEROEFFENTLICHT`, Version 1 vor
|
||||
- [ ] Im Onboarding-Lauf steht im Schritt „Konto anlegen" der vollständige `New-ADUser`-Befehl mit Name, UPN, `-Path "OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local"` und der Gruppen-Befehl mit `G-Buchhaltung, G-Alle`
|
||||
- [ ] `SEED_MODE=SERVICE` erzeugt `MUS-PC-001` gemäß Hostname-Regel `{kundenkuerzel}-PC-{nnn}`
|
||||
- [ ] Die Beispiel-Verzeichnisquelle ist deaktiviert und löst keinen Abgleich aus
|
||||
- [ ] Kein Seed-Datensatz enthält ein Klartext-Passwort außer dem Admin-Passwort aus der Umgebung, und das nur als argon2-Hash
|
||||
@@ -0,0 +1,33 @@
|
||||
# #018 M13 – E2E-Tests, Anleitung, PDF
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, test, docs
|
||||
**Geschätzt:** 14 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Nachweis, dass die Abläufe wirklich durchlaufen, und eine Anleitung, mit der ein Techniker ohne Einweisung arbeiten kann.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Playwright-Hauptablauf: anmelden → „PC Neuinstallation" für `PC-001` starten → Schritte abhaken → Wert eintragen → einen Schritt „nicht zutreffend" mit Notiz → abschließen → archivieren → im Archiv finden → Schreibschutz greift
|
||||
- [ ] Derselbe Hauptablauf mit `SEED_MODE=SERVICE` gegen `MUS-PC-001`, mit Kundenwahl im Start-Dialog
|
||||
- [ ] Playwright-QR-Ablauf: `/t/LZ-7K3F9Q` ohne Anmeldung → Login → Geräteseite → „Vorlage zuweisen" → Lauf erscheint unter „Zugewiesen" → „Starten" setzt `IN_ARBEIT`; dazu unregistrierter Tag → Registrierformular → Geräteseite; unbekannter Tag → Hinweis
|
||||
- [ ] Playwright-Onboarding-Ablauf: Vorlage starten → Vorname und Nachname eingeben → UPN, `sam` und Mail werden vorgeschlagen → Abteilung „Buchhaltung" wählen → im Schritt „Konto anlegen" steht der fertige `New-ADUser`-Befehl → Kopieren liefert exakt diesen Text (Clipboard auslesen)
|
||||
- [ ] Playwright-Mobilprüfung: auf allen Hauptseiten bei 390 × 844 ist `scrollWidth <= clientWidth`
|
||||
- [ ] Playwright-Rollenprüfung: `LESER` sieht keine Schreib-Knöpfe und wird serverseitig abgewiesen
|
||||
- [ ] `pnpm docs:screenshots`: Playwright-Skript, Desktop 1440 × 900 und Handy 390 × 844, Seed-Daten, dunkles Design, mit Markierungen (Rahmen, Nummern) für die beschriebenen Stellen; Ablage `docs/anleitung/bilder/`
|
||||
- [ ] `pnpm docs:pdf`: Anleitung als PDF über Chromium (`page.pdf`, A4, `printBackground`), Ablage `docs/anleitung/laufzettel-anleitung.pdf`
|
||||
- [ ] `docs/anleitung/` auf Deutsch, getrennt für Techniker und Admins: Erste Schritte · Vorlagen bauen · Läufe abarbeiten (Desktop und Handy) · QR-Tags drucken und registrieren · Mitarbeiter und Abteilungen · Betriebsmodus · Admin: Verzeichnis, Benutzer, Einstellungen · Fehlerbehebung
|
||||
- [ ] `README.md` vollständig: Setup, alle Umgebungsvariablen, Betrieb mit Docker Compose, authentik-Anbindung inkl. Gruppen für Rollen, Seed, Sicherungshinweis für die Datenbank
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Alle Playwright-Abläufe grün, in `INTERN` und in `DIENSTLEISTUNG`
|
||||
- [ ] Der Kopier-Knopf liefert exakt den erwarteten Befehlstext, zeichengenau geprüft
|
||||
- [ ] Keine Hauptseite scrollt bei 390 px horizontal
|
||||
- [ ] `pnpm docs:screenshots` erzeugt alle in der Anleitung verlinkten Bilder reproduzierbar neu
|
||||
- [ ] `pnpm pruefe:doku` meldet kein fehlendes Bild
|
||||
- [ ] Das PDF enthält alle Kapitel mit Bildern, Umlaute korrekt, keine abgeschnittenen Seiten
|
||||
- [ ] Die README beschreibt authentik so, dass ein Dritter die Anbindung ohne Rückfrage hinbekommt
|
||||
@@ -0,0 +1,38 @@
|
||||
# #019 M14 – Betriebswege und Release v0.1.0
|
||||
|
||||
**Status:** offen
|
||||
**Meilenstein:** v0.1.0
|
||||
**Labels:** phase-1, infra, docs
|
||||
**Geschätzt:** 10 h **Tatsächlich:** –
|
||||
|
||||
## Ziel
|
||||
|
||||
Zwei erprobte Wege in den Betrieb und ein sauberes erstes Release.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] `deploy/install.sh`: native Installation auf Debian 13 — PostgreSQL 17 mit `listen_addresses = ''` (nur Unix-Socket), Node 24 aus dem NodeSource-Repo, corepack/pnpm, Dienstnutzer `laufzettel` ohne Login, Quellcode nach `/opt/laufzettel`, `pnpm install`, `pnpm build`, `prisma migrate deploy`, `prisma db seed`
|
||||
- [ ] Geheimnisse im Zielsystem erzeugen: `APP_SECRET` per `openssl rand -base64 48`, Zufallspasswörter für Datenbank und Admin; Ablage in `/etc/laufzettel/laufzettel.env` mit Modus 0600 und Eigentümer root
|
||||
- [ ] `laufzettel.service`: systemd-Unit für den Next-standalone-Server, `User=laufzettel`, `EnvironmentFile=`, `NoNewPrivileges=yes`, `ProtectSystem=strict`, `PrivateTmp=yes`, `Restart=on-failure`
|
||||
- [ ] Erneuter Aufruf aktualisiert statt neu anzulegen: `git pull`, `pnpm build`, `migrate deploy`, `systemctl restart`; Geheimnisse und Daten bleiben
|
||||
- [ ] `deploy/proxmox/laufzettel.sh`: One-Liner für die Proxmox-Shell — `set -Eeuo pipefail`, `trap` mit Zeilennummer, farbige Ausgabe, Spinner, Prüfung auf PVE 8/9 und root
|
||||
- [ ] `whiptail`-Dialog Standard gegen Erweitert; Standard: nächste freie CTID, Hostname `laufzettel`, Debian 13, 2 Kerne, 2 GB RAM, 8 GB Platte, unprivilegiert, DHCP auf `vmbr0`
|
||||
- [ ] Debian-Vorlage bei Bedarf per `pveam` holen, `pct create`, starten, `deploy/install.sh` im Container ausführen
|
||||
- [ ] `--dry-run`: gibt alle `pct`-Aufrufe nur aus, ohne etwas anzulegen
|
||||
- [ ] Abschlussbildschirm: URL, Admin-Kennung, erzeugtes Passwort, Hinweis auf Reverse Proxy und `docs/betrieb/`
|
||||
- [ ] `docs/betrieb/`: `inbetriebnahme.md`, `proxmox.md`, `reverse-proxy.md`, `authentik.md`, `sicherung.md` (`pg_dump` über den Dienstnamen bzw. den Unix-Socket)
|
||||
- [ ] Prüfliste für den Lauf auf echtem Proxmox, da ich den `pct`-Teil hier nicht ausführen kann
|
||||
- [ ] `docs/abnahme-mvp.md` abschließend durchgehen, Review-Agent gegen alle Kriterien, Befunde als Issues
|
||||
- [ ] Devlog-Phasenzusammenfassung: Gesamtstunden, Stunden je Meilenstein, größte Zeitfresser, Abweichung vom Plan
|
||||
- [ ] `CHANGELOG.md` für `v0.1.0` mit Datum, Version in `package.json`, Tag `v0.1.0`, Gitea-Release mit Changelog-Auszug, Anleitung als PDF beigelegt
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] `docker run --rm debian:13` mit `deploy/install.sh` installiert vollständig durch: Postgres läuft, Migration und Seed gelaufen, Dienst antwortet auf `/api/gesundheit`
|
||||
- [ ] Im nativen Weg lauscht PostgreSQL auf keinem TCP-Port (`ss -ltn` zeigt 5432 nicht)
|
||||
- [ ] Ein zweiter Lauf von `deploy/install.sh` aktualisiert und löscht keine Daten
|
||||
- [ ] `bash deploy/proxmox/laufzettel.sh --dry-run` gibt einen plausiblen, vollständigen `pct create`-Aufruf aus
|
||||
- [ ] `shellcheck --severity=warning` über alle Skripte ohne Befund
|
||||
- [ ] Im Compose-Weg ist die Datenbank vom Host nicht erreichbar, aus `web` schon
|
||||
- [ ] `pnpm pruefe:doku` grün, `v0.1.0` getaggt und als Gitea-Release veröffentlicht
|
||||
- [ ] Alle Kriterien aus `docs/abnahme-mvp.md` abgehakt oder als Folge-Issue erfasst
|
||||
@@ -0,0 +1,27 @@
|
||||
# Issues
|
||||
|
||||
Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Tracker.
|
||||
`pnpm pruefe:doku` erzwingt, dass Dateien und diese Übersicht übereinstimmen.
|
||||
|
||||
| Nr. | Titel | Status | Meilenstein | Geschätzt | Tatsächlich |
|
||||
|---|---|---|---|---|---|
|
||||
| #001 | [Phase 1: MVP](001-epic-phase-1-mvp.md) | in Arbeit | v0.1.0 | – | – |
|
||||
| #002 | [Phase 2: Verzeichnis-Alternativen, Bausteine, Anhänge](002-epic-phase-2.md) | offen | v0.2.0 | – | – |
|
||||
| #003 | [Phase 3: Protokoll, bedingte Schritte, Freigabe](003-epic-phase-3.md) | offen | v0.3.0 | – | – |
|
||||
| #004 | [Phase 4: Aktionen, Connectors, REST-API](004-epic-phase-4.md) | offen | v0.4.0 | – | – |
|
||||
| #005 | [M0 – Gerüst, Prozess, Design-Token](005-geruest-prozess-design.md) | erledigt | v0.1.0 | 6 h | 1 h 25 min |
|
||||
| #006 | [M1 – Datenmodell](006-datenmodell.md) | offen | v0.1.0 | 5 h | – |
|
||||
| #007 | [M2 – Auth, Rollen, Benutzer](007-auth-rollen-benutzer.md) | offen | v0.1.0 | 8 h | – |
|
||||
| #008 | [M3 – Betriebsmodus und Kunden](008-betriebsmodus-kunden.md) | offen | v0.1.0 | 6 h | – |
|
||||
| #009 | [M4 – Vorlagen-Versionierung im Service](009-vorlagen-versionierung.md) | offen | v0.1.0 | 6 h | – |
|
||||
| #010 | [M5 – Vorlagen-Editor](010-vorlagen-editor.md) | offen | v0.1.0 | 10 h | – |
|
||||
| #011 | [M6 – Mitarbeiter, Abteilungen, Geräte](011-mitarbeiter-abteilungen-geraete.md) | offen | v0.1.0 | 9 h | – |
|
||||
| #012 | [M7 – Platzhalter, Namensregeln, Escaping](012-platzhalter-namensregeln.md) | offen | v0.1.0 | 8 h | – |
|
||||
| #013 | [M8 – Läufe: Service und Abarbeitung](013-laeufe-abarbeitung.md) | offen | v0.1.0 | 14 h | – |
|
||||
| #014 | [M9 – QR-Tags, Geräteseite, Etiketten, Scanner](014-qr-tags-geraeteseite.md) | offen | v0.1.0 | 12 h | – |
|
||||
| #015 | [M10 – Dashboard und Archiv](015-dashboard-archiv.md) | offen | v0.1.0 | 8 h | – |
|
||||
| #016 | [M11 – Verzeichnis und LDAP-Abgleich](016-verzeichnis-ldap.md) | offen | v0.1.0 | 12 h | – |
|
||||
| #017 | [M12 – Seed der sieben Vorlagen](017-seed-sieben-vorlagen.md) | offen | v0.1.0 | 8 h | – |
|
||||
| #018 | [M13 – E2E-Tests, Anleitung, PDF](018-e2e-anleitung-pdf.md) | offen | v0.1.0 | 14 h | – |
|
||||
| #019 | [M14 – Betriebswege und Release v0.1.0](019-betrieb-release.md) | offen | v0.1.0 | 10 h | – |
|
||||
| | **Summe** | | | **136 h** | **1 h 25 min** |
|
||||
Reference in New Issue
Block a user