permission form

This commit is contained in:
Julian Krauser 2024-08-27 11:46:24 +02:00
parent 04e66dd918
commit 68efdc131b
8 changed files with 273 additions and 3 deletions

View 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>

View file

@ -15,7 +15,31 @@ export const useAbilityStore = defineStore("ability", {
if (type == "admin") return permissions.admin ?? false;
if (permissions.admin) return true;
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?.includes(type)
)

View file

@ -205,7 +205,7 @@ export const useNavigationStore = defineStore("navigation", {
{
key: "#user",
title: "Benutzer",
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/user/User.vue"))),
},
]
: []),

View file

@ -0,0 +1,12 @@
import { defineStore } from "pinia";
export const usePermissionStore = defineStore("permission", {
state: () => {
return {
sections: [],
};
},
actions: {
logoutAccount() {},
},
});

View file

@ -24,7 +24,31 @@ export type PermissionString =
export type PermissionObject = {
[section in PermissionSection]?: {
[module in PermissionModule]?: Array<PermissionType> | "*";
} & { all?: PermissionType | "*" };
} & { all?: Array<PermissionType> | "*" };
} & {
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"],
};

View 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>