diff --git a/webui/.proxyrc b/webui/.proxyrc new file mode 100644 index 00000000..98c5259e --- /dev/null +++ b/webui/.proxyrc @@ -0,0 +1,8 @@ +{ + "/apiproxy": { + "target": "http://localhost:8080/", + "pathRewrite": { + "^/apiproxy": "" + } + } +} diff --git a/webui/src/App.tsx b/webui/src/App.tsx index ac7fd175..ac28fb77 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -8,6 +8,8 @@ import { import "./App.scss"; import { Home } from "./Home"; import { PeerState } from "./PeerState"; +import { peerStateLoader } from "./PeerStatePlumbing"; +import { ErrorGate } from "./ErrorGate"; const router = createBrowserRouter( [ @@ -21,11 +23,19 @@ const router = createBrowserRouter( // return redirect("/home"); // } }, + errorElement: , }, { path: "/home", element: , - children: [{ path: "peers", element: }], + children: [ + { + path: "peers", + element: , + loader: peerStateLoader, + }, + ], + errorElement: , }, ], { basename: "/webui" }, diff --git a/webui/src/ErrorGate.tsx b/webui/src/ErrorGate.tsx new file mode 100644 index 00000000..61d003e4 --- /dev/null +++ b/webui/src/ErrorGate.tsx @@ -0,0 +1,7 @@ +import { useRouteError } from "react-router-dom"; + +export function ErrorGate() { + const error = useRouteError(); + console.error(error); + return
{JSON.stringify(error)}
; +} diff --git a/webui/src/PeerState.tsx b/webui/src/PeerState.tsx index 9d996b56..9d787e6f 100644 --- a/webui/src/PeerState.tsx +++ b/webui/src/PeerState.tsx @@ -1,5 +1,14 @@ import "./PeerState.scss"; +import { useLoaderData } from "react-router-dom"; +import { LoaderToType } from "./commonPlumbing"; +import { peerStateLoader } from "./PeerStatePlumbing"; export function PeerState() { - return
peerstate
; + const loaderData = useLoaderData() as LoaderToType; + + const availablePeers = loaderData?.availablePeers.map((p) => { + return
{p.uuid}
; + }); + + return
{availablePeers}
; } diff --git a/webui/src/PeerStatePlumbing.tsx b/webui/src/PeerStatePlumbing.tsx new file mode 100644 index 00000000..899ecf81 --- /dev/null +++ b/webui/src/PeerStatePlumbing.tsx @@ -0,0 +1,7 @@ +import { getAvailablePeers } from "./api/PeerState"; + +export async function peerStateLoader() { + return { + availablePeers: await getAvailablePeers(), + }; +} diff --git a/webui/src/api/PeerState.ts b/webui/src/api/PeerState.ts new file mode 100644 index 00000000..866e8956 --- /dev/null +++ b/webui/src/api/PeerState.ts @@ -0,0 +1,13 @@ +import { fetchJSON_throws } from "./utils"; +import { + AvailablePeerInfoToResp, + TAvailablePeerInfoArrTo, + TAvailablePeerInfoToResp, +} from "./dto"; + +export async function getAvailablePeers(): Promise { + return fetchJSON_throws< + TAvailablePeerInfoToResp, + typeof AvailablePeerInfoToResp + >("/objects-manage/available-peers", "GET", AvailablePeerInfoToResp); +} diff --git a/webui/src/api/dto.ts b/webui/src/api/dto.ts index b09a7791..b3eef3e4 100644 --- a/webui/src/api/dto.ts +++ b/webui/src/api/dto.ts @@ -33,3 +33,18 @@ export type TTokenTo = z.infer; export const TokenToResp = CreateAPIResponse(TokenTo); export type TTokenToResp = z.infer; + +export const AvailablePeerInfoTo = z.object({ + uuid: z.string(), + addr: z.string(), + port: z.number(), +}); +export type TAvailablePeerInfoTo = z.infer; + +export const AvailablePeerInfoArrTo = z.array(AvailablePeerInfoTo); +export type TAvailablePeerInfoArrTo = z.infer; + +export const AvailablePeerInfoToResp = CreateAPIResponse( + AvailablePeerInfoArrTo, +); +export type TAvailablePeerInfoToResp = z.infer; diff --git a/webui/src/api/utils.ts b/webui/src/api/utils.ts index 33d64c35..47bac190 100644 --- a/webui/src/api/utils.ts +++ b/webui/src/api/utils.ts @@ -1,5 +1,5 @@ import { jwtDecode } from "jwt-decode"; -import { isError } from "./dto"; +import { isError, TErrorTo } from "./dto"; declare const process: { env: { @@ -8,7 +8,9 @@ declare const process: { }; const apiRoot: string = - process.env.NODE_ENV == "production" ? "" : "http://localhost:8080"; + process.env.NODE_ENV == "production" + ? "" + : "http://localhost:1234/apiproxy"; let token: string | null; @@ -88,3 +90,21 @@ export async function fetchJSONAuth T }>( throw new Error("Not logged in"); } } + +function errorCheck( + fn: (...args: A) => R, +): (...args: A) => Promise, TErrorTo>> { + return async (...args: A): Promise, TErrorTo>> => { + const ret = await fn(...args); + if (isError(ret)) { + throw ret; + } else { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + return ret; + } + }; +} + +export const fetchJSON_throws = errorCheck(fetchJSON); +export const fetchJSONAuth_throws = errorCheck(fetchJSONAuth); diff --git a/webui/src/commonPlumbing.ts b/webui/src/commonPlumbing.ts new file mode 100644 index 00000000..632a7ca9 --- /dev/null +++ b/webui/src/commonPlumbing.ts @@ -0,0 +1,3 @@ +export type LoaderToType any> = + | Exclude>, Response> + | undefined;