mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-19 05:58:34 +02:00
Started import work
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import repository from '@/api/repository'
|
||||
|
||||
const resource = 'migrations'
|
||||
|
||||
export default {
|
||||
getMigrations (query) {
|
||||
return repository.get(`/${resource}/`, query)
|
||||
},
|
||||
getMigration (slug, query) {
|
||||
return repository.get(`/${resource}/${slug}/`, query)
|
||||
},
|
||||
deleteMigration (slug) {
|
||||
return repository.delete(`/${resource}/${slug}/`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import repository from '@/api/repository'
|
||||
|
||||
const resource = 'email-providers'
|
||||
|
||||
export default {
|
||||
getProviders (params) {
|
||||
return repository.get(`/${resource}/`, { params })
|
||||
},
|
||||
getProvider (providerId) {
|
||||
return repository.get(`/${resource}/${providerId}/`)
|
||||
},
|
||||
createProvider (params) {
|
||||
return repository.post(`/${resource}/`, params)
|
||||
},
|
||||
updateProvider (slug, params) {
|
||||
return repository.put(`/${resource}/${slug}/`, params)
|
||||
},
|
||||
patchProvider (providerId, params) {
|
||||
return repository.patch(`/${resource}/${providerId}/`, params)
|
||||
},
|
||||
deleteProvider (slug) {
|
||||
return repository.delete(`/${resource}/${slug}/`)
|
||||
},
|
||||
checkProvider (params) {
|
||||
return repository.post(`/${resource}/check_connection/`, params)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<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-text-field>
|
||||
</v-toolbar>
|
||||
<v-data-table
|
||||
v-model="selected"
|
||||
:headers="headers"
|
||||
:items="migrations"
|
||||
:search="search"
|
||||
item-key="pk"
|
||||
:options.sync="options"
|
||||
:server-items-length="totalMigrations"
|
||||
:loading="loading"
|
||||
class="elevation-1"
|
||||
show-select
|
||||
>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<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="getMenuItems(item)" :object="item" />
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Migration from '@/api/imap_migration/migrations'
|
||||
import MenuItems from '@/components/tools/MenuItems'
|
||||
import { bus } from '@/main'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuItems
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
migrations: [],
|
||||
headers: [
|
||||
{ text: this.$gettext('Provider'), value: 'provider' },
|
||||
{ text: this.$gettext('Old account'), value: 'old_account' },
|
||||
{ text: this.$gettext('New account'), value: 'new_account' },
|
||||
{ text: this.$gettext('Actions'), value: 'actions', sortable: false, align: 'right' }
|
||||
|
||||
],
|
||||
selected: [],
|
||||
loading: true,
|
||||
search: '',
|
||||
options: {},
|
||||
totalMigrations: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteMigration (migration) {
|
||||
this.$dialog.confirm(
|
||||
this.$gettext('Remove this migration?'), {
|
||||
cancelText: this.$gettext('Cancel'),
|
||||
okText: this.$gettext('Proceed')
|
||||
}
|
||||
).then(dialog => {
|
||||
Migration.deleteMigration(migration.id).then(response => {
|
||||
bus.$emit('notification', { msg: this.$gettext('Provider deleted') })
|
||||
this.migrations = this.migrations.filter(item => {
|
||||
return item.id !== migration.id
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
getMigrations (filter) {
|
||||
this.loading = true
|
||||
let query = {}
|
||||
if (filter !== undefined) {
|
||||
query = { params: { search: filter } }
|
||||
}
|
||||
Migration.getMigrations(query).then(response => {
|
||||
this.migrations = response.data
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
getMenuItems (item) {
|
||||
const result = []
|
||||
result.push({
|
||||
label: this.$gettext('Delete'),
|
||||
icon: 'mdi-delete-outline',
|
||||
onClick: this.deleteMigration,
|
||||
color: 'red'
|
||||
})
|
||||
return result
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.getMigrations()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<validation-observer ref="observer">
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required"
|
||||
vid="initialdomain"
|
||||
>
|
||||
<v-text-field
|
||||
ref="initialdomainField"
|
||||
v-model="domain.initialdomain"
|
||||
:error-messages="errors"
|
||||
:placeholder="'provider.domain' | translate"
|
||||
outlined
|
||||
dense
|
||||
@input="update"
|
||||
@keydown="onKeyDown"
|
||||
/>
|
||||
</validation-provider>
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required"
|
||||
vid="newd_omain"
|
||||
>
|
||||
<v-text-field
|
||||
ref="newdomainField"
|
||||
v-model="domain.new_domain"
|
||||
:error-messages="errors"
|
||||
:placeholder="'local.domain' | translate"
|
||||
outlined
|
||||
dense
|
||||
@input="update"
|
||||
@keydown="onKeyDown"
|
||||
/>
|
||||
</validation-provider>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="addDomain"
|
||||
>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-chip
|
||||
v-for="(domain, index) in domains"
|
||||
:key="index"
|
||||
class="mr-2 mt-2"
|
||||
close
|
||||
@click:close="removeDomain(index)"
|
||||
>
|
||||
<template v-if="domain.new_domain">
|
||||
{{domain.name}} --> {{domain.new_domain}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{domain.name}} --> {{domain.name}}
|
||||
</template>
|
||||
</v-chip>
|
||||
</validation-observer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
data () {
|
||||
return {
|
||||
domains: [],
|
||||
domain: {
|
||||
initialdomain: '',
|
||||
new_domain: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async validateForm () {
|
||||
return await this.$refs.observer.validate()
|
||||
},
|
||||
update () {
|
||||
this.$emit('input', this.domains)
|
||||
},
|
||||
async addDomain () {
|
||||
if (await this.validateForm()) {
|
||||
this.domains.push({
|
||||
name: this.domain.initialdomain,
|
||||
new_domain: this.domain.new_domain
|
||||
})
|
||||
this.$emit('input', this.domains)
|
||||
this.$refs.initialdomainField.reset()
|
||||
this.$refs.newdomainField.reset()
|
||||
}
|
||||
},
|
||||
removeDomain (index) {
|
||||
this.domains.splice(index, 1)
|
||||
this.$emit('input', this.domains)
|
||||
},
|
||||
onKeyDown (e) {
|
||||
const keyCode = e.keyCode
|
||||
if (keyCode === 13 || keyCode === 9) {
|
||||
// on enter or tab
|
||||
this.addDomain()
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler: function (newValue) {
|
||||
if (newValue) {
|
||||
this.domains = [...newValue]
|
||||
} else {
|
||||
this.domains = []
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<creation-form
|
||||
:title="'New provider'|translate"
|
||||
:steps="steps"
|
||||
:form-observer-getter="getObserver"
|
||||
:validate-object="validateProvider"
|
||||
:summary-sections="summarySections"
|
||||
@close="close"
|
||||
@create="submit"
|
||||
>
|
||||
<template v-slot:form.general="{ step }">
|
||||
<provider-general-form :ref="`form_${step}`" v-model="provider" />
|
||||
</template>
|
||||
<template v-slot:form.associated="{ step }">
|
||||
<provider-associated-form :ref="`form_${step}`" v-model="provider.domains" />
|
||||
</template>
|
||||
</creation-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { bus } from '@/main'
|
||||
import CreationForm from '@/components/tools/CreationForm'
|
||||
import ProviderAssociatedForm from './ProviderAssociatedForm'
|
||||
import ProviderGeneralForm from './ProviderGeneralForm'
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
components: {
|
||||
CreationForm,
|
||||
'provider-associated-form': ProviderAssociatedForm,
|
||||
ProviderGeneralForm
|
||||
},
|
||||
computed: {
|
||||
formatAssociatedDomains () {
|
||||
let associatedDomains = ''
|
||||
for (const domain of this.provider.domains) {
|
||||
associatedDomains += `${domain.name} => ${domain.new_domain}, `
|
||||
}
|
||||
return associatedDomains
|
||||
},
|
||||
summarySections () {
|
||||
const result = [
|
||||
{
|
||||
title: this.$gettext('General'),
|
||||
items: [
|
||||
{ key: this.$gettext('Name'), value: this.provider.name },
|
||||
{ key: this.$gettext('Address'), value: this.provider.address },
|
||||
{ key: this.$gettext('Port'), value: this.provider.port },
|
||||
{ key: this.$gettext('Secured'), value: this.provider.secured, type: 'yesno' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: this.$gettext('Associated domains'),
|
||||
items: [
|
||||
{ key: this.$gettext('Perform a connection check'), value: this.formatAssociatedDomains }
|
||||
]
|
||||
}
|
||||
]
|
||||
return result
|
||||
},
|
||||
steps () {
|
||||
return [
|
||||
{ name: 'general', title: this.$gettext('General') },
|
||||
{ name: 'associated', title: this.$gettext('Associated domains') }
|
||||
]
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
provider: {
|
||||
domains: []
|
||||
},
|
||||
formErrors: {}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initProvider()
|
||||
},
|
||||
methods: {
|
||||
initProvider () {
|
||||
this.provider = {
|
||||
name: '',
|
||||
address: '',
|
||||
port: 993,
|
||||
secured: true,
|
||||
domains: []
|
||||
}
|
||||
},
|
||||
close () {
|
||||
this.initProvider()
|
||||
this.$emit('close')
|
||||
},
|
||||
getObserver (step) {
|
||||
return this.$refs[`form_${step}`].$refs.observer
|
||||
},
|
||||
validateProvider () {
|
||||
|
||||
},
|
||||
performConnectionCheck () {
|
||||
|
||||
},
|
||||
submit () {
|
||||
const data = JSON.parse(JSON.stringify(this.provider))
|
||||
console.log(this.provider)
|
||||
this.$store.dispatch('providers/createProvider', data).then(resp => {
|
||||
bus.$emit('notification', { msg: this.$gettext('Provider created') })
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.highligth {
|
||||
background-color: #515D78;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,138 @@
|
||||
<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>General</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>Name: </translate> {{ editedProvider.name }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-fade-transition>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<provider-general-form ref="generalForm" v-model="editedProvider" />
|
||||
</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>Associated domains</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 v-if="editedProvider.domains.length > 0" cols="6">
|
||||
<translate>Number of associated domains: </translate> {{ editedProvider.domains.length }}
|
||||
</v-col>
|
||||
<v-col v-else cols="6">
|
||||
<translate>No associated domains</translate>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-fade-transition>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<provider-associated-form ref="associatedForm" v-model="editedProvider.domains" />
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
<div class="mt-4 d-flex justify-end">
|
||||
<v-btn @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 { bus } from '@/main'
|
||||
import providers from '@/api/imap_migration/providers'
|
||||
import ProviderGeneralForm from './ProviderGeneralForm'
|
||||
import ProviderAssociatedForm from './ProviderAssociatedForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProviderGeneralForm,
|
||||
ProviderAssociatedForm
|
||||
},
|
||||
props: ['provider'],
|
||||
data () {
|
||||
return {
|
||||
editedProvider: {},
|
||||
panel: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async save () {
|
||||
if (this.$refs.generalForm !== undefined) {
|
||||
const valid = await this.$refs.generalForm.validateForm()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (this.$refs.associatedForm !== undefined) {
|
||||
const valid = await this.$refs.associatedForm.validateForm()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(JSON.stringify(this.editedProvider))
|
||||
await providers.patch(this.editedProvider.id, data).then(resp => {
|
||||
bus.$emit('notification', { msg: this.$gettext('Provider updated') })
|
||||
})
|
||||
} catch (error) {
|
||||
if (this.$refs.generalForm) {
|
||||
this.$refs.generalForm.$refs.observer.setErrors(error.response.data)
|
||||
}
|
||||
if (this.$refs.associatedForm) {
|
||||
this.$refs.associatedForm.$refs.observer.setErrors(error.response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
provider: {
|
||||
handler: function (val) {
|
||||
if (val) {
|
||||
this.editedProvider = { ...val }
|
||||
if (!this.editedProvider.domains) {
|
||||
this.editedProvider.domains = []
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<validation-observer ref="observer">
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required"
|
||||
>
|
||||
<v-text-field
|
||||
:label="'Provider name' | translate"
|
||||
v-model="form.name"
|
||||
:error-messages="errors"
|
||||
outlined
|
||||
@input="update"
|
||||
/>
|
||||
</validation-provider>
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required"
|
||||
>
|
||||
<v-text-field
|
||||
:label="'Address (hostname or IP)' | translate"
|
||||
v-model="form.address"
|
||||
:error-messages="errors"
|
||||
outlined
|
||||
@input="update"
|
||||
/>
|
||||
</validation-provider>
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required|portNumber"
|
||||
>
|
||||
<v-text-field
|
||||
:label="'IMAP Port' | translate"
|
||||
v-model="form.port"
|
||||
type="number"
|
||||
:error-messages="errors"
|
||||
outlined
|
||||
@input="update"
|
||||
/>
|
||||
</validation-provider>
|
||||
<v-switch
|
||||
:label="'Secured' | translate"
|
||||
v-model="form.secured"
|
||||
:hint="'Control if the IMAP is secured through SSL/TLS or Start SSL' | translate"
|
||||
persistent-hint
|
||||
@change="update"
|
||||
/>
|
||||
<div class="my-7">
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
color="primary"
|
||||
@click="checkConnection"
|
||||
>
|
||||
<translate>Check connection: </translate>
|
||||
<v-icon
|
||||
v-if="connectionStatus === 0"
|
||||
color="blue-grey-darken-2"
|
||||
>
|
||||
mdi-help-circle-outline
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-else-if="connectionStatus === 1"
|
||||
color="success"
|
||||
>
|
||||
mdi-check-all
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-else
|
||||
color="warning"
|
||||
>
|
||||
mdi-alert-circle-outline
|
||||
</v-icon>
|
||||
<template v-slot:loader>
|
||||
<span class="custom-loader">
|
||||
<v-icon light>mdi-cached</v-icon>
|
||||
</span>
|
||||
</template>
|
||||
</v-btn>
|
||||
</div>
|
||||
</validation-observer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Provider from '@/api/imap_migration/providers'
|
||||
|
||||
export default {
|
||||
props: ['value'],
|
||||
data () {
|
||||
return {
|
||||
form: {},
|
||||
connectionStatus: 0,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async checkConnection () {
|
||||
if (await this.validateForm()) {
|
||||
this.loading = true
|
||||
const params = {
|
||||
address: this.form.address,
|
||||
port: this.form.port,
|
||||
secured: this.form.secured
|
||||
}
|
||||
console.error(params)
|
||||
Provider.checkProvider(params).then(response => {
|
||||
this.connectionStatus = 1
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
this.loading = false
|
||||
this.connectionStatus = 2
|
||||
})
|
||||
}
|
||||
},
|
||||
async validateForm () {
|
||||
return await this.$refs.observer.validate()
|
||||
},
|
||||
update () {
|
||||
this.connectionStatus = 0
|
||||
console.log(this.form)
|
||||
this.$emit('input', this.form)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.form = { ...this.value }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-loader {
|
||||
animation: loader 1s infinite;
|
||||
display: flex;
|
||||
}
|
||||
@-moz-keyframes loader {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes loader {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes loader {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes loader {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<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-text-field>
|
||||
</v-toolbar>
|
||||
<v-data-table
|
||||
v-model="selected"
|
||||
:headers="headers"
|
||||
:items="providers"
|
||||
:search="search"
|
||||
item-key="name"
|
||||
single-expand
|
||||
:expanded.sync="expanded"
|
||||
class="elevation-1"
|
||||
show-select
|
||||
@click:row="(item, slot) => slot.expand(!slot.isExpanded)"
|
||||
>
|
||||
<template v-slot:item.secured="{ item }">
|
||||
<v-icon
|
||||
v-if="item.secured === true"
|
||||
color="green"
|
||||
>
|
||||
mdi-lock
|
||||
</v-icon>
|
||||
<v-icon
|
||||
color="red"
|
||||
v-else
|
||||
>
|
||||
mdi-lock-off
|
||||
</v-icon>
|
||||
</template>
|
||||
<template v-slot:item.domains="{ item }">
|
||||
<v-chip
|
||||
v-if="item.domains.length > 0"
|
||||
>
|
||||
{{item.domains.length}} associated domain(s)
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-else
|
||||
>
|
||||
No associated domain
|
||||
</v-chip>
|
||||
</template>
|
||||
<template v-slot:expanded-item="{ headers, item }">
|
||||
<td :colspan="headers.length">
|
||||
<v-chip
|
||||
v-for="(domain, index) in item.domains"
|
||||
:key="index"
|
||||
>
|
||||
<template v-if="domain.new_domain">
|
||||
{{domain.name}} --> {{domain.new_domain}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{domain.name}} --> {{domain.name}}
|
||||
</template>
|
||||
</v-chip>
|
||||
</td>
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<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="getMenuItems(item)" :object="item" />
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapGetters } from 'vuex'
|
||||
import { bus } from '@/main'
|
||||
import MenuItems from '@/components/tools/MenuItems'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuItems
|
||||
},
|
||||
computed: mapGetters({
|
||||
providers: 'providers/providers'
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
headers: [
|
||||
{ text: this.$gettext('Name'), value: 'name' },
|
||||
{ text: this.$gettext('Address'), value: 'address' },
|
||||
{ text: this.$gettext('Port'), value: 'port' },
|
||||
{ text: this.$gettext('Secured'), value: 'secured' },
|
||||
{ text: this.$gettext('Associated domains'), value: 'domains' },
|
||||
{ text: this.$gettext('Actions'), value: 'actions', sortable: false, align: 'right' }
|
||||
],
|
||||
selected: [],
|
||||
search: '',
|
||||
expanded: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteProvider (provider) {
|
||||
this.$store.dispatch('providers/deleteProvider', { id: provider.id }).then(resp => {
|
||||
bus.$emit('notification', { msg: this.$gettext('Provider deleted') })
|
||||
})
|
||||
},
|
||||
editProvider (provider) {
|
||||
this.$router.push({ name: 'ProviderEdit', params: { id: provider.id } })
|
||||
},
|
||||
loadAssociatedDomains ({ item, value }) {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
this.$set(this.item.domains)
|
||||
},
|
||||
getMenuItems (item) {
|
||||
const result = []
|
||||
result.push({
|
||||
label: this.$gettext('Delete'),
|
||||
icon: 'mdi-delete-outline',
|
||||
onClick: this.deleteProvider,
|
||||
color: 'red'
|
||||
})
|
||||
result.push({
|
||||
label: this.$gettext('Edit'),
|
||||
icon: 'mdi-circle-edit-outline',
|
||||
onClick: this.editProvider
|
||||
})
|
||||
return result
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('providers/getProviders')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -168,6 +168,23 @@ export default {
|
||||
icon: 'mdi-domain',
|
||||
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins']
|
||||
},
|
||||
{
|
||||
icon: 'mdi-email-sync-outline',
|
||||
text: this.$gettext('Migrations'),
|
||||
roles: ['SuperAdmins', 'Resellers'],
|
||||
children: [
|
||||
{
|
||||
text: this.$gettext('Email providers'),
|
||||
to: { name: 'ProvdiersList' },
|
||||
roles: ['SuperAdmins', 'Resellers']
|
||||
},
|
||||
{
|
||||
text: this.$gettext('Migrations'),
|
||||
to: { name: 'MigrationsList' },
|
||||
roles: ['Resellers', 'SuperAdmins']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: this.$gettext('Identities'),
|
||||
to: { name: 'Identities' },
|
||||
@@ -261,7 +278,7 @@ export default {
|
||||
created () {
|
||||
parameters.getApplications().then(response => {
|
||||
response.data.forEach(item => {
|
||||
this.mainMenuItems[5].children.push({
|
||||
this.mainMenuItems[6].children.push({
|
||||
text: item.label,
|
||||
to: { name: 'ParametersEdit', params: { app: item.name } }
|
||||
})
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-text-field
|
||||
ref="input"
|
||||
v-model="input"
|
||||
outlined
|
||||
dense
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@input="update"
|
||||
@keydown="onKeyDown"
|
||||
/>
|
||||
<div class="menu"
|
||||
v-if="showMenu"
|
||||
:style="`width: ${width}px;`"
|
||||
>
|
||||
<v-list class="list">
|
||||
<v-list-item
|
||||
v-for="(domain, index) in filteredDomains"
|
||||
:key="index"
|
||||
@click="selectDomain(domain)"
|
||||
:class="selectionIndex === index ? 'selected' : ''"
|
||||
>
|
||||
<v-list-item-title>{{ domain.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: String,
|
||||
allowAdd: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
domains: 'identities/domains'
|
||||
}),
|
||||
filteredDomains () {
|
||||
return this.domains.filter(domain => domain.name.startsWith(this.input))
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
input: this.value,
|
||||
selectionIndex: 0,
|
||||
showMenu: false,
|
||||
width: 500
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onKeyDown (e) {
|
||||
const keyCode = e.keyCode
|
||||
|
||||
if (keyCode === 40 || keyCode === 34) {
|
||||
// on arrow down or page down
|
||||
if (!this.showMenu) {
|
||||
this.showMenu = true
|
||||
} else {
|
||||
this.increaseSelectionIndex()
|
||||
}
|
||||
e.preventDefault()
|
||||
} else if (keyCode === 38 || keyCode === 33) {
|
||||
// on arrow up or page up
|
||||
if (!this.showMenu) {
|
||||
this.showMenu = true
|
||||
} else {
|
||||
this.decreaseSelectionIndex()
|
||||
}
|
||||
e.preventDefault()
|
||||
} else if (keyCode === 13 || keyCode === 9) {
|
||||
// on enter or tab
|
||||
if (this.filteredDomains.length > 0) {
|
||||
this.selectDomain(this.filteredDomains[this.selectionIndex])
|
||||
} else if (this.allowAdd) {
|
||||
this.selectDomain()
|
||||
}
|
||||
e.preventDefault()
|
||||
} else if (keyCode === 27) {
|
||||
// on escape
|
||||
if (this.filteredDomains.length > 0) {
|
||||
this.showMenu = false
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
},
|
||||
increaseSelectionIndex () {
|
||||
if (this.selectionIndex >= this.filteredDomains.length - 1) {
|
||||
this.selectionIndex = 0
|
||||
} else {
|
||||
this.selectionIndex += 1
|
||||
}
|
||||
},
|
||||
decreaseSelectionIndex () {
|
||||
if (this.selectionIndex <= 0) {
|
||||
this.selectionIndex = this.filteredDomains.length - 1
|
||||
} else {
|
||||
this.selectionIndex -= 1
|
||||
}
|
||||
},
|
||||
selectDomain (domain) {
|
||||
if (domain !== undefined) {
|
||||
this.input = domain.name
|
||||
}
|
||||
this.showMenu = false
|
||||
this.$emit('input', this.input)
|
||||
this.$emit('domain-selected')
|
||||
},
|
||||
update (value) {
|
||||
if (this.allowAdd) {
|
||||
this.showMenu = this.filteredDomains.length > 0
|
||||
} else {
|
||||
this.showMenu = true
|
||||
}
|
||||
this.$emit('input', this.input)
|
||||
},
|
||||
onResize () {
|
||||
this.updateMinWidthProperty()
|
||||
},
|
||||
updateMinWidthProperty () {
|
||||
this.width = this.$refs.input.$el.clientWidth
|
||||
},
|
||||
reset () {
|
||||
this.input = ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// window.addEventListener('resize', this.onResize)
|
||||
},
|
||||
updated () {
|
||||
this.$nextTick(() => {
|
||||
this.updateMinWidthProperty()
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
value (newValue) {
|
||||
this.input = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
margin-top: -20px;
|
||||
padding: 5px 0;
|
||||
box-shadow: 0 1px 5px 1px rgba(0,0,0,0.3);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
contain: content;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.1em;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: cornsilk;
|
||||
}
|
||||
</style>
|
||||
@@ -36,6 +36,15 @@ extend('validPassword', {
|
||||
},
|
||||
message: $gettext('Invalid password')
|
||||
})
|
||||
extend('portNumber', {
|
||||
validate: async (value) => {
|
||||
if (value < 0 || value > 65535) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
message: $gettext('Specify a valid port number')
|
||||
})
|
||||
|
||||
// Register components globally
|
||||
Vue.component('ValidationObserver', ValidationObserver)
|
||||
|
||||
@@ -56,6 +56,33 @@ const routes = [
|
||||
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/imap_migration/migrations',
|
||||
name: 'MigrationsList',
|
||||
component: () => import('../components/imap_migration/MigrationsList.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['Resellers', 'SuperAdmins']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/imap_migration/providers',
|
||||
name: 'ProvdiersList',
|
||||
component: () => import('../views/imap_migration/Providers.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['Resellers', 'SuperAdmins']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/imap_migration/providers/:id/edit',
|
||||
name: 'ProviderEdit',
|
||||
component: () => import('../views/imap_migration/ProviderEdit.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['Resellers', 'SuperAdmins']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/identities',
|
||||
name: 'Identities',
|
||||
|
||||
@@ -4,6 +4,7 @@ import Vuex from 'vuex'
|
||||
import auth from '@/store/modules/auth'
|
||||
import domains from '@/store/modules/domains'
|
||||
import identities from '@/store/modules/identities'
|
||||
import providers from '@/store/modules/providers'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
@@ -11,7 +12,8 @@ export default new Vuex.Store({
|
||||
modules: {
|
||||
auth,
|
||||
domains,
|
||||
identities
|
||||
identities,
|
||||
providers
|
||||
},
|
||||
strict: process.env.NODE_ENV !== 'production'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import Vue from 'vue'
|
||||
import providerApi from '@/api/imap_migration/providers'
|
||||
|
||||
// mutation types
|
||||
export const ADD_PROVIDER = 'ADD_PROVIDER'
|
||||
export const UPDATE_PROVIDER = 'SET_PROVIDER'
|
||||
export const SET_PROVIDERS = 'SET_PROVIDERS'
|
||||
export const DELETE_PROVIDER = 'DELETE_PROVIDER'
|
||||
|
||||
const state = {
|
||||
providersLoaded: false,
|
||||
providers: []
|
||||
}
|
||||
|
||||
const getters = {
|
||||
providers: state => state.providers
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getProviders ({ commit }, params) {
|
||||
return providerApi.getProviders(params).then(response => {
|
||||
commit(SET_PROVIDERS, { providers: response.data })
|
||||
})
|
||||
},
|
||||
createProvider ({ commit }, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
providerApi.createProvider(data).then(response => {
|
||||
commit(ADD_PROVIDER, { provider: response.data })
|
||||
resolve(response)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
updateProvider ({ commit }, data) {
|
||||
return providerApi.updateProvider(data.id, data).then(response => {
|
||||
commit(UPDATE_PROVIDER, { provider: response.data })
|
||||
})
|
||||
},
|
||||
deleteProvider ({ commit }, data) {
|
||||
return providerApi.deleteProvider(data.id).then(response => {
|
||||
commit(DELETE_PROVIDER, data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
[SET_PROVIDERS] (state, { providers }) {
|
||||
state.providersLoaded = true
|
||||
state.providers = providers
|
||||
},
|
||||
[ADD_PROVIDER] (state, { provider }) {
|
||||
state.providers.push(provider)
|
||||
},
|
||||
[UPDATE_PROVIDER] (state, { provider }) {
|
||||
state.providers.forEach(function (item, pos) {
|
||||
if (item.pk === provider.pk) {
|
||||
Vue.set(state.providers, pos, provider)
|
||||
}
|
||||
})
|
||||
},
|
||||
[DELETE_PROVIDER] (state, data) {
|
||||
state.providers = state.providers.filter(item => item.id !== data.id)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="text-h4"><translate>Edit provider</translate></span>
|
||||
<div class="mt-4" />
|
||||
<provider-form :provider="provider" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import providers from '@/api/imap_migration/providers'
|
||||
import ProviderForm from '@/components/imap_migration/ProviderForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProviderForm
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
provider: {}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
providers.getProvider(this.$route.params.id).then(resp => {
|
||||
this.provider = resp.data
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title><translate>Providers</translate></v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
offset-y
|
||||
color="primary"
|
||||
@click="showProviderWizard = true"
|
||||
>
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
<translate>New</translate>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<providers-list />
|
||||
|
||||
<v-dialog
|
||||
v-model="showProviderWizard"
|
||||
fullscreen
|
||||
scrollable
|
||||
transition="dialog-bottom-transition"
|
||||
>
|
||||
<provider-creation-form @close="showProviderWizard = false" />
|
||||
<template v-slot:help>
|
||||
<ul>
|
||||
<li><em>domain; name; quota; default mailbox quota; enabled</em></li>
|
||||
<li><em>domainalias; name; targeted domain; enabled</em></li>
|
||||
</ul>
|
||||
</template>
|
||||
</v-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProviderCreationForm from '@/components/imap_migration/ProviderCreationForm'
|
||||
import ProvidersList from '@/components/imap_migration/ProvidersList'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProviderCreationForm,
|
||||
ProvidersList
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showProviderWizard: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-toolbar {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user