Files
flancer/README.md
jbergner 57f7470310
All checks were successful
release-tag / release-image (push) Successful in 2m6s
Mobile-Update + Bericht
2026-08-14 18:17:38 +02:00

237 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pocketwatch Go
Ein schlanker, self-hosted Zeiterfasser in Go funktional an `winnicodes/pocketwatch` angelehnt, aber ohne React, Node.js, PHP oder Nginx im Anwendungs-Stack.
Die Anwendung besteht aus einem Go-Binary, eingebettetem HTML/CSS/Vanilla-JavaScript und SQLite über `modernc.org/sqlite` (pure Go, kein CGO notwendig).
## Enthalten
- Start-/Stop-Timer mit Live-Anzeige
- Laufender Timer überlebt Reloads und Gerätewechsel
- Kunde + Tätigkeit, Kunden-Autocomplete nach letzter Nutzung
- Tages- und Wochensumme
- Verlauf gruppiert nach Tagen
- Suche über Kunde und Tätigkeit
- Zeitraumfilter: Tag, Woche, Monat, Jahr, alle
- Zeitraum vor/zurück schalten
- Einträge nachtragen, bearbeiten und löschen
- Rundung 160 Minuten, normal oder immer aufwärts
- CSV-Export (Semikolon + UTF-8 BOM für Excel)
- PDF-Export ohne PDF-Framework
- Service-Bericht als PDF pro Kunde mit Tätigkeitsübersicht, Auftrag/Ticket, Ansprechpartner, Einsatzort, Bemerkungen und Unterschriftsfeldern für Kunde/Techniker
- Export wahlweise für aktuelle Ansicht oder freien Datumsbereich, auf-/absteigend und kompakt
- Responsive Desktop-/Mobile-Oberfläche
- Mobile Kompaktansicht: startet mit dem Timer im Fokus; Verlauf/Übersicht und Einstellungen werden separat über die untere Navigation geöffnet
- Mehrbenutzerbetrieb mit strikt getrennten Daten
- Admin-/Benutzerrollen
- Benutzer anlegen, deaktivieren und Passwörter zurücksetzen
- Eigenes Passwort ändern
- Keine öffentliche Registrierung nach der Ersteinrichtung
- Sessions in SQLite, HttpOnly-Cookie, CSRF-Token
- Login-Rate-Limit
- CSP und weitere Security-Header
- SQLite WAL, Foreign Keys, Busy Timeout
- Healthcheck unter `/healthz`
- Docker/Compose, unprivilegierter Runtime-Benutzer, alle Linux-Capabilities entfernt
## Abhängigkeiten
Zur Laufzeit gibt es nur eine externe Go-Abhängigkeit:
```text
modernc.org/sqlite
```
Alles andere verwendet die Go-Standardbibliothek. Das Frontend hat **keine** npm-/Node-Abhängigkeiten und keinen separaten Build-Schritt.
## Schnellstart mit Docker Compose
```bash
docker compose up -d --build
```
Danach:
```text
http://localhost:8080
```
Beim ersten Aufruf erscheint die Ersteinrichtung. Der erste Account wird Administrator.
Die Daten liegen im Docker-Volume `pocketwatch-data` in `/data/pocketwatch.db`. Das Image enthält außerdem einen Docker-Healthcheck gegen `/healthz`.
Bei einem Host-Bind-Mount statt eines Named Volumes muss das Zielverzeichnis für UID/GID `10001` schreibbar sein.
## Lokal ohne Docker
Voraussetzungen:
- Go 1.26+
Dann:
```bash
go mod tidy
DATA_DIR=./data APP_ADDR=:8080 go run ./cmd/pocketwatch
```
Oder:
```bash
make run
```
## Konfiguration
| Variable | Default | Bedeutung |
|---|---:|---|
| `APP_ADDR` | `:8080` | Listen-Adresse des HTTP-Servers |
| `DATA_DIR` | `/data` | Verzeichnis für `pocketwatch.db` |
| `COOKIE_SECURE` | `false` | Auf `true` setzen, wenn die App ausschließlich über HTTPS erreichbar ist |
### Hinter Reverse Proxy / HTTPS
Wenn z. B. Caddy, Traefik oder nginx TLS terminiert:
```yaml
environment:
COOKIE_SECURE: "true"
```
Die App setzt selbst keine CORS-Header. API und UI sind als Same-Origin-Anwendung gedacht.
## Datenmodell
SQLite enthält vier Kernbereiche:
- `users` Accounts, Rollen, Aktivstatus
- `sessions` gehashte Session-Tokens + CSRF-Token
- `entries` Zeiteinträge, immer mit `user_id`
- `user_settings` persönliche Rundungs-, Export- und Anzeigeeinstellungen
Ein partieller Unique-Index stellt sicher, dass pro Benutzer höchstens ein laufender Timer existiert.
Zeitpunkte werden als Unix-Millisekunden gespeichert. Die Web-Oberfläche verwendet die lokale Browser-Zeitzone; Exporte verwenden die persönliche IANA-Zeitzone, z. B. `Europe/Berlin`.
## Sicherheit
### Passwörter
Passwörter werden mit PBKDF2-HMAC-SHA256, zufälligem Salt und 310.000 Iterationen gespeichert. Die Implementierung nutzt `crypto/pbkdf2`, `crypto/sha256` und `crypto/rand` aus der Go-Standardbibliothek von Go 1.26.
### Sessions
- 256-Bit zufällige Session-Tokens
- nur SHA-256-Digest des Session-Tokens in SQLite
- HttpOnly-Cookie
- SameSite=Lax
- optional `Secure`
- serverseitiges Ablaufdatum
- Sessions werden beim Deaktivieren eines Benutzers oder Passwort-Reset invalidiert
### CSRF
Schreibende API-Aufrufe benötigen zusätzlich ein zufälliges, sitzungsgebundenes `X-CSRF-Token`.
### Mandantentrennung
Jede SQL-Operation auf Zeiten enthält die `user_id` aus der authentifizierten Session. IDs aus einem anderen Benutzerkonto reichen daher nicht aus, um fremde Einträge zu lesen oder zu verändern.
## Mobile Kompaktansicht
Auf Smartphones (bis 820 px Breite) ist standardmäßig die **Kompaktansicht** aktiv. Dabei verschwindet die Desktop-Kopfleiste und die Timer-Erfassung nutzt nahezu den gesamten verfügbaren Bildschirm. Kunde, Tätigkeit, Timer, Start/Stop und die Tages-/Wochensummen bleiben direkt sichtbar.
Die untere mobile Navigation trennt die Bereiche bewusst:
- **Timer** konzentrierte Zeiterfassung
- **Übersicht** Verlauf, Suche, Filter, Nachtragen und Export
- **Mehr** Einstellungen, Benutzerverwaltung (für Admins) und Abmelden
Die Einstellung **„Kompaktansicht auf Mobilgeräten“** kann pro Benutzer deaktiviert werden. Sie wird in SQLite in `user_settings.mobile_compact` gespeichert. Bestehende Datenbanken werden beim Start automatisch um die neue Spalte ergänzt; der Standardwert ist aktiviert.
## Service-Berichte
Im Exportdialog steht zusätzlich **„Service-Bericht für Unterschrift“** zur Verfügung. Der Bericht verwendet den gewählten Exportzeitraum und filtert anschließend exakt auf einen Kunden. Dadurch können keine Tätigkeiten anderer Kunden versehentlich in denselben unterschreibbaren Bericht geraten.
Der Service-Bericht enthält:
- Kunde und Ansprechpartner
- Einsatzort
- Auftrags-/Ticketnummer
- Betreff sowie optionale Zusammenfassung/Bemerkungen
- chronologische Tätigkeiten mit Datum, Uhrzeit und gerundeter Dauer
- Gesamtdauer
- Ort und Berichtsdatum
- getrennte Unterschriftsfelder für Kunde und Techniker
Die zusätzlichen Berichtsdaten werden per `POST /api/service-report.pdf` übertragen und nicht als Query-Parameter in der Download-URL abgelegt. Es werden keine Service-Berichte oder Unterschriften dauerhaft in SQLite gespeichert; der PDF-Export wird bei Bedarf erzeugt.
## Backup
Wegen WAL sollte die Datenbank nicht blind während Schreibzugriffen als einzelne Datei kopiert werden. Der einfachste konsistente Weg bei Docker Compose:
```bash
docker compose stop pocketwatch
docker run --rm \
-v pocketwatch-go_pocketwatch-data:/data:ro \
-v "$PWD:/backup" \
alpine:3.22 \
tar czf /backup/pocketwatch-backup.tgz -C /data .
docker compose start pocketwatch
```
Der konkrete Volume-Name kann je nach Compose-Projektname abweichen (`docker volume ls`).
## Projektstruktur
```text
pocketwatch-go/
├── cmd/pocketwatch/main.go
├── internal/app/
│ ├── auth.go
│ ├── db.go
│ ├── export.go
│ ├── server.go
│ ├── *_test.go
│ └── web/
│ ├── index.html
│ ├── login.html
│ ├── app.css
│ ├── app.js
│ └── login.js
├── Dockerfile
├── docker-compose.yml
├── Makefile
└── go.mod
```
## Tests
```bash
go test ./...
```
Enthalten sind u. a. Tests für PBKDF2, Rundungslogik und den minimalen PDF-Writer.
## Bewusste Unterschiede zum ursprünglichen Pocketwatch
Diese Implementierung übernimmt das Produktkonzept und die wichtigsten Bedienabläufe, ist aber technisch ein Neuaufbau:
- SQLite statt JSON-Dateien
- Go statt PHP
- Vanilla JS statt React/Vite/Tailwind
- kein Node.js im Build oder Betrieb
- Login und Mehrbenutzerbetrieb
- Adminverwaltung
- serverseitige Datenisolation
- CSRF- und Session-Schutz
- Healthcheck und Security-Header
Die Oberfläche orientiert sich am dunklen, kompakten Amber-Design des Originals, ist aber kein 1:1 kopierter Frontend-Quellcode. Sie ist derzeit bewusst deutschsprachig; das Datenmodell hält die Spracheinstellung bereits für eine spätere vollständige Lokalisierung vor.
## Inspiration
Inspiriert von [`winnicodes/pocketwatch`](https://github.com/winnicodes/pocketwatch), das als minimalistischer self-hosted Zeiterfasser unter MIT veröffentlicht ist.