mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 11:20:36 +08:00
Add externalNodes to node detail view
Instead of showing the node id with a clipboard that copies a possible wrong port number, this commit adds the externalAddresses config to the node detail view, so node operators can define which externalAddresses should show up. Signed-off-by: Sebastian Martinez <me@sebastinez.dev> Co-authored-by: Rūdolfs Ošiņš <rudolfs@osins.org>
This commit is contained in:
co-authored by
Rūdolfs Ošiņš
parent
38fbddeb21
commit
1add6958cb
+36
-16
@@ -30,10 +30,10 @@ import type {
|
||||
Revision,
|
||||
Verdict,
|
||||
} from "./lib/project/patch.js";
|
||||
import type { RequestOptions, Method } from "./lib/fetcher.js";
|
||||
import type { RequestOptions } from "./lib/fetcher.js";
|
||||
import type { ZodSchema } from "zod";
|
||||
|
||||
import { array, literal, number, object, string, union } from "zod";
|
||||
import { z, array, boolean, literal, number, object, string, union } from "zod";
|
||||
|
||||
import * as project from "./lib/project.js";
|
||||
import * as session from "./lib/session.js";
|
||||
@@ -69,28 +69,48 @@ export type {
|
||||
Verdict,
|
||||
};
|
||||
|
||||
export interface Node {
|
||||
id: string;
|
||||
}
|
||||
export type Node = z.infer<typeof nodeSchema>;
|
||||
|
||||
const nodeSchema = object({
|
||||
id: string(),
|
||||
}) satisfies ZodSchema<Node>;
|
||||
config: object({
|
||||
alias: string(),
|
||||
peers: union([
|
||||
object({ type: literal("static") }),
|
||||
object({ type: literal("dynamic"), target: number() }),
|
||||
]),
|
||||
connect: array(string()),
|
||||
externalAddresses: array(string()),
|
||||
network: union([literal("main"), literal("test")]),
|
||||
relay: boolean(),
|
||||
limits: object({
|
||||
routingMaxSize: number(),
|
||||
routingMaxAge: number(),
|
||||
fetchConcurrency: number(),
|
||||
rate: object({
|
||||
inbound: object({
|
||||
fillRate: number(),
|
||||
capacity: number(),
|
||||
}),
|
||||
outbound: object({
|
||||
fillRate: number(),
|
||||
capacity: number(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
policy: union([literal("track"), literal("block")]),
|
||||
scope: union([literal("trusted"), literal("all")]),
|
||||
}).nullable(),
|
||||
state: union([literal("running"), literal("stopped")]),
|
||||
});
|
||||
|
||||
export interface NodeInfo {
|
||||
message: string;
|
||||
service: string;
|
||||
version: string;
|
||||
node: Node;
|
||||
path: string;
|
||||
links: { href: string; rel: string; type: Method }[];
|
||||
}
|
||||
export type NodeInfo = z.infer<typeof nodeInfoSchema>;
|
||||
|
||||
const nodeInfoSchema = object({
|
||||
message: string(),
|
||||
service: string(),
|
||||
version: string(),
|
||||
node: nodeSchema,
|
||||
node: nodeSchema.pick({ id: true }),
|
||||
path: string(),
|
||||
links: array(
|
||||
object({
|
||||
@@ -104,7 +124,7 @@ const nodeInfoSchema = object({
|
||||
]),
|
||||
}),
|
||||
),
|
||||
}) satisfies ZodSchema<NodeInfo>;
|
||||
});
|
||||
|
||||
export interface NodeStats {
|
||||
projects: { count: number };
|
||||
|
||||
@@ -74,7 +74,7 @@ export interface FetchParams {
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export type Method = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
|
||||
type Method = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
|
||||
|
||||
export class Fetcher {
|
||||
#baseUrl: BaseUrl;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { BaseUrl } from "@httpd-client";
|
||||
import type { ProjectActivity } from "@app/views/nodes/router";
|
||||
|
||||
import { config } from "@app/lib/config";
|
||||
import { isLocal, truncateId } from "@app/lib/utils";
|
||||
import { loadProjects } from "@app/views/nodes/router";
|
||||
|
||||
@@ -15,6 +14,7 @@
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let nid: string;
|
||||
export let externalAddresses: string[];
|
||||
export let projectCount: number;
|
||||
export let projectPageIndex: number;
|
||||
export let projects: ProjectActivity[] = [];
|
||||
@@ -107,11 +107,24 @@
|
||||
{hostname}
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="address">
|
||||
{truncateId(nid)}@{baseUrl.hostname}
|
||||
<Clipboard
|
||||
small
|
||||
text={`${nid}@${baseUrl.hostname}:${config.nodes.defaultNodePort}`} />
|
||||
<div>
|
||||
{#each externalAddresses as address}
|
||||
<!-- If there are externalAddresses this is probably a remote node -->
|
||||
<!-- in that case, we show all the defined externalAddresses as a listing -->
|
||||
<div class="address">
|
||||
{truncateId(nid)}@{address}
|
||||
<Clipboard small text={`${nid}@${address}`} />
|
||||
</div>
|
||||
{:else}
|
||||
<!-- else this is probably a local node -->
|
||||
<!-- So we show only the nid -->
|
||||
<div class="address layout-desktop">
|
||||
{nid}<Clipboard small text={nid} />
|
||||
</div>
|
||||
<div class="address layout-mobile">
|
||||
{truncateId(nid)}<Clipboard small text={nid} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="version">
|
||||
v{version}
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface NodesLoadedRoute {
|
||||
baseUrl: BaseUrl;
|
||||
projectPageIndex: number;
|
||||
version: string;
|
||||
externalAddresses: string[];
|
||||
nid: string;
|
||||
projects: ProjectActivity[];
|
||||
projectCount: number;
|
||||
@@ -96,8 +97,9 @@ export async function loadNodeRoute(
|
||||
const api = new HttpdClient(params.baseUrl);
|
||||
try {
|
||||
const projectPageIndex = 0;
|
||||
const [nodeInfo, { projects, total }] = await Promise.all([
|
||||
const [nodeInfo, node, { projects, total }] = await Promise.all([
|
||||
api.getNodeInfo(),
|
||||
api.getNode(),
|
||||
loadProjects(projectPageIndex, params.baseUrl),
|
||||
]);
|
||||
return {
|
||||
@@ -106,6 +108,7 @@ export async function loadNodeRoute(
|
||||
projectPageIndex: projectPageIndex + 1,
|
||||
baseUrl: params.baseUrl,
|
||||
nid: nodeInfo.node.id,
|
||||
externalAddresses: node.config?.externalAddresses ?? [],
|
||||
version: nodeInfo.version,
|
||||
projects: projects,
|
||||
projectCount: total,
|
||||
|
||||
@@ -63,7 +63,7 @@ test("copy to clipboard", async ({ page, browserName, context }) => {
|
||||
// Node address.
|
||||
{
|
||||
await page.locator(".clipboard").first().click();
|
||||
await expectClipboard(`${nodeRemote}@127.0.0.1:8776`, page);
|
||||
await expectClipboard(`${nodeRemote}`, page);
|
||||
}
|
||||
|
||||
// Clear the system clipboard contents so developers don't wonder why there's
|
||||
|
||||
+18
-3
@@ -6,14 +6,29 @@ import {
|
||||
test,
|
||||
} from "@tests/support/fixtures.js";
|
||||
|
||||
test("node metadata", async ({ page }) => {
|
||||
await page.goto("/nodes/radicle.local");
|
||||
test("node metadata", async ({ page, peerManager }) => {
|
||||
const peer = await peerManager.startPeer({
|
||||
name: "node-metadata-peer",
|
||||
});
|
||||
await peer.startHttpd();
|
||||
await peer.startNode({
|
||||
policy: "track",
|
||||
scope: "all",
|
||||
alias: "palm",
|
||||
externalAddresses: ["seed.radicle.test:8123"],
|
||||
});
|
||||
|
||||
await page.goto(peer.uiUrl());
|
||||
|
||||
await expect(
|
||||
page.locator(".header").getByText("radicle.local"),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText(shortNodeRemote)).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`${shortNodeRemote}@seed.radicle.test:8123`),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText("0.1.0-")).toBeVisible();
|
||||
await peer.stopHttpd();
|
||||
await peer.stopNode();
|
||||
});
|
||||
|
||||
test("node projects", async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user