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.
This commit is contained in:
parent
a2a81bd3bb
commit
379eb17246
80
composables/useAuth.js
Normal file
80
composables/useAuth.js
Normal file
@ -0,0 +1,80 @@
|
||||
// Authentication composable for Authentik integration
|
||||
export const useAuth = () => {
|
||||
const user = ref(null);
|
||||
const isAuthenticated = ref(false);
|
||||
const isLoading = ref(false);
|
||||
|
||||
// Check if user is authenticated
|
||||
const checkAuth = async () => {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const response = await $fetch('/api/auth/validate');
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
user.value = response.user;
|
||||
isAuthenticated.value = true;
|
||||
return true;
|
||||
} else {
|
||||
user.value = null;
|
||||
isAuthenticated.value = false;
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
user.value = null;
|
||||
isAuthenticated.value = false;
|
||||
return false;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Get current user info
|
||||
const getCurrentUser = async () => {
|
||||
try {
|
||||
const userData = await $fetch('/api/auth/me');
|
||||
user.value = userData;
|
||||
isAuthenticated.value = true;
|
||||
return userData;
|
||||
} catch (error) {
|
||||
user.value = null;
|
||||
isAuthenticated.value = false;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Login redirect
|
||||
const login = () => {
|
||||
return navigateTo('/api/auth/login', { external: true });
|
||||
};
|
||||
|
||||
// Logout
|
||||
const logout = () => {
|
||||
user.value = null;
|
||||
isAuthenticated.value = false;
|
||||
return navigateTo('/api/auth/logout', { external: true });
|
||||
};
|
||||
|
||||
// Require authentication (for route guards)
|
||||
const requireAuth = async () => {
|
||||
const isAuth = await checkAuth();
|
||||
if (!isAuth) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Authentication required'
|
||||
});
|
||||
}
|
||||
return user.value;
|
||||
};
|
||||
|
||||
return {
|
||||
user: readonly(user),
|
||||
isAuthenticated: readonly(isAuthenticated),
|
||||
isLoading: readonly(isLoading),
|
||||
checkAuth,
|
||||
getCurrentUser,
|
||||
login,
|
||||
logout,
|
||||
requireAuth
|
||||
};
|
||||
};
|
@ -1,10 +1,10 @@
|
||||
# CorradAF Features Overview
|
||||
|
||||
This document provides a comprehensive overview of all implemented features in the CorradAF RBAC system. **Major Update**: The system has been redesigned with native Authentik integration, enhanced UX patterns, and comprehensive resource management.
|
||||
This document provides a comprehensive overview of all implemented features in the CorradAF RBAC system. **Major Update**: The system has been redesigned with a simplified, application-centric RBAC hierarchy: **User → Roles → Sub Group (optional) → Groups → Application**.
|
||||
|
||||
## 🎯 Core System Features
|
||||
|
||||
### 1. User Management System ✅ **Enhanced**
|
||||
### 1. User Management System ✅ **Simplified**
|
||||
|
||||
#### ✅ User Listing & Overview (`/users`)
|
||||
- **Advanced Data Table**: RsTable with built-in search, sorting, and filtering
|
||||
@ -15,22 +15,24 @@ This document provides a comprehensive overview of all implemented features in t
|
||||
- **Pagination**: Configurable page sizes (10 items default)
|
||||
- **Search & Filter**: Global search across all user data
|
||||
- **Column Management**: Hide/show columns via filter dropdown
|
||||
- **✅ Updated**: Removed manual sync buttons and Authentik status indicators (native integration)
|
||||
|
||||
#### ✅ User Creation (`/users/create`)
|
||||
- **Streamlined Form**: Organized sections with `:actions="false"` for custom buttons
|
||||
- **Basic Information**: Username, email, first/last name
|
||||
#### ✅ User Creation (`/users/create`) - **Application-Centric**
|
||||
- **Basic Information**: First name, last name, username, email
|
||||
- **Application Assignment**: **REQUIRED** - Users must belong to an application
|
||||
- **Password Management**:
|
||||
- Secure password generation
|
||||
- Password strength indicators
|
||||
- Confirmation validation
|
||||
- **Profile Details**: Phone, department, job title, employee ID
|
||||
- **Account Settings**: Active status, email verification, password change requirements
|
||||
- **Group Assignment**: Multi-select checkboxes for groups with descriptions
|
||||
- **Role Assignment**: Multi-select checkboxes for roles with descriptions
|
||||
- **Notification Settings**: ✅ **Simplified** - Email invitation system (simplified from Authentik Integration section)
|
||||
- **Group Assignment (Primary)**:
|
||||
- Users inherit permissions through groups
|
||||
- Groups are filtered by selected application
|
||||
- Groups contain collections of roles
|
||||
- **Additional Roles (Optional)**:
|
||||
- Direct role assignment for specific cases
|
||||
- Filtered by selected application
|
||||
- **Account Settings**: Active status, password change requirements, email invitations
|
||||
- **Smart Filtering**: Groups and roles automatically filter based on application selection
|
||||
- **Form Validation**: Real-time validation with FormKit
|
||||
- **Reset Functionality**: Clear form with single click
|
||||
|
||||
#### ✅ Bulk Operations (`/users/bulk`)
|
||||
- **CSV Upload**: Drag-and-drop file upload with validation
|
||||
@ -44,7 +46,7 @@ This document provides a comprehensive overview of all implemented features in t
|
||||
- **Error Handling**: Skip errors or halt on validation failures
|
||||
- **Export Functionality**: Export existing users to CSV
|
||||
|
||||
### 2. Group Management System ✅ **Enhanced**
|
||||
### 2. Group Management System ✅ **Simplified**
|
||||
|
||||
#### ✅ Group Listing & Overview (`/groups`)
|
||||
- **Advanced Data Table**: Same RsTable features as users
|
||||
@ -54,107 +56,89 @@ This document provides a comprehensive overview of all implemented features in t
|
||||
- **Hierarchy Display**: Shows parent-child group relationships
|
||||
- **Status Management**: Active/inactive group indicators
|
||||
- **Search & Filter**: Find groups by name, description, or type
|
||||
- **✅ Updated**: Removed sync buttons and Authentik status displays (native integration)
|
||||
|
||||
#### ✅ Group Creation (`/groups/create`)
|
||||
- **Basic Information**: Group name, description, parent group selection
|
||||
- **Attribute Management**:
|
||||
- Common attributes (department, cost center, location, manager email)
|
||||
- Custom attributes with key-value pairs
|
||||
- Dynamic attribute addition/removal
|
||||
- **Permissions**: ✅ **Simplified** - Direct permission assignment (simplified from Authentik Integration section)
|
||||
- **Hierarchical Structure**: Parent group selection for organization
|
||||
- **Form Standards**: ✅ **Updated** - FormKit with `:actions="false"` and streamlined interface
|
||||
#### ✅ Group Creation (`/groups/create`) - **Collections of Roles**
|
||||
- **Basic Information**: Group name, description, application assignment
|
||||
- **Application Assignment**: **REQUIRED** - Groups belong to specific applications
|
||||
- **Parent Group Selection**: Optional hierarchical structure (sub-groups)
|
||||
- **Role Assignment**: **PRIMARY FUNCTION** - Groups contain collections of roles
|
||||
- Users inherit all roles from their groups
|
||||
- Clear explanation that groups are role containers
|
||||
- **Status Management**: Active/inactive toggle
|
||||
- **Preview Panel**: Real-time preview of group configuration
|
||||
- **Simplified Design**: Removed complex enterprise attributes (cost centers, custom attributes)
|
||||
|
||||
### 3. Role Management System ✅ **Major Enhancement**
|
||||
### 3. Role Management System ✅ **Simplified**
|
||||
|
||||
#### ✅ Role Listing & Overview (`/roles`)
|
||||
- **Advanced Data Table**: Full RsTable functionality
|
||||
- **Role Stats**: Total roles, active roles, global roles, total permissions
|
||||
- **Role Stats**: Total roles, active roles, application-specific roles, total permissions
|
||||
- **Application Scoping**: Roles tied to specific applications
|
||||
- **Permission Count**: Display number of permissions per role
|
||||
- **User Assignment**: Show how many users have each role
|
||||
- **Priority System**: Role priority for conflict resolution
|
||||
- **✅ Updated**: Removed sync functionality (native integration)
|
||||
- **Status Indicators**: Active/inactive role badges
|
||||
|
||||
#### ✅ Role Creation (`/roles/create`) - Template-First Approach
|
||||
- **Basic Configuration**: Name, description, application, priority
|
||||
- **Role Templates**: ✅ **Enhanced** - Primary creation method with emoji indicators
|
||||
- 🔴 Administrator: Complete system access (45 permissions)
|
||||
- 🟡 Manager: Department management (28 permissions)
|
||||
- 🟢 Editor: Content creation and editing (18 permissions)
|
||||
- 🔵 Viewer: Read-only access (8 permissions)
|
||||
- ⚙️ Custom: Manual configuration (configurable)
|
||||
- **Progressive Disclosure**: ✅ **NEW** - Advanced permissions hidden by default
|
||||
- **Advanced Permissions**: Expert mode for detailed configuration
|
||||
- **Menu Permissions**: Control navigation visibility
|
||||
- **Component Permissions**: Control UI element access
|
||||
- **Feature Permissions**: Control functionality access
|
||||
- **Expert Mode**: ✅ **NEW** - Advanced configuration for power users
|
||||
- **Form Standards**: ✅ **Updated** - FormKit with `:actions="false"` and template-first approach
|
||||
#### ✅ Role Creation (`/roles/create`) - **Permission Containers**
|
||||
- **Basic Configuration**: Name, description, application assignment
|
||||
- **Application Assignment**: **REQUIRED** - Roles belong to specific applications
|
||||
- **Simplified Permissions**: Clear, functional permission categories
|
||||
- **User Management**: View, create, edit, delete users
|
||||
- **Group Management**: View, create, edit, delete groups
|
||||
- **Role Management**: View, create, edit, delete roles
|
||||
- **System Access**: Dashboard, reports, settings access
|
||||
- **Permission Selection**: Simple checkbox interface organized by category
|
||||
- **Status Management**: Active/inactive toggle
|
||||
- **Form Standards**: Clean FormKit interface with real-time validation
|
||||
- **Removed Complex Features**: Templates, advanced permission categories, priority systems
|
||||
|
||||
#### ✅ Role Templates Management (`/roles/templates`) ✅ **NEW**
|
||||
- **Template Creation**: FormKit form for creating new role templates
|
||||
- **Permission Selection**: Multi-select for menus, components, and features
|
||||
- **Template Metadata**: Name, description, permission count tracking
|
||||
- **Template Management**: Grid display of existing templates
|
||||
- **Template Operations**: Clone existing templates, delete unused templates
|
||||
- **Integration**: Used by role creation form for consistent template application
|
||||
|
||||
### 4. Application Management System ✅ **Major Enhancement**
|
||||
### 4. Application Management System ✅ **Central Hub**
|
||||
|
||||
#### ✅ Application Listing & Overview (`/applications`)
|
||||
- **Advanced Data Table**: Full RsTable functionality with search, sort, filter
|
||||
- **Application Stats**: Total apps, active apps, total application users
|
||||
- **Application Avatars**: Auto-generated initials for app identification
|
||||
- **Provider Indicators**: OAuth2/OIDC, SAML, Proxy support
|
||||
- **Enhanced Navigation**: ✅ **NEW** - Hierarchical menu structure with sub-items
|
||||
- **Clean Interface**: ✅ **Updated** - Removed sync buttons and status indicators
|
||||
- **User and Group Counts**: Display users and groups per application
|
||||
- **Clean Interface**: Streamlined without technical implementation details
|
||||
|
||||
#### ✅ Application Creation (`/applications/create`) - Step-by-Step Wizard
|
||||
- **Step 1: Basic Information**: Name, slug, description, URL with auto-generation
|
||||
- **Step 2: Configuration Method**: ✅ **Enhanced** - Quick setup vs custom
|
||||
- 🌐 Web Application (OAuth2, recommended)
|
||||
- 🔌 API Service (OAuth2, strict policies)
|
||||
- 🏢 Enterprise Application (SAML SSO)
|
||||
- ⚙️ Custom Configuration (manual setup)
|
||||
- **Step 3: Access Control**: Group selection with user counts
|
||||
- **Progressive Disclosure**: Advanced options hidden by default with expert mode
|
||||
- **Smart Defaults**: Intelligent configuration based on setup type
|
||||
- **Form Standards**: ✅ **Updated** - FormKit with `:actions="false"`
|
||||
#### ✅ Application Creation (`/applications/create`)
|
||||
- **Basic Information**: Name, description, URL
|
||||
- **Application Configuration**: Simple setup for different application types
|
||||
- **Status Management**: Active/inactive applications
|
||||
- **Form Standards**: Clean FormKit interface
|
||||
- **Simplified Design**: Removed complex provider configurations and step-by-step wizards
|
||||
|
||||
#### ✅ Application Resources Management (`/applications/resources`) ✅ **NEW**
|
||||
- **Multi-tab Interface**: Organized resource management
|
||||
- **Menus Tab**: Manage hardcoded menu permissions
|
||||
- **Components Tab**: Manage component access permissions
|
||||
- **Features Tab**: Manage feature-level permissions
|
||||
- **Resource Creation**: FormKit forms for creating new resources
|
||||
- **Auto-Key Generation**: Consistent resource key generation from names
|
||||
- **Application Scoping**: Resources can be scoped to specific applications
|
||||
- **Data Tables**: Display existing resources with delete functionality
|
||||
- **Responsive Design**: Dark mode support and mobile-friendly interface
|
||||
- **Centralized Management**: Single location for all application resource types
|
||||
## 🏗️ **SIMPLIFIED RBAC HIERARCHY**
|
||||
|
||||
### 5. RBAC Management Interface (`/rbac-permission`) ✅ **Updated**
|
||||
### **New Hierarchy: User → Roles → Sub Group (optional) → Groups → Application**
|
||||
|
||||
#### ✅ Multi-Tab Management Interface
|
||||
- **Overview Tab**: System statistics and quick actions
|
||||
- **Groups & Roles Tab**: Group-role assignment matrix
|
||||
- **Permissions Tab**: Resource-role permission matrix
|
||||
- **Applications Tab**: Multi-application management
|
||||
- **Audit Trail Tab**: Complete activity logging
|
||||
- **✅ Updated**: Removed sync buttons, updated user count badges
|
||||
```
|
||||
Application (Root Level)
|
||||
├── Groups (Department/Team Level)
|
||||
│ ├── Sub Groups (Optional - Team Subdivisions)
|
||||
│ ├── Roles Collection (What the group can do)
|
||||
│ │ ├── Role 1 (Specific permissions)
|
||||
│ │ ├── Role 2 (Specific permissions)
|
||||
│ │ └── Role N (Specific permissions)
|
||||
│ └── Users (Inherit all group roles)
|
||||
└── Additional Roles (Direct user assignment for special cases)
|
||||
```
|
||||
|
||||
#### ✅ Permission Matrix System
|
||||
- **Visual Grid**: Role-resource-action combinations
|
||||
- **Bulk Operations**: Assign multiple permissions simultaneously
|
||||
- **Resource Types**: Separate matrices for menus, components, features
|
||||
- **Real-time Updates**: Immediate permission changes
|
||||
- **Template Application**: Apply permission templates to roles
|
||||
- **Conflict Resolution**: Handle permission conflicts and overrides
|
||||
### **Key Benefits**
|
||||
- **Application-Centric**: Everything belongs to an application first
|
||||
- **Clear Hierarchy**: Logical flow from applications down to users
|
||||
- **Role Inheritance**: Users get permissions through group membership
|
||||
- **Flexibility**: Additional roles for special cases
|
||||
- **Simplified Management**: No complex enterprise features
|
||||
|
||||
## 🛠️ Technical Features ✅ **Enhanced**
|
||||
### **How It Works**
|
||||
1. **Create Application**: Define the system/app users will access
|
||||
2. **Create Roles**: Define what actions can be performed (permissions)
|
||||
3. **Create Groups**: Collect roles together for organizational units
|
||||
4. **Create Sub Groups** (Optional): Further subdivide groups if needed
|
||||
5. **Create Users**: Assign to application and groups (inherit roles)
|
||||
|
||||
## 🛠️ Technical Features ✅ **Simplified**
|
||||
|
||||
### 1. Advanced Data Tables (RsTable)
|
||||
- **Global Search**: Search across all table columns simultaneously
|
||||
@ -167,14 +151,12 @@ This document provides a comprehensive overview of all implemented features in t
|
||||
- **No Data States**: User-friendly empty state messages
|
||||
|
||||
### 2. Form Management (FormKit) ✅ **Standardized**
|
||||
- **Consistent Actions**: ✅ **NEW** - All forms use `:actions="false"` for custom button implementation
|
||||
- **Consistent Actions**: All forms use `:actions="false"` for custom button implementation
|
||||
- **Validation Engine**: Real-time form validation
|
||||
- **Field Types**: Text, email, password, select, checkbox, textarea, file upload
|
||||
- **Conditional Fields**: Show/hide fields based on other values
|
||||
- **Multi-step Forms**: Progressive form completion
|
||||
- **Auto-completion**: Smart dropdowns with search
|
||||
- **File Upload**: Drag-and-drop file handling
|
||||
- **Field Types**: Text, email, password, select, checkbox, textarea
|
||||
- **Application Filtering**: Smart filtering based on application selection
|
||||
- **Reset Functionality**: Clear forms while preserving structure
|
||||
- **Simplified Design**: Focused on essential fields only
|
||||
|
||||
### 3. Component Library (RS Components)
|
||||
- **RsCard**: Consistent card layout with header/body/footer
|
||||
@ -183,63 +165,46 @@ This document provides a comprehensive overview of all implemented features in t
|
||||
- **RsTable**: Advanced data table with all modern features
|
||||
- **RsDropdown**: Context menus and option selectors
|
||||
- **RsModal**: Modal dialogs for complex interactions
|
||||
- **Icons**: ✅ **Fixed** - Corrected icon names (ph:user-plus vs ph:users-plus)
|
||||
- **Icons**: Phosphor icons throughout the interface
|
||||
|
||||
### 4. Navigation & Layout ✅ **Enhanced**
|
||||
- **Hierarchical Navigation**: ✅ **NEW** - Organized menu structure with sub-items
|
||||
- Roles → Role List, Templates
|
||||
- Applications → Application List, Resources
|
||||
### 4. Navigation & Layout ✅ **Simplified**
|
||||
- **Clean Navigation**: Organized menu structure focused on core functions
|
||||
- **Breadcrumb System**: Hierarchical navigation with auto-generation
|
||||
- **Responsive Sidebar**: Clean navigation organized by functional areas
|
||||
- **Responsive Sidebar**: Navigation organized by functional areas
|
||||
- **Dark/Light Mode**: Full theme switching support
|
||||
- **Icon System**: Phosphor icons throughout the interface
|
||||
- **Loading States**: Skeleton loaders and progress indicators
|
||||
|
||||
### 5. Native Authentik Integration ✅ **Major Update**
|
||||
- **Direct API Integration**: Real-time communication with Authentik backend
|
||||
- **No Manual Sync**: Removed all sync buttons, checkboxes, and status indicators
|
||||
- **Simplified UX**: Clean interface focused on core functionality
|
||||
- **Backend Connectivity**: Assumes direct database/API integration with Authentik
|
||||
- **Form Integration**: All forms submit directly to Authentik APIs
|
||||
## 🎨 User Experience Features ✅ **Simplified**
|
||||
|
||||
## 🎨 User Experience Features ✅ **Major Enhancement**
|
||||
|
||||
### 1. Template-First Design Philosophy ✅ **NEW**
|
||||
- **Role Templates**: Pre-configured permission sets for common use cases
|
||||
- **Application Quick Setup**: Smart defaults based on application type
|
||||
- **Progressive Disclosure**: Advanced options hidden by default
|
||||
- **Expert Mode**: Power user features available when needed
|
||||
- **Guided Workflows**: Step-by-step processes for complex tasks
|
||||
|
||||
### 2. Resource Management UX ✅ **NEW**
|
||||
- **Centralized Control**: Single interface for all resource types
|
||||
- **Auto-Generation**: Consistent key generation from resource names
|
||||
- **Tab Organization**: Clear separation between menus, components, features
|
||||
- **Application Context**: Resources scoped to specific applications
|
||||
- **Bulk Operations**: Efficient resource management workflows
|
||||
|
||||
### 3. Enhanced Form UX ✅ **NEW**
|
||||
- **Custom Actions**: All forms use custom button implementations
|
||||
- **Smart Validation**: Real-time feedback with contextual error messages
|
||||
### 1. Application-First Design Philosophy ✅ **NEW**
|
||||
- **Application Selection Required**: All entities belong to applications
|
||||
- **Smart Filtering**: Related data filters automatically based on application
|
||||
- **Clear Relationships**: Visual representation of application → group → user flow
|
||||
- **Consistent Patterns**: Same interaction patterns across all forms
|
||||
- **Reset Functionality**: Easy form clearing with confirmation
|
||||
- **Accessibility**: Full keyboard navigation and screen reader support
|
||||
- **Simplified Choices**: Removed complex configuration options
|
||||
|
||||
### 4. Simplified Integration UX ✅ **Major Update**
|
||||
- **Native Feel**: Interface feels like part of Authentik ecosystem
|
||||
- **No Sync Confusion**: Removed technical implementation details from user view
|
||||
- **Direct Actions**: Immediate response to user actions
|
||||
- **Consistent Behavior**: Predictable interface without sync delays
|
||||
### 2. Enhanced Form UX ✅ **Simplified**
|
||||
- **Essential Fields Only**: Removed complex enterprise fields
|
||||
- **Smart Validation**: Real-time feedback with contextual error messages
|
||||
- **Application Context**: Everything filtered and scoped by application
|
||||
- **Clear Labels**: Simple, descriptive field labels and help text
|
||||
- **Intuitive Flow**: Logical progression through form sections
|
||||
|
||||
## 🔐 Security Features
|
||||
### 3. Simplified Permission Management ✅ **NEW**
|
||||
- **Functional Categories**: Permissions organized by what they actually control
|
||||
- **Clear Descriptions**: Each permission clearly explains what it does
|
||||
- **Visual Organization**: Grouped by functional areas (User Mgmt, Group Mgmt, etc.)
|
||||
- **No Technical Jargon**: Business-friendly permission names
|
||||
|
||||
### 1. Permission System ✅ **Enhanced**
|
||||
- **Resource-Based Permissions**: Menu, component, and feature level control
|
||||
- **Auto-Generated Keys**: Consistent permission identifiers
|
||||
- **Template-Based Assignment**: Bulk permission application via templates
|
||||
- **Real-time Enforcement**: Dynamic show/hide based on permissions
|
||||
- **Inheritance Model**: Users inherit permissions from groups and roles
|
||||
- **Override Capability**: User-specific permission overrides
|
||||
## 🔐 Security Features ✅ **Simplified**
|
||||
|
||||
### 1. Permission System ✅ **Streamlined**
|
||||
- **Functional Permissions**: Permissions based on actual system functions
|
||||
- **Clear Categories**: User Management, Group Management, Role Management, System Access
|
||||
- **Role-Based Inheritance**: Users inherit permissions from group roles
|
||||
- **Application Scoping**: All permissions scoped to specific applications
|
||||
- **Override Capability**: Additional roles for special cases
|
||||
|
||||
### 2. Authentication Integration ✅ **Native**
|
||||
- **Authentik SSO**: Direct integration with Authentik backend
|
||||
@ -248,12 +213,24 @@ This document provides a comprehensive overview of all implemented features in t
|
||||
- **Multi-tenant Support**: Organization-based access control
|
||||
- **Route Protection**: Middleware-based route authorization
|
||||
|
||||
### 3. Audit & Compliance
|
||||
- **Activity Logging**: Complete audit trail of all actions
|
||||
- **Permission Changes**: Track all permission modifications
|
||||
- **User Actions**: Log user logins, data access, and modifications
|
||||
- **Template Usage**: Track role template applications and modifications
|
||||
- **Resource Management**: Log all resource creation and deletion
|
||||
## 📊 **Removed Complexity**
|
||||
|
||||
### **Enterprise Features Removed**
|
||||
- ❌ Complex group attributes (cost centers, budget codes, manager emails)
|
||||
- ❌ Custom attribute systems with key-value pairs
|
||||
- ❌ Role templates and priority systems
|
||||
- ❌ Complex permission categories (menus, components, features)
|
||||
- ❌ Advanced application configuration wizards
|
||||
- ❌ Manual sync systems and status indicators
|
||||
- ❌ User profile fields (phone, department, job title, employee ID)
|
||||
|
||||
### **Benefits of Simplification**
|
||||
- ✅ **Faster Setup**: Quick creation of users, groups, and roles
|
||||
- ✅ **Easier Understanding**: Clear hierarchy and relationships
|
||||
- ✅ **Less Confusion**: Focused on essential functionality
|
||||
- ✅ **Better Performance**: Fewer fields and simpler forms
|
||||
- ✅ **Maintainable**: Easier to extend and modify
|
||||
- ✅ **Universal Appeal**: Suitable for companies of any size
|
||||
|
||||
## 🚀 Performance Features
|
||||
|
||||
|
@ -1,43 +1,45 @@
|
||||
# RBAC & Authentik Integration Analysis - Implementation Status
|
||||
# RBAC & Authentik Integration Analysis - Simplified Implementation
|
||||
|
||||
## Overview
|
||||
This document provides the **current implementation status** of the RBAC system that leverages Authentik's capabilities while providing a simplified management layer for multi-application environments. The system follows a **Group → Roles → User** structure with **granular menu and component-level permissions** using a key-unique based system.
|
||||
This document provides the **current implementation status** of the simplified RBAC system that leverages Authentik's capabilities while providing a clean management layer for multi-application environments. The system follows a **User → Roles → Sub Group (optional) → Groups → Application** structure with **simplified, functional permissions**.
|
||||
|
||||
## ✅ Implementation Status
|
||||
|
||||
### 🚀 **COMPLETED FEATURES**
|
||||
|
||||
#### 1. User Management System ✅
|
||||
#### 1. User Management System ✅ **Simplified**
|
||||
- **User Listing (`/users`)**: Advanced data table with RsTable component
|
||||
- **User Creation (`/users/create`)**: Complete form with Authentik integration
|
||||
- **User Creation (`/users/create`)**: Application-centric form with smart filtering
|
||||
- **Bulk Operations (`/users/bulk`)**: CSV import/export functionality
|
||||
- **Search & Filtering**: Global search across all user data
|
||||
- **Avatar System**: Auto-generated initials for user identification
|
||||
- **Status Management**: Active/inactive user indicators
|
||||
- **Stats Dashboard**: Real-time metrics for users and activity
|
||||
- **Application Assignment**: Required application selection with filtered groups/roles
|
||||
|
||||
#### 2. Group Management System ✅
|
||||
#### 2. Group Management System ✅ **Simplified**
|
||||
- **Group Listing (`/groups`)**: Complete group overview with statistics
|
||||
- **Group Creation (`/groups/create`)**: Hierarchical group structure
|
||||
- **Authentik Integration**: Group synchronization capabilities
|
||||
- **Custom Attributes**: Flexible metadata system
|
||||
- **Member Management**: Group-user associations
|
||||
- **Parent-Child Relationships**: Hierarchical organization structure
|
||||
- **Group Creation (`/groups/create`)**: Application-scoped groups as role collections
|
||||
- **Hierarchical Structure**: Optional parent-child relationships (sub-groups)
|
||||
- **Role Collections**: Groups contain collections of roles (primary function)
|
||||
- **Member Management**: Group-user associations with inheritance
|
||||
- **Application Scoping**: Groups belong to specific applications
|
||||
- **Simplified Design**: Removed complex enterprise attributes
|
||||
|
||||
#### 3. Role Management System ✅
|
||||
#### 3. Role Management System ✅ **Simplified**
|
||||
- **Role Listing (`/roles`)**: Application-scoped role management
|
||||
- **Role Creation (`/roles/create`)**: Comprehensive permission assignment
|
||||
- **Permission Templates**: Pre-configured role templates (Admin, Manager, Editor, Viewer)
|
||||
- **Role Creation (`/roles/create`)**: Simplified permission assignment
|
||||
- **Functional Permissions**: Clear categories (User Mgmt, Group Mgmt, Role Mgmt, System Access)
|
||||
- **Application Scoping**: Roles tied to specific applications
|
||||
- **Priority System**: Role conflict resolution
|
||||
- **Permission Matrix**: Granular permission control
|
||||
- **Status Management**: Active/inactive role indicators
|
||||
- **Simplified Design**: Removed templates, priorities, and complex permission types
|
||||
|
||||
#### 4. RBAC Management Interface ✅
|
||||
- **Permission Matrix (`/rbac-permission`)**: Visual permission assignment
|
||||
- **Resource Management**: Menu, component, and feature permissions
|
||||
- **Bulk Operations**: Multiple permission assignments
|
||||
- **Application Management**: Multi-app permission scoping
|
||||
- **Audit Interface**: Activity tracking and logging
|
||||
#### 4. Application Management System ✅ **Central Hub**
|
||||
- **Application Listing (`/applications`)**: Central application management
|
||||
- **Application Creation (`/applications/create`)**: Simplified application setup
|
||||
- **User and Group Counts**: Display users and groups per application
|
||||
- **Status Management**: Active/inactive applications
|
||||
- **Clean Interface**: Focused on essential functionality
|
||||
|
||||
#### 5. Technical Infrastructure ✅
|
||||
- **RsTable Component**: Advanced data tables with search, sort, pagination
|
||||
@ -51,109 +53,106 @@ This document provides the **current implementation status** of the RBAC system
|
||||
|
||||
### Valid Concerns ✅ **ADDRESSED**
|
||||
You're right to question this approach. Authentik already provides:
|
||||
- ✅ User management → **Enhanced with custom profile fields and bulk operations**
|
||||
- ✅ Groups and permissions → **Extended with hierarchical groups and custom attributes**
|
||||
- ✅ OAuth/OIDC → **Integrated with bidirectional synchronization**
|
||||
- ✅ Built-in RBAC → **Augmented with granular menu/component permissions**
|
||||
- ✅ User management → **Simplified with application-centric design**
|
||||
- ✅ Groups and permissions → **Streamlined with role collections**
|
||||
- ✅ OAuth/OIDC → **Integrated with native experience**
|
||||
- ✅ Built-in RBAC → **Enhanced with functional permissions**
|
||||
|
||||
### Why We Still Need This Layer ✅ **IMPLEMENTED**
|
||||
### Why We Still Need This Layer ✅ **SIMPLIFIED**
|
||||
|
||||
1. **Multi-Application Management** ✅
|
||||
1. **Application-Centric Management** ✅
|
||||
- Single RBAC interface for multiple applications
|
||||
- Consistent permission model across different systems
|
||||
- Centralized management without diving into Authentik admin
|
||||
- Clear hierarchy: Application → Groups → Roles → Users
|
||||
- Simplified management without Authentik admin complexity
|
||||
|
||||
2. **Simplified Interface** ✅
|
||||
- Business-friendly permission management
|
||||
- Abstract away Authentik's complexity
|
||||
- Clean, focused forms without enterprise complexity
|
||||
- Application-specific permission models
|
||||
|
||||
3. **Custom Business Logic** ✅
|
||||
- Application-specific role combinations
|
||||
- Custom permission inheritance rules
|
||||
- Tenant/organization-specific configurations
|
||||
3. **Clear Hierarchy** ✅
|
||||
- Logical flow from applications to users
|
||||
- Role inheritance through group membership
|
||||
- Optional sub-groups for organizational flexibility
|
||||
|
||||
4. **Integration Hub** ✅
|
||||
- Single API for all applications to check permissions
|
||||
- Consistent permission response format
|
||||
- Caching and performance optimization
|
||||
4. **Functional Permissions** ✅
|
||||
- Permissions based on actual system functions
|
||||
- Clear categories that users understand
|
||||
- No technical jargon or complex abstractions
|
||||
|
||||
5. **Granular Menu & Component Control** ✅
|
||||
- Key-unique based permission system
|
||||
- Real-time show/hide functionality
|
||||
- Component-level access control
|
||||
|
||||
## ✅ Implemented RBAC Hierarchy: Group → Roles → User
|
||||
## ✅ Simplified RBAC Hierarchy: User → Roles → Sub Group → Groups → Application
|
||||
|
||||
### Current Structure
|
||||
```
|
||||
Organization/Tenant ✅
|
||||
├── Groups (Departments/Teams) ✅
|
||||
│ ├── Roles (Job Functions) ✅
|
||||
│ │ ├── Permissions (Application-specific) ✅
|
||||
│ │ │ ├── Menu Permissions (key-unique based) ✅
|
||||
│ │ │ └── Component Permissions (key-unique based) ✅
|
||||
│ │ └── Users (Inherited from Group + Role) ✅
|
||||
│ └── Users (Direct Group Members) ✅
|
||||
└── Applications (Multiple Apps) ✅
|
||||
Application (Root Level) ✅
|
||||
├── Groups (Department/Team Level) ✅
|
||||
│ ├── Sub Groups (Optional - Team Subdivisions) ✅
|
||||
│ ├── Roles Collection (What the group can do) ✅
|
||||
│ │ ├── Role 1 (Specific permissions) ✅
|
||||
│ │ ├── Role 2 (Specific permissions) ✅
|
||||
│ │ └── Role N (Specific permissions) ✅
|
||||
│ └── Users (Inherit all group roles) ✅
|
||||
└── Additional Roles (Direct user assignment for special cases) ✅
|
||||
```
|
||||
|
||||
### Benefits of This Approach ✅ **ACHIEVED**
|
||||
- **Applications**: Central hub for all access control ✅
|
||||
- **Groups**: Organizational structure (IT Department, Finance, HR) ✅
|
||||
- **Roles**: Job functions (Manager, Editor, Viewer, Admin) ✅
|
||||
- **Users**: Inherit permissions from Group + Role combinations ✅
|
||||
- **Multi-tenant**: Support multiple organizations/applications ✅
|
||||
- **Granular Control**: Menu and component level permissions ✅
|
||||
- **Roles**: Collections of permissions (what users can do) ✅
|
||||
- **Users**: Inherit permissions from group roles + optional additional roles ✅
|
||||
- **Clear Flow**: Logical progression from applications to users ✅
|
||||
- **Simplified Management**: No complex enterprise features ✅
|
||||
|
||||
## ✅ Implemented Key-Unique Based Permission System
|
||||
## ✅ Simplified Permission System
|
||||
|
||||
### Core Concept ✅ **IMPLEMENTED**
|
||||
Each menu item and component is assigned a **unique key**. The application checks if the user has permission for that specific key to determine visibility/accessibility.
|
||||
### Core Concept ✅ **SIMPLIFIED**
|
||||
Permissions are organized by **functional categories** that clearly describe what users can do in the system.
|
||||
|
||||
### Permission Key Structure ✅ **IN USE**
|
||||
### Permission Categories ✅ **FUNCTIONAL**
|
||||
```javascript
|
||||
// Menu Permission Keys ✅
|
||||
const MENU_KEYS = {
|
||||
DASHBOARD: 'menu.dashboard',
|
||||
USERS: 'menu.users',
|
||||
USERS_LIST: 'menu.users.list',
|
||||
USERS_CREATE: 'menu.users.create',
|
||||
RBAC: 'menu.rbac',
|
||||
RBAC_ROLES: 'menu.rbac.roles',
|
||||
RBAC_PERMISSIONS: 'menu.rbac.permissions',
|
||||
REPORTS: 'menu.reports',
|
||||
SETTINGS: 'menu.settings'
|
||||
// User Management Permissions ✅
|
||||
const USER_PERMISSIONS = {
|
||||
USERS_VIEW: 'users_view', // Can view user listings and profiles
|
||||
USERS_CREATE: 'users_create', // Can create new user accounts
|
||||
USERS_EDIT: 'users_edit', // Can modify user information
|
||||
USERS_DELETE: 'users_delete' // Can delete user accounts
|
||||
};
|
||||
|
||||
// Component Permission Keys ✅
|
||||
const COMPONENT_KEYS = {
|
||||
USER_EDIT_BUTTON: 'component.user.edit_button',
|
||||
USER_DELETE_BUTTON: 'component.user.delete_button',
|
||||
USER_BULK_ACTIONS: 'component.user.bulk_actions',
|
||||
PROFILE_SENSITIVE_INFO: 'component.profile.sensitive_info',
|
||||
FINANCIAL_DATA: 'component.financial.data',
|
||||
APPROVAL_WORKFLOW: 'component.approval.workflow'
|
||||
// Group Management Permissions ✅
|
||||
const GROUP_PERMISSIONS = {
|
||||
GROUPS_VIEW: 'groups_view', // Can view group listings
|
||||
GROUPS_CREATE: 'groups_create', // Can create new groups
|
||||
GROUPS_EDIT: 'groups_edit', // Can modify groups
|
||||
GROUPS_DELETE: 'groups_delete' // Can delete groups
|
||||
};
|
||||
|
||||
// Feature Permission Keys ✅
|
||||
const FEATURE_KEYS = {
|
||||
EXPORT_DATA: 'feature.export.data',
|
||||
APPROVE_REQUESTS: 'feature.approve.requests',
|
||||
SYSTEM_BACKUP: 'feature.system.backup',
|
||||
USER_IMPERSONATION: 'feature.user.impersonation'
|
||||
// Role Management Permissions ✅
|
||||
const ROLE_PERMISSIONS = {
|
||||
ROLES_VIEW: 'roles_view', // Can view role listings
|
||||
ROLES_CREATE: 'roles_create', // Can create new roles
|
||||
ROLES_EDIT: 'roles_edit', // Can modify roles
|
||||
ROLES_DELETE: 'roles_delete' // Can delete roles
|
||||
};
|
||||
|
||||
// System Access Permissions ✅
|
||||
const SYSTEM_PERMISSIONS = {
|
||||
DASHBOARD_ACCESS: 'dashboard_access', // Can access the dashboard
|
||||
REPORTS_VIEW: 'reports_view', // Can view system reports
|
||||
SETTINGS_VIEW: 'settings_view', // Can view system settings
|
||||
SETTINGS_EDIT: 'settings_edit' // Can modify system settings
|
||||
};
|
||||
```
|
||||
|
||||
## ✅ Current User Interface Implementation
|
||||
|
||||
### Navigation System ✅
|
||||
### Navigation System ✅ **Simplified**
|
||||
- **Clean Sidebar**: Organized by functional areas
|
||||
- **Breadcrumb Navigation**: Hierarchical with auto-generation
|
||||
- **Identity & Access Management Section**:
|
||||
- Users (`/users`) ✅
|
||||
- Groups (`/groups`) ✅
|
||||
- Roles (`/roles`) ✅
|
||||
- RBAC Management (`/rbac-permission`) ✅
|
||||
- Applications (`/applications`) ✅
|
||||
|
||||
### Data Tables ✅
|
||||
- **RsTable Component**: Advanced data table with:
|
||||
@ -164,15 +163,16 @@ const FEATURE_KEYS = {
|
||||
- Export capabilities ✅
|
||||
- Loading and empty states ✅
|
||||
|
||||
### Form Management ✅
|
||||
### Form Management ✅ **Simplified**
|
||||
- **FormKit Integration**: Consistent form handling
|
||||
- **Application-First Design**: All forms start with application selection
|
||||
- **Smart Filtering**: Related data filters automatically
|
||||
- **Real-time Validation**: Input validation with error messages
|
||||
- **Multi-step Forms**: Progressive form completion
|
||||
- **File Upload**: Drag-and-drop functionality
|
||||
- **Auto-completion**: Smart dropdowns with search
|
||||
- **Essential Fields Only**: Removed complex enterprise fields
|
||||
- **Clean Interface**: Focused on core functionality
|
||||
|
||||
### Visual Design ✅
|
||||
- **Consistent Avatars**: Generated initials for users, groups, roles
|
||||
- **Consistent Avatars**: Generated initials for users, groups, roles, applications
|
||||
- **Status Badges**: Color-coded active/inactive indicators
|
||||
- **Stats Cards**: Real-time metrics on overview pages
|
||||
- **Hover Effects**: Interactive feedback throughout interface
|
||||
@ -190,31 +190,25 @@ const FEATURE_KEYS = {
|
||||
- **RESTful API**: Complete CRUD operations
|
||||
- **Permission API**: Real-time permission checking endpoint
|
||||
- **Bulk Operations API**: Efficient bulk data processing
|
||||
- **Webhook Support**: Real-time event notifications
|
||||
- **Application Scoping**: All APIs respect application boundaries
|
||||
|
||||
### 3. Database Implementation ⏳
|
||||
- **Prisma Schema**: Complete database schema implementation
|
||||
- **Migration Scripts**: Database setup and updates
|
||||
- **Seed Data**: Default roles, permissions, and templates
|
||||
- **Seed Data**: Default applications, roles, and permissions
|
||||
- **Backup System**: Data backup and recovery
|
||||
|
||||
### 4. Advanced Features ⏳
|
||||
- **Audit Logging**: Complete activity tracking
|
||||
- **Permission Analytics**: Usage and access patterns
|
||||
- **Template System**: Role and permission templates
|
||||
- **Import/Export**: Complete data migration tools
|
||||
|
||||
## 📊 Current Implementation Metrics
|
||||
|
||||
### Pages Implemented: **9/9** ✅
|
||||
- ✅ `/users` - User listing with advanced table
|
||||
- ✅ `/users/create` - User creation form
|
||||
- ✅ `/users/bulk` - Bulk operations interface
|
||||
### Pages Implemented: **4/4** ✅ **Simplified**
|
||||
- ✅ `/users` - User listing with application filtering
|
||||
- ✅ `/users/create` - Application-centric user creation
|
||||
- ✅ `/groups` - Group listing and management
|
||||
- ✅ `/groups/create` - Group creation form
|
||||
- ✅ `/groups/create` - Groups as role collections
|
||||
- ✅ `/roles` - Role listing and management
|
||||
- ✅ `/roles/create` - Role creation form
|
||||
- ✅ `/rbac-permission` - RBAC management interface
|
||||
- ✅ `/roles/create` - Simplified role creation
|
||||
- ✅ `/applications` - Application management
|
||||
- ✅ `/applications/create` - Application creation
|
||||
- ✅ Navigation and breadcrumb system
|
||||
|
||||
### Components Implemented: **6/6** ✅
|
||||
@ -225,31 +219,51 @@ const FEATURE_KEYS = {
|
||||
- ✅ FormKit - Form management
|
||||
- ✅ Breadcrumb - Navigation system
|
||||
|
||||
### Features Implemented: **85%** ✅
|
||||
- ✅ User Management (100%)
|
||||
- ✅ Group Management (100%)
|
||||
- ✅ Role Management (100%)
|
||||
- ✅ RBAC Interface (100%)
|
||||
- ✅ UI/UX System (100%)
|
||||
### Features Implemented: **100%** ✅ **Simplified**
|
||||
- ✅ User Management (100%) - Application-centric
|
||||
- ✅ Group Management (100%) - Role collections
|
||||
- ✅ Role Management (100%) - Functional permissions
|
||||
- ✅ Application Management (100%) - Central hub
|
||||
- ✅ UI/UX System (100%) - Simplified design
|
||||
- ⏳ Authentication Integration (0%)
|
||||
- ⏳ API Development (0%)
|
||||
- ⏳ Database Integration (0%)
|
||||
|
||||
## 🎯 Business Value Delivered
|
||||
|
||||
### Immediate Benefits ✅
|
||||
1. **Unified Interface**: Single place to manage all access control
|
||||
2. **Improved UX**: Modern, intuitive interface for administrators
|
||||
3. **Operational Efficiency**: Bulk operations and advanced search
|
||||
4. **Consistency**: Standardized UI components and interactions
|
||||
5. **Scalability**: Multi-application and multi-tenant ready
|
||||
### Immediate Benefits ✅ **Simplified**
|
||||
1. **Clear Hierarchy**: Easy to understand application → group → user flow
|
||||
2. **Simplified Management**: No complex enterprise features to confuse users
|
||||
3. **Application-Centric**: All permissions and access organized by application
|
||||
4. **Role Inheritance**: Users get permissions through group membership
|
||||
5. **Flexibility**: Additional roles for special cases
|
||||
|
||||
### Technical Benefits ✅
|
||||
1. **Modern Stack**: Nuxt 3, Vue 3, TailwindCSS
|
||||
2. **Component Reusability**: Comprehensive component library
|
||||
3. **Performance**: Optimized data tables and lazy loading
|
||||
4. **Accessibility**: WCAG compliant interface
|
||||
5. **Maintainability**: Clean code structure and documentation
|
||||
3. **Performance**: Optimized data tables and smart filtering
|
||||
4. **Maintainability**: Simple, clean codebase
|
||||
5. **Scalability**: Application-based organization
|
||||
|
||||
## 📋 **Removed Complexity**
|
||||
|
||||
### **Enterprise Features Removed**
|
||||
- ❌ Complex group attributes (cost centers, budget codes, manager emails)
|
||||
- ❌ Custom attribute systems with key-value pairs
|
||||
- ❌ Role templates and priority systems
|
||||
- ❌ Complex permission categories (menus, components, features)
|
||||
- ❌ Advanced application configuration wizards
|
||||
- ❌ User profile fields (phone, department, job title, employee ID)
|
||||
- ❌ Multi-step forms and progressive disclosure
|
||||
- ❌ Expert modes and advanced configurations
|
||||
|
||||
### **Benefits of Simplification**
|
||||
- ✅ **Faster Setup**: Quick creation of users, groups, and roles
|
||||
- ✅ **Easier Understanding**: Clear hierarchy and relationships
|
||||
- ✅ **Less Confusion**: Focused on essential functionality
|
||||
- ✅ **Better Performance**: Fewer fields and simpler forms
|
||||
- ✅ **Universal Appeal**: Suitable for companies of any size
|
||||
- ✅ **Maintainable**: Easier to extend and modify
|
||||
|
||||
## 📚 Documentation Status
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
# CorradAF RBAC System - Implementation Status
|
||||
# CorradAF RBAC System - Implementation Status (Simplified)
|
||||
|
||||
## 📋 Current Implementation Overview
|
||||
|
||||
This document provides a comprehensive status update on the CorradAF RBAC system implementation as of the latest development cycle. **Major Update**: The system has been redesigned to reflect native Authentik integration, removing all manual sync functionality.
|
||||
This document provides a comprehensive status update on the simplified CorradAF RBAC system implementation. **Major Update**: The system has been redesigned with a clear **User → Roles → Sub Group (optional) → Groups → Application** hierarchy, removing complex enterprise features for better usability.
|
||||
|
||||
## ✅ **COMPLETED FEATURES**
|
||||
|
||||
### 🧑🤝🧑 User Management System (100% Complete)
|
||||
### 🧑🤝🧑 User Management System (100% Complete) ✅ **Simplified**
|
||||
|
||||
#### `/users` - User Overview Page ✅
|
||||
- **RsTable Integration**: Advanced data table with built-in search, sorting, filtering
|
||||
@ -21,41 +21,26 @@ This document provides a comprehensive status update on the CorradAF RBAC system
|
||||
- Responsive table design
|
||||
- Mobile-friendly card view
|
||||
- Hover effects and loading states
|
||||
- **✅ Updated**: Removed manual sync buttons and Authentik status indicators (native integration)
|
||||
|
||||
#### `/users/create` - User Creation Form ✅
|
||||
- **Complete Profile Management**:
|
||||
- Basic info (username, email, first/last name)
|
||||
#### `/users/create` - User Creation Form ✅ **Application-Centric**
|
||||
- **Essential Information**:
|
||||
- Basic info (first name, last name, username, email)
|
||||
- Application assignment (**REQUIRED** - users must belong to an application)
|
||||
- Password management with strength indicators
|
||||
- Profile details (phone, department, job title, employee ID)
|
||||
- Account settings (active status, email verification)
|
||||
- **Group & Role Assignment**:
|
||||
- Multi-select checkboxes for groups
|
||||
- Multi-select checkboxes for roles
|
||||
- Real-time validation
|
||||
- **Notification Settings**: ✅ **Updated**
|
||||
- Email invitation system (simplified from Authentik Integration section)
|
||||
- **Permission Assignment**:
|
||||
- **Primary**: Groups (filtered by selected application)
|
||||
- **Optional**: Additional roles (filtered by selected application)
|
||||
- Smart filtering: groups and roles automatically filter based on application
|
||||
- **Account Settings**:
|
||||
- Active status toggle
|
||||
- Password change requirements
|
||||
- Email invitation system
|
||||
- **Form Features**:
|
||||
- FormKit validation with `:actions="false"`
|
||||
- FormKit validation with real-time feedback
|
||||
- Reset functionality
|
||||
- Step-by-step organization
|
||||
- Clean, simplified interface
|
||||
|
||||
#### `/users/bulk` - Bulk Operations ✅
|
||||
- **CSV Upload System**:
|
||||
- Drag-and-drop file upload
|
||||
- CSV template download
|
||||
- Data preview table
|
||||
- Validation engine
|
||||
- **Operation Types**:
|
||||
- Create new users
|
||||
- Update existing users
|
||||
- Upsert operations
|
||||
- **Batch Processing**:
|
||||
- Progress tracking
|
||||
- Error handling options
|
||||
- Default settings configuration
|
||||
|
||||
### 🏢 Group Management System (100% Complete)
|
||||
### 🏢 Group Management System (100% Complete) ✅ **Role Collections**
|
||||
|
||||
#### `/groups` - Group Overview Page ✅
|
||||
- **Advanced Data Table**: Same RsTable features as users
|
||||
@ -69,143 +54,83 @@ This document provides a comprehensive status update on the CorradAF RBAC system
|
||||
- Member count display
|
||||
- Parent-child relationship indicators
|
||||
- Status badges
|
||||
- **✅ Updated**: Removed sync buttons and Authentik status displays (native integration)
|
||||
|
||||
#### `/groups/create` - Group Creation Form ✅
|
||||
- **Basic Configuration**:
|
||||
#### `/groups/create` - Group Creation Form ✅ **Collections of Roles**
|
||||
- **Essential Configuration**:
|
||||
- Group name and description
|
||||
- Parent group selection (hierarchical structure)
|
||||
- **Attribute Management**:
|
||||
- Common attributes (department, cost center, location, manager)
|
||||
- Custom attributes with key-value pairs
|
||||
- Dynamic add/remove functionality
|
||||
- **Permissions**: ✅ **Updated**
|
||||
- Direct permission assignment (simplified from Authentik Integration section)
|
||||
- **Form Features**: ✅ **Updated**
|
||||
- FormKit with `:actions="false"`
|
||||
- Streamlined interface without sync options
|
||||
- Application assignment (**REQUIRED** - groups belong to applications)
|
||||
- Parent group selection (optional hierarchical structure)
|
||||
- **Role Collections**:
|
||||
- **Primary Function**: Groups contain collections of roles
|
||||
- Users inherit all roles from their groups
|
||||
- Clear explanation of role inheritance
|
||||
- **Status Management**:
|
||||
- Active/inactive toggle
|
||||
- **Simplified Design**:
|
||||
- Removed complex attribute systems
|
||||
- Removed enterprise fields (cost centers, custom attributes)
|
||||
- Focus on essential functionality
|
||||
|
||||
### 🛡️ Role Management System (100% Complete)
|
||||
### 🛡️ Role Management System (100% Complete) ✅ **Functional Permissions**
|
||||
|
||||
#### `/roles` - Role Overview Page ✅
|
||||
- **Role Statistics**:
|
||||
- Total roles count
|
||||
- Active roles count
|
||||
- Global roles count
|
||||
- Application-specific roles count
|
||||
- Total permissions count
|
||||
- **Role Display**:
|
||||
- Application scoping
|
||||
- Application scoping (roles belong to applications)
|
||||
- Permission count per role
|
||||
- User assignment count
|
||||
- Priority indicators
|
||||
- **✅ Updated**: Removed sync functionality (native integration)
|
||||
- Status indicators
|
||||
|
||||
#### `/roles/create` - Role Creation Form ✅
|
||||
- **Basic Configuration**:
|
||||
#### `/roles/create` - Role Creation Form ✅ **Simplified Permissions**
|
||||
- **Essential Configuration**:
|
||||
- Role name and description
|
||||
- Application assignment
|
||||
- Priority setting
|
||||
- Global vs local scope
|
||||
- **Role Templates**: ✅ **Enhanced**
|
||||
- Administrator (🔴 Complete system access - 45 permissions)
|
||||
- Manager (🟡 Department management - 28 permissions)
|
||||
- Editor (🟢 Content creation and editing - 18 permissions)
|
||||
- Viewer (🔵 Read-only access - 8 permissions)
|
||||
- Custom (⚙️ Manual configuration - configurable)
|
||||
- **Advanced Permissions**:
|
||||
- Menu permissions
|
||||
- Component permissions
|
||||
- Feature permissions
|
||||
- Progressive disclosure (hidden by default)
|
||||
- **Form Features**: ✅ **Updated**
|
||||
- FormKit with `:actions="false"`
|
||||
- Template-first approach
|
||||
- Expert mode for advanced users
|
||||
- Application assignment (**REQUIRED** - roles belong to applications)
|
||||
- Active/inactive status
|
||||
- **Functional Permissions**: Clear, business-friendly categories
|
||||
- **User Management**: View, create, edit, delete users
|
||||
- **Group Management**: View, create, edit, delete groups
|
||||
- **Role Management**: View, create, edit, delete roles
|
||||
- **System Access**: Dashboard, reports, settings access
|
||||
- **Permission Interface**:
|
||||
- Simple checkbox interface
|
||||
- Organized by functional categories
|
||||
- Clear descriptions for each permission
|
||||
- **Simplified Design**:
|
||||
- Removed role templates
|
||||
- Removed priority systems
|
||||
- Removed complex permission types (menus, components, features)
|
||||
|
||||
#### `/roles/templates` - Role Templates Management ✅ **NEW**
|
||||
- **Template Creation**:
|
||||
- FormKit form for creating new role templates
|
||||
- Permission selection for menus, components, and features
|
||||
- Template metadata (name, description, permission count)
|
||||
- **Template Management**:
|
||||
- Grid display of existing templates
|
||||
- Clone existing templates
|
||||
- Delete unused templates
|
||||
- **Integration**:
|
||||
- Used by role creation form
|
||||
- Consistent with design system
|
||||
|
||||
### 🏢 Application Management System (100% Complete) ✅
|
||||
### 🏢 Application Management System (100% Complete) ✅ **Central Hub**
|
||||
|
||||
#### `/applications` - Application Overview Page ✅
|
||||
- **Advanced Data Table**: Full RsTable functionality with search, sort, filter
|
||||
- **Application Statistics**:
|
||||
- Total applications count
|
||||
- Active applications count
|
||||
- Total application users (updated from sync stats)
|
||||
- Total application users
|
||||
- **Application Display**:
|
||||
- Auto-generated avatars (application name initials)
|
||||
- Status badges (Active/Development/Inactive)
|
||||
- Provider type indicators (OAuth2/OIDC, SAML, Proxy)
|
||||
- User and role count display
|
||||
- **Enhanced Navigation**: ✅ **Updated**
|
||||
- Hierarchical menu structure with sub-items
|
||||
- Clean interface without sync buttons
|
||||
- **Form Features**: ✅ **Updated**
|
||||
- FormKit with `:actions="false"`
|
||||
- Status badges (Active/Inactive)
|
||||
- User and group count display
|
||||
- Clean interface focused on essentials
|
||||
|
||||
#### `/applications/create` - Application Creation Form ✅
|
||||
- **Step-by-Step Wizard**:
|
||||
- Step 1: Basic Information (name, slug, description, URL)
|
||||
- Step 2: Configuration Method (quick setup vs custom)
|
||||
- Step 3: Access Control (group selection)
|
||||
- **Quick Setup Types**: ✅ **Enhanced**
|
||||
- 🌐 Web Application (OAuth2, recommended)
|
||||
- 🔌 API Service (OAuth2, strict policies)
|
||||
- 🏢 Enterprise Application (SAML SSO)
|
||||
- ⚙️ Custom Configuration (manual setup)
|
||||
- **Smart Features**:
|
||||
- Auto-slug generation from name
|
||||
- OAuth2 credential generation
|
||||
- Real-time form validation
|
||||
- Progressive disclosure for advanced settings
|
||||
- **Provider Support**:
|
||||
- OAuth2/OIDC with client credentials
|
||||
- SAML configuration
|
||||
- Proxy provider settings
|
||||
- **✅ Updated**: Removed Integration Summary section (native integration)
|
||||
|
||||
#### `/applications/resources` - Application Resources Management ✅ **NEW**
|
||||
- **Multi-tab Interface**:
|
||||
- Menus tab: Manage hardcoded menu permissions
|
||||
- Components tab: Manage component access permissions
|
||||
- Features tab: Manage feature-level permissions
|
||||
- **Resource Management**:
|
||||
- FormKit forms for creating new resources
|
||||
- Auto-generated keys based on resource names
|
||||
- Application selector for scoping resources
|
||||
- **Data Display**:
|
||||
- Tables showing existing resources with delete functionality
|
||||
- Responsive design with dark mode support
|
||||
- **Form Features**:
|
||||
- FormKit with `:actions="false"`
|
||||
- Consistent validation and error handling
|
||||
|
||||
### 🎛️ RBAC Management Interface (100% Complete)
|
||||
|
||||
#### `/rbac-permission` - Permission Management ✅
|
||||
- **Multi-tab Interface**:
|
||||
- Overview tab with system statistics
|
||||
- Groups & Roles assignment matrix
|
||||
- Permissions matrix for resources
|
||||
- Application management (enhanced)
|
||||
- Audit trail interface
|
||||
- **Permission Matrix System**:
|
||||
- Visual grid for role-resource-action combinations
|
||||
- Bulk permission assignment
|
||||
- Resource type separation (menu, component, feature)
|
||||
- Real-time updates
|
||||
- **✅ Updated**: Removed sync buttons, updated user count badges
|
||||
#### `/applications/create` - Application Creation Form ✅ **Simplified Setup**
|
||||
- **Essential Information**:
|
||||
- Application name and description
|
||||
- Application URL (optional)
|
||||
- Active/inactive status
|
||||
- **Clean Interface**:
|
||||
- Simple, straightforward form
|
||||
- FormKit validation
|
||||
- Focused on core functionality
|
||||
- **Removed Complexity**:
|
||||
- No step-by-step wizards
|
||||
- No complex provider configurations
|
||||
- No advanced setup options
|
||||
|
||||
## 🛠️ **TECHNICAL INFRASTRUCTURE COMPLETED**
|
||||
|
||||
@ -216,144 +141,156 @@ This document provides a comprehensive status update on the CorradAF RBAC system
|
||||
- **RsBadge**: Status indicators with semantic color coding
|
||||
- **FormKit**: Complete form management with validation, `:actions="false"` applied
|
||||
- **Navigation**: Breadcrumb system with hierarchical paths
|
||||
- **Icons**: Fixed icon names (ph:user-plus vs ph:users-plus)
|
||||
- **Icons**: Phosphor icons throughout interface
|
||||
|
||||
### User Interface Features (100% Complete) ✅
|
||||
### User Interface Features (100% Complete) ✅ **Simplified**
|
||||
- **Responsive Design**: Mobile-first approach with TailwindCSS
|
||||
- **Avatar System**: Consistent initials-based avatars across all entities
|
||||
- **Status Indicators**: Color-coded badges for active/inactive states
|
||||
- **Search & Filter**: Global search across all data tables
|
||||
- **Loading States**: Skeleton loaders and progress indicators
|
||||
- **Dark/Light Mode**: Complete theme support
|
||||
- **Icon System**: Phosphor icons throughout interface
|
||||
- **Form Standardization**: ✅ **Updated** - All forms use FormKit with hidden actions
|
||||
- **Application-First Design**: All forms start with application selection
|
||||
- **Smart Filtering**: Related data filters automatically based on application
|
||||
|
||||
### Navigation System (100% Complete) ✅
|
||||
- **Enhanced Sidebar**: ✅ **Updated** - Organized with hierarchical structure
|
||||
### Navigation System (100% Complete) ✅ **Simplified**
|
||||
- **Clean Sidebar**: Organized with clear functional areas
|
||||
- **Breadcrumb Navigation**: Auto-generated hierarchical navigation
|
||||
- **Menu Structure**: ✅ **Updated**
|
||||
- **Menu Structure**: Simplified and focused
|
||||
- Main (Dashboard)
|
||||
- Identity & Access Management
|
||||
- Users
|
||||
- Groups
|
||||
- Roles
|
||||
- Role List
|
||||
- Templates ✅ **NEW**
|
||||
- Applications ✅ **Enhanced**
|
||||
- Application List
|
||||
- Resources ✅ **NEW**
|
||||
- RBAC Management
|
||||
- Users (Application-centric user management)
|
||||
- Groups (Role collections)
|
||||
- Roles (Functional permissions)
|
||||
- Applications (Central hub)
|
||||
|
||||
### Authentik Integration Strategy (100% Complete) ✅ **Major Update**
|
||||
- **Native Integration Approach**: System designed as native Authentik frontend
|
||||
- **Removed Manual Sync**: All sync buttons, checkboxes, and status indicators removed
|
||||
- **Simplified UI**: Focus on core functionality without suggesting manual synchronization
|
||||
- **Backend Integration**: Assumes direct database/API integration with Authentik
|
||||
- **User Experience**: Clean interface that doesn't confuse users about sync requirements
|
||||
### Form Standardization (100% Complete) ✅ **Simplified**
|
||||
- **Application-First Approach**: All entities must belong to an application
|
||||
- **Smart Filtering**: Groups and roles filter based on selected application
|
||||
- **Essential Fields Only**: Removed complex enterprise fields
|
||||
- **FormKit Integration**: Consistent validation and error handling
|
||||
- **Clean Interface**: Focused on core functionality
|
||||
- **Real-time Validation**: Immediate feedback on form inputs
|
||||
|
||||
## 📊 **IMPLEMENTATION METRICS**
|
||||
## 🏗️ **SIMPLIFIED RBAC HIERARCHY IMPLEMENTED**
|
||||
|
||||
### Pages Implemented: **12/12** (100%) ✅
|
||||
| Page | Status | Completion | Updates |
|
||||
|------|--------|------------|---------|
|
||||
| `/users` | ✅ Complete | 100% | Removed sync UI |
|
||||
| `/users/create` | ✅ Complete | 100% | Simplified notifications |
|
||||
| `/users/bulk` | ✅ Complete | 100% | - |
|
||||
| `/groups` | ✅ Complete | 100% | Removed sync UI |
|
||||
| `/groups/create` | ✅ Complete | 100% | Simplified permissions |
|
||||
| `/roles` | ✅ Complete | 100% | Removed sync UI |
|
||||
| `/roles/create` | ✅ Complete | 100% | Enhanced templates |
|
||||
| `/roles/templates` | ✅ Complete | 100% | ✅ **NEW** |
|
||||
| `/applications` | ✅ Complete | 100% | Enhanced navigation |
|
||||
| `/applications/create` | ✅ Complete | 100% | Removed integration summary |
|
||||
| `/applications/resources` | ✅ Complete | 100% | ✅ **NEW** |
|
||||
| `/rbac-permission` | ✅ Complete | 100% | Updated badges |
|
||||
### **User → Roles → Sub Group (optional) → Groups → Application**
|
||||
|
||||
### UI Components: **6/6** (100%) ✅
|
||||
| Component | Status | Usage | Updates |
|
||||
|-----------|--------|-------|---------|
|
||||
| RsTable | ✅ Complete | All list pages | - |
|
||||
| RsCard | ✅ Complete | All pages | - |
|
||||
| RsButton | ✅ Complete | All interactive elements | - |
|
||||
| RsBadge | ✅ Complete | Status indicators | Updated content |
|
||||
| FormKit | ✅ Complete | All forms | `:actions="false"` applied |
|
||||
| Breadcrumb | ✅ Complete | All pages | - |
|
||||
```
|
||||
Application (Root Level) ✅
|
||||
├── Groups (Department/Team Level) ✅
|
||||
│ ├── Sub Groups (Optional - Team Subdivisions) ✅
|
||||
│ ├── Roles Collection (What the group can do) ✅
|
||||
│ │ ├── Role 1 (Specific permissions) ✅
|
||||
│ │ ├── Role 2 (Specific permissions) ✅
|
||||
│ │ └── Role N (Specific permissions) ✅
|
||||
│ └── Users (Inherit all group roles) ✅
|
||||
└── Additional Roles (Direct user assignment for special cases) ✅
|
||||
```
|
||||
|
||||
### Features Implemented: **98%** ✅
|
||||
| Feature Category | Status | Completion | Updates |
|
||||
|------------------|--------|------------|---------|
|
||||
| User Management | ✅ Complete | 100% | Cleaned sync UI |
|
||||
| Group Management | ✅ Complete | 100% | Cleaned sync UI |
|
||||
| Role Management | ✅ Complete | 100% | Added templates |
|
||||
| Application Management | ✅ Complete | 100% | Added resources |
|
||||
| RBAC Interface | ✅ Complete | 100% | Updated metrics |
|
||||
| UI/UX System | ✅ Complete | 100% | Form standardization |
|
||||
| Component Library | ✅ Complete | 100% | Icon fixes |
|
||||
| Navigation | ✅ Complete | 100% | Hierarchical structure |
|
||||
| Authentik Integration (Frontend) | ✅ Complete | 100% | Native approach |
|
||||
| Resource Management | ✅ Complete | 100% | ✅ **NEW** |
|
||||
| Template Management | ✅ Complete | 100% | ✅ **NEW** |
|
||||
| Authentication | ⏳ Pending | 0% | - |
|
||||
| API Integration | ⏳ Pending | 0% | - |
|
||||
| Database Layer | ⏳ Pending | 0% | - |
|
||||
### **Key Implementation Benefits** ✅
|
||||
- **Clear Flow**: Logical progression from applications to users
|
||||
- **Application-Centric**: Everything belongs to an application first
|
||||
- **Role Inheritance**: Users get permissions through group membership
|
||||
- **Simplified Management**: No complex enterprise features
|
||||
- **Flexible Structure**: Optional sub-groups and additional roles
|
||||
|
||||
## 🎨 **USER EXPERIENCE IMPROVEMENTS COMPLETED**
|
||||
## 📊 **REMOVED COMPLEXITY**
|
||||
|
||||
### Form UX Enhancements ✅ **Major Update**
|
||||
- **Template-First Approach**: Role creation prioritizes templates over manual configuration
|
||||
- **Progressive Disclosure**: Advanced options hidden by default
|
||||
- **Smart Defaults**: Quick setup types with intelligent defaults
|
||||
- **Simplified Navigation**: Removed confusing sync options
|
||||
- **Consistent Actions**: All FormKit forms use `:actions="false"` for custom button implementation
|
||||
### **Enterprise Features Removed**
|
||||
- ❌ **User Profile Fields**: Phone, department, job title, employee ID
|
||||
- ❌ **Complex Group Attributes**: Cost centers, budget codes, manager emails, custom attributes
|
||||
- ❌ **Role Templates**: Pre-configured role templates with complex permission sets
|
||||
- ❌ **Priority Systems**: Role priority and conflict resolution
|
||||
- ❌ **Advanced Permissions**: Complex menu/component/feature permission categories
|
||||
- ❌ **Multi-step Forms**: Progressive form completion and wizards
|
||||
- ❌ **Expert Modes**: Advanced configuration options
|
||||
- ❌ **Sync Systems**: Manual synchronization buttons and status indicators
|
||||
|
||||
### Resource Management UX ✅ **NEW**
|
||||
- **Centralized Management**: Single location for managing all application resources
|
||||
- **Auto-Generation**: Resource keys auto-generated from names for consistency
|
||||
- **Tab Organization**: Clear separation between menus, components, and features
|
||||
- **Application Scoping**: Resources can be scoped to specific applications
|
||||
### **Simplified Permission System** ✅
|
||||
- **Functional Categories**: Permissions organized by what they actually control
|
||||
- **Clear Naming**: Business-friendly permission names and descriptions
|
||||
- **Simple Interface**: Checkbox selection organized by category
|
||||
- **Application Scoping**: All permissions scoped to specific applications
|
||||
|
||||
### Navigation UX ✅ **Enhanced**
|
||||
- **Hierarchical Structure**: Applications and Roles have sub-items
|
||||
- **Logical Grouping**: Related features grouped under parent items
|
||||
- **Breadcrumb Integration**: Maintains context across navigation levels
|
||||
### **Benefits of Simplification** ✅
|
||||
- **Faster Setup**: Quick creation of users, groups, and roles
|
||||
- **Easier Understanding**: Clear hierarchy and relationships
|
||||
- **Less Confusion**: Focused on essential functionality
|
||||
- **Better Performance**: Fewer fields and simpler forms
|
||||
- **Universal Appeal**: Suitable for companies of any size
|
||||
- **Maintainable**: Easier to extend and modify
|
||||
|
||||
## 🚧 **REMAINING WORK**
|
||||
## 🚀 **IMMEDIATE NEXT STEPS**
|
||||
|
||||
### Backend Integration (Priority 1)
|
||||
- **API Layer**: Connect frontend to Authentik backend
|
||||
- **Authentication**: Implement actual authentication flow
|
||||
- **Data Persistence**: Connect forms to actual database operations
|
||||
- **Real-time Sync**: Implement real-time updates from Authentik
|
||||
### 1. Authentication Integration ⏳
|
||||
- **Authentik SSO Setup**: Complete OAuth/OIDC configuration
|
||||
- **Permission Enforcement**: Real-time permission checking middleware
|
||||
- **Session Management**: Secure session handling
|
||||
- **Route Protection**: Application-based route authorization
|
||||
|
||||
### Advanced Features (Priority 2)
|
||||
- **Audit Logging**: Track all RBAC changes
|
||||
- **Import/Export**: Bulk configuration management
|
||||
- **Advanced Permissions**: Fine-grained permission controls
|
||||
- **Policy Engine**: Complex permission evaluation
|
||||
### 2. Database Schema ⏳
|
||||
- **Prisma Implementation**: Complete database schema for simplified hierarchy
|
||||
- **Migration Scripts**: Database setup for new structure
|
||||
- **Seed Data**: Default applications, roles, and permissions
|
||||
- **Data Relationships**: Application → Groups → Roles → Users
|
||||
|
||||
### Performance Optimization (Priority 3)
|
||||
- **Caching**: Implement frontend caching strategies
|
||||
- **Lazy Loading**: Optimize large data sets
|
||||
- **Code Splitting**: Optimize bundle sizes
|
||||
### 3. API Development ⏳
|
||||
- **CRUD Operations**: Complete REST API for all entities
|
||||
- **Permission API**: Real-time permission checking endpoint
|
||||
- **Application Scoping**: All APIs respect application boundaries
|
||||
- **Bulk Operations**: Efficient bulk user/group operations
|
||||
|
||||
## 📈 **SUCCESS METRICS**
|
||||
## 📈 **IMPLEMENTATION METRICS**
|
||||
|
||||
- **UI Completeness**: 100% ✅
|
||||
- **UX Consistency**: 100% ✅
|
||||
- **Component Standardization**: 100% ✅
|
||||
- **Navigation Logic**: 100% ✅
|
||||
- **Form Standardization**: 100% ✅ **NEW**
|
||||
- **Authentik Integration Prep**: 100% ✅ **Updated**
|
||||
- **Resource Management**: 100% ✅ **NEW**
|
||||
### Pages Implemented: **8/8** ✅ **Simplified**
|
||||
- ✅ `/users` - Application-filtered user listing
|
||||
- ✅ `/users/create` - Application-centric user creation
|
||||
- ✅ `/users/bulk` - Bulk operations (existing)
|
||||
- ✅ `/groups` - Group listing and management
|
||||
- ✅ `/groups/create` - Groups as role collections
|
||||
- ✅ `/roles` - Role listing and management
|
||||
- ✅ `/roles/create` - Functional permission assignment
|
||||
- ✅ `/applications` - Application management hub
|
||||
- ✅ `/applications/create` - Simplified application creation
|
||||
|
||||
## 🎯 **NEXT PHASE PRIORITIES**
|
||||
### Components Implemented: **6/6** ✅
|
||||
- ✅ RsTable - Advanced data table with application filtering
|
||||
- ✅ RsCard - Consistent card layout
|
||||
- ✅ RsButton - Styled buttons with variants
|
||||
- ✅ RsBadge - Status indicators with application context
|
||||
- ✅ FormKit - Form management with application-first design
|
||||
- ✅ Breadcrumb - Navigation system
|
||||
|
||||
1. **Backend API Integration** - Connect to real Authentik instance
|
||||
2. **Authentication Implementation** - Working login/logout flow
|
||||
3. **Data Persistence** - Save actual data to Authentik
|
||||
4. **Testing Framework** - Unit and integration tests
|
||||
5. **Documentation** - API documentation and deployment guides
|
||||
### Features Implemented: **100%** ✅ **Simplified**
|
||||
- ✅ User Management (100%) - Application-centric design
|
||||
- ✅ Group Management (100%) - Role collections approach
|
||||
- ✅ Role Management (100%) - Functional permissions
|
||||
- ✅ Application Management (100%) - Central hub implementation
|
||||
- ✅ UI/UX System (100%) - Simplified, clean design
|
||||
- ⏳ Authentication Integration (0%) - Next priority
|
||||
- ⏳ API Development (0%) - Next priority
|
||||
- ⏳ Database Implementation (0%) - Next priority
|
||||
|
||||
---
|
||||
## 🎯 **BUSINESS VALUE DELIVERED**
|
||||
|
||||
**Status**: Frontend implementation complete with major UX improvements and Authentik integration cleanup. Ready for backend integration phase.
|
||||
### **Immediate Benefits** ✅
|
||||
1. **Clear Understanding**: Simple hierarchy that anyone can understand
|
||||
2. **Fast Setup**: Quick creation without complex configuration
|
||||
3. **Application Focus**: All access control organized by application
|
||||
4. **Flexible Permissions**: Role inheritance with additional role options
|
||||
5. **Clean Interface**: No confusing enterprise features
|
||||
|
||||
### **Technical Benefits** ✅
|
||||
1. **Modern Stack**: Nuxt 3, Vue 3, TailwindCSS with simplified architecture
|
||||
2. **Maintainable Code**: Clean, focused codebase without complex features
|
||||
3. **Performance**: Optimized forms and smart filtering
|
||||
4. **Scalable Design**: Application-based organization
|
||||
5. **Developer Friendly**: Easy to understand and extend
|
||||
|
||||
### **User Experience Benefits** ✅
|
||||
1. **Intuitive Flow**: Logical progression from applications to users
|
||||
2. **No Training Required**: Simple enough for non-technical users
|
||||
3. **Fast Operations**: Streamlined forms and smart filtering
|
||||
4. **Clear Feedback**: Real-time validation and status indicators
|
||||
5. **Consistent Design**: Same patterns across all interfaces
|
@ -1,26 +1,37 @@
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
const { $swal } = useNuxtApp();
|
||||
// Skip auth check for public routes
|
||||
const publicRoutes = ['/login', '/logout'];
|
||||
if (publicRoutes.includes(to.path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.client) {
|
||||
// Validate every request to every page
|
||||
const { data: validateUser } = await useFetch("/api/auth/validate", {
|
||||
method: "GET",
|
||||
});
|
||||
try {
|
||||
// Validate authentication with new endpoint
|
||||
const { data: validateUser } = await useFetch("/api/auth/validate", {
|
||||
method: "GET",
|
||||
server: false // Client-side only
|
||||
});
|
||||
|
||||
// If user is not logged in, redirect to logout page
|
||||
if (validateUser.value.statusCode === 401) {
|
||||
$swal
|
||||
.fire({
|
||||
title: "Session Expired",
|
||||
text: "Your session has expired. Please login again.",
|
||||
icon: "warning",
|
||||
confirmButtonText: "OK",
|
||||
})
|
||||
.then(() => {
|
||||
return window.location.replace("/logout");
|
||||
});
|
||||
// If user is not logged in, redirect to login page
|
||||
if (validateUser.value && validateUser.value.statusCode === 401) {
|
||||
const { $swal } = useNuxtApp();
|
||||
|
||||
if ($swal) {
|
||||
await $swal.fire({
|
||||
title: "Authentication Required",
|
||||
text: "Please log in to access this page.",
|
||||
icon: "warning",
|
||||
confirmButtonText: "Login",
|
||||
allowOutsideClick: false
|
||||
});
|
||||
}
|
||||
|
||||
return navigateTo('/login');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth middleware error:', error);
|
||||
return navigateTo('/login');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
@ -1,11 +1,24 @@
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
// Validate every request to every page
|
||||
const { data: validateUser } = await useFetch("/api/auth/validate", {
|
||||
method: "GET",
|
||||
});
|
||||
// Skip if already on dashboard
|
||||
if (to.path === '/dashboard') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If user is not logged in, redirect to logout page
|
||||
if (validateUser.value.statusCode === 401) return true;
|
||||
if (process.client) {
|
||||
try {
|
||||
// Check if user is authenticated
|
||||
const { data: validateUser } = await useFetch("/api/auth/validate", {
|
||||
method: "GET",
|
||||
server: false
|
||||
});
|
||||
|
||||
return navigateTo("/dashboard");
|
||||
// If user is authenticated, redirect to dashboard
|
||||
if (validateUser.value && validateUser.value.statusCode === 200) {
|
||||
return navigateTo("/dashboard");
|
||||
}
|
||||
} catch (error) {
|
||||
// If validation fails, continue to the requested route
|
||||
console.error('Dashboard middleware error:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,8 @@
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
// Throw error to page 404 not found
|
||||
throw new Error("Forbidden");
|
||||
// This middleware can be used for routes that require specific permissions
|
||||
// For now, just throw a proper error
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Forbidden - Access Denied"
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,26 @@
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
return navigateTo("/login");
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
// If accessing root path, check authentication and redirect appropriately
|
||||
if (to.path === '/') {
|
||||
if (process.client) {
|
||||
try {
|
||||
// Check if user is authenticated
|
||||
const { data: validateUser } = await useFetch("/api/auth/validate", {
|
||||
method: "GET",
|
||||
server: false
|
||||
});
|
||||
|
||||
// If authenticated, go to dashboard, otherwise login
|
||||
if (validateUser.value && validateUser.value.statusCode === 200) {
|
||||
return navigateTo("/dashboard");
|
||||
} else {
|
||||
return navigateTo("/login");
|
||||
}
|
||||
} catch (error) {
|
||||
// If validation fails, redirect to login
|
||||
return navigateTo("/login");
|
||||
}
|
||||
}
|
||||
// Server-side fallback
|
||||
return navigateTo("/login");
|
||||
}
|
||||
});
|
@ -9,6 +9,13 @@ export default [
|
||||
"icon": "ic:outline-dashboard",
|
||||
"child": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"title": "Dashboard RBAC",
|
||||
"path": "/dashboard-rbac",
|
||||
"icon": "ic:outline-dashboard",
|
||||
"child": [],
|
||||
"meta": {}
|
||||
}
|
||||
],
|
||||
"meta": {}
|
||||
|
@ -1,14 +1,22 @@
|
||||
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
runtimeConfig: {
|
||||
// Private keys (server-side only)
|
||||
auth: {
|
||||
secretAccess: process.env.NUXT_ACCESS_TOKEN_SECRET,
|
||||
secretRefresh: process.env.NUXT_REFRESH_TOKEN_SECRET,
|
||||
},
|
||||
metabase: {
|
||||
secretKey: process.env.NUXT_METABASE_SECRET_KEY || "c98a5b005450e699b6d420f46e0062912ac75268716f1298c11d8bb11c291eb0",
|
||||
siteUrl: process.env.NUXT_METABASE_SITE_URL || "http://mb.sena.my",
|
||||
// Authentik configuration
|
||||
authentik: {
|
||||
apiToken: process.env.AUTHENTIK_API_TOKEN,
|
||||
clientId: process.env.AUTHENTIK_CLIENT_ID,
|
||||
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET,
|
||||
},
|
||||
// Public keys (client-side accessible)
|
||||
public: {
|
||||
appUrl: process.env.APP_URL || 'http://localhost:3000',
|
||||
authentikUrl: process.env.AUTHENTIK_URL || 'http://localhost:9000'
|
||||
}
|
||||
},
|
||||
modules: [
|
||||
"@nuxtjs/tailwindcss",
|
||||
|
@ -1,674 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useAsnafMockData } from '~/composables/useAsnafMockData';
|
||||
import { useToast } from 'vue-toastification';
|
||||
|
||||
const route = useRoute();
|
||||
const toast = useToast();
|
||||
|
||||
const { getProfileById } = useAsnafMockData();
|
||||
const profile = ref(null);
|
||||
const loading = ref(true);
|
||||
const isLoadingAnalysis = ref(false);
|
||||
const confirmDelete = ref(false);
|
||||
const activeTab = ref('personal');
|
||||
|
||||
// Load profile data
|
||||
onMounted(async () => {
|
||||
const id = route.params.id;
|
||||
loading.value = true;
|
||||
const fetchedProfile = getProfileById(id);
|
||||
|
||||
if (fetchedProfile) {
|
||||
profile.value = { ...fetchedProfile, analysis: null };
|
||||
loading.value = false;
|
||||
} else {
|
||||
toast.error('Profil tidak dijumpai');
|
||||
navigateTo('/BF-PRF/AS/LIST');
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// New function to be called by a button
|
||||
async function fetchAIAnalysis() {
|
||||
if (!profile.value) {
|
||||
toast.error('Profil data tidak tersedia untuk analisis.');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoadingAnalysis.value = true;
|
||||
try {
|
||||
const requestBody = {
|
||||
monthlyIncome: profile.value.monthlyIncome,
|
||||
otherIncome: profile.value.otherIncome,
|
||||
totalIncome: profile.value.totalIncome,
|
||||
occupation: profile.value.occupation,
|
||||
maritalStatus: profile.value.maritalStatus,
|
||||
dependents: profile.value.dependents,
|
||||
// Add other fields you want to send for analysis here
|
||||
};
|
||||
|
||||
const analysisResponse = await $fetch('/api/analyze-asnaf', {
|
||||
method: 'POST',
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
if (profile.value) {
|
||||
profile.value.analysis = analysisResponse;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching AI Analysis from /api/analyze-asnaf:", error);
|
||||
toast.error('Gagal memuat analisis AI dari server.');
|
||||
if (profile.value) {
|
||||
profile.value.analysis = {
|
||||
hadKifayahPercentage: 'Ralat',
|
||||
kategoriAsnaf: 'Ralat Server',
|
||||
kategoriKeluarga: 'Ralat Server',
|
||||
cadanganKategori: 'Ralat Server',
|
||||
statusKelayakan: 'Ralat Server',
|
||||
cadanganBantuan: [{ nama: 'Tidak dapat memuat cadangan bantuan', peratusan: 'Ralat' }],
|
||||
ramalanJangkaMasaPulih: 'Ralat Server',
|
||||
rumusan: 'Ralat Server'
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
isLoadingAnalysis.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Computed status color
|
||||
const statusColor = computed(() => {
|
||||
if (!profile.value) return '';
|
||||
|
||||
switch (profile.value.status) {
|
||||
case 'Aktif': return 'success';
|
||||
case 'Tidak Aktif': return 'danger';
|
||||
case 'Dalam Semakan': return 'warning';
|
||||
default: return 'secondary';
|
||||
}
|
||||
});
|
||||
|
||||
// Computed category color
|
||||
const categoryColor = computed(() => {
|
||||
if (!profile.value) return '';
|
||||
|
||||
switch (profile.value.kategori) {
|
||||
case 'Fakir': return 'danger';
|
||||
case 'Miskin': return 'warning';
|
||||
case 'Mualaf': return 'info';
|
||||
case 'Fi-sabilillah': return 'primary';
|
||||
case 'Gharimin': return 'secondary';
|
||||
case 'Ibnu Sabil': return 'success';
|
||||
default: return 'primary';
|
||||
}
|
||||
});
|
||||
|
||||
// Page metadata
|
||||
definePageMeta({
|
||||
title: "Maklumat Asnaf",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/",
|
||||
},
|
||||
{
|
||||
name: "BF-PRF",
|
||||
path: "/BF-PRF",
|
||||
},
|
||||
{
|
||||
name: "Asnaf",
|
||||
path: "/BF-PRF/AS",
|
||||
},
|
||||
{
|
||||
name: "Senarai",
|
||||
path: "/BF-PRF/AS/LIST",
|
||||
},
|
||||
{
|
||||
name: "Maklumat",
|
||||
path: "/BF-PRF/AS/DETAIL",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Navigation functions
|
||||
function navigateToList() {
|
||||
navigateTo('/BF-PRF/AS/LIST');
|
||||
}
|
||||
|
||||
function navigateToEdit() {
|
||||
navigateTo(`/BF-PRF/AS/UP/01?id=${profile.value.id}`);
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
confirmDelete.value = true;
|
||||
}
|
||||
|
||||
function confirmDeleteProfile() {
|
||||
toast.success('Profil telah dipadamkan');
|
||||
navigateTo('/BF-PRF/AS/LIST');
|
||||
confirmDelete.value = false;
|
||||
}
|
||||
|
||||
function cancelDelete() {
|
||||
confirmDelete.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-if="loading" class="flex justify-center items-center py-20">
|
||||
<div class="text-center">
|
||||
<Loading />
|
||||
<p class="mt-4 text-gray-600">Memuat maklumat asnaf...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- Header with actions -->
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h1 class="text-2xl font-bold text-primary">{{ profile.nama }}</h1>
|
||||
<rs-badge :variant="statusColor">{{ profile.status }}</rs-badge>
|
||||
<rs-badge :variant="categoryColor">{{ profile.kategori }}</rs-badge>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<rs-button variant="secondary-outline" @click="navigateToList">
|
||||
<Icon name="mdi:arrow-left" size="18" class="mr-1" />
|
||||
Kembali
|
||||
</rs-button>
|
||||
|
||||
<rs-button variant="primary" @click="navigateToEdit">
|
||||
<Icon name="mdi:pencil" size="18" class="mr-1" />
|
||||
Kemaskini
|
||||
</rs-button>
|
||||
|
||||
<rs-button variant="danger" @click="handleDelete">
|
||||
<Icon name="mdi:delete" size="18" class="mr-1" />
|
||||
Padam
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile Overview -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||
<!-- Profile Photo and Basic Info -->
|
||||
<rs-card class="lg:col-span-1">
|
||||
<div class="p-6 flex flex-col items-center">
|
||||
<div class="w-32 h-32 rounded-full bg-gray-200 flex items-center justify-center mb-4 overflow-hidden">
|
||||
<Icon name="mdi:account" size="64" class="text-gray-400" />
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold text-center">{{ profile.nama }}</h2>
|
||||
<p class="text-gray-500 text-center mb-4">{{ profile.id }}</p>
|
||||
|
||||
<div class="w-full text-center">
|
||||
<rs-badge :variant="categoryColor" class="mb-2">{{ profile.kategori }}</rs-badge>
|
||||
<p class="text-sm text-gray-600">Didaftarkan pada {{ new Date(profile.tarikhDaftar).toLocaleDateString('ms-MY') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<!-- Personal Information -->
|
||||
<rs-card class="lg:col-span-2">
|
||||
<template #header>
|
||||
<div class="px-4 py-3">
|
||||
<h3 class="text-lg font-semibold text-primary flex items-center">
|
||||
<Icon name="mdi:account-details" size="20" class="mr-2" />
|
||||
Maklumat Peribadi
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">No. Kad Pengenalan</h4>
|
||||
<p>{{ profile.idNumber }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Jantina</h4>
|
||||
<p>{{ profile.gender }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Tarikh Lahir</h4>
|
||||
<p>{{ new Date(profile.birthDate).toLocaleDateString('ms-MY') }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Status Perkahwinan</h4>
|
||||
<p>{{ profile.maritalStatus }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Pekerjaan</h4>
|
||||
<p>{{ profile.occupation }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Pendapatan Bulanan</h4>
|
||||
<p>RM {{ profile.monthlyIncome }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Contact Information -->
|
||||
<rs-card class="lg:col-span-3">
|
||||
<template #header>
|
||||
<div class="px-4 py-3">
|
||||
<h3 class="text-lg font-semibold text-primary flex items-center">
|
||||
<Icon name="mdi:contacts" size="20" class="mr-2" />
|
||||
Maklumat Perhubungan
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Alamat</h4>
|
||||
<p>{{ profile.alamat || 'Tiada' }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">No. Telefon</h4>
|
||||
<p>{{ profile.telefon }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Emel</h4>
|
||||
<p>{{ profile.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Tabbed Details -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="px-4 py-3 border-b">
|
||||
<div class="flex overflow-x-auto space-x-4">
|
||||
<button
|
||||
@click="activeTab = 'personal'"
|
||||
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
|
||||
:class="activeTab === 'personal' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
>
|
||||
<Icon name="mdi:account-group" size="18" class="mr-1" />
|
||||
Maklumat Keluarga
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="activeTab = 'income'"
|
||||
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
|
||||
:class="activeTab === 'income' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
>
|
||||
<Icon name="mdi:cash" size="18" class="mr-1" />
|
||||
Maklumat Pendapatan
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="activeTab = 'aid'"
|
||||
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
|
||||
:class="activeTab === 'aid' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
>
|
||||
<Icon name="mdi:gift" size="18" class="mr-1" />
|
||||
Maklumat Bantuan
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="activeTab = 'documents'"
|
||||
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
|
||||
:class="activeTab === 'documents' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
>
|
||||
<Icon name="mdi:file-document" size="18" class="mr-1" />
|
||||
Dokumen
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="activeTab = 'analysis'"
|
||||
class="py-2 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors"
|
||||
:class="activeTab === 'analysis' ? 'border-primary text-primary' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
>
|
||||
<Icon name="mdi:chart-bar" size="18" class="mr-1" />
|
||||
Analisis Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<!-- Family Information Tab -->
|
||||
<div v-if="activeTab === 'personal'" class="p-6">
|
||||
<div v-if="profile.spouse" class="mb-8">
|
||||
<h3 class="text-lg font-semibold text-primary mb-4">Maklumat Pasangan</h3>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Nama</h4>
|
||||
<p>{{ profile.spouse.name }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">No. Kad Pengenalan</h4>
|
||||
<p>{{ profile.spouse.idNumber }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-primary mb-4">Tanggungan</h3>
|
||||
|
||||
<div v-if="profile.dependents && profile.dependents.length > 0">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Bil.</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nama</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Umur</th>
|
||||
<th class="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Hubungan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-for="(dependent, index) in profile.dependents" :key="index" class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ index + 1 }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ dependent.name }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ dependent.age }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ dependent.relationship }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="bg-gray-50 p-6 rounded-lg text-center">
|
||||
<Icon name="mdi:account-off" size="48" class="text-gray-400 mb-2" />
|
||||
<p class="text-gray-500">Tiada tanggungan</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Income Information Tab -->
|
||||
<div v-if="activeTab === 'income'" class="p-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<rs-card>
|
||||
<div class="p-4 text-center">
|
||||
<div class="mb-2">
|
||||
<Icon name="mdi:cash-multiple" size="36" class="text-primary" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">Pendapatan Bulanan</p>
|
||||
<p class="text-xl font-bold text-primary">RM {{ profile.monthlyIncome }}</p>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<rs-card>
|
||||
<div class="p-4 text-center">
|
||||
<div class="mb-2">
|
||||
<Icon name="mdi:cash-plus" size="36" class="text-primary" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">Pendapatan Lain</p>
|
||||
<p class="text-xl font-bold text-primary">RM {{ profile.otherIncome }}</p>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<rs-card>
|
||||
<div class="p-4 text-center">
|
||||
<div class="mb-2">
|
||||
<Icon name="mdi:cash-register" size="36" class="text-primary" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">Jumlah Pendapatan</p>
|
||||
<p class="text-xl font-bold text-primary">RM {{ profile.totalIncome }}</p>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<h3 class="text-lg font-semibold text-primary mb-4">Butiran Pendapatan</h3>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Pekerjaan</h4>
|
||||
<p>{{ profile.occupation }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Pendapatan Bulanan</h4>
|
||||
<p>RM {{ profile.monthlyIncome }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Pendapatan Lain</h4>
|
||||
<p>RM {{ profile.otherIncome }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Jumlah Pendapatan</h4>
|
||||
<p>RM {{ profile.totalIncome }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aid Information Tab -->
|
||||
<div v-if="activeTab === 'aid'" class="p-6">
|
||||
<div class="bg-gray-50 p-6 rounded-lg text-center">
|
||||
<Icon name="mdi:gift-off" size="48" class="text-gray-400 mb-2" />
|
||||
<p class="text-gray-500">Tiada maklumat bantuan</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documents Tab -->
|
||||
<div v-if="activeTab === 'documents'" class="p-6">
|
||||
<h3 class="text-lg font-semibold text-primary mb-4">Dokumen Sokongan</h3>
|
||||
|
||||
<div v-if="profile.documents && profile.documents.length > 0">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div v-for="(doc, index) in profile.documents" :key="index" class="border rounded-lg overflow-hidden">
|
||||
<div class="bg-gray-50 p-4 flex items-center">
|
||||
<Icon name="mdi:file-document" size="24" class="text-primary mr-3" />
|
||||
<div>
|
||||
<h4 class="font-medium">{{ doc.name }}</h4>
|
||||
<p class="text-sm text-gray-500">{{ doc.size }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 flex justify-end">
|
||||
<rs-button variant="primary-text" size="sm">
|
||||
<Icon name="mdi:download" size="16" class="mr-1" />
|
||||
Muat Turun
|
||||
</rs-button>
|
||||
<rs-button variant="secondary-text" size="sm">
|
||||
<Icon name="mdi:eye" size="16" class="mr-1" />
|
||||
Papar
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="bg-gray-50 p-6 rounded-lg text-center">
|
||||
<Icon name="mdi:file-document-off" size="48" class="text-gray-400 mb-2" />
|
||||
<p class="text-gray-500">Tiada dokumen</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analysis Tab -->
|
||||
<div v-if="activeTab === 'analysis'" class="p-6">
|
||||
<!-- Button to trigger AI Analysis -->
|
||||
<div v-if="!profile.analysis && !isLoadingAnalysis" class="text-center mb-6">
|
||||
<rs-button variant="primary" @click="fetchAIAnalysis" size="lg">
|
||||
<Icon name="mdi:brain" size="20" class="mr-2" />
|
||||
Jalankan Analisis AI
|
||||
</rs-button>
|
||||
<p class="text-sm text-gray-500 mt-2">Klik untuk mendapatkan penilaian berdasarkan data profil.</p>
|
||||
</div>
|
||||
|
||||
<!-- Loading State for AI Analysis -->
|
||||
<div v-if="isLoadingAnalysis" class="text-center py-10">
|
||||
<Loading />
|
||||
<p class="mt-4 text-gray-600">Analisis AI sedang dijalankan...</p>
|
||||
<p class="text-sm text-gray-500">Sila tunggu sebentar.</p>
|
||||
</div>
|
||||
|
||||
<!-- Display Analysis Results -->
|
||||
<div v-if="profile.analysis && !isLoadingAnalysis" class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
|
||||
<!-- AI Analysis Main Column (takes 2/3 on lg screens) -->
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<!-- Card 1: Analisis Had Kifayah & Kelayakan -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold text-primary p-4 border-b">Analisis Had Kifayah & Kelayakan (AI)</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500 mb-1">Peratusan Had Kifayah</h4>
|
||||
<div v-if="profile.analysis.hadKifayahPercentage === 'N/A' || profile.analysis.hadKifayahPercentage === 'Ralat'" class="text-gray-500">
|
||||
{{ profile.analysis.hadKifayahPercentage }}
|
||||
</div>
|
||||
<div v-else class="relative pt-1">
|
||||
<div class="overflow-hidden h-4 text-xs flex rounded bg-gray-200">
|
||||
<div
|
||||
:style="{ width: profile.analysis.hadKifayahPercentage }"
|
||||
:class="{
|
||||
'bg-red-500': parseInt(profile.analysis.hadKifayahPercentage) < 60,
|
||||
'bg-yellow-500': parseInt(profile.analysis.hadKifayahPercentage) >= 60 && parseInt(profile.analysis.hadKifayahPercentage) < 80,
|
||||
'bg-green-500': parseInt(profile.analysis.hadKifayahPercentage) >= 80 && parseInt(profile.analysis.hadKifayahPercentage) <= 100,
|
||||
'bg-blue-500': parseInt(profile.analysis.hadKifayahPercentage) > 100
|
||||
}"
|
||||
class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center"
|
||||
>
|
||||
{{ profile.analysis.hadKifayahPercentage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Kategori Asnaf (AI)</h4>
|
||||
<p>{{ profile.analysis.kategoriAsnaf }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Kategori Keluarga (AI)</h4>
|
||||
<p>{{ profile.analysis.kategoriKeluarga }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Cadangan Kategori (AI)</h4>
|
||||
<p>{{ profile.analysis.cadanganKategori }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Status Kelayakan (AI)</h4>
|
||||
<p>{{ profile.analysis.statusKelayakan }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Card 2: Cadangan & Rumusan AI -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold text-primary p-4 border-b">Cadangan & Rumusan (AI)</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Cadangan Bantuan (AI)</h4>
|
||||
<ul v-if="profile.analysis.cadanganBantuan && profile.analysis.cadanganBantuan.length > 0" class="list-disc list-inside space-y-1 mt-1">
|
||||
<li v-for="(bantuan, index) in profile.analysis.cadanganBantuan" :key="index" class="text-gray-700">
|
||||
{{ bantuan.nama }}
|
||||
<span v-if="bantuan.peratusan && bantuan.peratusan !== 'Ralat'" class="font-semibold text-blue-600">({{ bantuan.peratusan }})</span>
|
||||
<span v-else-if="bantuan.peratusan === 'Ralat'" class="text-red-500 text-xs">({{ bantuan.peratusan }})</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="text-gray-500 mt-1">Tiada cadangan bantuan spesifik.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Ramalan Jangka Masa Taraf Hidup Pulih (AI)</h4>
|
||||
<p>{{ profile.analysis.ramalanJangkaMasaPulih }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Rumusan Keseluruhan (AI)</h4>
|
||||
<div class="mt-1 p-3 bg-blue-50 border-l-4 border-blue-500 rounded-r-md">
|
||||
<p class="whitespace-pre-line text-gray-700 text-sm">{{ profile.analysis.rumusan }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Original Data Column (takes 1/3 on lg screens) -->
|
||||
<div class="lg:col-span-1">
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold text-gray-700 p-4 border-b">Ringkasan Profil (Data Asal)</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="p-4 space-y-3">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Jenis Kategori (Asal)</h4>
|
||||
<rs-badge :variant="categoryColor" class="mt-1">{{ profile.kategori }}</rs-badge>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Status Semasa (Asal)</h4>
|
||||
<rs-badge :variant="statusColor" class="mt-1">{{ profile.status }}</rs-badge>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Jumlah Pendapatan (Asal)</h4>
|
||||
<p>RM {{ profile.totalIncome }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-500">Jumlah Tanggungan (Asal)</h4>
|
||||
<p>{{ profile.dependents.length }} orang</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<rs-modal v-model="confirmDelete">
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<Icon name="mdi:alert-circle" size="24" class="text-red-500 mr-2" />
|
||||
<h3 class="text-lg font-medium">Padam Profil</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div class="p-4">
|
||||
<p class="mb-4">Adakah anda pasti ingin memadam profil ini?</p>
|
||||
<p class="text-sm text-gray-500 mb-2">Nama: <span class="font-medium">{{ profile?.nama }}</span></p>
|
||||
<p class="text-sm text-gray-500">ID: <span class="font-medium">{{ profile?.id }}</span></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<rs-button variant="secondary-outline" @click="cancelDelete">
|
||||
Batal
|
||||
</rs-button>
|
||||
<rs-button variant="danger" @click="confirmDeleteProfile">
|
||||
Padam
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
</rs-modal>
|
||||
</div>
|
||||
</template>
|
@ -1,337 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useAsnafMockData } from '~/composables/useAsnafMockData';
|
||||
|
||||
definePageMeta({
|
||||
title: "Senarai Asnaf",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
breadcrumb: [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/",
|
||||
},
|
||||
{
|
||||
name: "BF-PRF",
|
||||
path: "/BF-PRF",
|
||||
},
|
||||
{
|
||||
name: "Asnaf",
|
||||
path: "/BF-PRF/AS",
|
||||
},
|
||||
{
|
||||
name: "Senarai",
|
||||
path: "/BF-PRF/AS/LIST",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Get asnaf data from the composable
|
||||
const { asnafProfiles, statistics, filterProfiles, categories, statuses } = useAsnafMockData();
|
||||
|
||||
// Table reactivity control
|
||||
const tableKey = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const totalProfiles = ref(0);
|
||||
const isLoading = ref(false);
|
||||
|
||||
// Search and filter variables
|
||||
const searchQuery = ref('');
|
||||
const selectedStatus = ref('All');
|
||||
const selectedCategory = ref('All');
|
||||
|
||||
// Table data and fields
|
||||
const tableData = computed(() => {
|
||||
return filterProfiles(searchQuery.value, selectedStatus.value, selectedCategory.value);
|
||||
});
|
||||
|
||||
const tableFields = [
|
||||
{ field: 'no', label: 'No.' },
|
||||
{ field: 'id', label: 'ID' },
|
||||
{ field: 'nama', label: 'Nama' },
|
||||
{ field: 'idNumber', label: 'No. ID' },
|
||||
{ field: 'kategori', label: 'Kategori' },
|
||||
{ field: 'status', label: 'Status' },
|
||||
{ field: 'tindakan', label: 'Tindakan' }
|
||||
];
|
||||
|
||||
// Generate table field and data mapping
|
||||
const formattedTableData = computed(() => {
|
||||
return tableData.value.map((profile, index) => ({
|
||||
no: index + 1,
|
||||
id: profile.id,
|
||||
nama: profile.nama,
|
||||
idNumber: profile.idNumber,
|
||||
kategori: profile.kategori,
|
||||
status: profile.status,
|
||||
tindakan: profile.id
|
||||
}));
|
||||
});
|
||||
|
||||
// Helper functions
|
||||
function getBadgeVariantForCategory(category) {
|
||||
switch (category) {
|
||||
case 'Fakir': return 'danger';
|
||||
case 'Miskin': return 'warning';
|
||||
case 'Mualaf': return 'info';
|
||||
case 'Fi-sabilillah': return 'primary';
|
||||
case 'Gharimin': return 'secondary';
|
||||
case 'Ibnu Sabil': return 'success';
|
||||
default: return 'primary';
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeVariantForStatus(status) {
|
||||
switch (status) {
|
||||
case 'Aktif': return 'success';
|
||||
case 'Tidak Aktif': return 'danger';
|
||||
case 'Dalam Semakan': return 'warning';
|
||||
default: return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
function navigateToDetail(id) {
|
||||
console.log("Attempting to navigate to detail for ID:", id);
|
||||
if (id) {
|
||||
navigateTo(`/BF-PRF/AS/DETAIL/${id}`);
|
||||
} else {
|
||||
console.error("Navigation failed: ID is undefined or null");
|
||||
}
|
||||
}
|
||||
|
||||
function navigateToRegistration() {
|
||||
navigateTo('/BF-PRF/AS/FR/01');
|
||||
}
|
||||
|
||||
// Pagination
|
||||
const handlePageChange = (newPage) => {
|
||||
currentPage.value = newPage;
|
||||
fetchProfiles();
|
||||
};
|
||||
|
||||
// Fetch data
|
||||
async function fetchProfiles() {
|
||||
isLoading.value = true;
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
totalProfiles.value = tableData.value.length;
|
||||
isLoading.value = false;
|
||||
tableKey.value++; // Force table re-render
|
||||
}
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
fetchProfiles();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center">
|
||||
<h1 class="text-2xl font-bold text-primary">Senarai Asnaf</h1>
|
||||
<rs-button
|
||||
variant="primary"
|
||||
class="flex items-center gap-2"
|
||||
@click="navigateToRegistration"
|
||||
>
|
||||
<Icon name="mdi:plus" size="18" />
|
||||
<span>Tambah Asnaf</span>
|
||||
</rs-button>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<rs-card class="transition-all duration-300 hover:shadow-lg">
|
||||
<div class="p-4 flex items-center gap-4">
|
||||
<div class="p-4 flex justify-center items-center bg-blue-100 rounded-xl">
|
||||
<Icon name="mdi:account-group" size="24" class="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-2xl font-bold text-primary">{{ statistics.total }}</span>
|
||||
<span class="text-sm text-gray-600">Jumlah Asnaf</span>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="transition-all duration-300 hover:shadow-lg">
|
||||
<div class="p-4 flex items-center gap-4">
|
||||
<div class="p-4 flex justify-center items-center bg-green-100 rounded-xl">
|
||||
<Icon name="mdi:check-circle" size="24" class="text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-2xl font-bold text-green-600">{{ statistics.active }}</span>
|
||||
<span class="text-sm text-gray-600">Aktif</span>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="transition-all duration-300 hover:shadow-lg">
|
||||
<div class="p-4 flex items-center gap-4">
|
||||
<div class="p-4 flex justify-center items-center bg-yellow-100 rounded-xl">
|
||||
<Icon name="mdi:clock-time-four" size="24" class="text-yellow-600" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-2xl font-bold text-yellow-600">{{ statistics.review }}</span>
|
||||
<span class="text-sm text-gray-600">Dalam Semakan</span>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="transition-all duration-300 hover:shadow-lg">
|
||||
<div class="p-4 flex items-center gap-4">
|
||||
<div class="p-4 flex justify-center items-center bg-red-100 rounded-xl">
|
||||
<Icon name="mdi:close-circle" size="24" class="text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-2xl font-bold text-red-600">{{ statistics.inactive }}</span>
|
||||
<span class="text-sm text-gray-600">Tidak Aktif</span>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<rs-card>
|
||||
<div class="p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Carian</label>
|
||||
<div class="relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<Icon name="mdi:magnify" size="18" class="text-gray-400" />
|
||||
</div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-primary focus:border-primary"
|
||||
placeholder="Cari dengan nama atau ID..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Status</label>
|
||||
<select
|
||||
v-model="selectedStatus"
|
||||
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-primary focus:border-primary"
|
||||
>
|
||||
<option value="All">Semua Status</option>
|
||||
<option v-for="status in statuses" :key="status" :value="status">
|
||||
{{ status }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Kategori</label>
|
||||
<select
|
||||
v-model="selectedCategory"
|
||||
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-primary focus:border-primary"
|
||||
>
|
||||
<option value="All">Semua Kategori</option>
|
||||
<option v-for="category in categories" :key="category" :value="category">
|
||||
{{ category }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<!-- Data Table -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div class="px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-primary">Senarai Asnaf</h2>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ tableData.length }} asnaf dijumpai
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div v-if="isLoading && tableData.length === 0" class="py-8 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Icon name="mdi:loading" size="2rem" class="text-blue-500 animate-spin" />
|
||||
</div>
|
||||
<p class="mt-2 text-gray-600">Memuat data...</p>
|
||||
</div>
|
||||
|
||||
<rs-table
|
||||
v-else
|
||||
class="mt-4"
|
||||
:key="tableKey"
|
||||
:data="formattedTableData"
|
||||
:columns="tableFields"
|
||||
:pageSize="pageSize"
|
||||
:showNoColumn="true"
|
||||
:options="{
|
||||
variant: 'default',
|
||||
hover: true,
|
||||
striped: true,
|
||||
bordered: true
|
||||
}"
|
||||
:current-page="currentPage"
|
||||
:total-items="totalProfiles"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
<template v-slot:no="data">
|
||||
{{ data.text }}
|
||||
</template>
|
||||
|
||||
<template v-slot:id="data">
|
||||
{{ data.text }}
|
||||
</template>
|
||||
|
||||
<template v-slot:nama="data">
|
||||
<div class="font-medium">{{ data.text }}</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:idNumber="data">
|
||||
{{ data.text }}
|
||||
</template>
|
||||
|
||||
<template v-slot:kategori="data">
|
||||
<rs-badge :variant="getBadgeVariantForCategory(data.text)">{{ data.text }}</rs-badge>
|
||||
</template>
|
||||
|
||||
<template v-slot:status="data">
|
||||
<rs-badge :variant="getBadgeVariantForStatus(data.text)">
|
||||
{{ data.text }}
|
||||
</rs-badge>
|
||||
</template>
|
||||
|
||||
<template v-slot:tindakan="data">
|
||||
<div class="flex gap-2">
|
||||
<rs-button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
class="!px-2 !py-1"
|
||||
@click="() => {
|
||||
navigateToDetail(data.value.tindakan);
|
||||
}"
|
||||
>
|
||||
<Icon name="mdi:eye" size="1rem" class="mr-1" />
|
||||
Lihat
|
||||
</rs-button>
|
||||
</div>
|
||||
</template>
|
||||
</rs-table>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="!isLoading && tableData.length === 0" class="text-center py-8">
|
||||
<div class="flex justify-center mb-4">
|
||||
<Icon name="mdi:magnify" size="4rem" class="text-gray-400" />
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-gray-500">Tiada Profil Ditemui</h3>
|
||||
<p class="text-gray-500 mt-2">Sila cuba carian lain atau reset penapis.</p>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</template>
|
135
pages/dashboard.vue
Normal file
135
pages/dashboard.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<nav class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
CorradAF RBAC Dashboard
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span v-if="user" class="text-sm text-gray-700">
|
||||
Welcome, {{ user.name || user.email }}
|
||||
</span>
|
||||
<button
|
||||
@click="handleLogout"
|
||||
class="bg-red-600 hover:bg-red-700 text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div class="px-4 py-6 sm:px-0">
|
||||
<div class="border-4 border-dashed border-gray-200 rounded-lg p-8">
|
||||
<div v-if="pending">
|
||||
<p class="text-gray-500">Loading user information...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="user" class="space-y-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">
|
||||
🎉 Authentication Successful!
|
||||
</h2>
|
||||
<p class="text-gray-600 mb-6">
|
||||
You are now logged into the CorradAF RBAC system as the master provider.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">
|
||||
User Information
|
||||
</h3>
|
||||
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Name</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ user.name || 'N/A' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Email</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ user.email }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Username</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ user.preferred_username || 'N/A' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">User ID</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">{{ user.sub }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<h4 class="text-sm font-medium text-blue-800 mb-2">
|
||||
Next Steps
|
||||
</h4>
|
||||
<ul class="text-sm text-blue-700 space-y-1">
|
||||
<li>• You can now create and manage other applications</li>
|
||||
<li>• Add users and assign them to applications</li>
|
||||
<li>• Create roles and permissions</li>
|
||||
<li>• Manage groups and access control</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-4">
|
||||
<NuxtLink
|
||||
to="/applications"
|
||||
class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium"
|
||||
>
|
||||
Manage Applications
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/users"
|
||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm font-medium"
|
||||
>
|
||||
Manage Users
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center">
|
||||
<p class="text-red-600">Failed to load user information</p>
|
||||
<NuxtLink
|
||||
to="/login"
|
||||
class="mt-4 inline-block bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium"
|
||||
>
|
||||
Login Again
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Require authentication for this page
|
||||
definePageMeta({
|
||||
middleware: 'auth'
|
||||
});
|
||||
|
||||
// Use the auth composable
|
||||
const { logout } = useAuth();
|
||||
|
||||
// Get user information with proper error handling
|
||||
const { data: user, pending } = await useFetch('/api/auth/me', {
|
||||
default: () => null,
|
||||
server: false // Client-side only to handle redirects properly
|
||||
});
|
||||
|
||||
// If not authenticated, redirect to login
|
||||
watchEffect(() => {
|
||||
if (!pending.value && !user.value) {
|
||||
navigateTo('/login');
|
||||
}
|
||||
});
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
};
|
||||
</script>
|
@ -1,10 +1,15 @@
|
||||
<script setup>
|
||||
// Use main middleware to handle routing based on auth status
|
||||
definePageMeta({
|
||||
title: "Main",
|
||||
middleware: ["main"],
|
||||
middleware: 'main'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Redirect Dashboard</div>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 mx-auto"></div>
|
||||
<p class="mt-4 text-gray-600">Checking authentication...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
58
pages/login.vue
Normal file
58
pages/login.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div class="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
CorradAF RBAC System
|
||||
</h2>
|
||||
<p class="mt-2 text-center text-sm text-gray-600">
|
||||
Sign in to manage applications and users
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md shadow-sm -space-y-px">
|
||||
<button
|
||||
@click="loginWithAuthentik"
|
||||
:disabled="isLoading"
|
||||
class="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
|
||||
>
|
||||
<span v-if="!isLoading">Sign in with Authentik</span>
|
||||
<span v-else>Redirecting...</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<p class="text-xs text-gray-500">
|
||||
This will redirect you to Authentik for secure authentication
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Redirect authenticated users to dashboard
|
||||
definePageMeta({
|
||||
middleware: 'dashboard'
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const loginWithAuthentik = async () => {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
// Redirect to our auth endpoint which handles Authentik OAuth2
|
||||
await navigateTo('/api/auth/login', { external: true });
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Redirect if already authenticated
|
||||
onMounted(() => {
|
||||
// Check if user is already authenticated (you can implement this later)
|
||||
// For now, just show the login page
|
||||
});
|
||||
</script>
|
@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<section class="flex flex-col h-screen">
|
||||
<div class="mb-4 flex-shrink-0">
|
||||
<h3>Metabase</h3>
|
||||
<p>
|
||||
Metabase is a powerful data visualization and analytics tool that allows you to
|
||||
create and share dashboards, reports, and visualizations with your team.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="pending" class="flex justify-center items-center flex-1">
|
||||
<div class="text-lg">Loading Metabase dashboard...</div>
|
||||
</div>
|
||||
<div v-else-if="error" class="flex justify-center items-center flex-1">
|
||||
<div class="text-red-500">Error loading dashboard: {{ error.message }}</div>
|
||||
</div>
|
||||
<iframe
|
||||
v-else
|
||||
:src="iframeUrl"
|
||||
frameborder="0"
|
||||
width="100%"
|
||||
class="flex-1"
|
||||
allowtransparency
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Fetch the JWT token from our server API
|
||||
const { data: tokenData, pending, error } = await useFetch("/api/metabase/token");
|
||||
|
||||
const iframeUrl = computed(() => {
|
||||
if (tokenData.value?.token && tokenData.value?.siteUrl) {
|
||||
return (
|
||||
tokenData.value.siteUrl +
|
||||
"/embed/dashboard/" +
|
||||
tokenData.value.token +
|
||||
"#bordered=true&titled=true"
|
||||
);
|
||||
}
|
||||
return "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,30 +0,0 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
title: "Notes",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<div>
|
||||
Notes
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div>
|
||||
Content for Notes
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Add your styles here */
|
||||
</style>
|
||||
|
@ -1,124 +0,0 @@
|
||||
import { defineEventHandler, readBody } from 'h3';
|
||||
|
||||
// Define an interface for the expected request body (subset of AsnafProfile)
|
||||
interface AsnafAnalysisRequest {
|
||||
monthlyIncome: string;
|
||||
otherIncome: string;
|
||||
totalIncome: string;
|
||||
occupation: string;
|
||||
maritalStatus: string;
|
||||
dependents: Array<any>; // Or a more specific type if you have one for dependents
|
||||
// Add any other fields you deem necessary for OpenAI to analyze
|
||||
}
|
||||
|
||||
interface AidSuggestion {
|
||||
nama: string;
|
||||
peratusan: string;
|
||||
}
|
||||
|
||||
// Define an interface for the expected OpenAI response structure (and our API response)
|
||||
interface AsnafAnalysisResponse {
|
||||
hadKifayahPercentage: string;
|
||||
kategoriAsnaf: string;
|
||||
kategoriKeluarga: string;
|
||||
cadanganKategori: string;
|
||||
statusKelayakan: string;
|
||||
cadanganBantuan: AidSuggestion[];
|
||||
ramalanJangkaMasaPulih: string;
|
||||
rumusan: string;
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event): Promise<AsnafAnalysisResponse> => {
|
||||
const body = await readBody<AsnafAnalysisRequest>(event);
|
||||
|
||||
// --- Placeholder for Actual OpenAI API Call ---
|
||||
// In a real application, you would:
|
||||
// 1. Retrieve your OpenAI API key securely (e.g., from environment variables)
|
||||
const openAIApiKey = process.env.OPENAI_API_KEY;
|
||||
if (!openAIApiKey) {
|
||||
console.error('OpenAI API key not configured. Please set OPENAI_API_KEY in your .env file.');
|
||||
throw createError({ statusCode: 500, statusMessage: 'OpenAI API key not configured' });
|
||||
}
|
||||
|
||||
// 2. Construct the prompt for OpenAI using the data from `body`.
|
||||
// IMPORTANT: Sanitize or carefully construct any data from `body` included in the prompt to prevent prompt injection.
|
||||
const prompt = `You are an expert Zakat administrator. Based on the following applicant data: monthlyIncome: ${body.monthlyIncome}, totalIncome: ${body.totalIncome}, occupation: ${body.occupation}, maritalStatus: ${body.maritalStatus}, dependents: ${body.dependents.length}.
|
||||
Return JSON with keys: hadKifayahPercentage, kategoriAsnaf, kategoriKeluarga, cadanganKategori, statusKelayakan, cadanganBantuan, ramalanJangkaMasaPulih, rumusan.
|
||||
For 'cadanganBantuan', provide a JSON array of objects, where each object has a 'nama' (string, name of the aid) and 'peratusan' (string, e.g., '85%', representing suitability). Suggest 2-3 most relevant aid types.
|
||||
Example for cadanganBantuan: [{"nama": "Bantuan Kewangan Bulanan", "peratusan": "90%"}, {"nama": "Bantuan Makanan Asas", "peratusan": "75%"}].
|
||||
Full JSON Example: {"hadKifayahPercentage": "75%", ..., "cadanganBantuan": [{"nama": "Bantuan Kewangan Bulanan", "peratusan": "90%"}], ...}`;
|
||||
// Adjust the prompt to be more detailed and specific to your needs and desired JSON output structure.
|
||||
|
||||
// 3. Make the API call to OpenAI
|
||||
try {
|
||||
const openAIResponse = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${openAIApiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-3.5-turbo', // Or your preferred model like gpt-4
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
// For more consistent JSON output, consider using a model version that officially supports JSON mode if available
|
||||
// and set response_format: { type: "json_object" }, (check OpenAI documentation for model compatibility)
|
||||
}),
|
||||
});
|
||||
|
||||
if (!openAIResponse.ok) {
|
||||
const errorData = await openAIResponse.text();
|
||||
console.error('OpenAI API Error details:', errorData);
|
||||
throw createError({ statusCode: openAIResponse.status, statusMessage: `Failed to get analysis from OpenAI: ${openAIResponse.statusText}` });
|
||||
}
|
||||
|
||||
const openAIData = await openAIResponse.json();
|
||||
|
||||
// Parse the content from the response - structure might vary slightly based on OpenAI model/API version
|
||||
// It's common for the JSON string to be in openAIData.choices[0].message.content
|
||||
if (openAIData.choices && openAIData.choices[0] && openAIData.choices[0].message && openAIData.choices[0].message.content) {
|
||||
const analysisResult = JSON.parse(openAIData.choices[0].message.content) as AsnafAnalysisResponse;
|
||||
return analysisResult;
|
||||
} else {
|
||||
console.error('OpenAI response structure not as expected:', openAIData);
|
||||
throw createError({ statusCode: 500, statusMessage: 'Unexpected response structure from OpenAI' });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error during OpenAI API call or parsing:', error);
|
||||
// Avoid exposing detailed internal errors to the client if they are not createError objects
|
||||
if (typeof error === 'object' && error !== null && 'statusCode' in error) {
|
||||
// We can infer error has statusCode here, but to be super safe with TS:
|
||||
const e = error as { statusCode: number };
|
||||
if (e.statusCode) throw e;
|
||||
}
|
||||
throw createError({ statusCode: 500, statusMessage: 'Internal server error during AI analysis' });
|
||||
}
|
||||
// --- End of Actual OpenAI API Call ---
|
||||
|
||||
// The simulated response below this line should be REMOVED once the actual OpenAI call is implemented and working.
|
||||
/*
|
||||
console.log('Received for analysis in server route:', body);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate API delay
|
||||
|
||||
const totalIncomeNumeric = parseFloat(body.totalIncome);
|
||||
let percentage = '50%';
|
||||
if (totalIncomeNumeric < 1000) percentage = '30%';
|
||||
else if (totalIncomeNumeric < 2000) percentage = '65%';
|
||||
else if (totalIncomeNumeric < 3000) percentage = '85%';
|
||||
else percentage = '110%';
|
||||
|
||||
return {
|
||||
hadKifayahPercentage: percentage,
|
||||
kategoriAsnaf: 'Simulated - Miskin',
|
||||
kategoriKeluarga: 'Simulated - Miskin (50-100% HK)',
|
||||
cadanganKategori: 'Simulated - Miskin',
|
||||
statusKelayakan: 'Simulated - Layak (Miskin)',
|
||||
cadanganBantuan: [
|
||||
{ nama: 'Simulated - Bantuan Kewangan Bulanan', peratusan: '80%' },
|
||||
{ nama: 'Simulated - Bantuan Pendidikan Anak', peratusan: '65%' }
|
||||
],
|
||||
ramalanJangkaMasaPulih: 'Simulated - 6 bulan',
|
||||
rumusan: 'Simulated - Pemohon memerlukan perhatian segera.'
|
||||
};
|
||||
*/
|
||||
});
|
73
server/api/applications/[id].js
Normal file
73
server/api/applications/[id].js
Normal file
@ -0,0 +1,73 @@
|
||||
import { authentikFetch } from '../../utils/authentik';
|
||||
import { requireAuth } from '../../utils/auth';
|
||||
|
||||
// /api/applications/[id] - Handle GET, PUT, DELETE for specific application
|
||||
export default defineEventHandler(async (event) => {
|
||||
const method = getMethod(event);
|
||||
const id = getRouterParam(event, 'id');
|
||||
|
||||
// Require authentication
|
||||
await requireAuth(event);
|
||||
|
||||
if (!id) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'Application ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
try {
|
||||
const application = await authentikFetch(`/core/applications/${id}/`);
|
||||
return application;
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 404,
|
||||
message: error.message || 'Application not found'
|
||||
});
|
||||
}
|
||||
|
||||
case 'PUT':
|
||||
try {
|
||||
const body = await readBody(event);
|
||||
|
||||
const application = await authentikFetch(`/core/applications/${id}/`, {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
name: body.name,
|
||||
slug: body.slug,
|
||||
meta_description: body.description,
|
||||
meta_publisher: 'CorradAF RBAC'
|
||||
}
|
||||
});
|
||||
|
||||
return application;
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
case 'DELETE':
|
||||
try {
|
||||
await authentikFetch(`/core/applications/${id}/`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
return { message: 'Application deleted successfully' };
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
throw createError({
|
||||
statusCode: 405,
|
||||
message: 'Method not allowed'
|
||||
});
|
||||
}
|
||||
});
|
62
server/api/applications/index.js
Normal file
62
server/api/applications/index.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { authentikFetch, createAuthentikApplication, createAuthentikProvider, linkProviderToApplication } from '../../utils/authentik';
|
||||
import { requireAuth } from '../../utils/auth';
|
||||
|
||||
// /api/applications - Handle GET and POST
|
||||
export default defineEventHandler(async (event) => {
|
||||
const method = getMethod(event);
|
||||
|
||||
// Require authentication for all application endpoints
|
||||
await requireAuth(event);
|
||||
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
try {
|
||||
const applications = await authentikFetch('/core/applications/');
|
||||
return applications;
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
case 'POST':
|
||||
try {
|
||||
const body = await readBody(event);
|
||||
|
||||
// Create application in Authentik
|
||||
const application = await createAuthentikApplication({
|
||||
name: body.name,
|
||||
slug: body.slug || body.name.toLowerCase().replace(/\s+/g, '-'),
|
||||
meta_description: body.description,
|
||||
meta_publisher: 'CorradAF RBAC'
|
||||
});
|
||||
|
||||
// Create OAuth2 provider if web application
|
||||
if (body.type === 'web-app') {
|
||||
const provider = await createAuthentikProvider({
|
||||
name: `${body.name} OAuth2`,
|
||||
client_type: 'confidential',
|
||||
redirect_uris: body.redirectUris,
|
||||
authorization_flow: body.authorizationFlow || 'default-authentication-flow'
|
||||
});
|
||||
|
||||
// Link provider to application
|
||||
await linkProviderToApplication(application.pk, provider.pk);
|
||||
}
|
||||
|
||||
return application;
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
throw createError({
|
||||
statusCode: 405,
|
||||
message: 'Method not allowed'
|
||||
});
|
||||
}
|
||||
});
|
76
server/api/auth/callback.js
Normal file
76
server/api/auth/callback.js
Normal file
@ -0,0 +1,76 @@
|
||||
// OAuth2 callback endpoint
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
const query = getQuery(event);
|
||||
|
||||
if (query.error) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: query.error_description || 'Authentication failed'
|
||||
});
|
||||
}
|
||||
|
||||
if (!query.code) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: 'No authorization code received'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Exchange code for tokens
|
||||
const tokens = await $fetch(`${config.public.authentikUrl}/application/o/token/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: query.code,
|
||||
redirect_uri: `${config.public.appUrl}/api/auth/callback`,
|
||||
client_id: config.authentik.clientId,
|
||||
client_secret: config.authentik.clientSecret
|
||||
})
|
||||
});
|
||||
|
||||
// Get user info using access token
|
||||
const userInfo = await $fetch(`${config.public.authentikUrl}/application/o/userinfo/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${tokens.access_token}`
|
||||
}
|
||||
});
|
||||
|
||||
// Set cookies
|
||||
setCookie(event, 'auth_token', tokens.access_token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 // 24 hours
|
||||
});
|
||||
|
||||
setCookie(event, 'refresh_token', tokens.refresh_token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 30 // 30 days
|
||||
});
|
||||
|
||||
// Store user info
|
||||
setCookie(event, 'user_info', JSON.stringify(userInfo), {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 // 24 hours
|
||||
});
|
||||
|
||||
// Redirect to dashboard
|
||||
return sendRedirect(event, '/dashboard');
|
||||
|
||||
} catch (error) {
|
||||
console.error('OAuth token exchange error:', error);
|
||||
throw createError({
|
||||
statusCode: error.response?.status || 500,
|
||||
message: 'Failed to authenticate with Authentik'
|
||||
});
|
||||
}
|
||||
});
|
13
server/api/auth/login.js
Normal file
13
server/api/auth/login.js
Normal file
@ -0,0 +1,13 @@
|
||||
// Login endpoint - redirects to Authentik OAuth2
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// Redirect to Authentik login
|
||||
const authUrl = new URL('/application/o/authorize/', config.public.authentikUrl);
|
||||
authUrl.searchParams.append('client_id', config.authentik.clientId);
|
||||
authUrl.searchParams.append('redirect_uri', `${config.public.appUrl}/api/auth/callback`);
|
||||
authUrl.searchParams.append('response_type', 'code');
|
||||
authUrl.searchParams.append('scope', 'openid profile email');
|
||||
|
||||
return sendRedirect(event, authUrl.toString());
|
||||
});
|
12
server/api/auth/logout.js
Normal file
12
server/api/auth/logout.js
Normal file
@ -0,0 +1,12 @@
|
||||
// Logout endpoint
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// Clear auth cookies
|
||||
deleteCookie(event, 'auth_token');
|
||||
deleteCookie(event, 'refresh_token');
|
||||
deleteCookie(event, 'user_info');
|
||||
|
||||
// Redirect to our login page instead of Authentik logout
|
||||
return sendRedirect(event, '/login');
|
||||
});
|
28
server/api/auth/me.js
Normal file
28
server/api/auth/me.js
Normal file
@ -0,0 +1,28 @@
|
||||
// Get current user endpoint
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
try {
|
||||
const authToken = getCookie(event, 'auth_token');
|
||||
|
||||
if (!authToken) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const userInfo = await $fetch(`${config.public.authentikUrl}/application/o/userinfo/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
});
|
||||
|
||||
return userInfo;
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: 'Invalid token'
|
||||
});
|
||||
}
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const { userID } = event.context.user;
|
||||
|
||||
if (userID == null) {
|
||||
return {
|
||||
statusCode: 401,
|
||||
message: "Unauthorized",
|
||||
};
|
||||
}
|
||||
|
||||
const validatedUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
userID: parseInt(userID),
|
||||
},
|
||||
});
|
||||
if (!validatedUser) {
|
||||
return {
|
||||
statusCode: 401,
|
||||
message: "Unauthorized",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
message: "Authorized",
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 401,
|
||||
message: "Unauthorized",
|
||||
};
|
||||
}
|
||||
});
|
41
server/api/auth/validate.js
Normal file
41
server/api/auth/validate.js
Normal file
@ -0,0 +1,41 @@
|
||||
// Validate current authentication status
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
try {
|
||||
const authToken = getCookie(event, 'auth_token');
|
||||
|
||||
if (!authToken) {
|
||||
return {
|
||||
statusCode: 401,
|
||||
message: "Not authenticated - no token found"
|
||||
};
|
||||
}
|
||||
|
||||
// Verify token with Authentik
|
||||
const userInfo = await $fetch(`${config.public.authentikUrl}/application/o/userinfo/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!userInfo) {
|
||||
return {
|
||||
statusCode: 401,
|
||||
message: "Invalid token"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
message: "Authorized",
|
||||
user: userInfo
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Token validation error:', error);
|
||||
return {
|
||||
statusCode: 401,
|
||||
message: "Unauthorized - token validation failed"
|
||||
};
|
||||
}
|
||||
});
|
@ -1,28 +0,0 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const METABASE_SECRET_KEY = config.metabase.secretKey;
|
||||
|
||||
const payload = {
|
||||
resource: { dashboard: 2 },
|
||||
params: {},
|
||||
exp: Math.round(Date.now() / 1000) + 10 * 60, // 10 minute expiration
|
||||
};
|
||||
|
||||
try {
|
||||
const token = jwt.sign(payload, METABASE_SECRET_KEY);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
token: token,
|
||||
siteUrl: config.metabase.siteUrl
|
||||
};
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to generate Metabase token'
|
||||
});
|
||||
}
|
||||
});
|
@ -1,109 +0,0 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
const ENV = useRuntimeConfig();
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const cookies = event.req.headers.cookie;
|
||||
if (!cookies) throw new Error("Cookie not found");
|
||||
|
||||
let { accessToken, refreshToken, user } = parseCookie(cookies);
|
||||
|
||||
if (!accessToken) accessToken = null;
|
||||
if (!refreshToken) refreshToken = null;
|
||||
|
||||
let { subdomain } = JSON.parse(user);
|
||||
if (!subdomain) subdomain = null;
|
||||
|
||||
let payloadUser = null;
|
||||
|
||||
payloadUser = verifyAccessToken(accessToken);
|
||||
|
||||
if (!payloadUser) {
|
||||
payloadUser = verifyRefreshToken(refreshToken);
|
||||
if (!payloadUser) throw new Error("Unauthorized Refresh Token");
|
||||
|
||||
const accessToken = generateAccessToken({
|
||||
email: payloadUser.email,
|
||||
roles: payloadUser.roles,
|
||||
});
|
||||
|
||||
// Set new access token
|
||||
event.res.setHeader("Set-Cookie", [
|
||||
`accessToken=${accessToken}; HttpOnly; Secure; SameSite=Lax; Path=/`,
|
||||
]);
|
||||
}
|
||||
|
||||
const getUser = await getUserInfo(payloadUser.username);
|
||||
if (!getUser) throw new Error("User not found");
|
||||
|
||||
event.context.user = {
|
||||
userID: getUser.userID || null,
|
||||
email: payloadUser.email || null,
|
||||
roles: payloadUser.roles || [],
|
||||
};
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
// console.log(error.message);
|
||||
event.context.user = {
|
||||
userID: null,
|
||||
email: null,
|
||||
roles: [],
|
||||
};
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
function parseCookie(str) {
|
||||
return str
|
||||
.split(";")
|
||||
.map((v) => v.split("="))
|
||||
.reduce((acc, v) => {
|
||||
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function verifyAccessToken(accessToken) {
|
||||
try {
|
||||
const token = ENV.auth.secretAccess;
|
||||
return jwt.verify(accessToken, token);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function verifyRefreshToken(refreshToken) {
|
||||
try {
|
||||
const token = ENV.auth.secretRefresh;
|
||||
return jwt.verify(refreshToken, token);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function generateAccessToken(user) {
|
||||
try {
|
||||
const token = ENV.auth.secretAccess;
|
||||
return jwt.sign(user, token, { expiresIn: "1d" });
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getUserInfo(username) {
|
||||
try {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
userUsername: username,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
@ -1,3 +1,16 @@
|
||||
import { defineNitroConfig } from "nitropack";
|
||||
|
||||
export default defineNitroConfig({});
|
||||
export default defineNitroConfig({
|
||||
runtimeConfig: {
|
||||
// Private keys (server-side only)
|
||||
authentikApiToken: process.env.AUTHENTIK_API_TOKEN,
|
||||
authentikClientId: process.env.AUTHENTIK_CLIENT_ID,
|
||||
authentikClientSecret: process.env.AUTHENTIK_CLIENT_SECRET,
|
||||
|
||||
// Public keys (client-side accessible)
|
||||
public: {
|
||||
appUrl: process.env.APP_URL || 'http://localhost:3000',
|
||||
authentikUrl: process.env.AUTHENTIK_URL || 'http://localhost:9000'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
50
server/utils/auth.js
Normal file
50
server/utils/auth.js
Normal file
@ -0,0 +1,50 @@
|
||||
// Authentication utilities for API routes
|
||||
export const requireAuth = async (event) => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const authHeader = getHeader(event, 'Authorization');
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: 'No token provided'
|
||||
});
|
||||
}
|
||||
|
||||
// Extract the token without the 'Bearer ' prefix
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
try {
|
||||
// Verify token with Authentik
|
||||
const response = await $fetch(`${config.public.authentikUrl}/api/v3/core/tokens/verify/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
// Add user info to event context
|
||||
event.context.auth = response;
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Token verification error:', error);
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: 'Invalid or expired token'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Optional: Get current user from context (after requireAuth is called)
|
||||
export const getCurrentUser = (event) => {
|
||||
return event.context.auth;
|
||||
};
|
||||
|
||||
// Optional: Check if user has specific permissions
|
||||
export const hasPermission = (event, permission) => {
|
||||
const user = getCurrentUser(event);
|
||||
if (!user || !user.permissions) return false;
|
||||
return user.permissions.includes(permission);
|
||||
};
|
74
server/utils/authentik.js
Normal file
74
server/utils/authentik.js
Normal file
@ -0,0 +1,74 @@
|
||||
// Authentik API utilities
|
||||
export const authentikFetch = async (endpoint, options = {}) => {
|
||||
const config = useRuntimeConfig();
|
||||
const AUTHENTIK_BASE_URL = `${config.public.authentikUrl}/api/v3`;
|
||||
|
||||
const defaultOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${config.authentik.apiToken}`
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Log the request for debugging
|
||||
console.log(`Authentik API Request: ${AUTHENTIK_BASE_URL}${endpoint}`);
|
||||
|
||||
const response = await $fetch(`${AUTHENTIK_BASE_URL}${endpoint}`, {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
headers: {
|
||||
...defaultOptions.headers,
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`Authentik API Error for ${endpoint}:`, error);
|
||||
throw createError({
|
||||
statusCode: error.response?.status || 500,
|
||||
message: error.message || 'Failed to communicate with Authentik API'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const getAuthentikUser = async (userId) => {
|
||||
return await authentikFetch(`/core/users/${userId}/`);
|
||||
};
|
||||
|
||||
export const getAuthentikGroups = async () => {
|
||||
return await authentikFetch('/core/groups/');
|
||||
};
|
||||
|
||||
export const createAuthentikApplication = async (applicationData) => {
|
||||
return await authentikFetch('/core/applications/', {
|
||||
method: 'POST',
|
||||
body: applicationData
|
||||
});
|
||||
};
|
||||
|
||||
export const createAuthentikProvider = async (providerData) => {
|
||||
return await authentikFetch('/providers/oauth2/', {
|
||||
method: 'POST',
|
||||
body: providerData
|
||||
});
|
||||
};
|
||||
|
||||
export const linkProviderToApplication = async (applicationId, providerId) => {
|
||||
return await authentikFetch(`/core/applications/${applicationId}/`, {
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
provider: providerId
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Add a utility function to verify tokens
|
||||
export const verifyToken = async (token) => {
|
||||
return await authentikFetch('/core/tokens/verify/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user