mirror of
https://github.com/usatiuk/dhfs.git
synced 2025-10-29 04:57:48 +01:00
add peers through ui
This commit is contained in:
@@ -196,6 +196,7 @@ public class RemoteHostManager {
|
|||||||
|
|
||||||
public Collection<AvailablePeerInfo> getSeenButNotAddedHosts() {
|
public Collection<AvailablePeerInfo> getSeenButNotAddedHosts() {
|
||||||
return _seenHostsButNotAdded.entrySet().stream()
|
return _seenHostsButNotAdded.entrySet().stream()
|
||||||
|
.filter(e -> !persistentRemoteHostsService.existsHost(e.getKey()))
|
||||||
.map(e -> new AvailablePeerInfo(e.getKey().toString(), e.getValue().getAddr(), e.getValue().getPort()))
|
.map(e -> new AvailablePeerInfo(e.getKey().toString(), e.getValue().getAddr(), e.getValue().getPort()))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.usatiuk.dhfs.storage.objects.repository.distributed.webapi;
|
||||||
|
|
||||||
|
public record KnownPeerInfo(String uuid) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.usatiuk.dhfs.storage.objects.repository.distributed.webapi;
|
||||||
|
|
||||||
|
public record KnownPeerPut(String uuid) {
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.usatiuk.dhfs.storage.objects.repository.distributed.webapi;
|
package com.usatiuk.dhfs.storage.objects.repository.distributed.webapi;
|
||||||
|
|
||||||
import com.usatiuk.dhfs.storage.objects.repository.distributed.HostInfo;
|
|
||||||
import com.usatiuk.dhfs.storage.objects.repository.distributed.PersistentRemoteHostsService;
|
import com.usatiuk.dhfs.storage.objects.repository.distributed.PersistentRemoteHostsService;
|
||||||
import com.usatiuk.dhfs.storage.objects.repository.distributed.RemoteHostManager;
|
import com.usatiuk.dhfs.storage.objects.repository.distributed.RemoteHostManager;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
@@ -22,14 +21,14 @@ public class ManagementApi {
|
|||||||
|
|
||||||
@Path("known-peers")
|
@Path("known-peers")
|
||||||
@GET
|
@GET
|
||||||
public List<HostInfo> knownPeers() {
|
public List<KnownPeerInfo> knownPeers() {
|
||||||
return persistentRemoteHostsService.getHosts();
|
return persistentRemoteHostsService.getHosts().stream().map(h -> new KnownPeerInfo(h.getUuid().toString())).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("known-peers")
|
@Path("known-peers")
|
||||||
@PUT
|
@PUT
|
||||||
public void addPeer(String hostname) {
|
public void addPeer(KnownPeerPut knownPeerPut) {
|
||||||
remoteHostManager.addRemoteHost(UUID.fromString(hostname));
|
remoteHostManager.addRemoteHost(UUID.fromString(knownPeerPut.uuid()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("available-peers")
|
@Path("available-peers")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import "./App.scss";
|
import "./App.scss";
|
||||||
import { Home } from "./Home";
|
import { Home } from "./Home";
|
||||||
import { PeerState } from "./PeerState";
|
import { PeerState } from "./PeerState";
|
||||||
import { peerStateLoader } from "./PeerStatePlumbing";
|
import { peerStateAction, peerStateLoader } from "./PeerStatePlumbing";
|
||||||
import { ErrorGate } from "./ErrorGate";
|
import { ErrorGate } from "./ErrorGate";
|
||||||
|
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
@@ -33,6 +33,7 @@ const router = createBrowserRouter(
|
|||||||
path: "peers",
|
path: "peers",
|
||||||
element: <PeerState />,
|
element: <PeerState />,
|
||||||
loader: peerStateLoader,
|
loader: peerStateLoader,
|
||||||
|
action: peerStateAction,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
errorElement: <ErrorGate />,
|
errorElement: <ErrorGate />,
|
||||||
|
|||||||
13
webui/src/PeerAvailableCard.scss
Normal file
13
webui/src/PeerAvailableCard.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@import "./PeerCardCommon";
|
||||||
|
|
||||||
|
.peerAvailableCard {
|
||||||
|
@extend .peerCard;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.peerInfo {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
26
webui/src/PeerAvailableCard.tsx
Normal file
26
webui/src/PeerAvailableCard.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { TAvailablePeerInfoTo } from "./api/dto";
|
||||||
|
import { useFetcher } from "react-router-dom";
|
||||||
|
|
||||||
|
import "./PeerAvailableCard.scss";
|
||||||
|
|
||||||
|
export interface TPeerAvailableCardProps {
|
||||||
|
peerInfo: TAvailablePeerInfoTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PeerAvailableCard({ peerInfo }: TPeerAvailableCardProps) {
|
||||||
|
const fetcher = useFetcher();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="peerAvailableCard">
|
||||||
|
<div className={"peerInfo"}>
|
||||||
|
<span>UUID: </span>
|
||||||
|
<span>{peerInfo.uuid}</span>
|
||||||
|
</div>
|
||||||
|
<fetcher.Form method="put" action={"/home/peers"}>
|
||||||
|
<input name="intent" hidden={true} value={"add_peer"} />
|
||||||
|
<input name="uuid" hidden={true} value={peerInfo.uuid} />
|
||||||
|
<button type="submit">connect</button>
|
||||||
|
</fetcher.Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
webui/src/PeerCardCommon.scss
Normal file
7
webui/src/PeerCardCommon.scss
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@import "./common";
|
||||||
|
|
||||||
|
.peerCard {
|
||||||
|
@include border-shadow;
|
||||||
|
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
5
webui/src/PeerKnownCard.scss
Normal file
5
webui/src/PeerKnownCard.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@import "./PeerCardCommon";
|
||||||
|
|
||||||
|
.peerKnownCard {
|
||||||
|
@extend .peerCard;
|
||||||
|
}
|
||||||
11
webui/src/PeerKnownCard.tsx
Normal file
11
webui/src/PeerKnownCard.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { TKnownPeerInfoTo } from "./api/dto";
|
||||||
|
|
||||||
|
import "./PeerKnownCard.scss";
|
||||||
|
|
||||||
|
export interface TPeerKnownCardProps {
|
||||||
|
peerInfo: TKnownPeerInfoTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PeerKnownCard({ peerInfo }: TPeerKnownCardProps) {
|
||||||
|
return <div className="peerKnownCard">{peerInfo.uuid}</div>;
|
||||||
|
}
|
||||||
@@ -2,4 +2,11 @@
|
|||||||
|
|
||||||
#PeerState {
|
#PeerState {
|
||||||
@include home-view;
|
@include home-view;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,30 @@ import "./PeerState.scss";
|
|||||||
import { useLoaderData } from "react-router-dom";
|
import { useLoaderData } from "react-router-dom";
|
||||||
import { LoaderToType } from "./commonPlumbing";
|
import { LoaderToType } from "./commonPlumbing";
|
||||||
import { peerStateLoader } from "./PeerStatePlumbing";
|
import { peerStateLoader } from "./PeerStatePlumbing";
|
||||||
|
import { PeerAvailableCard } from "./PeerAvailableCard";
|
||||||
|
import { PeerKnownCard } from "./PeerKnownCard";
|
||||||
|
|
||||||
export function PeerState() {
|
export function PeerState() {
|
||||||
const loaderData = useLoaderData() as LoaderToType<typeof peerStateLoader>;
|
const loaderData = useLoaderData() as LoaderToType<typeof peerStateLoader>;
|
||||||
|
|
||||||
const availablePeers = loaderData?.availablePeers.map((p) => {
|
const knownPeers = loaderData.knownPeers.map((p) => (
|
||||||
return <div>{p.uuid}</div>;
|
<PeerKnownCard peerInfo={p} key={p.uuid} />
|
||||||
});
|
));
|
||||||
|
|
||||||
return <div id={"PeerState"}>{availablePeers}</div>;
|
const availablePeers = loaderData.availablePeers.map((p) => (
|
||||||
|
<PeerAvailableCard peerInfo={p} key={p.uuid} />
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={"PeerState"}>
|
||||||
|
<div>
|
||||||
|
<div>Known peers</div>
|
||||||
|
<div>{knownPeers}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>Available peers</div>
|
||||||
|
<div>{availablePeers}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
25
webui/src/PeerStatePlumbing.ts
Normal file
25
webui/src/PeerStatePlumbing.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
getAvailablePeers,
|
||||||
|
getKnownPeers,
|
||||||
|
putKnownPeer,
|
||||||
|
} from "./api/PeerState";
|
||||||
|
import { ActionFunctionArgs } from "react-router-dom";
|
||||||
|
|
||||||
|
export async function peerStateLoader() {
|
||||||
|
return {
|
||||||
|
availablePeers: await getAvailablePeers(),
|
||||||
|
knownPeers: await getKnownPeers(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PeerStateActionType = "add_peer" | unknown;
|
||||||
|
|
||||||
|
export async function peerStateAction({ request }: ActionFunctionArgs) {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const intent = formData.get("intent") as PeerStateActionType;
|
||||||
|
if (intent === "add_peer") {
|
||||||
|
return await putKnownPeer(formData.get("uuid") as string);
|
||||||
|
} else {
|
||||||
|
throw new Error("Malformed action: " + JSON.stringify(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { getAvailablePeers } from "./api/PeerState";
|
|
||||||
|
|
||||||
export async function peerStateLoader() {
|
|
||||||
return {
|
|
||||||
availablePeers: await getAvailablePeers(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import { fetchJSON_throws } from "./utils";
|
import { fetchJSON, fetchJSON_throws } from "./utils";
|
||||||
import {
|
import {
|
||||||
AvailablePeerInfoToResp,
|
AvailablePeerInfoToResp,
|
||||||
|
KnownPeerInfoToResp,
|
||||||
|
NoContentToResp,
|
||||||
TAvailablePeerInfoArrTo,
|
TAvailablePeerInfoArrTo,
|
||||||
TAvailablePeerInfoToResp,
|
TAvailablePeerInfoToResp,
|
||||||
|
TKnownPeerInfoArrTo,
|
||||||
|
TKnownPeerInfoToResp,
|
||||||
|
TNoContentToResp,
|
||||||
} from "./dto";
|
} from "./dto";
|
||||||
|
|
||||||
export async function getAvailablePeers(): Promise<TAvailablePeerInfoArrTo> {
|
export async function getAvailablePeers(): Promise<TAvailablePeerInfoArrTo> {
|
||||||
@@ -11,3 +16,17 @@ export async function getAvailablePeers(): Promise<TAvailablePeerInfoArrTo> {
|
|||||||
typeof AvailablePeerInfoToResp
|
typeof AvailablePeerInfoToResp
|
||||||
>("/objects-manage/available-peers", "GET", AvailablePeerInfoToResp);
|
>("/objects-manage/available-peers", "GET", AvailablePeerInfoToResp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getKnownPeers(): Promise<TKnownPeerInfoArrTo> {
|
||||||
|
return fetchJSON_throws<TKnownPeerInfoToResp, typeof KnownPeerInfoToResp>(
|
||||||
|
"/objects-manage/known-peers",
|
||||||
|
"GET",
|
||||||
|
KnownPeerInfoToResp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putKnownPeer(uuid: string): Promise<TNoContentToResp> {
|
||||||
|
return fetchJSON("/objects-manage/known-peers", "PUT", NoContentToResp, {
|
||||||
|
uuid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ export type TNoContentTo = z.infer<typeof NoContentTo>;
|
|||||||
export const NoContentToResp = CreateAPIResponse(NoContentTo);
|
export const NoContentToResp = CreateAPIResponse(NoContentTo);
|
||||||
export type TNoContentToResp = z.infer<typeof NoContentToResp>;
|
export type TNoContentToResp = z.infer<typeof NoContentToResp>;
|
||||||
|
|
||||||
|
// TokenRequest
|
||||||
export const TokenRequestTo = z.object({
|
export const TokenRequestTo = z.object({
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
});
|
});
|
||||||
export type TTokenRequestTo = z.infer<typeof TokenRequestTo>;
|
export type TTokenRequestTo = z.infer<typeof TokenRequestTo>;
|
||||||
|
|
||||||
|
// Token
|
||||||
export const TokenTo = z.object({
|
export const TokenTo = z.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
});
|
});
|
||||||
@@ -34,6 +36,7 @@ export type TTokenTo = z.infer<typeof TokenTo>;
|
|||||||
export const TokenToResp = CreateAPIResponse(TokenTo);
|
export const TokenToResp = CreateAPIResponse(TokenTo);
|
||||||
export type TTokenToResp = z.infer<typeof TokenToResp>;
|
export type TTokenToResp = z.infer<typeof TokenToResp>;
|
||||||
|
|
||||||
|
// AvailablePeerInfo
|
||||||
export const AvailablePeerInfoTo = z.object({
|
export const AvailablePeerInfoTo = z.object({
|
||||||
uuid: z.string(),
|
uuid: z.string(),
|
||||||
addr: z.string(),
|
addr: z.string(),
|
||||||
@@ -48,3 +51,19 @@ export const AvailablePeerInfoToResp = CreateAPIResponse(
|
|||||||
AvailablePeerInfoArrTo,
|
AvailablePeerInfoArrTo,
|
||||||
);
|
);
|
||||||
export type TAvailablePeerInfoToResp = z.infer<typeof AvailablePeerInfoToResp>;
|
export type TAvailablePeerInfoToResp = z.infer<typeof AvailablePeerInfoToResp>;
|
||||||
|
|
||||||
|
// KnownPeerInfo
|
||||||
|
export const KnownPeerInfoTo = z.object({
|
||||||
|
uuid: z.string(),
|
||||||
|
});
|
||||||
|
export type TKnownPeerInfoTo = z.infer<typeof KnownPeerInfoTo>;
|
||||||
|
|
||||||
|
export const KnownPeerInfoArrTo = z.array(KnownPeerInfoTo);
|
||||||
|
export type TKnownPeerInfoArrTo = z.infer<typeof KnownPeerInfoArrTo>;
|
||||||
|
|
||||||
|
export const KnownPeerInfoToResp = CreateAPIResponse(KnownPeerInfoArrTo);
|
||||||
|
export type TKnownPeerInfoToResp = z.infer<typeof KnownPeerInfoToResp>;
|
||||||
|
|
||||||
|
// KnownPeerPut
|
||||||
|
export const KnownPeerPutTo = z.object({ uuid: z.string() });
|
||||||
|
export type TKnownPeerPutTo = z.infer<typeof KnownPeerPutTo>;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ declare const process: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const apiRoot: string =
|
export const apiRoot: string =
|
||||||
process.env.NODE_ENV == "production"
|
process.env.NODE_ENV == "production"
|
||||||
? ""
|
? ""
|
||||||
: "http://localhost:1234/apiproxy";
|
: "http://localhost:1234/apiproxy";
|
||||||
@@ -91,9 +91,7 @@ export async function fetchJSONAuth<T, P extends { parse: (arg: string) => T }>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function errorCheck<A extends unknown[], R>(
|
function errorCheck<A extends unknown[], R>(fn: (...args: A) => R) {
|
||||||
fn: (...args: A) => R,
|
|
||||||
): (...args: A) => Promise<Exclude<Awaited<R>, TErrorTo>> {
|
|
||||||
return async (...args: A): Promise<Exclude<Awaited<R>, TErrorTo>> => {
|
return async (...args: A): Promise<Exclude<Awaited<R>, TErrorTo>> => {
|
||||||
const ret = await fn(...args);
|
const ret = await fn(...args);
|
||||||
if (isError(ret)) {
|
if (isError(ret)) {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export type LoaderToType<T extends (...args: any) => any> =
|
export type LoaderToType<T extends (...args: unknown[]) => unknown> = Awaited<
|
||||||
| Exclude<Awaited<ReturnType<T>>, Response>
|
ReturnType<T>
|
||||||
| undefined;
|
>;
|
||||||
|
|||||||
Reference in New Issue
Block a user