mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 08:06:26 +00:00
* feat: include token label in audit logs + show end user in runs page * fix: reverse username and permissioned as on runs page * fix: ui nit * fix: remove permissioned as from runs page
186 lines
4.5 KiB
Svelte
186 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
import Badge from '$lib/components/common/badge/Badge.svelte'
|
|
import Cell from '$lib/components/table/Cell.svelte'
|
|
import DataTable from '$lib/components/table/DataTable.svelte'
|
|
import Head from '$lib/components/table/Head.svelte'
|
|
import Row from '$lib/components/table/Row.svelte'
|
|
import type { AuditLog } from '$lib/gen'
|
|
import { displayDate } from '$lib/utils'
|
|
import { createEventDispatcher } from 'svelte'
|
|
import Button from '../common/button/Button.svelte'
|
|
import { ListFilter } from 'lucide-svelte'
|
|
|
|
export let logs: AuditLog[] = []
|
|
export let pageIndex: number | undefined = 1
|
|
export let perPage: number | undefined = 100
|
|
export let actionKind: string | undefined = undefined
|
|
export let operation: string | undefined = undefined
|
|
export let selectedId: number | undefined = undefined
|
|
export let usernameFilter: string | undefined = undefined
|
|
export let resourceFilter: string | undefined = undefined
|
|
|
|
function groupLogsByDay(logs: AuditLog[]): Record<string, AuditLog[]> {
|
|
const groupedLogs = {}
|
|
|
|
if (!logs) return groupedLogs
|
|
|
|
for (const log of logs) {
|
|
const date = new Date(log.timestamp)
|
|
const key = date.toLocaleString('en-US', {
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric'
|
|
})
|
|
|
|
if (!groupedLogs[key]) {
|
|
groupedLogs[key] = []
|
|
}
|
|
|
|
groupedLogs[key].push(log)
|
|
}
|
|
return groupedLogs
|
|
}
|
|
|
|
const dispatch = createEventDispatcher()
|
|
$: groupedLogs = groupLogsByDay(logs)
|
|
|
|
function kindToBadgeColor(kind: string) {
|
|
if (kind == 'Execute') {
|
|
return 'blue'
|
|
} else if (kind == 'Delete') {
|
|
return 'red'
|
|
} else if (kind == 'Update') {
|
|
return 'yellow'
|
|
} else if (kind == 'Create') {
|
|
return 'green'
|
|
}
|
|
return 'gray'
|
|
}
|
|
</script>
|
|
|
|
<DataTable
|
|
on:next={() => {
|
|
pageIndex = (pageIndex ?? 1) + 1
|
|
}}
|
|
on:previous={() => {
|
|
pageIndex = (pageIndex ?? 1) - 1
|
|
}}
|
|
currentPage={pageIndex}
|
|
paginated
|
|
rounded={false}
|
|
size="sm"
|
|
bind:perPage
|
|
shouldHidePagination={logs?.length === 0}
|
|
>
|
|
<Head>
|
|
<Cell first head>ID</Cell>
|
|
<Cell head>Timestamp</Cell>
|
|
<Cell head>Username</Cell>
|
|
<Cell head>Operation</Cell>
|
|
<Cell head last>Resource</Cell>
|
|
</Head>
|
|
{#if logs?.length > 0}
|
|
<tbody class="divide-y">
|
|
{#each Object.entries(groupedLogs) as [date, logsByDay]}
|
|
<tr class="border-t">
|
|
<Cell
|
|
first
|
|
colspan="6"
|
|
scope="colgroup"
|
|
class="bg-surface-secondary/30 py-2 border-b font-semibold"
|
|
>
|
|
{date}
|
|
</Cell>
|
|
</tr>
|
|
{#each logsByDay as { id, timestamp, username, operation: op, action_kind, resource, parameters }}
|
|
<Row
|
|
hoverable
|
|
selected={id === selectedId}
|
|
on:click={() => {
|
|
dispatch('select', id)
|
|
}}
|
|
>
|
|
<Cell first>
|
|
{id}
|
|
</Cell>
|
|
<Cell>
|
|
{displayDate(timestamp)}
|
|
</Cell>
|
|
<Cell>
|
|
<div class="flex flex-row gap-2 items-center">
|
|
<div class="whitespace-nowrap overflow-x-auto no-scrollbar max-w-52">
|
|
{username}
|
|
{#if parameters && 'end_user' in parameters}
|
|
<span> ({parameters.end_user})</span>
|
|
{/if}
|
|
</div>
|
|
<Button
|
|
color="light"
|
|
size="xs2"
|
|
iconOnly
|
|
startIcon={{ icon: ListFilter }}
|
|
on:click={() => {
|
|
usernameFilter = username
|
|
}}
|
|
/>
|
|
</div>
|
|
</Cell>
|
|
<Cell>
|
|
<div class="flex flex-row gap-1">
|
|
<Badge
|
|
on:click={() => {
|
|
actionKind = action_kind.toLocaleLowerCase()
|
|
}}
|
|
color={kindToBadgeColor(action_kind)}>{action_kind}</Badge
|
|
>
|
|
<Badge
|
|
on:click={() => {
|
|
operation = op
|
|
}}
|
|
>
|
|
{op}
|
|
</Badge>
|
|
</div>
|
|
</Cell>
|
|
<Cell last>
|
|
<div class="flex flex-row gap-2 items-center">
|
|
<div class="whitespace-nowrap overflow-x-auto no-scrollbar w-48">
|
|
{resource}
|
|
</div>
|
|
<Button
|
|
color="light"
|
|
size="xs2"
|
|
iconOnly
|
|
startIcon={{ icon: ListFilter }}
|
|
on:click={() => {
|
|
resourceFilter = resource
|
|
}}
|
|
/>
|
|
</div>
|
|
</Cell>
|
|
</Row>
|
|
{/each}
|
|
{/each}
|
|
</tbody>
|
|
{:else}
|
|
<tr>
|
|
<td colspan="4" class="text-center py-8">
|
|
<div class="text-xs text-secondary"> No logs found for the selected filters. </div>
|
|
</td>
|
|
</tr>
|
|
{/if}
|
|
</DataTable>
|
|
|
|
<style lang="postcss">
|
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
|
.no-scrollbar::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
/* Hide scrollbar for IE, Edge and Firefox */
|
|
.no-scrollbar {
|
|
-ms-overflow-style: none; /* IE and Edge */
|
|
scrollbar-width: none; /* Firefox */
|
|
}
|
|
</style>
|