plugin settings: the client already adds /api, and a Test button
Save returned 404 and the form silently showed what was typed rather than what
was stored. Both from one mistake: useClient prefixes /api itself, so
`/api${prefix}/config` reached /api/api/transcribe/config. Every other caller in
the tree passes a bare path; I copied the shape of a curl instead.
The GET failing was the worse half, because it looked like it worked — an empty
field populated by typing is indistinguishable from one populated by the server.
Adds Test, which asks the plugin's /_health whether its connection works NOW. The
plugin validates on save, so a stored value has been reached once — but once,
whenever that was, is not the same as now, and the server it points at is a
separate machine that can stop answering. Offered only once something is stored,
since there is nothing to test before that.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -55,7 +55,7 @@ export const PluginSettings = ({ appName, prefix, settings, enabled }: PluginSet
|
||||
|
||||
const { data: current, isLoading } = useQuery({
|
||||
queryKey: ['plugin-config', appName],
|
||||
queryFn: () => client.get<Record<string, unknown>>(`/api${prefix}/config`),
|
||||
queryFn: () => client.get<Record<string, unknown>>(`${prefix}/config`),
|
||||
enabled,
|
||||
// A plugin that has no /config yet, or is mid-restart, is not an error worth a red toast on a screen
|
||||
// the owner opened to read about the plugin.
|
||||
@@ -80,6 +80,23 @@ export const PluginSettings = ({ appName, prefix, settings, enabled }: PluginSet
|
||||
setValues((prev) => ({ ...next, ...prev }));
|
||||
}, [current, settings]);
|
||||
|
||||
/*
|
||||
Test what is STORED, not what is typed.
|
||||
|
||||
The plugin validates on save, so a stored value has already been reached once — but "once, whenever
|
||||
that was" is not the same as "now", and the server it points at is a separate machine that can stop
|
||||
answering. This asks the plugin whether its connection works at this moment, which is the question
|
||||
somebody staring at a form actually has.
|
||||
*/
|
||||
const test = useMutation({
|
||||
mutationFn: () => client.get<{ ok?: boolean; error?: string }>(`${prefix}/_health`),
|
||||
onSuccess: (r) =>
|
||||
r?.ok === false
|
||||
? toast.error(r.error || t('plugins.settingsTestFailed'))
|
||||
: toast.success(t('plugins.settingsTestOk')),
|
||||
onError: (err) => toast.error(err instanceof Error ? err.message : t('plugins.settingsTestFailed')),
|
||||
});
|
||||
|
||||
const save = useMutation({
|
||||
mutationFn: () => {
|
||||
// Only what was typed. An untouched secret must not be sent as an empty string, which the plugin
|
||||
@@ -89,7 +106,7 @@ export const PluginSettings = ({ appName, prefix, settings, enabled }: PluginSet
|
||||
const v = values[f.key];
|
||||
if (v !== undefined && v !== '') body[f.key] = v;
|
||||
}
|
||||
return client.put(`/api${prefix}/config`, body);
|
||||
return client.put(`${prefix}/config`, body);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success(t('plugins.settingsSaved'));
|
||||
@@ -146,7 +163,13 @@ export const PluginSettings = ({ appName, prefix, settings, enabled }: PluginSet
|
||||
onClick={() => save.mutate()}
|
||||
>
|
||||
{save.isPending ? t('plugins.settingsSaving') : t('plugins.settingsSave')}
|
||||
</Button>{' '}
|
||||
{/* Offered only once something is stored — there is nothing to test before that. */}
|
||||
{current ? (
|
||||
<Button disabled={test.isPending || save.isPending} onClick={() => test.mutate()}>
|
||||
{test.isPending ? t('plugins.settingsTesting') : t('plugins.settingsTest')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -206,7 +206,11 @@
|
||||
"settingsSaving": "Wird gespeichert…",
|
||||
"settingsSaved": "Einstellungen gespeichert",
|
||||
"settingsFailed": "Einstellungen konnten nicht gespeichert werden",
|
||||
"settingsSecretSet": "Gesetzt — leer lassen, um es zu behalten"
|
||||
"settingsSecretSet": "Gesetzt — leer lassen, um es zu behalten",
|
||||
"settingsTest": "Testen",
|
||||
"settingsTesting": "Wird getestet…",
|
||||
"settingsTestOk": "Verbindung funktioniert",
|
||||
"settingsTestFailed": "Nicht erreichbar"
|
||||
},
|
||||
"commandHq": {
|
||||
"selectOne": "Wählen Sie einen Commander aus oder starten Sie einen neuen.",
|
||||
|
||||
@@ -206,7 +206,11 @@
|
||||
"settingsSaving": "Saving…",
|
||||
"settingsSaved": "Settings saved",
|
||||
"settingsFailed": "Could not save the settings",
|
||||
"settingsSecretSet": "Set — leave blank to keep"
|
||||
"settingsSecretSet": "Set — leave blank to keep",
|
||||
"settingsTest": "Test",
|
||||
"settingsTesting": "Testing…",
|
||||
"settingsTestOk": "Connection works",
|
||||
"settingsTestFailed": "Could not reach it"
|
||||
},
|
||||
"commandHq": {
|
||||
"selectOne": "Select a commander, or start a new one.",
|
||||
|
||||
@@ -206,7 +206,11 @@
|
||||
"settingsSaving": "Guardando…",
|
||||
"settingsSaved": "Ajustes guardados",
|
||||
"settingsFailed": "No se han podido guardar los ajustes",
|
||||
"settingsSecretSet": "Definido — déjalo vacío para mantenerlo"
|
||||
"settingsSecretSet": "Definido — déjalo vacío para mantenerlo",
|
||||
"settingsTest": "Probar",
|
||||
"settingsTesting": "Probando…",
|
||||
"settingsTestOk": "La conexión funciona",
|
||||
"settingsTestFailed": "No se ha podido alcanzar"
|
||||
},
|
||||
"commandHq": {
|
||||
"selectOne": "Selecciona un commander, o inicia uno nuevo.",
|
||||
|
||||
@@ -206,7 +206,11 @@
|
||||
"settingsSaving": "Enregistrement…",
|
||||
"settingsSaved": "Paramètres enregistrés",
|
||||
"settingsFailed": "Impossible d'enregistrer les paramètres",
|
||||
"settingsSecretSet": "Défini — laissez vide pour le conserver"
|
||||
"settingsSecretSet": "Défini — laissez vide pour le conserver",
|
||||
"settingsTest": "Tester",
|
||||
"settingsTesting": "Test en cours…",
|
||||
"settingsTestOk": "La connexion fonctionne",
|
||||
"settingsTestFailed": "Impossible de le joindre"
|
||||
},
|
||||
"commandHq": {
|
||||
"selectOne": "Sélectionnez un commander, ou démarrez-en un nouveau.",
|
||||
|
||||
@@ -206,7 +206,11 @@
|
||||
"settingsSaving": "A guardar…",
|
||||
"settingsSaved": "Definições guardadas",
|
||||
"settingsFailed": "Não foi possível guardar as definições",
|
||||
"settingsSecretSet": "Definido — deixe em branco para manter"
|
||||
"settingsSecretSet": "Definido — deixe em branco para manter",
|
||||
"settingsTest": "Testar",
|
||||
"settingsTesting": "A testar…",
|
||||
"settingsTestOk": "A ligação funciona",
|
||||
"settingsTestFailed": "Não foi possível alcançar"
|
||||
},
|
||||
"commandHq": {
|
||||
"selectOne": "Selecione um comandante ou inicie um novo.",
|
||||
|
||||
@@ -206,7 +206,11 @@
|
||||
"settingsSaving": "Sparar…",
|
||||
"settingsSaved": "Inställningarna sparades",
|
||||
"settingsFailed": "Det gick inte att spara inställningarna",
|
||||
"settingsSecretSet": "Angivet — lämna tomt för att behålla"
|
||||
"settingsSecretSet": "Angivet — lämna tomt för att behålla",
|
||||
"settingsTest": "Testa",
|
||||
"settingsTesting": "Testar…",
|
||||
"settingsTestOk": "Anslutningen fungerar",
|
||||
"settingsTestFailed": "Det gick inte att nå den"
|
||||
},
|
||||
"commandHq": {
|
||||
"selectOne": "Välj en commander, eller starta en ny.",
|
||||
|
||||
Reference in New Issue
Block a user