API draft to manually set peer addresses

This commit is contained in:
2025-03-30 16:11:09 +02:00
parent 8b3c0a6f2c
commit 69eb96b10c
25 changed files with 2915 additions and 2399 deletions

4868
webui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,27 +10,31 @@
"browserslist": "> 0.5%, last 2 versions, not dead",
"dependencies": {
"jwt-decode": "^4.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.24.0",
"zod": "^3.23.8"
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router": "^7.4.1",
"react-router-dom": "^7.4.1",
"zod": "^3.24.2"
},
"@parcel/resolver-default": {
"packageExports": true
},
"devDependencies": {
"@parcel/transformer-sass": "^2.12.0",
"@parcel/transformer-typescript-tsc": "^2.12.0",
"@parcel/validator-typescript": "^2.12.0",
"@types/eslint": "^8.56.10",
"@parcel/transformer-sass": "^2.14.4",
"@parcel/transformer-typescript-tsc": "^2.14.4",
"@parcel/validator-typescript": "^2.14.4",
"@types/eslint": "^9.6.1",
"@types/eslint-config-prettier": "^6.11.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"eslint": "^8",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.34.3",
"parcel": "^2.12.0",
"prettier": "^3.3.2",
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"eslint": "^9",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-react": "^7.37.4",
"parcel": "^2.14.4",
"prettier": "^3.5.3",
"process": "^0.11.10",
"typescript": "^5.5.2"
"typescript": "^5.8.2"
}
}

View File

@@ -9,7 +9,6 @@ export interface TPeerAvailableCardProps {
export function PeerAvailableCard({ peerInfo }: TPeerAvailableCardProps) {
const fetcher = useFetcher();
return (
<div className="peerAvailableCard">
<div className={"peerInfo"}>
@@ -22,8 +21,8 @@ export function PeerAvailableCard({ peerInfo }: TPeerAvailableCardProps) {
action={"/home/peers"}
>
<button type="submit">connect</button>
<input name="intent" hidden={true} value={"add_peer"} />
<input name="uuid" hidden={true} value={peerInfo.uuid} />
<input name="intent" hidden={true} defaultValue={"add_peer"} />
<input name="uuid" hidden={true} defaultValue={peerInfo.uuid} />
</fetcher.Form>
</div>
);

View File

@@ -1,7 +1,9 @@
import { TKnownPeerInfoTo } from "./api/dto";
import "./PeerKnownCard.scss";
import { useFetcher } from "react-router-dom";
import { useFetcher, useLoaderData } from "react-router-dom";
import { LoaderToType } from "./commonPlumbing";
import { peerStateLoader } from "./PeerStatePlumbing";
export interface TPeerKnownCardProps {
peerInfo: TKnownPeerInfoTo;
@@ -9,12 +11,42 @@ export interface TPeerKnownCardProps {
export function PeerKnownCard({ peerInfo }: TPeerKnownCardProps) {
const fetcher = useFetcher();
const loaderData = useLoaderData() as LoaderToType<typeof peerStateLoader>;
const addr = loaderData.peerAddresses.find(
(item) => item.uuid === peerInfo.uuid,
);
return (
<div className="peerKnownCard">
<div className={"peerInfo"}>
<span>UUID: </span>
<span>{peerInfo.uuid}</span>
<div>
<span>UUID: </span>
<span>{peerInfo.uuid}</span>
</div>
<div>
<fetcher.Form
className="actions"
method="put"
action={"/home/peers"}
>
<input
name="intent"
hidden={true}
defaultValue={"save_addr"}
/>
<input
name="uuid"
hidden={true}
defaultValue={peerInfo.uuid}
/>
<input
name="address"
defaultValue={addr?.address || ""}
/>
<button type="submit">save</button>
</fetcher.Form>
</div>
</div>
<fetcher.Form
className="actions"
@@ -22,8 +54,12 @@ export function PeerKnownCard({ peerInfo }: TPeerKnownCardProps) {
action={"/home/peers"}
>
<button type="submit">remove</button>
<input name="intent" hidden={true} value={"remove_peer"} />
<input name="uuid" hidden={true} value={peerInfo.uuid} />
<input
name="intent"
hidden={true}
defaultValue={"remove_peer"}
/>
<input name="uuid" hidden={true} defaultValue={peerInfo.uuid} />
</fetcher.Form>
</div>
);

View File

@@ -1,7 +1,9 @@
import {
getAvailablePeers,
getKnownPeers,
getPeerAddresses,
putKnownPeer,
putPeerAddress,
removeKnownPeer,
} from "./api/PeerState";
import { ActionFunctionArgs } from "react-router-dom";
@@ -10,10 +12,15 @@ export async function peerStateLoader() {
return {
availablePeers: await getAvailablePeers(),
knownPeers: await getKnownPeers(),
peerAddresses: await getPeerAddresses(),
};
}
export type PeerStateActionType = "add_peer" | "remove_peer" | unknown;
export type PeerStateActionType =
| "add_peer"
| "remove_peer"
| "save_addr"
| unknown;
export async function peerStateAction({ request }: ActionFunctionArgs) {
const formData = await request.formData();
@@ -22,6 +29,11 @@ export async function peerStateAction({ request }: ActionFunctionArgs) {
return await putKnownPeer(formData.get("uuid") as string);
} else if (intent === "remove_peer") {
return await removeKnownPeer(formData.get("uuid") as string);
} else if (intent === "save_addr") {
return await putPeerAddress(
formData.get("uuid") as string,
formData.get("address") as string,
);
} else {
throw new Error("Malformed action: " + JSON.stringify(request));
}

View File

@@ -3,36 +3,73 @@ import {
AvailablePeerInfoToResp,
KnownPeerInfoToResp,
NoContentToResp,
PeerAddressInfoToResp,
TAvailablePeerInfoArrTo,
TAvailablePeerInfoToResp,
TKnownPeerInfoArrTo,
TKnownPeerInfoToResp,
TNoContentToResp,
TPeerAddressInfoArrTo,
TPeerAddressInfoToResp,
} from "./dto";
export async function getAvailablePeers(): Promise<TAvailablePeerInfoArrTo> {
return fetchJSON_throws<
TAvailablePeerInfoToResp,
typeof AvailablePeerInfoToResp
>("/objects-manage/available-peers", "GET", AvailablePeerInfoToResp);
>("/peers-manage/available-peers", "GET", AvailablePeerInfoToResp);
}
export async function getKnownPeers(): Promise<TKnownPeerInfoArrTo> {
return fetchJSON_throws<TKnownPeerInfoToResp, typeof KnownPeerInfoToResp>(
"/objects-manage/known-peers",
"/peers-manage/known-peers",
"GET",
KnownPeerInfoToResp,
);
}
export async function putKnownPeer(uuid: string): Promise<TNoContentToResp> {
return fetchJSON("/objects-manage/known-peers", "PUT", NoContentToResp, {
return fetchJSON("/peers-manage/known-peers", "PUT", NoContentToResp, {
uuid,
});
}
export async function removeKnownPeer(uuid: string): Promise<TNoContentToResp> {
return fetchJSON("/objects-manage/known-peers", "DELETE", NoContentToResp, {
return fetchJSON("/peers-manage/known-peers", "DELETE", NoContentToResp, {
uuid,
});
}
export async function getPeerAddresses(): Promise<TPeerAddressInfoArrTo> {
return fetchJSON_throws<
TPeerAddressInfoToResp,
typeof PeerAddressInfoToResp
>("/peers-addr-manage", "GET", PeerAddressInfoToResp);
}
export async function putPeerAddress(
uuid: string,
address: string,
): Promise<TNoContentToResp> {
return fetchJSON(
`/peers-addr-manage/${uuid}`,
"PUT",
NoContentToResp,
address,
);
}
export async function removePeerAddress(
uuid: string,
): Promise<TNoContentToResp> {
return fetchJSON(`/peers-addr-manage/${uuid}`, "DELETE", NoContentToResp);
}
export async function getPeerAddress(
uuid: string,
): Promise<TPeerAddressInfoToResp> {
return fetchJSON_throws<
TPeerAddressInfoToResp,
typeof PeerAddressInfoToResp
>(`/peers-addr-manage/${uuid}`, "GET", PeerAddressInfoToResp);
}

View File

@@ -64,6 +64,19 @@ export type TKnownPeerInfoArrTo = z.infer<typeof KnownPeerInfoArrTo>;
export const KnownPeerInfoToResp = CreateAPIResponse(KnownPeerInfoArrTo);
export type TKnownPeerInfoToResp = z.infer<typeof KnownPeerInfoToResp>;
// PeerAddressInfo
export const PeerAddressInfoTo = z.object({
uuid: z.string(),
address: z.string(),
});
export type TPeerAddressInfoTo = z.infer<typeof PeerAddressInfoTo>;
export const PeerAddressInfoArrTo = z.array(PeerAddressInfoTo);
export type TPeerAddressInfoArrTo = z.infer<typeof PeerAddressInfoArrTo>;
export const PeerAddressInfoToResp = CreateAPIResponse(PeerAddressInfoArrTo);
export type TPeerAddressInfoToResp = z.infer<typeof PeerAddressInfoToResp>;
// KnownPeerPut
export const KnownPeerPutTo = z.object({ uuid: z.string() });
export type TKnownPeerPutTo = z.infer<typeof KnownPeerPutTo>;

View File

@@ -44,14 +44,17 @@ export async function fetchJSON<T, P extends { parse: (arg: string) => T }>(
body?: string | Record<string, unknown> | File,
headers?: Record<string, string>,
): Promise<T> {
const reqBody = () =>
body instanceof File
const reqBody = () => {
if (typeof body === "string" || body instanceof String)
return body.toString();
return body instanceof File
? (() => {
const fd = new FormData();
fd.append("file", body);
return fd;
})()
: JSON.stringify(body);
};
const reqHeaders = () =>
body instanceof File

View File

@@ -6,7 +6,7 @@
],
"jsx": "react-jsx",
"target": "es2015",
"moduleResolution": "Node",
"moduleResolution": "bundler",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,