Files
Inventory/README.md
Denis Stoecklmeier dee25b5cbb
Some checks failed
Tests / postgres (push) Has been cancelled
Tests / postgres (pull_request) Has been cancelled
Merge remote-tracking branch 'origin/master' into feature/usb-aio-workflow
# Conflicts:
#	README.md
#	inventory/test_specification_migration.py
2026-10-14 20:04:37 +02:00

367 lines
17 KiB
Markdown

# CS-R Geräteverwaltung
Produktionsnahe interne Geräteverwaltung als kompakter Django-Monolith. Die Anwendung ersetzt die bisherige Excel-Liste und ist für den Betrieb auf einem kleinen Linux-VPS ausgelegt.
## Funktionen
- Rollenbasierte Anmeldung mit `ADMIN`, `EDITOR` und `VIEWER`
- Schneller Quick-Add für einzelne Geräte oder Serien mit automatisch fortlaufenden, niemals wiederverwendeten Gerätenummern
- Serverseitige Suche, kombinierbare Filter, Sortierung und Pagination
- Kompakte Gerätetabelle mit ein-/ausblendbaren Spalten und Inline-Bearbeitung
- Detail-Drawer und vollständige Bearbeitungsansicht
- Optimistic Concurrency gegen unbemerktes Überschreiben
- Globale, deaktivierbare Auswahlwerte und Mitarbeiterverwaltung
- Benutzerdefinierte Felder ohne neue Datenbankmigration
- Transaktionaler XLSX-Import mit Vorschau und Spaltenzuordnung
- CSV-, XLSX- und ODS-Export für LibreOffice, auch für gefilterte Bestände
- Bearbeitbare Statusfarben mit klar unterscheidbarer Farbpalette
- CPU-Livevorschläge aus einer Intel-/AMD-Masterliste mit Hersteller- und Serienfiltern
- Vollständige Änderungshistorie und gerätebezogenes Audit-Log
- Abgesicherte JSON-API mit getrennten Lese-/Schreibschlüsseln, Ablaufdatum und API-Audit
- Archivierung und Wiederherstellung statt versehentlichem Löschen
- PostgreSQL-Trigramm- und JSONB-Indizes für große Bestände
- Docker Compose mit PostgreSQL, Django/Gunicorn und Traefik/HTTPS
- Healthchecks, persistente Volumes und einfache Backup-/Restore-Skripte
## Anleitung für den LAN-Server
Für Kollegen: [Lokales Deployment und Serverbetrieb](docs/LOCAL_DEPLOY.md).
Die Anleitung enthält Zugang, Update mit Backup, Prüfung, Fehlerbehebung und
Datenbankzugriff für den LAN-Server.
## Lokale Entwicklung
Voraussetzungen: Python 3.12 oder neuer und PostgreSQL 17, oder Docker Compose.
Die Anwendung verwendet auch lokal PostgreSQL. Für bestehende SQLite-Daten siehe
[Docker-Migration zu PostgreSQL](docs/postgresql-migration.md).
```bash
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
# POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD und POSTGRES_HOST
# in der Prozessumgebung setzen (lokal typischerweise POSTGRES_HOST=localhost).
python manage.py migrate
python manage.py seed_initial_data
python manage.py createsuperuser
python manage.py runserver
```
Ohne PostgreSQL-Verbindung startet die Datenbankfunktion nicht; es wird keine
SQLite-Datenbank mehr automatisch geöffnet oder angelegt. `.env` wird von Docker
Compose geladen, bei direktem Python-Start müssen die Variablen gesetzt sein.
Der [Migrationsleitfaden](docs/postgresql-migration.md) enthält auch einen lokalen
Docker-Start ohne HTTPS-Domain.
Testdaten können gezielt erzeugt werden:
```bash
python manage.py generate_test_devices 5000
```
## Installation auf Netcup/Linux
### 1. Server vorbereiten
Empfohlen sind Ubuntu 24.04 oder Debian 12, mindestens 2 GB RAM, eine feste IPv4/IPv6-Adresse und eine Domain. In DNS einen A- und optional AAAA-Record der gewünschten Subdomain auf den Server zeigen lassen. Die Ports 22, 80 und 443 in der Firewall öffnen.
Docker gemäß der offiziellen Docker-Anleitung installieren. Anschließend prüfen:
```bash
docker --version
docker compose version
```
### 2. Projekt und Umgebungsvariablen
```bash
git clone <REPOSITORY-URL> csr-inventory
cd csr-inventory
cp .env.example .env
```
In `.env` mindestens ändern:
- `SECRET_KEY`: langer Zufallswert, z. B. mit `openssl rand -base64 48`
- `POSTGRES_PASSWORD`: starkes, einmaliges Datenbankpasswort
- `APP_HOST`: Domain ohne Protokoll/Port, z. B. `geraete.example.org`
- `ACME_EMAIL`: Kontaktadresse für öffentliche TLS-Zertifikate
- `ALLOWED_HOSTS`: dieselbe Domain sowie `127.0.0.1,localhost` für den internen Healthcheck
- `CSRF_TRUSTED_ORIGINS`: vollständige HTTPS-URL
Die Datei `.env` niemals in Git einchecken.
### 3. Starten
```bash
docker compose up -d --build
docker compose ps
docker compose logs -f web traefik
```
Beim Start wartet der Webdienst auf PostgreSQL, führt Migrationen und das idempotente Seed-Command aus und sammelt statische Dateien. Traefik fordert anschließend automatisch ein TLS-Zertifikat an.
### 4. Ersten Administrator anlegen
```bash
docker compose exec web python manage.py createsuperuser
```
Danach unter `https://IHRE-DOMAIN/accounts/login/` anmelden. Weitere Konten und ihre Rollen `ADMIN`, `EDITOR` oder `VIEWER` werden direkt im Bereich „Benutzer“ verwaltet. Es gibt keine öffentliche Registrierung.
## Aktualisierung
Vor jeder Aktualisierung ein Backup erstellen. Danach:
```bash
git pull
docker compose up -d --build
docker compose logs -f web
```
Migrationen und Seed-Daten werden beim Containerstart automatisch angewendet.
## Sichere API für interne Skripte
Die API läuft unter derselben HTTPS-Domain wie die Anwendung, zum Beispiel
`https://inventory.example.org/api/v1/`. Eine zusätzliche FastAPI-Anwendung oder
eine zweite Subdomain ist für diesen internen Anwendungsfall nicht nötig. Das hält
Betrieb, Rechteprüfung und Änderungsprotokoll an einer Stelle.
Browser-Sitzung und API sind bewusst getrennt: Auch ein im Browser angemeldeter
Benutzer kann die API ohne API-Schlüssel nicht lesen. Schlüssel werden nur gehasht
gespeichert, sind widerrufbar, laufen standardmäßig nach 90 Tagen ab und ihr
Klartext wird nur beim Anlegen einmal angezeigt.
Einen reinen Leseschlüssel für ein aktives Konto erstellen:
```bash
docker compose exec web python manage.py create_api_key ausgabedokumente JohannesHundshammer --read-only
```
Einen Schreibschlüssel für ein Konto mit Rolle `EDITOR` oder `ADMIN` erstellen:
```bash
docker compose exec web python manage.py create_api_key hardware-test-team2 JohannesHundshammer --write --expires-days 30
```
Den ausgegebenen Schlüssel sofort in einem Secret-Store oder in einer nur für den
jeweiligen Benutzer lesbaren Umgebungsdatei speichern. Niemals in Quellcode, Git,
Tickets oder Logdateien schreiben. Im Django-Admin unter `/admin/` können
Administratoren Schlüssel sperren und die letzte Nutzung prüfen.
Beispiel: Gerät 30 lesen:
```bash
curl --fail --silent \
-H "Authorization: Api-Key $CSR_API_KEY" \
https://inventory.example.org/api/v1/devices/30/
```
Für eine Änderung zuerst `revision` lesen und als `expected_revision`
zurücksenden. Bestehende Clients können weiterhin `updated_at` als
`expected_updated_at` senden. Veraltete Versionen werden mit HTTP 409 abgewiesen:
```bash
curl --fail --silent --request PATCH \
-H "Authorization: Api-Key $CSR_API_KEY" \
-H "Content-Type: application/json" \
--data '{"cpu":"Intel Core i5-8500","ram_gb":16,"hwid":"ABC-123","expected_revision":1}' \
https://inventory.example.org/api/v1/devices/30/
```
Mehrere Geräte atomar ändern (höchstens 500 pro Anfrage):
```bash
curl --fail --silent --request POST \
-H "Authorization: Api-Key $CSR_API_KEY" \
-H "Content-Type: application/json" \
--data '{"device_numbers":[30,31,32],"changes":{"status":2,"recipient":5}}' \
https://inventory.example.org/api/v1/devices/bulk-update/
```
Verfügbare Auswahl-IDs liefert `GET /api/v1/choices/`. Die Geräteliste unter
`GET /api/v1/devices/` unterstützt `number`, `number_prefix`, `status`, `type`,
`employee`, `changed_since`, `archived`, `page` und `page_size` (maximal 100).
Die API nimmt ausschließlich bekannte Gerätefelder an. Benutzerpasswörter,
Passwort-Hashes und Kontoverwaltung sind absichtlich nicht Teil der API. Jede
Feldänderung enthält in der Änderungshistorie sowohl das zugeordnete Konto als
auch den Namen des verwendeten API-Clients.
## Geräte per Live-USB erfassen
Für den Werkstattbetrieb gibt es einen [eigenständigen USB-Stick](docs/usb-appliance.md)
im separaten Ordner `../USB-Intake`: Nach Eingabe von Server-IP und API-Schlüssel
startet die Prüfung automatisch. Ergebnisse werden direkt hochgeladen oder
dauerhaft auf dem Stick gespeichert. Gespeicherte
Berichte lassen sich später gesammelt senden, auch per WLAN. Der Archiso-Build
enthält alle benötigten Programme und benötigt keine interne Festplatte.
Laptops und Mini-PCs können ohne interne SSD/HDD von einem Ubuntu-Live-USB
gestartet, kurz auf CPU/RAM geprüft und über die API neu erfasst werden.
`../USB-Intake/intake.py` sammelt Hardwaredaten und speichert einen offline
nutzbaren Bericht; der Upload erstellt das Gerät mit automatisch vergebener
Nummer und Prüfbericht im Kommentar. Wiederholte Uploads desselben Berichts
erzeugen keine weiteren Geräte. Einrichtung und Ablauf stehen in der
[Live-USB-Anleitung](docs/usb-intake.md).
## Backup und Wiederherstellung
Backup-Verzeichnis anlegen und Skripte ausführbar machen:
```bash
chmod +x scripts/*.sh
./scripts/backup.sh
```
Das Ergebnis liegt beispielsweise unter `backups/backup_2026-09-19_0200.sql.gz`. Backups regelmäßig auf ein anderes System kopieren.
Wiederherstellung zuerst in einer Testumgebung prüfen. Für eine leere Zieldatenbank:
```bash
./scripts/restore.sh backups/backup_2026-09-19_0200.sql.gz
```
Beispiel für einen täglichen Cronjob um 02:00 Uhr:
```cron
0 2 * * * cd /opt/csr-inventory && ./scripts/backup.sh >> /var/log/csr-backup.log 2>&1
```
## Tests und Prüfungen
```bash
python manage.py test
python manage.py check
python manage.py makemigrations --check --dry-run
```
Diese Befehle verwenden PostgreSQL. CI prüft PostgreSQL 17 einschließlich
gleichzeitiger Bearbeitungen. Ohne PostgreSQL können reine Unit-Tests explizit
mit `python manage.py test --settings=csr_inventory.test_settings` ausgeführt
werden; PostgreSQL-Sperrtests werden dabei übersprungen. Für Tests `DEBUG=true`
und `SECURE_SSL_REDIRECT=false` in der Prozessumgebung setzen.
Browserprüfung mit einer automatisch angelegten und anschließend entfernten
SQLite-Datenbank (berührt die produktive PostgreSQL-Datenbank nicht):
```bash
pip install playwright
python -m playwright install chromium --only-shell
python scripts/test_browser.py
```
Der Playwright-Lauf prüft Anmeldung, Rollen, Geräteliste, Erfassung,
Bearbeitung, Ablauf und Aufgaben, Archiv, Einstellungen, Import/Export,
Änderungshistorie sowie Mobilnavigation und Dunkelmodus.
Für einen bewusst beschreibbaren Testserver kann `scripts/test_server_browser.py`
mit `CSR_TEST_BASE_URL`, `CSR_TEST_USER` und `CSR_TEST_PASSWORD` ausgeführt
werden. Der Lauf legt eindeutig markierte Testgeräte an und gibt die Kennung
`TEST_MARKER` zur anschließenden Bereinigung aus. Er akzeptiert das lokale
selbstsignierte HTTPS-Zertifikat des Testservers.
Docker-Prüfung auf einem Rechner mit Docker:
```bash
docker compose config
docker compose build
docker compose up -d
curl -f https://IHRE-DOMAIN/health/
```
## Importformat
Der XLSX-Import erkennt typische deutsche Spaltennamen automatisch. Unterstützte Core-Spalten sind:
`Nr.`, `Regal`, `Status`, `Datum`, `Empfänger`, `Ort`, `Ticket`, `Typ`, `Preis`, `Akku`, `WLAN`, `LAN`, `Zoll`, `RAM`, `SSD`, `HDD`, `CPU`, `HWID`, `Modell`, `Bearbeiter`, `Spender`, `Kommentar`.
Vor dem Import erscheint eine Vorschau mit Spaltenzuordnung. Bereits vorhandene Gerätenummern führen zum vollständigen Abbruch; es bleibt kein Teilimport zurück. Mitarbeiter werden über ihr Kürzel zugeordnet.
## Betrieb und Sicherheit
- Nur Traefik veröffentlicht Ports; PostgreSQL ist nicht von außen erreichbar.
- Excel-Uploads werden nicht öffentlich über Traefik ausgeliefert.
- CSRF-Schutz, sichere Cookies, HSTS, serverseitige Rollenprüfung und Django-Passworthashing sind im Produktionsmodus aktiv.
- Die API akzeptiert keine Browser-Sitzungen, liefert nur JSON, begrenzt Seitengrößen und drosselt wiederholte Anfragen.
- Technische Fehler werden nur in Container-Logs geschrieben: `docker compose logs web`.
- Für mindestens monatliche Sicherheitsupdates die Basisimages neu bauen: `docker compose build --pull`.
- Für große Datenbestände nach umfangreichen Importen PostgreSQL analysieren: `docker compose exec db vacuumdb -U "$POSTGRES_USER" -d "$POSTGRES_DB" --analyze`.
## Architektur
- Django Templates und leichtes Vanilla-JavaScript; kein separater Frontend-Build
- Django ORM und Migrationen
- PostgreSQL 17 mit `pg_trgm`
- Gunicorn als WSGI-Server
- Traefik als Reverse Proxy mit automatischem HTTPS
- Keine Microservices, kein Redis, kein Elasticsearch
## Traefik und LAN-Zertifikate
Traefik verwendet einen File-Provider ohne Docker-Socket. Statische Dateien liefert WhiteNoise aus. Öffentliche Domains nutzen Let's Encrypt; für private IP-Adressen siehe [Traefik-Betrieb](docs/traefik.md).
## Geräteablauf und Aufgaben
Jedes Gerät hat einen eigenen Arbeitsschritt: Eingang → Inspektion →
bei Bedarf Reparatur → Lagerung → Ausgabe. Bearbeiter können auf der
Geräteseite Schritte mit Notiz dokumentieren und Aufgaben mit Fälligkeit
und zuständiger Person anlegen. Offene Aufgaben stehen unter „Aufgaben“.
Vor Lagerung oder Ausgabe müssen sie erledigt sein; für die Ausgabe ist
ein Empfänger erforderlich. Eine Rückgabe beginnt wieder beim Eingang.
Änderungen erscheinen in der Ablaufhistorie und im Auditprotokoll. Bei Reparatur, Lagerung und Ausgabe wird der bisherige Bestandsstatus automatisch auf Defekt, Fertig beziehungsweise Ausgegeben gesetzt.
Die Gerätetypen Laptop, AiO, Desktop, Bildschirm und Drucker zeigen
nur passende Hardwarefelder. Bei einem Typwechsel werden nicht mehr
passende Werte unter `custom_values.legacy_hardware` erhalten und aus
den aktiven Feldern entfernt. `inventory_device` enthält nur gemeinsame
Gerätedaten. Laptop, Desktop, AiO, Monitor und Drucker besitzen eigene
Spezifikationstabellen; die bisherige Computertabelle bleibt für frei
definierte Gerätetypen. Monitore haben unter anderem Diagonale, Auflösung
und Anschlüsse, Laptops RAM, SSD/HDD, CPU, Akku und Anschlüsse. Drucker
haben WLAN/LAN, Anschlüsse, Druckverfahren, Duplex und Farbdruck.
Migration 0012 verschiebt bestehende Computerwerte in die passenden Tabellen.
Export, Filter und API behalten die bisherigen Feldnamen und ergänzen die
neuen Typfelder. Details stehen in [Datenbankschema](docs/datenbankschema.md).
Über „Ablauf“ in der Hauptnavigation sind alle Arbeitsschritte direkt
erreichbar. Auf dem Board lassen sich Aufgaben anlegen und Geräte zum
nächsten Schritt bewegen. Ein Klick auf eine Tabellenzeile öffnet die
Geräteseite; Ablauf und Aufgaben stehen dort vor den Stammdaten.
In der Inspektion werden Mängel und passende technische Daten erfasst,
bei der Reparatur die durchgeführten Arbeiten und in der Lagerung der
Empfänger. Befunde und Reparaturen bleiben mit Zeit und Bearbeiter als
eigene Datensätze erhalten (Migration 0011).
Vor dem Update einer bestehenden Installation eine Datenbanksicherung
erstellen, dann `python manage.py migrate` ausführen. Ein Löschen der
Datenbank ist nicht nötig und würde Geräte und Benutzerkonten entfernen.
## USB AIO station API
The USB station resolves hardware identity with `POST /api/v1/devices/resolve/`, then reads the returned inventory number, workflow `stage`, and status. For `INTAKE` devices only, `POST /api/v1/devices/<number>/intake/` accepts verified CPU/model/RAM/storage measurements plus `hwid` and `expected_revision`. Later workflow stages reject intake writes.
`POST /api/v1/devices/<number>/inspection/` accepts a UUID `request_id`, `hwid`, `expected_revision`, phase (`STARTED`, `IN_PROGRESS`, `COMPLETE`), technician, notes and a dictionary of test results. Each result preserves outcome, notes, evidence and attempt history. The API moves allowed devices to `INSPECTION`, sets status to `Inspektion`, adds a readable inspection record and retains the structured snapshot. A unique request ID prevents duplicate records after lost responses; reused IDs with different content are rejected. Inventory workflow checks, hardware matching and API-key permissions remain enforced. Completed inspections require a named technician and no unfinished results; unavailable tests remain distinct from hardware failures.
Apply migrations through `0015_inspectionsubmission` before deploying the new USB application. Wi-Fi credentials and inventory API keys stay on USB persistent storage, not in this repository.
## Korrekturen für die Geräteansicht (Migration 0014)
Die Geräteliste bleibt auch bei kleinen oder niedrigen Fenstern scrollbar. Neue
Zusatzspalten werden bei vorhandenen Spalteneinstellungen automatisch eingeblendet;
anschließend können sie weiterhin über die Spaltenauswahl ausgeblendet werden.
Gespeicherte JA/NEIN-Werte und Zahlenwerte wie `0` bleiben beim Bearbeiten sichtbar.
Laptop und AiO bieten jetzt `camera` (Kamera) und `sound` (Sound). Bildschirme
bieten `hdmi`, `vga`, `dvi`, `displayport` (DP) sowie `aspect_ratio`
(Seitenverhältnis, beispielsweise `16:9`). Die Ausstattungsfelder verwenden
`YES`, `NO` oder `UNKNOWN` und sind in der Geräteliste filterbar. Sie stehen auch
in Erfassung, Inspektion, API und Tabellenimport/-export zur Verfügung.
Beim Update `python manage.py migrate` ausführen; Docker erledigt dies beim
Containerstart. Bestehende Anschlussnotizen bleiben erhalten. Neue Auswahlfelder
starten mit „UNBEKANNT“, da Freitext keine verlässliche JA/NEIN-Zuordnung erlaubt.
Die Gitea-Pipeline prüft zusätzlich die Browserabläufe einschließlich kleiner
Bildschirme und benutzerdefinierter Spalten.