Skip to content

Commit

Permalink
refactor and getBlock (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
qiweiii authored Oct 24, 2024
1 parent b3a2e80 commit 4cab80b
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 104 deletions.
27 changes: 26 additions & 1 deletion app/node/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,39 @@

import { useSearchParams } from "next/navigation";
import State from "@/components/state";
import { useEffect, useState } from "react";
import { sendRequest } from "@/lib/ws";
import { toast } from "sonner";

export default function NodePage() {
const searchParams = useSearchParams();
const endpoint = decodeURIComponent(searchParams.get("endpoint") || "");
const [nodeName, setNodeName] = useState<string>("");

useEffect(() => {
const fetchNodeName = async () => {
try {
const name = await sendRequest(endpoint, "system_name");
setNodeName(name as string);
} catch (error) {
toast.error(
`Failed to fetch node name: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
};

if (endpoint) {
fetchNodeName();
}
}, [endpoint]);

return (
<div className="container mt-3 mb-8 flex flex-col gap-8 items-center sm:items-start">
<h3 className="text-xl font-bold mb-2">{endpoint}</h3>
<h3 className="text-xl font-bold mb-2">
{nodeName ? `${nodeName} (${endpoint})` : endpoint}
</h3>
<State endpoint={endpoint} />
</div>
);
Expand Down
130 changes: 86 additions & 44 deletions components/state.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
"use client";

import { useState, useEffect, useCallback } from "react";
import { useState, useCallback } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { connectToNode, sendRequest, disconnectFromNode } from "@/lib/ws";
import { JSONObject, sendRequest } from "@/lib/ws";
import { toast } from "sonner";
import { X } from "lucide-react";

type KeyValuePair = {
blockHash: string;
stateRoot: string;
type Result = {
type: "block" | "state";
data: JSONObject;
};

export default function State({ endpoint }: { endpoint: string }) {
const [hashInput, setHashInput] = useState("");
const [stateRoots, setStateRoots] = useState<KeyValuePair[]>([]);
const [blockHashInput, setBlockHashInput] = useState("");
const [stateHashInput, setStateHashInput] = useState("");
const [results, setResults] = useState<Result[]>([]);

useEffect(() => {
connectToNode(endpoint).catch((error: unknown) =>
const fetchBlock = useCallback(async () => {
if (blockHashInput.length !== 66 && blockHashInput.length !== 0) {
toast.error(
`Failed to connect: ${
"Hash must be a valid 32 byte hex string `0x{string}` or empty for best block"
);
return;
}

try {
const method = "chain_getBlock";
const params = { hash: blockHashInput };
const result = await sendRequest(endpoint, method, params);
setResults((prev) => [
{ type: "block", data: result as JSONObject },
...prev,
]);
setBlockHashInput("");
} catch (error: unknown) {
toast.error(
`Failed to fetch block: ${
error instanceof Error ? error.message : "Unknown error"
}`
)
);

return () => {
disconnectFromNode(endpoint);
};
}, [endpoint]);
);
}
}, [endpoint, blockHashInput]);

const fetchState = useCallback(async () => {
if (hashInput.length !== 66 && hashInput.length !== 0) {
if (stateHashInput.length !== 66 && stateHashInput.length !== 0) {
toast.error(
"Hash must be a valid 32 byte hex string `0x{string}` or empty for best block"
);
Expand All @@ -39,52 +53,80 @@ export default function State({ endpoint }: { endpoint: string }) {

try {
const method = "chain_getState";
const params = hashInput ? { hash: hashInput } : {};
const result = (await sendRequest(endpoint, method, params)) as {
[key: string]: string;
};
setStateRoots((prev) => [
{
blockHash: result?.blockHash,
stateRoot: result?.stateRoot,
},
const params = { hash: stateHashInput };
const result = await sendRequest(endpoint, method, params);
setResults((prev) => [
{ type: "state", data: result as JSONObject },
...prev,
]);
setHashInput("");
setStateHashInput("");
} catch (error: unknown) {
toast.error(
`Failed to fetch state root: ${
`Failed to fetch state: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
}, [endpoint, hashInput]);
}, [endpoint, stateHashInput]);

const removeResult = (index: number) => {
setResults((prev) => prev.filter((_, i) => i !== index));
};

const renderJSON = (json: JSONObject) => {
return (
<pre className="text-sm overflow-auto">
{JSON.stringify(json, null, 2)}
</pre>
);
};

return (
<div className="w-full space-y-4">
<div className="w-full space-y-8">
<div>
<h3 className="text-lg font-semibold">Fetch State</h3>
<h3 className="text-lg font-semibold mb-2">Fetch Block</h3>
<div className="flex space-x-2">
<Input
type="text"
placeholder="0x... (optional 32 byte block hash)"
value={hashInput}
onChange={(e) => setHashInput(e.target.value)}
value={blockHashInput}
onChange={(e) => setBlockHashInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && fetchBlock()}
/>
<Button onClick={fetchBlock}>Fetch</Button>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">Fetch State</h3>
<div className="flex space-x-2">
<Input
type="text"
placeholder="0x... (optional 32 byte block hash)"
value={stateHashInput}
onChange={(e) => setStateHashInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && fetchState()}
/>
<Button onClick={fetchState}>Fetch</Button>
</div>
</div>
{stateRoots.map((pair, index) => (
<div key={index}>
<h3 className="text-md font-semibold">Block Hash</h3>
<p className="font-mono break-all">{pair.blockHash}</p>
<h3 className="text-md font-semibold mt-2">State Root</h3>
<p className="font-mono break-all">
{pair.stateRoot || "No state root found"}
</p>
</div>
))}
<div className="space-y-4">
{results.map((result, index) => (
<div key={index} className="border rounded-lg p-4 relative">
<Button
variant="ghost"
size="sm"
className="absolute top-2 right-2"
onClick={() => removeResult(index)}
>
<X className="h-4 w-4" />
</Button>
<h4 className="text-md font-semibold mb-2">
{result.type === "block" ? "Block" : "State"}
</h4>
{renderJSON(result.data)}
</div>
))}
</div>
</div>
);
}
61 changes: 18 additions & 43 deletions components/telemetry-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Hash, Cpu, Blocks, Users, X, RefreshCw } from "lucide-react";
import { connectToNode, sendRequest, disconnectFromNode } from "@/lib/ws";
import { sendRequest, disconnectFromNode } from "@/lib/ws";
import { toast } from "sonner";

type NodeInfo = {
Expand Down Expand Up @@ -85,46 +85,22 @@ export default function TelemetryDashboard() {
[setNodeConnected]
);

const connectAndSubscribe = useCallback(
// TODO: use real subscription
const startPolling = useCallback(
(endpoint: string) => {
const connect = async () => {
try {
await connectToNode(endpoint);
setNodeConnected(endpoint, true);
await updateNodeInfo(endpoint);
} catch (error: unknown) {
setNodeConnected(endpoint, false);
toast.error(
`Failed to connect to ${endpoint}: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
};
if (intervalsRef.current[endpoint]) {
clearInterval(intervalsRef.current[endpoint]);
}

// Initial connection attempt
connect();
updateNodeInfo(endpoint);

// Set up interval for periodic updates and reconnection attempts
const interval = setInterval(async () => {
if (!nodeInfo.find((node) => node.endpoint === endpoint)?.connected) {
// If not connected, try to reconnect
await connect();
} else {
// If connected, update node info
await updateNodeInfo(endpoint);
}
const interval = setInterval(() => {
updateNodeInfo(endpoint);
}, 4000);

intervalsRef.current[endpoint] = interval;

return () => {
clearInterval(interval);
delete intervalsRef.current[endpoint];
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[setNodeConnected, updateNodeInfo]
[updateNodeInfo]
);

useEffect(() => {
Expand All @@ -140,7 +116,7 @@ export default function TelemetryDashboard() {
);

endpoints.forEach((endpoint) => {
connectAndSubscribe(endpoint);
startPolling(endpoint);
});
}
};
Expand All @@ -150,10 +126,9 @@ export default function TelemetryDashboard() {
return () => {
const currentIntervals = intervalsRef.current;
Object.values(currentIntervals).forEach(clearInterval);
nodeInfo.forEach((node) => disconnectFromNode(node.endpoint));
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [connectAndSubscribe]);
}, []);

const validateUrl = (url: string) => {
const pattern = /^(wss?:\/\/).+$/;
Expand All @@ -171,7 +146,7 @@ export default function TelemetryDashboard() {
...prev,
{ endpoint: rpcInput, connected: false },
]); // Add as disconnected
connectAndSubscribe(rpcInput);
startPolling(rpcInput);
setRpcInput("");

const savedEndpoints = JSON.parse(
Expand All @@ -180,7 +155,7 @@ export default function TelemetryDashboard() {
savedEndpoints.push(rpcInput);
localStorage.setItem(STORAGE_KEY, JSON.stringify(savedEndpoints));
}
}, [rpcInput, nodeInfo, connectAndSubscribe]);
}, [rpcInput, nodeInfo, startPolling]);

const removeRpc = useCallback((endpoint: string) => {
disconnectFromNode(endpoint);
Expand Down Expand Up @@ -212,9 +187,9 @@ export default function TelemetryDashboard() {

// Reconnect to all endpoints
nodeInfo.forEach((node) => {
connectAndSubscribe(node.endpoint);
startPolling(node.endpoint);
});
}, [nodeInfo, connectAndSubscribe]);
}, [nodeInfo, startPolling]);

return (
<div className="w-full h-full flex flex-col gap-4">
Expand All @@ -233,7 +208,7 @@ export default function TelemetryDashboard() {
<RefreshCw className="h-4 w-4" />
</Button>
</div>
<div className="overflow-x-auto min-h-[300px]">
<div className="overflow-x-auto min-h-[300px] text-nowrap">
<Table>
<TableHeader>
<TableRow>
Expand Down Expand Up @@ -274,7 +249,7 @@ export default function TelemetryDashboard() {
className={`cursor-pointer hover:bg-muted/50 ${
node.connected
? ""
: "bg-red-100 dark:bg-red-500 text-black dark:text-white"
: "bg-red-200 dark:bg-red-500 text-black dark:text-white hover:bg-red-100"
}`}
onClick={() => {
router.push(
Expand Down
Loading

0 comments on commit 4cab80b

Please sign in to comment.