diff --git a/ui/src/i18n/en.js b/ui/src/i18n/en.js index 778f7a6..0155406 100644 --- a/ui/src/i18n/en.js +++ b/ui/src/i18n/en.js @@ -2,6 +2,10 @@ // time. Keep keys flat (dot-separated) to match the host's existing // convention. export default { + 'delegate.type.user': 'User', + 'delegate.type.group': 'Group', + 'delegate.type.company': 'Company', + 'delegate.type.tree': 'Tree', 'user.deleteConfirmBefore': 'Are you sure you want to delete ', 'user.deleteConfirmAfter': '?', 'group.deleteConfirmBefore': 'Are you sure you want to delete ', diff --git a/ui/src/i18n/fr.js b/ui/src/i18n/fr.js index 4a9c5cd..d7d5b4f 100644 --- a/ui/src/i18n/fr.js +++ b/ui/src/i18n/fr.js @@ -2,6 +2,10 @@ // time. Keep keys flat (dot-separated) to match the host's existing // convention. export default { + 'delegate.type.user': 'Utilisateur', + 'delegate.type.group': 'Groupe', + 'delegate.type.company': 'Entité', + 'delegate.type.tree': 'Arborescence', 'user.deleteConfirmBefore': 'Êtes-vous certain de supprimer ', 'user.deleteConfirmAfter': ' ?', 'group.deleteConfirmBefore': 'Êtes-vous certain de supprimer ', diff --git a/ui/src/views/DelegateEditView.vue b/ui/src/views/DelegateEditView.vue index ae55b6e..9fec6b2 100644 --- a/ui/src/views/DelegateEditView.vue +++ b/ui/src/views/DelegateEditView.vue @@ -6,9 +6,25 @@ - + + + - + + + @@ -73,8 +89,39 @@ const confirmDelete = ref(false) const isEdit = computed(() => !!route.params.id) -const receiverTypes = ['USER', 'GROUP', 'COMPANY'] -const resourceTypes = ['USER', 'GROUP', 'COMPANY', 'TREE'] +// Static icon map keyed by the raw enum value. Const at module scope so +// it is never re-created reactively — referenced from both the +// prepend-inner-icon computed (selected value) and the #item slot +// (dropdown rows). +const TYPE_ICONS = { + USER: 'mdi-account', + GROUP: 'mdi-account-group', + COMPANY: 'mdi-domain', + TREE: 'mdi-file-tree', +} + +// Items as plain objects with the raw enum value + an i18n key. v-model +// still holds the value, item-value="value" wires it back. The earlier +// attempt at adding icons via the #selection slot (with item.raw.icon) +// triggered "Maximum recursive updates exceeded" inside v-select — this +// version keeps only the #item slot and renders the selected icon via +// prepend-inner-icon, which sidesteps that loop. +const receiverTypes = [ + { value: 'USER', titleKey: 'delegate.type.user' }, + { value: 'GROUP', titleKey: 'delegate.type.group' }, + { value: 'COMPANY', titleKey: 'delegate.type.company' }, +] +const resourceTypes = [ + { value: 'USER', titleKey: 'delegate.type.user' }, + { value: 'GROUP', titleKey: 'delegate.type.group' }, + { value: 'COMPANY', titleKey: 'delegate.type.company' }, + { value: 'TREE', titleKey: 'delegate.type.tree' }, +] + +/** v-select item-title callback: resolves the i18n key from the item object. */ +function typeTitle(item) { + return t(item.titleKey) +} const form = ref({ receiver: '', @@ -85,6 +132,10 @@ const form = ref({ canWrite: false, }) +// Icon shown inside the field, driven by the currently selected value. +const receiverIcon = computed(() => TYPE_ICONS[form.value.receiverType] || '') +const typeIcon = computed(() => TYPE_ICONS[form.value.type] || '') + const { showGuardDialog, confirmLeave, cancelLeave, markClean, init: initGuard } = useFormGuard(form) const rules = { @@ -97,9 +148,13 @@ onMounted(async () => { const data = await api.get(`rest/security/delegate/${route.params.id}`) if (data) { form.value.receiver = data.receiver?.id || data.receiver || '' - form.value.receiverType = data.receiverType || 'USER' + // Normalize to the uppercase enum form used by the v-select items. + // The backend stores some delegates with lowercase values ("company", + // "tree", …) and v-model would otherwise mismatch every item, locking + // the select in a "Maximum recursive updates exceeded" loop. + form.value.receiverType = (data.receiverType || 'USER').toUpperCase() form.value.name = data.name || '' - form.value.type = data.type || 'GROUP' + form.value.type = (data.type || 'GROUP').toUpperCase() form.value.canAdmin = !!data.canAdmin form.value.canWrite = !!data.canWrite }