123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- <script lang="typescript">
- import type {
- PollOptions,
- PollResult,
- LogEvent,
- } from "src/routes/logs/poll";
- import { faSyncAlt } from "@fortawesome/free-solid-svg-icons";
- import Icon from "svelte-awesome/components/Icon.svelte";
- const sync = (faSyncAlt as unknown) as undefined;
- const POLL_INTERVAL = 5000;
- let logEvents: LogEvent[] | undefined = undefined;
- let syncing = false;
- let poller = 0;
- let maxMessages = 50;
- let error = "";
- const LEVEL_COLORS: Record<string, string> = {
- "info": "#EFF2F7",
- "warn": "#ECC94B",
- "error": "#C53030",
- };
- function getLevelColor(level: string) {
- if (level in LEVEL_COLORS) {
- return LEVEL_COLORS[level];
- }
- return "#FFFFFF";
- }
- function onAutoUpdateChecked(event: Event & { target: HTMLInputElement }) {
- if (event.target.checked) {
- const pollCallback = async () => {
- await pollEvents();
- poller = setTimeout(pollCallback, POLL_INTERVAL);
- }
- if (poller >= 0) {
- poller = setTimeout(pollCallback, POLL_INTERVAL);
- }
- } else {
- clearTimeout(poller);
- poller = -1;
- }
- }
- async function pollEvents() {
- if (syncing) {
- return;
- }
- syncing = true;
- const params: PollOptions = {
- limit: maxMessages.toString(),
- };
- const query = Object.entries(params).reduce(
- (prev, [key, val]) =>
- `${prev}&${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
- ""
- );
- const response = await fetch(`/logs/poll?${query}`, {
- method: "get",
- credentials: "include",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- },
- });
- const result = (await response.json()) as PollResult;
- if (result.ok) {
- logEvents = result.events;
- } else {
- error = result.error;
- }
- syncing = false;
- }
- </script>
- <style>
- .sync-button {
- @apply bg-blue-600 py-1 px-3 text-white cursor-pointer rounded-sm text-sm;
- &:hover {
- @apply bg-blue-700 text-gray-100;
- }
- }
- .logs-table {
- @apply bg-black m-3 block overflow-y-auto;
- max-height: 70%;
- td,
- th {
- @apply font-mono px-2 text-sm;
- }
- thead th {
- @apply sticky top-0 bg-black py-2;
- }
- }
- .error-box {
- @apply text-gray-100 bg-red-600 py-4 px-3 rounded-sm;
- }
- .query-row {
- @apply flex flex-row justify-end items-center;
- }
- .event-log-options {
- @apply flex flex-col;
- input[type="number"] {
- @apply text-black w-16;
- }
- }
- </style>
- <div class="viewport text-white">
- <h1 class="text-3xl pb-4">Event logs</h1>
- <form class="py-2" on:submit|preventDefault={pollEvents}>
- <div class="event-log-options">
- <label>Max messages: <input type="number" min="0" bind:value={maxMessages} /></label>
- </div>
- <div class="query-row">
- {#if syncing}
- <Icon class="mx-1" data={sync} spin />
- {/if}
- <input class="sync-button" type="submit" value="Query" disabled={syncing} />
- <label class="mx-1"><input type="checkbox" on:input={onAutoUpdateChecked}/> Auto update</label>
- </div>
- </form>
- {#if error}
- <p class="error-box">
- Failed to query: <span class="font-mono">{error}</span>
- </p>
- {/if}
- {#if logEvents}
- <table class="logs-table">
- <thead>
- <tr>
- <th>Timestamp</th>
- <th>Source</th>
- <th>Level</th>
- <th>Message</th>
- </tr>
- </thead>
- <tbody>
- {#each logEvents as logEvent (logEvent._id)}
- <tr style="color: {getLevelColor(logEvent.level)};">
- <td>{logEvent.timestamp}</td>
- <td>{logEvent.label}</td>
- <td>{logEvent.level}</td>
- <td>{logEvent.message}</td>
- </tr>
- {/each}
- </tbody>
- </table>
- {/if}
- </div>
|