Account creation form improvements.

This commit is contained in:
Antoine Nguyen
2021-04-27 19:22:39 +02:00
parent 8e48ed1bd4
commit 92eafd55e9
13 changed files with 149 additions and 40 deletions
+3
View File
@@ -6,6 +6,9 @@ export default {
getAll ({ domain, role }) {
return repository.get(`/${resource}/`, { params: { domain, role } })
},
getRandomPassword () {
return repository.get(`${resource}/random_password`)
},
validate (data) {
return repository.post(`${resource}/validate/`, data)
},
@@ -104,7 +104,26 @@
<creation-summary
:sections="summarySections"
@modify-step="val => step = val"
/>
>
<template v-slot:item.random_password="{ item }">
<v-col cols="12" class="highligth white--text">
<v-row>
<v-col><span>{{ item.key }}</span></v-col>
<v-col class="text-right" v-if="item.type === 'yesno'">
{{ item.value|yesno }}
</v-col>
</v-row>
<v-row>
<v-col class="text-right py-1">
{{ account.password }}
<v-btn icon color="white" :title="'Copy to clipboard'|translate" @click="copyPassword">
<v-icon>mdi-clipboard-multiple-outline</v-icon>
</v-btn>
</v-col>
</v-row>
</v-col>
</template>
</creation-summary>
<div class="d-flex justify-center mt-8">
<v-btn
color="primary"
@@ -156,6 +175,7 @@ export default {
{ key: this.$gettext('First name'), value: this.account.first_name },
{ key: this.$gettext('Last name'), value: this.account.last_name },
{
name: 'random_password',
key: this.$gettext('Random password'),
value: this.account.random_password,
type: 'yesno'
@@ -204,6 +224,11 @@ export default {
}
},
methods: {
copyPassword () {
navigator.clipboard.writeText(this.account.password).then(() => {
bus.$emit('notification', { msg: this.$gettext('Password copied to clipboard') })
})
},
getInitialForm () {
return {
aliases: [],
@@ -211,7 +236,8 @@ export default {
role: 'SimpleUsers',
mailbox: {
use_domain_quota: true
}
},
random_password: true
}
},
async close (withConfirm) {
@@ -242,6 +268,11 @@ export default {
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 () {
@@ -286,4 +317,7 @@ export default {
.edit-link {
text-decoration: none;
}
.highligth {
background-color: #515D78;
}
</style>
@@ -5,23 +5,26 @@
rules="required"
vid="username"
>
<label class="m-label">{{ $gettext('Username') }}</label>
<email-field
v-model="account.username"
:label="'Username' | translate"
:error-messages="errors"
:placeholder="usernamePlaceholder"
outlined
class="mt-4"
dense
/>
</validation-provider>
<label class="m-label">{{ $gettext('First name') }}</label>
<v-text-field
v-model="account.first_name"
:label="'First name' | translate"
outlined
dense
/>
<label class="m-label">{{ $gettext('Last name') }}</label>
<v-text-field
v-model="account.last_name"
:label="'Last name' | translate"
outlined
dense
/>
<v-switch
v-model="account.random_password"
@@ -32,13 +35,15 @@
v-if="!account.random_password"
v-slot="{ errors }"
rules="required"
vid="password"
>
<label class="m-label">{{ $gettext('Password') }}</label>
<v-text-field
v-model="account.password"
:label="'Password' | translate"
:error-messages="errors"
outlined
type="password"
dense
/>
</validation-provider>
<validation-provider
@@ -46,12 +51,13 @@
v-slot="{ errors }"
rules="required"
>
<label class="m-label">{{ $gettext('Confirmation') }}</label>
<v-text-field
v-model="account.password_confirmation"
:label="'Confirmation' | translate"
:error-messages="errors"
outlined
type="password"
dense
/>
</validation-provider>
<v-switch
@@ -70,6 +76,14 @@ export default {
EmailField
},
props: ['account'],
computed: {
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 {
}
@@ -10,29 +10,38 @@
:error-messages="errors"
:disabled="account.role === 'SimpleUsers'"
outlined
dense
/>
</validation-provider>
<div class="d-flex">
<label class="m-label">{{ $gettext('Quota') }}</label>
<v-switch
v-model="account.mailbox.use_domain_quota"
:label="useDomainQuotaLabel"
/>
<validation-provider
v-if="!account.mailbox.use_domain_quota"
v-slot="{ errors }"
rules=""
vid="quota"
>
<v-text-field
v-model="account.mailbox.quota"
:label="'Quota' | translate"
:placeholder="'Ex: 10MB. Leave empty for no limit'|translate"
:hint="'Quota for this mailbox, can be expressed in KB, MB (default) or GB. Define a custom value or use domain\'s default one. Leave empty to define an unlimited value (not allowed for domain administrators).' | translate"
persistent-hint
class="mr-4 quota"
:disabled="account.mailbox.use_domain_quota"
outlined
:error-messages="errors"
dense
/>
<v-switch
v-model="account.mailbox.use_domain_quota"
:label="useDomainQuotaLabel"
/>
</div>
</validation-provider>
<label class="m-label">{{ $gettext('Daily message sending limit') }}</label>
<v-text-field
v-model="account.mailbox.message_limit"
:label="'Message sending limit' | translate"
:placeholder="'Leave empty for no limit' | translate"
:hint="'Number of messages this mailbox can send per day. Leave empty for no limit.' | translate"
persistent-hint
outlined
dense
/>
</validation-observer>
</template>
@@ -4,12 +4,23 @@
v-slot="{ errors }"
rules="required"
>
<choice-field v-model="account.role"
:label="'Role' | translate"
:choices="accountRoles"
:error-messages="errors"
:choices-per-line="2"
/>
<v-row>
<v-col cols="7">
<choice-field v-model="account.role"
:label="'Role' | translate"
:choices="accountRoles"
:error-messages="errors"
:choices-per-line="2"
/>
</v-col>
<v-col cols="5">
<v-alert text color="primary" class="mt-11 ml-4 rounded-lg">
<h3 class="headline">{{ roleLabel }}</h3>
<p class="mt-4">{{ roleHelp }}</p>
<v-icon color="white" class="float-right" large>mdi-help-circle-outline</v-icon>
</v-alert>
</v-col>
</v-row>
</validation-provider>
</validation-observer>
</template>
@@ -22,24 +33,36 @@ export default {
ChoiceField
},
props: ['account'],
computed: {
roleLabel () {
return this.accountRoles.find(role => role.value === this.account.role).label
},
roleHelp () {
return this.accountRoles.find(role => role.value === this.account.role).help
}
},
data () {
return {
accountRoles: [
{
label: this.$gettext('Domain administrator'),
value: 'DomainAdmins'
value: 'DomainAdmins',
help: this.$gettext('A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.')
},
{
label: this.$gettext('Reseller'),
value: 'Resellers'
value: 'Resellers',
help: this.$gettext('help')
},
{
label: this.$gettext('Simple user'),
value: 'SimpleUsers'
value: 'SimpleUsers',
help: this.$gettext('A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.')
},
{
label: this.$gettext('Super administrator'),
value: 'SuperAdmins'
value: 'SuperAdmins',
help: this.$gettext('A user with all privileges, can do anything. By default, such a user does not have a mailbox so he can\'t access end-user features.')
}
]
}
@@ -1,7 +1,7 @@
<template>
<div>
<div class="text-subtitle-1 grey--text text--darken-1 mb-4" :class="{ 'label--disabled': disabled }">
<label class="v-label">{{ label }}</label>
<label class="m-label">{{ label }}</label>
</div>
<div class="d-flex" v-for="(lineChoices, index1) in formatedChoices" :key="index1">
<div v-for="(choice, index2) in lineChoices"
@@ -10,12 +10,14 @@
dense
>
<template v-if="item.value !== undefined">
<v-col><span class="grey--text">{{ item.key }}</span></v-col>
<v-col class="text-right" v-if="item.type === 'yesno'">{{ item.value|yesno }}</v-col>
<v-col class="text-right" v-else-if="item.type === 'list'">
{{ displayList(item.value) }}
</v-col>
<v-col class="text-right" v-else>{{ item.value }}</v-col>
<slot :name="`item.${item.name}`" v-bind:item="item" v-bind:displayList="displayList">
<v-col><span class="grey--text">{{ item.key }}</span></v-col>
<v-col class="text-right" v-if="item.type === 'yesno'">{{ item.value|yesno }}</v-col>
<v-col class="text-right" v-else-if="item.type === 'list'">
{{ displayList(item.value) }}
</v-col>
<v-col class="text-right" v-else>{{ item.value }}</v-col>
</slot>
</template>
</v-row>
</div>
+1
View File
@@ -7,6 +7,7 @@ import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
import sassStyles from './styles/global.scss' // eslint-disable-line no-unused-vars
Vue.config.productionTip = false
+5 -1
View File
@@ -5,10 +5,14 @@ Vue.use(Vuetify)
export default new Vuetify({
theme: {
options: {
customProperties: true
},
themes: {
light: {
primary: '#046BF8',
secondary: '#F18429'
secondary: '#F18429',
label: '#616161'
}
}
}
+5
View File
@@ -0,0 +1,5 @@
.m-label {
display: block;
margin-bottom: 4px;
color: var(--v-label-base);
}
+7 -1
View File
@@ -305,7 +305,13 @@ class WritableAccountSerializer(AccountSerializer):
raise serializers.ValidationError({
"username": _("Must be equal to mailbox full_address")
})
if not self.partial and not data.get("random_password"):
condition = (
not data.get("random_password") and (
data.get("password") or
not self.partial
)
)
if condition:
password = data.get("password")
if password:
try:
+5 -3
View File
@@ -160,9 +160,11 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer):
)
class Meta(v1_serializers.WritableAccountSerializer.Meta):
fields = v1_serializers.WritableAccountSerializer.Meta.fields + (
"aliases",
)
fields = tuple(
field
for field in v1_serializers.WritableAccountSerializer.Meta.fields
if field != "random_password"
) + ("aliases", )
def validate_aliases(self, value):
"""Check if required domains are locals and user can access them."""
+6
View File
@@ -161,6 +161,12 @@ class AccountViewSet(v1_viewsets.AccountViewSet):
serializer.is_valid(raise_exception=True)
return response.Response(status=204)
@action(methods=["get"], detail=False)
def random_password(self, request, **kwargs):
"""Generate a random password."""
password = lib.make_password()
return response.Response({"password": password})
class IdentityViewSet(viewsets.ViewSet):
"""Viewset for identities."""