Various frontend fixes when connected user is a domain admin

This commit is contained in:
Antoine Nguyen
2025-06-11 18:16:00 +02:00
parent 820045582a
commit d2c44b1c39
20 changed files with 162 additions and 74 deletions
@@ -19,6 +19,7 @@
v-model="selectedAccount"
:label="$gettext('Select an account')"
:items="accounts"
variant="outlined"
item-title="username"
class="mt-5"
return-object
@@ -106,7 +106,7 @@
<td>
<v-progress-linear v-model="item.allocated_quota_in_percent" />
</td>
<td>
<td v-if="canSetRole">
<div class="text-right">
<v-menu offset-y>
<template #activator="{ props }">
@@ -181,9 +181,10 @@
</template>
<script setup lang="js">
import { useBusStore, useDomainsStore } from '@/stores'
import { useAuthStore, useBusStore, useDomainsStore } from '@/stores'
import { useGettext } from 'vue3-gettext'
import { useRouter } from 'vue-router'
import { usePermissions } from '@/composables/permissions'
import DomainAdminList from './DomainAdminList.vue'
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
import DNSStatusChip from './DNSStatusChip.vue'
@@ -195,6 +196,7 @@ import { computed, ref, onMounted } from 'vue'
const { $gettext } = useGettext()
const router = useRouter()
const { canSetRole } = usePermissions()
const busStore = useBusStore()
const domainStore = useDomainsStore()
const domains = computed(() => domainStore.domains)
@@ -211,13 +213,16 @@ const domainHeaders = [
},
{ title: $gettext('Sending limit'), key: 'message_limit' },
{ title: $gettext('Quota'), key: 'allocated_quota_in_percent' },
{
]
if (canSetRole.value) {
domainHeaders.push({
title: $gettext('Actions'),
value: 'actions',
sortable: false,
align: 'end',
},
]
})
}
const confirm = ref()
const keepDomainFolder = ref(false)
@@ -43,7 +43,7 @@
:title="$gettext('Copy to clipboard')"
@click="copyPassword"
>
{{ account.password }}
{{ account.new_password }}
</v-btn>
</v-col>
</v-row>
@@ -72,8 +72,10 @@ import AccountAliasForm from './form_steps/AccountAliasForm.vue'
import { useGettext } from 'vue3-gettext'
import { ref, computed, onMounted } from 'vue'
import { useBusStore, useDomainsStore, useIdentitiesStore } from '@/stores'
import { usePermissions } from '@/composables/permissions'
const { $gettext } = useGettext()
const { canSetRole } = usePermissions()
const busStore = useBusStore()
const domainsStore = useDomainsStore()
const identitiesStore = useIdentitiesStore()
@@ -157,16 +159,17 @@ const summarySections = computed(() => {
})
const steps = computed(() => {
const result = [
{
const result = []
if (canSetRole.value) {
result.push({
name: 'role',
title: $gettext('Role'),
},
{
name: 'identification',
title: $gettext('Identification'),
},
]
})
}
result.push({
name: 'identification',
title: $gettext('Identification'),
})
if (needsMailbox.value) {
return result.concat([
{
@@ -206,7 +209,7 @@ const formStepsComponents = {
}
function copyPassword() {
navigator.clipboard.writeText(account.value.password).then(() => {
navigator.clipboard.writeText(account.value.new_password).then(() => {
busStore.displayNotification({
msg: $gettext('Password copied to clipboard'),
})
@@ -254,6 +257,9 @@ function close() {
function submit() {
const data = preparePayload({ ...account.value })
data.password = data.new_password
delete data.new_password
delete data.password_confirmation
identitiesStore.createIdentity('account', data).then(() => {
emit('created')
close()
@@ -2,7 +2,7 @@
<LoadingData v-if="!accountsStore.accountsLoaded || working" />
<div v-else>
<v-expansion-panels v-model="panel" :multiple="formErrors">
<v-expansion-panel eager value="roleForm">
<v-expansion-panel v-if="canSetRole" eager value="roleForm">
<v-expansion-panel-title v-slot="{ expanded }">
<v-row no-gutters>
<v-col cols="4">
@@ -198,7 +198,7 @@
</template>
<script setup lang="js">
import { useAccountsStore, useIdentitiesStore } from '@/stores'
import { useAccountsStore, useAuthStore, useIdentitiesStore } from '@/stores'
import AccountGeneralForm from './form_steps/AccountGeneralForm.vue'
import AccountMailboxForm from './form_steps/AccountMailboxForm.vue'
import AccountAliasForm from './form_steps/AccountAliasForm.vue'
@@ -206,17 +206,18 @@ import AccountRoleForm from './form_steps/AccountRoleForm.vue'
import ResourcesForm from '@/components/tools/ResourcesForm.vue'
import LoadingData from '@/components/tools/LoadingData.vue'
import parametersApi from '@/api/parameters'
import { ref, computed } from 'vue'
import { ref, computed, onMounted, watch } from 'vue'
import { useGettext } from 'vue3-gettext'
import { onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { watch } from 'vue'
import { usePermissions } from '@/composables/permissions'
const { $gettext } = useGettext()
const accountsStore = useAccountsStore()
const authStore = useAuthStore()
const identitiesStore = useIdentitiesStore()
const route = useRoute()
const router = useRouter()
const { canSetRole } = usePermissions()
const editedAccount = ref({ pk: route.params.id })
@@ -290,8 +291,9 @@ async function save() {
} else {
delete data.mailbox.full_address
}
if (!data.password) {
delete data.password
if (data.new_password) {
data.password = data.new_password
delete data.new_password
delete data.password_confirmation
}
if (needsResources.value && resourcesForm.value !== null) {
@@ -300,6 +302,13 @@ async function save() {
if (data.aliases === null) {
delete data.aliases
}
if (!canSetRole.value) {
if (editedAccount.value.pk === authStore.authUser.pk) {
data.role = authStore.authUser.role
} else {
data.role = 'SimpleUsers'
}
}
identitiesStore.updateIdentity('account', data).then(() => {
router.push({
name: 'AccountDetail',
@@ -316,9 +325,11 @@ onMounted(() => {
.getAccount(route.params.id)
.then((response) => (editedAccount.value = { ...response.data }))
parametersApi.getGlobalApplication('limits').then((response) => {
limitsConfig.value = response.data
})
if (canSetRole.value) {
parametersApi.getGlobalApplication('limits').then((response) => {
limitsConfig.value = response.data
})
}
})
watch(
@@ -31,11 +31,15 @@
/>
<AccountPasswordSubForm
v-if="authStore.authUser.pk !== account.pk"
ref="passwordForm"
v-model="account"
:editing="editing"
:form-errors="formErrors"
/>
<v-alert v-else type="info" variant="tonal" class="py-2">
{{ $gettext('You can update your password from the Account section') }}
</v-alert>
<v-switch
v-model="account.is_active"
@@ -51,9 +55,12 @@ import AccountPasswordSubForm from './AccountPasswordSubForm.vue'
import EmailField from '@/components/tools/EmailField.vue'
import { ref, computed } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useAuthStore } from '@/stores'
import rules from '@/plugins/rules'
const { $gettext } = useGettext()
const authStore = useAuthStore()
const props = defineProps({
modelValue: { type: Object, default: null },
editing: { type: Boolean, default: false },
@@ -122,7 +122,7 @@ function cleanQuota(value) {
function cleanMessageLimit(value) {
if (value === '') {
delete form.value.mailbox.message_limit
form.value.mailbox.message_limit = null
}
}
@@ -3,14 +3,15 @@
<template v-if="withPasswordCheck">
<label class="m-label">{{ $gettext('Current password') }}</label>
<v-text-field
v-model="account.currentPassword"
v-model="account.password"
autocomplete="new-password"
variant="outlined"
type="password"
density="compact"
validate-on="submit"
:rules="disabled ? [] : [rules.required, validPassword]"
:rules="disabled ? [] : [rules.required]"
:disabled="disabled"
:error-messages="props.formErrors ? props.formErrors.password : []"
/>
</template>
<v-row justify="center">
@@ -32,7 +33,7 @@
class="ml-6"
density="compact"
>
<span class="text-white mr-4">{{ account.password }}</span>
<span class="text-white mr-4">{{ account.new_password }}</span>
<v-btn
size="small"
color="white"
@@ -48,7 +49,7 @@
</v-row>
<template v-if="!account.random_password">
<v-text-field
v-model="account.password"
v-model="account.new_password"
variant="outlined"
:label="$gettext('Password')"
type="password"
@@ -65,9 +66,7 @@
density="compact"
:disabled="disabled"
:rules="isRuleActive ? [rules.required, passwordConfirmationRules] : []"
:error-messages="
formErrors && formErrors.value ? formErrors.value.password : []
"
:error-messages="props.formErrors ? props.formErrors.new_password : []"
/>
</template>
</div>
@@ -76,7 +75,6 @@
<script setup lang="js">
import { useBusStore } from '@/stores'
import accountsApi from '@/api/accounts'
import accountApi from '@/api/account'
import { computed } from 'vue'
import rules from '@/plugins/rules'
import { useGettext } from 'vue3-gettext'
@@ -98,10 +96,10 @@ const isRuleActive = computed(
)
const passwordConfirmationRules = (value) =>
value === account.value.password || $gettext('Password mismatch')
value === account.value.new_password || $gettext('Password mismatch')
function copyPassword() {
navigator.clipboard.writeText(account.value.password).then(() => {
navigator.clipboard.writeText(account.value.new_password).then(() => {
busStore.displayNotification({
msg: $gettext('Password copied to clipboard'),
})
@@ -111,21 +109,11 @@ function copyPassword() {
function updatePassword(value) {
if (value) {
accountsApi.getRandomPassword().then((resp) => {
account.value.password = resp.data.password
account.value.new_password = resp.data.password
})
} else {
account.value.password = null
account.value.new_password = null
account.value.password_confirmation = null
}
}
async function validPassword(value) {
if (value) {
return accountApi
.checkPassword(value)
.then(() => true)
.catch(() => $gettext('Invalid password'))
}
return true
}
</script>
@@ -36,6 +36,7 @@
<v-text-field
v-model="alias.address"
:label="$gettext('Generated address')"
variant="outlined"
readonly
>
<template #append-inner>