- Updated README.md to reflect the new project name and provide an overview of the Role-Based Access Control (RBAC) system. - Added new components for RBAC management, including: - PermissionExample.vue: Demonstrates permission-based navigation. - GroupCard.vue: Displays group information and assigned roles. - PermissionMatrix.vue: Visual representation of permissions across roles and resources. - RoleTemplates.vue: Quick role templates for applying pre-configured permissions. - StatsCards.vue: Displays statistics related to users, groups, and roles. - Introduced useRbacPermissions.js for managing permission checks. - Created docker-compose.yml for PostgreSQL and Redis services. - Developed comprehensive documentation for application management and Authentik integration. - Added multiple pages for managing applications, groups, roles, and users, including bulk operations and templates. - Updated navigation structure to include new RBAC management paths.
313 lines
9.5 KiB
Vue
313 lines
9.5 KiB
Vue
<script setup>
|
|
definePageMeta({
|
|
title: "Roles",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{ name: "Dashboard", path: "/dashboard" },
|
|
{ name: "Roles", path: "/roles", type: "current" }
|
|
]
|
|
});
|
|
|
|
import { ref, onMounted, computed } from 'vue'
|
|
|
|
// Sample roles data
|
|
const roles = ref([
|
|
{
|
|
id: 1,
|
|
name: 'Administrator',
|
|
description: 'Full system access with all permissions',
|
|
application: 'corradAF',
|
|
usersCount: 3,
|
|
permissions: 45,
|
|
isActive: true,
|
|
isGlobal: true,
|
|
priority: 100,
|
|
created: '2024-01-10'
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Manager',
|
|
description: 'Department management and approval permissions',
|
|
application: 'corradAF',
|
|
usersCount: 8,
|
|
permissions: 28,
|
|
isActive: true,
|
|
isGlobal: false,
|
|
priority: 75,
|
|
created: '2024-01-12'
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'Editor',
|
|
description: 'Content creation and editing capabilities',
|
|
application: 'corradAF',
|
|
usersCount: 15,
|
|
permissions: 18,
|
|
isActive: true,
|
|
isGlobal: false,
|
|
priority: 50,
|
|
created: '2024-01-15'
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'Viewer',
|
|
description: 'Read-only access to application features',
|
|
application: 'corradAF',
|
|
usersCount: 25,
|
|
permissions: 8,
|
|
isActive: true,
|
|
isGlobal: false,
|
|
priority: 25,
|
|
created: '2024-01-18'
|
|
},
|
|
{
|
|
id: 5,
|
|
name: 'HR Staff',
|
|
description: 'Human resources management access',
|
|
application: 'HR System',
|
|
usersCount: 5,
|
|
permissions: 12,
|
|
isActive: false,
|
|
isGlobal: false,
|
|
priority: 60,
|
|
created: '2024-01-20'
|
|
}
|
|
])
|
|
|
|
const isLoading = ref(false)
|
|
const searchQuery = ref('')
|
|
|
|
// Computed
|
|
const filteredRoles = computed(() => {
|
|
if (!searchQuery.value) return roles.value
|
|
|
|
return roles.value.filter(role =>
|
|
role.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
|
role.description.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
|
role.application.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
)
|
|
})
|
|
|
|
const activeRolesCount = computed(() =>
|
|
roles.value.filter(role => role.isActive).length
|
|
)
|
|
|
|
const globalRolesCount = computed(() =>
|
|
roles.value.filter(role => role.isGlobal).length
|
|
)
|
|
|
|
const totalPermissions = computed(() =>
|
|
roles.value.reduce((sum, role) => sum + role.permissions, 0)
|
|
)
|
|
|
|
// Methods
|
|
const refreshRoles = async () => {
|
|
isLoading.value = true
|
|
try {
|
|
// API call would go here
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const viewRole = (roleId) => {
|
|
// Navigate to role details
|
|
console.log('View role:', roleId)
|
|
}
|
|
|
|
const editRole = (roleId) => {
|
|
// Navigate to edit role
|
|
console.log('Edit role:', roleId)
|
|
}
|
|
|
|
const deleteRole = (roleId) => {
|
|
// Handle role deletion
|
|
console.log('Delete role:', roleId)
|
|
}
|
|
|
|
const getPriorityBadgeVariant = (priority) => {
|
|
if (priority >= 80) return 'danger'
|
|
if (priority >= 60) return 'warning'
|
|
if (priority >= 40) return 'info'
|
|
return 'secondary'
|
|
}
|
|
|
|
// Initialize
|
|
onMounted(() => {
|
|
// Load roles data
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<LayoutsBreadcrumb />
|
|
|
|
<!-- Header -->
|
|
<div class="mb-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Roles</h1>
|
|
<p class="text-gray-600 dark:text-gray-400">Manage roles and permissions across applications</p>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<rs-button @click="refreshRoles" :disabled="isLoading" variant="primary-outline">
|
|
<Icon name="ph:arrows-clockwise" class="w-4 h-4 mr-2" />
|
|
{{ isLoading ? 'Loading...' : 'Refresh' }}
|
|
</rs-button>
|
|
<rs-button @click="navigateTo('/roles/create')">
|
|
<Icon name="ph:shield-plus" class="w-4 h-4 mr-2" />
|
|
Add Role
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:shield-check" class="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Roles</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ roles.length }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-green-100 dark:bg-green-900 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:check-circle" class="w-5 h-5 text-green-600 dark:text-green-400" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Active Roles</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ activeRolesCount }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-purple-100 dark:bg-purple-900 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:globe" class="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Global Roles</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ globalRolesCount }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-yellow-100 dark:bg-yellow-900 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:key" class="w-5 h-5 text-yellow-600 dark:text-yellow-400" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Permissions</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ totalPermissions }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Roles Table -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">All Roles</h3>
|
|
<rs-badge variant="info">{{ roles.length }} roles</rs-badge>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<RsTable
|
|
:field="['role', 'application', 'status', 'users', 'actions']"
|
|
:data="roles"
|
|
:advanced="true"
|
|
:options="{
|
|
variant: 'default',
|
|
striped: false,
|
|
bordered: false,
|
|
hover: true
|
|
}"
|
|
:optionsAdvanced="{
|
|
sortable: true,
|
|
outsideBorder: false
|
|
}"
|
|
:pageSize="10"
|
|
>
|
|
<!-- Role Column -->
|
|
<template #role="{ value }">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0 h-10 w-10">
|
|
<div class="h-10 w-10 rounded-full bg-primary flex items-center justify-center">
|
|
<span class="text-sm font-medium text-white">{{ value.name[0] }}{{ value.name.split(' ')[1]?.[0] || value.name[1] || '' }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">{{ value.name }}</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">{{ value.description }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Application Column -->
|
|
<template #application="{ value }">
|
|
<span class="text-sm text-gray-900 dark:text-white">{{ value.application }}</span>
|
|
</template>
|
|
|
|
<!-- Status Column -->
|
|
<template #status="{ value }">
|
|
<rs-badge :variant="value.isActive ? 'success' : 'secondary'">
|
|
{{ value.isActive ? 'Active' : 'Inactive' }}
|
|
</rs-badge>
|
|
</template>
|
|
|
|
<!-- Users Column -->
|
|
<template #users="{ value }">
|
|
<span class="text-sm text-gray-500 dark:text-gray-400">{{ value.usersCount }}</span>
|
|
</template>
|
|
|
|
<!-- Actions Column -->
|
|
<template #actions="{ value }">
|
|
<div class="flex items-center justify-end space-x-2">
|
|
<button @click="viewRole(value.id)" class="text-primary hover:text-primary/80">
|
|
<Icon name="ph:eye" class="w-4 h-4" />
|
|
</button>
|
|
<button @click="editRole(value.id)" class="text-primary hover:text-primary/80">
|
|
<Icon name="ph:pencil" class="w-4 h-4" />
|
|
</button>
|
|
<button @click="deleteRole(value.id)" class="text-red-600 hover:text-red-800">
|
|
<Icon name="ph:trash" class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</RsTable>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Custom styles */
|
|
</style> |