feat(web): logs improvements (#284)

feat: add log searching
feat: wrap parent container to add a scrollbar on overflow so it doesn't push the page lower with each message
feat: switch to showing time instead of date+time
This commit is contained in:
stacksmash76 2022-05-25 20:54:40 +02:00 committed by GitHub
parent f836272fe6
commit 52fad1da95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 38 deletions

View file

@ -11,6 +11,7 @@
"formik": "^2.2.9", "formik": "^2.2.9",
"react": "^17.0.2", "react": "^17.0.2",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
"react-debounce-input": "^3.2.5",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-error-boundary": "^3.1.4", "react-error-boundary": "^3.1.4",
"react-hot-toast": "^2.1.1", "react-hot-toast": "^2.1.1",

View file

@ -64,7 +64,7 @@ declare module "react-table" {
// note that having Record here allows you to add anything to the options, this matches the spirit of the // note that having Record here allows you to add anything to the options, this matches the spirit of the
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your // underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
// feature set, this is a safe default. // feature set, this is a safe default.
Record<string, any> {} Record<string, unknown> {}
export interface Hooks<D extends Record<string, unknown> = Record<string, unknown>> export interface Hooks<D extends Record<string, unknown> = Record<string, unknown>>
extends UseExpandedHooks<D>, extends UseExpandedHooks<D>,

View file

@ -1,5 +1,7 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { ExclamationIcon } from "@heroicons/react/solid"; import { ExclamationIcon } from "@heroicons/react/solid";
import format from "date-fns/format";
import { DebounceInput } from "react-debounce-input";
import { APIClient } from "../api/APIClient"; import { APIClient } from "../api/APIClient";
import { Checkbox } from "../components/Checkbox"; import { Checkbox } from "../components/Checkbox";
@ -25,7 +27,10 @@ export const Logs = () => {
const [settings, setSettings] = SettingsContext.use(); const [settings, setSettings] = SettingsContext.use();
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const [logs, setLogs] = useState<LogEvent[]>([]); const [logs, setLogs] = useState<LogEvent[]>([]);
const [searchFilter, setSearchFilter] = useState("");
const [filteredLogs, setFilteredLogs] = useState<LogEvent[]>([]);
const scrollToBottom = () => { const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "auto" }); messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
@ -45,6 +50,21 @@ export const Logs = () => {
return () => es.close(); return () => es.close();
}, [setLogs, settings]); }, [setLogs, settings]);
useEffect(() => {
if (!searchFilter.length) {
setFilteredLogs(logs);
return;
}
const newLogs: LogEvent[] = [];
logs.forEach((log) => {
if (log.message.indexOf(searchFilter) !== -1)
newLogs.push(log);
});
setFilteredLogs(newLogs);
}, [logs, searchFilter]);
const onSetValue = ( const onSetValue = (
key: "scrollOnNewLog" | "indentLogLines" | "hideWrappedText", key: "scrollOnNewLog" | "indentLogLines" | "hideWrappedText",
newValue: boolean newValue: boolean
@ -58,7 +78,7 @@ export const Logs = () => {
<header className="py-10"> <header className="py-10">
<div className="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold text-black dark:text-white">Logs</h1> <h1 className="text-3xl font-bold text-black dark:text-white">Logs</h1>
<div className="flex mt-4 justify-center"> <div className="flex justify-center">
<ExclamationIcon <ExclamationIcon
className="h-5 w-5 text-yellow-400" className="h-5 w-5 text-yellow-400"
aria-hidden="true" aria-hidden="true"
@ -69,8 +89,56 @@ export const Logs = () => {
</header> </header>
<div className="max-w-screen-xl mx-auto pb-12 px-2 sm:px-4 lg:px-8"> <div className="max-w-screen-xl mx-auto pb-12 px-2 sm:px-4 lg:px-8">
<div <div
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg px-2 sm:px-4 pb-3 sm:pb-4" className="bg-white dark:bg-gray-800 rounded-lg shadow-lg px-2 sm:px-4 pt-3 sm:pt-4"
> >
<DebounceInput
minLength={2}
debounceTimeout={200}
onChange={(event) => setSearchFilter(event.target.value.toLowerCase().trim())}
id="filter"
type="text"
autoComplete="off"
className={classNames(
"focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
"block w-full dark:bg-gray-800 shadow-sm dark:text-gray-100 sm:text-sm rounded-md"
)}
placeholder="Enter a string to filter logs by..."
/>
<div
className="mt-2 overflow-y-auto p-2 rounded-lg h-[60vh] min-w-full bg-gray-100 dark:bg-gray-900 overflow-auto"
>
{filteredLogs.map((entry, idx) => (
<div
key={idx}
className={classNames(
settings.indentLogLines ? "grid justify-start grid-flow-col" : "",
settings.hideWrappedText ? "truncate hover:text-ellipsis hover:whitespace-normal" : ""
)}
>
<span
title={entry.time}
className="font-mono text-gray-500 dark:text-gray-600 mr-2 h-full"
>
{format(new Date(entry.time), "HH:mm:ss.SSS")}
</span>
{entry.level in LogColors ? (
<span
className={classNames(
LogColors[entry.level as LogLevel],
"font-mono font-semibold h-full"
)}
>
{entry.level}
{" "}
</span>
) : null}
<span className="ml-2 text-black dark:text-gray-300">
{entry.message}
</span>
</div>
))}
<div ref={messagesEndRef} />
</div>
<Checkbox <Checkbox
label="Scroll to bottom on new message" label="Scroll to bottom on new message"
value={settings.scrollOnNewLog} value={settings.scrollOnNewLog}
@ -88,40 +156,6 @@ export const Logs = () => {
value={settings.hideWrappedText} value={settings.hideWrappedText}
setValue={(newValue) => onSetValue("hideWrappedText", newValue)} setValue={(newValue) => onSetValue("hideWrappedText", newValue)}
/> />
<div
className="overflow-y-auto p-2 rounded-lg min-h-[32rem] lg:min-h-[48rem] min-w-full bg-gray-100 dark:bg-gray-900"
>
{logs.map((a, idx) => (
<div
key={idx}
className={classNames(
settings.indentLogLines ? "grid justify-start grid-flow-col" : "",
settings.hideWrappedText ? "truncate hover:text-ellipsis hover:whitespace-normal" : ""
)}
>
<span
className="font-mono text-gray-500 dark:text-gray-600 mr-2 h-full"
>
{a.time}
</span>
{a.level in LogColors ? (
<span
className={classNames(
LogColors[a.level as LogLevel],
"font-mono font-semibold h-full"
)}
>
{a.level}
{" "}
</span>
) : null}
<span className="ml-2 text-black dark:text-gray-300">
{a.message}
</span>
</div>
))}
<div ref={messagesEndRef} />
</div>
</div> </div>
</div> </div>
</main> </main>