Afiq 379eb17246 Implement Authentik Integration and Simplify RBAC Structure
- Updated nuxt.config.js to include Authentik configuration and public keys for client-side access.
- Introduced a new composable, useAuth.js, for handling authentication logic with Authentik, including user validation, login, and logout functionalities.
- Enhanced documentation to reflect the simplified RBAC structure and the integration of Authentik, emphasizing user-centric design and streamlined permission management.
- Refactored middleware for authentication checks and improved error handling during user validation.
- Created new pages for login and dashboard, ensuring proper routing and user experience.
- Removed obsolete Metabase integration and unnecessary complexity from the project structure.
2025-05-31 19:15:21 +08:00

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>