CRUD Example: Roles Management
Copy-paste ready Roles CRUD: controller with cache-aware filtering, Bootstrap 5 Blade views, routes, and JavaScript helpers.
Controller: app/Http/Controllers/Admin/RoleController.php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use RbacSuite\OmniAccess\Models\Role;
use RbacSuite\OmniAccess\Models\Permission;
use RbacSuite\OmniAccess\Models\Group;
use RbacSuite\OmniAccess\Services\CacheService;
class RoleController extends Controller
{
protected $cacheService;
public function __construct(CacheService $cacheService)
{
$this->cacheService = $cacheService;
// Apply middleware
$this->middleware('auth');
$this->middleware('permission:roles.view')->only(['index', 'show']);
$this->middleware('permission:roles.create')->only(['create', 'store']);
$this->middleware('permission:roles.edit')->only(['edit', 'update']);
$this->middleware('permission:roles.delete')->only('destroy');
}
/**
* Display a listing of roles
*/
public function index(Request $request)
{
$query = Role::withCount(['users', 'permissions']);
// Search functionality
if ($request->filled('search')) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('slug', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
// Sort functionality
$sortField = $request->get('sort', 'name');
$sortDirection = $request->get('direction', 'asc');
if (in_array($sortField, ['name', 'slug', 'created_at', 'users_count', 'permissions_count'])) {
$query->orderBy($sortField, $sortDirection);
}
$roles = $query->paginate(10)->withQueryString();
return view('admin.roles.index', compact('roles'));
}
/**
* Show the form for creating a new role
*/
public function create()
{
$permissions = Permission::with('group')
->get()
->groupBy('group.name');
return view('admin.roles.create', compact('permissions'));
}
/**
* Store a newly created role
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:50',
'slug' => 'required|string|max:50|unique:roles,slug|regex:/^[a-z0-9\-]+$/',
'description' => 'nullable|string|max:255',
'is_default' => 'boolean',
'permissions' => 'nullable|array',
'permissions.*' => 'exists:permissions,id'
]);
// Create role
$role = Role::create([
'name' => $validated['name'],
'slug' => $validated['slug'],
'description' => $validated['description'],
'is_default' => $request->boolean('is_default')
]);
// Assign permissions
if (!empty($validated['permissions'])) {
$role->permissions()->sync($validated['permissions']);
}
// Clear cache
$this->cacheService->forgetRole($role->id);
return redirect()
->route('admin.roles.index')
->with('success', 'Role created successfully!');
}
/**
* Display the specified role
*/
public function show(Role $role)
{
$role->load(['permissions.group', 'users']);
$groupedPermissions = $role->permissions
->groupBy('group.name');
$recentUsers = $role->users()
->latest('role_user.created_at')
->limit(5)
->get();
return view('admin.roles.show', compact('role', 'groupedPermissions', 'recentUsers'));
}
/**
* Show the form for editing the specified role
*/
public function edit(Role $role)
{
// Prevent editing super-admin role
if ($role->slug === config('omni-access.super_admin_role')) {
return redirect()
->route('admin.roles.index')
->with('error', 'Cannot edit super admin role!');
}
$permissions = Permission::with('group')
->get()
->groupBy('group.name');
$rolePermissions = $role->permissions->pluck('id')->toArray();
return view('admin.roles.edit', compact('role', 'permissions', 'rolePermissions'));
}
/**
* Update the specified role
*/
public function update(Request $request, Role $role)
{
// Prevent editing super-admin role
if ($role->slug === config('omni-access.super_admin_role')) {
return redirect()
->route('admin.roles.index')
->with('error', 'Cannot edit super admin role!');
}
$validated = $request->validate([
'name' => 'required|string|max:50',
'slug' => 'required|string|max:50|unique:roles,slug,' . $role->id . '|regex:/^[a-z0-9\-]+$/',
'description' => 'nullable|string|max:255',
'is_default' => 'boolean',
'permissions' => 'nullable|array',
'permissions.*' => 'exists:permissions,id'
]);
// Update role
$role->update([
'name' => $validated['name'],
'slug' => $validated['slug'],
'description' => $validated['description'],
'is_default' => $request->boolean('is_default')
]);
// Sync permissions
$role->permissions()->sync($validated['permissions'] ?? []);
// Clear cache
$this->cacheService->forgetRole($role->id);
return redirect()
->route('admin.roles.show', $role)
->with('success', 'Role updated successfully!');
}
/**
* Remove the specified role
*/
public function destroy(Role $role)
{
// Prevent deleting super-admin role
if ($role->slug === config('omni-access.super_admin_role')) {
return response()->json([
'success' => false,
'message' => 'Cannot delete super admin role!'
], 403);
}
// Prevent deleting default role
if ($role->is_default) {
return response()->json([
'success' => false,
'message' => 'Cannot delete default role!'
], 403);
}
// Check if role has users
if ($role->users()->count() > 0) {
return response()->json([
'success' => false,
'message' => 'Cannot delete role with assigned users!'
], 403);
}
$role->delete();
// Clear cache
$this->cacheService->forgetRole($role->id);
if (request()->wantsJson()) {
return response()->json([
'success' => true,
'message' => 'Role deleted successfully!'
]);
}
return redirect()
->route('admin.roles.index')
->with('success', 'Role deleted successfully!');
}
/**
* Clone a role
*/
public function clone(Role $role)
{
$newRole = $role->replicate();
$newRole->name = $role->name . ' (Copy)';
$newRole->slug = $role->slug . '-copy-' . time();
$newRole->is_default = false;
$newRole->save();
// Clone permissions
$newRole->permissions()->sync($role->permissions);
return redirect()
->route('admin.roles.edit', $newRole)
->with('success', 'Role cloned successfully!');
}
}
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use RbacSuite\OmniAccess\Models\Role;
use RbacSuite\OmniAccess\Models\Permission;
use RbacSuite\OmniAccess\Models\Group;
use RbacSuite\OmniAccess\Services\CacheService;
class RoleController extends Controller
{
protected $cacheService;
public function __construct(CacheService $cacheService)
{
$this->cacheService = $cacheService;
// Apply middleware
$this->middleware('auth');
$this->middleware('permission:roles.view')->only(['index', 'show']);
$this->middleware('permission:roles.create')->only(['create', 'store']);
$this->middleware('permission:roles.edit')->only(['edit', 'update']);
$this->middleware('permission:roles.delete')->only('destroy');
}
/**
* Display a listing of roles
*/
public function index(Request $request)
{
$query = Role::withCount(['users', 'permissions']);
// Search functionality
if ($request->filled('search')) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('slug', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
// Sort functionality
$sortField = $request->get('sort', 'name');
$sortDirection = $request->get('direction', 'asc');
if (in_array($sortField, ['name', 'slug', 'created_at', 'users_count', 'permissions_count'])) {
$query->orderBy($sortField, $sortDirection);
}
$roles = $query->paginate(10)->withQueryString();
return view('admin.roles.index', compact('roles'));
}
/**
* Show the form for creating a new role
*/
public function create()
{
$permissions = Permission::with('group')
->get()
->groupBy('group.name');
return view('admin.roles.create', compact('permissions'));
}
/**
* Store a newly created role
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:50',
'slug' => 'required|string|max:50|unique:roles,slug|regex:/^[a-z0-9\-]+$/',
'description' => 'nullable|string|max:255',
'is_default' => 'boolean',
'permissions' => 'nullable|array',
'permissions.*' => 'exists:permissions,id'
]);
// Create role
$role = Role::create([
'name' => $validated['name'],
'slug' => $validated['slug'],
'description' => $validated['description'],
'is_default' => $request->boolean('is_default')
]);
// Assign permissions
if (!empty($validated['permissions'])) {
$role->permissions()->sync($validated['permissions']);
}
// Clear cache
$this->cacheService->forgetRole($role->id);
return redirect()
->route('admin.roles.index')
->with('success', 'Role created successfully!');
}
/**
* Display the specified role
*/
public function show(Role $role)
{
$role->load(['permissions.group', 'users']);
$groupedPermissions = $role->permissions
->groupBy('group.name');
$recentUsers = $role->users()
->latest('role_user.created_at')
->limit(5)
->get();
return view('admin.roles.show', compact('role', 'groupedPermissions', 'recentUsers'));
}
/**
* Show the form for editing the specified role
*/
public function edit(Role $role)
{
// Prevent editing super-admin role
if ($role->slug === config('omni-access.super_admin_role')) {
return redirect()
->route('admin.roles.index')
->with('error', 'Cannot edit super admin role!');
}
$permissions = Permission::with('group')
->get()
->groupBy('group.name');
$rolePermissions = $role->permissions->pluck('id')->toArray();
return view('admin.roles.edit', compact('role', 'permissions', 'rolePermissions'));
}
/**
* Update the specified role
*/
public function update(Request $request, Role $role)
{
// Prevent editing super-admin role
if ($role->slug === config('omni-access.super_admin_role')) {
return redirect()
->route('admin.roles.index')
->with('error', 'Cannot edit super admin role!');
}
$validated = $request->validate([
'name' => 'required|string|max:50',
'slug' => 'required|string|max:50|unique:roles,slug,' . $role->id . '|regex:/^[a-z0-9\-]+$/',
'description' => 'nullable|string|max:255',
'is_default' => 'boolean',
'permissions' => 'nullable|array',
'permissions.*' => 'exists:permissions,id'
]);
// Update role
$role->update([
'name' => $validated['name'],
'slug' => $validated['slug'],
'description' => $validated['description'],
'is_default' => $request->boolean('is_default')
]);
// Sync permissions
$role->permissions()->sync($validated['permissions'] ?? []);
// Clear cache
$this->cacheService->forgetRole($role->id);
return redirect()
->route('admin.roles.show', $role)
->with('success', 'Role updated successfully!');
}
/**
* Remove the specified role
*/
public function destroy(Role $role)
{
// Prevent deleting super-admin role
if ($role->slug === config('omni-access.super_admin_role')) {
return response()->json([
'success' => false,
'message' => 'Cannot delete super admin role!'
], 403);
}
// Prevent deleting default role
if ($role->is_default) {
return response()->json([
'success' => false,
'message' => 'Cannot delete default role!'
], 403);
}
// Check if role has users
if ($role->users()->count() > 0) {
return response()->json([
'success' => false,
'message' => 'Cannot delete role with assigned users!'
], 403);
}
$role->delete();
// Clear cache
$this->cacheService->forgetRole($role->id);
if (request()->wantsJson()) {
return response()->json([
'success' => true,
'message' => 'Role deleted successfully!'
]);
}
return redirect()
->route('admin.roles.index')
->with('success', 'Role deleted successfully!');
}
/**
* Clone a role
*/
public function clone(Role $role)
{
$newRole = $role->replicate();
$newRole->name = $role->name . ' (Copy)';
$newRole->slug = $role->slug . '-copy-' . time();
$newRole->is_default = false;
$newRole->save();
// Clone permissions
$newRole->permissions()->sync($role->permissions);
return redirect()
->route('admin.roles.edit', $newRole)
->with('success', 'Role cloned successfully!');
}
}
View: resources/views/admin/roles/index.blade.php
@extends('layouts.admin')
@section('title', 'Roles Management')
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<div class="page-header d-flex justify-content-between align-items-center">
<div>
<h1 class="h3 mb-0 text-gray-800">Roles Management</h1>
<p class="text-muted mb-0">Manage system roles and their permissions</p>
</div>
<div>
@permission('roles.create')
<a href="{{ route('admin.roles.create') }}" class="btn btn-primary">
<i class="bi bi-plus-circle me-2"></i>Create New Role
</a>
@endpermission
</div>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-primary shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
Total Roles
</div>
<div class="h5 mb-0 font-weight-bold">{{ \RbacSuite\OmniAccess\Models\Role::count() }}</div>
</div>
<div class="col-auto">
<i class="bi bi-person-badge fs-2 text-primary"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-success shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
Active Users
</div>
<div class="h5 mb-0 font-weight-bold">{{ \App\Models\User::count() }}</div>
</div>
<div class="col-auto">
<i class="bi bi-people fs-2 text-success"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-info shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
Total Permissions
</div>
<div class="h5 mb-0 font-weight-bold">{{ \RbacSuite\OmniAccess\Models\Permission::count() }}</div>
</div>
<div class="col-auto">
<i class="bi bi-key fs-2 text-info"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-warning shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
Default Role
</div>
<div class="h6 mb-0 font-weight-bold">
{{ \RbacSuite\OmniAccess\Models\Role::where('is_default', true)->first()->name ?? 'None' }}
</div>
</div>
<div class="col-auto">
<i class="bi bi-star fs-2 text-warning"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Alerts -->
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle me-2"></i>{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
@if(session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<!-- Main Card -->
<div class="card shadow-sm">
<div class="card-header bg-white py-3">
<div class="row align-items-center">
<div class="col-md-6">
<form action="{{ route('admin.roles.index') }}" method="GET" class="d-flex">
<div class="input-group">
<input type="text"
name="search"
class="form-control"
placeholder="Search roles..."
value="{{ request('search') }}">
<button class="btn btn-outline-secondary" type="submit">
<i class="bi bi-search"></i>
</button>
@if(request('search'))
<a href="{{ route('admin.roles.index') }}" class="btn btn-outline-secondary">
<i class="bi bi-x-lg"></i>
</a>
@endif
</div>
</form>
</div>
<div class="col-md-6 text-end">
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-secondary" onclick="toggleBulkSelect()">
<i class="bi bi-check-square me-1"></i>Select
</button>
<button type="button" class="btn btn-outline-danger d-none" id="bulkDeleteBtn" onclick="bulkDelete()">
<i class="bi bi-trash me-1"></i>Delete Selected
</button>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="bulk-select-column d-none" style="width: 40px;">
<input type="checkbox" class="form-check-input" id="selectAll">
</th>
<th>Name</th>
<th>Slug</th>
<th>Description</th>
<th class="text-center">Users</th>
<th class="text-center">Permissions</th>
<th class="text-center">Status</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
@forelse($roles as $role)
<tr>
<td class="bulk-select-column d-none">
@if($role->slug !== config('omni-access.super_admin_role') && !$role->is_default && $role->users_count == 0)
<input type="checkbox" class="form-check-input role-checkbox" value="{{ $role->id }}">
@endif
</td>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle me-2">
<span class="text-primary fw-bold">{{ strtoupper(substr($role->name, 0, 2)) }}</span>
</div>
<div>
<div class="fw-semibold">{{ $role->name }}</div>
@if($role->slug === config('omni-access.super_admin_role'))
<span class="badge bg-danger">Super Admin</span>
@elseif($role->is_default)
<span class="badge bg-info">Default</span>
@endif
</div>
</div>
</td>
<td>
<code class="text-primary">{{ $role->slug }}</code>
</td>
<td>
<span class="text-muted">{{ $role->description ?: '-' }}</span>
</td>
<td class="text-center">
<span class="badge bg-success">{{ $role->users_count }}</span>
</td>
<td class="text-center">
<span class="badge bg-primary">{{ $role->permissions_count }}</span>
</td>
<td class="text-center">
@if($role->deleted_at)
<span class="badge bg-danger">Deleted</span>
@else
<span class="badge bg-success">Active</span>
@endif
</td>
<td class="text-end">
<div class="btn-group" role="group">
<a href="{{ route('admin.roles.show', $role) }}"
class="btn btn-sm btn-outline-info"
title="View">
<i class="bi bi-eye"></i>
</a>
@permission('roles.edit')
@if($role->slug !== config('omni-access.super_admin_role'))
<a href="{{ route('admin.roles.edit', $role) }}"
class="btn btn-sm btn-outline-primary"
title="Edit">
<i class="bi bi-pencil"></i>
</a>
@endif
@endpermission
@permission('roles.create')
<button type="button"
class="btn btn-sm btn-outline-secondary"
onclick="cloneRole({{ $role->id }})"
title="Clone">
<i class="bi bi-files"></i>
</button>
@endpermission
@permission('roles.delete')
@if($role->slug !== config('omni-access.super_admin_role') && !$role->is_default && $role->users_count == 0)
<button type="button"
class="btn btn-sm btn-outline-danger"
onclick="deleteRole({{ $role->id }}, '{{ $role->name }}')"
title="Delete">
<i class="bi bi-trash"></i>
</button>
@endif
@endpermission
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center py-4">
<div class="text-muted">
<i class="bi bi-inbox fs-1"></i>
<p class="mt-2">No roles found</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@if($roles->hasPages())
<div class="card-footer bg-white">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted">
Showing {{ $roles->firstItem() }} to {{ $roles->lastItem() }} of {{ $roles->total() }} results
</div>
<div>
{{ $roles->links('pagination::bootstrap-5') }}
</div>
</div>
</div>
@endif
</div>
</div>
@endsection
@extends('layouts.admin')
@section('title', 'Roles Management')
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<div class="page-header d-flex justify-content-between align-items-center">
<div>
<h1 class="h3 mb-0 text-gray-800">Roles Management</h1>
<p class="text-muted mb-0">Manage system roles and their permissions</p>
</div>
<div>
@permission('roles.create')
<a href="{{ route('admin.roles.create') }}" class="btn btn-primary">
<i class="bi bi-plus-circle me-2"></i>Create New Role
</a>
@endpermission
</div>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-primary shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
Total Roles
</div>
<div class="h5 mb-0 font-weight-bold">{{ \RbacSuite\OmniAccess\Models\Role::count() }}</div>
</div>
<div class="col-auto">
<i class="bi bi-person-badge fs-2 text-primary"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-success shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
Active Users
</div>
<div class="h5 mb-0 font-weight-bold">{{ \App\Models\User::count() }}</div>
</div>
<div class="col-auto">
<i class="bi bi-people fs-2 text-success"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-info shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
Total Permissions
</div>
<div class="h5 mb-0 font-weight-bold">{{ \RbacSuite\OmniAccess\Models\Permission::count() }}</div>
</div>
<div class="col-auto">
<i class="bi bi-key fs-2 text-info"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<div class="card border-left-warning shadow-sm h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
Default Role
</div>
<div class="h6 mb-0 font-weight-bold">
{{ \RbacSuite\OmniAccess\Models\Role::where('is_default', true)->first()->name ?? 'None' }}
</div>
</div>
<div class="col-auto">
<i class="bi bi-star fs-2 text-warning"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Alerts -->
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle me-2"></i>{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
@if(session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<!-- Main Card -->
<div class="card shadow-sm">
<div class="card-header bg-white py-3">
<div class="row align-items-center">
<div class="col-md-6">
<form action="{{ route('admin.roles.index') }}" method="GET" class="d-flex">
<div class="input-group">
<input type="text"
name="search"
class="form-control"
placeholder="Search roles..."
value="{{ request('search') }}">
<button class="btn btn-outline-secondary" type="submit">
<i class="bi bi-search"></i>
</button>
@if(request('search'))
<a href="{{ route('admin.roles.index') }}" class="btn btn-outline-secondary">
<i class="bi bi-x-lg"></i>
</a>
@endif
</div>
</form>
</div>
<div class="col-md-6 text-end">
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-secondary" onclick="toggleBulkSelect()">
<i class="bi bi-check-square me-1"></i>Select
</button>
<button type="button" class="btn btn-outline-danger d-none" id="bulkDeleteBtn" onclick="bulkDelete()">
<i class="bi bi-trash me-1"></i>Delete Selected
</button>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="bulk-select-column d-none" style="width: 40px;">
<input type="checkbox" class="form-check-input" id="selectAll">
</th>
<th>Name</th>
<th>Slug</th>
<th>Description</th>
<th class="text-center">Users</th>
<th class="text-center">Permissions</th>
<th class="text-center">Status</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
@forelse($roles as $role)
<tr>
<td class="bulk-select-column d-none">
@if($role->slug !== config('omni-access.super_admin_role') && !$role->is_default && $role->users_count == 0)
<input type="checkbox" class="form-check-input role-checkbox" value="{{ $role->id }}">
@endif
</td>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle me-2">
<span class="text-primary fw-bold">{{ strtoupper(substr($role->name, 0, 2)) }}</span>
</div>
<div>
<div class="fw-semibold">{{ $role->name }}</div>
@if($role->slug === config('omni-access.super_admin_role'))
<span class="badge bg-danger">Super Admin</span>
@elseif($role->is_default)
<span class="badge bg-info">Default</span>
@endif
</div>
</div>
</td>
<td>
<code class="text-primary">{{ $role->slug }}</code>
</td>
<td>
<span class="text-muted">{{ $role->description ?: '-' }}</span>
</td>
<td class="text-center">
<span class="badge bg-success">{{ $role->users_count }}</span>
</td>
<td class="text-center">
<span class="badge bg-primary">{{ $role->permissions_count }}</span>
</td>
<td class="text-center">
@if($role->deleted_at)
<span class="badge bg-danger">Deleted</span>
@else
<span class="badge bg-success">Active</span>
@endif
</td>
<td class="text-end">
<div class="btn-group" role="group">
<a href="{{ route('admin.roles.show', $role) }}"
class="btn btn-sm btn-outline-info"
title="View">
<i class="bi bi-eye"></i>
</a>
@permission('roles.edit')
@if($role->slug !== config('omni-access.super_admin_role'))
<a href="{{ route('admin.roles.edit', $role) }}"
class="btn btn-sm btn-outline-primary"
title="Edit">
<i class="bi bi-pencil"></i>
</a>
@endif
@endpermission
@permission('roles.create')
<button type="button"
class="btn btn-sm btn-outline-secondary"
onclick="cloneRole({{ $role->id }})"
title="Clone">
<i class="bi bi-files"></i>
</button>
@endpermission
@permission('roles.delete')
@if($role->slug !== config('omni-access.super_admin_role') && !$role->is_default && $role->users_count == 0)
<button type="button"
class="btn btn-sm btn-outline-danger"
onclick="deleteRole({{ $role->id }}, '{{ $role->name }}')"
title="Delete">
<i class="bi bi-trash"></i>
</button>
@endif
@endpermission
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center py-4">
<div class="text-muted">
<i class="bi bi-inbox fs-1"></i>
<p class="mt-2">No roles found</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@if($roles->hasPages())
<div class="card-footer bg-white">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted">
Showing {{ $roles->firstItem() }} to {{ $roles->lastItem() }} of {{ $roles->total() }} results
</div>
<div>
{{ $roles->links('pagination::bootstrap-5') }}
</div>
</div>
</div>
@endif
</div>
</div>
@endsection
View: resources/views/admin/roles/create.blade.php
@extends('layouts.admin')
@section('title', 'Create Role')
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.roles.index') }}">Roles</a></li>
<li class="breadcrumb-item active">Create Role</li>
</ol>
</nav>
<h1 class="h3 mb-0 text-gray-800">Create New Role</h1>
</div>
</div>
<form action="{{ route('admin.roles.store') }}" method="POST" id="createRoleForm">
@csrf
<div class="row">
<!-- Role Information -->
<div class="col-lg-8">
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="name" class="form-label">Role Name <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('name') is-invalid @enderror"
id="name"
name="name"
value="{{ old('name') }}"
placeholder="e.g., Content Manager"
required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">Display name for the role</small>
</div>
<div class="mb-3">
<label for="slug" class="form-label">Slug <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('slug') is-invalid @enderror"
id="slug"
name="slug"
value="{{ old('slug') }}"
placeholder="e.g., content-manager"
pattern="[a-z0-9\-]+"
required>
@error('slug')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">Unique identifier (lowercase, no spaces)</small>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description"
name="description"
rows="3"
placeholder="Brief description of this role...">{{ old('description') }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-check">
<input class="form-check-input"
type="checkbox"
id="is_default"
name="is_default"
value="1"
{{ old('is_default') ? 'checked' : '' }}>
<label class="form-check-label" for="is_default">
Set as default role for new users
</label>
</div>
</div>
</div>
<!-- Permissions -->
<div class="card shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-key me-2"></i>Permissions</h5>
<div>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="selectAll()">
Select All
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectNone()">
Clear All
</button>
</div>
</div>
<div class="card-body">
@forelse($permissions as $groupName => $groupPermissions)
<div class="permission-group mb-4">
<div class="d-flex align-items-center mb-2">
<h6 class="mb-0 me-auto">{{ $groupName ?: 'Ungrouped' }}</h6>
<button type="button"
class="btn btn-sm btn-link text-decoration-none"
onclick="toggleGroup('{{ Str::slug($groupName) }}')">
Select Group
</button>
</div>
<div class="row" data-group="{{ Str::slug($groupName) }}">
@foreach($groupPermissions as $permission)
<div class="col-md-6 col-lg-4 mb-2">
<div class="form-check">
<input class="form-check-input permission-checkbox"
type="checkbox"
name="permissions[]"
value="{{ $permission->id }}"
id="permission_{{ $permission->id }}"
{{ in_array($permission->id, old('permissions', [])) ? 'checked' : '' }}>
<label class="form-check-label" for="permission_{{ $permission->id }}">
{{ $permission->name }}
<small class="text-muted d-block">{{ $permission->slug }}</small>
</label>
</div>
</div>
@endforeach
</div>
</div>
@if(!$loop->last)
<hr>
@endif
@empty
<div class="text-center text-muted py-4">
<i class="bi bi-shield-x fs-1"></i>
<p class="mt-2">No permissions available</p>
</div>
@endforelse
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-lightning me-2"></i>Quick Actions</h5>
</div>
<div class="card-body">
<button type="submit" class="btn btn-primary w-100 mb-2">
<i class="bi bi-check-circle me-2"></i>Create Role
</button>
<button type="submit" name="create_another" value="1" class="btn btn-outline-primary w-100 mb-2">
<i class="bi bi-plus-circle me-2"></i>Create & Add Another
</button>
<a href="{{ route('admin.roles.index') }}" class="btn btn-outline-secondary w-100">
<i class="bi bi-x-circle me-2"></i>Cancel
</a>
</div>
</div>
<!-- Role Templates -->
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-collection me-2"></i>Role Templates</h5>
</div>
<div class="card-body">
<p class="small text-muted">Quick permission presets:</p>
<button type="button" class="btn btn-sm btn-outline-info w-100 mb-2" onclick="applyTemplate('viewer')">
<i class="bi bi-eye me-1"></i>Viewer (Read Only)
</button>
<button type="button" class="btn btn-sm btn-outline-warning w-100 mb-2" onclick="applyTemplate('editor')">
<i class="bi bi-pencil me-1"></i>Editor (Create & Edit)
</button>
<button type="button" class="btn btn-sm btn-outline-success w-100 mb-2" onclick="applyTemplate('moderator')">
<i class="bi bi-shield-check me-1"></i>Moderator (All Content)
</button>
<button type="button" class="btn btn-sm btn-outline-danger w-100" onclick="applyTemplate('admin')">
<i class="bi bi-gear me-1"></i>Admin (Full Access)
</button>
</div>
</div>
</div>
</div>
</form>
</div>
@endsection
@extends('layouts.admin')
@section('title', 'Create Role')
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.roles.index') }}">Roles</a></li>
<li class="breadcrumb-item active">Create Role</li>
</ol>
</nav>
<h1 class="h3 mb-0 text-gray-800">Create New Role</h1>
</div>
</div>
<form action="{{ route('admin.roles.store') }}" method="POST" id="createRoleForm">
@csrf
<div class="row">
<!-- Role Information -->
<div class="col-lg-8">
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="name" class="form-label">Role Name <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('name') is-invalid @enderror"
id="name"
name="name"
value="{{ old('name') }}"
placeholder="e.g., Content Manager"
required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">Display name for the role</small>
</div>
<div class="mb-3">
<label for="slug" class="form-label">Slug <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('slug') is-invalid @enderror"
id="slug"
name="slug"
value="{{ old('slug') }}"
placeholder="e.g., content-manager"
pattern="[a-z0-9\-]+"
required>
@error('slug')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">Unique identifier (lowercase, no spaces)</small>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description"
name="description"
rows="3"
placeholder="Brief description of this role...">{{ old('description') }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-check">
<input class="form-check-input"
type="checkbox"
id="is_default"
name="is_default"
value="1"
{{ old('is_default') ? 'checked' : '' }}>
<label class="form-check-label" for="is_default">
Set as default role for new users
</label>
</div>
</div>
</div>
<!-- Permissions -->
<div class="card shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-key me-2"></i>Permissions</h5>
<div>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="selectAll()">
Select All
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectNone()">
Clear All
</button>
</div>
</div>
<div class="card-body">
@forelse($permissions as $groupName => $groupPermissions)
<div class="permission-group mb-4">
<div class="d-flex align-items-center mb-2">
<h6 class="mb-0 me-auto">{{ $groupName ?: 'Ungrouped' }}</h6>
<button type="button"
class="btn btn-sm btn-link text-decoration-none"
onclick="toggleGroup('{{ Str::slug($groupName) }}')">
Select Group
</button>
</div>
<div class="row" data-group="{{ Str::slug($groupName) }}">
@foreach($groupPermissions as $permission)
<div class="col-md-6 col-lg-4 mb-2">
<div class="form-check">
<input class="form-check-input permission-checkbox"
type="checkbox"
name="permissions[]"
value="{{ $permission->id }}"
id="permission_{{ $permission->id }}"
{{ in_array($permission->id, old('permissions', [])) ? 'checked' : '' }}>
<label class="form-check-label" for="permission_{{ $permission->id }}">
{{ $permission->name }}
<small class="text-muted d-block">{{ $permission->slug }}</small>
</label>
</div>
</div>
@endforeach
</div>
</div>
@if(!$loop->last)
<hr>
@endif
@empty
<div class="text-center text-muted py-4">
<i class="bi bi-shield-x fs-1"></i>
<p class="mt-2">No permissions available</p>
</div>
@endforelse
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-lightning me-2"></i>Quick Actions</h5>
</div>
<div class="card-body">
<button type="submit" class="btn btn-primary w-100 mb-2">
<i class="bi bi-check-circle me-2"></i>Create Role
</button>
<button type="submit" name="create_another" value="1" class="btn btn-outline-primary w-100 mb-2">
<i class="bi bi-plus-circle me-2"></i>Create & Add Another
</button>
<a href="{{ route('admin.roles.index') }}" class="btn btn-outline-secondary w-100">
<i class="bi bi-x-circle me-2"></i>Cancel
</a>
</div>
</div>
<!-- Role Templates -->
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-collection me-2"></i>Role Templates</h5>
</div>
<div class="card-body">
<p class="small text-muted">Quick permission presets:</p>
<button type="button" class="btn btn-sm btn-outline-info w-100 mb-2" onclick="applyTemplate('viewer')">
<i class="bi bi-eye me-1"></i>Viewer (Read Only)
</button>
<button type="button" class="btn btn-sm btn-outline-warning w-100 mb-2" onclick="applyTemplate('editor')">
<i class="bi bi-pencil me-1"></i>Editor (Create & Edit)
</button>
<button type="button" class="btn btn-sm btn-outline-success w-100 mb-2" onclick="applyTemplate('moderator')">
<i class="bi bi-shield-check me-1"></i>Moderator (All Content)
</button>
<button type="button" class="btn btn-sm btn-outline-danger w-100" onclick="applyTemplate('admin')">
<i class="bi bi-gear me-1"></i>Admin (Full Access)
</button>
</div>
</div>
</div>
</div>
</form>
</div>
@endsection
View: resources/views/admin/roles/edit.blade.php
@extends('layouts.admin')
@section('title', 'Edit Role - ' . $role->name)
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.roles.index') }}">Roles</a></li>
<li class="breadcrumb-item active">Edit Role</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0 text-gray-800">Edit Role: {{ $role->name }}</h1>
<a href="{{ route('admin.roles.show', $role) }}" class="btn btn-outline-info">
<i class="bi bi-eye me-2"></i>View Role
</a>
</div>
</div>
</div>
<form action="{{ route('admin.roles.update', $role) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
<!-- Role Information -->
<div class="col-lg-8">
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="name" class="form-label">Role Name <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('name') is-invalid @enderror"
id="name"
name="name"
value="{{ old('name', $role->name) }}"
required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="slug" class="form-label">Slug <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('slug') is-invalid @enderror"
id="slug"
name="slug"
value="{{ old('slug', $role->slug) }}"
pattern="[a-z0-9\-]+"
required>
@error('slug')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">Changing the slug may affect existing code references</small>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description"
name="description"
rows="3">{{ old('description', $role->description) }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-check">
<input class="form-check-input"
type="checkbox"
id="is_default"
name="is_default"
value="1"
{{ old('is_default', $role->is_default) ? 'checked' : '' }}>
<label class="form-check-label" for="is_default">
Set as default role for new users
</label>
</div>
</div>
</div>
<!-- Permissions -->
<div class="card shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-key me-2"></i>Permissions</h5>
<div>
<span class="badge bg-primary me-2">
<span id="selectedCount">{{ count($rolePermissions) }}</span> selected
</span>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="selectAll()">
Select All
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectNone()">
Clear All
</button>
</div>
</div>
<div class="card-body">
@forelse($permissions as $groupName => $groupPermissions)
<div class="permission-group mb-4">
<div class="d-flex align-items-center mb-2">
<h6 class="mb-0 me-auto">{{ $groupName ?: 'Ungrouped' }}</h6>
<button type="button"
class="btn btn-sm btn-link text-decoration-none"
onclick="toggleGroup('{{ Str::slug($groupName) }}')">
Toggle Group
</button>
</div>
<div class="row" data-group="{{ Str::slug($groupName) }}">
@foreach($groupPermissions as $permission)
<div class="col-md-6 col-lg-4 mb-2">
<div class="form-check">
<input class="form-check-input permission-checkbox"
type="checkbox"
name="permissions[]"
value="{{ $permission->id }}"
id="permission_{{ $permission->id }}"
{{ in_array($permission->id, old('permissions', $rolePermissions)) ? 'checked' : '' }}
onchange="updateCount()">
<label class="form-check-label" for="permission_{{ $permission->id }}">
{{ $permission->name }}
<small class="text-muted d-block">{{ $permission->slug }}</small>
</label>
</div>
</div>
@endforeach
</div>
</div>
@if(!$loop->last)
<hr>
@endif
@empty
<div class="text-center text-muted py-4">
<i class="bi bi-shield-x fs-1"></i>
<p class="mt-2">No permissions available</p>
</div>
@endforelse
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-lightning me-2"></i>Actions</h5>
</div>
<div class="card-body">
<button type="submit" class="btn btn-primary w-100 mb-2">
<i class="bi bi-save me-2"></i>Save Changes
</button>
<button type="submit" name="save_and_continue" value="1" class="btn btn-outline-primary w-100 mb-2">
<i class="bi bi-save me-2"></i>Save & Continue Editing
</button>
<a href="{{ route('admin.roles.index') }}" class="btn btn-outline-secondary w-100">
<i class="bi bi-x-circle me-2"></i>Cancel
</a>
</div>
</div>
<!-- Role Info -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Details</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-5">Created:</dt>
<dd class="col-7">{{ $role->created_at->format('M d, Y') }}</dd>
<dt class="col-5">Updated:</dt>
<dd class="col-7">{{ $role->updated_at->format('M d, Y') }}</dd>
<dt class="col-5">Users:</dt>
<dd class="col-7">
<span class="badge bg-success">{{ $role->users()->count() }}</span>
</dd>
<dt class="col-5">Permissions:</dt>
<dd class="col-7">
<span class="badge bg-primary">{{ $role->permissions()->count() }}</span>
</dd>
</dl>
</div>
</div>
</div>
</div>
</form>
</div>
@endsection
@extends('layouts.admin')
@section('title', 'Edit Role - ' . $role->name)
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.roles.index') }}">Roles</a></li>
<li class="breadcrumb-item active">Edit Role</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0 text-gray-800">Edit Role: {{ $role->name }}</h1>
<a href="{{ route('admin.roles.show', $role) }}" class="btn btn-outline-info">
<i class="bi bi-eye me-2"></i>View Role
</a>
</div>
</div>
</div>
<form action="{{ route('admin.roles.update', $role) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
<!-- Role Information -->
<div class="col-lg-8">
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="name" class="form-label">Role Name <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('name') is-invalid @enderror"
id="name"
name="name"
value="{{ old('name', $role->name) }}"
required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="slug" class="form-label">Slug <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('slug') is-invalid @enderror"
id="slug"
name="slug"
value="{{ old('slug', $role->slug) }}"
pattern="[a-z0-9\-]+"
required>
@error('slug')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">Changing the slug may affect existing code references</small>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description"
name="description"
rows="3">{{ old('description', $role->description) }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-check">
<input class="form-check-input"
type="checkbox"
id="is_default"
name="is_default"
value="1"
{{ old('is_default', $role->is_default) ? 'checked' : '' }}>
<label class="form-check-label" for="is_default">
Set as default role for new users
</label>
</div>
</div>
</div>
<!-- Permissions -->
<div class="card shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-key me-2"></i>Permissions</h5>
<div>
<span class="badge bg-primary me-2">
<span id="selectedCount">{{ count($rolePermissions) }}</span> selected
</span>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="selectAll()">
Select All
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectNone()">
Clear All
</button>
</div>
</div>
<div class="card-body">
@forelse($permissions as $groupName => $groupPermissions)
<div class="permission-group mb-4">
<div class="d-flex align-items-center mb-2">
<h6 class="mb-0 me-auto">{{ $groupName ?: 'Ungrouped' }}</h6>
<button type="button"
class="btn btn-sm btn-link text-decoration-none"
onclick="toggleGroup('{{ Str::slug($groupName) }}')">
Toggle Group
</button>
</div>
<div class="row" data-group="{{ Str::slug($groupName) }}">
@foreach($groupPermissions as $permission)
<div class="col-md-6 col-lg-4 mb-2">
<div class="form-check">
<input class="form-check-input permission-checkbox"
type="checkbox"
name="permissions[]"
value="{{ $permission->id }}"
id="permission_{{ $permission->id }}"
{{ in_array($permission->id, old('permissions', $rolePermissions)) ? 'checked' : '' }}
onchange="updateCount()">
<label class="form-check-label" for="permission_{{ $permission->id }}">
{{ $permission->name }}
<small class="text-muted d-block">{{ $permission->slug }}</small>
</label>
</div>
</div>
@endforeach
</div>
</div>
@if(!$loop->last)
<hr>
@endif
@empty
<div class="text-center text-muted py-4">
<i class="bi bi-shield-x fs-1"></i>
<p class="mt-2">No permissions available</p>
</div>
@endforelse
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-lightning me-2"></i>Actions</h5>
</div>
<div class="card-body">
<button type="submit" class="btn btn-primary w-100 mb-2">
<i class="bi bi-save me-2"></i>Save Changes
</button>
<button type="submit" name="save_and_continue" value="1" class="btn btn-outline-primary w-100 mb-2">
<i class="bi bi-save me-2"></i>Save & Continue Editing
</button>
<a href="{{ route('admin.roles.index') }}" class="btn btn-outline-secondary w-100">
<i class="bi bi-x-circle me-2"></i>Cancel
</a>
</div>
</div>
<!-- Role Info -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Details</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-5">Created:</dt>
<dd class="col-7">{{ $role->created_at->format('M d, Y') }}</dd>
<dt class="col-5">Updated:</dt>
<dd class="col-7">{{ $role->updated_at->format('M d, Y') }}</dd>
<dt class="col-5">Users:</dt>
<dd class="col-7">
<span class="badge bg-success">{{ $role->users()->count() }}</span>
</dd>
<dt class="col-5">Permissions:</dt>
<dd class="col-7">
<span class="badge bg-primary">{{ $role->permissions()->count() }}</span>
</dd>
</dl>
</div>
</div>
</div>
</div>
</form>
</div>
@endsection
View: resources/views/admin/roles/show.blade.php
@extends('layouts.admin')
@section('title', 'Role Details - ' . $role->name)
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.roles.index') }}">Roles</a></li>
<li class="breadcrumb-item active">{{ $role->name }}</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center">
<div>
<h1 class="h3 mb-2 text-gray-800">{{ $role->name }}</h1>
<div>
@if($role->slug === config('omni-access.super_admin_role'))
<span class="badge bg-danger">Super Admin</span>
@elseif($role->is_default)
<span class="badge bg-info">Default Role</span>
@endif
@if($role->deleted_at)
<span class="badge bg-danger">Deleted</span>
@else
<span class="badge bg-success">Active</span>
@endif
</div>
</div>
<div>
@permission('roles.edit')
@if($role->slug !== config('omni-access.super_admin_role'))
<a href="{{ route('admin.roles.edit', $role) }}" class="btn btn-primary">
<i class="bi bi-pencil me-2"></i>Edit Role
</a>
@endif
@endpermission
</div>
</div>
</div>
</div>
<div class="row">
<!-- Role Information -->
<div class="col-lg-4 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Information</h5>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-4">Name:</dt>
<dd class="col-sm-8">{{ $role->name }}</dd>
<dt class="col-sm-4">Slug:</dt>
<dd class="col-sm-8"><code>{{ $role->slug }}</code></dd>
<dt class="col-sm-4">Description:</dt>
<dd class="col-sm-8">{{ $role->description ?: 'No description' }}</dd>
<dt class="col-sm-4">Default Role:</dt>
<dd class="col-sm-8">
@if($role->is_default)
<span class="badge bg-success">Yes</span>
@else
<span class="badge bg-secondary">No</span>
@endif
</dd>
<dt class="col-sm-4">Created:</dt>
<dd class="col-sm-8">
{{ $role->created_at->format('M d, Y') }}
<small class="text-muted d-block">{{ $role->created_at->diffForHumans() }}</small>
</dd>
<dt class="col-sm-4">Updated:</dt>
<dd class="col-sm-8">
{{ $role->updated_at->format('M d, Y') }}
<small class="text-muted d-block">{{ $role->updated_at->diffForHumans() }}</small>
</dd>
</dl>
<hr>
<div class="row text-center">
<div class="col-6">
<h3 class="mb-0">{{ $role->users->count() }}</h3>
<p class="text-muted mb-0">Users</p>
</div>
<div class="col-6">
<h3 class="mb-0">{{ $role->permissions->count() }}</h3>
<p class="text-muted mb-0">Permissions</p>
</div>
</div>
</div>
</div>
</div>
<!-- Permissions -->
<div class="col-lg-8 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0">
<i class="bi bi-key me-2"></i>
Permissions
<span class="badge bg-primary ms-2">{{ $role->permissions->count() }}</span>
</h5>
</div>
<div class="card-body">
@forelse($groupedPermissions as $groupName => $permissions)
<div class="mb-4">
<h6 class="text-uppercase text-muted mb-3">
{{ $groupName ?: 'Ungrouped' }}
<span class="badge bg-secondary ms-2">{{ $permissions->count() }}</span>
</h6>
<div class="row">
@foreach($permissions as $permission)
<div class="col-md-6 col-lg-4 mb-3">
<div class="d-flex align-items-start">
<i class="bi bi-check-circle-fill text-success me-2 mt-1"></i>
<div>
<div class="fw-semibold">{{ $permission->name }}</div>
<code class="small text-muted">{{ $permission->slug }}</code>
@if($permission->description)
<p class="small text-muted mb-0 mt-1">{{ $permission->description }}</p>
@endif
</div>
</div>
</div>
@endforeach
</div>
</div>
@if(!$loop->last)
<hr>
@endif
@empty
<div class="text-center py-5">
<i class="bi bi-shield-x fs-1 text-muted"></i>
<p class="mt-2 text-muted">No permissions assigned to this role</p>
@permission('roles.edit')
@if($role->slug !== config('omni-access.super_admin_role'))
<a href="{{ route('admin.roles.edit', $role) }}" class="btn btn-primary">
<i class="bi bi-plus-circle me-2"></i>Add Permissions
</a>
@endif
@endpermission
</div>
@endforelse
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('layouts.admin')
@section('title', 'Role Details - ' . $role->name)
@section('content')
<div class="container-fluid">
<!-- Page Header -->
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.roles.index') }}">Roles</a></li>
<li class="breadcrumb-item active">{{ $role->name }}</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center">
<div>
<h1 class="h3 mb-2 text-gray-800">{{ $role->name }}</h1>
<div>
@if($role->slug === config('omni-access.super_admin_role'))
<span class="badge bg-danger">Super Admin</span>
@elseif($role->is_default)
<span class="badge bg-info">Default Role</span>
@endif
@if($role->deleted_at)
<span class="badge bg-danger">Deleted</span>
@else
<span class="badge bg-success">Active</span>
@endif
</div>
</div>
<div>
@permission('roles.edit')
@if($role->slug !== config('omni-access.super_admin_role'))
<a href="{{ route('admin.roles.edit', $role) }}" class="btn btn-primary">
<i class="bi bi-pencil me-2"></i>Edit Role
</a>
@endif
@endpermission
</div>
</div>
</div>
</div>
<div class="row">
<!-- Role Information -->
<div class="col-lg-4 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>Role Information</h5>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-4">Name:</dt>
<dd class="col-sm-8">{{ $role->name }}</dd>
<dt class="col-sm-4">Slug:</dt>
<dd class="col-sm-8"><code>{{ $role->slug }}</code></dd>
<dt class="col-sm-4">Description:</dt>
<dd class="col-sm-8">{{ $role->description ?: 'No description' }}</dd>
<dt class="col-sm-4">Default Role:</dt>
<dd class="col-sm-8">
@if($role->is_default)
<span class="badge bg-success">Yes</span>
@else
<span class="badge bg-secondary">No</span>
@endif
</dd>
<dt class="col-sm-4">Created:</dt>
<dd class="col-sm-8">
{{ $role->created_at->format('M d, Y') }}
<small class="text-muted d-block">{{ $role->created_at->diffForHumans() }}</small>
</dd>
<dt class="col-sm-4">Updated:</dt>
<dd class="col-sm-8">
{{ $role->updated_at->format('M d, Y') }}
<small class="text-muted d-block">{{ $role->updated_at->diffForHumans() }}</small>
</dd>
</dl>
<hr>
<div class="row text-center">
<div class="col-6">
<h3 class="mb-0">{{ $role->users->count() }}</h3>
<p class="text-muted mb-0">Users</p>
</div>
<div class="col-6">
<h3 class="mb-0">{{ $role->permissions->count() }}</h3>
<p class="text-muted mb-0">Permissions</p>
</div>
</div>
</div>
</div>
</div>
<!-- Permissions -->
<div class="col-lg-8 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0">
<i class="bi bi-key me-2"></i>
Permissions
<span class="badge bg-primary ms-2">{{ $role->permissions->count() }}</span>
</h5>
</div>
<div class="card-body">
@forelse($groupedPermissions as $groupName => $permissions)
<div class="mb-4">
<h6 class="text-uppercase text-muted mb-3">
{{ $groupName ?: 'Ungrouped' }}
<span class="badge bg-secondary ms-2">{{ $permissions->count() }}</span>
</h6>
<div class="row">
@foreach($permissions as $permission)
<div class="col-md-6 col-lg-4 mb-3">
<div class="d-flex align-items-start">
<i class="bi bi-check-circle-fill text-success me-2 mt-1"></i>
<div>
<div class="fw-semibold">{{ $permission->name }}</div>
<code class="small text-muted">{{ $permission->slug }}</code>
@if($permission->description)
<p class="small text-muted mb-0 mt-1">{{ $permission->description }}</p>
@endif
</div>
</div>
</div>
@endforeach
</div>
</div>
@if(!$loop->last)
<hr>
@endif
@empty
<div class="text-center py-5">
<i class="bi bi-shield-x fs-1 text-muted"></i>
<p class="mt-2 text-muted">No permissions assigned to this role</p>
@permission('roles.edit')
@if($role->slug !== config('omni-access.super_admin_role'))
<a href="{{ route('admin.roles.edit', $role) }}" class="btn btn-primary">
<i class="bi bi-plus-circle me-2"></i>Add Permissions
</a>
@endif
@endpermission
</div>
@endforelse
</div>
</div>
</div>
</div>
</div>
@endsection
Routes Configuration
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\RoleController;
// Admin Routes with OMNI Access
Route::prefix('admin')->name('admin.')->middleware(['auth'])->group(function () {
// Dashboard
Route::get('/', function () {
return view('admin.dashboard');
})->name('dashboard');
// Roles Management
Route::prefix('roles')->name('roles.')->group(function () {
Route::get('/', [RoleController::class, 'index'])->name('index');
Route::get('/create', [RoleController::class, 'create'])->name('create');
Route::post('/', [RoleController::class, 'store'])->name('store');
Route::get('/{role}', [RoleController::class, 'show'])->name('show');
Route::get('/{role}/edit', [RoleController::class, 'edit'])->name('edit');
Route::put('/{role}', [RoleController::class, 'update'])->name('update');
Route::delete('/{role}', [RoleController::class, 'destroy'])->name('destroy');
// Additional routes
Route::post('/bulk-delete', [RoleController::class, 'bulkDelete'])->name('bulk-delete');
Route::post('/{role}/clone', [RoleController::class, 'clone'])->name('clone');
});
});
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\RoleController;
// Admin Routes with OMNI Access
Route::prefix('admin')->name('admin.')->middleware(['auth'])->group(function () {
// Dashboard
Route::get('/', function () {
return view('admin.dashboard');
})->name('dashboard');
// Roles Management
Route::prefix('roles')->name('roles.')->group(function () {
Route::get('/', [RoleController::class, 'index'])->name('index');
Route::get('/create', [RoleController::class, 'create'])->name('create');
Route::post('/', [RoleController::class, 'store'])->name('store');
Route::get('/{role}', [RoleController::class, 'show'])->name('show');
Route::get('/{role}/edit', [RoleController::class, 'edit'])->name('edit');
Route::put('/{role}', [RoleController::class, 'update'])->name('update');
Route::delete('/{role}', [RoleController::class, 'destroy'])->name('destroy');
// Additional routes
Route::post('/bulk-delete', [RoleController::class, 'bulkDelete'])->name('bulk-delete');
Route::post('/{role}/clone', [RoleController::class, 'clone'])->name('clone');
});
});
JavaScript Functions (Add to Views)
// Toggle bulk select
function toggleBulkSelect() {
const columns = document.querySelectorAll('.bulk-select-column');
const bulkBtn = document.getElementById('bulkDeleteBtn');
columns.forEach(col => col.classList.toggle('d-none'));
bulkBtn.classList.toggle('d-none');
}
// Select all checkboxes
document.getElementById('selectAll')?.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.role-checkbox');
checkboxes.forEach(cb => cb.checked = this.checked);
});
// Delete single role
function deleteRole(id, name) {
if (!confirm(`Are you sure you want to delete the role "${name}"?`)) {
return;
}
fetch(`/admin/roles/${id}`, {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message || 'Error deleting role');
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the role');
});
}
// Auto-generate slug from name
document.getElementById('name')?.addEventListener('input', function(e) {
const slug = document.getElementById('slug');
if (!slug.dataset.manual) {
slug.value = e.target.value
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
}
});
// Mark slug as manually edited
document.getElementById('slug')?.addEventListener('input', function() {
this.dataset.manual = true;
});
// Select all permissions
function selectAll() {
document.querySelectorAll('.permission-checkbox').forEach(cb => cb.checked = true);
updateCount?.();
}
// Clear all permissions
function selectNone() {
document.querySelectorAll('.permission-checkbox').forEach(cb => cb.checked = false);
updateCount?.();
}
// Toggle group permissions
function toggleGroup(group) {
const checkboxes = document.querySelectorAll(`[data-group="${group}"] .permission-checkbox`);
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
checkboxes.forEach(cb => cb.checked = !allChecked);
updateCount?.();
}
// Update selected count
function updateCount() {
const count = document.querySelectorAll('.permission-checkbox:checked').length;
const counter = document.getElementById('selectedCount');
if (counter) {
counter.textContent = count;
}
}
// Apply permission template
function applyTemplate(template) {
selectNone();
const templates = {
viewer: ['view'],
editor: ['view', 'create', 'edit'],
moderator: ['view', 'create', 'edit', 'delete'],
admin: ['view', 'create', 'edit', 'delete', 'manage', 'settings']
};
const keywords = templates[template] || [];
document.querySelectorAll('.permission-checkbox').forEach(cb => {
const label = cb.nextElementSibling.textContent.toLowerCase();
cb.checked = keywords.some(keyword => label.includes(keyword));
});
updateCount?.();
}
// Toggle bulk select
function toggleBulkSelect() {
const columns = document.querySelectorAll('.bulk-select-column');
const bulkBtn = document.getElementById('bulkDeleteBtn');
columns.forEach(col => col.classList.toggle('d-none'));
bulkBtn.classList.toggle('d-none');
}
// Select all checkboxes
document.getElementById('selectAll')?.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.role-checkbox');
checkboxes.forEach(cb => cb.checked = this.checked);
});
// Delete single role
function deleteRole(id, name) {
if (!confirm(`Are you sure you want to delete the role "${name}"?`)) {
return;
}
fetch(`/admin/roles/${id}`, {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message || 'Error deleting role');
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while deleting the role');
});
}
// Auto-generate slug from name
document.getElementById('name')?.addEventListener('input', function(e) {
const slug = document.getElementById('slug');
if (!slug.dataset.manual) {
slug.value = e.target.value
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
}
});
// Mark slug as manually edited
document.getElementById('slug')?.addEventListener('input', function() {
this.dataset.manual = true;
});
// Select all permissions
function selectAll() {
document.querySelectorAll('.permission-checkbox').forEach(cb => cb.checked = true);
updateCount?.();
}
// Clear all permissions
function selectNone() {
document.querySelectorAll('.permission-checkbox').forEach(cb => cb.checked = false);
updateCount?.();
}
// Toggle group permissions
function toggleGroup(group) {
const checkboxes = document.querySelectorAll(`[data-group="${group}"] .permission-checkbox`);
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
checkboxes.forEach(cb => cb.checked = !allChecked);
updateCount?.();
}
// Update selected count
function updateCount() {
const count = document.querySelectorAll('.permission-checkbox:checked').length;
const counter = document.getElementById('selectedCount');
if (counter) {
counter.textContent = count;
}
}
// Apply permission template
function applyTemplate(template) {
selectNone();
const templates = {
viewer: ['view'],
editor: ['view', 'create', 'edit'],
moderator: ['view', 'create', 'edit', 'delete'],
admin: ['view', 'create', 'edit', 'delete', 'manage', 'settings']
};
const keywords = templates[template] || [];
document.querySelectorAll('.permission-checkbox').forEach(cb => {
const label = cb.nextElementSibling.textContent.toLowerCase();
cb.checked = keywords.some(keyword => label.includes(keyword));
});
updateCount?.();
}
Features Included in This Example
-
✓Complete CRUD operations
-
✓Search and filtering
-
✓Sorting by columns
-
✓Pagination with Bootstrap 5
-
✓Bulk delete functionality
-
✓Role cloning feature
-
✓Permission templates
-
✓Permission-based access control
-
✓Form validation
-
✓Responsive Bootstrap 5 UI
-
✓AJAX operations
-
✓User avatars with UI Avatars
-
✓Statistics cards
-
✓Breadcrumb navigation
Community Discussions 0
Ask questions, share step solutions, or discuss software workflows with other professionals.
Join the Community Discussion
Got stuck on a step or have a question about this configuration? Sign in to join the discussion and receive direct advice from instructors and peers.
No Discussions Yet
Have a question or found a clever tip for this guide? Start the conversation using the comment box above!