Trying to optimize logs JS code
This commit is contained in:
parent
2603b20f27
commit
98d6a6f054
11 changed files with 3100 additions and 2989 deletions
|
|
@ -837,10 +837,10 @@ impl<'a> PeerConnectionHandler for &'a PeerHandler {
|
||||||
}
|
}
|
||||||
Message::Have(h) => self.on_have(h),
|
Message::Have(h) => self.on_have(h),
|
||||||
Message::NotInterested => {
|
Message::NotInterested => {
|
||||||
debug!("received \"not interested\", but we don't process it yet")
|
trace!("received \"not interested\", but we don't process it yet")
|
||||||
}
|
}
|
||||||
Message::Cancel(_) => {
|
Message::Cancel(_) => {
|
||||||
debug!("received \"cancel\", but we don't process it yet")
|
trace!("received \"cancel\", but we don't process it yet")
|
||||||
}
|
}
|
||||||
message => {
|
message => {
|
||||||
warn!("received unsupported message {:?}, ignoring", message);
|
warn!("received unsupported message {:?}, ignoring", message);
|
||||||
|
|
@ -1306,7 +1306,7 @@ impl PeerHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_i_am_unchoked(&self) {
|
fn on_i_am_unchoked(&self) {
|
||||||
debug!("we are unchoked");
|
trace!("we are unchoked");
|
||||||
self.locked.write().i_am_choked = false;
|
self.locked.write().i_am_choked = false;
|
||||||
self.unchoke_notify.notify_waiters();
|
self.unchoke_notify.notify_waiters();
|
||||||
self.requests_sem.add_permits(16);
|
self.requests_sem.add_permits(16);
|
||||||
|
|
|
||||||
20
crates/librqbit/webui/dist/assets/index.js
vendored
20
crates/librqbit/webui/dist/assets/index.js
vendored
File diff suppressed because one or more lines are too long
2
crates/librqbit/webui/dist/manifest.json
vendored
2
crates/librqbit/webui/dist/manifest.json
vendored
|
|
@ -4,7 +4,7 @@
|
||||||
"src": "assets/logo.svg"
|
"src": "assets/logo.svg"
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"file": "assets/index-050cef91.js",
|
"file": "assets/index-b02909de.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "index.html"
|
"src": "index.html"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2474
crates/librqbit/webui/node_modules/.package-lock.json
generated
vendored
2474
crates/librqbit/webui/node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load diff
3138
crates/librqbit/webui/package-lock.json
generated
3138
crates/librqbit/webui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,23 +1,25 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build && ./post-build",
|
"build": "vite build && ./post-build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"react": "^18.2.0",
|
"lodash.debounce": "^4.0.8",
|
||||||
"react-bootstrap": "^2.9.1",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-bootstrap": "^2.9.1",
|
||||||
"react-icons": "^4.12.0"
|
"react-dom": "^18.2.0",
|
||||||
},
|
"react-icons": "^4.12.0"
|
||||||
"devDependencies": {
|
},
|
||||||
"@types/react": "^18.2.38",
|
"devDependencies": {
|
||||||
"@types/react-dom": "^18.2.16",
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"prettier": "3.1.0",
|
"@types/react": "^18.2.38",
|
||||||
"typescript": "^5.3.2",
|
"@types/react-dom": "^18.2.16",
|
||||||
"vite": "^4.5.1"
|
"prettier": "3.1.0",
|
||||||
}
|
"typescript": "^5.3.2",
|
||||||
|
"vite": "^4.5.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,48 @@ export interface AddTorrentOptions {
|
||||||
preferred_id?: number | null;
|
preferred_id?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Value = string | number | boolean;
|
||||||
|
|
||||||
|
export interface Span {
|
||||||
|
name: string;
|
||||||
|
[key: string]: Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Example log line
|
||||||
|
|
||||||
|
const EXAMPLE_LOG_JSON: JSONLogLine = {
|
||||||
|
timestamp: "2023-12-08T21:48:13.649165Z",
|
||||||
|
level: "DEBUG",
|
||||||
|
fields: { message: "successfully port forwarded 192.168.0.112:4225" },
|
||||||
|
target: "librqbit_upnp",
|
||||||
|
span: { port: 4225, name: "manage_port" },
|
||||||
|
spans: [
|
||||||
|
{ port: 4225, name: "upnp_forward" },
|
||||||
|
{
|
||||||
|
location: "http://192.168.0.1:49152/IGDdevicedesc_brlan0.xml",
|
||||||
|
name: "upnp_endpoint",
|
||||||
|
},
|
||||||
|
{ device: "ARRIS TG3492LG", name: "device" },
|
||||||
|
{ device: "WANDevice:1", name: "device" },
|
||||||
|
{ device: "WANConnectionDevice:1", name: "device" },
|
||||||
|
{ url: "/upnp/control/WANIPConnection0", name: "service" },
|
||||||
|
{ port: 4225, name: "manage_port" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
export interface JSONLogLine {
|
||||||
|
level: string;
|
||||||
|
timestamp: string;
|
||||||
|
fields: {
|
||||||
|
message: string;
|
||||||
|
[key: string]: Value;
|
||||||
|
};
|
||||||
|
target: string;
|
||||||
|
span: Span;
|
||||||
|
spans: Span[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface RqbitAPI {
|
export interface RqbitAPI {
|
||||||
getHttpBaseUrl: () => string | null;
|
getHttpBaseUrl: () => string | null;
|
||||||
listTorrents: () => Promise<ListTorrentsResponse>;
|
listTorrents: () => Promise<ListTorrentsResponse>;
|
||||||
|
|
|
||||||
88
crates/librqbit/webui/src/components/LogLine.tsx
Normal file
88
crates/librqbit/webui/src/components/LogLine.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import React from "react";
|
||||||
|
import { JSONLogLine, Span } from "../api-types";
|
||||||
|
|
||||||
|
const SpanFields: React.FC<{ span: Span }> = ({ span }) => {
|
||||||
|
let fields = Object.entries(span).filter(([name, value]) => name != "name");
|
||||||
|
if (fields.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{"{"}
|
||||||
|
{fields
|
||||||
|
.map(([name, value]) => {
|
||||||
|
return (
|
||||||
|
<span key={name}>
|
||||||
|
{name} = {value}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.reduce((prev, curr) => (
|
||||||
|
<>
|
||||||
|
{prev}, {curr}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
{"}"}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LogSpan: React.FC<{ span: Span }> = ({ span }) => (
|
||||||
|
<>
|
||||||
|
<span className="fw-bold">{span.name}</span>
|
||||||
|
<SpanFields span={span} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Fields: React.FC<{ fields: JSONLogLine["fields"] }> = ({ fields }) => (
|
||||||
|
<span
|
||||||
|
className={`m-1 ${
|
||||||
|
fields.message.match(/error|fail/g) ? "text-danger" : "text-muted"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{fields.message}
|
||||||
|
{Object.entries(fields)
|
||||||
|
.filter(([key, value]) => key != "message")
|
||||||
|
.map(([key, value]) => (
|
||||||
|
<span className="m-1" key={key}>
|
||||||
|
<span className="fst-italic fw-bold">{key}</span>={value}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LogLine: React.FC<{ line: JSONLogLine }> = React.memo(
|
||||||
|
({ line }) => {
|
||||||
|
const parsed = line;
|
||||||
|
|
||||||
|
const classNameByLevel = (level: string) => {
|
||||||
|
switch (level) {
|
||||||
|
case "DEBUG":
|
||||||
|
return "text-primary";
|
||||||
|
case "INFO":
|
||||||
|
return "text-success";
|
||||||
|
case "WARN":
|
||||||
|
return "text-warning";
|
||||||
|
case "ERROR":
|
||||||
|
return "text-danger";
|
||||||
|
default:
|
||||||
|
return "text-muted";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p className="font-monospace m-0 text-break" style={{ fontSize: "10px" }}>
|
||||||
|
<span className="m-1">{parsed.timestamp}</span>
|
||||||
|
<span className={`m-1 ${classNameByLevel(parsed.level)}`}>
|
||||||
|
{parsed.level}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="m-1">
|
||||||
|
{parsed.spans?.map((span, i) => <LogSpan key={i} span={span} />)}
|
||||||
|
</span>
|
||||||
|
<span className="m-1 text-muted">{parsed.target}</span>
|
||||||
|
<Fields fields={parsed.fields} />
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -8,13 +8,17 @@ import React, {
|
||||||
import { ErrorWithLabel } from "../rqbit-web";
|
import { ErrorWithLabel } from "../rqbit-web";
|
||||||
import { ErrorComponent } from "./ErrorComponent";
|
import { ErrorComponent } from "./ErrorComponent";
|
||||||
import { Form } from "react-bootstrap";
|
import { Form } from "react-bootstrap";
|
||||||
|
import { loopUntilSuccess } from "../helper/loopUntilSuccess";
|
||||||
|
import debounce from "lodash.debounce";
|
||||||
|
import { LogLine } from "./LogLine";
|
||||||
|
import { JSONLogLine } from "../api-types";
|
||||||
|
|
||||||
interface LogStreamProps {
|
interface LogStreamProps {
|
||||||
httpApiBase: string;
|
httpApiBase: string;
|
||||||
maxLines?: number;
|
maxLines?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Line {
|
export interface Line {
|
||||||
id: number;
|
id: number;
|
||||||
content: string;
|
content: string;
|
||||||
parsed: JSONLogLine;
|
parsed: JSONLogLine;
|
||||||
|
|
@ -22,6 +26,12 @@ interface Line {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeBuffers = (a1: Uint8Array, a2: Uint8Array): Uint8Array => {
|
const mergeBuffers = (a1: Uint8Array, a2: Uint8Array): Uint8Array => {
|
||||||
|
if (a1.length === 0) {
|
||||||
|
return a2;
|
||||||
|
}
|
||||||
|
if (a2.length === 0) {
|
||||||
|
return a1;
|
||||||
|
}
|
||||||
const merged = new Uint8Array(a1.length + a2.length);
|
const merged = new Uint8Array(a1.length + a2.length);
|
||||||
merged.set(a1);
|
merged.set(a1);
|
||||||
merged.set(a2, a1.length);
|
merged.set(a2, a1.length);
|
||||||
|
|
@ -30,7 +40,7 @@ const mergeBuffers = (a1: Uint8Array, a2: Uint8Array): Uint8Array => {
|
||||||
|
|
||||||
const streamLogs = (
|
const streamLogs = (
|
||||||
httpApiBase: string,
|
httpApiBase: string,
|
||||||
addLine: React.MutableRefObject<(text: string) => void>,
|
addLine: (text: string) => void,
|
||||||
setError: (error: ErrorWithLabel | null) => void
|
setError: (error: ErrorWithLabel | null) => void
|
||||||
): (() => void) => {
|
): (() => void) => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
@ -38,28 +48,14 @@ const streamLogs = (
|
||||||
|
|
||||||
let canceled = false;
|
let canceled = false;
|
||||||
|
|
||||||
const cancel = () => {
|
const cancelFetch = () => {
|
||||||
console.log("cancelling fetch");
|
console.log("cancelling fetch");
|
||||||
canceled = true;
|
canceled = true;
|
||||||
controller.abort();
|
controller.abort();
|
||||||
};
|
};
|
||||||
|
|
||||||
const run = async () => {
|
const runOnce = async () => {
|
||||||
let response = null;
|
let response = await fetch(httpApiBase + "/stream_logs", { signal });
|
||||||
try {
|
|
||||||
response = await fetch(httpApiBase + "/stream_logs", { signal });
|
|
||||||
} catch (e: any) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setError({
|
|
||||||
text: "network error fetching logs",
|
|
||||||
details: {
|
|
||||||
text: e.toString(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
let text = await response.text();
|
let text = await response.text();
|
||||||
|
|
@ -70,15 +66,18 @@ const streamLogs = (
|
||||||
text,
|
text,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
throw new Error("retry");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.body) {
|
if (!response.body) {
|
||||||
setError({
|
setError({
|
||||||
text: "error fetching logs: ReadableStream not supported.",
|
text: "error fetching logs: ReadableStream not supported.",
|
||||||
});
|
});
|
||||||
throw new Error("ReadableStream not supported.");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
|
|
||||||
let buffer = new Uint8Array();
|
let buffer = new Uint8Array();
|
||||||
|
|
@ -86,150 +85,47 @@ const streamLogs = (
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
// Handle stream completion or errors
|
setError({
|
||||||
break;
|
text: "log stream terminated",
|
||||||
|
});
|
||||||
|
throw new Error("retry");
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = mergeBuffers(buffer, value);
|
buffer = mergeBuffers(buffer, value);
|
||||||
|
|
||||||
while (true) {
|
for (let newLineIdx: number; (newLineIdx = buffer.indexOf(10)) !== -1; ) {
|
||||||
const newLineIdx = buffer.indexOf(10);
|
|
||||||
if (newLineIdx === -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let lineBytes = buffer.slice(0, newLineIdx);
|
let lineBytes = buffer.slice(0, newLineIdx);
|
||||||
let line = new TextDecoder().decode(lineBytes);
|
let line = new TextDecoder().decode(lineBytes);
|
||||||
addLine.current(line);
|
addLine(line);
|
||||||
buffer = buffer.slice(newLineIdx + 1);
|
buffer = buffer.slice(newLineIdx + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
run();
|
|
||||||
|
|
||||||
return cancel;
|
let cancelLoop = loopUntilSuccess(
|
||||||
};
|
() =>
|
||||||
|
runOnce().then(
|
||||||
type Value = string | number | boolean;
|
() => {},
|
||||||
|
(e) => {
|
||||||
interface Span {
|
if (canceled) {
|
||||||
name: string;
|
return;
|
||||||
[key: string]: Value;
|
}
|
||||||
}
|
setError({
|
||||||
|
text: "error streaming logs",
|
||||||
interface JSONLogLine {
|
details: {
|
||||||
level: string;
|
text: e.toString(),
|
||||||
timestamp: string;
|
},
|
||||||
fields: {
|
});
|
||||||
message: string;
|
throw e;
|
||||||
[key: string]: Value;
|
}
|
||||||
};
|
),
|
||||||
target: string;
|
1000
|
||||||
span: Span;
|
|
||||||
spans: Span[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXAMPLE_LOG_JSON: JSONLogLine = {
|
|
||||||
timestamp: "2023-12-08T21:48:13.649165Z",
|
|
||||||
level: "DEBUG",
|
|
||||||
fields: { message: "successfully port forwarded 192.168.0.112:4225" },
|
|
||||||
target: "librqbit_upnp",
|
|
||||||
span: { port: 4225, name: "manage_port" },
|
|
||||||
spans: [
|
|
||||||
{ port: 4225, name: "upnp_forward" },
|
|
||||||
{
|
|
||||||
location: "http://192.168.0.1:49152/IGDdevicedesc_brlan0.xml",
|
|
||||||
name: "upnp_endpoint",
|
|
||||||
},
|
|
||||||
{ device: "ARRIS TG3492LG", name: "device" },
|
|
||||||
{ device: "WANDevice:1", name: "device" },
|
|
||||||
{ device: "WANConnectionDevice:1", name: "device" },
|
|
||||||
{ url: "/upnp/control/WANIPConnection0", name: "service" },
|
|
||||||
{ port: 4225, name: "manage_port" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const LogLine = ({ line }: { line: Line }) => {
|
|
||||||
const parsed = line.parsed;
|
|
||||||
|
|
||||||
const classNameByLevel = (level: string) => {
|
|
||||||
switch (level) {
|
|
||||||
case "DEBUG":
|
|
||||||
return "text-primary";
|
|
||||||
case "INFO":
|
|
||||||
return "text-success";
|
|
||||||
case "WARN":
|
|
||||||
return "text-warning";
|
|
||||||
case "ERROR":
|
|
||||||
return "text-danger";
|
|
||||||
default:
|
|
||||||
return "text-muted";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const spanFields = (span: Span) => {
|
|
||||||
let fields = Object.entries(span).filter(([name, value]) => name != "name");
|
|
||||||
if (fields.length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{"{"}
|
|
||||||
{fields
|
|
||||||
.map(([name, value]) => {
|
|
||||||
return (
|
|
||||||
<span key={name}>
|
|
||||||
{name} = {value}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.reduce((prev, curr) => (
|
|
||||||
<>
|
|
||||||
{prev}, {curr}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
{"}"}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
hidden={!line.show}
|
|
||||||
className="font-monospace m-0 text-break"
|
|
||||||
style={{ fontSize: "10px" }}
|
|
||||||
>
|
|
||||||
<span className="m-1">{parsed.timestamp}</span>
|
|
||||||
<span className={`m-1 ${classNameByLevel(parsed.level)}`}>
|
|
||||||
{parsed.level}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="m-1">
|
|
||||||
{parsed.spans?.map((span, i) => (
|
|
||||||
<span key={i}>
|
|
||||||
<span className="fw-bold">{span.name}</span>
|
|
||||||
{spanFields(span)}:
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</span>
|
|
||||||
<span className="m-1 text-muted">{parsed.target}</span>
|
|
||||||
<span
|
|
||||||
className={`m-1 ${
|
|
||||||
parsed.fields.message.match(/error|fail/g)
|
|
||||||
? "text-danger"
|
|
||||||
: "text-muted"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{parsed.fields.message}
|
|
||||||
{Object.entries(parsed.fields)
|
|
||||||
.filter(([key, value]) => key != "message")
|
|
||||||
.map(([key, value]) => (
|
|
||||||
<span className="m-1" key={key}>
|
|
||||||
<span className="fst-italic fw-bold">{key}</span>={value}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelFetch();
|
||||||
|
cancelLoop();
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogStream: React.FC<LogStreamProps> = ({
|
export const LogStream: React.FC<LogStreamProps> = ({
|
||||||
|
|
@ -239,7 +135,7 @@ export const LogStream: React.FC<LogStreamProps> = ({
|
||||||
const [logLines, setLogLines] = useState<Line[]>([]);
|
const [logLines, setLogLines] = useState<Line[]>([]);
|
||||||
const [error, setError] = useState<ErrorWithLabel | null>(null);
|
const [error, setError] = useState<ErrorWithLabel | null>(null);
|
||||||
const [filter, setFilter] = useState<string>("");
|
const [filter, setFilter] = useState<string>("");
|
||||||
const filterRegex = useRef(new RegExp(""));
|
const filterRegex = useRef<RegExp | null>(null);
|
||||||
|
|
||||||
const maxL = maxLines ?? 1000;
|
const maxL = maxLines ?? 1000;
|
||||||
|
|
||||||
|
|
@ -253,7 +149,9 @@ export const LogStream: React.FC<LogStreamProps> = ({
|
||||||
id: nextLineId,
|
id: nextLineId,
|
||||||
content: text,
|
content: text,
|
||||||
parsed: JSON.parse(text) as JSONLogLine,
|
parsed: JSON.parse(text) as JSONLogLine,
|
||||||
show: !!text.match(filterRegex.current),
|
show: filterRegex.current
|
||||||
|
? !!text.match(filterRegex.current)
|
||||||
|
: true,
|
||||||
},
|
},
|
||||||
...logLines.slice(0, maxL - 1),
|
...logLines.slice(0, maxL - 1),
|
||||||
];
|
];
|
||||||
|
|
@ -266,27 +164,40 @@ export const LogStream: React.FC<LogStreamProps> = ({
|
||||||
const addLineRef = useRef(addLine);
|
const addLineRef = useRef(addLine);
|
||||||
addLineRef.current = addLine;
|
addLineRef.current = addLine;
|
||||||
|
|
||||||
|
const updateFilter = debounce((value: string) => {
|
||||||
|
let regex: RegExp | null = null;
|
||||||
|
try {
|
||||||
|
regex = new RegExp(value);
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filterRegex.current = regex;
|
||||||
|
setLogLines((logLines) => {
|
||||||
|
let tmp = [...logLines];
|
||||||
|
for (let line of tmp) {
|
||||||
|
line.show = !!line.content.match(regex as RegExp);
|
||||||
|
}
|
||||||
|
return tmp;
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
|
||||||
const handleFilterChange = (value: string) => {
|
const handleFilterChange = (value: string) => {
|
||||||
setFilter(value);
|
setFilter(value);
|
||||||
try {
|
updateFilter(value);
|
||||||
let regex = new RegExp(value);
|
|
||||||
filterRegex.current = regex;
|
|
||||||
setLogLines((logLines) => {
|
|
||||||
let tmp = [...logLines];
|
|
||||||
for (let line of tmp) {
|
|
||||||
line.show = !!line.content.match(regex);
|
|
||||||
}
|
|
||||||
return tmp;
|
|
||||||
});
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => updateFilter.cancel, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return streamLogs(httpApiBase, addLineRef, setError);
|
return streamLogs(
|
||||||
|
httpApiBase,
|
||||||
|
(line) => addLineRef.current(line),
|
||||||
|
setError
|
||||||
|
);
|
||||||
}, [httpApiBase]);
|
}, [httpApiBase]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div>
|
||||||
<ErrorComponent error={error} />
|
<ErrorComponent error={error} />
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
Showing last {maxL} logs since this window was opened
|
Showing last {maxL} logs since this window was opened
|
||||||
|
|
@ -296,6 +207,7 @@ export const LogStream: React.FC<LogStreamProps> = ({
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="text"
|
type="text"
|
||||||
value={filter}
|
value={filter}
|
||||||
|
name="filter"
|
||||||
placeholder="Enter filter (regex)"
|
placeholder="Enter filter (regex)"
|
||||||
onChange={(e) => handleFilterChange(e.target.value)}
|
onChange={(e) => handleFilterChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -303,7 +215,9 @@ export const LogStream: React.FC<LogStreamProps> = ({
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
{logLines.map((line) => (
|
{logLines.map((line) => (
|
||||||
<LogLine key={line.id} line={line} />
|
<div hidden={!line.show}>
|
||||||
|
<LogLine key={line.id} line={line.parsed} />
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
23
desktop/package-lock.json
generated
23
desktop/package-lock.json
generated
|
|
@ -6,9 +6,9 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "rqbit",
|
"name": "rqbit",
|
||||||
"version": "4.0.0-beta.3",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1.5.1",
|
"@tauri-apps/api": "^1.5.1",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-bootstrap": "^2.9.1",
|
"react-bootstrap": "^2.9.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": ">=2.0.0-alpha.16",
|
"@tauri-apps/cli": ">=2.0.0-alpha.16",
|
||||||
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
|
|
@ -1087,6 +1088,21 @@
|
||||||
"@babel/types": "^7.20.7"
|
"@babel/types": "^7.20.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.14.202",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
|
||||||
|
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash.debounce": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.11",
|
"version": "15.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||||
|
|
@ -1430,6 +1446,11 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||||
|
},
|
||||||
"node_modules/loose-envify": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1.5.1",
|
"@tauri-apps/api": "^1.5.1",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-bootstrap": "^2.9.1",
|
"react-bootstrap": "^2.9.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
@ -17,6 +18,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": ">=2.0.0-alpha.16",
|
"@tauri-apps/cli": ">=2.0.0-alpha.16",
|
||||||
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@vitejs/plugin-react": "^4.0.3",
|
"@vitejs/plugin-react": "^4.0.3",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue