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.
This commit is contained in:
parent
83e007088c
commit
c57c2ff19d
11 changed files with 504 additions and 47 deletions
|
|
@ -1,20 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="app-group-item">
|
<div class="group-item">
|
||||||
|
<tooltip :text="groupObj.group_description">
|
||||||
<checkbox :checked="selected" @checkbox-changed="checkboxChanged"></checkbox>
|
<checkbox :checked="selected" @checkbox-changed="checkboxChanged"></checkbox>
|
||||||
<div>
|
<div>
|
||||||
<div class="app-group-item-name">{{ groupObj.group_name }}</div>
|
<div class="group-item-name">{{ groupObj.group_name }}</div>
|
||||||
<div class="app-group-item-descr">{{ groupObj.group_description }}</div>
|
<!-- <div class="app-group-item-descr">{{ groupObj.group_description }}</div>-->
|
||||||
</div>
|
</div>
|
||||||
|
</tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import Checkbox from "@/components/UI/Checkbox.vue";
|
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||||
|
import Tooltip from "@/components/UI/Tooltip.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AppGroupItem",
|
name: "GroupItem",
|
||||||
components: {Checkbox},
|
components: {Tooltip, Checkbox},
|
||||||
props: {
|
props: {
|
||||||
groupObj: {
|
groupObj: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -35,17 +38,17 @@ export default {
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.app-group-item {
|
.group-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 0.8rem;
|
gap: 0.8rem;
|
||||||
padding: 1.6rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-group-item-name {
|
.group-item-name {
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
|
|
||||||
<div class="table-search-bar-container">
|
<div v-if="searchbar" class="table-search-bar-container">
|
||||||
|
|
||||||
<search-bar class="search-bar" v-model="filter" @input-changed="reload"></search-bar>
|
<search-bar class="search-bar" v-model="filter" @input-changed="reload"></search-bar>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -37,8 +37,10 @@
|
||||||
<td v-for="column in columns" :key="column.key" class="table-cell" :class="getAlignment(column.align)">
|
<td v-for="column in columns" :key="column.key" class="table-cell" :class="getAlignment(column.align)">
|
||||||
|
|
||||||
<flag v-if="column.showFlag" :tooltip-text="getCellValue(item, column)" :iso="getCellValue(item, column)"></flag>
|
<flag v-if="column.showFlag" :tooltip-text="getCellValue(item, column)" :iso="getCellValue(item, column)"></flag>
|
||||||
|
<div class="badge-container" v-else-if="column.badgeResolver != null && typeof column.badgeResolver === 'function'">
|
||||||
|
<basic-badge v-for="badge in getCellValue(item, column)" :variant="badge.variant">{{ badge.text }}</basic-badge>
|
||||||
|
</div>
|
||||||
<span v-else-if="column.iconResolver == null">{{ getCellValue(item, column) }}</span>
|
<span v-else-if="column.iconResolver == null">{{ getCellValue(item, column) }}</span>
|
||||||
<span v-else-if="column.badgeResolver">{{ getCellValue(item, column) }}</span>
|
|
||||||
<component v-else
|
<component v-else
|
||||||
:is="getCellValue(item, column)"
|
:is="getCellValue(item, column)"
|
||||||
weight="regular"
|
weight="regular"
|
||||||
|
|
@ -69,10 +71,12 @@ import SearchBar from "@/components/UI/SearchBar.vue";
|
||||||
import Box from "@/components/UI/Box.vue";
|
import Box from "@/components/UI/Box.vue";
|
||||||
import Pagination from "@/components/UI/Pagination.vue";
|
import Pagination from "@/components/UI/Pagination.vue";
|
||||||
import Flag from "@/components/UI/Flag.vue";
|
import Flag from "@/components/UI/Flag.vue";
|
||||||
|
import basicBadge from "@/components/UI/BasicBadge.vue";
|
||||||
|
import BasicBadge from "@/components/UI/BasicBadge.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "TableView",
|
name: "TableView",
|
||||||
components: {Flag, Pagination, Box, SearchBar, BasicButton, Checkbox, Spinner},
|
components: {BasicBadge, Flag, Pagination, Box, SearchBar, BasicButton, Checkbox, Spinner},
|
||||||
emits: ['row-click'],
|
emits: ['row-click'],
|
||||||
props: {
|
props: {
|
||||||
dataSource: {
|
dataSource: {
|
||||||
|
|
@ -84,6 +88,10 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
|
|
||||||
},
|
},
|
||||||
|
searchbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
columns: {
|
columns: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -111,7 +119,11 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.reload();
|
this.reload();
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {
|
||||||
|
basicBadge() {
|
||||||
|
return basicBadge
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async updatePage(page) {
|
async updatePage(page) {
|
||||||
this.reload(this.filter, page);
|
this.reload(this.filter, page);
|
||||||
|
|
@ -145,6 +157,10 @@ export default {
|
||||||
return column.iconResolver(rawValue, item);
|
return column.iconResolver(rawValue, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (column.badgeResolver && typeof column.badgeResolver === 'function') {
|
||||||
|
return column.badgeResolver(rawValue, item);
|
||||||
|
}
|
||||||
|
|
||||||
if (column.iconResolver) {
|
if (column.iconResolver) {
|
||||||
return 'PhImageBroken';
|
return 'PhImageBroken';
|
||||||
}
|
}
|
||||||
|
|
@ -178,6 +194,11 @@ export default {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.table-icon {
|
.table-icon {
|
||||||
transition: all 0.1s ease-in-out;
|
transition: all 0.1s ease-in-out;
|
||||||
color: #6B869C;
|
color: #6B869C;
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
<div class="add-app">
|
<div class="add-app">
|
||||||
<div>App name</div>
|
<div>App name</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-container"><input class="input-field" v-model="appName" @input="checkChange"/></div>
|
<div class="text-container"><input class="input-field" v-model="appName" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="add-app-group-header">App groups</div>
|
<div class="add-app-group-header">App groups</div>
|
||||||
<div>
|
<div class="groups-container">
|
||||||
<app-group-item v-for="group in groups" :group-obj="group" :key="group.group_name"
|
<group-item v-for="group in groups" :group-obj="group" :key="group.group_name"
|
||||||
:selected="isSelected(group.group_name)"
|
:selected="isSelected(group.group_name)"
|
||||||
@checkbox-changed="updateSelected"></app-group-item>
|
@checkbox-changed="updateSelected"></group-item>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -49,14 +49,14 @@ import {mapStores} from "pinia";
|
||||||
import {useGroupStore} from "@/store/group.js";
|
import {useGroupStore} from "@/store/group.js";
|
||||||
import Checkbox from "@/components/UI/Checkbox.vue";
|
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
import AppGroupItem from "@/components/UI/AppGroupItem.vue";
|
import GroupItem from "@/components/UI/GroupItem.vue";
|
||||||
import {useAppsStore} from "@/store/apps.js";
|
import {useAppsStore} from "@/store/apps.js";
|
||||||
import checkbox from "@/components/UI/Checkbox.vue";
|
import checkbox from "@/components/UI/Checkbox.vue";
|
||||||
import Box from "@/components/UI/Box.vue";
|
import Box from "@/components/UI/Box.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AddApp",
|
name: "AddApp",
|
||||||
components: {Box, AppGroupItem, BasicButton, Checkbox, InputField},
|
components: {Box, GroupItem, BasicButton, Checkbox, InputField},
|
||||||
computed: {
|
computed: {
|
||||||
checkbox() {
|
checkbox() {
|
||||||
return checkbox
|
return checkbox
|
||||||
|
|
@ -117,10 +117,19 @@ export default {
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.groups-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||||
|
gap: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.stage-container {
|
.stage-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 20rem;
|
min-height: 20rem;
|
||||||
margin: 1.6rem;
|
margin: 1.6rem;
|
||||||
|
min-width: 70rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Transition animations */
|
/* Transition animations */
|
||||||
|
|
@ -144,8 +153,8 @@ export default {
|
||||||
|
|
||||||
.add-app {
|
.add-app {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: auto 1fr;
|
||||||
grid-gap: 10px;
|
grid-gap: 1.6rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -153,7 +162,7 @@ export default {
|
||||||
|
|
||||||
.add-app-group-header {
|
.add-app-group-header {
|
||||||
align-self: start;
|
align-self: start;
|
||||||
padding-top: 2.4rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-container {
|
.text-container {
|
||||||
|
|
@ -24,7 +24,7 @@ import AppListItem from "@/components/UI/AppListItem.vue";
|
||||||
import {mapStores} from "pinia";
|
import {mapStores} from "pinia";
|
||||||
import {useAppsStore} from "@/store/apps.js";
|
import {useAppsStore} from "@/store/apps.js";
|
||||||
import Modal from "@/components/UI/Modal.vue";
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
import AddApp from "@/components/UI/AddApp.vue";
|
import AddApp from "@/components/layout/config/AddApp.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Apps",
|
name: "Apps",
|
||||||
|
|
|
||||||
193
src/frontend/src/components/layout/config/EditUser.vue
Normal file
193
src/frontend/src/components/layout/config/EditUser.vue
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
<template>
|
||||||
|
<div class="edit-user-container">
|
||||||
|
<div class="edit-user">
|
||||||
|
<div>Workday ID</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-container" :class="{disabled: !isNewUser}"><input :disabled="!isNewUser" class="input-field"
|
||||||
|
v-model="user.workday_id"/></div>
|
||||||
|
</div>
|
||||||
|
<div>Firstname</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-container"><input class="input-field" v-model="user.firstname"/></div>
|
||||||
|
</div>
|
||||||
|
<div>Lastname</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-container"><input class="input-field" v-model="user.lastname"/></div>
|
||||||
|
</div>
|
||||||
|
<div>E-Mail</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-container"><input class="input-field" v-model="user.mail"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="group-header">Groups</div>
|
||||||
|
<div class="groups-container">
|
||||||
|
<group-item v-for="group in groups" :group-obj="group" :key="group.group_name"
|
||||||
|
:selected="isSelected(group.group_name)"
|
||||||
|
@checkbox-changed="updateSelected"></group-item>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="add-app-footer">
|
||||||
|
<basic-button :show-icon="false" @click="closeModal(true)" :disabled="disableButton"> {{
|
||||||
|
applyText
|
||||||
|
}}
|
||||||
|
</basic-button>
|
||||||
|
<basic-button :show-icon="false" @click="closeModal(false)" variant="secondary">Cancel</basic-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
|
import GroupItem from "@/components/UI/GroupItem.vue";
|
||||||
|
import {mapStores} from "pinia";
|
||||||
|
import {useGroupStore} from "@/store/group.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "EditUser",
|
||||||
|
components: {GroupItem, BasicButton},
|
||||||
|
props: {
|
||||||
|
user: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isNewUser: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
groups: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeModal(save) {
|
||||||
|
this.$emit("close", save);
|
||||||
|
},
|
||||||
|
isSelected(groupName) {
|
||||||
|
if (groupName === null || this.user?.groups == null || Object.keys(this.groups).length === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this.user.groups.includes(groupName);
|
||||||
|
},
|
||||||
|
updateSelected(checked, groupName) {
|
||||||
|
|
||||||
|
|
||||||
|
const idx = this.user.groups?.indexOf(groupName);
|
||||||
|
console.log("update selected", idx, checked, groupName, this.user.groups);
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
if ((idx ?? null) !== null && idx === -1)
|
||||||
|
this.user.groups.push(groupName);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if ((idx ?? null) !== null && idx !== -1)
|
||||||
|
this.user.groups.splice(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useGroupStore),
|
||||||
|
applyText() {
|
||||||
|
return this.isNewUser ? "Create user" : "Update user";
|
||||||
|
},
|
||||||
|
mailValid() {
|
||||||
|
return String(this.user.mail)
|
||||||
|
.toLowerCase()
|
||||||
|
.match(
|
||||||
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
);
|
||||||
|
},
|
||||||
|
disableButton() {
|
||||||
|
return !this.mailValid ||
|
||||||
|
!(this.user.groups.length !== 0
|
||||||
|
&& this.user.firstname?.length !== 0
|
||||||
|
&& this.user.lastname?.length !== 0
|
||||||
|
&& this.user.mail?.length !== 0
|
||||||
|
&& this.user.workday_id?.length !== 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
await this.groupStore.loadGroups()
|
||||||
|
this.groups = this.groupStore.groups
|
||||||
|
this.groups?.forEach(group => {
|
||||||
|
group.selected = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.text-container.disabled {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
border-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container.disabled input {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.groups-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||||
|
gap: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-user-container {
|
||||||
|
min-width: 100rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-user {
|
||||||
|
padding: 1.6rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
grid-gap: 1.6rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
align-self: start;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
/* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);*/
|
||||||
|
border: 0.2rem solid #E3EDFF;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #002F54;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-app-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
148
src/frontend/src/components/layout/config/Users.vue
Normal file
148
src/frontend/src/components/layout/config/Users.vue
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="user-list">
|
||||||
|
<table-view ref="tableViewRef" :searchbar="false" :columns="columns" :data-source="fetch" @row-click="selectUser"
|
||||||
|
:mouse-over="true"></table-view>
|
||||||
|
</div>
|
||||||
|
<modal :state="showModal">
|
||||||
|
<edit-user @close="closeModal" v-model:user="selectedUser" :is-new-user="isNewUser"></edit-user>
|
||||||
|
</modal>
|
||||||
|
<basic-button icon="Plus" @click="createUser">New User</basic-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {mapStores} from "pinia";
|
||||||
|
import {useUsersStore} from "@/store/users.js";
|
||||||
|
import TableView from "@/components/UI/TableView.vue";
|
||||||
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
|
import AddApp from "@/components/layout/config/AddApp.vue";
|
||||||
|
import EditUser from "@/components/layout/config/EditUser.vue";
|
||||||
|
import logger from "@/logger.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Users",
|
||||||
|
props: {
|
||||||
|
isSelected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {EditUser, AddApp, Modal, BasicButton, TableView},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useUsersStore)
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
async isSelected(newVal) {
|
||||||
|
if (newVal === true) {
|
||||||
|
await this.usersStore.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.usersStore.load();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetch(query) {
|
||||||
|
await this.usersStore.setQuery(query);
|
||||||
|
this.pagination = this.usersStore.pagination;
|
||||||
|
return this.usersStore.users;
|
||||||
|
},
|
||||||
|
selectUser(user) {
|
||||||
|
this.isNewUser = false;
|
||||||
|
|
||||||
|
const groups = [...user.groups];
|
||||||
|
|
||||||
|
this.selectedUser = {
|
||||||
|
firstname: user.firstname,
|
||||||
|
lastname: user.lastname,
|
||||||
|
mail: user.mail,
|
||||||
|
workday_id: user.workday_id,
|
||||||
|
groups: groups
|
||||||
|
}
|
||||||
|
this.showModal = true;
|
||||||
|
},
|
||||||
|
createUser() {
|
||||||
|
|
||||||
|
this.isNewUser = true;
|
||||||
|
|
||||||
|
this.selectedUser = {
|
||||||
|
firstname: null,
|
||||||
|
lastname: null,
|
||||||
|
mail: null,
|
||||||
|
workday_id: null,
|
||||||
|
groups: []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showModal = true;
|
||||||
|
},
|
||||||
|
async closeModal(save) {
|
||||||
|
this.showModal = false;
|
||||||
|
if (save) {
|
||||||
|
await this.usersStore.updateUser(this.selectedUser);
|
||||||
|
await this.usersStore.load();
|
||||||
|
this.selectedUser = null;
|
||||||
|
this.$refs.tableViewRef.reload('', 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isNewUser: false,
|
||||||
|
showModal: false,
|
||||||
|
selectedUser: null,
|
||||||
|
users: null,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'workday_id',
|
||||||
|
label: 'Workday ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'firstname',
|
||||||
|
label: 'First name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastname',
|
||||||
|
label: 'Last name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'mail',
|
||||||
|
label: 'E-Mail',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'groups',
|
||||||
|
label: 'User groups',
|
||||||
|
badgeResolver: (value) => {
|
||||||
|
|
||||||
|
const formattedValues = []
|
||||||
|
value.slice(0,5).forEach(v => formattedValues.push({text: v, variant: "secondary"}));
|
||||||
|
|
||||||
|
if(value.length > 5)
|
||||||
|
formattedValues.push({text: "...", variant: "secondary"});
|
||||||
|
|
||||||
|
return formattedValues;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
pageCount: 1,
|
||||||
|
totalCount: 0
|
||||||
|
},
|
||||||
|
pageSize: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-list {
|
||||||
|
margin-bottom: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -28,6 +28,7 @@ import ErrorLog from "@/pages/ErrorLog.vue";
|
||||||
import {mapStores} from "pinia";
|
import {mapStores} from "pinia";
|
||||||
import {useActiveUserStore} from "@/store/activeuser.js";
|
import {useActiveUserStore} from "@/store/activeuser.js";
|
||||||
import Apps from "@/components/layout/config/Apps.vue";
|
import Apps from "@/components/layout/config/Apps.vue";
|
||||||
|
import Users from "@/components/layout/config/Users.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Config",
|
name: "Config",
|
||||||
|
|
@ -45,6 +46,11 @@ export default {
|
||||||
component: markRaw(Apps),
|
component: markRaw(Apps),
|
||||||
props: {isSelected: false},
|
props: {isSelected: false},
|
||||||
},
|
},
|
||||||
|
usersTab: {
|
||||||
|
title: 'Users',
|
||||||
|
component: markRaw(Users),
|
||||||
|
props: {isSelected: false},
|
||||||
|
},
|
||||||
systemLogTab: {
|
systemLogTab: {
|
||||||
title: 'System log',
|
title: 'System log',
|
||||||
component: markRaw(ErrorLog),
|
component: markRaw(ErrorLog),
|
||||||
|
|
@ -86,6 +92,10 @@ export default {
|
||||||
tabs.push(this.appsTab);
|
tabs.push(this.appsTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.activeUserStore.isRightManagement) {
|
||||||
|
tabs.push(this.usersTab);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.activeUserStore.isSuper || this.activeUserStore.isMaterial) {
|
if (this.activeUserStore.isSuper || this.activeUserStore.isMaterial) {
|
||||||
tabs.push(this.materialsTab);
|
tabs.push(this.materialsTab);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const router = createRouter({
|
||||||
name: 'edit',
|
name: 'edit',
|
||||||
beforeEnter: async (to, from) => {
|
beforeEnter: async (to, from) => {
|
||||||
const userStore = useActiveUserStore();
|
const userStore = useActiveUserStore();
|
||||||
await userStore.loadIfRequired();
|
await userStore.load();
|
||||||
|
|
||||||
if (userStore.allowCalculation) {
|
if (userStore.allowCalculation) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -72,7 +72,7 @@ const router = createRouter({
|
||||||
name: 'bulk',
|
name: 'bulk',
|
||||||
beforeEnter: async (to, from) => {
|
beforeEnter: async (to, from) => {
|
||||||
const userStore = useActiveUserStore();
|
const userStore = useActiveUserStore();
|
||||||
await userStore.loadIfRequired();
|
await userStore.load();
|
||||||
|
|
||||||
if (userStore.allowCalculation) {
|
if (userStore.allowCalculation) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -86,7 +86,7 @@ const router = createRouter({
|
||||||
name: 'bulk-single-edit',
|
name: 'bulk-single-edit',
|
||||||
beforeEnter: async (to, from) => {
|
beforeEnter: async (to, from) => {
|
||||||
const userStore = useActiveUserStore();
|
const userStore = useActiveUserStore();
|
||||||
await userStore.loadIfRequired();
|
await userStore.load();
|
||||||
|
|
||||||
if (userStore.allowCalculation) {
|
if (userStore.allowCalculation) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -99,7 +99,7 @@ const router = createRouter({
|
||||||
component: Reporting,
|
component: Reporting,
|
||||||
beforeEnter: async (to, from) => {
|
beforeEnter: async (to, from) => {
|
||||||
const userStore = useActiveUserStore();
|
const userStore = useActiveUserStore();
|
||||||
await userStore.loadIfRequired();
|
await userStore.load();
|
||||||
|
|
||||||
if (userStore.allowReporting) {
|
if (userStore.allowReporting) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -112,7 +112,7 @@ const router = createRouter({
|
||||||
component: Config,
|
component: Config,
|
||||||
beforeEnter: async (to, from) => {
|
beforeEnter: async (to, from) => {
|
||||||
const userStore = useActiveUserStore();
|
const userStore = useActiveUserStore();
|
||||||
await userStore.loadIfRequired();
|
await userStore.load();
|
||||||
|
|
||||||
if (userStore.allowConfiguration) {
|
if (userStore.allowConfiguration) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ export const useActiveUserStore = defineStore('activeUser', {
|
||||||
return false;
|
return false;
|
||||||
return state.user.groups?.includes("service");
|
return state.user.groups?.includes("service");
|
||||||
},
|
},
|
||||||
|
isRightManagement(state) {
|
||||||
|
if (state.user === null)
|
||||||
|
return false;
|
||||||
|
return state.user.groups?.includes("right-management");
|
||||||
|
},
|
||||||
isPackaging(state) {
|
isPackaging(state) {
|
||||||
if (state.user === null)
|
if (state.user === null)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,18 @@ export const useNotificationStore = defineStore('notification', {
|
||||||
duration: notification.duration ?? 8000
|
duration: notification.duration ?? 8000
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async addError(errorDto, options = {}) {
|
async addError(errorDto, options = {}, silent = false) {
|
||||||
const {request = null, store = null, global = false} = options;
|
const {request = null, store = null, global = false} = options;
|
||||||
const state = this.captureStoreState(store, global);
|
const state = this.captureStoreState(store, global);
|
||||||
|
|
||||||
this.addNotification({
|
if (!silent) {
|
||||||
icon: 'bug',
|
this.addNotification({
|
||||||
message: errorDto.message ?? 'Unknown error code',
|
icon: 'bug',
|
||||||
title: errorDto.title ?? 'Unknown error',
|
message: errorDto.message ?? 'Unknown error code',
|
||||||
variant: 'exception',
|
title: errorDto.title ?? 'Unknown error',
|
||||||
});
|
variant: 'exception',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const error = {
|
const error = {
|
||||||
error: {
|
error: {
|
||||||
|
|
@ -158,17 +160,18 @@ export function setupErrorBuffer() {
|
||||||
const errorStore = useNotificationStore()
|
const errorStore = useNotificationStore()
|
||||||
|
|
||||||
//Unhandled Promise Rejections
|
//Unhandled Promise Rejections
|
||||||
// window.addEventListener('unhandledrejection', (event) => {
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
//
|
|
||||||
// const error = {
|
const error = {
|
||||||
// code: "Unhandled rejection",
|
code: "Unhandled rejection",
|
||||||
// title: "Frontend error",
|
title: "Frontend error",
|
||||||
// message: event.reason?.message || 'Unhandled Promise Rejection',
|
message: event.reason?.message || 'Unhandled Promise Rejection',
|
||||||
// traceCombined: event.reason?.stack,
|
traceCombined: event.reason?.stack,
|
||||||
// };
|
};
|
||||||
//
|
|
||||||
// errorStore.addError(error, {global: true}).then(r => {} );
|
errorStore.addError(error, {global: true}, true).then(r => {
|
||||||
// })
|
});
|
||||||
|
})
|
||||||
|
|
||||||
// // JavaScript Errors
|
// // JavaScript Errors
|
||||||
window.addEventListener('error', (event) => {
|
window.addEventListener('error', (event) => {
|
||||||
|
|
@ -178,7 +181,8 @@ export function setupErrorBuffer() {
|
||||||
message: event.reason?.message || 'Unhandled Promise Rejection',
|
message: event.reason?.message || 'Unhandled Promise Rejection',
|
||||||
traceCombined: event.reason?.stack,
|
traceCombined: event.reason?.stack,
|
||||||
};
|
};
|
||||||
errorStore.addError(error, {global: true}).then(r => {} );
|
errorStore.addError(error, {global: true}).then(r => {
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('beforeunload', async () => {
|
window.addEventListener('beforeunload', async () => {
|
||||||
|
|
|
||||||
64
src/frontend/src/store/users.js
Normal file
64
src/frontend/src/store/users.js
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue