mqtt setup panel: clipboard fallback for http lan, fill webhook url
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m53s

two issues surfaced on unraid over plain http:

copy buttons: navigator.clipboard is undefined in non-secure contexts.
fall back to a hidden textarea + document.execCommand('copy'), and
surface a 'select & ⌘C' hint if even that fails.

webhook url: the field is only used by http destinations but the plugin
form requires a value regardless. put the broker url there (mqtt://host:
port) so validation passes and it's still obvious what it points at.
This commit is contained in:
2026-04-14 09:13:43 +02:00
parent 62ec7e0255
commit 76d97901cd
2 changed files with 42 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "netfelix-audio-fix",
"version": "2026.04.14.3",
"version": "2026.04.14.4",
"scripts": {
"dev:server": "NODE_ENV=development bun --hot server/index.tsx",
"dev:client": "vite",

View File

@@ -45,12 +45,41 @@ function parseBroker(raw: string): { host: string; port: string; useTls: boolean
}
}
async function copyText(text: string): Promise<boolean> {
// Secure-context fast path
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
/* fall through to legacy */
}
}
// HTTP LAN fallback: temporary textarea + execCommand.
try {
const el = document.createElement("textarea");
el.value = text;
el.setAttribute("readonly", "");
el.style.position = "fixed";
el.style.top = "0";
el.style.left = "0";
el.style.opacity = "0";
document.body.appendChild(el);
el.select();
const ok = document.execCommand("copy");
document.body.removeChild(el);
return ok;
} catch {
return false;
}
}
function CopyableValue({ label, value, mono = true }: { label: string; value: string; mono?: boolean }) {
const [copied, setCopied] = useState(false);
const [copied, setCopied] = useState<"ok" | "fail" | null>(null);
const copy = async () => {
await navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
const ok = await copyText(value);
setCopied(ok ? "ok" : "fail");
setTimeout(() => setCopied(null), 1500);
};
return (
<div className="flex items-start gap-2 mb-2">
@@ -63,7 +92,7 @@ function CopyableValue({ label, value, mono = true }: { label: string; value: st
onClick={copy}
className="text-xs px-2 py-1 rounded border border-gray-300 text-gray-600 hover:bg-gray-100 flex-shrink-0"
>
{copied ? "Copied" : "Copy"}
{copied === "ok" ? "Copied" : copied === "fail" ? "Select & ⌘C" : "Copy"}
</button>
</div>
);
@@ -257,7 +286,10 @@ export function MqttSection({ cfg, locked }: { cfg: Record<string, string>; lock
return (
<div className="mt-2">
<CopyableValue label="Webhook Name" value="Audio Fix" mono={false} />
<CopyableValue label="Webhook Url" value="n/a" mono={false} />
<CopyableValue
label="Webhook Url"
value={`${broker.useTls ? "mqtts" : "mqtt"}://${broker.host || "broker.lan"}:${broker.port}`}
/>
<CopyableValue label="Status" value="Enabled" mono={false} />
<CopyableValue label="Notification Type" value="Item Added" mono={false} />
<CopyableValue label="Item Type" value="Movies, Episodes" mono={false} />
@@ -278,8 +310,9 @@ export function MqttSection({ cfg, locked }: { cfg: Record<string, string>; lock
);
})()}
<p className="text-[11px] text-gray-400 mt-2">
Jellyfin's plugin doesn't expose an "Item Updated" event. When we rewrite a file, Jellyfin treats it as a new add
and fires <span className="font-mono">Item Added</span> that's the event we listen for.
Notes: the plugin's "Webhook Url" field is for HTTP destinations; MQTT ignores it, but the form won't save empty,
so we put the broker URL there as a placeholder. Jellyfin doesn't expose an "Item Updated" event any file
change fires <span className="font-mono">Item Added</span>, which is what we listen for.
</p>
</div>
</div>