From 9bb46ae968e291af64094bd6531e6471c3633482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Tue, 14 Apr 2026 09:26:43 +0200 Subject: [PATCH] mqtt setup panel: gate on enable toggle, reorder, move next to jellyfin - new mqtt_enabled config + toggle at top of the section; subscriber only starts when the box is checked - moved the whole MqttSection directly below the Jellyfin section so all jellyfin-adjacent config lives together - rewrote the plugin setup list to match the actual form order and group it: 'Top of plugin page' (Server Url = jellyfin base URL), 'Generic destination', 'MQTT settings', 'Template' - fields the user picks from a dropdown or toggles (Status, Notification Type, Item Type, Use TLS, Use Credentials, QoS) now render a 'select' hint instead of a broken Copy button --- package.json | 2 +- server/api/settings.ts | 10 +- server/db/index.ts | 1 + server/db/schema.ts | 1 + server/services/mqtt.ts | 1 + src/features/settings/MqttSection.tsx | 243 +++++++++++++++---------- src/features/settings/SettingsPage.tsx | 6 +- 7 files changed, 161 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index cc0caea..15451f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "netfelix-audio-fix", - "version": "2026.04.14.4", + "version": "2026.04.14.5", "scripts": { "dev:server": "NODE_ENV=development bun --hot server/index.tsx", "dev:client": "vite", diff --git a/server/api/settings.ts b/server/api/settings.ts index 1e04152..755c5e4 100644 --- a/server/api/settings.ts +++ b/server/api/settings.ts @@ -107,12 +107,20 @@ app.patch("/schedule", async (c) => { // ─── MQTT ──────────────────────────────────────────────────────────────────── app.post("/mqtt", async (c) => { - const body = await c.req.json<{ url?: string; topic?: string; username?: string; password?: string }>(); + const body = await c.req.json<{ + enabled?: boolean; + url?: string; + topic?: string; + username?: string; + password?: string; + }>(); + const enabled = body.enabled === true; const url = (body.url ?? "").trim(); const topic = (body.topic ?? "jellyfin/events").trim(); const username = (body.username ?? "").trim(); const password = body.password ?? ""; + setConfig("mqtt_enabled", enabled ? "1" : "0"); setConfig("mqtt_url", url); setConfig("mqtt_topic", topic || "jellyfin/events"); setConfig("mqtt_username", username); diff --git a/server/db/index.ts b/server/db/index.ts index ee59631..a4516f0 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -22,6 +22,7 @@ const ENV_MAP: Record = { sonarr_api_key: "SONARR_API_KEY", sonarr_enabled: "SONARR_ENABLED", audio_languages: "AUDIO_LANGUAGES", + mqtt_enabled: "MQTT_ENABLED", mqtt_url: "MQTT_URL", mqtt_topic: "MQTT_TOPIC", mqtt_username: "MQTT_USERNAME", diff --git a/server/db/schema.ts b/server/db/schema.ts index 0686aed..ad9b2f0 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -144,6 +144,7 @@ export const DEFAULT_CONFIG: Record = { process_schedule_start: "01:00", process_schedule_end: "07:00", + mqtt_enabled: "0", mqtt_url: "", mqtt_topic: "jellyfin/events", mqtt_username: "", diff --git a/server/services/mqtt.ts b/server/services/mqtt.ts index fda5616..dd1244f 100644 --- a/server/services/mqtt.ts +++ b/server/services/mqtt.ts @@ -35,6 +35,7 @@ function setStatus(next: MqttStatus, err: string | null = null): void { } function readConfig(): MqttConfig | null { + if (getConfig("mqtt_enabled") !== "1") return null; const url = getConfig("mqtt_url") ?? ""; if (!url) return null; return { diff --git a/src/features/settings/MqttSection.tsx b/src/features/settings/MqttSection.tsx index ddeaf43..4bc68f7 100644 --- a/src/features/settings/MqttSection.tsx +++ b/src/features/settings/MqttSection.tsx @@ -74,7 +74,22 @@ async function copyText(text: string): Promise { } } -function CopyableValue({ label, value, mono = true }: { label: string; value: string; mono?: boolean }) { +/** + * One row of the plugin-setup checklist. `copyable=false` is for fields the + * user selects from a dropdown / toggles (Status, Notification Type, Use TLS, + * etc.) — copying their value doesn't help, so we just display it. + */ +function SetupValue({ + label, + value, + mono = true, + copyable = true, +}: { + label: string; + value: string; + mono?: boolean; + copyable?: boolean; +}) { const [copied, setCopied] = useState<"ok" | "fail" | null>(null); const copy = async () => { const ok = await copyText(value); @@ -83,22 +98,27 @@ function CopyableValue({ label, value, mono = true }: { label: string; value: st }; return (
-
{label}
+
{label}
 				{value}
 			
- + {copyable ? ( + + ) : ( + select + )}
); } export function MqttSection({ cfg, locked }: { cfg: Record; locked: Set }) { + const [enabled, setEnabled] = useState(cfg.mqtt_enabled === "1"); const [url, setUrl] = useState(cfg.mqtt_url ?? ""); const [topic, setTopic] = useState(cfg.mqtt_topic || "jellyfin/events"); const [username, setUsername] = useState(cfg.mqtt_username ?? ""); @@ -142,7 +162,7 @@ export function MqttSection({ cfg, locked }: { cfg: Record; lock setSaving(true); setSavedMsg(""); try { - await api.post("/api/settings/mqtt", { url, topic, username, password }); + await api.post("/api/settings/mqtt", { enabled, url, topic, username, password }); setSavedMsg(password ? "Saved." : "Saved (password unchanged)."); setPassword(""); setTimeout(() => setSavedMsg(""), 2500); @@ -173,6 +193,10 @@ export function MqttSection({ cfg, locked }: { cfg: Record; lock ? "text-amber-700 bg-amber-50 border-amber-300" : "text-gray-600 bg-gray-50 border-gray-300"; + const broker = parseBroker(url); + const useCredentials = !!(username || cfg.mqtt_password); + const jellyfinBase = (cfg.jellyfin_url ?? "").replace(/\/$/, "") || "http://jellyfin.lan:8096"; + return (
@@ -186,56 +210,68 @@ export function MqttSection({ cfg, locked }: { cfg: Record; lock re-analyze the item, confirming it as done or flipping it back to pending if something didn't stick.

-