mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 08:00:59 +00:00
cac4bdd54f
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>
102 lines
2.4 KiB
Svelte
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>
|