mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
wip: Identities section.
This commit is contained in:
@@ -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'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user