Update seeding policy schema

This commit is contained in:
Sebastian Martinez
2024-06-28 13:59:35 +02:00
parent 37a88bbb83
commit e326f48cbf
13 changed files with 136 additions and 95 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"nodes": {
"fallbackPublicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path",
"apiVersion": "1.0.0",
"apiVersion": "1.1.0",
"defaultHttpdPort": 443,
"defaultLocalHttpdPort": 8080,
"defaultHttpdHostname": "seed.radicle.garden",
+34 -15
View File
@@ -12,8 +12,7 @@ import type {
SuccessResponse,
CodeLocation,
Range,
Policy,
Scope,
DefaultSeedingPolicy,
} from "./lib/shared.js";
import type { Comment, Embed, Reaction } from "./lib/project/comment.js";
import type {
@@ -48,7 +47,11 @@ import * as project from "./lib/project.js";
import * as profile from "./lib/profile.js";
import * as session from "./lib/session.js";
import { Fetcher } from "./lib/fetcher.js";
import { nodeConfigSchema, successResponseSchema } from "./lib/shared.js";
import {
nodeConfigSchema,
scopeSchema,
successResponseSchema,
} from "./lib/shared.js";
export type {
BaseUrl,
@@ -60,6 +63,7 @@ export type {
Commit,
CommitBlob,
CommitHeader,
DefaultSeedingPolicy,
Diff,
DiffBlob,
DiffContent,
@@ -74,7 +78,6 @@ export type {
Patch,
PatchState,
PatchUpdateAction,
Policy,
Project,
ProjectListQuery,
Range,
@@ -82,7 +85,6 @@ export type {
Remote,
Review,
Revision,
Scope,
TreeStats,
Tree,
Verdict,
@@ -120,15 +122,18 @@ const nodeInfoSchema = object({
),
});
export type NodeTracking = z.infer<typeof nodeTrackingSchema>;
export type NodePolicies = z.infer<typeof nodePoliciesSchema>;
const nodeTrackingSchema = array(
object({
id: string(),
scope: string(),
policy: string(),
}),
);
const nodePoliciesSchema = object({
rid: string(),
policy: union([
object({ policy: literal("block") }),
object({
policy: literal("allow"),
scope: scopeSchema,
}),
]),
});
export interface NodeStats {
repos: { total: number };
@@ -192,14 +197,28 @@ export class HttpdClient {
);
}
public async getTracking(options?: RequestOptions): Promise<NodeTracking> {
public async getPolicies(options?: RequestOptions): Promise<NodePolicies[]> {
return this.#fetcher.fetchOk(
{
method: "GET",
path: "node/policies/repos",
options,
},
nodeTrackingSchema,
array(nodePoliciesSchema),
);
}
public async getPoliciesById(
id: string,
options?: RequestOptions,
): Promise<NodePolicies["policy"][]> {
return this.#fetcher.fetchOk(
{
method: "GET",
path: `node/policies/repos/${id}`,
options,
},
array(nodePoliciesSchema.shape.policy),
);
}
+13 -8
View File
@@ -10,8 +10,17 @@ export const successResponseSchema = object({
success: literal(true),
}) satisfies ZodSchema<SuccessResponse>;
const policySchema = union([literal("allow"), literal("block")]);
const scopeSchema = union([literal("followed"), literal("all")]);
export const scopeSchema = union([literal("followed"), literal("all")]);
const defaultSeedingPolicySchema = union([
object({
default: literal("block"),
}),
object({
default: literal("allow"),
scope: scopeSchema,
}),
]);
export const nodeConfigSchema = object({
alias: string(),
@@ -61,14 +70,10 @@ export const nodeConfigSchema = object({
}),
}),
workers: number(),
seedingPolicy: object({
default: policySchema,
scope: scopeSchema.optional(),
}),
seedingPolicy: defaultSeedingPolicySchema,
});
export type Policy = z.infer<typeof policySchema>;
export type Scope = z.infer<typeof scopeSchema>;
export type DefaultSeedingPolicy = z.infer<typeof defaultSeedingPolicySchema>;
export const rangeSchema = union([
object({
+11 -9
View File
@@ -46,21 +46,23 @@
Your node is running and syncing with the network.
</div>
{#if node.scope && node.policy}
{#if node.seedingPolicy}
<div class="scope-policy">
<div style:display="flex">
Seeding Policy: <span style:margin-left="auto" class="txt-semibold">
{capitalize(node.policy)}
</span>
</div>
<div style:display="flex" style:margin-bottom="1rem">
Scope:
<span style:margin-left="auto" class="txt-semibold">
{capitalize(node.scope)}
{capitalize(node.seedingPolicy.default)}
</span>
</div>
{#if node.seedingPolicy.default === "allow"}
<div style:display="flex" style:margin-bottom="1rem">
Scope:
<span style:margin-left="auto" class="txt-semibold">
{capitalize(node.seedingPolicy.scope)}
</span>
</div>
{/if}
<ScopePolicyExplainer scope={node.scope} policy={node.policy} />
<ScopePolicyExplainer seedingPolicy={node.seedingPolicy} />
</div>
{/if}
<div class="label">
+18 -17
View File
@@ -1,10 +1,9 @@
<script lang="ts">
import type { Scope, Policy } from "@http-client";
import type { DefaultSeedingPolicy } from "@http-client";
import { capitalize } from "lodash";
export let scope: Scope | undefined = "all";
export let policy: Policy;
export let seedingPolicy: DefaultSeedingPolicy;
</script>
<style>
@@ -16,26 +15,28 @@
}
</style>
<div class="section">
Scope:
<span class="txt-bold">{capitalize(scope)}</span>
</div>
<div class="txt-missing">
{#if scope === "all"}
All changes in seeded repositories, made by any peer, will be synced.
{:else if scope === "followed"}
Only changes made by explicitly followed peers will be synced.
{/if}
</div>
{#if seedingPolicy.default === "allow"}
<div class="section">
Scope:
<span class="txt-bold">{capitalize(seedingPolicy.scope)}</span>
</div>
<div class="txt-missing">
{#if seedingPolicy.scope === "all"}
All changes in seeded repositories, made by any peer, will be synced.
{:else if seedingPolicy.scope === "followed"}
Only changes made by explicitly followed peers will be synced.
{/if}
</div>
{/if}
<div class="section">
Policy:
<span class="txt-bold">{capitalize(policy)}</span>
<span class="txt-bold">{capitalize(seedingPolicy.default)}</span>
</div>
<div class="txt-missing">
{#if policy === "allow"}
{#if seedingPolicy.default === "allow"}
All discovered repositories will get seeded.
{:else if policy === "block"}
{:else if seedingPolicy.default === "block"}
Only repositories marked as such will get seeded.
{/if}
</div>
+5 -9
View File
@@ -1,4 +1,4 @@
import type { Node, Policy, Scope } from "@http-client";
import type { DefaultSeedingPolicy, Node } from "@http-client";
import { get, writable } from "svelte/store";
import { withTimeout, Mutex, E_CANCELED, E_TIMEOUT } from "async-mutex";
@@ -17,8 +17,7 @@ export interface Session {
export interface HttpdNodeState {
id: Node["id"];
state: Node["state"];
policy: Policy | undefined;
scope: Scope | undefined;
seedingPolicy: DefaultSeedingPolicy | undefined;
}
export type HttpdState =
@@ -86,8 +85,7 @@ export async function authenticate(params: {
node: {
id,
state,
policy: config?.seedingPolicy.default,
scope: config?.seedingPolicy.scope,
seedingPolicy: config?.seedingPolicy,
},
});
return true;
@@ -116,8 +114,7 @@ export async function disconnect() {
node: {
id,
state,
policy: config?.seedingPolicy.default,
scope: config?.seedingPolicy.scope,
seedingPolicy: config?.seedingPolicy,
},
});
} catch (error) {
@@ -155,8 +152,7 @@ async function checkState() {
const node = {
id,
state,
policy: config?.seedingPolicy.default,
scope: config?.seedingPolicy.scope,
seedingPolicy: config?.seedingPolicy,
};
if (httpdState && httpdState.state === "authenticated") {
+9 -1
View File
@@ -1,4 +1,4 @@
import type { BaseUrl } from "@http-client";
import type { BaseUrl, DefaultSeedingPolicy } from "@http-client";
import md5 from "md5";
import bs58 from "bs58";
@@ -13,6 +13,14 @@ export function formatLocationHash(hash: string | null): number | null {
return null;
}
export function formatShortSeedingPolicy(
policy: DefaultSeedingPolicy | undefined,
) {
return policy?.default === "allow" && policy?.scope === "all"
? "permissive"
: "restrictive";
}
export function parseNodeId(
nid: string,
): { prefix: string; pubkey: string } | undefined {
+12 -11
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import type { Policy, Scope } from "@http-client";
import type { DefaultSeedingPolicy } from "@http-client";
import capitalize from "lodash/capitalize";
@@ -8,8 +8,7 @@
import IconButton from "@app/components/IconButton.svelte";
import Popover from "@app/components/Popover.svelte";
export let policy: Policy;
export let scope: Scope;
export let seedingPolicy: DefaultSeedingPolicy;
export let popoverPositionRight: string | undefined = undefined;
export let popoverPositionLeft: string | undefined = undefined;
</script>
@@ -34,15 +33,17 @@
<div class="container">
<span>
Seeding Policy: <span class="txt-semibold">
{capitalize(policy)}
Policy: <span class="txt-semibold">
{capitalize(seedingPolicy.default)}
</span>
</span>
<span class="separator" />
<span>
Scope:
<span class="txt-semibold">{capitalize(scope)}</span>
</span>
{#if seedingPolicy.default === "allow"}
<span class="separator" />
<span>
Scope:
<span class="txt-semibold">{capitalize(seedingPolicy.scope)}</span>
</span>
{/if}
<span style:color="var(--color-fill-gray)">
<Popover
{popoverPositionRight}
@@ -55,7 +56,7 @@
</IconButton>
<div slot="popover" class="popover">
<ScopePolicyExplainer {scope} {policy} />
<ScopePolicyExplainer {seedingPolicy} />
</div>
</Popover>
</span>
+11 -8
View File
@@ -1,11 +1,16 @@
<script lang="ts">
import type { BaseUrl, NodeStats, Policy, Scope } from "@http-client";
import type { BaseUrl, DefaultSeedingPolicy, NodeStats } from "@http-client";
import { capitalize } from "lodash";
import * as router from "@app/lib/router";
import { api, httpdStore } from "@app/lib/httpd";
import { baseUrlToString, isLocal, truncateId } from "@app/lib/utils";
import {
baseUrlToString,
formatShortSeedingPolicy,
isLocal,
truncateId,
} from "@app/lib/utils";
import { fetchProjectInfos } from "@app/components/ProjectCard";
import { handleError } from "@app/views/nodes/error";
import { isDelegate } from "@app/lib/roles";
@@ -24,11 +29,9 @@
export let stats: NodeStats;
export let externalAddresses: string[];
export let version: string;
export let policy: Policy | undefined = undefined;
export let scope: Scope | undefined = undefined;
export let seedingPolicy: DefaultSeedingPolicy | undefined = undefined;
$: shortScope =
scope === "all" && policy === "allow" ? "permissive" : "restrictive";
$: shortScope = formatShortSeedingPolicy(seedingPolicy);
$: hostname = isLocal(baseUrl.hostname) ? "Local Node" : baseUrl.hostname;
$: session =
$httpdStore.state === "authenticated" && isLocal(api.baseUrl.hostname)
@@ -157,7 +160,7 @@
{isLocal(baseUrl.hostname) ? "Seeded" : "Pinned"} repositories
</div>
<div class="seeding-policy">
{#if policy && scope}
{#if seedingPolicy}
<span class="txt-bold">Seeding Policy:</span>
{capitalize(shortScope)}
<div class="global-hide-on-mobile-down">
@@ -168,7 +171,7 @@
<IconButton slot="toggle" let:toggle on:click={toggle}>
<IconSmall name="help" />
</IconButton>
<ScopePolicyExplainer slot="popover" {scope} {policy} />
<ScopePolicyExplainer slot="popover" {seedingPolicy} />
</Popover>
</div>
{/if}
+3 -5
View File
@@ -1,4 +1,4 @@
import type { BaseUrl, NodeStats, Policy, Scope } from "@http-client";
import type { BaseUrl, DefaultSeedingPolicy, NodeStats } from "@http-client";
import type { ErrorRoute, NotFoundRoute } from "@app/lib/router/definitions";
import config from "virtual:config";
@@ -26,8 +26,7 @@ export interface NodesLoadedRoute {
externalAddresses: string[];
nid: string;
stats: NodeStats;
policy?: Policy;
scope?: Scope;
seedingPolicy?: DefaultSeedingPolicy;
};
}
@@ -56,8 +55,7 @@ export async function loadNodeRoute(
stats,
externalAddresses: node.config?.externalAddresses ?? [],
version: node.version,
policy: node.config?.seedingPolicy.default,
scope: node.config?.seedingPolicy.scope,
seedingPolicy: node.config?.seedingPolicy,
},
};
} catch (error) {
@@ -2,7 +2,7 @@
import type { BaseUrl, Node, Project } from "@http-client";
import { capitalize } from "lodash";
import { isLocal } from "@app/lib/utils";
import { formatShortSeedingPolicy, isLocal } from "@app/lib/utils";
import IconButton from "@app/components/IconButton.svelte";
import IconSmall from "@app/components/IconSmall.svelte";
@@ -17,11 +17,7 @@
let expandedNode = false;
$: shortSeedingPolicy =
node.config?.seedingPolicy.scope === "all" &&
node.config?.seedingPolicy.default === "allow"
? "permissive"
: "restrictive";
$: shortSeedingPolicy = formatShortSeedingPolicy(node.config?.seedingPolicy);
</script>
<style>
@@ -116,9 +112,7 @@
</div>
{#if expandedNode && node.config?.seedingPolicy}
<div style:padding-left="2.3rem">
<ScopePolicyExplainer
scope={node.config.seedingPolicy.scope}
policy={node.config.seedingPolicy.default} />
<ScopePolicyExplainer seedingPolicy={node.config.seedingPolicy} />
</div>
{/if}
</div>
+2 -2
View File
@@ -259,8 +259,8 @@ async function isLocalNodeSeeding(route: ProjectRoute): Promise<boolean> {
return false;
}
try {
const tracking = await httpd.api.getTracking();
return tracking.some(({ id }) => id === route.project);
const policies = await httpd.api.getPolicies();
return policies.some(({ rid }) => rid === route.project);
} catch (error) {
if (error instanceof ResponseError && error.status === 404) {
return false;
+14
View File
@@ -9,6 +9,20 @@ describe("Format functions", () => {
expect(utils.formatLocationHash(hash)).toEqual(expected);
});
test.each([
{
input: { default: "allow", scope: "all" },
expected: "permissive",
} as const,
{
input: { default: "allow", scope: "followed" },
expected: "restrictive",
} as const,
{ input: { default: "block" }, expected: "restrictive" } as const,
])("formatShortSeedingPolicy $input => $expected", ({ input, expected }) => {
expect(utils.formatShortSeedingPolicy(input)).toEqual(expected);
});
test.each([
{
id: "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT",