This commit is contained in:
Antoine Nguyen
2025-02-14 13:39:08 +01:00
parent afe3253fc8
commit a6dfe5aefa
17 changed files with 506 additions and 564 deletions
@@ -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>
+160
View File
@@ -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>
+34 -14
View File
@@ -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>
-386
View File
@@ -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>
@@ -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>
+7 -7
View File
@@ -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
+1 -1
View File
@@ -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',