mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 10:51:36 +01:00
Create logs route
This commit is contained in:
@@ -1,17 +1,20 @@
|
||||
import type { LayoutServerLoad } from "./$types";
|
||||
import { API_BASE_PREFIX } from "$lib/server/utils";
|
||||
import { error } from "@sveltejs/kit";
|
||||
import type { Website, Home } from "$lib/db-schema";
|
||||
import type { Website, Home, User } from "$lib/db-schema";
|
||||
|
||||
export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
|
||||
const websiteData = await fetch(`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||
Accept: "application/vnd.pgrst.object+json"
|
||||
const websiteData = await fetch(
|
||||
`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}&select=*,user!user_id(username)`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||
Accept: "application/vnd.pgrst.object+json"
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
if (!websiteData.ok) {
|
||||
throw error(404, "Website not found");
|
||||
@@ -26,7 +29,7 @@ export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
|
||||
}
|
||||
});
|
||||
|
||||
const website: Website = await websiteData.json();
|
||||
const website: Website & { user: { username: User["username"] } } = await websiteData.json();
|
||||
const home: Home = await homeData.json();
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import { API_BASE_PREFIX } from "$lib/server/utils";
|
||||
import type { ChangeLog, User, Collab } from "$lib/db-schema";
|
||||
|
||||
export const load: PageServerLoad = async ({ parent, fetch, params, cookies, url }) => {
|
||||
const userFilter = url.searchParams.get("logs_filter_user");
|
||||
const resourceFilter = url.searchParams.get("logs_filter_resource");
|
||||
const operationFilter = url.searchParams.get("logs_filter_operation");
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
const baseFetchUrl = `${API_BASE_PREFIX}/change_log?website_id=eq.${params.websiteId}&select=id,table_name,operation,tstamp,old_value,new_value,user!inner(username)&order=tstamp.desc`;
|
||||
|
||||
if (userFilter && userFilter !== "all") {
|
||||
searchParams.append("user.username", `eq.${userFilter}`);
|
||||
}
|
||||
|
||||
if (resourceFilter && resourceFilter !== "all") {
|
||||
searchParams.append("table_name", `eq.${resourceFilter}`);
|
||||
}
|
||||
|
||||
if (operationFilter && operationFilter !== "all") {
|
||||
searchParams.append("operation", `eq.${operationFilter.toUpperCase()}`);
|
||||
}
|
||||
|
||||
const constructedFetchUrl = `${baseFetchUrl}&${searchParams.toString()}`;
|
||||
|
||||
const changeLogData = await fetch(constructedFetchUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`
|
||||
}
|
||||
});
|
||||
|
||||
const resultChangeLogData = await fetch(constructedFetchUrl, {
|
||||
method: "HEAD",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||
Prefer: "count=exact"
|
||||
}
|
||||
});
|
||||
|
||||
const resultChangeLogCount = Number(
|
||||
resultChangeLogData.headers.get("content-range")?.split("/").at(-1)
|
||||
);
|
||||
|
||||
const collabData = await fetch(
|
||||
`${API_BASE_PREFIX}/collab?website_id=eq.${params.websiteId}&select=*,user!user_id(*)&order=last_modified_at.desc,added_at.desc`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const changeLog: (ChangeLog & { user: { username: User["username"] } })[] =
|
||||
await changeLogData.json();
|
||||
const collaborators: (Collab & { user: User })[] = await collabData.json();
|
||||
const { website, home } = await parent();
|
||||
|
||||
return {
|
||||
changeLog,
|
||||
resultChangeLogCount,
|
||||
website,
|
||||
home,
|
||||
collaborators
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
<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;
|
||||
}
|
||||
</script>
|
||||
|
||||
<WebsiteEditor
|
||||
id={data.website.id}
|
||||
contentType={data.website.content_type}
|
||||
title={data.website.title}
|
||||
previewContent={data.home.main_content}
|
||||
>
|
||||
<section id="logs">
|
||||
<hgroup>
|
||||
<h2>
|
||||
<a href="#logs">Logs</a>
|
||||
</h2>
|
||||
<p>
|
||||
<strong>{data.resultChangeLogCount}</strong>
|
||||
<small>results</small>
|
||||
</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>
|
||||
<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 }}
|
||||
<tr>
|
||||
<td>{user.username}</td>
|
||||
<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} — {operation}</p>
|
||||
</hgroup>
|
||||
|
||||
<pre style="white-space: pre-wrap">{@html htmlDiff(oldValue, newValue)}</pre>
|
||||
</Modal>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</WebsiteEditor>
|
||||
Reference in New Issue
Block a user