Skip to content

Commit

Permalink
add connection status indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
marhaupe committed Sep 15, 2024
1 parent c1b71b0 commit a841acf
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 47 deletions.
71 changes: 38 additions & 33 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Log } from "@/types";

export function App() {
const [logs, setLogs] = useState<Log[]>([]);
const [connectionStatus, setConnectionStatus] = useState<"active" | "inactive">("active");
const [filterState, setFilterState] = useQueryParams({
message: withDefault(StringParam, undefined),
caseInsensitive: withDefault(BooleanParam, undefined),
Expand All @@ -21,14 +22,42 @@ export function App() {
const logListRef = useRef<LogListRef>(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 {
Expand All @@ -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();
});
Expand Down Expand Up @@ -115,6 +119,7 @@ export function App() {
<div className="container p-6 h-screen">
<Card className="relative flex flex-col flex-1">
<ControlBar
status={connectionStatus}
filter={filterState}
ref={searchInputRef}
onFilterStateChange={(query) => setFilterState(query)}
Expand Down
33 changes: 19 additions & 14 deletions packages/frontend/src/components/ControlBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +26,7 @@ export type FilterState = {

export const ControlBar = forwardRef(function ControlBar(
{
status,
filter,
onFilterStateChange,
onClear,
Expand Down Expand Up @@ -95,9 +94,7 @@ export const ControlBar = forwardRef(function ControlBar(
<Toggle
aria-label="Match case"
pressed={watch("caseInsensitive")}
onPressedChange={(pressed) =>
setValue("caseInsensitive", pressed)
}
onPressedChange={(pressed) => setValue("caseInsensitive", pressed)}
>
Aa
</Toggle>
Expand All @@ -112,17 +109,25 @@ export const ControlBar = forwardRef(function ControlBar(
</div>
<div className="flex items-center space-x-1">
<Button variant="ghost" size="sm" onClick={onScrollToTop}>
<ChevronsUp className="w-4 h-4" />
<ChevronsUp className="size-4" />
</Button>
<Button variant="ghost" size="sm" onClick={onScrollToBottom}>
<ChevronsDown className="w-4 h-4" />
<ChevronsDown className="size-4" />
</Button>
<Button variant="ghost" size="sm" onClick={onClear}>
<Trash2 className="w-4 h-4 mr-1" />
<Trash2 className="size-4" />
</Button>
<Button variant="ghost" size="sm" onClick={onRestart}>
<RefreshCw className="w-4 h-4 mr-1" />
<RefreshCw className="size-4" />
</Button>
<div className={buttonVariants({ variant: "ghost", size: "sm" })}>
<Circle
className={cn(
"size-2 animate-pulse",
status === "active" ? "fill-green-500 text-green-500" : "fill-red-500 text-red-500"
)}
/>
</div>
</div>
</div>
</div>
Expand Down

0 comments on commit a841acf

Please sign in to comment.