From 95946bd11c19911f4b24eed40c1c3aec795e2650 Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Fri, 11 Sep 2026 11:36:55 +0200 Subject: [PATCH] Phase 6: App-Beschreibungen + Icon-Fetch-Mechanismus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration 0045: 47 von 56 Merkmalen hatten keine Beschreibung (Datenbank- Check 11.09.2026) - Kurzbeschreibungen ergänzt, gleicher Ton wie die 9 bestehenden. Neue Spalten homepage_url/icon_source_hint (nur real gegen die jeweilige URL bzw. https://cdn.simpleicons.org/ geprüft gesetzt, sonst NULL statt geraten) sowie icon_filename/icon_content_type (Cache, analog workspace_webapps). Neuer Icon-Fetch: fetch_merkmal_icon() probiert zuerst den Simple-Icons- Slug, sonst fetch_favicon() der Homepage (bestehende Funktion aus dem Webapp-Mechanismus direkt wiederverwendet) - Kombi aus beidem war die Nutzer-Entscheidung in der Plan-Klärungsrunde. aktualisiere_merkmal_icon() ist ein eigener, wiederholbar aufrufbarer Schritt (POST /api/v1/merkmale/ {id}/icon/aktualisieren) statt Teil des Anlegens, weil Merkmale per Migration entstehen, nicht per API-Call mit sofortigem Netzwerk-Kontext. Auslieferung über GET /merkmal-icons/{filename}, gleiches Muster wie /webapp-icons. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9 --- app.py | 123 +++++++++++++++++- migrations/0045_app_beschreibungen_icons.sql | 130 +++++++++++++++++++ 2 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 migrations/0045_app_beschreibungen_icons.sql diff --git a/app.py b/app.py index a75ab7a..44279d0 100644 --- a/app.py +++ b/app.py @@ -71,6 +71,13 @@ GOLDEN_IMAGES_DIR = Path(os.environ.get("TUXFLOTTE_GOLDEN_IMAGES_DIR", "data/gol # ansible-pull-Lauf, kennt aber (anders als Golden Images) keinen # Aktivierungs-Kontext dafuer. WEBAPP_ICONS_DIR = Path(os.environ.get("TUXFLOTTE_WEBAPP_ICONS_DIR", "data/webapp-icons")).resolve() +# Merkmal-Icons (Phase 6, 11.09.2026) - eigenes Verzeichnis statt +# WEBAPP_ICONS_DIR mitzunutzen, obwohl der Mechanismus fast identisch ist +# (fetch_merkmal_icon()/_write_merkmal_icon() unten): Merkmale sind +# Katalog-Stammdaten (per Migration angelegt), Webapps sind kundeneigene +# Laufzeitdaten - getrennte Verzeichnisse halten das auseinander. +MERKMAL_ICONS_DIR = Path(os.environ.get("TUXFLOTTE_MERKMAL_ICONS_DIR", "data/merkmal-icons")).resolve() +SIMPLE_ICONS_CDN_URL = "https://cdn.simpleicons.org" # At-Rest-Verschluesselung fuer Merkmal-Optionen (10.09.2026, Vorbereitung # AD/OIDC/Fileserver-Merkmale mit echten Zugangsdaten) - gleiches Muster # wie kundenplattform/encryption.py fuer den WLAN-PSK, aber eigener @@ -808,6 +815,28 @@ def fetch_favicon(page_url: str) -> tuple[bytes, str] | None: return _fetch_url_bytes(fallback_url, FAVICON_MAX_BYTES) +def fetch_merkmal_icon(icon_source_hint: str | None, homepage_url: str | None) -> tuple[bytes, str] | None: + """ + Icon-Fetch fuer ein Merkmal (Phase 6, 11.09.2026, Plan-Klaerungsrunde + "Kombi aus Simple-Icons-CDN + Favicon-Fetch"): zuerst den Simple-Icons- + Slug probieren (bekannte Marken-Apps, liefert ein sauberes einheitliches + SVG), sonst fetch_favicon() der hinterlegten Homepage (Nischen-Apps, + gleicher Mechanismus wie bei Webapps). None, wenn beides fehlschlaegt + oder gar nichts hinterlegt ist - der Aufrufer setzt dann kein + icon_filename, die Flottenmatrix faellt auf ihr bisheriges Haekchen/ + Fallback-Icon zurueck. + """ + if icon_source_hint: + gefunden = _fetch_url_bytes(f"{SIMPLE_ICONS_CDN_URL}/{icon_source_hint}", FAVICON_MAX_BYTES) + if gefunden is not None: + return gefunden + + if homepage_url: + return fetch_favicon(homepage_url) + + return None + + # Phase 4 des GUI-Ausbaus (11.09.2026): Kategorie-Key -> echter Freedesktop- # Categories=-Wert fuers .desktop-Menue einer Webapp (siehe kategorien- # Tabelle). Unsere eigenen Kategorie-Namen sind keine gueltigen XDG-Menue- @@ -2693,7 +2722,7 @@ def fetch_merkmale(): cur.execute( """ SELECT m.id, m.key, m.name, m.description, m.im_auftragskatalog, - m.kategorie_id, k.name + m.kategorie_id, k.name, m.icon_filename FROM merkmale m LEFT JOIN kategorien k ON k.id = m.kategorie_id ORDER BY m.key @@ -2708,10 +2737,13 @@ def fetch_merkmale(): "im_auftragskatalog": im_auftragskatalog, "kategorie_id": str(kategorie_id) if kategorie_id else None, "kategorie_name": kategorie_name, + # Phase 6 (11.09.2026) - relativer Pfad wie bei den + # Webapp-Icons, kein voller URL (siehe icon_path dort). + "icon_path": f"/merkmal-icons/{icon_filename}" if icon_filename else None, } for ( mid, key, name, description, im_auftragskatalog, - kategorie_id, kategorie_name, + kategorie_id, kategorie_name, icon_filename, ) in cur.fetchall() ] @@ -2961,6 +2993,55 @@ def _write_webapp_icon(webapp_id, icon_bytes: bytes, content_type: str) -> str: return filename +def _write_merkmal_icon(merkmal_id, icon_bytes: bytes, content_type: str) -> str: + extension = mimetypes.guess_extension(content_type) or ".svg" + if extension == ".jpe": + extension = ".jpg" + filename = f"{merkmal_id}{extension}" + MERKMAL_ICONS_DIR.mkdir(parents=True, exist_ok=True) + (MERKMAL_ICONS_DIR / filename).write_bytes(icon_bytes) + return filename + + +def aktualisiere_merkmal_icon(merkmal_id: str) -> dict | None: + """ + Holt (falls möglich) ein Icon fuer ein Merkmal und schreibt icon_filename/ + icon_content_type - Gegenstueck zu create_webapp()s Favicon-Fetch, aber + als eigener, wiederholbar aufrufbarer Schritt statt beim Anlegen + (Merkmale entstehen per Migration, nicht per API-Aufruf mit sofortigem + Netzwerk-Kontext). Liefert None, wenn das Merkmal nicht existiert; + {"icon_path": None} (kein Fehler), wenn weder Simple-Icons-Slug noch + Favicon-Fetch etwas geliefert haben - dann bleibt das Fallback-Icon in + der Flottenmatrix bestehen. + """ + with get_database_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT icon_source_hint, homepage_url FROM merkmale WHERE id = %s", + (merkmal_id,), + ) + row = cur.fetchone() + + if row is None: + return None + + icon_source_hint, homepage_url = row + gefunden = fetch_merkmal_icon(icon_source_hint, homepage_url) + + icon_filename = None + icon_content_type = None + if gefunden is not None: + icon_bytes, icon_content_type = gefunden + icon_filename = _write_merkmal_icon(merkmal_id, icon_bytes, icon_content_type) + + cur.execute( + "UPDATE merkmale SET icon_filename = %s, icon_content_type = %s WHERE id = %s", + (icon_filename, icon_content_type, merkmal_id), + ) + + return {"icon_path": f"/merkmal-icons/{icon_filename}" if icon_filename else None} + + def fetch_webapps_for_workspace(workspace_id: str): with get_database_connection() as conn: with conn.cursor() as cur: @@ -4463,6 +4544,44 @@ def update_merkmal_endpoint( return {"success": True} +@app.post("/api/v1/merkmale/{merkmal_id}/icon/aktualisieren") +def merkmal_icon_aktualisieren_endpoint(merkmal_id: str, authorization: str | None = Header(default=None)): + if not require_service_token(authorization): + return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."} + + ergebnis = aktualisiere_merkmal_icon(merkmal_id) + + if ergebnis is None: + return {"success": False, "error": "not_found", "message": "Merkmal wurde nicht gefunden."} + + return {"success": True, **ergebnis} + + +@app.get("/merkmal-icons/{filename}") +def get_merkmal_icon(filename: str): + # Gleiches Muster wie get_webapp_icon() oben (Path-Traversal-Schutz, + # unauthentifiziert - wird vom Kundenplattform-Proxy ohne Session- + # Kontext abgerufen). + safe_name = Path(filename).name + if safe_name != filename: + raise HTTPException(status_code=400, detail="invalid_filename") + + file_path = MERKMAL_ICONS_DIR / safe_name + if not file_path.is_file(): + raise HTTPException(status_code=404, detail="not_found") + + with get_database_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT icon_content_type FROM merkmale WHERE icon_filename = %s LIMIT 1", + (safe_name,), + ) + row = cur.fetchone() + media_type = row[0] if row and row[0] else "application/octet-stream" + + return FileResponse(file_path, media_type=media_type) + + @app.get("/api/v1/kategorien") def list_kategorien(authorization: str | None = Header(default=None)): if not require_service_token(authorization): diff --git a/migrations/0045_app_beschreibungen_icons.sql b/migrations/0045_app_beschreibungen_icons.sql new file mode 100644 index 0000000..65eaae6 --- /dev/null +++ b/migrations/0045_app_beschreibungen_icons.sql @@ -0,0 +1,130 @@ +BEGIN; + +-- Phase 6 des GUI-Ausbaus (11.09.2026, siehe +-- project_kundenplattform_gui_umbau_2026-09-11_dashboard_workspace-Memory): +-- 47 von 56 Merkmalen hatten keine Beschreibung (Datenbank-Check +-- 11.09.2026) - Kurzbeschreibungen ergänzt, gleicher knapper/faktischer +-- Ton wie die 9 bestehenden. Zusätzlich zwei neue, optionale Spalten: +-- +-- homepage_url: offizielle Projektseite, nur gesetzt wo eine stabile URL +-- real geprüft wurde (curl, 11.09.2026) - bewusst NULL bei reinen Konfig-/ +-- Härtungs-Merkmalen ohne eigene "Produktidentität" (z.B. auto-update, +-- firewall, tastaturlayout) sowie bei zwei GNOME-Apps (evolution, +-- metronome), deren offizielle Seiten (gitlab.gnome.org) automatisierte +-- Anfragen mit 406 ablehnen - Favicon-Fetch würde dort ohnehin scheitern. +-- +-- icon_source_hint: Simple-Icons-Slug, NUR gesetzt wenn der Slug real +-- gegen https://cdn.simpleicons.org/ geprüft wurde (200, nicht nur +-- vermutet) - die meisten Bildungs-/Nischen-Apps sind dort schlicht nicht +-- gelistet, bleiben NULL und fallen auf den Favicon-Fallback zurück +-- (siehe fetch_merkmal_icon() in app.py). +ALTER TABLE merkmale ADD COLUMN homepage_url TEXT; +ALTER TABLE merkmale ADD COLUMN icon_source_hint TEXT; +ALTER TABLE merkmale ADD COLUMN icon_filename TEXT; +ALTER TABLE merkmale ADD COLUMN icon_content_type TEXT; + +-- Homepage/Icon-Ergänzung für bereits beschriebene Merkmale. +UPDATE merkmale SET homepage_url = 'https://learningequality.org/kolibri' WHERE key = 'kolibri'; +UPDATE merkmale SET homepage_url = 'https://veyon.io' WHERE key = 'veyon'; +UPDATE merkmale SET homepage_url = 'https://www.chromium.org/' WHERE key = 'chromium'; + +-- Browser +UPDATE merkmale SET description = 'Fernwartungssoftware für Support durch Dritte.', + homepage_url = 'https://anydesk.com', icon_source_hint = 'anydesk' WHERE key = 'anydesk'; +UPDATE merkmale SET description = 'Chromium-basierter Browser mit eingebautem Werbe-/Tracker-Blocker.', + homepage_url = 'https://brave.com', icon_source_hint = 'brave' WHERE key = 'browser-brave'; +UPDATE merkmale SET description = 'Weit verbreiteter Browser von Google.', + homepage_url = 'https://www.google.com/chrome/', icon_source_hint = 'googlechrome' WHERE key = 'google-chrome'; +UPDATE merkmale SET description = 'Chromium-basierter Browser von Microsoft.', + homepage_url = 'https://www.microsoft.com/edge' WHERE key = 'microsoft-edge'; + +-- Kreativ/Grafik +UPDATE merkmale SET description = 'Bildbearbeitung, freie Alternative zu Photoshop.', + homepage_url = 'https://www.gimp.org', icon_source_hint = 'gimp' WHERE key = 'gimp'; +UPDATE merkmale SET description = 'Vektorgrafik, freie Alternative zu Illustrator.', + homepage_url = 'https://inkscape.org', icon_source_hint = 'inkscape' WHERE key = 'inkscape'; +UPDATE merkmale SET description = 'Desktop-Publishing für Broschüren, Flyer und Layouts.', + homepage_url = 'https://www.scribus.net' WHERE key = 'scribus'; +UPDATE merkmale SET description = 'Elektronik-Design für Platinen, Steckbrett-Simulationen und Schaltpläne.', + homepage_url = 'https://fritzing.org' WHERE key = 'fritzing'; +UPDATE merkmale SET description = '2D-CAD für technisches Zeichnen.', + homepage_url = 'https://librecad.org' WHERE key = 'librecad'; + +-- Musik/Audio +UPDATE merkmale SET description = 'Audioaufnahme und -bearbeitung, mehrspurig.', + homepage_url = 'https://www.audacityteam.org', icon_source_hint = 'audacity' WHERE key = 'audacity'; +UPDATE merkmale SET description = 'Notensatzprogramm zum Komponieren und Drucken von Noten.', + homepage_url = 'https://musescore.org' WHERE key = 'musescore'; +UPDATE merkmale SET description = 'Digitales Metronom für den Musikunterricht.' WHERE key = 'metronome'; +UPDATE merkmale SET description = 'Gehörbildung und Musiktheorie-Übungen.', + homepage_url = 'https://apps.kde.org/minuet' WHERE key = 'minuet'; + +-- Büro/Produktivität +UPDATE merkmale SET description = 'Vollständiges freies Office-Paket (Textverarbeitung, Tabellenkalkulation, Präsentation).', + homepage_url = 'https://www.libreoffice.org', icon_source_hint = 'libreoffice' WHERE key = 'office-libreoffice'; +UPDATE merkmale SET description = 'Office-Paket mit hoher Kompatibilität zu Microsoft-Office-Dateiformaten.', + homepage_url = 'https://www.onlyoffice.com', icon_source_hint = 'onlyoffice' WHERE key = 'office-onlyoffice'; +UPDATE merkmale SET description = 'E-Book-Reader für EPUB und weitere Formate.', + homepage_url = 'https://johnfactotum.github.io/foliate/' WHERE key = 'foliate'; +UPDATE merkmale SET description = 'E-Mail-Client mit Kalender- und Kontaktverwaltung.' WHERE key = 'evolution'; +UPDATE merkmale SET description = 'Remote-Desktop-Client (RDP, VNC, SSH u.a.).', + homepage_url = 'https://remmina.org' WHERE key = 'remmina'; + +-- Programmieren/Making +UPDATE merkmale SET description = 'Spieleentwicklung ohne Programmierkenntnisse, per visueller Ereignislogik.', + homepage_url = 'https://gdevelop.io' WHERE key = 'gdevelop'; +UPDATE merkmale SET description = 'Schnellere, erweiterte Ausführungsumgebung für Scratch-Projekte.', + homepage_url = 'https://turbowarp.org' WHERE key = 'turbowarp'; + +-- Bildung: Mathe & Naturwissenschaft +UPDATE merkmale SET description = 'Interaktive Geometrie, Algebra und Analysis.', + homepage_url = 'https://www.geogebra.org' WHERE key = 'geogebra'; +UPDATE merkmale SET description = 'Grafikrechner für Funktionen und Gleichungen.', + homepage_url = 'https://apps.kde.org/kalgebra' WHERE key = 'kalgebra'; +UPDATE merkmale SET description = 'Interaktives Periodensystem mit Element-Detailinformationen.', + homepage_url = 'https://apps.kde.org/kalzium' WHERE key = 'kalzium'; +UPDATE merkmale SET description = 'Übungsprogramm für Bruchrechnung.', + homepage_url = 'https://apps.kde.org/kbruch' WHERE key = 'kbruch'; +UPDATE merkmale SET description = 'Physik-Simulator für Kräfte, Bewegung und Kollisionen.', + homepage_url = 'https://apps.kde.org/step' WHERE key = 'step'; +UPDATE merkmale SET description = 'Numerisches Rechnen, weitgehend kompatibel zu MATLAB.', + homepage_url = 'https://octave.org', icon_source_hint = 'octave' WHERE key = 'octave'; +UPDATE merkmale SET description = 'Planetarium-Software für den Astronomie-Unterricht.', + homepage_url = 'https://stellarium.org' WHERE key = 'stellarium'; +UPDATE merkmale SET description = '3D-Globus mit Satellitenbildern für Erdkunde/Geografie.', + homepage_url = 'https://earth.google.com', icon_source_hint = 'googleearth' WHERE key = 'google-earth'; +UPDATE merkmale SET description = 'Virtueller Globus und Kartenprogramm.', + homepage_url = 'https://marble.kde.org' WHERE key = 'marble'; +UPDATE merkmale SET description = 'Netzwerksimulator für den Unterricht (Aufbau und Konfiguration virtueller Netze).', + homepage_url = 'https://lernsoftware-filius.de' WHERE key = 'filius'; + +-- Bildung: Sprache & Schreiben / Unterrichtswerkzeuge +UPDATE merkmale SET description = 'Buchstabenrätsel-Lernspiel.', + homepage_url = 'https://apps.kde.org/kanagram' WHERE key = 'kanagram'; +UPDATE merkmale SET description = 'Karteikarten-Lernprogramm für Vokabeln und Begriffe.', + homepage_url = 'https://apps.kde.org/kwordquiz' WHERE key = 'kwordquiz'; +UPDATE merkmale SET description = 'Tastschreiben lernen.', + homepage_url = 'https://apps.kde.org/ktouch' WHERE key = 'ktouch'; +UPDATE merkmale SET description = 'Tippspiel zum Erlernen des Zehnfingersystems.', + homepage_url = 'https://github.com/tux4kids/tuxtype' WHERE key = 'tuxtype'; +UPDATE merkmale SET description = 'Arcade-artiges Kopfrechnen-Lernspiel.', + homepage_url = 'https://github.com/tux4kids/tuxmath' WHERE key = 'tuxmath'; +UPDATE merkmale SET description = 'Sammlung von Lernspielen für jüngere Schüler (Mathematik, Lesen, Logik u.a.).', + homepage_url = 'https://gcompris.net' WHERE key = 'gcompris'; +UPDATE merkmale SET description = 'Einfache Programmiersprache mit Turtle-Grafik für den Programmier-Einstieg.', + homepage_url = 'https://apps.kde.org/kturtle' WHERE key = 'kturtle'; + +-- Konfiguration/Härtung (bewusst ohne homepage_url/icon_source_hint - +-- Konfig-Merkmale, keine eigenständige Anwendung mit Produktidentität). +UPDATE merkmale SET description = 'Installiert alle verfügbaren Paketupdates automatisch (nicht nur sicherheitsrelevante, siehe Automatische Sicherheitsupdates).' WHERE key = 'auto-update'; +UPDATE merkmale SET description = 'Installiert ausschließlich sicherheitsrelevante Paketupdates automatisch.' WHERE key = 'security-updates'; +UPDATE merkmale SET description = 'Aktiviert eine einfache, vorkonfigurierte Firewall (ufw).' WHERE key = 'firewall'; +UPDATE merkmale SET description = 'Installiert ClamAV zur On-Demand-Virenprüfung.', homepage_url = 'https://www.clamav.net' WHERE key = 'virenscanner'; +UPDATE merkmale SET description = 'Aktiviert Bildschirmlupe, Bildschirmleser und weitere Eingabehilfen.' WHERE key = 'barrierefreiheit'; +UPDATE merkmale SET description = 'Installiert zusätzliche Video-/Audio-Codecs für gängige Formate (z.B. MP3, H.264).' WHERE key = 'multimedia-codecs'; +UPDATE merkmale SET description = 'Installiert proprietäre Firmware für WLAN-/Netzwerk-Chipsätze, die im freien Repository fehlt.' WHERE key = 'wlan-firmware'; +UPDATE merkmale SET description = 'Setzt Zeitzone und Sprache passend zur gewählten Region.' WHERE key = 'laender'; +UPDATE merkmale SET description = 'Setzt das Tastaturlayout passend zur gewählten Sprache/Region.' WHERE key = 'tastaturlayout'; +UPDATE merkmale SET description = 'Synchronisiert die Systemzeit über NTP mit einem konfigurierbaren Zeitserver.' WHERE key = 'zeitserver'; + +COMMIT;