diff --git a/app/Http/Controllers/AuditLogController.php b/app/Http/Controllers/AuditLogController.php index ec3f36975..da8009d78 100644 --- a/app/Http/Controllers/AuditLogController.php +++ b/app/Http/Controllers/AuditLogController.php @@ -3,6 +3,8 @@ namespace BookStack\Http\Controllers; use BookStack\Actions\Activity; +use BookStack\Actions\ActivityType; +use BookStack\Util\SimpleListOptions; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -13,10 +15,15 @@ class AuditLogController extends Controller $this->checkPermission('settings-manage'); $this->checkPermission('users-manage'); - $listDetails = [ - 'order' => $request->get('order', 'desc'), + $sort = $request->get('sort', 'activity_date'); + $order = $request->get('order', 'desc'); + $listOptions = (new SimpleListOptions('', $sort, $order))->withSortOptions([ + 'created_at' => trans('settings.audit_table_date'), + 'type' => trans('settings.audit_table_event'), + ]); + + $filters = [ 'event' => $request->get('event', ''), - 'sort' => $request->get('sort', 'created_at'), 'date_from' => $request->get('date_from', ''), 'date_to' => $request->get('date_to', ''), 'user' => $request->get('user', ''), @@ -25,39 +32,38 @@ class AuditLogController extends Controller $query = Activity::query() ->with([ - 'entity' => function ($query) { - $query->withTrashed(); - }, + 'entity' => fn ($query) => $query->withTrashed(), 'user', ]) - ->orderBy($listDetails['sort'], $listDetails['order']); + ->orderBy($listOptions->getSort(), $listOptions->getOrder()); - if ($listDetails['event']) { - $query->where('type', '=', $listDetails['event']); + if ($filters['event']) { + $query->where('type', '=', $filters['event']); } - if ($listDetails['user']) { - $query->where('user_id', '=', $listDetails['user']); + if ($filters['user']) { + $query->where('user_id', '=', $filters['user']); } - if ($listDetails['date_from']) { - $query->where('created_at', '>=', $listDetails['date_from']); + if ($filters['date_from']) { + $query->where('created_at', '>=', $filters['date_from']); } - if ($listDetails['date_to']) { - $query->where('created_at', '<=', $listDetails['date_to']); + if ($filters['date_to']) { + $query->where('created_at', '<=', $filters['date_to']); } - if ($listDetails['ip']) { - $query->where('ip', 'like', $listDetails['ip'] . '%'); + if ($filters['ip']) { + $query->where('ip', 'like', $filters['ip'] . '%'); } $activities = $query->paginate(100); - $activities->appends($listDetails); + $activities->appends($request->all()); - $types = DB::table('activities')->select('type')->distinct()->pluck('type'); + $types = ActivityType::all(); $this->setPageTitle(trans('settings.audit')); return view('settings.audit', [ 'activities' => $activities, - 'listDetails' => $listDetails, + 'filters' => $filters, + 'listOptions' => $listOptions, 'activityTypes' => $types, ]); } diff --git a/resources/js/components/list-sort-control.js b/resources/js/components/list-sort-control.js index 23fc64ae6..3b642dbde 100644 --- a/resources/js/components/list-sort-control.js +++ b/resources/js/components/list-sort-control.js @@ -1,17 +1,22 @@ /** * ListSortControl * Manages the logic for the control which provides list sorting options. + * @extends {Component} */ class ListSortControl { - constructor(elem) { - this.elem = elem; - this.menu = elem.querySelector('ul'); + setup() { + this.elem = this.$el; + this.menu = this.$refs.menu; - this.sortInput = elem.querySelector('[name="sort"]'); - this.orderInput = elem.querySelector('[name="order"]'); - this.form = elem.querySelector('form'); + this.sortInput = this.$refs.sort; + this.orderInput = this.$refs.order; + this.form = this.$refs.form; + this.setupListeners(); + } + + setupListeners() { this.menu.addEventListener('click', event => { if (event.target.closest('[data-sort-value]') !== null) { this.sortOptionClick(event); @@ -34,8 +39,7 @@ class ListSortControl { sortDirectionClick(event) { const currentDir = this.orderInput.value; - const newDir = (currentDir === 'asc') ? 'desc' : 'asc'; - this.orderInput.value = newDir; + this.orderInput.value = (currentDir === 'asc') ? 'desc' : 'asc'; event.preventDefault(); this.form.submit(); } diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index d4413d32c..51389dc69 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -144,6 +144,10 @@ body.flexbox { flex-direction: column; } +.flex-container-row.inline, .flex-container-column.inline { + display: inline-flex !important; +} + .flex-container-column.wrap, .flex-container-row.wrap { flex-wrap: wrap; } diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index ab97466a5..44d0055b5 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -352,15 +352,4 @@ input.scroll-box-search, .scroll-box-header-item { transform: rotate(180deg); } } -} - -table.table .table-user-item { - display: grid; - grid-template-columns: 42px 1fr; - align-items: center; -} -table.table .table-entity-item { - display: grid; - grid-template-columns: 36px 1fr; - align-items: center; } \ No newline at end of file diff --git a/resources/views/common/sort.blade.php b/resources/views/common/sort.blade.php index f81ed797f..996f7a837 100644 --- a/resources/views/common/sort.blade.php +++ b/resources/views/common/sort.blade.php @@ -2,25 +2,40 @@ $selectedSort = (isset($sort) && array_key_exists($sort, $options)) ? $sort : array_keys($options)[0]; $order = (isset($order) && in_array($order, ['asc', 'desc'])) ? $order : 'asc'; ?> -<div class="list-sort-container" list-sort-control> +<div component="list-sort-control" class="list-sort-container"> <div class="list-sort-label">{{ trans('common.sort') }}</div> - <form action="{{ url("/settings/users/". user()->id ."/change-sort/{$type}") }}" method="post"> + <form refs="list-sort-control@form" + @if($useQuery ?? false) + action="{{ url()->current() }}" + method="get" + @else + action="{{ url("/settings/users/". user()->id ."/change-sort/{$type}") }}" + method="post" + @endif + > - {!! csrf_field() !!} - {!! method_field('PATCH') !!} - <input type="hidden" value="{{ $selectedSort }}" name="sort"> - <input type="hidden" value="{{ $order }}" name="order"> + @if($useQuery ?? false) + @foreach(array_filter(request()->except(['sort', 'order'])) as $key => $value) + <input type="hidden" name="{{ $key }}" value="{{ $value }}"> + @endforeach + @else + {!! method_field('PATCH') !!} + {!! csrf_field() !!} + @endif + + <input refs="list-sort-control@sort" type="hidden" value="{{ $selectedSort }}" name="sort"> + <input refs="list-sort-control@order" type="hidden" value="{{ $order }}" name="order"> <div class="list-sort"> <div component="dropdown" class="list-sort-type dropdown-container"> <div refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" tabindex="0">{{ $options[$selectedSort] }}</div> - <ul refs="dropdown@menu" class="dropdown-menu"> + <ul refs="dropdown@menu list-sort-control@menu" class="dropdown-menu"> @foreach($options as $key => $label) <li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}" class="text-item">{{ $label }}</a></li> @endforeach </ul> </div> - <button href="#" class="list-sort-dir" type="button" data-sort-dir + <button class="list-sort-dir" type="button" data-sort-dir aria-label="{{ trans('common.sort_direction_toggle') }} - {{ $order === 'asc' ? trans('common.sort_ascending') : trans('common.sort_descending') }}" tabindex="0"> @icon($order === 'desc' ? 'sort-up' : 'sort-down') </button> diff --git a/resources/views/settings/audit.blade.php b/resources/views/settings/audit.blade.php index 2daeb8a82..2df2499ab 100644 --- a/resources/views/settings/audit.blade.php +++ b/resources/views/settings/audit.blade.php @@ -9,7 +9,11 @@ <h1 class="list-heading">{{ trans('settings.audit') }}</h1> <p class="text-muted">{{ trans('settings.audit_desc') }}</p> - <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-m"> + <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-x-m gap-y-xs"> + + @foreach(request()->only(['order', 'sort']) as $key => $val) + <input type="hidden" name="{{ $key }}" value="{{ $val }}"> + @endforeach <div component="dropdown" class="list-sort-type dropdown-container"> <label for="">{{ trans('settings.audit_event_filter') }}</label> @@ -18,17 +22,17 @@ aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" - class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button> + class="input-base text-left">{{ $filters['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button> <ul refs="dropdown@menu" class="dropdown-menu"> - <li @if($listDetails['event'] === '') class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => '']) }}" class="text-item">{{ trans('settings.audit_event_filter_no_filter') }}</a></li> + <li @if($filters['event'] === '') class="active" @endif><a href="{{ sortUrl('/settings/audit', array_filter(request()->except('page')), ['event' => '']) }}" class="text-item">{{ trans('settings.audit_event_filter_no_filter') }}</a></li> @foreach($activityTypes as $type) - <li @if($type === $listDetails['event']) class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => $type]) }}" class="text-item">{{ $type }}</a></li> + <li @if($type === $filters['event']) class="active" @endif><a href="{{ sortUrl('/settings/audit', array_filter(request()->except('page')), ['event' => $type]) }}" class="text-item">{{ $type }}</a></li> @endforeach </ul> </div> - @if(!empty($listDetails['event'])) - <input type="hidden" name="event" value="{{ $listDetails['event'] }}"> + @if(!empty($filters['event'])) + <input type="hidden" name="event" value="{{ $filters['event'] }}"> @endif @foreach(['date_from', 'date_to'] as $filterKey) @@ -38,7 +42,7 @@ component="submit-on-change" type="date" name="{{ $filterKey }}" - value="{{ $listDetails[$filterKey] ?? '' }}"> + value="{{ $filters[$filterKey] ?? '' }}"> </div> @endforeach @@ -46,44 +50,47 @@ component="submit-on-change" option:submit-on-change:filter='[name="user"]'> <label for="owner">{{ trans('settings.audit_table_user') }}</label> - @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user']) + @include('form.user-select', ['user' => $filters['user'] ? \BookStack\Auth\User::query()->find($filters['user']) : null, 'name' => 'user']) </div> <div class="form-group"> <label for="ip">{{ trans('settings.audit_table_ip') }}</label> - @include('form.text', ['name' => 'ip', 'model' => (object) $listDetails]) + @include('form.text', ['name' => 'ip', 'model' => (object) $filters]) <input type="submit" style="display: none"> </div> </form> - <hr class="mt-l mb-s"> + <hr class="mt-m mb-s"> - {{ $activities->links() }} + <div class="flex-container-row justify-space-between items-center wrap"> + <div class="flex-2 min-width-xl">{{ $activities->links() }}</div> + <div class="flex-none min-width-m py-m"> + @include('common.sort', [...$listOptions->getSortControlData(), 'useQuery' => true]) + </div> + </div> - <table class="table"> - <tbody> - <tr> - <th>{{ trans('settings.audit_table_user') }}</th> - <th> - <a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'key']) }}">{{ trans('settings.audit_table_event') }}</a> - </th> - <th>{{ trans('settings.audit_table_related') }}</th> - <th>{{ trans('settings.audit_table_ip') }}</th> - <th> - <a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'created_at']) }}">{{ trans('settings.audit_table_date') }}</a></th> - </tr> + <div class="item-list"> + <div class="item-list-row flex-container-row items-center bold hide-under-m"> + <div class="flex-2 px-m py-xs flex-container-row items-center">{{ trans('settings.audit_table_user') }}</div> + <div class="flex-2 px-m py-xs">{{ trans('settings.audit_table_event') }}</div> + <div class="flex-3 px-m py-xs">{{ trans('settings.audit_table_related') }}</div> + <div class="flex-container-row flex-3"> + <div class="flex px-m py-xs">{{ trans('settings.audit_table_ip') }}</div> + <div class="flex-2 px-m py-xs text-right">{{ trans('settings.audit_table_date') }}</div> + </div> + </div> @foreach($activities as $activity) - <tr> - <td> + <div class="item-list-row flex-container-row items-center wrap"> + <div class="flex-2 px-m py-xs flex-container-row items-center min-width-m"> @include('settings.parts.table-user', ['user' => $activity->user, 'user_id' => $activity->user_id]) - </td> - <td>{{ $activity->type }}</td> - <td width="40%"> + </div> + <div class="flex-2 px-m py-xs min-width-m"><strong class="mr-xs hide-over-m">{{ trans('settings.audit_table_event') }}:</strong> {{ $activity->type }}</div> + <div class="flex-3 px-m py-xs min-width-l"> @if($activity->entity) - <a href="{{ $activity->entity->getUrl() }}" class="table-entity-item"> - <span role="presentation" class="icon text-{{$activity->entity->getType()}}">@icon($activity->entity->getType())</span> - <div class="text-{{ $activity->entity->getType() }}"> + <a href="{{ $activity->entity->getUrl() }}" class="flex-container-row items-center"> + <span role="presentation" class="icon flex-none text-{{$activity->entity->getType()}}">@icon($activity->entity->getType())</span> + <div class="flex text-{{ $activity->entity->getType() }}"> {{ $activity->entity->name }} </div> </a> @@ -95,15 +102,18 @@ @elseif($activity->detail) <div class="px-m">{{ $activity->detail }}</div> @endif - </td> - <td>{{ $activity->ip }}</td> - <td>{{ $activity->created_at }}</td> - </tr> + </div> + <div class="flex-container-row flex-3 wrap"> + <div class="flex px-m py-xs min-width-xs"><strong class="mr-xs hide-over-m">{{ trans('settings.audit_table_ip') }}:<br></strong> {{ $activity->ip }}</div> + <div class="flex-2 px-m py-xs text-m-right min-width-xs"><strong class="mr-xs hide-over-m">{{ trans('settings.audit_table_date') }}:<br></strong> {{ $activity->created_at }}</div> + </div> + </div> @endforeach - </tbody> - </table> + </div> - {{ $activities->links() }} + <div class="py-m"> + {{ $activities->links() }} + </div> </div> </div> diff --git a/resources/views/settings/parts/table-user.blade.php b/resources/views/settings/parts/table-user.blade.php index a8f2777f0..d29ad1979 100644 --- a/resources/views/settings/parts/table-user.blade.php +++ b/resources/views/settings/parts/table-user.blade.php @@ -3,9 +3,9 @@ $user - User mode to display, Can be null. $user_id - Id of user to show. Must be provided. --}} @if($user) - <a href="{{ $user->getEditUrl() }}" class="table-user-item"> - <div><img class="avatar block" src="{{ $user->getAvatar(40)}}" alt="{{ $user->name }}"></div> - <div>{{ $user->name }}</div> + <a href="{{ $user->getEditUrl() }}" class="flex-container-row inline gap-s items-center"> + <div class="flex-none"><img width="40" height="40" class="avatar block" src="{{ $user->getAvatar(40)}}" alt="{{ $user->name }}"></div> + <div class="flex">{{ $user->name }}</div> </a> @else [ID: {{ $user_id }}] {{ trans('common.deleted_user') }}