mirror of
https://github.com/idanoo/autobrr
synced 2025-07-26 02:09:13 +00:00

* - fix: changed all {text,bg}-{teal,indigo}-* color properties to {text,bg}-{blue}-* so there is only one dominant primary color in the UI -- blue - enhancement: added `cursor: pointer` to the PageButton component (used in pagination) - enhancement: modified TitleCell to use the new Tooltip component and modified the width selectors to a more sane default value, now instead of scrolling one just has to tap the cell to see it's untruncated value - enhancement: modified the Tooltip component to use react-popper-tooltip (which in turn uses popper.js) which is a much better alternative, since it uses auto-positioning in case there's not enough space and some things aren't as broken as in the previous version (e.g. there was a problem with forcing the previous tooltip to use a specific width) - enhancement: added a useMedia hook selector from the react-use repository, which might come in handy in the future for better/easier decoupling of Desktop and Mobile UI/UX patterns via JS (versus CSS-only) - enhancement: made the mobile navbar more visible and clear. also fixed previous path === "/" bug which was fixed on desktop. - fix: fixed table headers/footers so they're rounded - enhancement: made pagination components more compact (buttons and show N result selector) * changed {ring, border}-indigo-* to ring-blue-* * build: add yarn.lock * fix: formatting warnings * fix: formatting warnings * fix: more formatting warnings * fix: more formatting
162 lines
5.4 KiB
TypeScript
162 lines
5.4 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
|
import format from "date-fns/format";
|
|
import { DebounceInput } from "react-debounce-input";
|
|
import { APIClient } from "../api/APIClient";
|
|
import { Checkbox } from "../components/Checkbox";
|
|
import { classNames } from "../utils";
|
|
import { SettingsContext } from "../utils/Context";
|
|
|
|
type LogEvent = {
|
|
time: string;
|
|
level: string;
|
|
message: string;
|
|
};
|
|
|
|
type LogLevel = "TRACE" | "DEBUG" | "INFO" | "ERROR";
|
|
|
|
const LogColors: Record<LogLevel, string> = {
|
|
"TRACE": "text-purple-300",
|
|
"DEBUG": "text-yellow-500",
|
|
"INFO": "text-green-500",
|
|
"ERROR": "text-red-500"
|
|
};
|
|
|
|
export const Logs = () => {
|
|
const [settings, setSettings] = SettingsContext.use();
|
|
|
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
|
|
const [logs, setLogs] = useState<LogEvent[]>([]);
|
|
const [searchFilter, setSearchFilter] = useState("");
|
|
const [filteredLogs, setFilteredLogs] = useState<LogEvent[]>([]);
|
|
|
|
const scrollToBottom = () => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: "auto" });
|
|
};
|
|
|
|
useEffect(() => {
|
|
const es = APIClient.events.logs();
|
|
|
|
es.onmessage = (event) => {
|
|
const newData = JSON.parse(event.data) as LogEvent;
|
|
setLogs((prevState) => [...prevState, newData]);
|
|
|
|
if (settings.scrollOnNewLog)
|
|
scrollToBottom();
|
|
};
|
|
|
|
return () => es.close();
|
|
}, [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 = (
|
|
key: "scrollOnNewLog" | "indentLogLines" | "hideWrappedText",
|
|
newValue: boolean
|
|
) => setSettings((prevState) => ({
|
|
...prevState,
|
|
[key]: newValue
|
|
}));
|
|
|
|
return (
|
|
<main>
|
|
<header className="pt-10 pb-5">
|
|
<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>
|
|
<div className="flex justify-center mt-1">
|
|
<ExclamationTriangleIcon
|
|
className="h-5 w-5 text-yellow-400"
|
|
aria-hidden="true"
|
|
/>
|
|
<p className="ml-2 text-sm text-black dark:text-gray-400">This page shows only new logs, i.e. no history.</p>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<div className="max-w-screen-xl mx-auto pb-12 px-2 sm:px-4 lg:px-8">
|
|
<div
|
|
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-blue-500 dark:focus:ring-blue-500 focus:border-blue-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
|
|
label="Scroll to bottom on new message"
|
|
value={settings.scrollOnNewLog}
|
|
setValue={(newValue) => onSetValue("scrollOnNewLog", newValue)}
|
|
/>
|
|
<Checkbox
|
|
label="Indent log lines"
|
|
description="Indent each log line according to their respective starting position."
|
|
value={settings.indentLogLines}
|
|
setValue={(newValue) => onSetValue("indentLogLines", newValue)}
|
|
/>
|
|
<Checkbox
|
|
label="Hide wrapped text"
|
|
description="Hides text that is meant to be wrapped."
|
|
value={settings.hideWrappedText}
|
|
setValue={(newValue) => onSetValue("hideWrappedText", newValue)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
};
|