feat(admin): migrate mail to vue 3 composable

parent 36ba1eb1
...@@ -11,6 +11,6 @@ ...@@ -11,6 +11,6 @@
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"server/locales" "ux/src/i18n/locales"
] ]
} }
...@@ -4,8 +4,8 @@ q-page.admin-mail ...@@ -4,8 +4,8 @@ q-page.admin-mail
.col-auto .col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-message-settings.svg') img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-message-settings.svg')
.col.q-pl-md .col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.mail.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.mail.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.mail.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.mail.subtitle') }}
.col-auto .col-auto
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle' icon='las la-question-circle'
...@@ -19,16 +19,16 @@ q-page.admin-mail ...@@ -19,16 +19,16 @@ q-page.admin-mail
icon='las la-redo-alt' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
:loading='loading > 0' :loading='state.loading > 0'
@click='load' @click='load'
) )
q-btn( q-btn(
unelevated unelevated
icon='mdi-check' icon='fa-solid fa-check'
:label='$t(`common.actions.apply`)' :label='t(`common.actions.apply`)'
color='secondary' color='secondary'
@click='save' @click='save'
:disabled='loading > 0' :disabled='state.loading > 0'
) )
q-separator(inset) q-separator(inset)
.row.q-pa-md.q-col-gutter-md .row.q-pa-md.q-col-gutter-md
...@@ -38,184 +38,184 @@ q-page.admin-mail ...@@ -38,184 +38,184 @@ q-page.admin-mail
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.configuration')}} .text-subtitle1 {{t('admin.mail.configuration')}}
q-item q-item
blueprint-icon(icon='contact') blueprint-icon(icon='contact')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.senderName`)}} q-item-label {{t(`admin.mail.senderName`)}}
q-item-label(caption) {{$t(`admin.general.senderNameHint`)}} q-item-label(caption) {{t(`admin.general.senderNameHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.senderName' v-model='state.config.senderName'
dense dense
hide-bottom-space hide-bottom-space
:aria-label='$t(`admin.mail.senderName`)' :aria-label='t(`admin.mail.senderName`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='envelope') blueprint-icon(icon='envelope')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.senderEmail`)}} q-item-label {{t(`admin.mail.senderEmail`)}}
q-item-label(caption) {{$t(`admin.general.senderEmailHint`)}} q-item-label(caption) {{t(`admin.general.senderEmailHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.senderEmail' v-model='state.config.senderEmail'
dense dense
:aria-label='$t(`admin.mail.senderEmail`)' :aria-label='t(`admin.mail.senderEmail`)'
) )
//- ----------------------- //- -----------------------
//- SMTP //- SMTP
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.smtp')}} .text-subtitle1 {{t('admin.mail.smtp')}}
q-item q-item
blueprint-icon(icon='dns') blueprint-icon(icon='dns')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpHost`)}} q-item-label {{t(`admin.mail.smtpHost`)}}
q-item-label(caption) {{$t(`admin.mail.smtpHostHint`)}} q-item-label(caption) {{t(`admin.mail.smtpHostHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.host' v-model='state.config.host'
dense dense
hide-bottom-space hide-bottom-space
:aria-label='$t(`admin.mail.smtpHost`)' :aria-label='t(`admin.mail.smtpHost`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='ethernet-off') blueprint-icon(icon='ethernet-off')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpPort`)}} q-item-label {{t(`admin.mail.smtpPort`)}}
q-item-label(caption) {{$t(`admin.mail.smtpPortHint`)}} q-item-label(caption) {{t(`admin.mail.smtpPortHint`)}}
q-item-section(style='flex: 0 0 120px;') q-item-section(style='flex: 0 0 120px;')
q-input( q-input(
outlined outlined
v-model='config.port' v-model='state.config.port'
dense dense
:aria-label='$t(`admin.mail.smtpPort`)' :aria-label='t(`admin.mail.smtpPort`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='secure') blueprint-icon(icon='secure')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpTLS`)}} q-item-label {{t(`admin.mail.smtpTLS`)}}
q-item-label(caption) {{$t(`admin.mail.smtpTLSHint`)}} q-item-label(caption) {{t(`admin.mail.smtpTLSHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.secure' v-model='state.config.secure'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.mail.smtpTLS`)' :aria-label='t(`admin.mail.smtpTLS`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='security-ssl') blueprint-icon(icon='security-ssl')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpVerifySSL`)}} q-item-label {{t(`admin.mail.smtpVerifySSL`)}}
q-item-label(caption) {{$t(`admin.mail.smtpVerifySSLHint`)}} q-item-label(caption) {{t(`admin.mail.smtpVerifySSLHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.verifySSL' v-model='state.config.verifySSL'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.mail.smtpVerifySSL`)' :aria-label='t(`admin.mail.smtpVerifySSL`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='test-account') blueprint-icon(icon='test-account')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpUser`)}} q-item-label {{t(`admin.mail.smtpUser`)}}
q-item-label(caption) {{$t(`admin.mail.smtpUserHint`)}} q-item-label(caption) {{t(`admin.mail.smtpUserHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.user' v-model='state.config.user'
dense dense
:aria-label='$t(`admin.mail.smtpUser`)' :aria-label='t(`admin.mail.smtpUser`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='password') blueprint-icon(icon='password')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpPwd`)}} q-item-label {{t(`admin.mail.smtpPwd`)}}
q-item-label(caption) {{$t(`admin.mail.smtpPwdHint`)}} q-item-label(caption) {{t(`admin.mail.smtpPwdHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.pass' v-model='state.config.pass'
dense dense
:aria-label='$t(`admin.mail.smtpPwd`)' :aria-label='t(`admin.mail.smtpPwd`)'
) )
//- ----------------------- //- -----------------------
//- DKIM //- DKIM
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.dkim')}} .text-subtitle1 {{t('admin.mail.dkim')}}
q-item.q-pt-none q-item.q-pt-none
q-item-section q-item-section
q-card.bg-info.text-white.rounded-borders(flat) q-card.bg-info.text-white.rounded-borders(flat)
q-card-section.items-center(horizontal) q-card-section.items-center(horizontal)
q-card-section.col-auto.q-pr-none q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm') q-icon(name='las la-info-circle', size='sm')
q-card-section.text-caption {{ $t('admin.mail.dkimHint') }} q-card-section.text-caption {{ t('admin.mail.dkimHint') }}
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='received') blueprint-icon(icon='received')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimUse`)}} q-item-label {{t(`admin.mail.dkimUse`)}}
q-item-label(caption) {{$t(`admin.mail.dkimUseHint`)}} q-item-label(caption) {{t(`admin.mail.dkimUseHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.useDKIM' v-model='state.config.useDKIM'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.mail.dkimUse`)' :aria-label='t(`admin.mail.dkimUse`)'
) )
template(v-if='config.useDKIM') template(v-if='state.config.useDKIM')
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='dns') blueprint-icon(icon='dns')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimDomainName`)}} q-item-label {{t(`admin.mail.dkimDomainName`)}}
q-item-label(caption) {{$t(`admin.mail.dkimDomainNameHint`)}} q-item-label(caption) {{t(`admin.mail.dkimDomainNameHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.dkimDomainName' v-model='state.config.dkimDomainName'
dense dense
:aria-label='$t(`admin.mail.dkimDomainName`)' :aria-label='t(`admin.mail.dkimDomainName`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='access') blueprint-icon(icon='access')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimKeySelector`)}} q-item-label {{t(`admin.mail.dkimKeySelector`)}}
q-item-label(caption) {{$t(`admin.mail.dkimKeySelectorHint`)}} q-item-label(caption) {{t(`admin.mail.dkimKeySelectorHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.dkimKeySelector' v-model='state.config.dkimKeySelector'
dense dense
:aria-label='$t(`admin.mail.dkimKeySelector`)' :aria-label='t(`admin.mail.dkimKeySelector`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='grand-master-key') blueprint-icon(icon='grand-master-key')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimPrivateKey`)}} q-item-label {{t(`admin.mail.dkimPrivateKey`)}}
q-item-label(caption) {{$t(`admin.mail.dkimPrivateKeyHint`)}} q-item-label(caption) {{t(`admin.mail.dkimPrivateKeyHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.dkimPrivateKey' v-model='state.config.dkimPrivateKey'
dense dense
:aria-label='$t(`admin.mail.dkimPrivateKey`)' :aria-label='t(`admin.mail.dkimPrivateKey`)'
type='textarea' type='textarea'
) )
...@@ -225,12 +225,12 @@ q-page.admin-mail ...@@ -225,12 +225,12 @@ q-page.admin-mail
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.templates')}} .text-subtitle1 {{t('admin.mail.templates')}}
q-list q-list
q-item q-item
blueprint-icon(icon='resume-template') blueprint-icon(icon='resume-template')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.templateWelcome`)}} q-item-label {{t(`admin.mail.templateWelcome`)}}
q-item-section(side) q-item-section(side)
q-btn( q-btn(
outline outline
...@@ -238,13 +238,13 @@ q-page.admin-mail ...@@ -238,13 +238,13 @@ q-page.admin-mail
icon='las la-edit' icon='las la-edit'
color='primary' color='primary'
@click='' @click=''
:label='$t(`common.actions.edit`)' :label='t(`common.actions.edit`)'
) )
q-separator(inset) q-separator(inset)
q-item q-item
blueprint-icon(icon='resume-template') blueprint-icon(icon='resume-template')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.templateResetPwd`)}} q-item-label {{t(`admin.mail.templateResetPwd`)}}
q-item-section(side) q-item-section(side)
q-btn( q-btn(
outline outline
...@@ -252,220 +252,249 @@ q-page.admin-mail ...@@ -252,220 +252,249 @@ q-page.admin-mail
icon='las la-edit' icon='las la-edit'
color='primary' color='primary'
@click='' @click=''
:label='$t(`common.actions.edit`)' :label='t(`common.actions.edit`)'
) )
//- ----------------------- //- -----------------------
//- SMTP TEST //- SMTP TEST
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.test')}} .text-subtitle1 {{t('admin.mail.test')}}
q-item q-item
blueprint-icon.self-start(icon='email') blueprint-icon.self-start(icon='email')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.testRecipient`)}} q-item-label {{t(`admin.mail.testRecipient`)}}
q-item-label(caption) {{$t(`admin.mail.testRecipientHint`)}} q-item-label(caption) {{t(`admin.mail.testRecipientHint`)}}
q-input.q-mt-md( q-input.q-mt-md(
outlined outlined
v-model='testEmail' v-model='state.testEmail'
dense dense
:aria-label='$t(`admin.mail.testRecipient`)' :aria-label='t(`admin.mail.testRecipient`)'
) )
.flex.justify-end.q-pr-md.q-py-sm .flex.justify-end.q-pr-md.q-py-sm
q-btn( q-btn(
unelevated unelevated
color='primary' color='primary'
icon='las la-paper-plane' icon='las la-paper-plane'
:label='$t(`admin.mail.testSend`)' :label='t(`admin.mail.testSend`)'
@click='sendTest' @click='sendTest'
:loading='testLoading' :loading='state.testLoading'
) )
</template> </template>
<script> <script setup>
import toSafeInteger from 'lodash/toSafeInteger' import toSafeInteger from 'lodash/toSafeInteger'
import _get from 'lodash/get' import _get from 'lodash/get'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { createMetaMixin } from 'quasar'
export default { import { useI18n } from 'vue-i18n'
mixins: [ import { useMeta, useQuasar } from 'quasar'
createMetaMixin(function () { import { computed, onMounted, reactive, watch } from 'vue'
return {
title: this.$t('admin.mail.title') import { useAdminStore } from 'src/stores/admin'
} import { useSiteStore } from 'src/stores/site'
}) import { useDataStore } from 'src/stores/data'
],
data () { // QUASAR
return {
config: { const $q = useQuasar()
senderName: '',
senderEmail: '', // STORES
host: '',
port: 0, const adminStore = useAdminStore()
secure: false, const siteStore = useSiteStore()
verifySSL: false, const dataStore = useDataStore()
user: '',
pass: '', // I18N
useDKIM: false,
dkimDomainName: '', const { t } = useI18n()
dkimKeySelector: '',
dkimPrivateKey: '' // META
},
testEmail: '', useMeta({
testLoading: false, title: t('admin.mail.title')
loading: 0 })
}
}, // DATA
mounted () {
this.load() const state = reactive({
config: {
senderName: '',
senderEmail: '',
host: '',
port: 0,
secure: false,
verifySSL: false,
user: '',
pass: '',
useDKIM: false,
dkimDomainName: '',
dkimKeySelector: '',
dkimPrivateKey: ''
}, },
methods: { testEmail: '',
async load () { testLoading: false,
this.loading++ loading: 0
try { })
const resp = await this.$apollo.query({
query: gql` // METHODS
query getMailConfig { async function load () {
mailConfig { state.loading++
senderName try {
senderEmail const resp = await APOLLO_CLIENT.query({
host query: gql`
port query getMailConfig {
secure mailConfig {
verifySSL senderName
user senderEmail
pass host
useDKIM port
dkimDomainName secure
dkimKeySelector verifySSL
dkimPrivateKey user
} pass
} useDKIM
`, dkimDomainName
fetchPolicy: 'no-cache' dkimKeySelector
}) dkimPrivateKey
if (!resp?.data?.mailConfig) { }
throw new Error('Failed to fetch mail config.')
} }
this.config = cloneDeep(resp.data.mailConfig) `,
} catch (err) { fetchPolicy: 'network-only'
this.$q.notify({ })
type: 'negative', if (!resp?.data?.mailConfig) {
message: 'Failed to fetch mail config', throw new Error('Failed to fetch mail config.')
caption: err.message }
}) state.config = cloneDeep(resp.data.mailConfig)
} } catch (err) {
this.loading-- $q.notify({
}, type: 'negative',
async save () { message: 'Failed to fetch mail config',
if (this.loading > 0) { return } caption: err.message
})
}
state.loading--
}
async function save () {
if (state.loading > 0) { return }
this.loading++ state.loading++
try { try {
await this.$apollo.mutate({ await APOLLO_CLIENT.mutate({
mutation: gql` mutation: gql`
mutation saveMailConfig ( mutation saveMailConfig (
$senderName: String! $senderName: String!
$senderEmail: String! $senderEmail: String!
$host: String! $host: String!
$port: Int! $port: Int!
$secure: Boolean! $secure: Boolean!
$verifySSL: Boolean! $verifySSL: Boolean!
$user: String! $user: String!
$pass: String! $pass: String!
$useDKIM: Boolean! $useDKIM: Boolean!
$dkimDomainName: String! $dkimDomainName: String!
$dkimKeySelector: String! $dkimKeySelector: String!
$dkimPrivateKey: String! $dkimPrivateKey: String!
) { ) {
updateMailConfig ( updateMailConfig (
senderName: $senderName senderName: $senderName
senderEmail: $senderEmail senderEmail: $senderEmail
host: $host host: $host
port: $port port: $port
secure: $secure secure: $secure
verifySSL: $verifySSL verifySSL: $verifySSL
user: $user user: $user
pass: $pass pass: $pass
useDKIM: $useDKIM useDKIM: $useDKIM
dkimDomainName: $dkimDomainName dkimDomainName: $dkimDomainName
dkimKeySelector: $dkimKeySelector dkimKeySelector: $dkimKeySelector
dkimPrivateKey: $dkimPrivateKey dkimPrivateKey: $dkimPrivateKey
) { ) {
status { status {
succeeded succeeded
slug slug
message message
}
}
} }
`,
variables: {
senderName: this.config.senderName || '',
senderEmail: this.config.senderEmail || '',
host: this.config.host || '',
port: toSafeInteger(this.config.port) || 0,
secure: this.config.secure ?? false,
verifySSL: this.config.verifySSL ?? false,
user: this.config.user || '',
pass: this.config.pass || '',
useDKIM: this.config.useDKIM ?? false,
dkimDomainName: this.config.dkimDomainName || '',
dkimKeySelector: this.config.dkimKeySelector || '',
dkimPrivateKey: this.config.dkimPrivateKey || ''
} }
}) }
this.$q.notify({ `,
type: 'positive', variables: {
message: this.$t('admin.mail.saveSuccess') senderName: state.config.senderName || '',
}) senderEmail: state.config.senderEmail || '',
} catch (err) { host: state.config.host || '',
this.$store.commit('pushGraphError', err) port: toSafeInteger(state.config.port) || 0,
secure: state.config.secure ?? false,
verifySSL: state.config.verifySSL ?? false,
user: state.config.user || '',
pass: state.config.pass || '',
useDKIM: state.config.useDKIM ?? false,
dkimDomainName: state.config.dkimDomainName || '',
dkimKeySelector: state.config.dkimKeySelector || '',
dkimPrivateKey: state.config.dkimPrivateKey || ''
} }
this.loading-- })
}, $q.notify({
async sendTest () { type: 'positive',
this.loading++ message: t('admin.mail.saveSuccess')
try { })
const resp = await this.$apollo.mutate({ } catch (err) {
mutation: gql` $q.notify({
mutation sentMailTest ( type: 'negative',
$recipientEmail: String! message: err.message
) { })
sendMailTest( }
recipientEmail: $recipientEmail state.loading--
) { }
status {
succeeded async function sendTest () {
slug state.loading++
message try {
} const resp = await APOLLO_CLIENT.mutate({
} mutation: gql`
mutation sentMailTest (
$recipientEmail: String!
) {
sendMailTest(
recipientEmail: $recipientEmail
) {
status {
succeeded
slug
message
} }
`,
variables: {
recipientEmail: this.testEmail
} }
})
if (!_get(resp, 'data.sendMailTest.status.succeeded', false)) {
throw new Error(_get(resp, 'data.sendMailTest.status.message', 'An unexpected error occurred.'))
} }
`,
this.testEmail = '' variables: {
this.$q.notify({ recipientEmail: state.testEmail
type: 'positive',
message: this.$t('admin.mail.sendTestSuccess')
})
} catch (err) {
this.$store.commit('pushGraphError', err)
} }
this.loading-- })
if (!_get(resp, 'data.sendMailTest.status.succeeded', false)) {
throw new Error(_get(resp, 'data.sendMailTest.status.message', 'An unexpected error occurred.'))
} }
state.testEmail = ''
$q.notify({
type: 'positive',
message: t('admin.mail.sendTestSuccess')
})
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
} }
state.loading--
} }
// MOUNTED
onMounted(() => {
load()
})
</script> </script>
<style lang='scss'> <style lang='scss'>
......
...@@ -46,7 +46,7 @@ const routes = [ ...@@ -46,7 +46,7 @@ const routes = [
// -> System // -> System
// { path: 'api', component: () => import('../pages/AdminApi.vue') }, // { path: 'api', component: () => import('../pages/AdminApi.vue') },
// { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') }, // { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') },
// { path: 'mail', component: () => import('../pages/AdminMail.vue') }, { path: 'mail', component: () => import('../pages/AdminMail.vue') },
// { path: 'security', component: () => import('../pages/AdminSecurity.vue') }, // { path: 'security', component: () => import('../pages/AdminSecurity.vue') },
{ path: 'system', component: () => import('../pages/AdminSystem.vue') }, { path: 'system', component: () => import('../pages/AdminSystem.vue') },
// { path: 'utilities', component: () => import('../pages/AdminUtilities.vue') }, // { path: 'utilities', component: () => import('../pages/AdminUtilities.vue') },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment