mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Update
This commit is contained in:
@@ -14,6 +14,17 @@
|
||||
>
|
||||
<template #top>
|
||||
<v-toolbar color="white" flat>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
placeholder="Search"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
flat
|
||||
single-line
|
||||
class="flex-grow-0 w-33 mr-4"
|
||||
></v-text-field>
|
||||
<v-menu location="bottom">
|
||||
<template #activator="{ props }">
|
||||
<v-btn v-bind="props" size="small">
|
||||
@@ -25,17 +36,6 @@
|
||||
<MenuItems :items="getActionMenuItems()" />
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
placeholder="Search"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
flat
|
||||
single-line
|
||||
></v-text-field>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
<template #[`item.status`]="{ item }">
|
||||
|
||||
@@ -13,6 +13,18 @@
|
||||
>
|
||||
<template #top>
|
||||
<v-toolbar color="white" flat>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
:placeholder="$gettext('Search')"
|
||||
variant="outlined"
|
||||
single-line
|
||||
flat
|
||||
hide-details
|
||||
density="compact"
|
||||
class="flex-grow-0 w-33 mr-4"
|
||||
></v-text-field>
|
||||
<slot name="extraActions" />
|
||||
<v-menu location="bottom">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
@@ -31,16 +43,6 @@
|
||||
icon="mdi-reload"
|
||||
@click="reloadDomains"
|
||||
></v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
:placeholder="$gettext('Search')"
|
||||
variant="outlined"
|
||||
single-line
|
||||
flat
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
<template
|
||||
|
||||
@@ -12,6 +12,18 @@
|
||||
>
|
||||
<template #top>
|
||||
<v-toolbar flat color="white">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
:placeholder="$gettext('Search')"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
single-line
|
||||
flat
|
||||
density="compact"
|
||||
class="flex-grow-0 w-33 mr-4"
|
||||
></v-text-field>
|
||||
<slot name="extraActions" />
|
||||
<v-menu location="bottom">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
@@ -33,15 +45,6 @@
|
||||
@click="fetchIdentities"
|
||||
></v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
:placeholder="$gettext('Search')"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
single-line
|
||||
flat
|
||||
></v-text-field>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
<template #[`item.identity`]="{ item }">
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
>
|
||||
<template #top>
|
||||
<v-toolbar flat color="white">
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
:placeholder="$gettext('Search')"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
density="compact"
|
||||
class="flex-grow-0 w-33"
|
||||
/>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
>
|
||||
<template #top>
|
||||
<v-toolbar flat color="white">
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
:placeholder="$gettext('Search')"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
density="compact"
|
||||
class="flex-grow-0 w-33"
|
||||
/>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<v-navigation-drawer
|
||||
v-model="drawer"
|
||||
:rail="rail"
|
||||
permanent
|
||||
:color="mainColor"
|
||||
app
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<v-img
|
||||
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
|
||||
max-width="190"
|
||||
class="logo"
|
||||
@click="router.push(props.logoRoute)"
|
||||
/>
|
||||
<v-btn
|
||||
:icon="rail ? 'mdi-chevron-right' : 'mdi-chevron-left'"
|
||||
variant="text"
|
||||
@click.stop="rail = !rail"
|
||||
>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-list nav>
|
||||
<template v-for="item in menuItems" :key="item.text">
|
||||
<template v-if="displayMenuItem(item)">
|
||||
<v-list-subheader v-if="item.subheader" class="text-white">
|
||||
{{ item.text.toUpperCase() }}
|
||||
</v-list-subheader>
|
||||
<v-list-item
|
||||
v-else-if="item.action"
|
||||
:value="item"
|
||||
:exact="item.exact"
|
||||
:title="item.text"
|
||||
:prepend-icon="item.icon"
|
||||
@click="item.action"
|
||||
>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-else-if="!item.children"
|
||||
:value="item"
|
||||
:to="item.to"
|
||||
link
|
||||
:exact="item.exact"
|
||||
:title="item.text"
|
||||
:prepend-icon="item.icon"
|
||||
>
|
||||
<template v-if="item.withBell && displayInformationBell" #append>
|
||||
<v-icon color="red" icon="mdi-bell-alert-outline" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-group v-else :value="item.text">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
:key="item.text"
|
||||
:title="item.text"
|
||||
color="white"
|
||||
:prepend-icon="item.icon"
|
||||
>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-for="subitem in item.children" :key="subitem.text">
|
||||
<v-list-item
|
||||
v-if="displayMenuItem(subitem)"
|
||||
:to="subitem.to"
|
||||
link
|
||||
:title="subitem.text"
|
||||
:value="subitem"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-list-group>
|
||||
</template>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useGlobalStore, useAuthStore } from '@/stores'
|
||||
|
||||
const props = defineProps({
|
||||
menuItems: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
logoRoute: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const globalStore = useGlobalStore()
|
||||
const authStore = useAuthStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const rail = ref(false)
|
||||
const drawer = ref(true)
|
||||
|
||||
const authUser = computed(() => authStore.authUser)
|
||||
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
||||
|
||||
const mainColor = computed(() => {
|
||||
if (route.meta.layout === 'account') {
|
||||
return 'grey'
|
||||
}
|
||||
return 'primary'
|
||||
})
|
||||
|
||||
const displayInformationBell = computed(
|
||||
() =>
|
||||
globalStore.notifications.length !== undefined &&
|
||||
globalStore.notifications.length !== 0
|
||||
)
|
||||
|
||||
function displayMenuItem(item) {
|
||||
if (isAuthenticated.value) {
|
||||
const condition =
|
||||
(item.roles === undefined ||
|
||||
item.roles.indexOf(authUser.value.role) !== -1) &&
|
||||
(item.condition === undefined || item.condition()) &&
|
||||
item.activated !== false
|
||||
return condition
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (authUser.value.role === 'SuperAdmins') {
|
||||
globalStore.fetchNotifications()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-list-item {
|
||||
&--active {
|
||||
&::before {
|
||||
opacity: 0;
|
||||
}
|
||||
background-color: #034bad !important;
|
||||
color: white;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.v-list-subheader {
|
||||
&__text {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -36,20 +36,19 @@
|
||||
<v-list-item :title="user.username" />
|
||||
</div>
|
||||
<v-divider></v-divider>
|
||||
<template v-if="remote">
|
||||
<v-list-item href="https://localhost:3000/account">
|
||||
<template #prepend>
|
||||
<v-icon icon="mdi-account-circle-outline"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Account</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="logout">
|
||||
<template #prepend>
|
||||
<v-icon icon="mdi-logout"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Logout</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item
|
||||
v-for="item in userMenuItems"
|
||||
:key="item.text"
|
||||
:to="item.to"
|
||||
:href="item.href"
|
||||
link
|
||||
@click="item.click"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon :icon="item.icon"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
@@ -60,6 +59,7 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { getActivePinia } from 'pinia'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import accountApi from '@/api/account'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -73,6 +73,8 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const applications = ref([])
|
||||
|
||||
const userInitials = computed(() => {
|
||||
@@ -81,6 +83,23 @@ const userInitials = computed(() => {
|
||||
: props.user.username.slice(0, 2).toUpperCase()
|
||||
})
|
||||
|
||||
const userMenuItems = computed(() => {
|
||||
return [
|
||||
{
|
||||
text: $gettext('Account'),
|
||||
icon: 'mdi-account-circle-outline',
|
||||
to: !props.remote ? { name: 'AccountSettings' } : null,
|
||||
href: props.remote ? 'https://localhost:3000/account' : '',
|
||||
click: () => null,
|
||||
},
|
||||
{
|
||||
text: $gettext('Logout'),
|
||||
icon: 'mdi-logout',
|
||||
click: logout,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
async function logout() {
|
||||
getActivePinia()._s.forEach(async (store) => await store.$reset())
|
||||
}
|
||||
@@ -97,6 +116,7 @@ onMounted(() => {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.application {
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<NavBar :menu-items="menuItems" />
|
||||
<TopMenu :user="authUser" />
|
||||
<ConnectedView />
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="notificationColor"
|
||||
:timeout="notificationTimeout"
|
||||
location="top"
|
||||
>
|
||||
{{ notification }}
|
||||
<template #actions>
|
||||
<v-btn color="white" text @click="snackbar = false">
|
||||
{{ $gettext('Close') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useAuthStore, useBusStore, useParametersStore } from '@/stores'
|
||||
import parametersApi from '@/api/parameters'
|
||||
import ConnectedView from './ConnectedView.vue'
|
||||
import NavBar from '@/components/shared/NavBar.vue'
|
||||
import TopMenu from '@/components/shared/TopMenu.vue'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const busStore = useBusStore()
|
||||
const parametersStore = useParametersStore()
|
||||
const { $gettext } = useGettext()
|
||||
const route = useRoute()
|
||||
|
||||
const authUser = computed(() => authStore.authUser)
|
||||
const notificationColor = computed(() => busStore.notificationColor)
|
||||
const notification = computed(() => busStore.notification)
|
||||
const snackbar = ref(false)
|
||||
const imapMigrationEnabled = computed(
|
||||
() => parametersStore.imapMigrationEnabled
|
||||
)
|
||||
|
||||
const notificationTimeout = 2000
|
||||
const settings = []
|
||||
const adminMenuItems = [
|
||||
{
|
||||
text: $gettext('Dashboard'),
|
||||
to: { name: 'Dashboard' },
|
||||
exact: true,
|
||||
icon: 'mdi-view-dashboard-outline',
|
||||
},
|
||||
{
|
||||
text: $gettext('Domains'),
|
||||
to: { name: 'DomainList' },
|
||||
icon: 'mdi-domain',
|
||||
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Identities'),
|
||||
to: { name: 'Identities' },
|
||||
icon: 'mdi-account',
|
||||
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Alarms'),
|
||||
to: { name: 'Alarms' },
|
||||
icon: 'mdi-bell',
|
||||
},
|
||||
{
|
||||
icon: 'mdi-history',
|
||||
text: $gettext('Monitoring'),
|
||||
roles: ['SuperAdmins', 'Resellers', 'DomainAdmins'],
|
||||
children: [
|
||||
{
|
||||
text: $gettext('Statistics'),
|
||||
to: { name: 'Statistics' },
|
||||
roles: ['SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Audit trail'),
|
||||
to: { name: 'AuditTrail' },
|
||||
roles: ['SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Messages'),
|
||||
to: { name: 'MessageLog' },
|
||||
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'mdi-email-sync-outline',
|
||||
text: $gettext('IMAP Migration'),
|
||||
roles: ['SuperAdmins', 'Resellers'],
|
||||
condition: () => imapMigrationEnabled.value,
|
||||
children: [
|
||||
{
|
||||
text: $gettext('Email providers'),
|
||||
to: { name: 'ProvidersList' },
|
||||
roles: ['SuperAdmins', 'Resellers'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Migrations'),
|
||||
to: { name: 'MigrationsList' },
|
||||
roles: ['Resellers', 'SuperAdmins'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'mdi-cog',
|
||||
text: $gettext('Settings'),
|
||||
children: settings,
|
||||
roles: ['SuperAdmins'],
|
||||
},
|
||||
{
|
||||
icon: 'mdi-information',
|
||||
text: $gettext('Information'),
|
||||
roles: ['SuperAdmins'],
|
||||
to: { name: 'Information' },
|
||||
withBell: true,
|
||||
},
|
||||
]
|
||||
|
||||
function getUserSettingsMenuItems() {
|
||||
const result = []
|
||||
|
||||
if (authUser.value.mailbox) {
|
||||
result.push({
|
||||
text: $gettext('Filters'),
|
||||
to: { name: 'AccountFilters' },
|
||||
icon: 'mdi-filter',
|
||||
})
|
||||
}
|
||||
result.push({
|
||||
text: $gettext('Settings'),
|
||||
to: { name: 'AccountSettings' },
|
||||
icon: 'mdi-cog',
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
const menuItems = computed(() => {
|
||||
if (route.meta.layout === 'account') {
|
||||
return getUserSettingsMenuItems()
|
||||
}
|
||||
return adminMenuItems
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (authUser.value.role === 'SuperAdmins') {
|
||||
parametersApi.getApplications().then((response) => {
|
||||
response.data.forEach((item) => {
|
||||
settings.push({
|
||||
text: item.label,
|
||||
to: { name: 'ParametersEdit', params: { app: item.name } },
|
||||
})
|
||||
})
|
||||
})
|
||||
if (parametersStore.imapMigrationEnabled === null) {
|
||||
parametersApi.getApplication('imap_migration').then((response) => {
|
||||
parametersStore.imapMigrationEnabled =
|
||||
response.data.params.enabled_imapmigration
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
busStore.$onAction(({ name, after }) => {
|
||||
if (name === 'displayNotification') {
|
||||
after(() => {
|
||||
snackbar.value = true
|
||||
})
|
||||
}
|
||||
}, true)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v-application {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
</style>
|
||||
@@ -1,50 +0,0 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<NavBar />
|
||||
<DashboardView />
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="notificationColor"
|
||||
:timeout="notificationTimeout"
|
||||
location="top"
|
||||
>
|
||||
{{ notification }}
|
||||
<template #actions>
|
||||
<v-btn color="white" text @click="snackbar = false">
|
||||
{{ $gettext('Close') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DashboardView from './DashboardView.vue'
|
||||
import NavBar from './NavBar.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import { useBusStore } from '@/stores'
|
||||
|
||||
const busStore = useBusStore()
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const notificationColor = computed(() => busStore.notificationColor)
|
||||
const notificationTimeout = 2000
|
||||
const notification = computed(() => busStore.notification)
|
||||
const snackbar = ref(false)
|
||||
|
||||
busStore.$onAction(({ name, after }) => {
|
||||
if (name === 'displayNotification') {
|
||||
after(() => {
|
||||
snackbar.value = true
|
||||
})
|
||||
}
|
||||
}, true)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v-application {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
</style>
|
||||
@@ -1,386 +0,0 @@
|
||||
<template>
|
||||
<v-navigation-drawer
|
||||
v-model="drawer"
|
||||
:rail="rail"
|
||||
permanent
|
||||
:color="mainColor"
|
||||
app
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<v-img
|
||||
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
|
||||
max-width="190"
|
||||
class="logo"
|
||||
@click="router.push(props.logoRoute)"
|
||||
/>
|
||||
<v-btn
|
||||
:icon="rail ? 'mdi-chevron-right' : 'mdi-chevron-left'"
|
||||
variant="text"
|
||||
@click.stop="rail = !rail"
|
||||
>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-list nav>
|
||||
<template v-for="item in menuItems" :key="item.text">
|
||||
<template v-if="displayMenuItem(item)">
|
||||
<v-list-subheader v-if="item.subheader">
|
||||
{{ item.text.toUpperCase() }}
|
||||
</v-list-subheader>
|
||||
<v-list-item
|
||||
v-else-if="item.action"
|
||||
:value="item"
|
||||
:exact="item.exact"
|
||||
:title="item.text"
|
||||
:prepend-icon="item.icon"
|
||||
@click="item.action"
|
||||
>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-else-if="!item.children"
|
||||
:value="item"
|
||||
:to="item.to"
|
||||
link
|
||||
:exact="item.exact"
|
||||
:title="item.text"
|
||||
:prepend-icon="item.icon"
|
||||
>
|
||||
<template v-if="item.withBell && displayInformationBell" #append>
|
||||
<v-icon color="red" icon="mdi-bell-alert-outline" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-group v-else :value="item.text">
|
||||
<template #activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
:key="item.text"
|
||||
:title="item.text"
|
||||
color="white"
|
||||
:prepend-icon="item.icon"
|
||||
>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-for="subitem in item.children" :key="subitem.text">
|
||||
<v-list-item
|
||||
v-if="displayMenuItem(subitem)"
|
||||
:to="subitem.to"
|
||||
link
|
||||
:title="subitem.text"
|
||||
:value="subitem"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-list-group>
|
||||
</template>
|
||||
</template>
|
||||
</v-list>
|
||||
<template #append>
|
||||
<!-- <v-menu v-if="isAuthenticated" rounded="lg" offset-y top>
|
||||
<template #activator="{ props }">
|
||||
<div
|
||||
:class="[
|
||||
backgroundColor,
|
||||
'd-flex',
|
||||
'justify-center',
|
||||
'align-center',
|
||||
'pa-2',
|
||||
]"
|
||||
>
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
:class="[backgroundColor, 'text-white']"
|
||||
rounded
|
||||
density="compact"
|
||||
height="48"
|
||||
elevation="0"
|
||||
>
|
||||
<v-avatar size="40" color="primary">
|
||||
<span class="text-white headline">{{ userInitials }}</span>
|
||||
</v-avatar>
|
||||
<template v-if="!rail">
|
||||
<span class="mx-2">{{ displayName }}</span>
|
||||
<v-icon class="float-right">mdi-chevron-up</v-icon>
|
||||
</template>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="item in userMenuItems"
|
||||
:key="item.text"
|
||||
:to="item.to"
|
||||
link
|
||||
@click="item.click"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon :icon="item.icon"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu> -->
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { getActivePinia } from 'pinia'
|
||||
import { useGlobalStore, useAuthStore, useParametersStore } from '@/stores'
|
||||
import parametersApi from '@/api/parameters'
|
||||
|
||||
const props = defineProps({
|
||||
menuItems: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
logoRoute: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const globalStore = useGlobalStore()
|
||||
const authStore = useAuthStore()
|
||||
const parametersStore = useParametersStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const rail = ref(false)
|
||||
const drawer = ref(true)
|
||||
|
||||
const authUser = computed(() => authStore.authUser)
|
||||
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
||||
const backgroundColor = computed(() =>
|
||||
route.meta.layout === 'account' ? 'bg-grey-darken-1' : 'bg-primary-darken-1'
|
||||
)
|
||||
const userInitials = computed(() => {
|
||||
let initials = null
|
||||
if (authUser.value.first_name) {
|
||||
initials = authUser.value.first_name[0]
|
||||
}
|
||||
if (authUser.value.last_name) {
|
||||
initials = initials
|
||||
? initials + authUser.value.last_name[0]
|
||||
: authUser.value.last_name[0]
|
||||
}
|
||||
if (!initials) {
|
||||
initials = authUser.value.username[0]
|
||||
}
|
||||
return initials
|
||||
})
|
||||
const displayName = computed(() => {
|
||||
return authUser.value.first_name || authUser.value.last_name
|
||||
? `${authUser.value.first_name} ${authUser.value.last_name}`
|
||||
: authUser.value.username
|
||||
})
|
||||
|
||||
const menuItems = computed(() => {
|
||||
if (props.menuItems) {
|
||||
return props.menuItems
|
||||
}
|
||||
if (route.meta.layout === 'account') {
|
||||
return getUserSettingsMenuItems()
|
||||
}
|
||||
return mainMenuItems
|
||||
})
|
||||
|
||||
const mainColor = computed(() => {
|
||||
if (route.meta.layout === 'account') {
|
||||
return 'grey'
|
||||
}
|
||||
return 'primary'
|
||||
})
|
||||
|
||||
const imapMigration = computed(() => parametersStore.imapMigrationEnabled)
|
||||
|
||||
const displayInformationBell = computed(
|
||||
() =>
|
||||
globalStore.notifications.length !== undefined &&
|
||||
globalStore.notifications.length !== 0
|
||||
)
|
||||
|
||||
const settings = []
|
||||
const mainMenuItems = [
|
||||
{
|
||||
text: $gettext('Dashboard'),
|
||||
to: { name: 'Dashboard' },
|
||||
exact: true,
|
||||
icon: 'mdi-view-dashboard-outline',
|
||||
},
|
||||
{
|
||||
text: $gettext('Domains'),
|
||||
to: { name: 'DomainList' },
|
||||
icon: 'mdi-domain',
|
||||
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Identities'),
|
||||
to: { name: 'Identities' },
|
||||
icon: 'mdi-account',
|
||||
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Alarms'),
|
||||
to: { name: 'Alarms' },
|
||||
icon: 'mdi-bell',
|
||||
},
|
||||
{
|
||||
icon: 'mdi-history',
|
||||
text: $gettext('Monitoring'),
|
||||
roles: ['SuperAdmins', 'Resellers', 'DomainAdmins'],
|
||||
children: [
|
||||
{
|
||||
text: $gettext('Statistics'),
|
||||
to: { name: 'Statistics' },
|
||||
roles: ['SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Audit trail'),
|
||||
to: { name: 'AuditTrail' },
|
||||
roles: ['SuperAdmins'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Messages'),
|
||||
to: { name: 'MessageLog' },
|
||||
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'mdi-email-sync-outline',
|
||||
text: $gettext('IMAP Migration'),
|
||||
roles: ['SuperAdmins', 'Resellers'],
|
||||
children: [
|
||||
{
|
||||
text: $gettext('Email providers'),
|
||||
to: { name: 'ProvidersList' },
|
||||
roles: ['SuperAdmins', 'Resellers'],
|
||||
},
|
||||
{
|
||||
text: $gettext('Migrations'),
|
||||
to: { name: 'MigrationsList' },
|
||||
roles: ['Resellers', 'SuperAdmins'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'mdi-cog',
|
||||
text: $gettext('Settings'),
|
||||
children: settings,
|
||||
roles: ['SuperAdmins'],
|
||||
},
|
||||
{
|
||||
icon: 'mdi-information',
|
||||
text: $gettext('Information'),
|
||||
roles: ['SuperAdmins'],
|
||||
to: { name: 'Information' },
|
||||
withBell: true,
|
||||
},
|
||||
]
|
||||
|
||||
const userMenuItems = [
|
||||
{
|
||||
text: $gettext('Account'),
|
||||
icon: 'mdi-account-circle-outline',
|
||||
to: { name: 'AccountSettings' },
|
||||
click: () => null,
|
||||
},
|
||||
{
|
||||
text: $gettext('Logout'),
|
||||
icon: 'mdi-logout',
|
||||
click: logout,
|
||||
},
|
||||
]
|
||||
|
||||
if (isAuthenticated.value && authUser.value.role !== 'SimpleUsers') {
|
||||
userMenuItems.unshift({
|
||||
text: $gettext('Admin'),
|
||||
to: { name: 'Dashboard' },
|
||||
icon: 'mdi-view-dashboard-outline',
|
||||
})
|
||||
}
|
||||
|
||||
function getUserSettingsMenuItems() {
|
||||
const result = []
|
||||
|
||||
if (authUser.value.mailbox) {
|
||||
result.push({
|
||||
text: $gettext('Filters'),
|
||||
to: { name: 'AccountFilters' },
|
||||
icon: 'mdi-filter',
|
||||
})
|
||||
}
|
||||
result.push({
|
||||
text: $gettext('Settings'),
|
||||
to: { name: 'AccountSettings' },
|
||||
icon: 'mdi-cog',
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function displayMenuItem(item) {
|
||||
if (isAuthenticated.value) {
|
||||
const condition =
|
||||
(item.roles === undefined ||
|
||||
item.roles.indexOf(authUser.value.role) !== -1) &&
|
||||
(item.condition === undefined || item.condition()) &&
|
||||
item.activated !== false
|
||||
if (item.icon === 'mdi-email-sync-outline') {
|
||||
// For imapMigration
|
||||
return condition && imapMigration.value
|
||||
}
|
||||
return condition
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
getActivePinia()._s.forEach(async (store) => await store.$reset())
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (authUser.value.role === 'SuperAdmins') {
|
||||
parametersApi.getApplications().then((response) => {
|
||||
response.data.forEach((item) => {
|
||||
settings.push({
|
||||
text: item.label,
|
||||
to: { name: 'ParametersEdit', params: { app: item.name } },
|
||||
})
|
||||
})
|
||||
})
|
||||
globalStore.fetchNotifications()
|
||||
if (parametersStore.imapMigrationEnabled === null) {
|
||||
parametersApi.getApplication('imap_migration').then((response) => {
|
||||
parametersStore.imapMigrationEnabled =
|
||||
response.data.params.enabled_imapmigration
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-list-item {
|
||||
&--active {
|
||||
&::before {
|
||||
opacity: 0;
|
||||
}
|
||||
background-color: #034bad !important;
|
||||
color: white;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.v-list-subheader {
|
||||
&__text {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<DefaultView />
|
||||
<EmptyView />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DefaultView from './DefaultView.vue'
|
||||
import EmptyView from './EmptyView.vue'
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@ import { useAuthStore } from '@/stores'
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/layouts/default/DefaultLayout.vue'),
|
||||
component: () => import('@/layouts/empty/EmptyLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -21,7 +21,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
component: () => import('@/layouts/dashboard/DashboardLayout.vue'),
|
||||
component: () => import('@/layouts/connected/ConnectedLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -33,7 +33,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: 'domains',
|
||||
component: () => import('@/layouts/default/DefaultLayout.vue'),
|
||||
component: () => import('@/layouts/empty/Layout.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
@@ -70,7 +70,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: 'imap_migration',
|
||||
component: () => import('@/layouts/default/DefaultLayout.vue'),
|
||||
component: () => import('@/layouts/empty/Layout.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
@@ -88,7 +88,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: 'providers',
|
||||
component: () => import('@/layouts/default/DefaultLayout.vue'),
|
||||
component: () => import('@/layouts/empty/Layout.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['Resellers', 'SuperAdmins'],
|
||||
@@ -120,7 +120,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: 'identities',
|
||||
component: () => import('@/layouts/default/DefaultLayout.vue'),
|
||||
component: () => import('@/layouts/empty/Layout.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
allowedRoles: ['DomainAdmins', 'Resellers', 'SuperAdmins'],
|
||||
@@ -233,7 +233,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
component: () => import('@/layouts/dashboard/DashboardLayout.vue'),
|
||||
component: () => import('@/layouts/connected/Layout.vue'),
|
||||
meta: {
|
||||
layout: 'account',
|
||||
requiresAuth: true,
|
||||
|
||||
@@ -2,44 +2,47 @@
|
||||
<div>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Domains') }}</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Import domains and aliases from CSV file')"
|
||||
variant="elevated"
|
||||
icon="mdi-file-import-outline"
|
||||
@click="showImportForm = true"
|
||||
></v-btn>
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Export domains and aliases to CSV')"
|
||||
variant="elevated"
|
||||
icon="mdi-file-export-outline"
|
||||
@click="exportDomains"
|
||||
></v-btn>
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-plus"
|
||||
>
|
||||
{{ $gettext('New') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact">
|
||||
<v-list-item @click="showDomainWizard = true">
|
||||
<v-list-item-title>{{ $gettext('Domain') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showAliasForm = true">
|
||||
<v-list-item-title>{{ $gettext('Alias') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-toolbar>
|
||||
|
||||
<DomainList />
|
||||
<DomainList>
|
||||
<template #extraActions>
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-plus"
|
||||
class="mr-4"
|
||||
>
|
||||
{{ $gettext('New') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact">
|
||||
<v-list-item @click="showDomainWizard = true">
|
||||
<v-list-item-title>{{ $gettext('Domain') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showAliasForm = true">
|
||||
<v-list-item-title>{{ $gettext('Alias') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Import domains and aliases from CSV file')"
|
||||
variant="flat"
|
||||
icon="mdi-file-import-outline"
|
||||
@click="showImportForm = true"
|
||||
></v-btn>
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Export domains and aliases to CSV')"
|
||||
variant="flat"
|
||||
icon="mdi-file-export-outline"
|
||||
@click="exportDomains"
|
||||
></v-btn>
|
||||
</template>
|
||||
</DomainList>
|
||||
|
||||
<v-dialog v-model="showDomainWizard" fullscreen scrollable z-index="10">
|
||||
<DomainCreationForm @close="showDomainWizard = false" />
|
||||
|
||||
@@ -2,44 +2,48 @@
|
||||
<div>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Identities') }}</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Import accounts and aliases from CSV file')"
|
||||
variant="elevated"
|
||||
icon="mdi-file-import-outline"
|
||||
@click="showImportForm = true"
|
||||
></v-btn>
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Export accounts and aliases to CSV')"
|
||||
icon="mdi-file-export-outline"
|
||||
variant="elevated"
|
||||
@click="exportIdentities"
|
||||
></v-btn>
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-plus"
|
||||
>
|
||||
{{ $gettext('New') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact">
|
||||
<v-list-item @click="showCreationWizard = true">
|
||||
<v-list-item-title>{{ $gettext('Account') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showAliasCreationWizard = true">
|
||||
<v-list-item-title>{{ $gettext('Alias') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-toolbar>
|
||||
|
||||
<IdentityList />
|
||||
<IdentityList>
|
||||
<template #extraActions>
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-plus"
|
||||
class="mr-4"
|
||||
>
|
||||
{{ $gettext('New') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact">
|
||||
<v-list-item @click="showCreationWizard = true">
|
||||
<v-list-item-title>{{ $gettext('Account') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showAliasCreationWizard = true">
|
||||
<v-list-item-title>{{ $gettext('Alias') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Import accounts and aliases from CSV file')"
|
||||
variant="flat"
|
||||
icon="mdi-file-import-outline"
|
||||
@click="showImportForm = true"
|
||||
></v-btn>
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
:title="$gettext('Export accounts and aliases to CSV')"
|
||||
icon="mdi-file-export-outline"
|
||||
variant="flat"
|
||||
@click="exportIdentities"
|
||||
></v-btn>
|
||||
</template>
|
||||
</IdentityList>
|
||||
<v-dialog
|
||||
v-model="showAliasCreationWizard"
|
||||
fullscreen
|
||||
|
||||
@@ -40,7 +40,7 @@ export default defineConfig({
|
||||
'./api-repository': './src/api/repository.js',
|
||||
'./auth-store': './src/stores/auth.store.js',
|
||||
'./auth-callback': './src/views/login/LoginCallbackView.vue',
|
||||
'./navbar': './src/layouts/dashboard/NavBar.vue',
|
||||
'./navbar': './src/components/shared/NavBar.vue',
|
||||
'./top-menu': './src/components/shared/TopMenu.vue',
|
||||
'./menu-items': './src/components/tools/MenuItems.vue',
|
||||
'./form-rules': './src/plugins/rules.js',
|
||||
|
||||
Reference in New Issue
Block a user