- Removed the RBAC Management section from the navigation structure. - Updated the dashboard page title to "RBAC Management" and modified the breadcrumb to reflect the new path. - Simplified the dashboard component by removing unused data and integrating new tab functionalities for managing applications, groups, roles, and permissions. - Enhanced the create group and create role pages to include application assignment and simplified form states. - Updated user creation page to include application assignment and filtered group/role options based on the selected application. - Deleted the obsolete rbac-permission page to streamline the project structure.
842 lines
35 KiB
Vue
842 lines
35 KiB
Vue
<script setup>
|
|
definePageMeta({
|
|
title: "RBAC Management",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
breadcrumb: [
|
|
{ name: "Dashboard", path: "/dashboard" },
|
|
{ name: "RBAC Management", path: "/rbac-permission", type: "current" }
|
|
]
|
|
});
|
|
|
|
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
// Active tab state
|
|
const activeTab = ref('overview')
|
|
|
|
// Get route and router for query param handling
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
// Tab definitions
|
|
const tabs = [
|
|
{ id: 'overview', name: 'Overview', icon: 'ph:chart-pie' },
|
|
{ id: 'groups', name: 'Groups & Roles', icon: 'ph:users-three' },
|
|
{ id: 'permissions', name: 'Permissions', icon: 'ph:shield-check' },
|
|
{ id: 'applications', name: 'Applications', icon: 'ph:app-window' },
|
|
{ id: 'audit', name: 'Audit Trail', icon: 'ph:clock-clockwise' }
|
|
]
|
|
|
|
// Handle tab query parameter
|
|
watch(() => route.query.tab, (newTab) => {
|
|
if (newTab && tabs.find(tab => tab.id === newTab)) {
|
|
activeTab.value = newTab
|
|
}
|
|
}, { immediate: true })
|
|
|
|
// Update URL when tab changes
|
|
watch(activeTab, (newTab) => {
|
|
router.push({ query: { ...route.query, tab: newTab } })
|
|
})
|
|
|
|
// Application state
|
|
const selectedAppId = ref('1')
|
|
const applications = ref([
|
|
{ id: '1', name: 'corradAF', description: 'Main Application', status: 'active' },
|
|
{ id: '2', name: 'HR System', description: 'Human Resources', status: 'active' },
|
|
{ id: '3', name: 'Finance System', description: 'Financial Management', status: 'development' }
|
|
])
|
|
|
|
// Organization state
|
|
const selectedOrgId = ref('1')
|
|
const organizations = ref([
|
|
{ id: '1', name: 'Main Organization', description: 'Primary tenant' },
|
|
{ id: '2', name: 'Branch Office', description: 'Secondary tenant' }
|
|
])
|
|
|
|
// Groups from Authentik
|
|
const authentikGroups = ref([
|
|
{
|
|
id: '1',
|
|
name: 'IT Department',
|
|
authentikUUID: 'uuid-1',
|
|
userCount: 12,
|
|
description: 'Information Technology Department'
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'HR Department',
|
|
authentikUUID: 'uuid-2',
|
|
userCount: 8,
|
|
description: 'Human Resources Department'
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'Finance Department',
|
|
authentikUUID: 'uuid-3',
|
|
userCount: 6,
|
|
description: 'Finance and Accounting Department'
|
|
},
|
|
{
|
|
id: '4',
|
|
name: 'Management',
|
|
authentikUUID: 'uuid-4',
|
|
userCount: 4,
|
|
description: 'Executive Management'
|
|
}
|
|
])
|
|
|
|
// Roles for selected application
|
|
const appRoles = ref([
|
|
{ id: '1', name: 'Administrator', description: 'Full system access', userCount: 2 },
|
|
{ id: '2', name: 'Manager', description: 'Department management access', userCount: 8 },
|
|
{ id: '3', name: 'Editor', description: 'Content editing access', userCount: 15 },
|
|
{ id: '4', name: 'Viewer', description: 'Read-only access', userCount: 25 }
|
|
])
|
|
|
|
// Resources for selected application
|
|
const resources = ref({
|
|
menus: [
|
|
{ id: '1', key: 'menu.dashboard', name: 'Dashboard', path: '/dashboard', level: 0 },
|
|
{ id: '2', key: 'menu.users', name: 'Users', path: '/users', level: 0 },
|
|
{ id: '3', key: 'menu.users.list', name: 'User List', path: '/users/list', level: 1 },
|
|
{ id: '4', key: 'menu.users.create', name: 'Create User', path: '/users/create', level: 1 },
|
|
{ id: '5', key: 'menu.rbac', name: 'RBAC', path: '/rbac', level: 0 },
|
|
{ id: '6', key: 'menu.reports', name: 'Reports', path: '/reports', level: 0 }
|
|
],
|
|
components: [
|
|
{ id: '1', key: 'component.user.edit_button', name: 'User Edit Button' },
|
|
{ id: '2', key: 'component.user.delete_button', name: 'User Delete Button' },
|
|
{ id: '3', key: 'component.user.bulk_actions', name: 'User Bulk Actions' },
|
|
{ id: '4', key: 'component.profile.sensitive_info', name: 'Profile Sensitive Info' },
|
|
{ id: '5', key: 'component.financial.data', name: 'Financial Data' }
|
|
],
|
|
features: [
|
|
{ id: '1', key: 'feature.export.data', name: 'Export Data' },
|
|
{ id: '2', key: 'feature.approve.requests', name: 'Approve Requests' },
|
|
{ id: '3', key: 'feature.system.backup', name: 'System Backup' },
|
|
{ id: '4', key: 'feature.user.impersonation', name: 'User Impersonation' }
|
|
]
|
|
})
|
|
|
|
// Actions
|
|
const actions = ref([
|
|
{ id: '1', name: 'view', label: 'View', icon: 'ph:eye' },
|
|
{ id: '2', name: 'create', label: 'Create', icon: 'ph:plus' },
|
|
{ id: '3', name: 'edit', label: 'Edit', icon: 'ph:pencil' },
|
|
{ id: '4', name: 'delete', label: 'Delete', icon: 'ph:trash' },
|
|
{ id: '5', name: 'approve', label: 'Approve', icon: 'ph:check' }
|
|
])
|
|
|
|
// Permission state
|
|
const groupRoleAssignments = ref({})
|
|
const rolePermissions = ref({})
|
|
const isLoading = ref(false)
|
|
|
|
// Stats for overview
|
|
const stats = computed(() => ({
|
|
totalGroups: authentikGroups.value.length,
|
|
totalRoles: appRoles.value.length,
|
|
totalUsers: authentikGroups.value.reduce((sum, group) => sum + group.userCount, 0),
|
|
totalResources: resources.value.menus.length + resources.value.components.length + resources.value.features.length
|
|
}))
|
|
|
|
// Navigation Methods - properly linked to actual pages
|
|
const navigateToUsers = () => {
|
|
navigateTo('/users')
|
|
}
|
|
|
|
const navigateToGroups = () => {
|
|
navigateTo('/groups')
|
|
}
|
|
|
|
const navigateToRoles = () => {
|
|
navigateTo('/roles')
|
|
}
|
|
|
|
const navigateToApplications = () => {
|
|
navigateTo('/applications')
|
|
}
|
|
|
|
const navigateToCreateRole = () => {
|
|
navigateTo('/roles/create')
|
|
}
|
|
|
|
const navigateToCreateApplication = () => {
|
|
navigateTo('/applications/create')
|
|
}
|
|
|
|
const navigateToCreateUser = () => {
|
|
navigateTo('/users/create')
|
|
}
|
|
|
|
const navigateToCreateGroup = () => {
|
|
navigateTo('/groups/create')
|
|
}
|
|
|
|
// Quick Action Handlers
|
|
const handleQuickAction = (action) => {
|
|
switch (action) {
|
|
case 'manage-users':
|
|
navigateToUsers()
|
|
break
|
|
case 'manage-roles':
|
|
activeTab.value = 'groups'
|
|
break
|
|
case 'manage-permissions':
|
|
activeTab.value = 'permissions'
|
|
break
|
|
case 'manage-applications':
|
|
activeTab.value = 'applications'
|
|
break
|
|
case 'view-audit':
|
|
activeTab.value = 'audit'
|
|
break
|
|
default:
|
|
console.log('Unknown action:', action)
|
|
}
|
|
}
|
|
|
|
// Event Handlers for Components
|
|
const handleGroupRoleChange = ({ groupId, roleId, assigned }) => {
|
|
updateGroupRole(groupId, roleId, assigned)
|
|
}
|
|
|
|
const handlePermissionChange = ({ roleId, resourceId, action, granted }) => {
|
|
updatePermission(roleId, resourceId, action, granted)
|
|
}
|
|
|
|
// Initialize
|
|
onMounted(() => {
|
|
// Set initial tab from URL
|
|
if (route.query.tab) {
|
|
activeTab.value = route.query.tab
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<LayoutsBreadcrumb />
|
|
|
|
<!-- Header -->
|
|
<rs-card class="mb-6">
|
|
<template #body>
|
|
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="mb-4 lg:mb-0">
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">RBAC Management</h1>
|
|
<p class="text-gray-600 dark:text-gray-400">Manage roles, permissions, and access control across applications</p>
|
|
</div>
|
|
|
|
<!-- Application & Organization Selector -->
|
|
<div class="flex flex-col sm:flex-row gap-4">
|
|
<div class="min-w-48">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Organization</label>
|
|
<select v-model="selectedOrgId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
|
<option v-for="org in organizations" :key="org.id" :value="org.id">
|
|
{{ org.name }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="min-w-48">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Application</label>
|
|
<select v-model="selectedAppId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
|
<option v-for="app in applications" :key="app.id" :value="app.id">
|
|
{{ app.name }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Tab Navigation -->
|
|
<div class="mb-6">
|
|
<nav class="flex space-x-8 border-b border-gray-200 dark:border-gray-700">
|
|
<button
|
|
v-for="tab in tabs"
|
|
:key="tab.id"
|
|
@click="activeTab = tab.id"
|
|
:class="[
|
|
'flex items-center py-2 px-1 border-b-2 font-medium text-sm transition-colors',
|
|
activeTab === tab.id
|
|
? 'border-primary text-primary'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
|
|
]"
|
|
>
|
|
<Icon :name="tab.icon" class="w-5 h-5 mr-2" />
|
|
{{ tab.name }}
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="space-y-6">
|
|
|
|
<!-- Overview Tab -->
|
|
<div v-if="activeTab === 'overview'" class="space-y-6">
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<!-- Total Groups -->
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:users-three" class="w-5 h-5 text-white" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.totalGroups }}</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400">Total Groups</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Total Roles -->
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-purple-500 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:shield-check" class="w-5 h-5 text-white" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.totalRoles }}</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400">Total Roles</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Total Users -->
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-green-500 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:user" class="w-5 h-5 text-white" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.totalUsers }}</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400">Total Users</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Total Resources -->
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-yellow-500 rounded-lg flex items-center justify-center">
|
|
<Icon name="ph:stack" class="w-5 h-5 text-white" />
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.totalResources }}</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400">Resources</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<rs-card>
|
|
<template #header>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Quick Actions</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<!-- Manage Users -->
|
|
<button
|
|
@click="handleQuickAction('manage-users')"
|
|
class="flex items-center justify-center p-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-primary hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="text-center">
|
|
<Icon name="ph:users" class="w-8 h-8 mx-auto mb-2 text-gray-600 dark:text-gray-400" />
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white">Manage Users</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">View and edit users</p>
|
|
</div>
|
|
</button>
|
|
|
|
<!-- Manage Permissions -->
|
|
<button
|
|
@click="handleQuickAction('manage-permissions')"
|
|
class="flex items-center justify-center p-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-primary hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="text-center">
|
|
<Icon name="ph:shield-plus" class="w-8 h-8 mx-auto mb-2 text-gray-600 dark:text-gray-400" />
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white">Manage Permissions</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Configure access control</p>
|
|
</div>
|
|
</button>
|
|
|
|
<!-- View Audit -->
|
|
<button
|
|
@click="handleQuickAction('view-audit')"
|
|
class="flex items-center justify-center p-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-primary hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="text-center">
|
|
<Icon name="ph:clock-clockwise" class="w-8 h-8 mx-auto mb-2 text-gray-600 dark:text-gray-400" />
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white">View Audit</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Check activity logs</p>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Recent Activity -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Recent Activity</h3>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-3">
|
|
<div class="flex items-start space-x-3">
|
|
<div class="flex-shrink-0 w-2 h-2 bg-green-500 rounded-full mt-2"></div>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm text-gray-900 dark:text-white">
|
|
<span class="font-medium">John Doe</span> was assigned to <span class="font-medium">Manager</span> role
|
|
</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">2 minutes ago</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start space-x-3">
|
|
<div class="flex-shrink-0 w-2 h-2 bg-blue-500 rounded-full mt-2"></div>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm text-gray-900 dark:text-white">
|
|
<span class="font-medium">IT Department</span> permissions updated for <span class="font-medium">User Management</span>
|
|
</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">15 minutes ago</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start space-x-3">
|
|
<div class="flex-shrink-0 w-2 h-2 bg-yellow-500 rounded-full mt-2"></div>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm text-gray-900 dark:text-white">
|
|
New role <span class="font-medium">Content Editor</span> created
|
|
</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">1 hour ago</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Groups & Roles Tab -->
|
|
<div v-if="activeTab === 'groups'" class="space-y-6">
|
|
<!-- Header with Actions -->
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Groups & Role Assignments</h2>
|
|
<div class="flex space-x-3">
|
|
<rs-button @click="navigateToCreateGroup" variant="secondary-outline">
|
|
<Icon name="ph:user-plus" class="w-4 h-4 mr-2" />
|
|
Create Group
|
|
</rs-button>
|
|
<rs-button @click="navigateToCreateRole" variant="primary-outline">
|
|
<Icon name="ph:shield-plus" class="w-4 h-4 mr-2" />
|
|
Create Role
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Groups Grid -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<rs-card v-for="group in authentikGroups" :key="group.id">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">{{ group.name }}</h3>
|
|
<rs-badge variant="primary">{{ group.userCount }} Users</rs-badge>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">{{ group.description }}</p>
|
|
|
|
<div class="flex items-center justify-between text-sm">
|
|
<span class="text-gray-600 dark:text-gray-400">Users:</span>
|
|
<span class="font-medium">{{ group.userCount }}</span>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Assigned Roles:</label>
|
|
<div class="space-y-1">
|
|
<div v-for="role in appRoles" :key="role.id" class="flex items-center">
|
|
<input
|
|
:id="`group-${group.id}-role-${role.id}`"
|
|
type="checkbox"
|
|
:checked="groupRoleAssignments[group.id]?.includes(role.id)"
|
|
@change="handleGroupRoleChange({
|
|
groupId: group.id,
|
|
roleId: role.id,
|
|
assigned: $event.target.checked
|
|
})"
|
|
class="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded"
|
|
/>
|
|
<label
|
|
:for="`group-${group.id}-role-${role.id}`"
|
|
class="ml-2 text-sm text-gray-700 dark:text-gray-300"
|
|
>
|
|
{{ role.name }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-2 pt-2">
|
|
<rs-button @click="navigateToUsers" variant="secondary-outline" size="sm" class="flex-1">
|
|
Manage Users
|
|
</rs-button>
|
|
<rs-button @click="navigateToGroups" variant="primary-outline" size="sm" class="flex-1">
|
|
View Details
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Permissions Tab -->
|
|
<div v-if="activeTab === 'permissions'" class="space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Permission Matrix</h2>
|
|
<div class="flex items-center space-x-2">
|
|
<rs-badge :variant="selectedAppId === '1' ? 'success' : 'secondary'">
|
|
{{ applications.find(app => app.id === selectedAppId)?.name }}
|
|
</rs-badge>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Menu Permissions -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center">
|
|
<Icon name="ph:list" class="w-5 h-5 mr-2 text-blue-600" />
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Menu Permissions</h3>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead class="bg-gray-50 dark:bg-gray-800">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
Menu Item
|
|
</th>
|
|
<th v-for="role in appRoles" :key="role.id" class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
{{ role.name }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
|
|
<tr v-for="menu in resources.menus" :key="menu.id">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ menu.name }}
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">{{ menu.path }}</div>
|
|
</td>
|
|
<td v-for="role in appRoles" :key="role.id" class="px-6 py-4 whitespace-nowrap text-center">
|
|
<input
|
|
type="checkbox"
|
|
:checked="rolePermissions[`${role.id}-${menu.id}-1`]"
|
|
@change="handlePermissionChange({
|
|
roleId: role.id,
|
|
resourceId: menu.id,
|
|
action: '1',
|
|
granted: $event.target.checked
|
|
})"
|
|
class="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Component Permissions -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center">
|
|
<Icon name="ph:squares-four" class="w-5 h-5 mr-2 text-green-600" />
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Component Permissions</h3>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead class="bg-gray-50 dark:bg-gray-800">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
Component
|
|
</th>
|
|
<th v-for="role in appRoles" :key="role.id" class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
{{ role.name }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
|
|
<tr v-for="component in resources.components" :key="component.id">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ component.name }}
|
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-mono">{{ component.key }}</div>
|
|
</td>
|
|
<td v-for="role in appRoles" :key="role.id" class="px-6 py-4 whitespace-nowrap text-center">
|
|
<input
|
|
type="checkbox"
|
|
:checked="rolePermissions[`${role.id}-${component.id}-1`]"
|
|
@change="handlePermissionChange({
|
|
roleId: role.id,
|
|
resourceId: component.id,
|
|
action: '1',
|
|
granted: $event.target.checked
|
|
})"
|
|
class="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
|
|
<!-- Feature Permissions -->
|
|
<rs-card>
|
|
<template #header>
|
|
<div class="flex items-center">
|
|
<Icon name="ph:gear" class="w-5 h-5 mr-2 text-purple-600" />
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">Feature Permissions</h3>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead class="bg-gray-50 dark:bg-gray-800">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
Feature
|
|
</th>
|
|
<th v-for="role in appRoles" :key="role.id" class="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
{{ role.name }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
|
|
<tr v-for="feature in resources.features" :key="feature.id">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ feature.name }}
|
|
<div class="text-xs text-gray-500 dark:text-gray-400 font-mono">{{ feature.key }}</div>
|
|
</td>
|
|
<td v-for="role in appRoles" :key="role.id" class="px-6 py-4 whitespace-nowrap text-center">
|
|
<input
|
|
type="checkbox"
|
|
:checked="rolePermissions[`${role.id}-${feature.id}-1`]"
|
|
@change="handlePermissionChange({
|
|
roleId: role.id,
|
|
resourceId: feature.id,
|
|
action: '1',
|
|
granted: $event.target.checked
|
|
})"
|
|
class="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
|
|
<!-- Applications Tab -->
|
|
<div v-if="activeTab === 'applications'" class="space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Application Management</h2>
|
|
<div class="flex space-x-3">
|
|
<rs-button @click="navigateToApplications" variant="secondary-outline">
|
|
<Icon name="ph:gear" class="w-4 h-4 mr-2" />
|
|
Manage Resources
|
|
</rs-button>
|
|
<rs-button @click="navigateToCreateApplication" variant="primary">
|
|
<Icon name="ph:plus" class="w-4 h-4 mr-2" />
|
|
Create Application
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Applications Grid -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
|
|
<rs-card v-for="app in applications" :key="app.id">
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white">{{ app.name }}</h3>
|
|
<rs-badge :variant="app.status === 'active' ? 'success' : app.status === 'development' ? 'warning' : 'secondary'">
|
|
{{ app.status }}
|
|
</rs-badge>
|
|
</div>
|
|
</template>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">{{ app.description }}</p>
|
|
|
|
<div class="grid grid-cols-3 gap-4 text-center">
|
|
<div>
|
|
<div class="text-lg font-semibold text-gray-900 dark:text-white">6</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">Menus</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-lg font-semibold text-gray-900 dark:text-white">5</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">Components</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-lg font-semibold text-gray-900 dark:text-white">4</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">Features</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-2">
|
|
<rs-button
|
|
@click="selectedAppId = app.id; activeTab = 'permissions'"
|
|
variant="primary-outline"
|
|
size="sm"
|
|
class="flex-1"
|
|
>
|
|
Configure
|
|
</rs-button>
|
|
<rs-button @click="navigateToApplications" variant="primary-outline" size="sm" class="flex-1">
|
|
View Details
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Trail Tab -->
|
|
<div v-if="activeTab === 'audit'" class="space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Audit Trail</h2>
|
|
<div class="flex items-center space-x-2">
|
|
<select class="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-sm">
|
|
<option>All Actions</option>
|
|
<option>Permission Changes</option>
|
|
<option>Role Assignments</option>
|
|
<option>User Actions</option>
|
|
</select>
|
|
<rs-button variant="primary-outline" size="sm">
|
|
<Icon name="ph:funnel" class="w-4 h-4 mr-2" />
|
|
Filter
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Log -->
|
|
<rs-card>
|
|
<template #body>
|
|
<div class="space-y-4">
|
|
<!-- Audit Entry -->
|
|
<div class="flex items-start space-x-4 p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center">
|
|
<Icon name="ph:check" class="w-4 h-4 text-green-600 dark:text-green-400" />
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center justify-between">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white">Permission Granted</p>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400">2 minutes ago</span>
|
|
</div>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
<span class="font-medium">Admin User</span> granted <span class="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded">component.user.edit_button</span> permission to <span class="font-medium">Manager</span> role
|
|
</p>
|
|
<div class="flex items-center space-x-4 mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<span>Application: corradAF</span>
|
|
<span>IP: 192.168.1.100</span>
|
|
<span>Session: abc123</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Entry -->
|
|
<div class="flex items-start space-x-4 p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center">
|
|
<Icon name="ph:user-plus" class="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center justify-between">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white">User Role Assignment</p>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400">15 minutes ago</span>
|
|
</div>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
<span class="font-medium">HR Manager</span> assigned <span class="font-medium">John Doe</span> to <span class="font-medium">Editor</span> role in <span class="font-medium">IT Department</span> group
|
|
</p>
|
|
<div class="flex items-center space-x-4 mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<span>Application: HR System</span>
|
|
<span>IP: 192.168.1.105</span>
|
|
<span>Session: def456</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Entry -->
|
|
<div class="flex items-start space-x-4 p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-8 h-8 bg-yellow-100 dark:bg-yellow-900/30 rounded-full flex items-center justify-center">
|
|
<Icon name="ph:arrows-clockwise" class="w-4 h-4 text-yellow-600 dark:text-yellow-400" />
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center justify-between">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white">Authentik Sync</p>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400">1 hour ago</span>
|
|
</div>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
<span class="font-medium">System</span> synchronized groups and users from Authentik. 3 new users added, 1 group updated.
|
|
</p>
|
|
<div class="flex items-center space-x-4 mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<span>Application: System</span>
|
|
<span>Automated Process</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</rs-card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.permission-grid {
|
|
@apply min-w-full;
|
|
}
|
|
|
|
.permission-cell input[type="checkbox"] {
|
|
@apply h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded;
|
|
}
|
|
|
|
.permission-key {
|
|
@apply text-gray-500 dark:text-gray-400 font-mono text-xs;
|
|
}
|
|
</style> |