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:
7
web-app/package-lock.json
generated
7
web-app/package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "web-app",
|
"name": "web-app",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"fast-diff": "1.3.0",
|
||||||
"github-slugger": "2.0.0",
|
"github-slugger": "2.0.0",
|
||||||
"highlight.js": "11.10.0",
|
"highlight.js": "11.10.0",
|
||||||
"isomorphic-dompurify": "2.14.0",
|
"isomorphic-dompurify": "2.14.0",
|
||||||
@@ -2549,6 +2550,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-diff": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"fast-diff": "1.3.0",
|
||||||
"github-slugger": "2.0.0",
|
"github-slugger": "2.0.0",
|
||||||
"highlight.js": "11.10.0",
|
"highlight.js": "11.10.0",
|
||||||
"isomorphic-dompurify": "2.14.0",
|
"isomorphic-dompurify": "2.14.0",
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
const { children, id, text }: { children: Snippet; id: string; text: string } = $props();
|
const {
|
||||||
|
children,
|
||||||
|
id,
|
||||||
|
text,
|
||||||
|
isWider = false
|
||||||
|
}: { children: Snippet; id: string; text: string; isWider?: boolean } = $props();
|
||||||
|
|
||||||
const modalId = `${id}-modal`;
|
const modalId = `${id}-modal`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href={`#${modalId}`} role="button">{text}</a>
|
<a href={`#${modalId}`} role="button">{text}</a>
|
||||||
|
|
||||||
<div id={modalId} class="modal">
|
<div id={modalId} class="modal" style="--modal-width: {isWider ? 600 : 300}px">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
<a href="#!" role="button">Close</a>
|
<a href="#!" role="button">Close</a>
|
||||||
@@ -46,7 +51,7 @@
|
|||||||
background-color: var(--bg-primary);
|
background-color: var(--bg-primary);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
border: var(--border-primary);
|
border: var(--border-primary);
|
||||||
inline-size: 300px;
|
inline-size: var(--modal-width);
|
||||||
max-inline-size: 100%;
|
max-inline-size: 100%;
|
||||||
max-block-size: calc(100vh - var(--space-m));
|
max-block-size: calc(100vh - var(--space-m));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@@ -55,6 +55,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="/website/{id}/publish">Publish</a>
|
<a href="/website/{id}/publish">Publish</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/website/{id}/logs">Logs</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -120,13 +123,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.operations {
|
.operations {
|
||||||
border-inline-end: var(--border-primary);
|
|
||||||
padding-block-start: var(--space-s);
|
padding-block-start: var(--space-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-block-start: var(--space-s);
|
padding-block-start: var(--space-s);
|
||||||
|
border-inline-start: var(--border-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ export interface Article {
|
|||||||
cover_image: string | null;
|
cover_image: string | null;
|
||||||
publication_date: Date | null;
|
publication_date: Date | null;
|
||||||
main_content: string | null;
|
main_content: string | null;
|
||||||
|
category: string | null;
|
||||||
|
article_weight: number | null;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
last_modified_at: Date;
|
last_modified_at: Date;
|
||||||
last_modified_by: string | null;
|
last_modified_by: string | null;
|
||||||
title_description_search: any | null;
|
title_description_search: any | null;
|
||||||
category: string | null;
|
|
||||||
article_weight: number | null;
|
|
||||||
}
|
}
|
||||||
export interface ArticleInput {
|
export interface ArticleInput {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -39,12 +39,12 @@ export interface ArticleInput {
|
|||||||
cover_image?: string | null;
|
cover_image?: string | null;
|
||||||
publication_date?: Date | null;
|
publication_date?: Date | null;
|
||||||
main_content?: string | null;
|
main_content?: string | null;
|
||||||
|
category?: string | null;
|
||||||
|
article_weight?: number | null;
|
||||||
created_at?: Date;
|
created_at?: Date;
|
||||||
last_modified_at?: Date;
|
last_modified_at?: Date;
|
||||||
last_modified_by?: string | null;
|
last_modified_by?: string | null;
|
||||||
title_description_search?: any | null;
|
title_description_search?: any | null;
|
||||||
category?: string | null;
|
|
||||||
article_weight?: number | null;
|
|
||||||
}
|
}
|
||||||
const article = {
|
const article = {
|
||||||
tableName: "article",
|
tableName: "article",
|
||||||
@@ -58,12 +58,12 @@ const article = {
|
|||||||
"cover_image",
|
"cover_image",
|
||||||
"publication_date",
|
"publication_date",
|
||||||
"main_content",
|
"main_content",
|
||||||
|
"category",
|
||||||
|
"article_weight",
|
||||||
"created_at",
|
"created_at",
|
||||||
"last_modified_at",
|
"last_modified_at",
|
||||||
"last_modified_by",
|
"last_modified_by",
|
||||||
"title_description_search",
|
"title_description_search"
|
||||||
"category",
|
|
||||||
"article_weight"
|
|
||||||
],
|
],
|
||||||
requiredForInsert: ["website_id", "title"],
|
requiredForInsert: ["website_id", "title"],
|
||||||
primaryKey: "id",
|
primaryKey: "id",
|
||||||
@@ -71,8 +71,8 @@ const article = {
|
|||||||
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
||||||
user_id: { table: "user", column: "id", $type: null as unknown as User },
|
user_id: { table: "user", column: "id", $type: null as unknown as User },
|
||||||
cover_image: { table: "media", column: "id", $type: null as unknown as Media },
|
cover_image: { table: "media", column: "id", $type: null as unknown as Media },
|
||||||
last_modified_by: { table: "user", column: "id", $type: null as unknown as User },
|
category: { table: "docs_category", column: "id", $type: null as unknown as DocsCategory },
|
||||||
category: { table: "docs_category", column: "id", $type: null as unknown as DocsCategory }
|
last_modified_by: { table: "user", column: "id", $type: null as unknown as User }
|
||||||
},
|
},
|
||||||
$type: null as unknown as Article,
|
$type: null as unknown as Article,
|
||||||
$input: null as unknown as ArticleInput
|
$input: null as unknown as ArticleInput
|
||||||
@@ -80,26 +80,39 @@ const article = {
|
|||||||
|
|
||||||
// Table change_log
|
// Table change_log
|
||||||
export interface ChangeLog {
|
export interface ChangeLog {
|
||||||
website_id: string;
|
id: string;
|
||||||
user_id: string;
|
website_id: string | null;
|
||||||
change_summary: string;
|
user_id: string | null;
|
||||||
previous_value: Json | null;
|
tstamp: Date;
|
||||||
new_value: Json | null;
|
table_name: string;
|
||||||
timestamp: Date;
|
operation: string;
|
||||||
|
old_value: any | null;
|
||||||
|
new_value: any | null;
|
||||||
}
|
}
|
||||||
export interface ChangeLogInput {
|
export interface ChangeLogInput {
|
||||||
website_id: string;
|
id?: string;
|
||||||
user_id?: string;
|
website_id?: string | null;
|
||||||
change_summary: string;
|
user_id?: string | null;
|
||||||
previous_value?: Json | null;
|
tstamp?: Date;
|
||||||
new_value?: Json | null;
|
table_name: string;
|
||||||
timestamp?: Date;
|
operation: string;
|
||||||
|
old_value?: any | null;
|
||||||
|
new_value?: any | null;
|
||||||
}
|
}
|
||||||
const change_log = {
|
const change_log = {
|
||||||
tableName: "change_log",
|
tableName: "change_log",
|
||||||
columns: ["website_id", "user_id", "change_summary", "previous_value", "new_value", "timestamp"],
|
columns: [
|
||||||
requiredForInsert: ["website_id", "change_summary"],
|
"id",
|
||||||
primaryKey: "website_id",
|
"website_id",
|
||||||
|
"user_id",
|
||||||
|
"tstamp",
|
||||||
|
"table_name",
|
||||||
|
"operation",
|
||||||
|
"old_value",
|
||||||
|
"new_value"
|
||||||
|
],
|
||||||
|
requiredForInsert: ["table_name", "operation"],
|
||||||
|
primaryKey: "id",
|
||||||
foreignKeys: {
|
foreignKeys: {
|
||||||
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
||||||
user_id: { table: "user", column: "id", $type: null as unknown as User }
|
user_id: { table: "user", column: "id", $type: null as unknown as User }
|
||||||
@@ -153,6 +166,8 @@ export interface DocsCategory {
|
|||||||
user_id: string | null;
|
user_id: string | null;
|
||||||
category_name: string;
|
category_name: string;
|
||||||
category_weight: number;
|
category_weight: number;
|
||||||
|
last_modified_at: Date;
|
||||||
|
last_modified_by: string | null;
|
||||||
}
|
}
|
||||||
export interface DocsCategoryInput {
|
export interface DocsCategoryInput {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -160,15 +175,26 @@ export interface DocsCategoryInput {
|
|||||||
user_id?: string | null;
|
user_id?: string | null;
|
||||||
category_name: string;
|
category_name: string;
|
||||||
category_weight: number;
|
category_weight: number;
|
||||||
|
last_modified_at?: Date;
|
||||||
|
last_modified_by?: string | null;
|
||||||
}
|
}
|
||||||
const docs_category = {
|
const docs_category = {
|
||||||
tableName: "docs_category",
|
tableName: "docs_category",
|
||||||
columns: ["id", "website_id", "user_id", "category_name", "category_weight"],
|
columns: [
|
||||||
|
"id",
|
||||||
|
"website_id",
|
||||||
|
"user_id",
|
||||||
|
"category_name",
|
||||||
|
"category_weight",
|
||||||
|
"last_modified_at",
|
||||||
|
"last_modified_by"
|
||||||
|
],
|
||||||
requiredForInsert: ["website_id", "category_name", "category_weight"],
|
requiredForInsert: ["website_id", "category_name", "category_weight"],
|
||||||
primaryKey: "id",
|
primaryKey: "id",
|
||||||
foreignKeys: {
|
foreignKeys: {
|
||||||
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
||||||
user_id: { table: "user", column: "id", $type: null as unknown as User }
|
user_id: { table: "user", column: "id", $type: null as unknown as User },
|
||||||
|
last_modified_by: { table: "user", column: "id", $type: null as unknown as User }
|
||||||
},
|
},
|
||||||
$type: null as unknown as DocsCategory,
|
$type: null as unknown as DocsCategory,
|
||||||
$input: null as unknown as DocsCategoryInput
|
$input: null as unknown as DocsCategoryInput
|
||||||
@@ -390,10 +416,10 @@ export interface Website {
|
|||||||
content_type: string;
|
content_type: string;
|
||||||
title: string;
|
title: string;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
|
is_published: boolean;
|
||||||
last_modified_at: Date;
|
last_modified_at: Date;
|
||||||
last_modified_by: string | null;
|
last_modified_by: string | null;
|
||||||
title_search: any | null;
|
title_search: any | null;
|
||||||
is_published: boolean;
|
|
||||||
}
|
}
|
||||||
export interface WebsiteInput {
|
export interface WebsiteInput {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -401,10 +427,10 @@ export interface WebsiteInput {
|
|||||||
content_type: string;
|
content_type: string;
|
||||||
title: string;
|
title: string;
|
||||||
created_at?: Date;
|
created_at?: Date;
|
||||||
|
is_published?: boolean;
|
||||||
last_modified_at?: Date;
|
last_modified_at?: Date;
|
||||||
last_modified_by?: string | null;
|
last_modified_by?: string | null;
|
||||||
title_search?: any | null;
|
title_search?: any | null;
|
||||||
is_published?: boolean;
|
|
||||||
}
|
}
|
||||||
const website = {
|
const website = {
|
||||||
tableName: "website",
|
tableName: "website",
|
||||||
@@ -414,10 +440,10 @@ const website = {
|
|||||||
"content_type",
|
"content_type",
|
||||||
"title",
|
"title",
|
||||||
"created_at",
|
"created_at",
|
||||||
|
"is_published",
|
||||||
"last_modified_at",
|
"last_modified_at",
|
||||||
"last_modified_by",
|
"last_modified_by",
|
||||||
"title_search",
|
"title_search"
|
||||||
"is_published"
|
|
||||||
],
|
],
|
||||||
requiredForInsert: ["content_type", "title"],
|
requiredForInsert: ["content_type", "title"],
|
||||||
primaryKey: "id",
|
primaryKey: "id",
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import type { LayoutServerLoad } from "./$types";
|
import type { LayoutServerLoad } from "./$types";
|
||||||
import { API_BASE_PREFIX } from "$lib/server/utils";
|
import { API_BASE_PREFIX } from "$lib/server/utils";
|
||||||
import { error } from "@sveltejs/kit";
|
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 }) => {
|
export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
|
||||||
const websiteData = await fetch(`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}`, {
|
const websiteData = await fetch(
|
||||||
method: "GET",
|
`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}&select=*,user!user_id(username)`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "GET",
|
||||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
headers: {
|
||||||
Accept: "application/vnd.pgrst.object+json"
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||||
|
Accept: "application/vnd.pgrst.object+json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
if (!websiteData.ok) {
|
if (!websiteData.ok) {
|
||||||
throw error(404, "Website not found");
|
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();
|
const home: Home = await homeData.json();
|
||||||
|
|
||||||
return {
|
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>
|
||||||
@@ -331,3 +331,13 @@ td {
|
|||||||
padding: var(--space-2xs);
|
padding: var(--space-2xs);
|
||||||
border: var(--border-primary);
|
border: var(--border-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ins {
|
||||||
|
background-color: var(--color-success);
|
||||||
|
color: var(--color-text-invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
del {
|
||||||
|
background-color: var(--color-error);
|
||||||
|
color: var(--color-text-invert);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user