2024-09-12 10:11:25 +02:00

119 lines
4.0 KiB
Vue

<template>
<Page wide>
<NotFound v-if="!$isGranted('*')" />
<div v-else>
<p>
<nuxt-link to="/admin">
<Icon v="user-cog" />
<T>admin.header</T>
</nuxt-link>
</p>
<h2>
<Icon v="file-search" />
Audit log
<br>
<small class="text-muted">
(username: {{ username }}, id: {{ userId }})
</small>
</h2>
<div v-if="categories.size > 0" class="btn-group">
<button
v-for="category in categories"
:class="['btn', category === categoryFilter ? 'btn-primary' : 'btn-outline-primary']"
@click="categoryFilter = categoryFilter === category ? null : category"
>
{{ category }}
</button>
</div>
<div class="table-responsive mt-4">
<table class="table">
<thead>
<tr>
<th>Event time</th>
<th>Username</th>
<th>User ID</th>
<th>Event</th>
<th>Payload</th>
</tr>
</thead>
<tbody>
<tr v-for="logEntry in visibleLogEntries">
<th>
{{ $datetime($ulidTime(logEntry.id)) }}
</th>
<td>
{{ logEntry.username }}
<template v-if="logEntry.username !== username">
<br>
<span class="badge bg-warning">Username mismatch</span>
</template>
</td>
<td>
{{ logEntry.userId }}
<template v-if="logEntry.userId !== userId">
<br>
<span class="badge bg-warning">ID mismatch</span>
</template>
</td>
<td>
<strong>{{ logEntry.event.split('/')[0] }}</strong>/{{ logEntry.event.split('/')[1] }}
</td>
<td>
<pre v-if="logEntry.payload">{{ JSON.stringify(JSON.parse(logEntry.payload), null, 4) }}</pre>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</Page>
</template>
<script>
import { useNuxtApp, useRoute, useFetch } from 'nuxt/app';
import useSimpleHead from '~/composables/useSimpleHead.ts';
export default {
async setup() {
const { $translator: translator } = useNuxtApp();
useSimpleHead({
title: `${translator.translate('admin.header')} • Audit log`,
}, translator);
const route = useRoute();
const { data: logEntries } = await useFetch(`/api/admin/audit-log/${route.params.username}/${route.params.id}`);
return {
username: route.params.username,
userId: route.params.id,
logEntries,
};
},
data() {
return {
categoryFilter: null,
};
},
computed: {
visibleLogEntries() {
return this.logEntries.filter((logEntry) => {
return this.categoryFilter === null || logEntry.event.startsWith(`${this.categoryFilter}/`);
});
},
categories() {
return new Set(this.logEntries.map((e) => e.event.split('/')[0]));
},
},
};
</script>
<style lang="scss" scoped>
pre {
max-height: 300px;
max-width: 400px;
overflow: auto;
}
</style>