Implement validation

This commit is contained in:
2025-07-16 09:22:13 +02:00
parent f8eac2b09c
commit f0c729cba7
3 changed files with 54 additions and 67 deletions

View File

@ -84,7 +84,11 @@ const sourceField = ref<HTMLSlInputElement>()
const publicKeyField = ref<HTMLSlInputElement>() const publicKeyField = ref<HTMLSlInputElement>()
const clientCreateForm = ref<HTMLElement>() const clientCreateForm = ref<HTMLElement>()
const props = defineProps<{ client?: Client | null; errors: any[] | null }>() interface Props {
client?: Client
errors?: any[]
}
const props = defineProps<Props>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'submit', data: ClientCreate): void (e: 'submit', data: ClientCreate): void
(e: 'cancel'): void (e: 'cancel'): void
@ -198,61 +202,3 @@ async function submitForm() {
} }
} }
</script> </script>
<style>
.client-form sl-input,
.client-form sl-select,
.client-form sl-checkbox {
display: block;
margin-bottom: var(--sl-spacing-medium);
}
/* user invalid styles */
.client-form sl-input[data-user-invalid]::part(base),
.client-form sl-select[data-user-invalid]::part(combobox),
.client-form sl-checkbox[data-user-invalid]::part(control) {
border-color: var(--sl-color-danger-600);
}
.client-form [data-user-invalid]::part(form-control-label),
.client-form [data-user-invalid]::part(form-control-help-text),
.client-form sl-checkbox[data-user-invalid]::part(label) {
color: var(--sl-color-danger-700);
}
.client-form sl-checkbox[data-user-invalid]::part(control) {
outline: none;
}
.client-form sl-input:focus-within[data-user-invalid]::part(base),
.client-form sl-select:focus-within[data-user-invalid]::part(combobox),
.client-form sl-checkbox:focus-within[data-user-invalid]::part(control) {
border-color: var(--sl-color-danger-600);
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-300);
}
/* User valid styles */
.client-form sl-input[data-user-valid]::part(base),
.client-form sl-select[data-user-valid]::part(combobox),
.client-form sl-checkbox[data-user-valid]::part(control) {
border-color: var(--sl-color-success-600);
}
.client-form [data-user-valid]::part(form-control-label),
.client-form [data-user-valid]::part(form-control-help-text),
.client-form sl-checkbox[data-user-valid]::part(label) {
color: var(--sl-color-success-700);
}
.client-form sl-checkbox[data-user-valid]::part(control) {
background-color: var(--sl-color-success-600);
outline: none;
}
.client-form sl-input:focus-within[data-user-valid]::part(base),
.client-form sl-select:focus-within[data-user-valid]::part(combobox),
.client-form sl-checkbox:focus-within[data-user-valid]::part(control) {
border-color: var(--sl-color-success-600);
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-success-300);
}
</style>

View File

@ -6,7 +6,8 @@
autocomplete="off" autocomplete="off"
help-text="Name of the secret" help-text="Name of the secret"
:value="secretName" :value="secretName"
@input="secretName = $event.target.value" @sl-input="secretName = $event.target.value"
@input="emit('clearErrors')"
ref="nameField" ref="nameField"
></sl-input> ></sl-input>
<template v-if="group"> <template v-if="group">
@ -59,15 +60,24 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SecretCreate } from '@/client' import type { SecretCreate } from '@/client'
import { ref } from 'vue' import { ref, watch } from 'vue'
import { generateRandomPassword } from '@/api/password' import { generateRandomPassword } from '@/api/password'
import AddSecretsToClients from '@/components/secrets/AddSecretToClients.vue' import AddSecretsToClients from '@/components/secrets/AddSecretToClients.vue'
import ClientSelectDropdown from '@/components/clients/ClientSelectDropdown.vue' import ClientSelectDropdown from '@/components/clients/ClientSelectDropdown.vue'
import { setFieldValidation } from '@/api/validation' import { setFieldValidation } from '@/api/validation'
const props = defineProps<{ group?: string }>() interface Props {
group?: string
errors?: any[]
}
const emit = defineEmits<{ (e: 'submit', data: SecretCreate): void; (e: 'cancel'): void }>() const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'submit', data: SecretCreate): void
(e: 'cancel'): void
(e: 'clearErrors'): void
}>()
const secretName = ref<string>() const secretName = ref<string>()
const createSecretForm = ref<HTMLFormElement>() const createSecretForm = ref<HTMLFormElement>()
@ -96,6 +106,10 @@ function cancelCreateSecret() {
emit('cancel') emit('cancel')
} }
function resetValidation() {
setFieldValidation(nameField, '')
}
function submitCreateSecret() { function submitCreateSecret() {
validateName() validateName()
validateSecret() validateSecret()
@ -117,4 +131,15 @@ function submitCreateSecret() {
emit('submit', secretCreate) emit('submit', secretCreate)
} }
} }
watch(
() => props.errors,
(errors) => {
resetValidation()
const nameErrors = errors.filter((e) => e.loc.includes('name'))
if (nameErrors.length > 0) {
setFieldValidation(nameField, nameErrors[0].msg)
}
},
)
</script> </script>

View File

@ -58,6 +58,8 @@
:group="createSecretParent" :group="createSecretParent"
@submit="createSecret" @submit="createSecret"
@cancel="createSecretDrawer = false" @cancel="createSecretDrawer = false"
:errors="createErrors"
@clearErrors="clearCreateErrors"
/> />
</Drawer> </Drawer>
</template> </template>
@ -71,6 +73,8 @@ import { useAlertsStore } from '@/store/useAlertsStore'
import { SshecretAdmin } from '@/client' import { SshecretAdmin } from '@/client'
import { SshecretObjectType } from '@/api/types' import { SshecretObjectType } from '@/api/types'
import { splitPath } from '@/api/paths' import { splitPath } from '@/api/paths'
import { ValidationError } from '@/api/errors'
import { assertSdkResponseOk } from '@/api/AssertSdkResponseOk'
import SecretGroup from '@/components/secrets/SecretGroup.vue' import SecretGroup from '@/components/secrets/SecretGroup.vue'
import SecretGroupTreeItem from '@/components/secrets/SecretGroupTreeItem.vue' import SecretGroupTreeItem from '@/components/secrets/SecretGroupTreeItem.vue'
import SecretGroupTreeEntry from '@/components/secrets/SecretGroupTreeEntry.vue' import SecretGroupTreeEntry from '@/components/secrets/SecretGroupTreeEntry.vue'
@ -101,6 +105,8 @@ const createDrawerKey = ref(0)
const createGroupDrawer = ref<boolean>(false) const createGroupDrawer = ref<boolean>(false)
const createSecretDrawer = ref<boolean>(false) const createSecretDrawer = ref<boolean>(false)
const createErrors = ref([])
function cancelCreateGroup() { function cancelCreateGroup() {
createGroupDrawer.value = false createGroupDrawer.value = false
drawerKey.value += 1 drawerKey.value += 1
@ -172,7 +178,8 @@ async function createSecret(secretCreate: SecretCreate) {
const response = await SshecretAdmin.addSecretApiV1SecretsPost({ const response = await SshecretAdmin.addSecretApiV1SecretsPost({
body: secretCreate, body: secretCreate,
}) })
if (response.status == 200) { try {
assertSdkResponseOk(response)
alerts.showAlert('Secret created', 'success') alerts.showAlert('Secret created', 'success')
// We can close the drawer now. // We can close the drawer now.
createSecretDrawer.value = false createSecretDrawer.value = false
@ -185,11 +192,20 @@ async function createSecret(secretCreate: SecretCreate) {
} }
treeState.selectSecret(secretCreate.name) treeState.selectSecret(secretCreate.name)
} catch (err) {
if (err instanceof ValidationError) {
createErrors.value = err.errors
} else { } else {
console.error(response) const errorMessage = err.message ?? 'Unknown error'
alerts.showAlert('Secret creation failed', 'error') alerts.showAlert(`Error from backend: ${errorMessage}`, 'error')
} }
} }
}
function clearCreateErrors() {
// Clear any errors from the create form.
createErrors.value = []
}
watch( watch(
() => treeState.secretGroupRevision, () => treeState.secretGroupRevision,