diff --git a/src/components/Footer.vue b/src/components/Footer.vue index ffd7fe5..658ff17 100644 --- a/src/components/Footer.vue +++ b/src/components/Footer.vue @@ -1,6 +1,6 @@ diff --git a/src/components/admin/user/role/UpdateRoleModal.vue b/src/components/admin/user/role/UpdateRoleModal.vue new file mode 100644 index 0000000..44fff5d --- /dev/null +++ b/src/components/admin/user/role/UpdateRoleModal.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/layouts/Sidebar.vue b/src/layouts/Sidebar.vue index 2bffff5..043e5d7 100644 --- a/src/layouts/Sidebar.vue +++ b/src/layouts/Sidebar.vue @@ -6,7 +6,7 @@ > -
+
@@ -29,7 +29,7 @@ export default defineComponent({ computed: { ...mapState(useNavigationStore, ["activeLink"]), defaultRoute() { - return this.activeLink == null; + return ((this.$route?.name as string) ?? "").includes("-default"); }, }, }); diff --git a/src/router/adminGuard.ts b/src/router/adminGuard.ts new file mode 100644 index 0000000..1a70b02 --- /dev/null +++ b/src/router/adminGuard.ts @@ -0,0 +1,24 @@ +import NProgress from "nprogress"; +import { useAbilityStore } from "../stores/ability"; +import { useNavigationStore } from "../stores/admin/navigation"; + +export async function abilityAndNavUpdate(to: any, from: any, next: any) { + NProgress.start(); + const ability = useAbilityStore(); + const navigation = useNavigationStore(); + + let type = to.meta.type; + let section = to.meta.section; + let module = to.meta.module; + + navigation.activeNavigation = to.name.split("-")[1]; + navigation.activeLink = to.name.split("-")[2]; + + if (ability.can(type, section, module)) { + NProgress.done(); + next(); + } else { + NProgress.done(); + next(false); + } +} diff --git a/src/router/index.ts b/src/router/index.ts index e1b1114..ffa9676 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,6 +4,8 @@ import Login from "../views/Login.vue"; import { isAuthenticated } from "./authGuards"; import { loadAccountData } from "./accountGuard"; import { isSetup } from "./setupGuard"; +import { abilityAndNavUpdate } from "./adminGuard"; +import type { PermissionType, PermissionSection, PermissionModule } from "../types/permissionTypes"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -41,6 +43,110 @@ const router = createRouter({ name: "admin", component: () => import("../views/admin/View.vue"), beforeEnter: [isAuthenticated], + children: [ + { + path: "", + name: "admin-default", + component: () => import("../views/RouterView.vue"), + }, + { + path: "club", + name: "admin-club", + component: () => import("../views/RouterView.vue"), + meta: { type: "read", section: "club" }, + beforeEnter: [abilityAndNavUpdate], + children: [ + { + path: "", + name: "admin-club-default", + component: () => import("../views/admin/ViewSelect.vue"), + }, + { + path: "members", + name: "admin-club-members", + component: () => import("../views/admin/members/Overview.vue"), + }, + { + path: "calendar", + name: "admin-club-calendar", + component: () => import("../views/admin/members/Overview.vue"), + }, + { + path: "newsletter", + name: "admin-club-newsletter", + component: () => import("../views/admin/members/Overview.vue"), + }, + { + path: "protocol", + name: "admin-club-protocol", + component: () => import("../views/admin/members/Overview.vue"), + }, + ], + }, + { + path: "settings", + name: "admin-settings", + component: () => import("../views/RouterView.vue"), + meta: { type: "read", section: "settings" }, + beforeEnter: [abilityAndNavUpdate], + children: [ + { + path: "", + name: "admin-settings-default", + component: () => import("../views/admin/ViewSelect.vue"), + }, + { + path: "qualification", + name: "admin-settings-qualification", + component: () => import("../views/admin/members/Overview.vue"), + }, + { + path: "award", + name: "admin-settings-award", + component: () => import("../views/admin/members/Overview.vue"), + }, + { + path: "executive-position", + name: "admin-settings-executive_position", + component: () => import("../views/admin/members/Overview.vue"), + }, + { + path: "communication", + name: "admin-settings-communication", + component: () => import("../views/admin/members/Overview.vue"), + }, + ], + }, + { + path: "user", + name: "admin-user", + component: () => import("../views/RouterView.vue"), + meta: { type: "read", section: "user" }, + beforeEnter: [abilityAndNavUpdate], + children: [ + { + path: "", + name: "admin-user-default", + component: () => import("../views/admin/ViewSelect.vue"), + }, + { + path: "user", + name: "admin-user-user", + component: () => import("../views/admin/user/User.vue"), + }, + { + path: "role", + name: "admin-user-role", + component: () => import("../views/admin/user/Role.vue"), + }, + ], + }, + { + path: ":pathMatch(.*)*", + name: "admin-404", + component: () => import("../views/notFound.vue"), + }, + ], }, { path: "/nopermissions", @@ -56,3 +162,11 @@ const router = createRouter({ }); export default router; + +declare module "vue-router" { + interface RouteMeta { + type?: PermissionType | "admin"; + section?: PermissionSection; + module?: PermissionModule; + } +} diff --git a/src/stores/admin/navigation.ts b/src/stores/admin/navigation.ts index db699ea..6b78396 100644 --- a/src/stores/admin/navigation.ts +++ b/src/stores/admin/navigation.ts @@ -1,6 +1,6 @@ import { defineStore } from "pinia"; -import { shallowRef, defineAsyncComponent } from "vue"; import { useAbilityStore } from "../ability"; +import router from "../../router"; export interface navigationModel { club: navigationSplitModel; @@ -26,17 +26,15 @@ export interface topLevelNavigationModel { export interface navigationLinkModel { key: string; title: string; - component: any; } export const useNavigationStore = defineStore("navigation", { state: () => { return { activeNavigation: "club" as topLevelNavigationType, - activeLink: null as null | navigationLinkModel, + activeLink: null as null | string, topLevel: [] as Array, navigation: {} as navigationModel, - componentOverwrite: null as null | any, }; }, getters: { @@ -45,42 +43,10 @@ export const useNavigationStore = defineStore("navigation", { (state.topLevel.find((elem) => elem.key == state.activeNavigation) ?? {}) as topLevelNavigationModel, }, actions: { - setTopLevel(key: topLevelNavigationType, disableSubLink: boolean = true) { - let level = this.topLevel.find((e) => e.key == key) ?? null; - if (!level) { - this.activeNavigation = "club"; - if (!disableSubLink) this.setLink(this.topLevel.find((e) => e.key == "club")?.levelDefault ?? null); - else this.setLink(null); - } else { - this.activeNavigation = level.key; - if (!disableSubLink) this.setLink(level.levelDefault); - else this.setLink(null); - } - this.resetComponentOverwrite(); - }, - setLink(key: string | null) { - let nav = this.navigation[this.activeNavigation]; - if (!nav) { - this.activeLink = null; - return; - } - let links = [...Object.values(nav.main), ...Object.values(nav.top ?? {})]; - this.activeLink = links.find((e) => e.key == key) ?? null; - this.resetComponentOverwrite(); - }, - setTopLevelNav(topLeveLinks: Array) { - this.topLevel = topLeveLinks; - }, - setComponentOverwrite(component: any) { - this.componentOverwrite = component; - }, - resetComponentOverwrite() { - this.componentOverwrite = null; - }, resetNavigation() { this.$reset(); }, - updateTopLevel() { + updateTopLevel(first: boolean = false) { const abilityStore = useAbilityStore(); this.topLevel = [ ...(abilityStore.canSection("read", "club") @@ -88,7 +54,7 @@ export const useNavigationStore = defineStore("navigation", { { key: "club", title: "Verein", - levelDefault: "#members", + levelDefault: "members", } as topLevelNavigationModel, ] : []), @@ -97,7 +63,7 @@ export const useNavigationStore = defineStore("navigation", { { key: "settings", title: "Einstellungen", - levelDefault: "#qualification", + levelDefault: "qualification", } as topLevelNavigationModel, ] : []), @@ -106,124 +72,51 @@ export const useNavigationStore = defineStore("navigation", { { key: "user", title: "Benutzer", - levelDefault: "#user", + levelDefault: "user", } as topLevelNavigationModel, ] : []), ]; - if (this.topLevel.findIndex((e) => e.key == this.activeNavigation) == -1) - this.activeNavigation = this.topLevel[0]?.key ?? "club"; + if (this.topLevel.findIndex((e) => e.key == this.activeNavigation) == -1 && !first) + router.push({ name: `admin-${this.topLevel[0]?.key ?? "club"}-default` }); }, - updateNavigation() { + updateNavigation(first: boolean = false) { const abilityStore = useAbilityStore(); this.navigation = { club: { mainTitle: "Verein", main: [ - ...(abilityStore.can("read", "club", "members") - ? [ - { - key: "#members", - title: "Mitglieder", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] - : []), - ...(abilityStore.can("read", "club", "calendar") - ? [ - { - key: "#calendar", - title: "Termine", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] - : []), - ...(abilityStore.can("read", "club", "newsletter") - ? [ - { - key: "#newsletter", - title: "Newsletter", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] - : []), - ...(abilityStore.can("read", "club", "protocoll") - ? [ - { - key: "#protocol", - title: "Protokolle", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] - : []), + ...(abilityStore.can("read", "club", "members") ? [{ key: "members", title: "Mitglieder" }] : []), + ...(abilityStore.can("read", "club", "calendar") ? [{ key: "calendar", title: "Termine" }] : []), + ...(abilityStore.can("read", "club", "newsletter") ? [{ key: "newsletter", title: "Newsletter" }] : []), + ...(abilityStore.can("read", "club", "protocoll") ? [{ key: "protocol", title: "Protokolle" }] : []), ], }, settings: { mainTitle: "Einstellungen", main: [ ...(abilityStore.can("read", "settings", "qualification") - ? [ - { - key: "#qualification", - title: "Qualifikationen", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] - : []), - ...(abilityStore.can("read", "settings", "award") - ? [ - { - key: "#award", - title: "Auszeichnungen", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] + ? [{ key: "qualification", title: "Qualifikationen" }] : []), + ...(abilityStore.can("read", "settings", "award") ? [{ key: "award", title: "Auszeichnungen" }] : []), ...(abilityStore.can("read", "settings", "executive_position") - ? [ - { - key: "#executive_position", - title: "Vereinsämter", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] + ? [{ key: "executive_position", title: "Vereinsämter" }] : []), ...(abilityStore.can("read", "settings", "communication") - ? [ - { - key: "#communication", - title: "Mitgliederdaten", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), - }, - ] + ? [{ key: "communication", title: "Mitgliederdaten" }] : []), ], }, user: { mainTitle: "Benutzer", main: [ - ...(abilityStore.can("read", "user", "user") - ? [ - { - key: "#user", - title: "Benutzer", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/user/User.vue"))), - }, - ] - : []), - ...(abilityStore.can("read", "user", "role") - ? [ - { - key: "#role", - title: "Rollen", - component: shallowRef(defineAsyncComponent(() => import("@/views/admin/user/Role.vue"))), - }, - ] - : []), + ...(abilityStore.can("read", "user", "user") ? [{ key: "user", title: "Benutzer" }] : []), + ...(abilityStore.can("read", "user", "role") ? [{ key: "role", title: "Rollen" }] : []), ], }, } as navigationModel; - if (this.topLevel.findIndex((e) => e.key == this.activeLink?.key) == -1) this.setLink(null); + if (this.topLevel.findIndex((e) => e.key == this.activeLink) == -1 && !first) + router.push({ name: `admin-${this.activeNavigation}-default` }); }, }, }); diff --git a/src/stores/admin/role.ts b/src/stores/admin/role.ts index 569dcbf..0e2e98f 100644 --- a/src/stores/admin/role.ts +++ b/src/stores/admin/role.ts @@ -25,7 +25,7 @@ export const useRoleStore = defineStore("role", { this.loadingAll = "failed"; }); }, - fetchRolesById(id: number) { + fetchRoleById(id: number) { this.role = null; this.loadingSingle = "loading"; http diff --git a/src/stores/admin/user.ts b/src/stores/admin/user.ts index 8b36fe5..38fc97f 100644 --- a/src/stores/admin/user.ts +++ b/src/stores/admin/user.ts @@ -24,7 +24,7 @@ export const useUserStore = defineStore("user", { this.loadingAll = "failed"; }); }, - fetchUsersById(id: number) { + fetchUserById(id: number) { this.user = null; this.loadingSingle = "loading"; http diff --git a/src/templates/Main.vue b/src/templates/Main.vue index d2d7ced..9292318 100644 --- a/src/templates/Main.vue +++ b/src/templates/Main.vue @@ -1,6 +1,8 @@ @@ -29,6 +28,7 @@ import SidebarLayout from "@/layouts/Sidebar.vue"; import SidebarTemplate from "@/templates/Sidebar.vue"; import RoutingLink from "@/components/admin/RoutingLink.vue"; import { useAbilityStore } from "../../stores/ability"; +import RouterView from "../RouterView.vue"; diff --git a/src/views/admin/ViewSelect.vue b/src/views/admin/ViewSelect.vue new file mode 100644 index 0000000..693ea42 --- /dev/null +++ b/src/views/admin/ViewSelect.vue @@ -0,0 +1,3 @@ +