permission form
This commit is contained in:
parent
04e66dd918
commit
68efdc131b
8 changed files with 273 additions and 3 deletions
25
package-lock.json
generated
25
package-lock.json
generated
|
@ -13,6 +13,7 @@
|
||||||
"@heroicons/vue": "^1.0.6",
|
"@heroicons/vue": "^1.0.6",
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.26.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pdf-dist": "^1.0.0",
|
"pdf-dist": "^1.0.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
"@rushstack/eslint-patch": "^1.8.0",
|
"@rushstack/eslint-patch": "^1.8.0",
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/eslint": "~9.6.0",
|
"@types/eslint": "~9.6.0",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.9",
|
||||||
"@types/node": "^20.14.5",
|
"@types/node": "^20.14.5",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
|
@ -3062,6 +3064,23 @@
|
||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.17.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
|
||||||
|
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash.clonedeep": {
|
||||||
|
"version": "4.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz",
|
||||||
|
"integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.15",
|
"version": "20.14.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz",
|
||||||
|
@ -6593,6 +6612,12 @@
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
"@heroicons/vue": "^1.0.6",
|
"@heroicons/vue": "^1.0.6",
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.26.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pdf-dist": "^1.0.0",
|
"pdf-dist": "^1.0.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
"@rushstack/eslint-patch": "^1.8.0",
|
"@rushstack/eslint-patch": "^1.8.0",
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/eslint": "~9.6.0",
|
"@types/eslint": "~9.6.0",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.9",
|
||||||
"@types/node": "^20.14.5",
|
"@types/node": "^20.14.5",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
|
|
160
src/components/admin/user/Permission.vue
Normal file
160
src/components/admin/user/Permission.vue
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-2 max-w-2xl mx-auto w-full select-none">
|
||||||
|
<div class="flex flex-row gap-2 h-fit w-full border border-gray-300 rounded-md p-2">
|
||||||
|
<input type="checkbox" name="admin" id="admin" class="cursor-pointer" :checked="isAdmin" @change="toggleAdmin" />
|
||||||
|
<label for="admin" class="cursor-pointer">Administratorrecht</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="section in sections"
|
||||||
|
:key="section"
|
||||||
|
class="flex flex-col gap-2 h-fit w-full border border-primary rounded-md"
|
||||||
|
:class="isAdmin ? ' pointer-events-none opacity-60 bg-gray-100' : ''"
|
||||||
|
>
|
||||||
|
<div class="bg-primary p-2 text-white flex flex-row justify-between items-center">
|
||||||
|
<p>Abschnitt: {{ section }}</p>
|
||||||
|
<div class="flex flex-row border border-white rounded-md overflow-hidden">
|
||||||
|
<EyeIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'read', section) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('read', section)"
|
||||||
|
/>
|
||||||
|
<PlusIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'create', section) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('create', section)"
|
||||||
|
/>
|
||||||
|
<PencilIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'update', section) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('update', section)"
|
||||||
|
/>
|
||||||
|
<TrashIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'delete', section) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('delete', section)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="modul in permissionStructure[section]"
|
||||||
|
:key="modul"
|
||||||
|
class="p-1 px-2 flex flex-row justify-between items-center"
|
||||||
|
>
|
||||||
|
<p>Modul: {{ modul }}</p>
|
||||||
|
<div class="flex flex-row border border-gray-300 rounded-md overflow-hidden">
|
||||||
|
<EyeIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'read', section, modul) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('read', section, modul)"
|
||||||
|
/>
|
||||||
|
<PlusIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'create', section, modul) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('create', section, modul)"
|
||||||
|
/>
|
||||||
|
<PencilIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'update', section, modul) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('update', section, modul)"
|
||||||
|
/>
|
||||||
|
<TrashIcon
|
||||||
|
class="w-5 h-5 p-1 box-content cursor-pointer"
|
||||||
|
:class="_can(permissionUpdate, 'delete', section, modul) ? 'bg-success' : ''"
|
||||||
|
@click="togglePermission('delete', section, modul)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-2 self-end pt-4">
|
||||||
|
<button primary-outline class="!w-fit" @click="$emit('abortPermissions')">abbrechen</button>
|
||||||
|
<button primary class="!w-fit" @click="$emit('savePermissions', permissionUpdate)">speichern</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
import type {
|
||||||
|
PermissionModule,
|
||||||
|
PermissionObject,
|
||||||
|
PermissionSection,
|
||||||
|
PermissionType,
|
||||||
|
SectionsAndModulesObject,
|
||||||
|
} 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 { useAbilityStore } from "../../../stores/ability";
|
||||||
|
import _cloneDeep from "lodash.clonedeep";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
permissions: {
|
||||||
|
type: Object as PropType<PermissionObject>,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["savePermissions", "abortPermissions"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isAdmin: false,
|
||||||
|
sections: [] as Array<PermissionSection>,
|
||||||
|
permissionStructure: {} as SectionsAndModulesObject,
|
||||||
|
permissionUpdate: {} as PermissionObject,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(useAbilityStore, ["_can"]),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.sections = permissionSections;
|
||||||
|
this.permissionStructure = sectionsAndModules;
|
||||||
|
this.permissionUpdate = _cloneDeep(this.permissions);
|
||||||
|
|
||||||
|
this.isAdmin = this.permissions.admin ?? false;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleAdmin(e: Event) {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
this.isAdmin = target.checked ?? false;
|
||||||
|
this.permissionUpdate.admin = this.isAdmin;
|
||||||
|
},
|
||||||
|
togglePermission(type: PermissionType, section: PermissionSection, modul?: PermissionModule) {
|
||||||
|
let permissions = [] as Array<PermissionType> | "*";
|
||||||
|
if (!modul) {
|
||||||
|
permissions = this.permissionUpdate[section]?.all ?? [];
|
||||||
|
} else {
|
||||||
|
permissions = this.permissionUpdate[section]?.[modul] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissions == "*") {
|
||||||
|
permissions = permissionTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissions.includes(type)) {
|
||||||
|
let add = permissions.slice(-1)[0] == type ? 0 : 1;
|
||||||
|
let whatToRemove = permissionTypes.slice(permissionTypes.indexOf(type) + add);
|
||||||
|
permissions = permissions.filter((permission) => !whatToRemove.includes(permission));
|
||||||
|
} else {
|
||||||
|
let whatToAdd = permissionTypes.slice(0, permissionTypes.indexOf(type) + 1);
|
||||||
|
permissions = whatToAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modul) {
|
||||||
|
if (!this.permissionUpdate[section]) {
|
||||||
|
this.permissionUpdate[section] = {};
|
||||||
|
}
|
||||||
|
this.permissionUpdate[section].all = permissions;
|
||||||
|
} else {
|
||||||
|
if (!this.permissionUpdate[section]) {
|
||||||
|
this.permissionUpdate[section] = {};
|
||||||
|
}
|
||||||
|
this.permissionUpdate[section][modul] = permissions;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -15,7 +15,31 @@ export const useAbilityStore = defineStore("ability", {
|
||||||
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 && permissions[section] != undefined) ||
|
(!module &&
|
||||||
|
permissions[section] != undefined &&
|
||||||
|
(permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) ||
|
||||||
|
permissions[section]?.all == "*" ||
|
||||||
|
permissions[section]?.all?.includes(type)
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type)))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
_can:
|
||||||
|
() =>
|
||||||
|
(
|
||||||
|
permissions: PermissionObject,
|
||||||
|
type: PermissionType | "admin",
|
||||||
|
section: PermissionSection,
|
||||||
|
module?: PermissionModule
|
||||||
|
): boolean => {
|
||||||
|
if (type == "admin") return permissions.admin ?? false;
|
||||||
|
if (permissions.admin) return true;
|
||||||
|
if (
|
||||||
|
(!module &&
|
||||||
|
permissions[section] != undefined &&
|
||||||
|
(permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) ||
|
||||||
permissions[section]?.all == "*" ||
|
permissions[section]?.all == "*" ||
|
||||||
permissions[section]?.all?.includes(type)
|
permissions[section]?.all?.includes(type)
|
||||||
)
|
)
|
||||||
|
|
|
@ -205,7 +205,7 @@ export const useNavigationStore = defineStore("navigation", {
|
||||||
{
|
{
|
||||||
key: "#user",
|
key: "#user",
|
||||||
title: "Benutzer",
|
title: "Benutzer",
|
||||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/user/User.vue"))),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|
12
src/stores/admin/permission.ts
Normal file
12
src/stores/admin/permission.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const usePermissionStore = defineStore("permission", {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
sections: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
logoutAccount() {},
|
||||||
|
},
|
||||||
|
});
|
|
@ -24,7 +24,31 @@ export type PermissionString =
|
||||||
export type PermissionObject = {
|
export type PermissionObject = {
|
||||||
[section in PermissionSection]?: {
|
[section in PermissionSection]?: {
|
||||||
[module in PermissionModule]?: Array<PermissionType> | "*";
|
[module in PermissionModule]?: Array<PermissionType> | "*";
|
||||||
} & { all?: PermissionType | "*" };
|
} & { all?: Array<PermissionType> | "*" };
|
||||||
} & {
|
} & {
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SectionsAndModulesObject = {
|
||||||
|
[section in PermissionSection]: Array<PermissionModule>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const permissionSections: Array<PermissionSection> = ["club", "settings", "user"];
|
||||||
|
export const permissionModules: Array<PermissionModule> = [
|
||||||
|
"members",
|
||||||
|
"calendar",
|
||||||
|
"newsletter",
|
||||||
|
"protocoll",
|
||||||
|
"qualification",
|
||||||
|
"award",
|
||||||
|
"executive_position",
|
||||||
|
"communication",
|
||||||
|
"user",
|
||||||
|
"role",
|
||||||
|
];
|
||||||
|
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
|
||||||
|
export const sectionsAndModules: SectionsAndModulesObject = {
|
||||||
|
club: ["members", "calendar", "newsletter", "protocoll"],
|
||||||
|
settings: ["qualification", "award", "executive_position", "communication"],
|
||||||
|
user: ["user", "role"],
|
||||||
|
};
|
||||||
|
|
23
src/views/admin/user/User.vue
Normal file
23
src/views/admin/user/User.vue
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<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">Benutzer</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #main>
|
||||||
|
<Permission />
|
||||||
|
</template>
|
||||||
|
</MainTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { mapState } from "pinia";
|
||||||
|
import MainTemplate from "@/templates/Main.vue";
|
||||||
|
import Permission from "@/components/admin/user/Permission.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default defineComponent({});
|
||||||
|
</script>
|
Loading…
Reference in a new issue