Files
Inventory/README.md
2026-09-29 08:43:05 +02:00

298 lines
13 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
## 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.
## 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.
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.