- 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.
600 lines
21 KiB
Vue
600 lines
21 KiB
Vue
<script setup>
|
|
definePageMeta({
|
|
title: "Create Role",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{ name: "Dashboard", path: "/dashboard" },
|
|
{ name: "Roles", path: "/roles" },
|
|
{ name: "Create Role", path: "/roles/create", type: "current" }
|
|
]
|
|
});
|
|
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
|
// Form state
|
|
const roleForm = reactive({
|
|
name: '',
|
|
description: '',
|
|
permissions: {
|
|
menus: [],
|
|
components: [],
|
|
features: []
|
|
}
|
|
})
|
|
|
|
// Simplified templates with clear descriptions
|
|
const roleTemplates = ref([
|
|
{
|
|
id: 'administrator',
|
|
name: '🔴 Administrator',
|
|
description: 'Complete system access - all features and permissions',
|
|
permissionCount: 45,
|
|
icon: 'ph:crown',
|
|
color: 'red',
|
|
permissions: {
|
|
menus: ['1', '2', '3', '4', '5', '6'],
|
|
components: ['1', '2', '3', '4', '5'],
|
|
features: ['1', '2', '3', '4']
|
|
}
|
|
},
|
|
{
|
|
id: 'manager',
|
|
name: '🟡 Manager',
|
|
description: 'Department management, approvals, and team oversight',
|
|
permissionCount: 28,
|
|
icon: 'ph:briefcase',
|
|
color: 'yellow',
|
|
permissions: {
|
|
menus: ['1', '2', '3', '5'],
|
|
components: ['1', '3', '4'],
|
|
features: ['2', '4']
|
|
}
|
|
},
|
|
{
|
|
id: 'editor',
|
|
name: '🟢 Editor',
|
|
description: 'Content creation, editing, and data management',
|
|
permissionCount: 18,
|
|
icon: 'ph:pencil',
|
|
color: 'green',
|
|
permissions: {
|
|
menus: ['1', '2', '3'],
|
|
components: ['1', '3'],
|
|
features: ['1']
|
|
}
|
|
},
|
|
{
|
|
id: 'viewer',
|
|
name: '🔵 Viewer',
|
|
description: 'Read-only access to view data and reports',
|
|
permissionCount: 8,
|
|
icon: 'ph:eye',
|
|
color: 'blue',
|
|
permissions: {
|
|
menus: ['1', '2'],
|
|
components: ['1'],
|
|
features: []
|
|
}
|
|
},
|
|
{
|
|
id: 'custom',
|
|
name: '⚙️ Custom Role',
|
|
description: 'Configure permissions manually for specific needs',
|
|
permissionCount: 0,
|
|
icon: 'ph:gear',
|
|
color: 'gray',
|
|
permissions: {
|
|
menus: [],
|
|
components: [],
|
|
features: []
|
|
}
|
|
}
|
|
])
|
|
|
|
// Application options
|
|
const applicationOptions = ref([
|
|
{ value: '1', label: 'corradAF - Main Application' },
|
|
{ value: '2', label: 'HR System - Human Resources' },
|
|
{ value: '3', label: 'Finance System - Financial Management' }
|
|
])
|
|
|
|
// Simplified resource lists (only shown for custom roles)
|
|
const resources = ref({
|
|
menus: [
|
|
{ id: '1', name: 'Dashboard', path: '/dashboard' },
|
|
{ id: '2', name: 'Users Management', path: '/users' },
|
|
{ id: '3', name: 'Groups Management', path: '/groups' },
|
|
{ id: '4', name: 'Roles Management', path: '/roles' },
|
|
{ id: '5', name: 'Applications', path: '/applications' },
|
|
{ id: '6', name: 'RBAC Management', path: '/rbac' }
|
|
],
|
|
components: [
|
|
{ id: '1', name: 'User Profile Actions', description: 'Edit, delete user profiles' },
|
|
{ id: '2', name: 'Data Export', description: 'Export system data' },
|
|
{ id: '3', name: 'Bulk Operations', description: 'Mass user/group operations' },
|
|
{ id: '4', name: 'System Settings', description: 'Configure system settings' },
|
|
{ id: '5', name: 'Audit Logs', description: 'View system audit trails' }
|
|
],
|
|
features: [
|
|
{ id: '1', name: 'Data Export', description: 'Export data to CSV/Excel' },
|
|
{ id: '2', name: 'Approval Workflows', description: 'Approve/reject requests' },
|
|
{ id: '3', name: 'System Backup', description: 'Create system backups' },
|
|
{ id: '4', name: 'User Impersonation', description: 'Login as other users' }
|
|
]
|
|
})
|
|
|
|
// Loading state
|
|
const isLoading = ref(false)
|
|
|
|
// Computed
|
|
const selectedTemplateData = computed(() => {
|
|
return roleTemplates.value.find(t => t.id === roleForm.selectedTemplate)
|
|
})
|
|
|
|
const isFormValid = computed(() => {
|
|
return roleForm.name &&
|
|
roleForm.description &&
|
|
roleForm.application &&
|
|
(roleForm.useTemplate ? roleForm.selectedTemplate : true)
|
|
})
|
|
|
|
const permissionSummary = computed(() => {
|
|
if (roleForm.useTemplate && selectedTemplateData.value) {
|
|
return selectedTemplateData.value.permissionCount
|
|
}
|
|
|
|
const manualCount = roleForm.selectedMenus.length +
|
|
roleForm.selectedComponents.length +
|
|
roleForm.selectedFeatures.length
|
|
return manualCount
|
|
})
|
|
|
|
// Methods
|
|
const selectTemplate = (templateId) => {
|
|
roleForm.selectedTemplate = templateId
|
|
roleForm.useTemplate = true
|
|
roleForm.showAdvancedPermissions = false
|
|
|
|
// Auto-apply template permissions if custom
|
|
if (templateId !== 'custom') {
|
|
const template = roleTemplates.value.find(t => t.id === templateId)
|
|
if (template) {
|
|
roleForm.selectedMenus = [...template.permissions.menus]
|
|
roleForm.selectedComponents = [...template.permissions.components]
|
|
roleForm.selectedFeatures = [...template.permissions.features]
|
|
}
|
|
} else {
|
|
// Clear permissions for custom role
|
|
roleForm.selectedMenus = []
|
|
roleForm.selectedComponents = []
|
|
roleForm.selectedFeatures = []
|
|
roleForm.showAdvancedPermissions = true
|
|
}
|
|
}
|
|
|
|
const toggleAdvancedPermissions = () => {
|
|
roleForm.showAdvancedPermissions = !roleForm.showAdvancedPermissions
|
|
if (roleForm.showAdvancedPermissions) {
|
|
roleForm.useTemplate = false
|
|
roleForm.selectedTemplate = 'custom'
|
|
}
|
|
}
|
|
|
|
const handleTemplateSubmit = (templateData) => {
|
|
console.log('Creating role from template:', templateData)
|
|
|
|
// Apply template to form
|
|
roleForm.name = templateData.name
|
|
roleForm.description = templateData.description
|
|
roleForm.permissions = { ...templateData.permissions }
|
|
}
|
|
|
|
const handleManualSubmit = (data) => {
|
|
console.log('Creating custom role:', data)
|
|
|
|
// Reset form
|
|
Object.assign(roleForm, {
|
|
name: '',
|
|
description: '',
|
|
permissions: {
|
|
menus: [],
|
|
components: [],
|
|
features: []
|
|
}
|
|
})
|
|
}
|
|
|
|
const createRole = async () => {
|
|
if (!isFormValid.value) return
|
|
|
|
isLoading.value = true
|
|
|
|
try {
|
|
const roleData = {
|
|
name: roleForm.name,
|
|
description: roleForm.description,
|
|
application: roleForm.application,
|
|
isGlobal: roleForm.isGlobal,
|
|
isActive: roleForm.isActive,
|
|
priority: roleForm.priority,
|
|
template: roleForm.useTemplate ? roleForm.selectedTemplate : null,
|
|
permissions: {
|
|
menus: roleForm.selectedMenus,
|
|
components: roleForm.selectedComponents,
|
|
features: roleForm.selectedFeatures
|
|
},
|
|
syncToAuthentik: roleForm.syncToAuthentik
|
|
}
|
|
|
|
console.log('Creating role:', roleData)
|
|
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
|
|
// Success - redirect
|
|
await navigateTo('/roles')
|
|
|
|
} catch (error) {
|
|
console.error('Failed to create role:', error)
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const resetForm = () => {
|
|
Object.assign(roleForm, {
|
|
name: '',
|
|
description: '',
|
|
application: '1',
|
|
isGlobal: false,
|
|
isActive: true,
|
|
priority: 50,
|
|
useTemplate: true,
|
|
selectedTemplate: '',
|
|
showAdvancedPermissions: false,
|
|
selectedMenus: [],
|
|
selectedComponents: [],
|
|
selectedFeatures: [],
|
|
selectedActions: {},
|
|
syncToAuthentik: true
|
|
})
|
|
}
|
|
|
|
// Initialize
|
|
onMounted(() => {
|
|
// Set default template
|
|
roleForm.selectedTemplate = 'viewer'
|
|
})
|
|
</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">Create New Role</h1>
|
|
<p class="text-gray-600 dark:text-gray-400">Choose a template or create a custom role with specific permissions</p>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<rs-button @click="resetForm" variant="primary-outline">
|
|
<Icon name="ph:arrow-clockwise" class="w-4 h-4 mr-2" />
|
|
Reset Form
|
|
</rs-button>
|
|
<rs-button @click="createRole" :disabled="!isFormValid || isLoading">
|
|
<Icon name="ph:shield-plus" class="w-4 h-4 mr-2" />
|
|
{{ isLoading ? 'Creating...' : 'Create Role' }}
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<FormKit type="form" @submit="createRole">
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- Main Form -->
|
|
<div class="lg:col-span-2 space-y-6">
|
|
|
|
<!-- Basic Information -->
|
|
<rs-card>
|
|
<template #header>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Basic Information</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormKit
|
|
v-model="roleForm.name"
|
|
type="text"
|
|
label="Role Name"
|
|
placeholder="e.g., Content Manager"
|
|
validation="required|length:3"
|
|
validation-visibility="dirty"
|
|
:validation-messages="{
|
|
required: 'Role name is required',
|
|
length: 'Role name must be at least 3 characters'
|
|
}"
|
|
/>
|
|
|
|
<FormKit
|
|
v-model="roleForm.application"
|
|
type="select"
|
|
label="Application"
|
|
:options="applicationOptions"
|
|
validation="required"
|
|
help="Which application this role applies to"
|
|
/>
|
|
</div>
|
|
|
|
<FormKit
|
|
v-model="roleForm.description"
|
|
type="textarea"
|
|
label="Description"
|
|
placeholder="Describe what this role can do and its responsibilities"
|
|
validation="required"
|
|
validation-visibility="dirty"
|
|
rows="3"
|
|
:validation-messages="{
|
|
required: 'Description is required'
|
|
}"
|
|
/>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<FormKit
|
|
v-model="roleForm.priority"
|
|
type="range"
|
|
label="Priority Level"
|
|
:min="0"
|
|
:max="100"
|
|
:step="10"
|
|
help="Higher priority wins in conflicts"
|
|
/>
|
|
|
|
<FormKit
|
|
v-model="roleForm.isGlobal"
|
|
type="checkbox"
|
|
label="Global Role"
|
|
help="Available across all applications"
|
|
/>
|
|
|
|
<FormKit
|
|
v-model="roleForm.isActive"
|
|
type="checkbox"
|
|
label="Active"
|
|
help="Role can be assigned to users"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Role Templates (PRIMARY METHOD) -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Choose Role Template</h3>
|
|
<rs-badge variant="info">Recommended</rs-badge>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
Select a pre-configured role template with common permission sets. You can customize later if needed.
|
|
</p>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div
|
|
v-for="template in roleTemplates"
|
|
:key="template.id"
|
|
@click="selectTemplate(template.id)"
|
|
class="cursor-pointer p-4 border-2 rounded-lg transition-all hover:shadow-md"
|
|
:class="{
|
|
'border-primary bg-primary/5': roleForm.selectedTemplate === template.id,
|
|
'border-gray-200 dark:border-gray-700 hover:border-primary/50': roleForm.selectedTemplate !== template.id
|
|
}"
|
|
>
|
|
<div class="flex items-start space-x-3">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-10 h-10 rounded-lg flex items-center justify-center"
|
|
:class="`bg-${template.color}-100 dark:bg-${template.color}-900/30`">
|
|
<Icon :name="template.icon" :class="`w-6 h-6 text-${template.color}-600 dark:text-${template.color}-400`" />
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-1">
|
|
{{ template.name }}
|
|
</h4>
|
|
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">
|
|
{{ template.description }}
|
|
</p>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs font-medium text-gray-500">
|
|
{{ template.permissionCount }} permissions
|
|
</span>
|
|
<Icon
|
|
v-if="roleForm.selectedTemplate === template.id"
|
|
name="ph:check-circle-fill"
|
|
class="w-5 h-5 text-primary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Permissions Toggle -->
|
|
<div v-if="roleForm.selectedTemplate && roleForm.selectedTemplate !== 'custom'" class="pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
<rs-button
|
|
@click="toggleAdvancedPermissions"
|
|
variant="secondary-outline"
|
|
size="sm"
|
|
class="w-full"
|
|
>
|
|
<Icon name="ph:gear" class="w-4 h-4 mr-2" />
|
|
{{ roleForm.showAdvancedPermissions ? 'Hide' : 'Show' }} Advanced Permissions
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Advanced Permissions (Hidden by default) -->
|
|
<rs-card v-if="roleForm.showAdvancedPermissions || roleForm.selectedTemplate === 'custom'">
|
|
<template #header>
|
|
<div class="flex items-center">
|
|
<Icon name="ph:gear" class="w-5 h-5 mr-2 text-orange-600" />
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Advanced Permissions</h3>
|
|
<rs-badge variant="warning" class="ml-2">Expert Mode</rs-badge>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-6">
|
|
<div class="p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
|
|
<div class="flex items-start">
|
|
<Icon name="ph:warning" class="w-5 h-5 text-yellow-600 dark:text-yellow-400 mr-2 mt-0.5" />
|
|
<div class="text-sm text-yellow-800 dark:text-yellow-200">
|
|
<p class="font-medium mb-1">Advanced Configuration</p>
|
|
<p>Configure individual permissions. Most users should use templates above.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Menu Access -->
|
|
<div>
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-3 flex items-center">
|
|
<Icon name="ph:list" class="w-4 h-4 mr-2 text-blue-600" />
|
|
Menu Access
|
|
</h4>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
<FormKit
|
|
v-for="menu in resources.menus"
|
|
:key="menu.id"
|
|
v-model="roleForm.selectedMenus"
|
|
type="checkbox"
|
|
:value="menu.id"
|
|
:label="menu.name"
|
|
:help="menu.path"
|
|
:classes="{
|
|
wrapper: 'mb-1',
|
|
label: '!text-sm',
|
|
help: '!text-xs'
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Component Access -->
|
|
<div>
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-3 flex items-center">
|
|
<Icon name="ph:squares-four" class="w-4 h-4 mr-2 text-green-600" />
|
|
Component Access
|
|
</h4>
|
|
<div class="grid grid-cols-1 gap-2">
|
|
<FormKit
|
|
v-for="component in resources.components"
|
|
:key="component.id"
|
|
v-model="roleForm.selectedComponents"
|
|
type="checkbox"
|
|
:value="component.id"
|
|
:label="component.name"
|
|
:help="component.description"
|
|
:classes="{
|
|
wrapper: 'mb-1',
|
|
label: '!text-sm',
|
|
help: '!text-xs'
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Feature Access -->
|
|
<div>
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-3 flex items-center">
|
|
<Icon name="ph:lightning" class="w-4 h-4 mr-2 text-purple-600" />
|
|
Feature Access
|
|
</h4>
|
|
<div class="grid grid-cols-1 gap-2">
|
|
<FormKit
|
|
v-for="feature in resources.features"
|
|
:key="feature.id"
|
|
v-model="roleForm.selectedFeatures"
|
|
type="checkbox"
|
|
:value="feature.id"
|
|
:label="feature.name"
|
|
:help="feature.description"
|
|
:classes="{
|
|
wrapper: 'mb-1',
|
|
label: '!text-sm',
|
|
help: '!text-xs'
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="space-y-6">
|
|
<!-- Role Preview -->
|
|
<rs-card>
|
|
<template #header>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Role Preview</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-3">
|
|
<div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Name:</span>
|
|
<p class="text-sm text-gray-900 dark:text-white">{{ roleForm.name || 'Not set' }}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Template:</span>
|
|
<p class="text-sm text-gray-900 dark:text-white">
|
|
{{ selectedTemplateData?.name || 'None selected' }}
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Permissions:</span>
|
|
<rs-badge :variant="permissionSummary > 0 ? 'success' : 'secondary'">
|
|
{{ permissionSummary }} permissions
|
|
</rs-badge>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Priority:</span>
|
|
<div class="flex items-center space-x-2">
|
|
<div class="flex-1 bg-gray-200 rounded-full h-2">
|
|
<div
|
|
class="bg-primary h-2 rounded-full transition-all duration-300"
|
|
:style="{ width: roleForm.priority + '%' }"
|
|
></div>
|
|
</div>
|
|
<span class="text-sm text-gray-600">{{ roleForm.priority }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-2">
|
|
<rs-badge :variant="roleForm.isGlobal ? 'info' : 'secondary'">
|
|
{{ roleForm.isGlobal ? 'Global' : 'App-specific' }}
|
|
</rs-badge>
|
|
<rs-badge :variant="roleForm.isActive ? 'success' : 'secondary'">
|
|
{{ roleForm.isActive ? 'Active' : 'Inactive' }}
|
|
</rs-badge>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
</div>
|
|
</FormKit>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Custom styles */
|
|
</style> |