feat(12.1-01): cabana.ForbiddenError answers a refused write with 403
- hooks and bulk, record, toolbar and widget actions may return it - 403 forbidden with the localized message and field details; the write's transaction is rolled back; other errors stay the opaque 500 - form shows a refused save as a persistent banner and keeps the values; a refused delete is a toast - smoke tests, OpenAPI notes, dist, README, docs
This commit is contained in:
@@ -58,6 +58,9 @@ const schema = ref<FormView | null>(null)
|
||||
const values = ref<AdminRecord>({})
|
||||
const labels = ref<RecordMeta['labels']>({})
|
||||
const errors = ref<Record<string, string[]>>({})
|
||||
// The text of a save the server refused with 403 (UI-SPEC S6). It is a
|
||||
// banner, not a toast: it stays readable until the next save attempt.
|
||||
const forbidden = ref<string | null>(null)
|
||||
const loading = ref(true)
|
||||
const failed = ref(false)
|
||||
const busy = ref(false)
|
||||
@@ -245,6 +248,7 @@ async function save(): Promise<RecordEnvelope | null> {
|
||||
return null
|
||||
}
|
||||
busy.value = true
|
||||
forbidden.value = null
|
||||
try {
|
||||
const body = editablePayload(fields.value, values.value)
|
||||
const result =
|
||||
@@ -265,6 +269,11 @@ async function save(): Promise<RecordEnvelope | null> {
|
||||
}
|
||||
if (result.response.status === 422) {
|
||||
await showErrors(result.error?.error)
|
||||
} else if (result.response.status === 403) {
|
||||
// Nothing was saved: every entered value and the dirty state stay.
|
||||
// Fields the refusal names are marked and the first is focused.
|
||||
forbidden.value = result.error?.error.message || t('backend::lang.form.forbidden')
|
||||
await showErrors(result.error?.error)
|
||||
} else {
|
||||
showToast(result.error?.error.message || t('backend::lang.form.error_generic'), 'danger')
|
||||
}
|
||||
@@ -324,7 +333,8 @@ async function onDelete(): Promise<void> {
|
||||
await go(listPath)
|
||||
return
|
||||
}
|
||||
showToast(result.error?.error.message || t('backend::lang.form.error_generic'), 'danger')
|
||||
const fallback = result.response.status === 403 ? 'backend::lang.list.action_forbidden' : 'backend::lang.form.error_generic'
|
||||
showToast(result.error?.error.message || t(fallback), 'danger')
|
||||
} catch {
|
||||
showToast(t('backend::lang.form.error_generic'), 'danger')
|
||||
} finally {
|
||||
@@ -403,7 +413,7 @@ void load()
|
||||
</p>
|
||||
|
||||
<template v-else-if="schema">
|
||||
<FormErrorBanner :errors="errors" :field-names="fieldNames" />
|
||||
<FormErrorBanner :errors="errors" :field-names="fieldNames" :forbidden="forbidden" />
|
||||
<form
|
||||
:id="tabs.length > 0 ? panelDomId(ID_PREFIX, activeIndex) : undefined"
|
||||
:role="tabs.length > 0 ? 'tabpanel' : undefined"
|
||||
|
||||
Reference in New Issue
Block a user