From 5ffdfcd6f2a68b7252b8d7e181c880590ba502a6 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Mon, 2 Sep 2024 15:57:03 +0200 Subject: [PATCH] role and user management --- package-lock.json | 8 +- package.json | 2 +- src/components/UserMenu.vue | 2 +- src/components/admin/Permission.vue | 16 ++- src/components/admin/RoutingLink.vue | 8 +- .../admin/user/role/CreateRoleModal.vue | 4 +- .../admin/user/role/DeleteRoleModal.vue | 59 +++++++++ .../admin/user/role/RoleListItem.vue | 32 +++-- .../admin/user/role/UpdateRoleModal.vue | 54 --------- .../admin/user/user/DeleteUserModal.vue | 59 +++++++++ .../admin/user/user/UserListItem.vue | 45 ++++++- src/router/index.ts | 88 +++++++++++++- src/stores/admin/role.ts | 53 +++++++- src/stores/admin/user.ts | 71 ++++++++++- src/templates/Main.vue | 8 +- src/viewmodels/admin/user.models.ts | 7 ++ src/views/admin/user/RoleEdit.vue | 62 ++++++++++ src/views/admin/user/RoleEditPermission.vue | 54 +++++++++ src/views/admin/user/UserEdit.vue | 86 +++++++++++++ src/views/admin/user/UserEditPermission.vue | 54 +++++++++ src/views/admin/user/UserEditRoles.vue | 114 ++++++++++++++++++ src/views/setup/Verify.vue | 4 +- 22 files changed, 798 insertions(+), 92 deletions(-) create mode 100644 src/components/admin/user/role/DeleteRoleModal.vue delete mode 100644 src/components/admin/user/role/UpdateRoleModal.vue create mode 100644 src/components/admin/user/user/DeleteUserModal.vue create mode 100644 src/views/admin/user/RoleEdit.vue create mode 100644 src/views/admin/user/RoleEditPermission.vue create mode 100644 src/views/admin/user/UserEdit.vue create mode 100644 src/views/admin/user/UserEditPermission.vue create mode 100644 src/views/admin/user/UserEditRoles.vue diff --git a/package-lock.json b/package-lock.json index cc47e42..6ff430b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "GPL-3.0-only", "dependencies": { "@headlessui/vue": "^1.7.13", - "@heroicons/vue": "^1.0.6", + "@heroicons/vue": "^2.1.5", "axios": "^0.26.1", "jwt-decode": "^4.0.0", "lodash.clonedeep": "^4.5.0", @@ -2422,9 +2422,9 @@ } }, "node_modules/@heroicons/vue": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-1.0.6.tgz", - "integrity": "sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.1.5.tgz", + "integrity": "sha512-IpqR72sFqFs55kyKfFS7tN+Ww6odFNeH/7UxycIOrlVYfj4WUGAdzQtLBnJspucSeqWFQsKM0g0YrgU655BEfA==", "license": "MIT", "peerDependencies": { "vue": ">= 3" diff --git a/package.json b/package.json index 8d629cb..1b2c737 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "license": "GPL-3.0-only", "dependencies": { "@headlessui/vue": "^1.7.13", - "@heroicons/vue": "^1.0.6", + "@heroicons/vue": "^2.1.5", "axios": "^0.26.1", "jwt-decode": "^4.0.0", "lodash.clonedeep": "^4.5.0", diff --git a/src/components/UserMenu.vue b/src/components/UserMenu.vue index 8be4d08..8c5bbe8 100644 --- a/src/components/UserMenu.vue +++ b/src/components/UserMenu.vue @@ -37,7 +37,7 @@ diff --git a/src/components/admin/Permission.vue b/src/components/admin/Permission.vue index 7a5c0c2..39325f8 100644 --- a/src/components/admin/Permission.vue +++ b/src/components/admin/Permission.vue @@ -68,6 +68,9 @@
+ + +
@@ -84,9 +87,12 @@ import type { } from "@/types/permissionTypes"; import { sectionsAndModules, permissionSections, permissionTypes } from "@/types/permissionTypes"; import { mapState, mapActions } from "pinia"; -import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from "@heroicons/vue/outline"; +import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from "@heroicons/vue/24/outline"; import { useAbilityStore } from "@/stores/ability"; import _cloneDeep from "lodash.clonedeep"; +import Spinner from "@/components/Spinner.vue"; +import SuccessCheckmark from "@/components/SuccessCheckmark.vue"; +import FailureXMark from "@/components/FailureXMark.vue"; diff --git a/src/components/admin/user/role/CreateRoleModal.vue b/src/components/admin/user/role/CreateRoleModal.vue index 99b6408..b3ea3f0 100644 --- a/src/components/admin/user/role/CreateRoleModal.vue +++ b/src/components/admin/user/role/CreateRoleModal.vue @@ -40,14 +40,14 @@ import { useRoleStore } from "@/stores/admin/role"; + + diff --git a/src/components/admin/user/role/RoleListItem.vue b/src/components/admin/user/role/RoleListItem.vue index 4033c5f..33e81fd 100644 --- a/src/components/admin/user/role/RoleListItem.vue +++ b/src/components/admin/user/role/RoleListItem.vue @@ -2,7 +2,23 @@

{{ role.role }} (Admin)

- +
+ + + + + + +
+ +
+
@@ -10,10 +26,11 @@ - - diff --git a/src/components/admin/user/user/DeleteUserModal.vue b/src/components/admin/user/user/DeleteUserModal.vue new file mode 100644 index 0000000..96ba510 --- /dev/null +++ b/src/components/admin/user/user/DeleteUserModal.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/components/admin/user/user/UserListItem.vue b/src/components/admin/user/user/UserListItem.vue index 2a30d7a..b175cb5 100644 --- a/src/components/admin/user/user/UserListItem.vue +++ b/src/components/admin/user/user/UserListItem.vue @@ -2,7 +2,29 @@

{{ user.firstname }} {{ user.lastname }}

- +
+ + + + + + + + + +
+ +
+
@@ -26,10 +48,12 @@ diff --git a/src/router/index.ts b/src/router/index.ts index ffa9676..db95e53 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -60,26 +60,36 @@ const router = createRouter({ path: "", name: "admin-club-default", component: () => import("../views/admin/ViewSelect.vue"), + meta: { type: "read", section: "club" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "members", name: "admin-club-members", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "club", module: "members" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "calendar", name: "admin-club-calendar", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "club", module: "calendar" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "newsletter", name: "admin-club-newsletter", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "club", module: "newsletter" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "protocol", name: "admin-club-protocol", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "club", module: "protocoll" }, + beforeEnter: [abilityAndNavUpdate], }, ], }, @@ -94,26 +104,36 @@ const router = createRouter({ path: "", name: "admin-settings-default", component: () => import("../views/admin/ViewSelect.vue"), + meta: { type: "read", section: "settings" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "qualification", name: "admin-settings-qualification", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "settings", module: "qualification" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "award", name: "admin-settings-award", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "settings", module: "award" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "executive-position", name: "admin-settings-executive_position", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "settings", module: "executive_position" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "communication", name: "admin-settings-communication", component: () => import("../views/admin/members/Overview.vue"), + meta: { type: "read", section: "settings", module: "communication" }, + beforeEnter: [abilityAndNavUpdate], }, ], }, @@ -128,16 +148,76 @@ const router = createRouter({ path: "", name: "admin-user-default", component: () => import("../views/admin/ViewSelect.vue"), + meta: { type: "read", section: "user" }, + beforeEnter: [abilityAndNavUpdate], }, { path: "user", - name: "admin-user-user", - component: () => import("../views/admin/user/User.vue"), + name: "admin-user-user-route", + component: () => import("../views/RouterView.vue"), + meta: { type: "read", section: "user", module: "user" }, + beforeEnter: [abilityAndNavUpdate], + children: [ + { + path: "", + name: "admin-user-user", + component: () => import("../views/admin/user/User.vue"), + }, + { + path: ":id/edit", + name: "admin-user-user-edit", + component: () => import("../views/admin/user/UserEdit.vue"), + meta: { type: "update", section: "user", module: "user" }, + beforeEnter: [abilityAndNavUpdate], + props: true, + }, + { + path: ":id/permission", + name: "admin-user-user-permission", + component: () => import("../views/admin/user/UserEditPermission.vue"), + meta: { type: "update", section: "user", module: "user" }, + beforeEnter: [abilityAndNavUpdate], + props: true, + }, + { + path: ":id/roles", + name: "admin-user-user-roles", + component: () => import("../views/admin/user/UserEditRoles.vue"), + meta: { type: "update", section: "user", module: "user" }, + beforeEnter: [abilityAndNavUpdate], + props: true, + }, + ], }, { path: "role", - name: "admin-user-role", - component: () => import("../views/admin/user/Role.vue"), + name: "admin-user-role-route", + component: () => import("../views/RouterView.vue"), + meta: { type: "read", section: "user", module: "role" }, + beforeEnter: [abilityAndNavUpdate], + children: [ + { + path: "", + name: "admin-user-role", + component: () => import("../views/admin/user/Role.vue"), + }, + { + path: ":id/edit", + name: "admin-user-role-edit", + component: () => import("../views/admin/user/RoleEdit.vue"), + meta: { type: "update", section: "user", module: "role" }, + beforeEnter: [abilityAndNavUpdate], + props: true, + }, + { + path: ":id/permission", + name: "admin-user-role-permission", + component: () => import("../views/admin/user/RoleEditPermission.vue"), + meta: { type: "update", section: "user", module: "role" }, + beforeEnter: [abilityAndNavUpdate], + props: true, + }, + ], }, ], }, diff --git a/src/stores/admin/role.ts b/src/stores/admin/role.ts index 0e2e98f..d255ffd 100644 --- a/src/stores/admin/role.ts +++ b/src/stores/admin/role.ts @@ -1,6 +1,7 @@ import { defineStore } from "pinia"; import type { RoleViewModel } from "../../viewmodels/admin/role.models"; import { http } from "../../serverCom"; +import type { PermissionObject } from "../../types/permissionTypes"; export const useRoleStore = defineStore("role", { state: () => { @@ -10,9 +11,16 @@ export const useRoleStore = defineStore("role", { loadingAll: null as null | "loading" | "success" | "failed", loadingSingle: null as null | "loading" | "success" | "failed", createStatus: null as null | "loading" | { status: "success" | "failed"; reason?: string }, + updateStatus: null as null | "loading" | { status: "success" | "failed"; reason?: string }, + deleteStatus: null as null | "loading" | { status: "success" | "failed"; reason?: string }, }; }, actions: { + resetStatus() { + this.createStatus = null; + this.updateStatus = null; + this.deleteStatus = null; + }, fetchRoles() { this.loadingAll = "loading"; http @@ -38,9 +46,6 @@ export const useRoleStore = defineStore("role", { this.loadingSingle = "failed"; }); }, - resetCreateStatus() { - this.createStatus = null; - }, createRole(role: string) { this.createStatus = "loading"; http @@ -55,5 +60,47 @@ export const useRoleStore = defineStore("role", { this.createStatus = { status: "failed", reason: err.data }; }); }, + updateActiveRole(role: string) { + if (this.role == null) return; + this.updateStatus = "loading"; + http + .patch(`/admin/role/${this.role.id}`, { + role: role, + }) + .then((result) => { + this.updateStatus = { status: "success" }; + this.fetchRoles(); + }) + .catch((err) => { + this.updateStatus = { status: "failed" }; + }); + }, + updateActiveRolePermissions(permission: PermissionObject) { + if (this.role == null) return; + this.updateStatus = "loading"; + http + .patch(`/admin/role/${this.role.id}/permissions`, { + permissions: permission, + }) + .then((result) => { + this.updateStatus = { status: "success" }; + this.fetchRoles(); + }) + .catch((err) => { + this.updateStatus = { status: "failed" }; + }); + }, + deleteRole(role: number) { + this.deleteStatus = "loading"; + http + .delete(`/admin/role/${role}`) + .then((res) => { + this.deleteStatus = { status: "success" }; + this.fetchRoles(); + }) + .catch((err) => { + this.deleteStatus = { status: "failed", reason: err.data }; + }); + }, }, }); diff --git a/src/stores/admin/user.ts b/src/stores/admin/user.ts index 38fc97f..a6905fd 100644 --- a/src/stores/admin/user.ts +++ b/src/stores/admin/user.ts @@ -1,6 +1,7 @@ import { defineStore } from "pinia"; -import type { UserViewModel } from "../../viewmodels/admin/user.models"; +import type { CreateOrUpdateUserViewModel, UserViewModel } from "../../viewmodels/admin/user.models"; import { http } from "../../serverCom"; +import type { PermissionObject } from "../../types/permissionTypes"; export const useUserStore = defineStore("user", { state: () => { @@ -9,9 +10,17 @@ export const useUserStore = defineStore("user", { user: null as null | UserViewModel, loadingAll: "loading" as "loading" | "fetched" | "failed", loadingSingle: "loading" as "loading" | "fetched" | "failed", + createStatus: null as null | "loading" | { status: "success" | "failed"; reason?: string }, + updateStatus: null as null | "loading" | { status: "success" | "failed"; reason?: string }, + deleteStatus: null as null | "loading" | { status: "success" | "failed"; reason?: string }, }; }, actions: { + resetStatus() { + this.createStatus = null; + this.updateStatus = null; + this.deleteStatus = null; + }, fetchUsers() { this.loadingAll = "loading"; http @@ -37,5 +46,65 @@ export const useUserStore = defineStore("user", { this.loadingSingle = "failed"; }); }, + updateActiveUser(user: CreateOrUpdateUserViewModel) { + if (this.user == null) return; + this.updateStatus = "loading"; + http + .patch(`/admin/user/${this.user.id}`, { + username: user.username, + firstname: user.firstname, + lastname: user.lastname, + mail: user.mail, + }) + .then((result) => { + this.updateStatus = { status: "success" }; + this.fetchUsers(); + }) + .catch((err) => { + this.updateStatus = { status: "failed" }; + }); + }, + updateActiveUserPermissions(permission: PermissionObject) { + if (this.user == null) return; + this.updateStatus = "loading"; + http + .patch(`/admin/user/${this.user.id}/permissions`, { + permissions: permission, + }) + .then((result) => { + this.updateStatus = { status: "success" }; + this.fetchUsers(); + }) + .catch((err) => { + this.updateStatus = { status: "failed" }; + }); + }, + updateActiveUserRoles(roles: Array) { + if (this.user == null) return; + this.updateStatus = "loading"; + http + .patch(`/admin/user/${this.user.id}/roles`, { + roleIds: roles, + }) + .then((result) => { + this.updateStatus = { status: "success" }; + this.fetchUsers(); + }) + .catch((err) => { + this.updateStatus = { status: "failed" }; + }); + }, + deleteUser(user: number) { + this.deleteStatus = "loading"; + http + .delete(`/admin/user/${user}`) + .then((res) => { + this.deleteStatus = { status: "success" }; + this.fetchUsers(); + }) + .catch((err) => { + this.deleteStatus = { status: "failed", reason: err.data }; + }); + }, }, }); diff --git a/src/templates/Main.vue b/src/templates/Main.vue index 9292318..cdf8a4d 100644 --- a/src/templates/Main.vue +++ b/src/templates/Main.vue @@ -1,6 +1,10 @@