--- /dev/null
+<?php
+
+namespace BookStack\Auth\Queries;
+
+use BookStack\Auth\Role;
+use Illuminate\Pagination\LengthAwarePaginator;
+
+/**
+ * Get all the roles in the system in a paginated format.
+ */
+class AllRolesPaginatedAndSorted
+{
+ /**
+ * @param array{sort: string, order: string, search: string} $sortData
+ */
+ public function run(int $count, array $sortData): LengthAwarePaginator
+ {
+ $sort = $sortData['sort'];
+ if ($sort === 'created_at') {
+ $sort = 'users.created_at';
+ }
+
+ $query = Role::query()->select(['*'])
+ ->withCount(['users', 'permissions'])
+ ->orderBy($sort, $sortData['order']);
+
+ if ($sortData['search']) {
+ $term = '%' . $sortData['search'] . '%';
+ $query->where(function ($query) use ($term) {
+ $query->where('display_name', 'like', $term)
+ ->orWhere('description', 'like', $term);
+ });
+ }
+
+ return $query->paginate($count);
+ }
+}
return static::query()->where('system_name', '=', $systemName)->first();
}
- /**
- * Get all visible roles.
- */
- public static function visible(): Collection
- {
- return static::query()->where('hidden', '=', false)->orderBy('name')->get();
- }
-
/**
* {@inheritdoc}
*/
namespace BookStack\Http\Controllers;
use BookStack\Auth\Permissions\PermissionsRepo;
+use BookStack\Auth\Queries\AllRolesPaginatedAndSorted;
use BookStack\Auth\Role;
use BookStack\Exceptions\PermissionsException;
use Exception;
class RoleController extends Controller
{
- protected $permissionsRepo;
+ protected PermissionsRepo $permissionsRepo;
- /**
- * PermissionController constructor.
- */
public function __construct(PermissionsRepo $permissionsRepo)
{
$this->permissionsRepo = $permissionsRepo;
/**
* Show a listing of the roles in the system.
*/
- public function index()
+ public function index(Request $request)
{
$this->checkPermission('user-roles-manage');
- $roles = $this->permissionsRepo->getAllRoles();
+
+ $listDetails = [
+ 'search' => $request->get('search', ''),
+ 'sort' => setting()->getForCurrentUser('roles_sort', 'display_name'),
+ 'order' => setting()->getForCurrentUser('roles_sort_order', 'asc'),
+ ];
+
+ $roles = (new AllRolesPaginatedAndSorted())->run(20, $listDetails);
+ $roles->appends(['search' => $listDetails['search']]);
$this->setPageTitle(trans('settings.roles'));
- return view('settings.roles.index', ['roles' => $roles]);
+ return view('settings.roles.index', [
+ 'roles' => $roles,
+ 'listDetails' => $listDetails,
+ ]);
}
/**
/**
* Show the form for editing a user role.
- *
- * @throws PermissionsException
*/
public function edit(string $id)
{
$this->checkPermission('user-roles-manage');
$role = $this->permissionsRepo->getRoleById($id);
- if ($role->hidden) {
- throw new PermissionsException(trans('errors.role_cannot_be_edited'));
- }
$this->setPageTitle(trans('settings.role_edit'));
*/
public function changeSort(Request $request, string $id, string $type)
{
- $validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users'];
+ $validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles'];
if (!in_array($type, $validSortTypes)) {
return redirect()->back(500);
}
$this->checkPermissionOrCurrentUser('users-manage', $userId);
$sort = $request->get('sort');
- if (!in_array($sort, ['name', 'created_at', 'updated_at', 'default', 'email', 'last_activity_at'])) {
+ // TODO - Need to find a better way to validate sort options
+ // Probably better to do a simple validation here then validate at usage.
+ $validSorts = [
+ 'name', 'created_at', 'updated_at', 'default', 'email', 'last_activity_at', 'display_name',
+ 'users_count', 'permissions_count',
+ ];
+ if (!in_array($sort, $validSorts)) {
$sort = 'name';
}
// Role Settings
'roles' => 'Roles',
'role_user_roles' => 'User Roles',
+ 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
+ 'roles_x_users_assigned' => '1 user assigned|:count users assigned',
+ 'roles_x_permissions_provided' => '1 permission|:count permissions',
+ 'roles_assigned_users' => 'Assigned Users',
+ 'roles_permissions_provided' => 'Provided Permissions',
'role_create' => 'Create New Role',
'role_create_success' => 'Role successfully created',
'role_delete' => 'Delete Role',
<h1 class="list-heading">{{ trans('settings.role_user_roles') }}</h1>
<div class="text-right">
- <a href="{{ url("/settings/roles/new") }}" class="button outline">{{ trans('settings.role_create') }}</a>
+ <a href="{{ url("/settings/roles/new") }}" class="button outline my-none">{{ trans('settings.role_create') }}</a>
</div>
</div>
- <table class="table">
- <tr>
- <th>{{ trans('settings.role_name') }}</th>
- <th></th>
- <th class="text-center">{{ trans('settings.users') }}</th>
- </tr>
+ <p class="text-muted">{{ trans('settings.roles_index_desc') }}</p>
+
+ <div class="flex-container-row items-center justify-space-between gap-m mt-m mb-l wrap">
+ <div>
+ <div class="block inline mr-xs">
+ <form method="get" action="{{ url("/settings/roles") }}">
+ <input type="text" name="search" placeholder="{{ trans('common.search') }}" @if($listDetails['search']) value="{{$listDetails['search']}}" @endif>
+ </form>
+ </div>
+ </div>
+ <div class="justify-flex-end">
+ @include('common.sort', ['options' => [
+ 'display_name' => trans('common.sort_name'),
+ 'users_count' => trans('settings.roles_assigned_users'),
+ 'permissions_count' => trans('settings.roles_permissions_provided'),
+ ], 'order' => $listDetails['order'], 'sort' => $listDetails['sort'], 'type' => 'roles'])
+ </div>
+ </div>
+
+ <div class="item-list">
@foreach($roles as $role)
- <tr>
- <td><a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td>
- <td>
- @if($role->mfa_enforced)
- <span title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </span>
- @endif
- {{ $role->description }}
- </td>
- <td class="text-center">{{ $role->users->count() }}</td>
- </tr>
+ @include('settings.roles.parts.roles-list-item', ['role' => $role])
@endforeach
- </table>
+ </div>
+ <div class="mb-m">
+ {{ $roles->links() }}
+ </div>
</div>
</div>
--- /dev/null
+<div class="item-list-row flex-container-row py-xs items-center">
+ <div class="py-xs px-m flex-2">
+ <a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a><br>
+ @if($role->mfa_enforced)
+ <small title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </small>
+ @endif
+ <small>{{ $role->description }}</small>
+ </div>
+ <div class="text-right flex py-xs px-m text-muted">
+ {{ trans_choice('settings.roles_x_users_assigned', $role->users_count, ['count' => $role->users_count]) }}
+ <br>
+ {{ trans_choice('settings.roles_x_permissions_provided', $role->permissions_count, ['count' => $role->permissions_count]) }}
+ </div>
+</div>
\ No newline at end of file
<div class="flex-container-row wrap justify-space-between items-center">
<h1 class="list-heading">{{ trans('settings.users') }}</h1>
<div>
- <a href="{{ url("/settings/users/create") }}" class="outline button mt-none">{{ trans('settings.users_add_new') }}</a>
+ <a href="{{ url("/settings/users/create") }}" class="outline button my-none">{{ trans('settings.users_add_new') }}</a>
</div>
</div>