Merge pull request #3205 from modoboa/feature/postfix_autoreply

Postfix autoreply merged back to core repo
This commit is contained in:
Antoine Nguyen
2024-03-20 10:41:00 +01:00
committed by GitHub
98 changed files with 40788 additions and 26070 deletions
+6
View File
@@ -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') })
})
}
+5 -1
View File
@@ -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
+16 -34
View File
@@ -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 =
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8 -29
View File
@@ -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,
},
},
+7 -2
View File
@@ -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>