Files
Infrastruktur-Monitoring-Sy…/frontend/src/components/UserForm.vue
Dennis Nemec f1136fdf3d
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / ui (push) Has been cancelled
WP-01: authentication, user management and application shell
Argon2id password hashing, JWT access tokens with rotating HttpOnly refresh
cookies and reuse detection, login rate limiting, auth audit log, bootstrap
admin, admin-only user CRUD and password reset, OpenAPI spec. Vue login page,
auth store with automatic token refresh, route guards, sidebar shell with
toasts and placeholder pages, user management page.

All tests green: 40 backend, 12 Vitest, 4 Playwright.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 21:37:14 +02:00

105 lines
3.1 KiB
Vue

<script setup lang="ts">
import { ref } from 'vue'
import type { CreateUserPayload, Role, UpdateUserPayload, User } from '../api/types'
const props = defineProps<{ mode: 'create' | 'edit'; user?: User; busy?: boolean }>()
const emit = defineEmits<{ submit: [payload: CreateUserPayload | UpdateUserPayload]; cancel: [] }>()
const email = ref(props.user?.email ?? '')
const displayName = ref(props.user?.display_name ?? '')
const password = ref('')
const role = ref<Role>(props.user?.role ?? 'user')
const isActive = ref(props.user?.is_active ?? true)
const error = ref('')
function submit() {
error.value = ''
if (props.mode === 'create') {
if (password.value.length < 12) {
error.value = 'Password must have at least 12 characters'
return
}
emit('submit', {
email: email.value,
display_name: displayName.value,
password: password.value,
role: role.value,
})
} else {
emit('submit', { display_name: displayName.value, role: role.value, is_active: isActive.value })
}
}
</script>
<template>
<form class="space-y-4" @submit.prevent="submit">
<div v-if="mode === 'create'">
<label for="uf-email" class="block text-sm font-medium">Email</label>
<input
id="uf-email"
v-model="email"
name="email"
type="email"
required
class="mt-1 w-full rounded-md border border-gray-300 px-3 py-2"
/>
</div>
<div>
<label for="uf-name" class="block text-sm font-medium">Display name</label>
<input
id="uf-name"
v-model="displayName"
name="display_name"
type="text"
required
class="mt-1 w-full rounded-md border border-gray-300 px-3 py-2"
/>
</div>
<div v-if="mode === 'create'">
<label for="uf-password" class="block text-sm font-medium">Password</label>
<input
id="uf-password"
v-model="password"
name="password"
type="password"
required
autocomplete="new-password"
class="mt-1 w-full rounded-md border border-gray-300 px-3 py-2"
/>
</div>
<div>
<label for="uf-role" class="block text-sm font-medium">Role</label>
<select
id="uf-role"
v-model="role"
name="role"
class="mt-1 w-full rounded-md border border-gray-300 px-3 py-2"
>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<label v-if="mode === 'edit'" class="flex items-center gap-2 text-sm">
<input v-model="isActive" name="is_active" type="checkbox" />
Active
</label>
<p v-if="error" role="alert" class="text-sm text-red-600">{{ error }}</p>
<div class="flex justify-end gap-2">
<button
type="button"
class="rounded-md border border-gray-300 px-4 py-2 text-sm"
@click="emit('cancel')"
>
Cancel
</button>
<button
type="submit"
:disabled="busy"
class="rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
>
{{ mode === 'create' ? 'Create' : 'Save' }}
</button>
</div>
</form>
</template>