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:
Sebastian Martinez
2023-11-06 14:34:18 +01:00
committed by Rūdolfs Ošiņš
co-authored by Rūdolfs Ošiņš
parent 38fbddeb21
commit 1add6958cb
6 changed files with 79 additions and 28 deletions
+36 -16
View File
@@ -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 };
+1 -1
View File
@@ -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;
+19 -6
View File
@@ -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}
+4 -1
View File
@@ -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,
+1 -1
View File
@@ -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
View File
@@ -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 }) => {