Added form to edit an account.

This commit is contained in:
Antoine Nguyen
2021-05-05 11:24:12 +02:00
parent 289e8eac46
commit 8fa43f75cf
12 changed files with 320 additions and 38 deletions
+6
View File
@@ -6,6 +6,9 @@ export default {
getAll ({ domain, role }) {
return repository.get(`/${resource}/`, { params: { domain, role } })
},
get (accountId) {
return repository.get(`${resource}/${accountId}/`)
},
getRandomPassword () {
return repository.get(`${resource}/random_password`)
},
@@ -14,5 +17,8 @@ export default {
},
create (data) {
return repository.post(`${resource}/`, data)
},
patch (accountId, data) {
return repository.patch(`${resource}/${accountId}/`, data)
}
}
@@ -265,14 +265,14 @@ export default {
try {
await accounts.validate(this.account)
} catch (error) {
if (error.response.data.mailbox && error.response.data.mailbox.full_address) {
this.$refs[`form_${current}`].$refs.observer.setErrors({
username: error.response.data.mailbox.full_address
})
}
this.$refs[`form_${current}`].$refs.observer.setErrors(error.response.data)
return
}
if (next === 5 && this.account.random_password) {
accounts.getRandomPassword().then(resp => {
this.$set(this.account, 'password', resp.data.password)
})
}
this.step = next
},
submit () {
@@ -0,0 +1,192 @@
<template>
<div>
<v-expansion-panels v-model="panel">
<v-expansion-panel>
<v-expansion-panel-header v-slot="{ open }">
<v-row no-gutters>
<v-col cols="4">
<translate>Role</translate>
</v-col>
<v-col
cols="8"
class="text--secondary"
>
<v-fade-transition leave-absolute>
<span v-if="open"></span>
<v-row
v-else
no-gutters
style="width: 100%"
>
<v-col cols="6">
{{ $refs.roleForm.roleLabel }}
</v-col>
</v-row>
</v-fade-transition>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content>
<account-role-form ref="roleForm" :account="editedAccount" />
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel>
<v-expansion-panel-header v-slot="{ open }">
<v-row no-gutters>
<v-col cols="4">
<translate>Identification</translate>
</v-col>
<v-col
cols="8"
class="text--secondary"
>
<v-fade-transition leave-absolute>
<span v-if="open"></span>
<v-row
v-else
no-gutters
style="width: 100%"
>
<v-col cols="6">
<translate class="mr-2">Username:</translate> {{ editedAccount.username }}
</v-col>
<v-col cols="6">
<translate class="mr-2">Enabled</translate>
<v-icon color="success" v-if="editedAccount.is_active">mdi-check-circle-outline</v-icon>
<v-icon v-else>mdi-close-circle-outline</v-icon>
</v-col>
</v-row>
</v-fade-transition>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content>
<account-general-form ref="generalForm" :account="editedAccount" />
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel v-if="usernameIsEmail">
<v-expansion-panel-header v-slot="{ open }">
<v-row no-gutters>
<v-col cols="4">
<translate>Mailbox</translate>
</v-col>
<v-col
cols="8"
class="text--secondary"
>
<v-fade-transition leave-absolute>
<span v-if="open"></span>
<v-row
v-else
no-gutters
style="width: 100%"
>
<template v-if="account.mailbox">
<v-col cols="6" v-if="account.mailbox.use_domain_quota">
<translate class="mr-2">Quota: </translate> <translate>domain's default value</translate>
</v-col>
<v-col cols="6" v-else>
<translate class="mr-2">Quota: </translate> {{ account.mailbox.quota }}
</v-col>
</template>
</v-row>
</v-fade-transition>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content>
<account-mailbox-form ref="mailboxForm" :account="editedAccount" />
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
<div class="mt-4 d-flex justify-end">
<v-btn color="grey lighten-1" @click="$router.go(-1)">
<translate>Cancel</translate>
</v-btn>
<v-btn class="ml-4" color="primary darken-1" @click="save">
<translate>Save</translate>
</v-btn>
</div>
</div>
</template>
<script>
import _isEmpty from 'lodash/isEmpty'
import { bus } from '@/main'
import accounts from '@/api/accounts'
import AccountGeneralForm from './AccountGeneralForm'
import AccountMailboxForm from './AccountMailboxForm'
import AccountRoleForm from './AccountRoleForm'
export default {
components: {
AccountGeneralForm,
AccountMailboxForm,
AccountRoleForm
},
props: ['account'],
computed: {
usernameIsEmail () {
return this.editedAccount.username.indexOf('@') !== -1
}
},
data () {
return {
editedAccount: {},
panel: 0
}
},
methods: {
async save () {
if (this.$refs.generalForm !== undefined) {
const valid = await this.$refs.generalForm.$refs.observer.validate()
if (!valid) {
return
}
}
try {
const data = JSON.parse(JSON.stringify(this.editedAccount))
if (this.usernameIsEmail) {
data.mailbox.full_address = data.username
} else {
delete data.mailbox.full_address
}
if (_isEmpty(data.mailbox)) {
delete data.mailbox
}
if (data.random_password || !data.password) {
delete data.password
delete data.password_confirmation
}
await accounts.patch(this.editedAccount.pk, data).then(resp => {
bus.$emit('notification', { msg: this.$gettext('Account updated') })
})
} catch (error) {
if (this.$refs.generalForm) {
this.$refs.generalForm.$refs.observer.setErrors(error.response.data)
}
if (this.$refs.mailboxForm) {
this.$refs.mailboxForm.$refs.observer.setErrors(error.response.data)
}
}
}
},
mounted () {
this.$store.dispatch('identities/fetchDomains')
},
watch: {
account: {
handler: function (val) {
if (val) {
this.editedAccount = JSON.parse(JSON.stringify(val))
delete this.editedAccount.domains
if (this.editedAccount.mailbox === null) {
this.editedAccount.mailbox = {}
}
}
},
immediate: true
}
}
}
</script>
@@ -7,9 +7,11 @@
>
<label class="m-label">{{ $gettext('Username') }}</label>
<email-field
ref="username"
v-model="account.username"
:error-messages="errors"
:placeholder="usernamePlaceholder"
:type="usernameInputType"
outlined
dense
/>
@@ -26,15 +28,26 @@
outlined
dense
/>
<v-switch
v-model="account.random_password"
:label="'Random password' | translate"
dense
/>
<div class="d-flex align-center">
<v-switch
v-model="account.random_password"
:label="'Random password'|translate"
@change="updatePassword"
dense
/>
<template v-if="account.pk && account.random_password">
<v-alert style="background-color: #515D78" class="ml-6" dense>
<span class="white--text mr-4">{{ account.password }}</span>
<v-btn small color="white" icon :title="'Copy to clipboard'|translate" @click="copyPassword">
<v-icon>mdi-clipboard-multiple-outline</v-icon>
</v-btn>
</v-alert>
</template>
</div>
<validation-provider
v-if="!account.random_password"
v-slot="{ errors }"
rules="required"
:rules="passwordRules"
vid="password"
>
<label class="m-label">{{ $gettext('Password') }}</label>
@@ -49,7 +62,7 @@
<validation-provider
v-if="!account.random_password"
v-slot="{ errors }"
rules="required"
:rules="passwordConfirmationRules"
>
<label class="m-label">{{ $gettext('Confirmation') }}</label>
<v-text-field
@@ -69,6 +82,8 @@
</template>
<script>
import { bus } from '@/main'
import accounts from '@/api/accounts'
import EmailField from '@/components/tools/EmailField'
export default {
@@ -77,20 +92,43 @@ export default {
},
props: ['account'],
computed: {
passwordRules () {
return (this.account.pk) ? '' : 'required'
},
passwordConfirmationRules () {
return (this.account.password) ? 'required' : ''
},
usernamePlaceholder () {
if (this.account.role === 'SimpleUsers') {
return this.$gettext('Enter an email address')
}
return this.$gettext('Enter a simple username or an email address')
}
},
data () {
return {
},
usernameInputType () {
return (this.account.role === 'SimpleUsers') ? 'email' : 'text'
}
},
methods: {
copyPassword () {
navigator.clipboard.writeText(this.account.password).then(() => {
bus.$emit('notification', { msg: this.$gettext('Password copied to clipboard') })
})
},
updatePassword (value) {
if (value) {
accounts.getRandomPassword().then(resp => {
this.$set(this.account, 'password', resp.data.password)
})
} else {
this.$set(this.account, 'password', null)
this.$set(this.account, 'password_confirmation', null)
}
}
},
mounted () {
if (this.account.random_password) {
this.updatePassword(true)
}
}
}
</script>
@@ -1,18 +1,5 @@
<template>
<validation-observer ref="observer">
<validation-provider
v-slot="{ errors }"
rules="required"
>
<v-text-field
v-model="account.mailbox.full_address"
:label="'Email' | translate"
:error-messages="errors"
:disabled="account.role === 'SimpleUsers'"
outlined
dense
/>
</validation-provider>
<label class="m-label">{{ $gettext('Quota') }}</label>
<v-switch
v-model="account.mailbox.use_domain_quota"
@@ -7,7 +7,7 @@
<v-row>
<v-col cols="7">
<choice-field v-model="account.role"
:label="'Role' | translate"
:label="'Choose a role' | translate"
:choices="accountRoles"
:error-messages="errors"
:choices-per-line="2"
@@ -35,10 +35,12 @@ export default {
props: ['account'],
computed: {
roleLabel () {
return this.accountRoles.find(role => role.value === this.account.role).label
const role = this.accountRoles.find(role => role.value === this.account.role)
return role !== undefined ? role.label : ''
},
roleHelp () {
return this.accountRoles.find(role => role.value === this.account.role).help
const role = this.accountRoles.find(role => role.value === this.account.role)
return role !== undefined ? role.help : ''
}
},
data () {
@@ -49,7 +49,7 @@
<v-icon>mdi-dots-horizontal</v-icon>
</v-btn>
</template>
<menu-items :items="menuItems" :object="item" />
<menu-items :items="getMenuItems(item)" :object="item" />
</v-menu>
</div>
</template>
@@ -60,11 +60,14 @@
<script>
import identities from '@/api/identities'
import MenuItems from '@/components/tools/MenuItems'
export default {
components: {
MenuItems
},
data () {
return {
menuItems: [],
headers: [
{ text: this.$gettext('Name'), value: 'identity' },
{ text: this.$gettext('Fullname/recipient'), value: 'name_or_rcpt' },
@@ -77,10 +80,18 @@ export default {
}
},
methods: {
editAccount (account) {
this.$router.push({ name: 'AccountEdit', params: { id: account.pk } })
},
fetchIdentities () {
identities.getAll().then(resp => {
this.identities = resp.data
})
},
getMenuItems (item) {
return [
{ label: this.$gettext('Edit'), icon: 'mdi-circle-edit-outline', onClick: this.editAccount }
]
}
},
mounted () {
+3 -1
View File
@@ -126,7 +126,9 @@ export default {
}
},
mounted () {
window.addEventListener('resize', this.onResize)
// window.addEventListener('resize', this.onResize)
},
updated () {
this.$nextTick(() => {
this.updateMinWidthProperty()
})
+9
View File
@@ -49,6 +49,15 @@ const routes = [
requiresAuth: true
}
},
{
path: '/identities/accounts/:id/edit',
name: 'AccountEdit',
component: () => import('../views/identities/AccountEdit.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/parameters/:app',
name: 'ParametersEdit',
@@ -0,0 +1,28 @@
<template>
<div>
<span class="text-h4"><translate>Edit account</translate></span>
<div class="mt-4" />
<account-form :account="account" />
</div>
</template>
<script>
import accounts from '@/api/accounts'
import AccountForm from '@/components/identities/AccountForm'
export default {
components: {
AccountForm
},
data () {
return {
account: {}
}
},
mounted () {
accounts.get(this.$route.params.id).then(resp => {
this.account = resp.data
})
}
}
</script>
+9 -3
View File
@@ -20,7 +20,7 @@ from modoboa.core import (
)
from modoboa.lib import (
email_utils, exceptions as lib_exceptions, fields as lib_fields,
permissions, web_utils
permissions, validators, web_utils
)
from modoboa.parameters import tools as param_tools
@@ -294,14 +294,20 @@ class WritableAccountSerializer(AccountSerializer):
"master_user": _("Not allowed for this role.")
})
if role == "SimpleUsers":
username = data.get("username")
if username:
try:
validators.UTF8EmailValidator()(username)
except ValidationError as err:
raise ValidationError({"username": err.message})
mailbox = data.get("mailbox")
if mailbox is None:
if not self.instance:
data["mailbox"] = {
"full_address": data["username"],
"full_address": username,
"use_domain_quota": True
}
elif "full_address" in mailbox and mailbox["full_address"] != data["username"]:
elif "full_address" in mailbox and mailbox["full_address"] != username:
raise serializers.ValidationError({
"username": _("Must be equal to mailbox full_address")
})
+1
View File
@@ -146,6 +146,7 @@ class TagSerializer(serializers.Serializer):
class IdentitySerializer(serializers.Serializer):
"""Serializer used for identities."""
pk = serializers.IntegerField()
identity = serializers.CharField()
name_or_rcpt = serializers.CharField()
tags = TagSerializer(many=True)