Add Period & Sort options to user access log

This commit is contained in:
Bubka 2024-04-15 18:39:37 +02:00
parent 4f17e2aff0
commit 44d7328d6c
7 changed files with 111 additions and 7 deletions

View File

@ -216,10 +216,12 @@ public function authentications(Request $request, User $user)
$this->authorize('view', $user);
$validated = $this->validate($request, [
'period' => 'sometimes|numeric',
'limit' => 'sometimes|numeric',
]);
$authentications = $request->has('limit') ? $user->authentications->take($validated['limit']) : $user->authentications;
$authentications = $request->has('period') ? $user->authentications($validated['period'])->get() : $user->authentications->get();
$authentications = $request->has('limit') ? $authentications->take($validated['limit']) : $authentications;
return UserAuthentication::collection($authentications);
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Models\Traits;
use Illuminate\Support\Carbon;
use Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog;
use Rappasoft\LaravelAuthenticationLog\Traits\AuthenticationLoggable as TraitsAuthenticationLoggable;
trait AuthenticationLoggable
{
use TraitsAuthenticationLoggable;
public function authentications(int $period = 1)
{
$from = Carbon::now()->subMonths($period);
return $this->morphMany(AuthenticationLog::class, 'authenticatable')
->where('login_at', '>=', $from)
->orWhere('logout_at', '>=', $from)
->orderByDesc('id');
}
}

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Models\Traits\AuthenticationLoggable;
use App\Models\Traits\WebAuthnManageCredentials;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Notifications\ResetPassword;
@ -12,7 +13,6 @@
use Illuminate\Support\Str;
use Laragear\WebAuthn\WebAuthnAuthentication;
use Laravel\Passport\HasApiTokens;
use Rappasoft\LaravelAuthenticationLog\Traits\AuthenticationLoggable;
/**
* App\Models\User

View File

@ -1,7 +1,8 @@
<script setup>
import SearchBox from '@/components/SearchBox.vue'
import { useNotifyStore } from '@/stores/notify'
import userService from '@/services/userService'
import Spinner from '@/components/Spinner.vue'
import { useNotifyStore } from '@/stores/notify'
import { FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
const notify = useNotifyStore()
@ -12,9 +13,18 @@
showSearch: Boolean
})
const periods = {
aMonth: 1,
threeMonths: 3,
halfYear: 6,
aYear: 12
}
const authentications = ref([])
const isFetching = ref(false)
const searched = ref('')
const period = ref(periods.aMonth)
const orderIsDesc = ref(true)
const visibleAuthentications = computed(() => {
return authentications.value.filter(authentication => {
@ -29,6 +39,46 @@
getAuthentications()
})
/**
* Sets the visible time span
*
* @param {int} duration
*/
function setPeriod(duration) {
period.value = duration
getAuthentications()
}
/**
* Set sort order to ASC
*/
function setAsc() {
orderIsDesc.value = false
sortAsc()
}
/**
* Sorts entries ascending
*/
function sortAsc() {
authentications.value.sort((a, b) => a.id > b.id ? 1 : -1)
}
/**
* Set sort order to DESC
*/
function setDesc() {
orderIsDesc.value = true
sortDesc()
}
/**
* Sorts entries descending
*/
function sortDesc() {
authentications.value.sort((a, b) => a.id < b.id ? 1 : -1)
}
/**
* Gets user authentication logs
*/
@ -36,9 +86,10 @@
isFetching.value = true
let limit = props.lastOnly ? 3 : false
userService.getauthentications(props.userId, limit, {returnError: true})
userService.getauthentications(props.userId, period.value, limit, {returnError: true})
.then(response => {
authentications.value = response.data
orderIsDesc.value == true ? sortDesc() : sortAsc()
})
.catch(error => {
notify.error(error)
@ -74,6 +125,24 @@
<template>
<SearchBox v-if="props.showSearch" v-model:keyword="searched" :hasNoBackground="true" />
<nav v-if="props.showSearch" class="level is-mobile">
<div class="level-item has-text-centered">
<div class="buttons">
<button id="btnShowOneMonth" :title="$t('admin.show_last_month_log')" v-on:click="setPeriod(periods.aMonth)" :class="{ 'has-text-grey' : period !== periods.aMonth }" class="button is-ghost p-1">1m</button>
<button id="btnShowThreeMonths" :title="$t('admin.show_three_months_log')" v-on:click="setPeriod(periods.threeMonths)" :class="{ 'has-text-grey' : period !== periods.threeMonths }" class="button is-ghost p-1">3m</button>
<button id="btnShowSixMonths" :title="$t('admin.show_six_months_log')" v-on:click="setPeriod(periods.halfYear)" :class="{ 'has-text-grey' : period !== periods.halfYear }" class="button is-ghost p-1">6m</button>
<button id="btnShowOneYear" :title="$t('admin.show_one_year_log')" v-on:click="setPeriod(periods.aYear)" :class="{ 'has-text-grey' : period !== periods.aYear }" class="button is-ghost p-1 mr-5">1y</button>
<button id="btnSortLogDesc" v-on:click="setDesc" :title="$t('admin.sort_by_date_desc')" :class="{ 'has-text-grey' : !orderIsDesc }" class="button p-1 is-ghost">
<FontAwesomeIcon :icon="['fas', 'arrow-up-long']" flip="vertical" />
<FontAwesomeIcon :icon="['far', 'calendar']" />
</button>
<button id="btnSortLogAsc" v-on:click="setAsc" :title="$t('admin.sort_by_date_asc')" :class="{ 'has-text-grey' : orderIsDesc }" class="button p-1 is-ghost">
<FontAwesomeIcon :icon="['fas', 'arrow-up-long']" />
<FontAwesomeIcon :icon="['far', 'calendar']" />
</button>
</div>
</div>
</nav>
<div v-if="visibleAuthentications.length > 0">
<div v-for="authentication in visibleAuthentications" :key="authentication.id" class="list-item is-size-6 is-size-7-mobile has-text-grey is-flex is-justify-content-space-between">
<div>
@ -104,4 +173,5 @@
<div v-else class="mt-5 pl-3">
{{ $t('commons.no_result') }}
</div>
<Spinner :isVisible="isFetching" />
</template>

View File

@ -51,11 +51,13 @@ import {
faMobileScreen,
faTabletScreenButton,
faDisplay,
faArrowUpLong,
} from '@fortawesome/free-solid-svg-icons'
import {
faStar,
faPaperPlane,
faCalendar
} from '@fortawesome/free-regular-svg-icons'
import {
@ -116,7 +118,9 @@ library.add(
faAlignLeft,
faMobileScreen,
faTabletScreenButton,
faDisplay
faDisplay,
faCalendar,
faArrowUpLong
);
export default FontAwesomeIcon

View File

@ -134,8 +134,8 @@ export default {
*
* @returns promise
*/
getauthentications(id, limit, config = {}) {
return apiClient.get('/users/' + id + '/authentications' + (limit ? '?limit=' + limit : ''), { ...config })
getauthentications(id, period = 90, limit, config = {}) {
return apiClient.get('/users/' + id + '/authentications?period=' + period + (limit ? '&limit=' + limit : ''), { ...config })
},
}

View File

@ -76,6 +76,12 @@
'browser_on_platform' => ':browser on :platform',
'access_log_has_more_entries' => 'The access log is likely to contain more entries.',
'access_log_legend_for_user' => 'Full access log for user :username',
'show_last_month_log' => 'Show entries from the last month',
'show_three_months_log' => 'Show entries from the last 3 months',
'show_six_months_log' => 'Show entries from the last 6 months',
'show_one_year_log' => 'Show entries from the last year',
'sort_by_date_asc' => 'Show least recent first',
'sort_by_date_desc' => 'Show most recent first',
'forms' => [
'use_encryption' => [
'label' => 'Protect sensitive data',