diff --git a/app/Entities/Entity.php b/app/Entities/Entity.php index 6a5894cac..120290d8f 100644 --- a/app/Entities/Entity.php +++ b/app/Entities/Entity.php @@ -238,10 +238,8 @@ class Entity extends Ownable /** * Gets a limited-length version of the entities name. - * @param int $length - * @return string */ - public function getShortName($length = 25) + public function getShortName(int $length = 25): string { if (mb_strlen($this->name) <= $length) { return $this->name; diff --git a/app/Http/Controllers/AuditLogController.php b/app/Http/Controllers/AuditLogController.php new file mode 100644 index 000000000..a3ef01baa --- /dev/null +++ b/app/Http/Controllers/AuditLogController.php @@ -0,0 +1,51 @@ +checkPermission('settings-manage'); + $this->checkPermission('users-manage'); + + $listDetails = [ + 'order' => $request->get('order', 'desc'), + 'event' => $request->get('event', ''), + 'sort' => $request->get('sort', 'created_at'), + 'date_from' => $request->get('date_from', ''), + 'date_to' => $request->get('date_to', ''), + ]; + + $query = Activity::query() + ->with(['entity', 'user']) + ->orderBy($listDetails['sort'], $listDetails['order']); + + if ($listDetails['event']) { + $query->where('key', '=', $listDetails['event']); + } + + if ($listDetails['date_from']) { + $query->where('created_at', '>=', $listDetails['date_from']); + } + if ($listDetails['date_to']) { + $query->where('created_at', '<=', $listDetails['date_to']); + } + + $activities = $query->paginate(100); + $activities->appends($listDetails); + + $keys = DB::table('activities')->select('key')->distinct()->pluck('key'); + $this->setPageTitle(trans('settings.audit')); + return view('settings.audit', [ + 'activities' => $activities, + 'listDetails' => $listDetails, + 'activityKeys' => $keys, + ]); + } +} diff --git a/app/helpers.php b/app/helpers.php index 65da1853b..83017c37d 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -153,10 +153,6 @@ function icon(string $name, array $attrs = []): string * Generate a url with multiple parameters for sorting purposes. * Works out the logic to set the correct sorting direction * Discards empty parameters and allows overriding. - * @param string $path - * @param array $data - * @param array $overrideData - * @return string */ function sortUrl(string $path, array $data, array $overrideData = []): string { @@ -166,7 +162,7 @@ function sortUrl(string $path, array $data, array $overrideData = []): string // Change sorting direction is already sorted on current attribute if (isset($overrideData['sort']) && $overrideData['sort'] === $data['sort']) { $queryData['order'] = ($data['order'] === 'asc') ? 'desc' : 'asc'; - } else { + } elseif (isset($overrideData['sort'])) { $queryData['order'] = 'asc'; } diff --git a/database/migrations/2020_09_19_094251_add_activity_indexes.php b/database/migrations/2020_09_19_094251_add_activity_indexes.php new file mode 100644 index 000000000..544b01e1f --- /dev/null +++ b/database/migrations/2020_09_19_094251_add_activity_indexes.php @@ -0,0 +1,34 @@ +index('key'); + $table->index('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('activities', function(Blueprint $table) { + $table->dropIndex('key'); + $table->dropIndex('created_at'); + }); + } +} diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 9a5f2d7d7..87c496c91 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -42,6 +42,7 @@ import settingColorPicker from "./setting-color-picker.js" import shelfSort from "./shelf-sort.js" import sidebar from "./sidebar.js" import sortableList from "./sortable-list.js" +import submitOnChange from "./submit-on-change.js" import tabs from "./tabs.js" import tagManager from "./tag-manager.js" import templateManager from "./template-manager.js" @@ -94,6 +95,7 @@ const componentMapping = { "shelf-sort": shelfSort, "sidebar": sidebar, "sortable-list": sortableList, + "submit-on-change": submitOnChange, "tabs": tabs, "tag-manager": tagManager, "template-manager": templateManager, diff --git a/resources/js/components/submit-on-change.js b/resources/js/components/submit-on-change.js new file mode 100644 index 000000000..979967242 --- /dev/null +++ b/resources/js/components/submit-on-change.js @@ -0,0 +1,19 @@ +/** + * Submit on change + * Simply submits a parent form when this input is changed. + * @extends {Component} + */ +class SubmitOnChange { + + setup() { + this.$el.addEventListener('change', () => { + const form = this.$el.closest('form'); + if (form) { + form.submit(); + } + }); + } + +} + +export default SubmitOnChange; \ No newline at end of file diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 679d4b8a8..2bd314cf0 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -81,6 +81,20 @@ return [ 'maint_send_test_email_mail_greeting' => 'Email delivery seems to work!', 'maint_send_test_email_mail_text' => 'Congratulations! As you received this email notification, your email settings seem to be configured properly.', + // Audit Log + 'audit' => 'Audit Log', + 'audit_desc' => 'This audit log displays a list of activities tracked in the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.', + 'audit_event_filter' => 'Event Filter', + 'audit_event_filter_no_filter' => 'No Filter', + 'audit_deleted_item' => 'Deleted Item', + 'audit_deleted_item_name' => 'Name: :name', + 'audit_table_user' => 'User', + 'audit_table_event' => 'Event', + 'audit_table_item' => 'Related Item', + 'audit_table_date' => 'Activity Date', + 'audit_date_from' => 'Date Range From', + 'audit_date_to' => 'Date Range To', + // Role Settings 'roles' => 'Roles', 'role_user_roles' => 'User Roles', diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index 439bf8512..cf2a1630e 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -121,6 +121,11 @@ body.flexbox { position: relative; } +.flex-container-row { + display: flex; + flex-direction: row; +} + .flex-container-column { display: flex; flex-direction: column; diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index f89fe039e..376541b5d 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -288,4 +288,15 @@ $btt-size: 40px; transform: rotate(180deg); } } +} + +table a.audit-log-user { + display: grid; + grid-template-columns: 42px 1fr; + align-items: center; +} +table a.icon-list-item { + display: grid; + grid-template-columns: 36px 1fr; + align-items: center; } \ No newline at end of file diff --git a/resources/views/settings/audit.blade.php b/resources/views/settings/audit.blade.php new file mode 100644 index 000000000..9b97f060d --- /dev/null +++ b/resources/views/settings/audit.blade.php @@ -0,0 +1,98 @@ +@extends('simple-layout') + +@section('body') +
{{ trans('settings.audit_desc') }}
+ +{{ trans('settings.audit_table_user') }} | ++ {{ trans('settings.audit_table_event') }} + | +{{ trans('settings.audit_table_item') }} | ++ {{ trans('settings.audit_table_date') }} | +
---|---|---|---|
+ @if($activity->user)
+
+
+ {{ $activity->user->name }}
+
+ @else
+ [ID: {{ $activity->user_id }}] {{ trans('common.deleted_user') }}
+ @endif
+ |
+ {{ $activity->key }} | +
+ @if($activity->entity)
+
+ @icon($activity->entity->getType())
+
+ {{ $activity->entity->name }}
+
+
+ @elseif($activity->extra)
+
+ {{ trans('settings.audit_deleted_item') }}
+ @endif
+ + {{ trans('settings.audit_deleted_item_name', ['name' => $activity->extra]) }} + |
+ {{ $activity->created_at }} | +