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 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<{
(e: 'submit', data: ClientCreate): void
(e: 'cancel'): void
@ -198,61 +202,3 @@ async function submitForm() {
}
}
</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"
help-text="Name of the secret"
:value="secretName"
@input="secretName = $event.target.value"
@sl-input="secretName = $event.target.value"
@input="emit('clearErrors')"
ref="nameField"
></sl-input>
<template v-if="group">
@ -59,15 +60,24 @@
<script setup lang="ts">
import type { SecretCreate } from '@/client'
import { ref } from 'vue'
import { ref, watch } from 'vue'
import { generateRandomPassword } from '@/api/password'
import AddSecretsToClients from '@/components/secrets/AddSecretToClients.vue'
import ClientSelectDropdown from '@/components/clients/ClientSelectDropdown.vue'
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 createSecretForm = ref<HTMLFormElement>()
@ -96,6 +106,10 @@ function cancelCreateSecret() {
emit('cancel')
}
function resetValidation() {
setFieldValidation(nameField, '')
}
function submitCreateSecret() {
validateName()
validateSecret()
@ -117,4 +131,15 @@ function submitCreateSecret() {
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>

View File

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