display users and roles

This commit is contained in:
Julian Krauser 2024-09-01 14:54:49 +02:00
parent eff79a4697
commit 2d0fb30558
12 changed files with 330 additions and 14 deletions

View file

@ -85,7 +85,7 @@ import type {
import { sectionsAndModules, permissionSections, permissionTypes } from "@/types/permissionTypes"; import { sectionsAndModules, permissionSections, permissionTypes } from "@/types/permissionTypes";
import { mapState, mapActions } from "pinia"; import { mapState, mapActions } from "pinia";
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from "@heroicons/vue/outline"; import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from "@heroicons/vue/outline";
import { useAbilityStore } from "../../../stores/ability"; import { useAbilityStore } from "@/stores/ability";
import _cloneDeep from "lodash.clonedeep"; import _cloneDeep from "lodash.clonedeep";
</script> </script>

View file

@ -0,0 +1,57 @@
<template>
<div class="w-full md:max-w-md">
<div class="flex flex-col items-center">
<p class="text-xl font-medium">Rolle erstellen</p>
</div>
<br />
<form class="flex flex-col gap-4 py-2" @submit.prevent="triggerCreateRole">
<div>
<label for="role">Rollenbezeichnung</label>
<input type="text" id="role" required />
</div>
<div class="flex flex-row gap-2">
<button primary type="submit" :disabled="createStatus == 'loading' || createStatus?.status == 'success'">
erstellen
</button>
<Spinner v-if="createStatus == 'loading'" class="my-auto" />
<SuccessCheckmark v-else-if="createStatus?.status == 'success'" />
<FailureXMark v-else-if="createStatus?.status == 'failed'" />
</div>
</form>
<div class="flex flex-row justify-end">
<div class="flex flex-row gap-4 py-2">
<button primary-outline @click="closeModal">schließen</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import { useModalStore } from "@/stores/modal";
import Spinner from "@/components/Spinner.vue";
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
import FailureXMark from "@/components/FailureXMark.vue";
import { useRoleStore } from "@/stores/admin/role";
</script>
<script lang="ts">
export default defineComponent({
mounted() {
this.resetCreateStatus();
},
computed: {
...mapState(useRoleStore, ["createStatus"]),
},
methods: {
...mapActions(useModalStore, ["closeModal"]),
...mapActions(useRoleStore, ["createRole", "resetCreateStatus"]),
triggerCreateRole(e: any) {
let formData = e.target.elements;
this.createRole(formData.role.value);
},
},
});
</script>

View file

@ -0,0 +1,27 @@
<template>
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>{{ role.role }} <small v-if="role.permissions?.isAdmin">(Admin)</small></p>
<PencilIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import { PencilIcon } from "@heroicons/vue/outline";
import type { RoleViewModel } from "@/viewmodels/admin/role.models";
</script>
<script lang="ts">
export default defineComponent({
props: {
role: { type: Object as PropType<RoleViewModel>, default: {} },
},
data() {
return {};
},
mounted() {},
});
</script>

View file

@ -0,0 +1,45 @@
<template>
<div class="flex flex-col h-fit w-full border border-primary rounded-md">
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
<p>{{ user.firstname }} {{ user.lastname }}</p>
<PencilIcon class="w-5 h-5 p-1 box-content cursor-pointer" />
</div>
<div class="flex flex-col p-2">
<div class="flex flex-row gap-2">
<p class="min-w-16">Benutzer:</p>
<p class="grow overflow-hidden">{{ user.username }}</p>
</div>
<div class="flex flex-row gap-2">
<p class="min-w-16">Mail:</p>
<p class="grow overflow-hidden">{{ user.mail }}</p>
</div>
<div class="flex flex-row gap-2">
<p class="min-w-16">Rollen:</p>
<div class="flex flex-row gap-2 flex-wrap grow">
<p v-for="role in user.roles" :key="role.id" class="px-1 border border-gray-300 rounded-md">
{{ role.role }}
</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent, type PropType } from "vue";
import { mapState, mapActions } from "pinia";
import type { UserViewModel } from "@/viewmodels/admin/user.models";
import { PencilIcon } from "@heroicons/vue/outline";
</script>
<script lang="ts">
export default defineComponent({
props: {
user: { type: Object as PropType<UserViewModel>, default: {} },
},
data() {
return {};
},
mounted() {},
});
</script>

View file

@ -12,8 +12,8 @@ export const useAbilityStore = defineStore("ability", {
(state) => (state) =>
(type: PermissionType | "admin", section: PermissionSection, module?: PermissionModule): boolean => { (type: PermissionType | "admin", section: PermissionSection, module?: PermissionModule): boolean => {
const permissions = state.permissions; const permissions = state.permissions;
if (type == "admin") return permissions.admin ?? false; if (type == "admin") return permissions?.admin ?? false;
if (permissions.admin) return true; if (permissions?.admin) return true;
if ( if (
(!module && (!module &&
permissions[section] != undefined && permissions[section] != undefined &&
@ -30,8 +30,8 @@ export const useAbilityStore = defineStore("ability", {
(state) => (state) =>
(type: PermissionType | "admin", section: PermissionSection): boolean => { (type: PermissionType | "admin", section: PermissionSection): boolean => {
const permissions = state.permissions; const permissions = state.permissions;
if (type == "admin") return permissions.admin ?? false; if (type == "admin") return permissions?.admin ?? false;
if (permissions.admin) return true; if (permissions?.admin) return true;
if ( if (
permissions[section]?.all == "*" || permissions[section]?.all == "*" ||
permissions[section]?.all?.includes(type) || permissions[section]?.all?.includes(type) ||
@ -48,8 +48,8 @@ export const useAbilityStore = defineStore("ability", {
section: PermissionSection, section: PermissionSection,
module?: PermissionModule module?: PermissionModule
): boolean => { ): boolean => {
if (type == "admin") return permissions.admin ?? false; if (type == "admin") return permissions?.admin ?? false;
if (permissions.admin) return true; if (permissions?.admin) return true;
if ( if (
(!module && (!module &&
permissions[section] != undefined && permissions[section] != undefined &&

View file

@ -56,6 +56,7 @@ export const useNavigationStore = defineStore("navigation", {
if (!disableSubLink) this.setLink(level.levelDefault); if (!disableSubLink) this.setLink(level.levelDefault);
else this.setLink(null); else this.setLink(null);
} }
this.resetComponentOverwrite();
}, },
setLink(key: string | null) { setLink(key: string | null) {
let nav = this.navigation[this.activeNavigation]; let nav = this.navigation[this.activeNavigation];
@ -65,6 +66,7 @@ export const useNavigationStore = defineStore("navigation", {
} }
let links = [...Object.values(nav.main), ...Object.values(nav.top ?? {})]; let links = [...Object.values(nav.main), ...Object.values(nav.top ?? {})];
this.activeLink = links.find((e) => e.key == key) ?? null; this.activeLink = links.find((e) => e.key == key) ?? null;
this.resetComponentOverwrite();
}, },
setTopLevelNav(topLeveLinks: Array<topLevelNavigationModel>) { setTopLevelNav(topLeveLinks: Array<topLevelNavigationModel>) {
this.topLevel = topLeveLinks; this.topLevel = topLeveLinks;
@ -209,12 +211,12 @@ export const useNavigationStore = defineStore("navigation", {
}, },
] ]
: []), : []),
...(abilityStore.can("admin", "user", "role") ...(abilityStore.can("read", "user", "role")
? [ ? [
{ {
key: "#role", key: "#role",
title: "Rollen", title: "Rollen",
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))), component: shallowRef(defineAsyncComponent(() => import("@/views/admin/user/Role.vue"))),
}, },
] ]
: []), : []),

59
src/stores/admin/role.ts Normal file
View file

@ -0,0 +1,59 @@
import { defineStore } from "pinia";
import type { RoleViewModel } from "../../viewmodels/admin/role.models";
import { http } from "../../serverCom";
export const useRoleStore = defineStore("role", {
state: () => {
return {
roles: [] as Array<RoleViewModel>,
role: null as null | RoleViewModel,
loadingAll: null as null | "loading" | "success" | "failed",
loadingSingle: null as null | "loading" | "success" | "failed",
createStatus: null as null | "loading" | { status: "success" | "failed"; reason?: string },
};
},
actions: {
fetchRoles() {
this.loadingAll = "loading";
http
.get("/admin/role")
.then((result) => {
this.roles = result.data;
this.loadingAll = "success";
})
.catch((err) => {
this.loadingAll = "failed";
});
},
fetchRolesById(id: number) {
this.role = null;
this.loadingSingle = "loading";
http
.get(`/admin/role/${id}`)
.then((result) => {
this.role = result.data;
this.loadingSingle = "success";
})
.catch((err) => {
this.loadingSingle = "failed";
});
},
resetCreateStatus() {
this.createStatus = null;
},
createRole(role: string) {
this.createStatus = "loading";
http
.post("/admin/role", {
role: role,
})
.then((res) => {
this.createStatus = { status: "success" };
this.fetchRoles();
})
.catch((err) => {
this.createStatus = { status: "failed", reason: err.data };
});
},
},
});

41
src/stores/admin/user.ts Normal file
View file

@ -0,0 +1,41 @@
import { defineStore } from "pinia";
import type { UserViewModel } from "../../viewmodels/admin/user.models";
import { http } from "../../serverCom";
export const useUserStore = defineStore("user", {
state: () => {
return {
users: [] as Array<UserViewModel>,
user: null as null | UserViewModel,
loadingAll: "loading" as "loading" | "fetched" | "failed",
loadingSingle: "loading" as "loading" | "fetched" | "failed",
};
},
actions: {
fetchUsers() {
this.loadingAll = "loading";
http
.get("/admin/user")
.then((result) => {
this.users = result.data;
this.loadingAll = "fetched";
})
.catch((err) => {
this.loadingAll = "failed";
});
},
fetchUsersById(id: number) {
this.user = null;
this.loadingSingle = "loading";
http
.get(`/admin/user/${id}`)
.then((result) => {
this.user = result.data;
this.loadingSingle = "fetched";
})
.catch((err) => {
this.loadingSingle = "failed";
});
},
},
});

View file

@ -0,0 +1,7 @@
import { PermissionObject } from "../../type/permissionTypes";
export interface RoleViewModel {
id: number;
permissions: PermissionObject;
role: string;
}

View file

@ -0,0 +1,13 @@
import { PermissionObject } from "../../type/permissionTypes";
import { RoleViewModel } from "./role.models";
export interface UserViewModel {
id: number;
username: string;
mail: string;
firstname: string;
lastname: string;
permissions: PermissionObject;
roles: Array<RoleViewModel>;
permissions_total: PermissionObject;
}

View file

@ -0,0 +1,46 @@
<template>
<MainTemplate>
<template #topBar>
<div class="flex flex-row items-center justify-between pt-5 pb-3 px-7">
<h1 class="font-bold text-xl h-8">Rollen</h1>
</div>
</template>
<template #diffMain>
<div class="flex flex-col gap-4 grow pl-7">
<div class="flex flex-col gap-2 grow overflow-y-scroll pr-7">
<RoleListItem v-for="role in roles" :key="role.id" :role="role" />
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit" @click="openCreateModal">Rolle erstellen</button>
</div>
</div>
</template>
</MainTemplate>
</template>
<script setup lang="ts">
import { defineAsyncComponent, defineComponent, markRaw } from "vue";
import { mapState, mapActions } from "pinia";
import MainTemplate from "@/templates/Main.vue";
import { useRoleStore } from "../../../stores/admin/role";
import RoleListItem from "@/components/admin/user/role/RoleListItem.vue";
import { useModalStore } from "@/stores/modal";
</script>
<script lang="ts">
export default defineComponent({
computed: {
...mapState(useRoleStore, ["roles"]),
},
mounted() {
this.fetchRoles();
},
methods: {
...mapActions(useRoleStore, ["fetchRoles"]),
...mapActions(useModalStore, ["openModal"]),
openCreateModal() {
this.openModal(markRaw(defineAsyncComponent(() => import("@/components/admin/user/role/CreateRoleModal.vue"))));
},
},
});
</script>

View file

@ -5,19 +5,38 @@
<h1 class="font-bold text-xl h-8">Benutzer</h1> <h1 class="font-bold text-xl h-8">Benutzer</h1>
</div> </div>
</template> </template>
<template #main> <template #diffMain>
<Permission /> <div class="flex flex-col gap-4 grow pl-7">
<div class="flex flex-col gap-2 grow overflow-y-scroll pr-7">
<UserListItem v-for="user in users" :key="user.id" :user="user" />
</div>
<div class="flex flex-row gap-4">
<button primary class="!w-fit">Nutzer einladen</button>
<button primary-outline class="!w-fit">offene Einladungen</button>
</div>
</div>
</template> </template>
</MainTemplate> </MainTemplate>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapState } from "pinia"; import { mapState, mapActions } from "pinia";
import MainTemplate from "@/templates/Main.vue"; import MainTemplate from "@/templates/Main.vue";
import Permission from "@/components/admin/user/Permission.vue"; import { useUserStore } from "@/stores/admin/user";
import UserListItem from "@/components/admin/user/user/UserListItem.vue";
</script> </script>
<script lang="ts"> <script lang="ts">
export default defineComponent({}); export default defineComponent({
computed: {
...mapState(useUserStore, ["users"]),
},
mounted() {
this.fetchUsers();
},
methods: {
...mapActions(useUserStore, ["fetchUsers"]),
},
});
</script> </script>