From 9d152e36779672d250c8773d3de3bf9b8cbb56c0 Mon Sep 17 00:00:00 2001 From: marc Date: Wed, 2 Sep 2026 17:15:28 +0200 Subject: [PATCH] refactor: App-Umschalter und HONEYBAR_KEY entfernt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beide waren Ballast, aber aus verschiedenen Gründen. Der App-Umschalter (EXTERNAL_APPS, AppSwitcher.tsx) hing seit dem 30.07.2026 nirgends mehr in der Oberfläche – auf Marcs Wunsch damals aus der Seitenleiste genommen, "weil eine gemeinsame Leiste für alle Apps noch überlegt wird". Diese Leiste gibt es inzwischen: die HoneyBar. Das Backend las die Variable trotzdem weiter und lieferte external_apps in der öffentlichen Konfiguration aus. Weg damit, samt Typ und Parser. HONEYBAR_KEY war der Ausweis für die App-Liste, wenn HoneyOS auf einer fremden Site liegt. Bei Marc liegen honeyos.duckdns.org und cloud.honeyos.duckdns.org unter derselben Site, das Sitzungscookie fließt also mit. Nachgesehen, wie HoneyOS entscheidet: Es prüft zuerst die Sitzung (SubFromRequest) und greift erst danach zum Schlüssel – die Leiste kommt hier ohne ihn an ihre Liste. Gedacht war er für Widgets; die sind vertagt. Aether schickt damit kein data-key mehr; HoneyOS behält seine Seite der Prüfung, für den Fall, dass später doch eine App auf fremder Domain dazu kommt. Nachgeprüft: go build, go vet und die Tests laufen; das Abbild baut ohne TypeScript-Fehler; die öffentliche Konfiguration liefert weder honeybar_key noch external_apps; im gebauten Bundle kommt data-key nicht mehr vor. Co-Authored-By: Claude Opus 5 --- .env.example | 8 -- README.md | 2 +- backend/cmd/aether/main.go | 12 --- backend/pkg/config/config.go | 55 ------------ frontend/src/api/client.ts | 10 --- frontend/src/components/AppSwitcher.tsx | 114 ------------------------ frontend/src/honeybar.ts | 3 +- frontend/src/hooks/useAppSettings.tsx | 2 +- frontend/src/pages/LoginPage.tsx | 2 +- 9 files changed, 4 insertions(+), 204 deletions(-) delete mode 100644 frontend/src/components/AppSwitcher.tsx diff --git a/.env.example b/.env.example index a1d8b5b..fcfbbbd 100644 --- a/.env.example +++ b/.env.example @@ -117,10 +117,6 @@ OFFICE_JWT_SECRET= # OCR_MAX_PAGES=50 # 0 = unbegrenzt; rund 1 Minute je Seite # OCR_TIMEOUT=10m # Notbremse je Datei -# ══ App-Umschalter (optional) ═════════════════════════════════════════════════ -# Andere selbst gehostete Apps im Menü der Seitenleiste. -# EXTERNAL_APPS=InkNote|https://notes.example.com|📝 - # ══ HoneyOS-Leiste (optional) ═════════════════════════════════════════════════ # Ursprung, von dem die Leiste geladen wird. Wird an zwei Stellen gebraucht und # kommt deshalb aus einer Quelle: die CSP erlaubt ihn (script-src + @@ -128,10 +124,6 @@ OFFICE_JWT_SECRET= # Wirkt beim **Neustart** – kein Neubau nötig. # Leer = keine Leiste, und die CSP bleibt so eng wie ohne sie. HONEYBAR_ORIGIN= -# Ausweis, mit dem sich die Leiste die App-Liste holt. Muss zu HONEYOS_APPS_KEY -# drüben passen. Nur nötig, wenn HoneyOS auf einer anderen Site liegt als -# Aether – dann schickt der Browser die HoneyOS-Sitzung nicht mit. -HONEYBAR_KEY= # ══ Später ════════════════════════════════════════════════════════════════════ # NOCH NICHT GEBAUT – Notiz, kein Schalter. Gesucht wird derzeit mit der diff --git a/README.md b/README.md index 000a6cd..501621f 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ ADMIN_USERNAME=admin ADMIN_PASSWORD= # nach dem ersten Start wieder leeren ``` -Alles Weitere – Sitzungsdauer, OIDC, Office, OCR, App-Umschalter – steht mit +Alles Weitere – Sitzungsdauer, OIDC, Office, OCR, HoneyOS-Leiste – steht mit Erklärung in derselben Datei, nach Abschnitten geordnet. Absichtlich ausführlich: Sie ist auch die Übersicht darüber, was überhaupt geht. diff --git a/backend/cmd/aether/main.go b/backend/cmd/aether/main.go index 8d79319..09e6bc2 100644 --- a/backend/cmd/aether/main.go +++ b/backend/cmd/aether/main.go @@ -374,16 +374,6 @@ func main() { faviconURL = admin.AppIconURL(s["app_icon_storage_key"], "64") appleTouchURL = admin.AppIconURL(s["app_icon_storage_key"], "192") } - // Ohne Ursprung gibt es keine Leiste — dann hat auch der Schluessel - // nichts auf der Seite zu suchen. - honeybarKey := "" - if cfg.HoneybarOrigin != "" { - honeybarKey = cfg.HoneybarKey - } - apps := cfg.ExternalApps - if apps == nil { - apps = []config.ExternalApp{} // nie null – das Frontend erwartet eine Liste - } response.JSON(w, http.StatusOK, map[string]any{ "oidc_enabled": oidcHandler.IsEnabled(), "cloud_name": cloudName, @@ -392,7 +382,6 @@ func main() { "logo_url": logoURL, "favicon_url": faviconURL, "apple_touch_icon_url": appleTouchURL, - "external_apps": apps, // Damit die Oberflaeche den Menuepunkt „Edit in Office" gar nicht // erst anbietet, wenn es keinen Office-Server gibt. "office_enabled": officeHandler.Enabled(), @@ -419,7 +408,6 @@ func main() { // .env umgezogen werden kann — und damit derselbe Ursprung // gilt, den die CSP erlaubt. "honeybar_origin": cfg.HoneybarOrigin, - "honeybar_key": honeybarKey, }) }) diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go index 0d48573..c6faace 100644 --- a/backend/pkg/config/config.go +++ b/backend/pkg/config/config.go @@ -40,10 +40,6 @@ type Config struct { AdminUsername string AdminPassword string - // ExternalApps erscheinen im App-Umschalter der Oberfläche. Kommt aus der - // Umgebung, damit neue Apps ohne Neubau des Frontends dazukommen können. - ExternalApps []ExternalApp - // OCR – Volltextsuche über den Inhalt von PDFs und Bildern. // Standardmäßig an; fehlen die Programme im Image, schaltet sich der // Arbeiter beim Start selbst ab, statt den Start zu verhindern. @@ -89,15 +85,6 @@ type Config struct { // // Leer heißt: keine Leiste, und die CSP bleibt so eng wie bisher. HoneybarOrigin string - // HoneybarKey ist der Ausweis, mit dem sich die Leiste bei HoneyOS die - // App-Liste holt (dort HONEYOS_APPS_KEY). Nötig, wenn HoneyOS auf einer - // anderen Site liegt als Aether: dann schickt der Browser die - // HoneyOS-Sitzung nicht mit. - // - // Er steht anschließend im Quelltext der Seite — das ist unvermeidlich, - // die Leiste läuft im Browser. Er gibt nur die App-Liste her, nichts - // Schreibendes. - HoneybarKey string // OfficeWopiURL ist die Adresse, unter der der **Office-Server** Aether // erreicht. // @@ -138,45 +125,6 @@ func (c *Config) OfficeEnabled() bool { return (c.OfficeProvider == "collabora" || c.OfficeProvider == "onlyoffice") && c.OfficeURL != "" } -// ExternalApp ist ein Eintrag im App-Umschalter. -type ExternalApp struct { - Name string `json:"name"` - URL string `json:"url"` - Icon string `json:"icon"` // ein Emoji, optional -} - -// parseExternalApps liest EXTERNAL_APPS im Format -// -// Name|https://url|Emoji , Name|https://url|Emoji -// -// Einträge ohne Namen oder URL werden übersprungen, damit ein Tippfehler in der -// .env nicht den Start verhindert. -func parseExternalApps(raw string) []ExternalApp { - var apps []ExternalApp - for _, entry := range strings.Split(raw, ",") { - entry = strings.TrimSpace(entry) - if entry == "" { - continue - } - parts := strings.Split(entry, "|") - if len(parts) < 2 { - continue - } - app := ExternalApp{ - Name: strings.TrimSpace(parts[0]), - URL: strings.TrimSpace(parts[1]), - } - if len(parts) > 2 { - app.Icon = strings.TrimSpace(parts[2]) - } - if app.Name == "" || app.URL == "" { - continue - } - apps = append(apps, app) - } - return apps -} - func Load() (*Config, error) { cfg := &Config{ AppEnv: getEnv("APP_ENV", "development"), @@ -198,8 +146,6 @@ func Load() (*Config, error) { AdminUsername: getEnv("ADMIN_USERNAME", ""), AdminPassword: getEnv("ADMIN_PASSWORD", ""), - ExternalApps: parseExternalApps(getEnv("EXTERNAL_APPS", "")), - OCREnabled: getEnv("OCR_ENABLED", "true") != "false", OfficeProvider: strings.ToLower(strings.TrimSpace(getEnv("OFFICE_PROVIDER", ""))), @@ -207,7 +153,6 @@ func Load() (*Config, error) { OfficeJWTSecret: getEnv("OFFICE_JWT_SECRET", ""), OfficePublicURL: strings.TrimRight(getEnv("OFFICE_PUBLIC_URL", ""), "/"), HoneybarOrigin: strings.TrimRight(getEnv("HONEYBAR_ORIGIN", ""), "/"), - HoneybarKey: getEnv("HONEYBAR_KEY", ""), OfficeWopiURL: strings.TrimRight(getEnv("OFFICE_WOPI_URL", ""), "/"), OfficeAllowedHosts: splitList(getEnv("OFFICE_ALLOWED_HOSTS", "")), OfficeInsecureTLS: getEnv("OFFICE_INSECURE_TLS", "false") == "true", diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 4b2c987..4c3dc70 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -205,12 +205,6 @@ export function uploadWithProgress( // ── Auth ───────────────────────────────────────────────────────────────────── -export interface ExternalApp { - name: string - url: string - icon: string -} - export interface PublicConfig { oidc_enabled: boolean cloud_name: string @@ -235,10 +229,6 @@ export interface PublicConfig { accent_color?: string /** Ursprung der HoneyOS-Leiste (HONEYBAR_ORIGIN). Leer = keine Leiste. */ honeybar_origin?: string - /** Ausweis fuer deren App-Liste (HONEYBAR_KEY). Nur gesetzt, wenn es einen - * Ursprung gibt. */ - honeybar_key?: string - external_apps: ExternalApp[] } export const publicApi = { diff --git a/frontend/src/components/AppSwitcher.tsx b/frontend/src/components/AppSwitcher.tsx deleted file mode 100644 index 1e12e47..0000000 --- a/frontend/src/components/AppSwitcher.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import { publicApi, type ExternalApp } from '../api/client' - -/** - * DERZEIT NICHT EINGEHÄNGT (Stand 2026-07-30). - * - * Auf Wunsch wieder aus der Seitenleiste entfernt – erst soll Aether für sich - * fertig werden. Die Komponente bleibt liegen, weil eine gemeinsame Leiste für - * alle Apps noch überlegt wird. Wird sie nirgends importiert, landet sie auch - * nicht im Bundle. Zum Reaktivieren: in `Sidebar.tsx` neben dem Markenschriftzug - * einsetzen und EXTERNAL_APPS in `build/.env` füllen. - * - * Umschalter zu den anderen selbst gehosteten Apps. - * - * Die Einträge kommen aus EXTERNAL_APPS in der .env des Servers – so lassen - * sich Apps ergänzen, ohne das Frontend neu zu bauen. Ist nichts gesetzt, - * rendert die Komponente nichts. - * - * Die Ziele liegen auf anderen Ursprüngen (eigene Subdomain je App). Deshalb - * `target="_blank"`: Ist die andere App als PWA installiert, übernimmt das - * Betriebssystem sie als eigene App; sonst öffnet sie in einem neuen Tab. - * Eine Navigation im selben Fenster würde die installierte Aether-App - * verlassen und in einer Browserleiste landen. - */ -export default function AppSwitcher() { - const [apps, setApps] = useState([]) - const [open, setOpen] = useState(false) - const boxRef = useRef(null) - - useEffect(() => { - publicApi.config() - .then(c => setApps(c.external_apps ?? [])) - .catch(() => {}) - }, []) - - useEffect(() => { - if (!open) return - function onDown(e: MouseEvent) { - if (boxRef.current && !boxRef.current.contains(e.target as Node)) setOpen(false) - } - function onEsc(e: KeyboardEvent) { - if (e.key === 'Escape') setOpen(false) - } - document.addEventListener('mousedown', onDown) - document.addEventListener('keydown', onEsc) - return () => { - document.removeEventListener('mousedown', onDown) - document.removeEventListener('keydown', onEsc) - } - }, [open]) - - if (apps.length === 0) return null - - return ( -
- - - {open && ( - - )} -
- ) -} diff --git a/frontend/src/honeybar.ts b/frontend/src/honeybar.ts index 9b4c44f..6a82f81 100644 --- a/frontend/src/honeybar.ts +++ b/frontend/src/honeybar.ts @@ -10,7 +10,7 @@ * Faellt der Server aus oder ist kein Ursprung gesetzt, passiert schlicht * nichts: Aether ist nicht auf die Leiste angewiesen. */ -export function mountHoneybar(origin?: string, key?: string) { +export function mountHoneybar(origin?: string) { if (!origin) return // Der Aufruf haengt an der Konfiguration, und die wird auch neu geladen, // wenn der Admin etwas aendert. Zweimal einbinden hiesse zwei Leisten. @@ -23,6 +23,5 @@ export function mountHoneybar(origin?: string, key?: string) { // honeybar.js liest seine Einstellungen ueber document.currentScript – // das gilt auch fuer ein nachtraeglich eingehaengtes Skript. el.dataset.app = 'aether' - if (key) el.dataset.key = key document.head.appendChild(el) } diff --git a/frontend/src/hooks/useAppSettings.tsx b/frontend/src/hooks/useAppSettings.tsx index 7f60aa0..5f1d799 100644 --- a/frontend/src/hooks/useAppSettings.tsx +++ b/frontend/src/hooks/useAppSettings.tsx @@ -115,7 +115,7 @@ export function AppSettingsProvider({ children }: { children: ReactNode }) { if (typeof cfg.max_versions_kept === 'number' && cfg.max_versions_kept > 0) { setMaxVersionsKept(cfg.max_versions_kept) } - mountHoneybar(cfg.honeybar_origin, cfg.honeybar_key) + mountHoneybar(cfg.honeybar_origin) applyTheme(cfg.primary_color, cfg.accent_color) applyThemeColor(cfg.primary_color) document.title = cfg.cloud_name || 'Aether' diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 4c7e1f2..be63ff8 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -10,7 +10,7 @@ export default function LoginPage({ onLogin }: Props) { const [password, setPassword] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) - const [config, setConfig] = useState({ oidc_enabled: false, cloud_name: 'Aether', external_apps: [] }) + const [config, setConfig] = useState({ oidc_enabled: false, cloud_name: 'Aether' }) useEffect(() => { // Show OIDC error from redirect if present