Files
windmill/frontend/src/lib/components/SaveButton.svelte
T
Ruben Fiszel cac4bdd54f fix: gracefully handle malformed OAuth entries in instance config (#8205)
When an OAuth provider entry in instance settings has unexpected types
(e.g. `"true"` instead of `true` for req_body_auth), the entire
/api/settings/instance_config endpoint would fail with a deserialization
error, preventing access to any instance settings.

Introduce OAuthClientEntry enum that tries typed OAuthClient
deserialization first and falls back to raw JSON, logging the
deserialization error. This allows the settings page to load even when
individual OAuth entries are malformed.

Also show a user-visible error toast in SaveButton on save failure
instead of only logging to console.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:37:37 +00:00

102 lines
2.4 KiB
Svelte

<script lang="ts">
import { Button } from './common'
import { type ButtonType } from './common/button/model'
import { Save, CheckCircle2, AlertCircle, Loader2 } from 'lucide-svelte'
import { fly } from 'svelte/transition'
import { sendUserToast } from '$lib/toast'
let {
onSave,
disabled = false,
label = 'Save settings',
size = undefined,
unifiedSize = undefined,
variant = 'accent'
}: {
onSave: () => void | Promise<void>
disabled?: boolean
label?: string
size?: ButtonType.Size | undefined
unifiedSize?: ButtonType.UnifiedSize | undefined
variant?: ButtonType.Variant
} = $props()
let isSaving = $state(false)
let saveStatus: 'success' | 'error' | null = $state(null)
let statusTimeout: ReturnType<typeof setTimeout> | null = null
function clearStatusTimeout() {
if (statusTimeout !== null) {
clearTimeout(statusTimeout)
statusTimeout = null
}
}
function handleClick() {
if (isSaving) return
isSaving = true
saveStatus = null
clearStatusTimeout()
Promise.resolve(onSave())
.then(() => {
saveStatus = 'success'
statusTimeout = setTimeout(() => {
saveStatus = null
statusTimeout = null
}, 1500)
})
.catch((error) => {
console.error('Save failed:', error)
sendUserToast(error?.message ?? 'Save failed', true)
saveStatus = 'error'
statusTimeout = setTimeout(() => {
saveStatus = null
statusTimeout = null
}, 3000)
})
.finally(() => {
isSaving = false
})
}
$effect(() => {
return () => {
clearStatusTimeout()
}
})
</script>
<div class="relative overflow-hidden rounded-md">
<Button
{variant}
{size}
{unifiedSize}
startIcon={{
icon: isSaving ? Loader2 : Save,
classes: isSaving ? 'animate-spin' : ''
}}
disabled={disabled || isSaving}
onClick={handleClick}
>
{isSaving ? 'Saving...' : label}
</Button>
{#if saveStatus === 'success'}
<div
class="absolute inset-0 flex items-center justify-center bg-green-200 dark:bg-green-800 rounded-md"
transition:fly={{ y: -10, duration: 300 }}
>
<CheckCircle2 class="w-5 h-5 text-green-700 dark:text-green-300" />
</div>
{:else if saveStatus === 'error'}
<div
class="absolute inset-0 flex items-center justify-center bg-red-500 dark:bg-red-700 rounded-md"
transition:fly={{ y: -10, duration: 300 }}
>
<AlertCircle class="w-5 h-5 text-white" />
</div>
{/if}
</div>