From d674d4af6cb95fccd412649bb43abbdda9ed861f Mon Sep 17 00:00:00 2001 From: tueytoma Date: Thu, 27 Feb 2025 23:09:37 +0700 Subject: [PATCH 1/2] feat: add log page # Conflicts: # bun.lockb --- package.json | 5 +- src/lib/api/index.js | 22 ++ src/lib/format/index.js | 11 + src/lib/use-action/clickAway.js | 15 + src/routes/(auth)/(project)/log/+page.js | 17 + src/routes/(auth)/(project)/log/+page.svelte | 354 ++++++++++++++++++ .../(project)/log/_components/Fields.svelte | 32 ++ .../(project)/log/_components/Input.svelte | 79 ++++ .../log/_components/Pagination.svelte | 40 ++ .../log/_components/TimeFilter.svelte | 301 +++++++++++++++ src/routes/(auth)/+layout.svelte | 4 +- src/routes/+layout.svelte | 13 +- src/routes/api/log/[fn]/+server.js | 17 + src/style/main.scss | 116 ++++++ src/types/api.d.ts | 8 + svelte.config.js | 3 +- 16 files changed, 1032 insertions(+), 5 deletions(-) create mode 100644 src/lib/use-action/clickAway.js create mode 100644 src/routes/(auth)/(project)/log/+page.js create mode 100644 src/routes/(auth)/(project)/log/+page.svelte create mode 100644 src/routes/(auth)/(project)/log/_components/Fields.svelte create mode 100644 src/routes/(auth)/(project)/log/_components/Input.svelte create mode 100644 src/routes/(auth)/(project)/log/_components/Pagination.svelte create mode 100644 src/routes/(auth)/(project)/log/_components/TimeFilter.svelte create mode 100644 src/routes/api/log/[fn]/+server.js diff --git a/package.json b/package.json index ee42e4b..5bb2a67 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,8 @@ "typescript": "~5.7.3", "valid-url": "^1.0.9" }, - "type": "module" + "type": "module", + "dependencies": { + "prismjs": "^1.29.0" + } } diff --git a/src/lib/api/index.js b/src/lib/api/index.js index af09ed2..de6035f 100644 --- a/src/lib/api/index.js +++ b/src/lib/api/index.js @@ -48,6 +48,27 @@ async function invoke (fn, args, fetch) { return body } +/** + * @template T + * @param {string} indexId + * @param {string} fn + * @param {Object} args + * @param {fetch} fetch + * @returns {Promise>} + */ +async function invokeLog (indexId, fn, args, fetch) { + const url = `${indexId}${fn ? ('/' + fn) : ''}${args ? ('?' + args) : ''}` + const resp = await fetch(`${endpoint}/log/${encodeURIComponent(url)}`, { + method: 'POST', + headers: { + 'content-type': 'application/json' + } + }) + + const body = await resp.json() + return body +} + /** * @param {Function} callback */ @@ -90,6 +111,7 @@ function intervalInvalidate (callback, interval) { export default { invoke, + invokeLog, setOnUnauth, invalidate: wrapInvalidate, intervalInvalidate diff --git a/src/lib/format/index.js b/src/lib/format/index.js index 60ea0cc..ae151fc 100644 --- a/src/lib/format/index.js +++ b/src/lib/format/index.js @@ -56,6 +56,17 @@ export function datetime (v) { return dayjs(v).format('YYYY-MM-DD HH:mm:ss') } +/** + * @param {string} v + * @returns {number} + */ +export function unixDatetime (v) { + if (!v) { + return 0 + } + return dayjs(v).unix() +} + /** * @param {string} project * @param {string} name diff --git a/src/lib/use-action/clickAway.js b/src/lib/use-action/clickAway.js new file mode 100644 index 0000000..eea6017 --- /dev/null +++ b/src/lib/use-action/clickAway.js @@ -0,0 +1,15 @@ +export default function clickAway (node) { + const handleClick = (event) => { + if (node && !node.contains(event.target) && !event.defaultPrevented) { + node.dispatchEvent(new CustomEvent('clickAway')) + } + } + + document.addEventListener('click', handleClick, true) + + return { + destroy () { + document.removeEventListener('click', handleClick, true) + } + } +} diff --git a/src/routes/(auth)/(project)/log/+page.js b/src/routes/(auth)/(project)/log/+page.js new file mode 100644 index 0000000..0fb0d15 --- /dev/null +++ b/src/routes/(auth)/(project)/log/+page.js @@ -0,0 +1,17 @@ +export async function load ({ url }) { + const duration = url.searchParams.get('duration') || '' + const startDate = url.searchParams.get('startDate') || '' + const endDate = url.searchParams.get('endDate') || '' + const query = url.searchParams.get('query') || '' + const indexId = url.searchParams.get('indexId') || '' + const endpoint = url.searchParams.get('endpoint') || '' + + return { + duration, + startDate, + endDate, + query, + indexId, + endpoint + } +} diff --git a/src/routes/(auth)/(project)/log/+page.svelte b/src/routes/(auth)/(project)/log/+page.svelte new file mode 100644 index 0000000..a0adf9c --- /dev/null +++ b/src/routes/(auth)/(project)/log/+page.svelte @@ -0,0 +1,354 @@ + + + + + + + + + +
+
Log
+ {#if initLoading} + + {:else} +
+
+
+ + {#if indexId && endpoint} +
+
+ { show.fields = !show.fields } } + aria-hidden="true" + > + +
+
+ {/if} +
+
+
+
+ {#if filter.startDate && indexId && endpoint} +
+ { format.datetime(filter.startDate * 1000) } to { format.datetime(filter.endDate * 1000) } +
+ {/if} +
+
+ +
+
+
+ +
+
+ per Page +
+
+ {#if indexId && endpoint } + + {/if} +
+
+
+ +
+
+
+ {#if indexId && endpoint} +
+
+ API URL: +
{ endpoint }/api/v1/{ indexId }/search?{createQueryString()}
+ copy(`${endpoint}/api/v1/${indexId}/search?${createQueryString()}`) } + aria-hidden="true" + > +
+ { result.total.toLocaleString() } hits found in { result.elapsedTime } seconds +
+ {#if loading} + + {:else} + {#if result.list.length} + + {/if} +
+ {#each result.list as item, idx} + {@const identity = `${item['logger.name']}-${item.timestamp}`} + {@const open = show.result.includes(identity)} +
+ { show.result = open ? show.result.filter(r => r !== identity) : [...show.result, identity] } } + aria-hidden="true" + > +
+
+ { show.result = open ? show.result.filter(r => r !== identity) : [...show.result, identity] } } + aria-hidden="true" + > + + { format.datetime(item.timestamp) } + + {#if open} +
{ JSON.stringify(item, null, 2) }
+ {:else} +
{ JSON.stringify(item) }
+ {/if} +
+
+
+ {:else} + No Data + {/each} +
+ {#if result.list.length} + + {/if} + {/if} + {/if} +
+
+ {/if} +
+ + diff --git a/src/routes/(auth)/(project)/log/_components/Fields.svelte b/src/routes/(auth)/(project)/log/_components/Fields.svelte new file mode 100644 index 0000000..6ab0883 --- /dev/null +++ b/src/routes/(auth)/(project)/log/_components/Fields.svelte @@ -0,0 +1,32 @@ + + +
+ { show = !show } } + aria-hidden="true" + > + Fields + + {#if show} +
+ {#each list as item} +
+
{ item.type }
+ { item.name } +
+ {/each} +
+ {/if} +
diff --git a/src/routes/(auth)/(project)/log/_components/Input.svelte b/src/routes/(auth)/(project)/log/_components/Input.svelte new file mode 100644 index 0000000..986cd51 --- /dev/null +++ b/src/routes/(auth)/(project)/log/_components/Input.svelte @@ -0,0 +1,79 @@ + + +
+
+ {#if !show} +
+ Endpoint URL: + { valueEndpoint } + Index ID: + { valueIndexId } +
+ + {:else} +
+
+ +
+
+ +
+ +
+ {/if} +
+
diff --git a/src/routes/(auth)/(project)/log/_components/Pagination.svelte b/src/routes/(auth)/(project)/log/_components/Pagination.svelte new file mode 100644 index 0000000..ee9788b --- /dev/null +++ b/src/routes/(auth)/(project)/log/_components/Pagination.svelte @@ -0,0 +1,40 @@ + + +
+
+
+
+ + + +
+
+
diff --git a/src/routes/(auth)/(project)/log/_components/TimeFilter.svelte b/src/routes/(auth)/(project)/log/_components/TimeFilter.svelte new file mode 100644 index 0000000..d8a609a --- /dev/null +++ b/src/routes/(auth)/(project)/log/_components/TimeFilter.svelte @@ -0,0 +1,301 @@ + + + + +
{ show = false } } + aria-hidden="true" +> +
{ show = false } } + > + +

Select a period

+
+
+ + + + + + + + + +
+ {#if showCustom} +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + +
+
+ {/if} +
+
+
+ + diff --git a/src/routes/(auth)/+layout.svelte b/src/routes/(auth)/+layout.svelte index c1fdc8a..8cf990c 100644 --- a/src/routes/(auth)/+layout.svelte +++ b/src/routes/(auth)/+layout.svelte @@ -88,7 +88,9 @@ padding-left: calc(var(--width-sidebar) + var(--content-sidegap)); padding-right: 2rem; padding-bottom: 2rem; - + min-height: 100dvh; + display: flex; + flex-direction: column; } @media screen and (max-width: 1023px) { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 60b4f05..fc9ceed 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,8 @@