mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Update seeding policy schema
This commit is contained in:
+1
-1
@@ -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
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user