wip: Identities section.

This commit is contained in:
Antoine Nguyen
2021-03-17 11:02:44 +01:00
parent 1962bd8443
commit 043763af9a
10 changed files with 421 additions and 1 deletions
+9
View File
@@ -0,0 +1,9 @@
import repository from './repository'
const resource = 'identities'
export default {
getAll () {
return repository.get(`${resource}/`)
}
}
@@ -0,0 +1,114 @@
<template>
<div class="d-flex justify-center inner">
<v-stepper v-model="step">
<v-stepper-header class="align-center px-10">
<v-img
src="../../assets/Modoboa_RVB-BLEU-SANS.png"
max-width="190"
/>
<v-stepper-step :complete="step > 1" step="1">
<translate>General</translate>
</v-stepper-step>
<v-stepper-step :complete="step > 2" step="2">
<translate>Mail</translate>
</v-stepper-step>
<v-btn icon @click="close">
<v-icon color="primary" x-large>mdi-close</v-icon>
</v-btn>
</v-stepper-header>
<v-stepper-items class="mt-4 d-flex justify-center">
<v-stepper-content step="1" class="flex-grow-0">
<div class="mb-6 text-h5">
<translate class="grey--text text--darken-1">New account</translate> /
<translate>General</translate>
</div>
<account-general-form ref="form_1" :account="account" />
<div class="d-flex justify-end">
<v-btn
color="primary"
@click="goToNextStep(1, 2)"
large
>
<translate>Next</translate>
</v-btn>
</div>
</v-stepper-content>
</v-stepper-items>
</v-stepper>
<confirm-dialog ref="confirm" />
</div>
</template>
<script>
import AccountGeneralForm from './AccountGeneralForm'
import ConfirmDialog from '@/components/layout/ConfirmDialog'
export default {
components: {
AccountGeneralForm,
ConfirmDialog
},
data () {
return {
account: {},
step: 1
}
},
methods: {
async close (withConfirm) {
if (withConfirm) {
const confirm = await this.$refs.confirm.open(
this.$gettext('Warning'),
this.$gettext('If you close this form now, your modifications won\'t be saved. Do you confirm?'),
{
color: 'error'
}
)
if (!confirm) {
return
}
}
this.$emit('close')
this.step = 1
},
async goToNextStep (current, next) {
const valid = await this.$refs[`form_${current}`].$refs.observer.validate()
if (!valid) {
return
}
this.step = next
}
}
}
</script>
<style lang="scss">
.inner {
background-color: #fff;
}
.v-stepper {
width: 100%;
overflow: auto;
&__content {
width: 60%;
}
&__items {
overflow-y: auto;
}
&__wrapper {
padding: 0 10px;
}
}
.subtitle {
color: #000;
border-bottom: 1px solid #DBDDDF;
}
.edit-link {
text-decoration: none;
}
</style>
@@ -0,0 +1,110 @@
<template>
<validation-observer ref="observer">
<validation-provider
v-slot="{ errors }"
rules="required"
>
<choice-field v-model="account.role"
:label="'Role' | translate"
:choices="accountRoles"
:error-messages="errors"
/>
</validation-provider>
<validation-provider
v-slot="{ errors }"
rules="required"
>
<v-text-field
v-model="account.username"
:label="'Username' | translate"
:error-messages="errors"
outlined
class="mt-4"
dense
/>
</validation-provider>
<v-text-field
v-model="account.first_name"
:label="'First name' | translate"
outlined
dense
/>
<v-text-field
v-model="account.last_name"
:label="'Last name' | translate"
outlined
dense
/>
<v-switch
v-model="account.random_password"
:label="'Random password' | translate"
dense
/>
<validation-provider
v-if="!account.random_password"
v-slot="{ errors }"
rules="required"
>
<v-text-field
v-model="account.password"
:label="'Password' | translate"
:error-messages="errors"
outlined
type="password"
dense
/>
</validation-provider>
<validation-provider
v-if="!account.random_password"
v-slot="{ errors }"
rules="required"
>
<v-text-field
v-model="account.password_confirmation"
:label="'Confirmation' | translate"
:error-messages="errors"
outlined
type="password"
dense
/>
</validation-provider>
<v-switch
v-model="account.is_active"
:label="'Enabled' | translate"
dense
/>
</validation-observer>
</template>
<script>
import ChoiceField from '@/components/tools/ChoiceField'
export default {
components: {
ChoiceField
},
props: ['account'],
data () {
return {
accountRoles: [
{
label: this.$gettext('Domain administrator'),
value: 'DomainAdmins'
},
{
label: this.$gettext('Reseller'),
value: 'Resellers'
},
{
label: this.$gettext('Simple user'),
value: 'SimpleUsers'
},
{
label: this.$gettext('Super administrator'),
value: 'SuperAdmins'
}
]
}
}
}
</script>
@@ -0,0 +1,85 @@
<template>
<v-card class="mt-6">
<v-toolbar flat>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn v-bind="attrs" v-on="on" small>
Actions <v-icon right>mdi-chevron-down</v-icon>
</v-btn>
</template>
<v-list dense>
</v-list>
</v-menu>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
prepend-inner-icon="mdi-magnify"
placeholder="Search"
filled
outlined
dense
hide-details
/>
</v-toolbar>
<v-data-table
v-model="selected"
:headers="headers"
:items="identities"
:search="search"
item-key="identity"
class="elevation-1"
show-select
>
<template v-slot:item.tags="{ item }">
<v-chip v-for="(tag, index) in item.tags"
:key="tag.name"
:color="(tag.type !== 'idt') ? 'primary' : 'default'"
:class="(index > 0) ? 'ml-2' : ''"
small
>
{{ tag.label }}
</v-chip>
</template>
<template v-slot:item.actions="{ item }">
<div class="text-right">
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn icon v-bind="attrs" v-on="on">
<v-icon>mdi-dots-horizontal</v-icon>
</v-btn>
</template>
<menu-items :items="menuItems" :object="item" />
</v-menu>
</div>
</template>
</v-data-table>
</v-card>
</template>
<script>
import identities from '@/api/identities'
export default {
data () {
return {
menuItems: [],
headers: [
{ text: this.$gettext('Name'), value: 'identity' },
{ text: this.$gettext('Fullname/recipient'), value: 'name_or_rcpt' },
{ text: this.$gettext('Tags'), value: 'tags' },
{ text: this.$gettext('Actions'), value: 'actions', sortable: false, align: 'right' }
],
identities: [],
search: '',
selected: []
}
},
mounted () {
identities.getAll().then(resp => {
this.identities = resp.data
})
}
}
</script>
@@ -122,6 +122,7 @@ export default {
},
{
text: this.$gettext('Identities'),
to: { name: 'Identities' },
icon: 'mdi-account'
},
{
+9 -1
View File
@@ -18,7 +18,7 @@ const routes = [
component: () => import(/* webpackChunkName: "about" */ '../views/Login.vue')
},
{
path: '/domains/',
path: '/domains',
name: 'DomainList',
component: () => import('../views/Domains.vue'),
meta: {
@@ -41,6 +41,14 @@ const routes = [
requiresAuth: true
}
},
{
path: '/identities',
name: 'Identities',
component: () => import('../views/identities/Identities.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/parameters/:app',
name: 'ParametersEdit',
@@ -0,0 +1,62 @@
<template>
<div>
<v-toolbar flat>
<v-toolbar-title><translate>Identities</translate></v-toolbar-title>
<v-spacer />
<v-btn class="mr-2">
<v-icon>mdi-file-import-outline</v-icon>
</v-btn>
<v-btn class="mr-2">
<v-icon>mdi-file-export-outline</v-icon>
</v-btn>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" v-bind="attrs" v-on="on">
<v-icon left>mdi-plus</v-icon>
<translate>New</translate>
</v-btn>
</template>
<v-list dense>
<v-list-item @click="showCreationWizard = true">
<v-list-item-title><translate>Account</translate></v-list-item-title>
</v-list-item>
<v-list-item @click="showAliasForm = true">
<v-list-item-title><translate>Alias</translate></v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar>
<identity-list />
<v-dialog
v-model="showCreationWizard"
fullscreen
scrollable
transition="dialog-bottom-transition"
>
<account-creation-form @close="closeCreationWizard" />
</v-dialog>
</div>
</template>
<script>
import AccountCreationForm from '@/components/identities/AccountCreationForm'
import IdentityList from '@/components/identities/IdentityList'
export default {
components: {
AccountCreationForm,
IdentityList
},
data () {
return {
showCreationWizard: false
}
},
methods: {
closeCreationWizard () {
this.showCreationWizard = false
}
}
}
</script>
+16
View File
@@ -132,3 +132,19 @@ class SimpleDomainAdminSerializer(serializers.Serializer):
account = serializers.PrimaryKeyRelatedField(
queryset=core_models.User.objects.all()
)
class TagSerializer(serializers.Serializer):
"""Serializer used to represent a tag."""
name = serializers.CharField()
label = serializers.CharField()
type = serializers.CharField()
class IdentitySerializer(serializers.Serializer):
"""Serializer used for identities."""
identity = serializers.CharField()
name_or_rcpt = serializers.CharField()
tags = TagSerializer(many=True)
+2
View File
@@ -15,5 +15,7 @@ router.register(r"aliases", v1_viewsets.AliasViewSet, basename="alias")
router.register(
r"senderaddresses", v1_viewsets.SenderAddressViewSet,
basename="sender_address")
router.register(
r"identities", viewsets.IdentityViewSet, basename="identities")
urlpatterns = router.urls
+13
View File
@@ -18,6 +18,7 @@ from modoboa.admin.api.v1 import (
from modoboa.core import models as core_models
from modoboa.lib import viewsets as lib_viewsets
from ... import lib
from ... import models
from . import serializers
@@ -144,3 +145,15 @@ class AccountViewSet(v1_viewsets.AccountViewSet):
.values_list("object_id", flat=True)
)
return core_models.User.objects.filter(pk__in=ids)
class IdentityViewSet(viewsets.ViewSet):
"""Viewset for identities."""
permission_classes = (permissions.IsAuthenticated, )
def list(self, request, **kwargs):
"""Return all identities."""
serializer = serializers.IdentitySerializer(
lib.get_identities(request.user), many=True)
return response.Response(serializer.data)