mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Added form to edit an account.
This commit is contained in:
@@ -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 () {
|
||||
|
||||
@@ -126,7 +126,9 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.onResize)
|
||||
// window.addEventListener('resize', this.onResize)
|
||||
},
|
||||
updated () {
|
||||
this.$nextTick(() => {
|
||||
this.updateMinWidthProperty()
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user