# 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 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//intake/` accepts verified CPU/model/RAM/storage measurements plus `hwid` and `expected_revision`. Later workflow stages reject intake writes. `POST /api/v1/devices//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.