From c57c2ff19d7f2758c175bc1d724d1db9e2fe9000 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 18 Nov 2025 17:30:56 +0100 Subject: [PATCH] Added user management functionality to the configuration page: - Introduced `Users` tab in `Config.vue` with a dynamic display controlled by user rights. - Added `EditUser.vue` and `Users.vue` components for managing user details and permissions via modal dialogs. - Implemented new `users` store handling user records, pagination, and updates. - Updated `TableView.vue` to support badge rendering and dynamic configurations. - Adjusted routing guards for consistent user data fetching and permissions. - Various UI refinements and component reorganization for better maintainability. --- .../UI/{AppGroupItem.vue => GroupItem.vue} | 21 +- src/frontend/src/components/UI/TableView.vue | 29 ++- .../{UI => layout/config}/AddApp.vue | 27 ++- .../src/components/layout/config/Apps.vue | 2 +- .../src/components/layout/config/EditUser.vue | 193 ++++++++++++++++++ .../src/components/layout/config/Users.vue | 148 ++++++++++++++ src/frontend/src/pages/Config.vue | 10 + src/frontend/src/router.js | 10 +- src/frontend/src/store/activeuser.js | 5 + src/frontend/src/store/notification.js | 42 ++-- src/frontend/src/store/users.js | 64 ++++++ 11 files changed, 504 insertions(+), 47 deletions(-) rename src/frontend/src/components/UI/{AppGroupItem.vue => GroupItem.vue} (65%) rename src/frontend/src/components/{UI => layout/config}/AddApp.vue (91%) create mode 100644 src/frontend/src/components/layout/config/EditUser.vue create mode 100644 src/frontend/src/components/layout/config/Users.vue create mode 100644 src/frontend/src/store/users.js diff --git a/src/frontend/src/components/UI/AppGroupItem.vue b/src/frontend/src/components/UI/GroupItem.vue similarity index 65% rename from src/frontend/src/components/UI/AppGroupItem.vue rename to src/frontend/src/components/UI/GroupItem.vue index 4c00016..4b3a46d 100644 --- a/src/frontend/src/components/UI/AppGroupItem.vue +++ b/src/frontend/src/components/UI/GroupItem.vue @@ -1,20 +1,23 @@ + + + \ No newline at end of file diff --git a/src/frontend/src/components/layout/config/Users.vue b/src/frontend/src/components/layout/config/Users.vue new file mode 100644 index 0000000..93eb6ff --- /dev/null +++ b/src/frontend/src/components/layout/config/Users.vue @@ -0,0 +1,148 @@ + + + + + + \ No newline at end of file diff --git a/src/frontend/src/pages/Config.vue b/src/frontend/src/pages/Config.vue index 9b0c42e..7685205 100644 --- a/src/frontend/src/pages/Config.vue +++ b/src/frontend/src/pages/Config.vue @@ -28,6 +28,7 @@ import ErrorLog from "@/pages/ErrorLog.vue"; import {mapStores} from "pinia"; import {useActiveUserStore} from "@/store/activeuser.js"; import Apps from "@/components/layout/config/Apps.vue"; +import Users from "@/components/layout/config/Users.vue"; export default { name: "Config", @@ -45,6 +46,11 @@ export default { component: markRaw(Apps), props: {isSelected: false}, }, + usersTab: { + title: 'Users', + component: markRaw(Users), + props: {isSelected: false}, + }, systemLogTab: { title: 'System log', component: markRaw(ErrorLog), @@ -86,6 +92,10 @@ export default { tabs.push(this.appsTab); } + if (this.activeUserStore.isRightManagement) { + tabs.push(this.usersTab); + } + if (this.activeUserStore.isSuper || this.activeUserStore.isMaterial) { tabs.push(this.materialsTab); } diff --git a/src/frontend/src/router.js b/src/frontend/src/router.js index ff4c4ec..1c227e9 100644 --- a/src/frontend/src/router.js +++ b/src/frontend/src/router.js @@ -58,7 +58,7 @@ const router = createRouter({ name: 'edit', beforeEnter: async (to, from) => { const userStore = useActiveUserStore(); - await userStore.loadIfRequired(); + await userStore.load(); if (userStore.allowCalculation) { return true; @@ -72,7 +72,7 @@ const router = createRouter({ name: 'bulk', beforeEnter: async (to, from) => { const userStore = useActiveUserStore(); - await userStore.loadIfRequired(); + await userStore.load(); if (userStore.allowCalculation) { return true; @@ -86,7 +86,7 @@ const router = createRouter({ name: 'bulk-single-edit', beforeEnter: async (to, from) => { const userStore = useActiveUserStore(); - await userStore.loadIfRequired(); + await userStore.load(); if (userStore.allowCalculation) { return true; @@ -99,7 +99,7 @@ const router = createRouter({ component: Reporting, beforeEnter: async (to, from) => { const userStore = useActiveUserStore(); - await userStore.loadIfRequired(); + await userStore.load(); if (userStore.allowReporting) { return true; @@ -112,7 +112,7 @@ const router = createRouter({ component: Config, beforeEnter: async (to, from) => { const userStore = useActiveUserStore(); - await userStore.loadIfRequired(); + await userStore.load(); if (userStore.allowConfiguration) { return true; diff --git a/src/frontend/src/store/activeuser.js b/src/frontend/src/store/activeuser.js index ab74960..c738235 100644 --- a/src/frontend/src/store/activeuser.js +++ b/src/frontend/src/store/activeuser.js @@ -35,6 +35,11 @@ export const useActiveUserStore = defineStore('activeUser', { return false; return state.user.groups?.includes("service"); }, + isRightManagement(state) { + if (state.user === null) + return false; + return state.user.groups?.includes("right-management"); + }, isPackaging(state) { if (state.user === null) return false; diff --git a/src/frontend/src/store/notification.js b/src/frontend/src/store/notification.js index 37c308e..c8abf14 100644 --- a/src/frontend/src/store/notification.js +++ b/src/frontend/src/store/notification.js @@ -41,16 +41,18 @@ export const useNotificationStore = defineStore('notification', { duration: notification.duration ?? 8000 }) }, - async addError(errorDto, options = {}) { + async addError(errorDto, options = {}, silent = false) { const {request = null, store = null, global = false} = options; const state = this.captureStoreState(store, global); - this.addNotification({ - icon: 'bug', - message: errorDto.message ?? 'Unknown error code', - title: errorDto.title ?? 'Unknown error', - variant: 'exception', - }); + if (!silent) { + this.addNotification({ + icon: 'bug', + message: errorDto.message ?? 'Unknown error code', + title: errorDto.title ?? 'Unknown error', + variant: 'exception', + }); + } const error = { error: { @@ -158,17 +160,18 @@ export function setupErrorBuffer() { const errorStore = useNotificationStore() //Unhandled Promise Rejections - // window.addEventListener('unhandledrejection', (event) => { - // - // const error = { - // code: "Unhandled rejection", - // title: "Frontend error", - // message: event.reason?.message || 'Unhandled Promise Rejection', - // traceCombined: event.reason?.stack, - // }; - // - // errorStore.addError(error, {global: true}).then(r => {} ); - // }) + window.addEventListener('unhandledrejection', (event) => { + + const error = { + code: "Unhandled rejection", + title: "Frontend error", + message: event.reason?.message || 'Unhandled Promise Rejection', + traceCombined: event.reason?.stack, + }; + + errorStore.addError(error, {global: true}, true).then(r => { + }); + }) // // JavaScript Errors window.addEventListener('error', (event) => { @@ -178,7 +181,8 @@ export function setupErrorBuffer() { message: event.reason?.message || 'Unhandled Promise Rejection', traceCombined: event.reason?.stack, }; - errorStore.addError(error, {global: true}).then(r => {} ); + errorStore.addError(error, {global: true}).then(r => { + }); }) window.addEventListener('beforeunload', async () => { diff --git a/src/frontend/src/store/users.js b/src/frontend/src/store/users.js new file mode 100644 index 0000000..9adc32c --- /dev/null +++ b/src/frontend/src/store/users.js @@ -0,0 +1,64 @@ +import {defineStore} from 'pinia' +import {config} from '@/config' +import {useNotificationStore} from "@/store/notification.js"; +import performRequest from "@/backend.js"; +import logger from "@/logger.js"; + + +export const useUsersStore = defineStore('users', { + state: () => ({ + users: [], + loading: false, + pagination: {}, + query: {}, + }), + getters: { + getById: (state) => { + return (id) => state.users.find(p => p.id === id) + } + }, + actions: { + async setQuery(query) { + this.query = query; + await this.load(); + }, + async updateUser(user) { + const body = { + firstname: user.firstname, + lastname: user.lastname, + mail: user.mail, + workday_id: user.workday_id, + groups: user.groups + } + + await performRequest(this, "PUT", `${config.backendUrl}/users/`, body, false); + }, + async load() { + + this.loading = true; + + const params = new URLSearchParams(); + + if (this.query?.page) + params.append('page', this.query.page); + + if (this.query?.pageSize) + params.append('limit', this.query.pageSize); + + const endpoint = '/users'; + const url = `${config.backendUrl}${endpoint}/${params.size === 0 ? '' : '?'}${params.toString()}`; + const {data: data, headers: headers} = await performRequest(this, "GET", url, null, true); + + this.pagination = { + page: parseInt(headers.get('X-Current-Page')), + pageCount: parseInt(headers.get('X-Page-Count')), + totalCount: parseInt(headers.get('X-Total-Count')) + }; + + this.loading = false; + this.empty = data.length === 0; + this.users = data; + } + } + +});