mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-24 06:48:35 +02:00
Merge pull request #3205 from modoboa/feature/postfix_autoreply
Postfix autoreply merged back to core repo
This commit is contained in:
@@ -44,4 +44,10 @@ export default {
|
||||
deleteAPIToken() {
|
||||
return repository.delete(`${resource}/api_token/`)
|
||||
},
|
||||
getARMessage() {
|
||||
return repository.get(`${resource}/armessage/`)
|
||||
},
|
||||
setARMessage(data) {
|
||||
return repository.put(`${resource}/armessage/`, data)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<v-card v-if="!token" flat>
|
||||
<v-card-text>
|
||||
{{ $gettext('Your API token has not been generated yet.') }}
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
:loading="loading"
|
||||
@click="generateKey"
|
||||
>
|
||||
{{ $gettext('Generate') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<v-card v-else flat>
|
||||
<v-card-text>
|
||||
<label class="m-label">
|
||||
{{ $gettext('Your API token is:') }}
|
||||
</label>
|
||||
<code class="ml-5 mr-5">
|
||||
{{ token }}
|
||||
</code>
|
||||
<v-btn
|
||||
icon="mdi-clipboard-plus"
|
||||
density="compact"
|
||||
:title="$gettext('Copy token to clipboard')"
|
||||
variant="text"
|
||||
@click="copyToClipboard()"
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
density="compact"
|
||||
color="error"
|
||||
variant="text"
|
||||
:title="$gettext('Delete token')"
|
||||
@click="openDeletionDialog"
|
||||
>
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-alert type="info" variant="tonal" border="start" class="mt-4">
|
||||
{{ $gettext('A documentation of the API is available') }}
|
||||
<a :href="apiDocUrl" target="_blank">{{ $gettext('here') }}</a
|
||||
>.
|
||||
</v-alert>
|
||||
<ConfirmDialog ref="confirm" @agree="deleteToken" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import accountApi from '@/api/account'
|
||||
import { useBusStore } from '@/stores'
|
||||
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
|
||||
import { ref, computed, inject, onMounted } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const busStore = useBusStore()
|
||||
|
||||
const $config = inject('$config')
|
||||
|
||||
const apiDocUrl = computed(() => $config.API_DOC_URL)
|
||||
|
||||
const loading = ref(false)
|
||||
const token = ref(null)
|
||||
const confirm = ref()
|
||||
|
||||
function copyToClipboard() {
|
||||
navigator.clipboard.writeText(token.value)
|
||||
busStore.displayNotification({
|
||||
msg: $gettext('API token copied to your clipboard'),
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
|
||||
function generateKey() {
|
||||
loading.value = true
|
||||
accountApi
|
||||
.createAPIToken()
|
||||
.then((resp) => {
|
||||
token.value = resp.data.token
|
||||
busStore.displayNotification({
|
||||
msg: $gettext('API token created'),
|
||||
type: 'success',
|
||||
})
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
}
|
||||
|
||||
function openDeletionDialog() {
|
||||
confirm.value.open(
|
||||
$gettext('Warning'),
|
||||
$gettext('You are about to delete your API access token'),
|
||||
{
|
||||
color: 'error',
|
||||
cancelLabel: $gettext('Cancel'),
|
||||
agreeLabel: $gettext('Proceed'),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function deleteToken() {
|
||||
await accountApi.deleteAPIToken()
|
||||
busStore.displayNotification({
|
||||
msg: $gettext('API token deleted'),
|
||||
type: 'success',
|
||||
})
|
||||
token.value = null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
accountApi.getAPIToken().then((resp) => {
|
||||
token.value = resp.data.token
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<v-form ref="formRef" @submit.prevent="submit">
|
||||
<v-checkbox
|
||||
v-model="form.enabled"
|
||||
:label="$gettext('Enabled')"
|
||||
color="primary"
|
||||
hide-details
|
||||
class="mb-4"
|
||||
/>
|
||||
<label class="m-label">{{ $gettext('Subject') }}</label>
|
||||
<v-text-field
|
||||
v-model="form.subject"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<div class="mb-4">
|
||||
<label class="m-label">{{ $gettext('Content') }}</label>
|
||||
<v-textarea
|
||||
v-model="form.content"
|
||||
:hint="$gettext('The content of your answer. You can use the following variables, which will be automatically replaced by the appropriate value: %(name)s, %(fromdate)s, %(untildate)s')"
|
||||
persistent-hint
|
||||
variant="outlined"
|
||||
rows="3"
|
||||
density="compact"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="m-label">{{ $gettext('From') }}</label>
|
||||
<v-text-field
|
||||
v-model="form.fromdate"
|
||||
:hint="$gettext('Activate your auto reply from this date')"
|
||||
variant="outlined"
|
||||
persistent-hint
|
||||
type="datetime-local"
|
||||
density="compact"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="m-label">{{ $gettext('Until') }}</label>
|
||||
<v-text-field
|
||||
v-model="form.untildate"
|
||||
:hint="$gettext('Activate your auto reply until this date')"
|
||||
variant="outlined"
|
||||
persistent-hint
|
||||
type="datetime-local"
|
||||
density="compact"
|
||||
/>
|
||||
</div>
|
||||
<v-btn color="success" type="submit" :loading="loading">
|
||||
{{ $gettext('Update auto-reply') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useBusStore } from '@/stores'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import accountApi from '@/api/account'
|
||||
import rules from '@/plugins/rules'
|
||||
|
||||
const busStore = useBusStore()
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const form = ref({})
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
async function submit() {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
accountApi.setARMessage(form.value).then(() => {
|
||||
busStore.displayNotification({ msg: $gettext('Auto-reply message updated') })
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
accountApi.getARMessage().then(resp => {
|
||||
form.value = resp.data
|
||||
if (form.value.fromdate) {
|
||||
form.value.fromdate = form.value.fromdate.slice(0, -4)
|
||||
}
|
||||
if (form.value.untildate) {
|
||||
form.value.untildate = form.value.untildate.slice(0, -4)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,4 +1,3 @@
|
||||
Ac
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
@@ -57,11 +56,12 @@ Ac
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
// import { bus } from '@/main'
|
||||
import { ref } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useBusStore } from '@/stores'
|
||||
import senderAddresses from '@/api/senderAddresses'
|
||||
|
||||
const busStore = useBusStore()
|
||||
const { $gettext } = useGettext()
|
||||
const props = defineProps({ account: { type: Object, default: null } })
|
||||
|
||||
@@ -94,14 +94,14 @@ function submit() {
|
||||
fetchSenderAddresses()
|
||||
newAddress.value = null
|
||||
hideAddBtn.value = false
|
||||
// bus.$emit('notification', { msg: this.$gettext('Sender address added') })
|
||||
busStore.displayNotification({ msg: $gettext('Sender address added') })
|
||||
})
|
||||
}
|
||||
|
||||
function deleteSenderAddress(senderAddress) {
|
||||
senderAddresses.delete(senderAddress.pk).then(() => {
|
||||
fetchSenderAddresses()
|
||||
// bus.$emit('notification', { msg: this.$gettext('Sender address deleted') })
|
||||
busStore.displayNotification({ msg: $gettext('Sender address deleted') })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,11 @@ async function authenticate() {
|
||||
authStore
|
||||
.login(payload)
|
||||
.then(() => {
|
||||
router.push({ name: 'Dashboard' })
|
||||
if (authStore.authUser.role === 'SimpleUsers') {
|
||||
router.push({ name: 'AccountSettings' })
|
||||
} else {
|
||||
router.push({ name: 'Dashboard' })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
loading.value = false
|
||||
|
||||
@@ -183,8 +183,8 @@ const mainMenuItems = [
|
||||
{
|
||||
text: $gettext('Dashboard'),
|
||||
to: { name: 'Dashboard' },
|
||||
icon: 'mdi-view-dashboard-outline',
|
||||
exact: true,
|
||||
icon: 'mdi-view-dashboard-outline'
|
||||
},
|
||||
{
|
||||
text: $gettext('Domains'),
|
||||
@@ -253,48 +253,22 @@ const mainMenuItems = [
|
||||
text: $gettext('Information'),
|
||||
roles: ['SuperAdmins'],
|
||||
to: { name: 'Information' },
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
const userSettingsMenuItems = [
|
||||
{
|
||||
text: $gettext('API'),
|
||||
roles: ['SuperAdmins'],
|
||||
to: { name: 'APISetup' },
|
||||
icon: 'mdi-api',
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
text: $gettext('Profile'),
|
||||
to: { name: 'UserProfile' },
|
||||
icon: 'mdi-account-circle-outline',
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
text: $gettext('Security'),
|
||||
to: { name: 'UserSecurity' },
|
||||
icon: 'mdi-lock-outline',
|
||||
exact: true,
|
||||
},
|
||||
// {
|
||||
// text: $gettext('Preferences'),
|
||||
// icon: 'mdi-tune',
|
||||
// exact: true
|
||||
// },
|
||||
{
|
||||
text: $gettext('Forward'),
|
||||
condition: () => authUser.value.mailbox !== null,
|
||||
to: { name: 'UserForward' },
|
||||
icon: 'mdi-forward',
|
||||
exact: true,
|
||||
},
|
||||
text: $gettext('Settings'),
|
||||
to: { name: 'AccountSettings' },
|
||||
icon: 'mdi-cog'
|
||||
}
|
||||
]
|
||||
|
||||
const userMenuItems = [
|
||||
{
|
||||
text: $gettext('Profile'),
|
||||
text: $gettext('Account'),
|
||||
icon: 'mdi-account-circle-outline',
|
||||
to: { name: 'UserProfile' },
|
||||
to: { name: 'AccountSettings' },
|
||||
click: () => null,
|
||||
},
|
||||
{
|
||||
@@ -304,6 +278,14 @@ const userMenuItems = [
|
||||
},
|
||||
]
|
||||
|
||||
if (authUser.value.role !== 'SimpleUsers') {
|
||||
userMenuItems.unshift({
|
||||
text: $gettext('Admin'),
|
||||
to: { name: 'Dashboard' },
|
||||
icon: 'mdi-view-dashboard-outline'
|
||||
})
|
||||
}
|
||||
|
||||
function displayMenuItem(item) {
|
||||
if (isAuthenticated.value) {
|
||||
const condition =
|
||||
|
||||
+254
-180
File diff suppressed because it is too large
Load Diff
+247
-180
File diff suppressed because it is too large
Load Diff
+254
-180
File diff suppressed because it is too large
Load Diff
+247
-180
File diff suppressed because it is too large
Load Diff
+254
-180
File diff suppressed because it is too large
Load Diff
+254
-180
File diff suppressed because it is too large
Load Diff
+252
-186
File diff suppressed because it is too large
Load Diff
+328
-299
File diff suppressed because it is too large
Load Diff
+254
-180
File diff suppressed because it is too large
Load Diff
+247
-180
File diff suppressed because it is too large
Load Diff
+245
-179
File diff suppressed because it is too large
Load Diff
+247
-180
File diff suppressed because it is too large
Load Diff
+247
-180
File diff suppressed because it is too large
Load Diff
+247
-180
File diff suppressed because it is too large
Load Diff
+254
-180
File diff suppressed because it is too large
Load Diff
+252
-186
File diff suppressed because it is too large
Load Diff
+252
-180
File diff suppressed because it is too large
Load Diff
+247
-180
File diff suppressed because it is too large
Load Diff
@@ -251,38 +251,17 @@ const routes = [
|
||||
{
|
||||
path: '/account',
|
||||
component: () => import('@/layouts/dashboard/DashboardLayout.vue'),
|
||||
meta: { layout: 'account' },
|
||||
meta: {
|
||||
layout: 'account',
|
||||
requiresAuth: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'api',
|
||||
name: 'APISetup',
|
||||
component: () => import('@/views/account/APISetupView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['SuperAdmins'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
name: 'UserProfile',
|
||||
component: () => import('@/views/account/ProfileView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'security',
|
||||
name: 'UserSecurity',
|
||||
component: () => import('@/views/account/SecurityView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'forward',
|
||||
name: 'UserForward',
|
||||
component: () => import('@/views/account/ForwardView.vue'),
|
||||
path: ':tab?',
|
||||
name: 'AccountSettings',
|
||||
component: () => import('@/views/account/SettingsView.vue'),
|
||||
meta: {
|
||||
layout: 'account',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import gettext from '@/plugins/gettext'
|
||||
|
||||
import repository from '@/api/repository'
|
||||
@@ -13,6 +13,10 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
const authUser = ref({})
|
||||
const isAuthenticated = ref(false)
|
||||
|
||||
const userHasMailbox = computed(() => {
|
||||
return authUser.value.mailbox !== null
|
||||
})
|
||||
|
||||
async function fetchUser() {
|
||||
return accountApi.getMe().then((resp) => {
|
||||
gettext.current = resp.data.language
|
||||
@@ -41,7 +45,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
const cookie = Cookies.withAttributes(cookiesAttributes)
|
||||
cookie.set('token', resp.data.access)
|
||||
cookie.set('refreshToken', resp.data.refresh)
|
||||
initialize()
|
||||
return initialize()
|
||||
}
|
||||
async function $reset() {
|
||||
delete repository.defaults.headers.common.Authorization
|
||||
@@ -87,5 +91,6 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
$reset,
|
||||
updateAccount,
|
||||
finalizeTFASetup,
|
||||
userHasMailbox,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Account settings') }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<div class="pa-4">
|
||||
<v-tabs
|
||||
v-model="tab"
|
||||
color="primary"
|
||||
class="mb-4"
|
||||
>
|
||||
<v-tab value="profile">
|
||||
{{ $gettext('Profile') }}
|
||||
</v-tab>
|
||||
<v-tab value="security">
|
||||
{{ $gettext('Security') }}
|
||||
</v-tab>
|
||||
<v-tab value="forward" v-if="authStore.userHasMailbox">
|
||||
{{ $gettext('Forward') }}
|
||||
</v-tab>
|
||||
<v-tab value="autoreply" v-if="authStore.userHasMailbox">
|
||||
{{ $gettext('Auto-reply message') }}
|
||||
</v-tab>
|
||||
<v-tab value="api" v-if="authStore.authUser.role === 'SuperAdmins'">
|
||||
{{ $gettext('API access') }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="profile">
|
||||
<ProfileForm />
|
||||
</v-window-item>
|
||||
<v-window-item value="security">
|
||||
<TwoFactorAuthForm />
|
||||
</v-window-item>
|
||||
<v-window-item value="forward" v-if="authStore.userHasMailbox">
|
||||
<ForwardForm />
|
||||
</v-window-item>
|
||||
<v-window-item value="autoreply" v-if="authStore.userHasMailbox">
|
||||
<AutoReplyForm />
|
||||
</v-window-item>
|
||||
<v-window-item value="api" v-if="authStore.authUser.role === 'SuperAdmins'">
|
||||
<APISetupForm />
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import ProfileForm from '@/components/account/ProfileForm.vue'
|
||||
import TwoFactorAuthForm from '@/components/account/TwoFactorAuthForm.vue'
|
||||
import ForwardForm from '@/components/account/ForwardForm.vue'
|
||||
import AutoReplyForm from '@/components/account/AutoReplyForm.vue'
|
||||
import APISetupForm from '@/components/account/APISetupForm.vue'
|
||||
import { useAuthStore } from '@/stores'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const tab = ref()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-toolbar {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
.v-tabs {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user