123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- <script setup>
- import SearchBox from '@/components/SearchBox.vue'
- import userService from '@/services/userService'
- import Spinner from '@/components/Spinner.vue'
- import { useNotifyStore } from '@/stores/notify'
- import { FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
- import { UseColorMode } from '@vueuse/components'
- const notify = useNotifyStore()
- const $2fauth = inject('2fauth')
- const props = defineProps({
- userId: [Number, String],
- lastOnly: Boolean,
- showSearch: Boolean,
- period: {
- type: [Number, String],
- default: 12
- },
- })
- const periods = {
- aMonth: 1,
- threeMonths: 3,
- halfYear: 6,
- aYear: 12
- }
- const authentications = ref([])
- const isFetching = ref(false)
- const searched = ref('')
- const period = ref(props.period)
- const orderIsDesc = ref(true)
- const emit = defineEmits(['has-more-entries'])
- const visibleAuthentications = computed(() => {
- return authentications.value.filter(authentication => {
- return JSON.stringify(authentication)
- .toString()
- .toLowerCase()
- .includes(searched.value);
- })
- })
- onMounted(() => {
- 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
- */
- function getAuthentications() {
- isFetching.value = true
- let limit = props.lastOnly ? 4 : false
- userService.getauthentications(props.userId, period.value, limit, {returnError: true})
- .then(response => {
- authentications.value = response.data
- if (authentications.value.length > 3 && props.lastOnly) {
- emit('has-more-entries')
- authentications.value.pop()
- }
-
- orderIsDesc.value == true ? sortDesc() : sortAsc()
- })
- .catch(error => {
- notify.error(error)
- })
- .finally(() => {
- isFetching.value = false
- })
- }
- const deviceIcon = (device) => {
- switch (device) {
- case "phone":
- return 'mobile-screen'
- case "tablet":
- return 'tablet-screen-button'
- default:
- return 'display'
- }
- }
- const isSuccessfulLogin = (authentication) => {
- return authentication.login_successful && authentication.login_at
- }
- const isSuccessfulLogout = (authentication) => {
- return !authentication.login_at && authentication.logout_at
- }
- const isFailedEntry = (authentication) => {
- return !authentication.login_successful && !authentication.logout_at
- }
- </script>
- <template>
- <SearchBox v-if="props.showSearch" v-model:keyword="searched" :hasNoBackground="true" />
- <nav v-if="props.showSearch" class="level is-mobile mb-2">
- <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">
- {{ $t('commons.one_month') }}
- </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">
- {{ $t('commons.x_month', { 'x' : '3' }) }}
- </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">
- {{ $t('commons.x_month', { 'x' : '6' }) }}
- </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">
- {{ $t('commons.one_year') }}
- </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">
- <UseColorMode v-slot="{ mode }">
- <div>
- <div>
- <span v-if="isFailedEntry(authentication)" v-html="$t('admin.failed_login_on', { login_at: authentication.login_at })" />
- <span v-else-if="isSuccessfulLogout(authentication)" v-html="$t('admin.successful_logout_on', { login_at: authentication.logout_at })" />
- <span v-else-if="$2fauth.config.proxyAuth" v-html="$t('admin.viewed_on', { login_at: authentication.login_at })" />
- <span v-else v-html="$t('admin.successful_login_on', { login_at: authentication.login_at })" />
- </div>
- <div>
- {{ $t('commons.IP') }}: <span class="light-or-darker">{{ authentication.ip_address }}</span> -
- {{ $t('commons.browser') }}: <span class="light-or-darker">{{ authentication.browser }}</span> -
- {{ $t('commons.operating_system_short') }}: <span class="light-or-darker">{{ authentication.platform }}</span>
- </div>
- </div>
- <div :class="mode == 'dark' ? 'has-text-grey-darker' : 'has-text-grey-lighter'" class="is-align-self-center ">
- <font-awesome-layers class="fa-2x width-1-5x">
- <FontAwesomeIcon :icon="['fas', deviceIcon(authentication.device)]" transform="grow-6" fixed-width />
- <FontAwesomeIcon :icon="['fas', isFailedEntry(authentication) ? 'times' : 'check']"
- :transform="'shrink-7' + (authentication.device == 'desktop' ? ' up-2' : '')"
- :class="isFailedEntry(authentication) ? 'has-text-danger' + (mode == 'dark' ? '-dark' : '') : 'has-text-success' + (mode == 'dark' ? '-dark' : '')" fixed-width />
- </font-awesome-layers>
- </div>
- </UseColorMode>
- </div>
- </div>
- <div v-else-if="authentications.length == 0" class="mt-4">
- {{ $t('commons.no_entry_yet') }}
- </div>
- <div v-else class="mt-5 pl-3">
- {{ $t('commons.no_result') }}
- </div>
- <Spinner :isVisible="isFetching" />
- </template>
|