Files
archtika/web-app/src/routes/(authenticated)/website/[websiteId]/logs/+page.svelte

264 lines
8.0 KiB
Svelte
Raw Normal View History

2024-09-13 17:04:04 +02:00
<script lang="ts">
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
import DateTime from "$lib/components/DateTime.svelte";
import Modal from "$lib/components/Modal.svelte";
import type { PageServerData } from "./$types";
import diff from "fast-diff";
import { page } from "$app/stores";
import { tables } from "$lib/db-schema";
const { data }: { data: PageServerData } = $props();
const htmlDiff = (oldValue: string, newValue: string) => {
return diff(oldValue, newValue)
.map(([type, value]) => {
let newString = "";
switch (type) {
case 1:
newString += `<ins>${value}</ins>`;
break;
case 0:
newString += `${value}`;
break;
case -1:
newString += `<del>${value}</del>`;
break;
}
return newString;
})
.join("");
};
let resources = $state({});
if (data.website.content_type === "Blog") {
const { user, change_log, media, docs_category, ...restTables } = tables;
resources = restTables;
}
if (data.website.content_type === "Docs") {
const { user, change_log, media, ...restTables } = tables;
resources = restTables;
}
2024-09-15 21:37:32 +02:00
let logsSection: HTMLElement;
2024-09-13 17:04:04 +02:00
</script>
<WebsiteEditor
id={data.website.id}
contentType={data.website.content_type}
title={data.website.title}
previewContent={data.home.main_content}
>
2024-09-15 21:37:32 +02:00
<section id="logs" bind:this={logsSection}>
2024-09-13 17:04:04 +02:00
<hgroup>
<h2>
<a href="#logs">Logs</a>
</h2>
<p>
2024-09-15 21:37:32 +02:00
<strong>{data.resultChangeLogCount.toLocaleString("en", { useGrouping: true })}</strong>
<small>result(s)</small>
2024-09-13 17:04:04 +02:00
</p>
</hgroup>
<details>
<summary>Filter</summary>
<form method="GET">
<label>
Username:
<input
list="users-{data.website.id}"
name="logs_filter_user"
value={$page.url.searchParams.get("logs_filter_user")}
/>
<datalist id="users-{data.website.id}">
<option value={data.website.user.username}></option>
{#each data.collaborators as { user: { username } }}
<option value={username}></option>
{/each}
</datalist>
</label>
<label>
Resource:
<select name="logs_filter_resource">
<option value="all">Show all</option>
{#each Object.keys(resources) as resource}
<option
value={resource}
selected={resource === $page.url.searchParams.get("logs_filter_resource")}
>{resource}</option
>
{/each}
</select>
</label>
<label>
Operation:
<select name="logs_filter_operation">
<option value="all">Show all</option>
<option
value="insert"
selected={"insert" === $page.url.searchParams.get("logs_filter_operation")}
>Insert</option
>
<option
value="update"
selected={"update" === $page.url.searchParams.get("logs_filter_operation")}
>Update</option
>
<option
value="delete"
selected={"delete" === $page.url.searchParams.get("logs_filter_operation")}
>Delete</option
>
</select>
</label>
2024-09-15 21:37:32 +02:00
<input type="hidden" name="logs_results_page" value={1} />
2024-09-13 17:04:04 +02:00
<button type="submit">Submit</button>
</form>
</details>
<div class="scroll-container">
<table>
<thead>
<tr>
<th>User</th>
<th>Resource</th>
<th>Operation</th>
<th>Date and time</th>
<th>Changes</th>
</tr>
</thead>
<tbody>
{#each data.changeLog as { id, table_name, operation, tstamp, old_value, new_value, user_id, username }}
2024-09-13 17:04:04 +02:00
<tr>
<td>
<span style:text-decoration={user_id ? "" : "line-through"}>
{username}
</span>
</td>
2024-09-13 17:04:04 +02:00
<td>{table_name}</td>
<td>{operation}</td>
<td>
<DateTime date={tstamp} />
</td>
<td>
<Modal id="log-{id}" text="Show" isWider={true}>
{@const oldValue = JSON.stringify(old_value, null, 2)}
{@const newValue = JSON.stringify(new_value, null, 2)}
<hgroup>
<h3>Log changes</h3>
<p>{table_name} &mdash; {operation}</p>
</hgroup>
<pre style="white-space: pre-wrap">{@html htmlDiff(oldValue, newValue)}</pre>
</Modal>
</td>
</tr>
{/each}
</tbody>
</table>
2024-09-15 21:37:32 +02:00
{#snippet commonFilterInputs()}
<input
type="hidden"
name="logs_filter_user"
value={$page.url.searchParams.get("logs_filter_user")}
/>
<input
type="hidden"
name="logs_filter_resource"
value={$page.url.searchParams.get("logs_filter_resource")}
/>
<input
type="hidden"
name="logs_filter_operation"
value={$page.url.searchParams.get("logs_filter_operation")}
/>
{/snippet}
<div class="pagination">
<p>
{$page.url.searchParams.get("logs_results_page") ?? 1} / {Math.max(
Math.floor(data.resultChangeLogCount / 50),
1
)}
</p>
<form method="GET" onsubmit={() => logsSection.scrollIntoView()}>
<input type="hidden" name="logs_results_page" value={1} />
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("logs_results_page") ?? "1") === "1"}
>First</button
>
</form>
<form method="GET" onsubmit={() => logsSection.scrollIntoView()}>
<input
type="hidden"
name="logs_results_page"
value={Math.max(
1,
Number.parseInt($page.url.searchParams.get("logs_results_page") ?? "1") - 1
)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("logs_results_page") ?? "1") === "1"}
>Previous</button
>
</form>
<form method="GET" onsubmit={() => logsSection.scrollIntoView()}>
<input
type="hidden"
name="logs_results_page"
value={Math.min(
Math.max(Math.floor(data.resultChangeLogCount / 50), 1),
Number.parseInt($page.url.searchParams.get("logs_results_page") ?? "1") + 1
)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("logs_results_page") ?? "1") ===
Math.max(Math.floor(data.resultChangeLogCount / 50), 1).toString()}>Next</button
>
</form>
<form method="GET" onsubmit={() => logsSection.scrollIntoView()}>
<input
type="hidden"
name="logs_results_page"
value={Math.max(Math.floor(data.resultChangeLogCount / 50), 1)}
/>
{@render commonFilterInputs()}
<button
type="submit"
disabled={($page.url.searchParams.get("logs_results_page") ?? "1") ===
Math.max(Math.floor(data.resultChangeLogCount / 50), 1).toString()}>Last</button
>
</form>
</div>
2024-09-13 17:04:04 +02:00
</div>
</section>
</WebsiteEditor>
2024-09-15 21:37:32 +02:00
<style>
.pagination {
display: flex;
align-items: center;
margin-inline: var(--space-2xs);
margin-block: var(--space-s);
flex-wrap: wrap;
gap: var(--space-xs);
justify-content: end;
}
.pagination > form:first-of-type {
margin-inline-start: auto;
}
button[disabled] {
opacity: 0.5;
pointer-events: none;
}
</style>