corrad-af-2024/components/rbac/StatsCards.vue
Afiq f05dd42c16 Enhance README and implement RBAC system with Authentik integration
- 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.
2025-05-31 15:58:41 +08:00

217 lines
5.7 KiB
Vue

<template>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<rs-card v-for="stat in stats" :key="stat.id">
<template #body>
<div class="flex items-center">
<div class="flex-1">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">{{ stat.label }}</p>
<p class="text-2xl font-semibold text-gray-900 dark:text-white">
{{ formatValue(stat.value, stat.type) }}
</p>
<div v-if="stat.trend" class="flex items-center mt-1">
<Icon
:name="stat.trend.direction === 'up' ? 'ph:trend-up' : 'ph:trend-down'"
class="w-4 h-4 mr-1"
:class="stat.trend.direction === 'up' ? 'text-green-500' : 'text-red-500'"
/>
<span
class="text-xs font-medium"
:class="stat.trend.direction === 'up' ? 'text-green-600' : 'text-red-600'"
>
{{ stat.trend.value }}{{ stat.trend.type === 'percentage' ? '%' : '' }}
</span>
<span class="text-xs text-gray-500 dark:text-gray-400 ml-1">vs last month</span>
</div>
</div>
<div class="flex-shrink-0">
<div
class="p-3 rounded-lg transition-colors"
:class="stat.iconBg"
>
<Icon
:name="stat.icon"
class="w-6 h-6"
:class="stat.iconColor"
/>
</div>
</div>
</div>
<!-- Progress bar for certain stats -->
<div v-if="stat.progress" class="mt-3">
<div class="flex items-center justify-between text-xs text-gray-600 dark:text-gray-400 mb-1">
<span>{{ stat.progress.label }}</span>
<span>{{ stat.progress.current }} / {{ stat.progress.total }}</span>
</div>
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5">
<div
class="h-1.5 rounded-full transition-all duration-300"
:class="stat.progress.color"
:style="{ width: (stat.progress.current / stat.progress.total * 100) + '%' }"
></div>
</div>
</div>
<!-- Quick action button -->
<div v-if="stat.action" class="mt-3">
<button
@click="$emit('stat-action', stat.action.type)"
class="w-full text-xs font-medium text-primary hover:text-primary/80 transition-colors"
>
{{ stat.action.label }}
</button>
</div>
</template>
</rs-card>
</div>
</template>
<script setup>
const props = defineProps({
totalGroups: {
type: Number,
default: 0
},
totalRoles: {
type: Number,
default: 0
},
totalUsers: {
type: Number,
default: 0
},
totalResources: {
type: Number,
default: 0
},
activePermissions: {
type: Number,
default: 0
},
totalPermissions: {
type: Number,
default: 0
},
lastSyncTime: {
type: String,
default: null
},
trends: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['stat-action'])
const stats = computed(() => [
{
id: 'groups',
label: 'Total Groups',
value: props.totalGroups,
type: 'number',
icon: 'ph:users-three',
iconColor: 'text-blue-600 dark:text-blue-400',
iconBg: 'bg-blue-100 dark:bg-blue-900/30',
trend: props.trends.groups ? {
direction: props.trends.groups >= 0 ? 'up' : 'down',
value: Math.abs(props.trends.groups),
type: 'number'
} : null,
action: {
type: 'sync-groups',
label: 'Sync from Authentik'
}
},
{
id: 'roles',
label: 'Total Roles',
value: props.totalRoles,
type: 'number',
icon: 'ph:shield-check',
iconColor: 'text-green-600 dark:text-green-400',
iconBg: 'bg-green-100 dark:bg-green-900/30',
trend: props.trends.roles ? {
direction: props.trends.roles >= 0 ? 'up' : 'down',
value: Math.abs(props.trends.roles),
type: 'number'
} : null,
action: {
type: 'manage-roles',
label: 'Manage Roles'
}
},
{
id: 'users',
label: 'Total Users',
value: props.totalUsers,
type: 'number',
icon: 'ph:user',
iconColor: 'text-purple-600 dark:text-purple-400',
iconBg: 'bg-purple-100 dark:bg-purple-900/30',
trend: props.trends.users ? {
direction: props.trends.users >= 0 ? 'up' : 'down',
value: Math.abs(props.trends.users),
type: 'percentage'
} : null,
progress: {
label: 'Active Users',
current: Math.floor(props.totalUsers * 0.85), // 85% active users
total: props.totalUsers,
color: 'bg-purple-500'
}
},
{
id: 'resources',
label: 'Resources',
value: props.totalResources,
type: 'number',
icon: 'ph:package',
iconColor: 'text-orange-600 dark:text-orange-400',
iconBg: 'bg-orange-100 dark:bg-orange-900/30',
progress: {
label: 'Configured',
current: props.activePermissions,
total: props.totalPermissions,
color: 'bg-orange-500'
},
action: {
type: 'manage-permissions',
label: 'Configure Permissions'
}
}
])
const formatValue = (value, type) => {
if (type === 'number') {
return value.toLocaleString()
}
if (type === 'percentage') {
return value + '%'
}
return value
}
</script>
<style scoped>
/* Add subtle hover effects */
.rs-card:hover {
@apply transform scale-[1.02] transition-transform duration-200;
}
.rs-card:hover .bg-blue-100 {
@apply bg-blue-200;
}
.rs-card:hover .bg-green-100 {
@apply bg-green-200;
}
.rs-card:hover .bg-purple-100 {
@apply bg-purple-200;
}
.rs-card:hover .bg-orange-100 {
@apply bg-orange-200;
}
</style>