add health and debug status, fix double fetching

This commit is contained in:
Sasha 2023-12-02 23:51:56 +01:00
parent 0389d6cce6
commit 6cc8f45028
No known key found for this signature in database
6 changed files with 190 additions and 30 deletions

View File

@ -1,28 +1,24 @@
import { Block, BlockTypes } from "@/components/Block";
import { Title } from "@/components/Title";
import { Status } from "@/components/Status";
import { useStore } from "@/hooks";
import { Button } from "@/components/Button";
type HeaderProps = {
children?: React.ReactNode;
onWalletConnect?: () => void;
}
export const Header: React.FunctionComponent<HeaderProps> = (props) => {
const { appStatus, wallet } = useStore();
return (
<>
<Block className="mb-5" type={BlockTypes.FlexHorizontal}>
<Title>Waku RLN</Title>
<Title>Waku</Title>
{props.onWalletConnect && (
<Button onClick={props.onWalletConnect}>
Connect Wallet
</Button>
)}
</Block>
<Status text="Application status" mark={appStatus} />
{wallet && <p className="mt-3 text-sm">Wallet connected: {wallet}</p> }
{props.children}
</>
);
};

View File

@ -2,15 +2,16 @@ import React from "react";
import { Block } from "@/components/Block";
import { Subtitle } from "@/components/Subtitle";
import { Button } from "@/components/Button";
import { MessageContent, useWaku } from "@/hooks";
import { MessageContent } from "@/hooks";
export const Waku: React.FunctionComponent<{}> = () => {
const {
onSend,
messages,
contentTopic: activeContentTopic,
onContentTopicChange: onActiveContentTopicChange
} = useWaku();
type WakuProps = {
onSend: (nick: string, text: string) => Promise<void>;
activeContentTopic: string;
messages: MessageContent[];
onActiveContentTopicChange: (contentTopic: string) => void;
}
export const Waku: React.FunctionComponent<WakuProps> = (props) => {
const {
nick,
text,
@ -21,16 +22,16 @@ export const Waku: React.FunctionComponent<{}> = () => {
const {
contentTopic,
onContentTopicChange,
} = useContentTopic(activeContentTopic);
} = useContentTopic(props.activeContentTopic);
const onSendClick = async () => {
await onSend(nick, text);
await props.onSend(nick, text);
resetText();
};
const renderedMessages = React.useMemo(
() => messages.map(renderMessage),
[messages]
() => props.messages.map(renderMessage),
[props.messages]
);
return (
@ -53,7 +54,7 @@ export const Waku: React.FunctionComponent<{}> = () => {
onChange={onContentTopicChange}
className="w-96 mr-2 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
<Button className="mt-1" onClick={() => { onActiveContentTopicChange(contentTopic); }}>Change</Button>
<Button className="mt-1" onClick={() => { props.onActiveContentTopicChange(contentTopic); }}>Change</Button>
</Block>
<Block className="mt-4 mr-10 min-w-fit">

View File

@ -1,12 +1,58 @@
"use client";
import { Header } from "@/app/components/Header";
import { Waku } from "@/app/components/Waku";
import { useWaku } from "@/hooks";
import { DebugInfo } from "@/services/waku";
export default function Home() {
const {
onSend,
messages,
debugInfo,
contentTopic,
onContentTopicChange
} = useWaku();
return (
<main className="flex min-h-screen flex-col p-24 font-mono max-w-screen-lg">
<Header />
<Waku />
<Header>
<DebugInfo value={debugInfo} />
</Header>
<Waku
onSend={onSend}
messages={messages}
activeContentTopic={contentTopic}
onActiveContentTopicChange={onContentTopicChange}
/>
</main>
);
}
type DebugInfoProps = {
value?: DebugInfo;
}
const DebugInfo: React.FunctionComponent<DebugInfoProps> = (props) => {
if (!props.value) {
return;
}
return (
<details className="border rounded p-2">
<summary className="cursor-pointer bg-gray-300 p-2 rounded-md">
<span className="font-bold">Show node info</span>
</summary>
<div className="mt-2 text-sm break-words">
<p className="mb-2">Health: {props.value.health}</p>
<p className="mb-2">Version: {props.value.version}</p>
<p className="mb-2">ENR URI: {props.value.enrUri}</p>
<p className="mb-2">Listen Addresses:</p>
<ul>
{props.value.listenAddresses.map((address, index) => (
<li key={index}>{address}</li>
))}
</ul>
</div>
</details>
);
}

View File

@ -3,12 +3,18 @@ import { Header } from "@/app/components/Header";
import { Keystore } from "@/app/components/Keystore";
import { KeystoreDetails } from "@/app/components/KeystoreDetails";
import { useWallet } from "@/hooks";
import { Status } from "@/components/Status";
import { useStore } from "@/hooks";
export default function KeystorePage() {
const { onWalletConnect } = useWallet();
const { appStatus, wallet } = useStore();
return (
<main className="flex min-h-screen flex-col p-24 font-mono max-w-screen-lg m-auto">
<Header onWalletConnect={onWalletConnect} />
<Header onWalletConnect={onWalletConnect}>
<Status text="Application status" mark={appStatus} />
{wallet && <p className="mt-3 text-sm">Wallet connected: {wallet}</p> }
</Header>
<Keystore />
<KeystoreDetails />
</main>

View File

@ -1,6 +1,6 @@
import React from "react";
import { CONTENT_TOPIC } from "@/constants";
import { Message, waku } from "@/services/waku";
import { DebugInfo, Message, waku } from "@/services/waku";
export type MessageContent = {
nick: string;
@ -11,6 +11,7 @@ export type MessageContent = {
export const useWaku = () => {
const [contentTopic, setContentTopic] = React.useState<string>(CONTENT_TOPIC);
const [messages, setMessages] = React.useState<Map<string, MessageContent>>(new Map());
const [debugInfo, setDebugInfo] = React.useState<undefined | DebugInfo>();
React.useEffect(() => {
const messageListener = (event: CustomEvent) => {
@ -38,6 +39,25 @@ export const useWaku = () => {
};
}, [messages, setMessages, contentTopic]);
React.useEffect(() => {
const debugInfoListener = (event: CustomEvent) => {
const debugInfo = event.detail;
if (!debugInfo) {
return;
}
setDebugInfo(debugInfo);
};
waku.debug.addEventListener("debug", debugInfoListener);
return () => {
waku.debug.removeEventListener("debug", debugInfoListener);
};
}, [debugInfo, setDebugInfo]);
const onSend = React.useCallback(
async (nick: string, text: string) => {
const timestamp = Date.now();
@ -73,6 +93,7 @@ export const useWaku = () => {
return {
onSend,
debugInfo,
contentTopic,
onContentTopicChange,
messages: Array.from(messages.values())

View File

@ -17,6 +17,7 @@ const RELAY = "/relay/v1";
const buildURL = (endpoint: string) => `${LOCAL_NODE}${endpoint}`;
class Relay {
private subscribing = false;
private readonly subscriptionsEmitter = new EventTarget();
// only one content topic subscriptions is possible now
private subscriptionRoutine: undefined | number;
@ -24,7 +25,7 @@ class Relay {
constructor() {}
public addEventListener(contentTopic: string, fn: EventListener) {
this.subscribe(contentTopic);
this.subscribe();
return this.subscriptionsEmitter.addEventListener(contentTopic, fn as any);
}
@ -35,11 +36,12 @@ class Relay {
);
}
private async subscribe(contentTopic: string) {
if (this.subscriptionRoutine) {
private async subscribe() {
if (this.subscriptionRoutine || this.subscribing) {
return;
}
this.subscribing = true;
try {
await http.post(buildURL(`${RELAY}/subscriptions`), [PUBSUB_TOPIC]);
@ -47,11 +49,12 @@ class Relay {
await this.fetchMessages();
}, 5 * SECOND);
} catch (error) {
console.error(`Failed to subscribe node ${contentTopic}:`, error);
console.error(`Failed to subscribe node ${PUBSUB_TOPIC}:`, error);
}
this.subscribing = false;
}
public async unsubscribe(contentTopic: string) {
public async unsubscribe() {
if (!this.subscriptionRoutine) {
return;
}
@ -59,10 +62,11 @@ class Relay {
try {
await http.delete(buildURL(`${RELAY}/subscriptions`), [PUBSUB_TOPIC]);
} catch (error) {
console.error(`Failed to unsubscribe node from ${contentTopic}:`, error);
console.error(`Failed to unsubscribe node from ${PUBSUB_TOPIC}:`, error);
}
clearInterval(this.subscriptionRoutine);
this.subscriptionRoutine = undefined;
}
private async fetchMessages(): Promise<void> {
@ -103,6 +107,92 @@ class Relay {
}
}
type DebugInfoResponse = {
enrUri: string;
listenAddresses: string[];
}
export type DebugInfo = {
health: string;
version: string;
} & DebugInfoResponse;
class Debug {
private subscribing = false;
private readonly subscriptionsEmitter = new EventTarget();
private subscriptionRoutine: undefined | number;
constructor() {}
public addEventListener(event: string, fn: EventListener) {
this.subscribe();
return this.subscriptionsEmitter.addEventListener(event, fn as any);
}
public removeEventListener(event: string, fn: EventListener) {
return this.subscriptionsEmitter.removeEventListener(
event,
fn as any
);
}
private async subscribe() {
if (this.subscriptionRoutine || this.subscribing) {
return;
}
this.subscribing = true;
try {
await this.fetchParameters();
this.subscriptionRoutine = window.setInterval(async () => {
await this.fetchParameters();
}, 30 * SECOND);
} catch(error) {
console.error("Failed to fetch debug info:", error);
}
this.subscribing = false;
}
private async unsubscribe() {
if (!this.subscriptionRoutine) {
return;
}
clearInterval(this.subscriptionRoutine);
this.subscriptionRoutine = undefined;
}
private async fetchParameters(): Promise<void> {
const health = await this.fetchHealth();
const debug = await this.fetchDebugInfo();
const version = await this.fetchDebugVersion();
this.subscriptionsEmitter.dispatchEvent(
new CustomEvent("debug", { detail: {
health,
version,
...debug,
} })
);
}
private async fetchHealth(): Promise<string> {
const response = await http.get(buildURL(`/health`));
return response.text();
}
private async fetchDebugInfo(): Promise<DebugInfoResponse> {
const response = await http.get(buildURL(`/debug/v1/info`));
const body: DebugInfoResponse = await response.json();
return body;
}
private async fetchDebugVersion(): Promise<string> {
const response = await http.get(buildURL(`/debug/v1/version`));
return response.text();
}
}
export const waku = {
relay: new Relay(),
debug: new Debug(),
};