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;
$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', ''),
$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,
]);
}
/**
* 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);
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();
}
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;
}
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
$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>
<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>
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)
component="submit-on-change"
type="date"
name="{{ $filterKey }}"
- value="{{ $listDetails[$filterKey] ?? '' }}">
+ value="{{ $filters[$filterKey] ?? '' }}">
</div>
@endforeach
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">
-
- {{ $activities->links() }}
-
- <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>
+ <hr class="mt-m mb-s">
+
+ <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>
+
+ <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>
@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>
$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') }}