From a841acf1ec27a4d942d5228c24356ad185f52da6 Mon Sep 17 00:00:00 2001 From: marhaupe Date: Sun, 15 Sep 2024 15:12:42 +0200 Subject: [PATCH] add connection status indicator --- packages/frontend/src/App.tsx | 71 ++++++++++--------- .../frontend/src/components/ControlBar.tsx | 33 +++++---- 2 files changed, 57 insertions(+), 47 deletions(-) diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index f683839..b2e2e2a 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -11,6 +11,7 @@ import { Log } from "@/types"; export function App() { const [logs, setLogs] = useState([]); + const [connectionStatus, setConnectionStatus] = useState<"active" | "inactive">("active"); const [filterState, setFilterState] = useQueryParams({ message: withDefault(StringParam, undefined), caseInsensitive: withDefault(BooleanParam, undefined), @@ -21,14 +22,42 @@ export function App() { const logListRef = useRef(null); useEffect(() => { - const prevTitle = document.title; - if (config.command) { - document.title = "uitail - " + config.command; + const url = new URL(`http://localhost:${config.port}${config.routes.events}`); + url.searchParams.set("stream", nanoid()); + if (filterState.message) { + url.searchParams.set("filter", filterState.message); } + if (filterState.caseInsensitive) { + url.searchParams.set("caseInsensitive", filterState.caseInsensitive.toString()); + } + const eventSource = new EventSource(url); + + eventSource.onopen = () => { + setConnectionStatus("active"); + }; + + eventSource.onerror = () => { + setConnectionStatus("inactive"); + }; + + eventSource.onmessage = (event: MessageEvent) => { + try { + const incomingLogs = JSON.parse(event.data); + if (incomingLogs.length !== 1) { + logListRef.current?.resetVirtualization(); + setLogs(incomingLogs); + } else { + setLogs((prevLogs) => [...prevLogs, ...incomingLogs]); + } + } catch (error) { + console.error("Error parsing log", error); + } + }; + return () => { - document.title = prevTitle; + eventSource.close(); }; - }, []); + }, [filterState]); async function handleClear() { try { @@ -51,40 +80,15 @@ export function App() { }); if (response.ok) { toast.success("Agent restarted"); + setConnectionStatus("active"); } } catch (error) { console.error("Error restarting agent:", error); + setConnectionStatus("inactive"); + toast.error("Failed to restart agent"); } } - useEffect(() => { - const url = new URL(`http://localhost:${config.port}${config.routes.events}`); - url.searchParams.set("stream", nanoid()); - if (filterState.message) { - url.searchParams.set("filter", filterState.message); - } - if (filterState.caseInsensitive) { - url.searchParams.set("caseInsensitive", filterState.caseInsensitive.toString()); - } - const eventSource = new EventSource(url); - eventSource.onmessage = (event: MessageEvent) => { - try { - const incomingLogs = JSON.parse(event.data); - if (incomingLogs.length !== 1) { - logListRef.current?.resetVirtualization(); - setLogs(incomingLogs); - } else { - setLogs((prevLogs) => [...prevLogs, ...incomingLogs]); - } - } catch (error) { - console.error("Error parsing log", error); - } - }; - return () => { - eventSource.close(); - }; - }, [filterState]); - useHotkeys("mod+k", () => { handleClear(); }); @@ -115,6 +119,7 @@ export function App() {
setFilterState(query)} diff --git a/packages/frontend/src/components/ControlBar.tsx b/packages/frontend/src/components/ControlBar.tsx index edb079d..608780d 100644 --- a/packages/frontend/src/components/ControlBar.tsx +++ b/packages/frontend/src/components/ControlBar.tsx @@ -3,16 +3,14 @@ import { forwardRef, useImperativeHandle, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; import debounce from "lodash.debounce"; import { Toggle } from "@/components/ui/toggle"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Trash2, RefreshCw, ChevronsUp, ChevronsDown } from "lucide-react"; +import { Circle } from "lucide-react"; +import { cn } from "@/lib/utils"; type Props = { + status: "active" | "inactive"; filter: FilterState; onFilterStateChange: (filter: FilterState) => void; onClear: () => void; @@ -28,6 +26,7 @@ export type FilterState = { export const ControlBar = forwardRef(function ControlBar( { + status, filter, onFilterStateChange, onClear, @@ -95,9 +94,7 @@ export const ControlBar = forwardRef(function ControlBar( - setValue("caseInsensitive", pressed) - } + onPressedChange={(pressed) => setValue("caseInsensitive", pressed)} > Aa @@ -112,17 +109,25 @@ export const ControlBar = forwardRef(function ControlBar(
+
+ +