feat: implement role-based access control (RBAC) management system with Keycloak integration and dynamic page permission configuration

This commit is contained in:
Fanrouver
2026-07-14 09:41:55 +07:00
parent a5d7338e58
commit 49e0ee2bc4
14 changed files with 737 additions and 163 deletions

No files matched your search

+27 -21
View File
@@ -129,6 +129,7 @@
prepend-icon="mdi-shield-edit-outline"
class="text-capitalize rounded-lg mr-2"
@click="editPermissions(item)"
v-permission="'canEdit'"
>
Atur Akses
</v-btn>
@@ -165,8 +166,8 @@
<div class="pa-6 pt-0">
<EditHakAkses
v-if="editedEntity"
:pages="editedEntity.pages"
@update:pages="editedEntity.pages = $event"
:hakAksesMenu="editedEntity.hakAksesMenu || []"
@update:hakAksesMenu="editedEntity.hakAksesMenu = $event"
/>
</div>
</v-card-text>
@@ -240,7 +241,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import EditHakAkses from '@/components/HakAkses/EditHakAkses.vue';
import { useNavItemsStore } from '~/stores/navItems1';
import { useNavItemsStore, defaultNavItems } from '~/stores/navItems1';
import type { HakAkses } from '~/types/setting';
definePageMeta({
@@ -321,6 +322,8 @@ const loadData = async () => {
}
};
// Computed list based on active tab
const displayItems = computed(() => {
let list = [];
@@ -328,15 +331,15 @@ const displayItems = computed(() => {
else if (activeTab.value === 'groups') list = keycloakEntities.value.groups;
else list = keycloakEntities.value.users;
// Get all valid paths from current navigation store
const allNavPaths = new Set<string>();
const extractPaths = (items: any[]) => {
// Get all valid menuKeys from current navigation store
const allNavKeys = new Set<string>();
const extractKeys = (items: any[]) => {
items.forEach((item: any) => {
if (item.path) allNavPaths.add(item.path);
if (item.children) extractPaths(item.children);
if (item.menuKey) allNavKeys.add(item.menuKey);
if (item.children) extractKeys(item.children);
});
};
extractPaths(navItemsStore.getNavItems);
extractKeys(defaultNavItems);
return list.map((entity: any) => {
// Find existing permission mapping
@@ -344,18 +347,15 @@ const displayItems = computed(() => {
const lookupKey = entity.type === 'user' ? entity.username : entity.name;
const mapping = hakAksesList.value.find(h => h.namaHakAkses === lookupKey);
if (!mapping) return { ...entity, pages: [], validPagesCount: 0, status: 'tidak aktif', id_mapping: null };
if (!mapping) return { ...entity, hakAksesMenu: [], validPagesCount: 0, status: 'tidak aktif', id_mapping: null };
// Count only valid pages that exist in navigation
const validPages = (mapping.pages || []).filter(p => {
const path = typeof p === 'string' ? p : (p as any)?.path;
return path && allNavPaths.has(path);
});
// Count only valid pages that exist in navigation and have canAccess
const validMenus = (mapping.hakAksesMenu || []).filter((m: any) => m.menuKey && allNavKeys.has(m.menuKey) && m.canAccess);
return {
...entity,
pages: mapping.pages, // Keep original for editing
validPagesCount: validPages.length,
hakAksesMenu: mapping.hakAksesMenu, // Keep original for editing
validPagesCount: validMenus.length,
status: mapping.status,
id_mapping: mapping.id
};
@@ -401,10 +401,14 @@ const savePermissions = async () => {
loading.value = true;
try {
const payload = {
id: editedEntity.value.id_mapping, // If null, backend create new
namaHakAkses: editedEntity.value.name,
id: editedEntity.value.id_mapping || undefined, // Zod optional() expects undefined, not null
namaHakAkses: editedEntity.value.type === 'user' ? editedEntity.value.username : editedEntity.value.name,
role: editedEntity.value.type === 'role' ? editedEntity.value.name : undefined,
group: editedEntity.value.type === 'group' ? editedEntity.value.name : undefined,
namaTipeUser: editedEntity.value.type === 'user' ? editedEntity.value.username : undefined,
isGroupBased: editedEntity.value.type === 'group',
status: 'aktif',
pages: editedEntity.value.pages
hakAksesMenu: editedEntity.value.hakAksesMenu || []
};
const response = await $fetch<{ success: boolean, message: string }>('/api/hak-akses', {
@@ -417,10 +421,12 @@ const savePermissions = async () => {
await loadData();
await navItemsStore.refreshNavItems();
showEditDialog.value = false;
} else {
showSnackbar(response.message || 'Gagal menyimpan perubahan', 'error');
}
} catch (error) {
console.error('Error saving:', error);
showSnackbar('Gagal menyimpan perubahan', 'error');
showSnackbar('Terjadi kesalahan pada server saat menyimpan perubahan', 'error');
} finally {
loading.value = false;
}