diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index cdacb42d8..0889141d8 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -3,13 +3,7 @@ "fallbackPublicExplorer": "FALLBACK_PUBLIC_EXPLORER", "apiVersion": "API_VERSION", "defaultHttpdPort": "DEFAULT_HTTPD_PORT", - "defaultHttpdHostname": "DEFAULT_HTTPD_HOSTNAME", - "defaultHttpdScheme": "DEFAULT_HTTPD_SCHEME", - "defaultNodePort": "DEFAULT_NODE_PORT", - "pinned": { - "__name": "PINNED_NODES", - "__format": "json" - } + "defaultHttpdScheme": "DEFAULT_HTTPD_SCHEME" }, "source": { "commitsPerPage": "COMMITS_PER_PAGE" diff --git a/config/default.json b/config/default.json index 9fd2c5e3b..de63e09be 100644 --- a/config/default.json +++ b/config/default.json @@ -3,18 +3,7 @@ "fallbackPublicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path", "apiVersion": "4.0.0", "defaultHttpdPort": 443, - "defaultHttpdHostname": "seed.radicle.garden", - "defaultHttpdScheme": "https", - "defaultNodePort": 8776, - "pinned": [ - { - "baseUrl": { - "hostname": "seed.radicle.xyz", - "port": 443, - "scheme": "https" - } - } - ] + "defaultHttpdScheme": "https" }, "source": { "commitsPerPage": 30 diff --git a/config/test.json b/config/test.json index c88f25948..82f33342c 100644 --- a/config/test.json +++ b/config/test.json @@ -1,16 +1,11 @@ { "nodes": { "defaultHttpdPort": 8081, - "defaultHttpdHostname": "127.0.0.1", - "defaultHttpdScheme": "http", - "pinned": [ - { - "baseUrl": { - "hostname": "127.0.0.1", - "port": 8081, - "scheme": "http" - } - } - ] + "defaultHttpdScheme": "http" + }, + "fallbackPreferredSeed": { + "hostname": "127.0.0.1", + "port": 8081, + "scheme": "http" } } diff --git a/module.d.ts b/module.d.ts index 6f273d69c..efe297db7 100644 --- a/module.d.ts +++ b/module.d.ts @@ -4,11 +4,8 @@ declare module "virtual:*" { apiVersion: string; fallbackPublicExplorer: string; defaultHttpdPort: number; - defaultHttpdHostname: string; defaultLocalHttpdPort: number; - defaultNodePort: number; defaultHttpdScheme: string; - pinned: { baseUrl: BaseUrl }[]; }; source: { commitsPerPage: number; diff --git a/public/images/default-seed-avatar.png b/public/images/default-seed-avatar.png new file mode 100644 index 000000000..bfe8fc51b Binary files /dev/null and b/public/images/default-seed-avatar.png differ diff --git a/public/images/default-seed-header.png b/public/images/default-seed-header.png new file mode 100644 index 000000000..929b2eb3e Binary files /dev/null and b/public/images/default-seed-header.png differ diff --git a/radicle-httpd/src/api/v1/node.rs b/radicle-httpd/src/api/v1/node.rs index 26c271602..20a064d96 100644 --- a/radicle-httpd/src/api/v1/node.rs +++ b/radicle-httpd/src/api/v1/node.rs @@ -15,7 +15,7 @@ use radicle::Node; use crate::api::error::Error; use crate::api::Context; -use crate::axum_extra::Path; +use crate::axum_extra::{cached_response, Path}; pub fn router(ctx: Context) -> Router { Router::new() @@ -84,13 +84,15 @@ async fn node_handler(State(ctx): State) -> impl IntoResponse { } }; - Ok::<_, Error>(Json(Response::new( + let response = Response::new( node_id, agent, config, node_state.to_string(), ctx.profile.config.web.clone(), - ))) + ); + + Ok::<_, Error>(cached_response(response, 600)) } /// Return stored information about other nodes. diff --git a/src/App.svelte b/src/App.svelte index 0859beff5..4ebcb0cd3 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -12,7 +12,6 @@ import Commit from "@app/views/projects/Commit.svelte"; import History from "@app/views/projects/History.svelte"; - import Home from "@app/views/home/Index.svelte"; import Issue from "@app/views/projects/Issue.svelte"; import Issues from "@app/views/projects/Issues.svelte"; import Nodes from "@app/views/nodes/View.svelte"; @@ -59,8 +58,6 @@
-{:else if $activeRouteStore.resource === "home"} - {:else if $activeRouteStore.resource === "nodes"} {:else if $activeRouteStore.resource === "project.source"} diff --git a/src/App/Header.svelte b/src/App/Header.svelte deleted file mode 100644 index 1831df5da..000000000 --- a/src/App/Header.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - - -
- - - - -
diff --git a/src/App/Header/Breadcrumbs.svelte b/src/App/Header/Breadcrumbs.svelte deleted file mode 100644 index e79596775..000000000 --- a/src/App/Header/Breadcrumbs.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - - -{#if $activeRouteStore.resource === "booting" || $activeRouteStore.resource === "home" || $activeRouteStore.resource === "error" || $activeRouteStore.resource === "notFound"} - -{:else if $activeRouteStore.resource === "nodes"} - -{:else if $activeRouteStore.resource === "project.source" || $activeRouteStore.resource === "project.history" || $activeRouteStore.resource === "project.commit" || $activeRouteStore.resource === "project.issues" || $activeRouteStore.resource === "project.issue" || $activeRouteStore.resource === "project.patches" || $activeRouteStore.resource === "project.patch"} - -{:else} - {utils.unreachable($activeRouteStore)} -{/if} diff --git a/src/App/Header/Breadcrumbs/NodeSegment.svelte b/src/App/Header/Breadcrumbs/NodeSegment.svelte deleted file mode 100644 index da955afa0..000000000 --- a/src/App/Header/Breadcrumbs/NodeSegment.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - {baseUrl.hostname} - - diff --git a/src/App/Header/Breadcrumbs/ProjectSegment.svelte b/src/App/Header/Breadcrumbs/ProjectSegment.svelte deleted file mode 100644 index 978bf80ef..000000000 --- a/src/App/Header/Breadcrumbs/ProjectSegment.svelte +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - -
- {#if activeRoute.params.project.visibility?.type === "private"} - - {/if} - {activeRoute.params.project.name} -
- -
- - - {#if activeRoute.resource === "project.history"} - - - Commits - - {:else if activeRoute.resource === "project.commit"} - - - Commits - - {:else if activeRoute.resource === "project.issue" || activeRoute.resource === "project.issues"} - - - Issues - - {:else if activeRoute.resource === "project.patch" || activeRoute.resource === "project.patches"} - - - Patches - - {:else if activeRoute.resource === "project.source"} - {#if activeRoute.params.path !== "/"} - - - {/if} - {:else} - {unreachable(activeRoute)} - {/if} - - -{#if activeRoute.resource === "project.commit"} - - -
- {activeRoute.params.commit.commit.id} -
-
- {formatObjectId(activeRoute.params.commit.commit.id)} -
-
-{:else if activeRoute.resource === "project.issue"} - - -
- {activeRoute.params.issue.id} -
-
- {formatObjectId(activeRoute.params.issue.id)} -
-
-{:else if activeRoute.resource === "project.patch"} - - -
- {activeRoute.params.patch.id} -
-
- {formatObjectId(activeRoute.params.patch.id)} -
-
-{/if} diff --git a/src/App/AppLayout.svelte b/src/App/Layout.svelte similarity index 61% rename from src/App/AppLayout.svelte rename to src/App/Layout.svelte index 1e980ad8c..7a71dc89e 100644 --- a/src/App/AppLayout.svelte +++ b/src/App/Layout.svelte @@ -1,7 +1,7 @@ +
+ Policy: + {capitalize(policy)} +
+
+ {#if policy === "allow"} + All discovered repositories will be seeded. + {:else if policy === "block"} + Only selected repositories will be seeded. + {/if} +
+ {#if policy === "allow"} -
+
Scope: {capitalize(scope)}
-
+
{#if scope === "all"} All changes in seeded repositories, made by any peer, will be synced. {:else if scope === "followed"} @@ -30,15 +42,3 @@ {/if}
{/if} - -
- Policy: - {capitalize(policy)} -
-
- {#if policy === "allow"} - All discovered repositories will get seeded. - {:else if policy === "block"} - Only repositories marked as such will get seeded. - {/if} -
diff --git a/src/lib/router.ts b/src/lib/router.ts index e1357960f..9d3ca624a 100644 --- a/src/lib/router.ts +++ b/src/lib/router.ts @@ -107,7 +107,7 @@ async function navigate( function setTitle(loadedRoute: LoadedRoute) { const title: string[] = []; - if (loadedRoute.resource === "booting" || loadedRoute.resource === "home") { + if (loadedRoute.resource === "booting") { title.push("Radicle"); } else if (loadedRoute.resource === "error") { title.push("Error"); @@ -194,11 +194,15 @@ function urlToRoute(url: URL): Route | null { params: { baseUrl, projectPageIndex: 0 }, }; } + } else { + return { + resource: "nodes", + params: undefined, + }; } - return null; } case "": { - return { resource: "home" }; + return { resource: "nodes", params: undefined }; } default: { return null; @@ -207,10 +211,12 @@ function urlToRoute(url: URL): Route | null { } export function routeToPath(route: Route): string { - if (route.resource === "home") { - return "/"; - } else if (route.resource === "nodes") { - return nodePath(route.params.baseUrl); + if (route.resource === "nodes") { + if (route.params === undefined) { + return "/"; + } else { + return nodePath(route.params.baseUrl); + } } else if ( route.resource === "project.source" || route.resource === "project.history" || diff --git a/src/lib/router/definitions.ts b/src/lib/router/definitions.ts index cb7be78c3..d74296b05 100644 --- a/src/lib/router/definitions.ts +++ b/src/lib/router/definitions.ts @@ -2,7 +2,6 @@ import type { ResponseError, ResponseParseError, } from "@http-client/lib/fetcher"; -import type { HomeRoute, HomeLoadedRoute } from "@app/views/home/router"; import type { ProjectLoadedRoute, ProjectRoute, @@ -37,7 +36,6 @@ export interface ErrorRoute { export type Route = | BootingRoute - | HomeRoute | ErrorRoute | NotFoundRoute | ProjectRoute @@ -45,7 +43,6 @@ export type Route = export type LoadedRoute = | BootingRoute - | HomeLoadedRoute | ErrorRoute | NotFoundRoute | ProjectLoadedRoute @@ -57,8 +54,6 @@ export async function loadRoute( ): Promise { if (route.resource === "nodes") { return await loadNodeRoute(route.params); - } else if (route.resource === "home") { - return { resource: "home" }; } else if ( route.resource === "project.source" || route.resource === "project.history" || diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 80445b14b..6e5f06cbb 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -8,11 +8,6 @@ export async function toClipboard(text: string): Promise { await navigator.clipboard.writeText(text); } -// Removes the first and last character which are always `/`. -export function formatUserAgent(agent: string): string { - return agent.slice(1, -1); -} - export function formatInlineTitle(input: string): string { return input.replaceAll(/`([^`]+)`/g, "$1"); } diff --git a/src/views/NotFound.svelte b/src/views/NotFound.svelte index bbffaa6f3..5b60841ff 100644 --- a/src/views/NotFound.svelte +++ b/src/views/NotFound.svelte @@ -1,5 +1,5 @@ - - - - -
- - -
- Explore - -
-
- - Pinned repositories on your selected seed node - - -
- {#if preferredSeedProjects instanceof Error} - - {:else} -
No pinned repositories
-
- The selected seed node doesn't have any pinned repositories. -
- {/if} -
-
-
- {#if preferredSeedProjects && !(preferredSeedProjects instanceof Error)} - {#each preferredSeedProjects as projectInfo} - - {/each} - {/if} -
-
-
-
diff --git a/src/views/home/components/HomepageSection.svelte b/src/views/home/components/HomepageSection.svelte deleted file mode 100644 index b26f0d9dd..000000000 --- a/src/views/home/components/HomepageSection.svelte +++ /dev/null @@ -1,89 +0,0 @@ - - - - -
-
-
- -

{title}

-
-
- -
-
-
- -
-
- - {#if loading} -
-
- -
-
- {:else if empty} -
-
- -
-
- {:else} -
- -
- {/if} -
diff --git a/src/views/home/error.ts b/src/views/home/error.ts deleted file mode 100644 index 391154143..000000000 --- a/src/views/home/error.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ResponseParseError, ResponseError } from "@http-client/lib/fetcher"; - -export function handleError( - error: Error | ResponseParseError | ResponseError, - url: string, -): { - error: Error | ResponseParseError | ResponseError; - title: string; - description: string; -} { - if (error instanceof ResponseParseError) { - return { - error, - title: "Could not parse the request", - description: error.description, - }; - } else if (error instanceof ResponseError) { - return { - error, - title: "Could not load the repositories", - description: `You're trying to fetch repositories from a node that is not reachable, make sure the address ${url} is correct and the right ports are exposed if its your node.`, - }; - } else if ( - error instanceof TypeError && - error.message === "Failed to fetch" - ) { - return { - error, - title: "Resource not found", - description: `You're trying to fetch a resource that is not available. Check that the ids is correct, and try to run $ rad sync.`, - }; - } else { - return { - error, - title: "Could not load this view", - description: - "You stumbled on an unknown error, we aren't exactly sure what happened.", - }; - } -} diff --git a/src/views/home/router.ts b/src/views/home/router.ts deleted file mode 100644 index 5a1d0d321..000000000 --- a/src/views/home/router.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface HomeRoute { - resource: "home"; -} - -export interface HomeLoadedRoute extends HomeRoute {} diff --git a/src/views/nodes/NodeAddress.svelte b/src/views/nodes/NodeAddress.svelte new file mode 100644 index 000000000..164b04ed2 --- /dev/null +++ b/src/views/nodes/NodeAddress.svelte @@ -0,0 +1,23 @@ + + +
+ + + {#if node.config?.externalAddresses.length} + {truncateId(node.id)}@{node.config?.externalAddresses[0]} + {:else} + {truncateId(node.id)} + {/if} + +
diff --git a/src/views/nodes/PolicyExplainer.svelte b/src/views/nodes/PolicyExplainer.svelte new file mode 100644 index 000000000..597a8fd4f --- /dev/null +++ b/src/views/nodes/PolicyExplainer.svelte @@ -0,0 +1,55 @@ + + + + +
+
+
+ Seeding Policy +
+
+
+ {capitalize(shortScope)} +
+ (expandedNode = !expandedNode)}> + + +
+
+ {#if expandedNode && seedingPolicy} +
+ +
+ {/if} +
diff --git a/src/views/home/components/PreferredSeedDropdown.svelte b/src/views/nodes/PreferredSeedDropdown.svelte similarity index 81% rename from src/views/home/components/PreferredSeedDropdown.svelte rename to src/views/nodes/PreferredSeedDropdown.svelte index 5b2510e96..bcc762b80 100644 --- a/src/views/home/components/PreferredSeedDropdown.svelte +++ b/src/views/nodes/PreferredSeedDropdown.svelte @@ -1,15 +1,19 @@ - - - -
- - Policy: - {capitalize(seedingPolicy.default)} - - - {#if seedingPolicy.default === "allow"} - - - Scope: - {capitalize(seedingPolicy.scope)} - - {/if} - - - - - - - - -
- -
-
-
-
diff --git a/src/views/nodes/Seeding.svelte b/src/views/nodes/Seeding.svelte new file mode 100644 index 000000000..2a2a0c584 --- /dev/null +++ b/src/views/nodes/Seeding.svelte @@ -0,0 +1,28 @@ + + + + +
+ Seeding +
+ {formatter.format(count).toLowerCase()} + +
+
diff --git a/src/views/nodes/UserAgent.svelte b/src/views/nodes/UserAgent.svelte new file mode 100644 index 000000000..9123bd47c --- /dev/null +++ b/src/views/nodes/UserAgent.svelte @@ -0,0 +1,43 @@ + + + + +
+
User Agent
+ +
+
{agent}
+
+
+
diff --git a/src/views/nodes/View.svelte b/src/views/nodes/View.svelte index 1932b5ef7..4605e8c2c 100644 --- a/src/views/nodes/View.svelte +++ b/src/views/nodes/View.svelte @@ -1,51 +1,117 @@ - -
-
-
-
{baseUrl.hostname}
-
-
- {#each externalAddresses as address} - - - - {:else} - - -
- -
-
- -
- {/each} -
- -
- {formatUserAgent(agent)} -
-
-
-
+ -
-
Pinned repositories
-
- {#if seedingPolicy} - Seeding Policy: - {capitalize(shortScope)} -
- - - - - - -
- {/if} +
+
+ - -
- {#await fetchProjectInfos( baseUrl, { show: "pinned", perPage: stats.repos.total }, )} -
- -
- {:then projectInfos} - {#if projectInfos.length > 0} -
- {#each projectInfos as projectInfo} - - {/each} -
- {:else} -
-
No pinned repositories
-
- This node doesn't have any pinned repositories. -
-
- {/if} - {:catch error} - {router.push(handleError(error, baseUrlToString(baseUrl)))} - {/await} -
+
- + +
+ +
diff --git a/src/views/nodes/router.ts b/src/views/nodes/router.ts index 5ca558569..73dedc820 100644 --- a/src/views/nodes/router.ts +++ b/src/views/nodes/router.ts @@ -1,4 +1,4 @@ -import type { BaseUrl, DefaultSeedingPolicy, NodeStats } from "@http-client"; +import type { BaseUrl, Node, NodeStats } from "@http-client"; import type { ErrorRoute, NotFoundRoute } from "@app/lib/router/definitions"; import config from "virtual:config"; @@ -7,11 +7,15 @@ import { ResponseError, ResponseParseError } from "@http-client/lib/fetcher"; import { baseUrlToString, isLocal } from "@app/lib/utils"; import { handleError } from "@app/views/nodes/error"; import { unreachableError } from "@app/views/projects/error"; +import { preferredSeeds } from "@app/lib/seeds"; +import { get } from "svelte/store"; -export interface NodesRouteParams { - baseUrl: BaseUrl; - projectPageIndex: number; -} +export type NodesRouteParams = + | { + baseUrl: BaseUrl; + projectPageIndex: number; + } + | undefined; export interface NodesRoute { resource: "nodes"; @@ -22,11 +26,8 @@ export interface NodesLoadedRoute { resource: "nodes"; params: { baseUrl: BaseUrl; - agent: string; - externalAddresses: string[]; - nid: string; stats: NodeStats; - seedingPolicy?: DefaultSeedingPolicy; + node: Node; }; } @@ -43,10 +44,17 @@ export function nodePath(baseUrl: BaseUrl) { export async function loadNodeRoute( params: NodesRouteParams, ): Promise { - if ( - import.meta.env.PROD && - isLocal(`${params.baseUrl.hostname}:${params.baseUrl.port}`) - ) { + let baseUrl: BaseUrl; + + if (params) { + baseUrl = params.baseUrl; + } else { + baseUrl = get(preferredSeeds).selected; + } + + const api = new HttpdClient(baseUrl); + + if (import.meta.env.PROD && isLocal(`${baseUrl.hostname}:${baseUrl.port}`)) { return { resource: "error", params: { @@ -56,19 +64,16 @@ export async function loadNodeRoute( }, }; } - const api = new HttpdClient(params.baseUrl); + try { const [node, stats] = await Promise.all([api.getNode(), api.getStats()]); return { resource: "nodes", params: { - baseUrl: params.baseUrl, - nid: node.id, + baseUrl, + node, stats, - externalAddresses: node.config?.externalAddresses ?? [], - seedingPolicy: node.config?.seedingPolicy, - agent: node.agent, }, }; } catch (error) { diff --git a/src/views/projects/Commit.svelte b/src/views/projects/Commit.svelte index b3fdc7ad5..fb3d560e0 100644 --- a/src/views/projects/Commit.svelte +++ b/src/views/projects/Commit.svelte @@ -1,20 +1,24 @@ @@ -45,7 +49,27 @@ } - + + + + + Commits + + + +
+ {commit.commit.id} +
+
+ {formatObjectId(commit.commit.id)} +
+
+
diff --git a/src/views/projects/History.svelte b/src/views/projects/History.svelte index a8bc47fa2..3e92055b2 100644 --- a/src/views/projects/History.svelte +++ b/src/views/projects/History.svelte @@ -4,8 +4,8 @@ CommitHeader, Project, Remote, - Tree, SeedingPolicy, + Tree, } from "@http-client"; import type { ProjectRoute } from "./router"; @@ -19,9 +19,11 @@ import ErrorMessage from "@app/components/ErrorMessage.svelte"; import Header from "./Source/Header.svelte"; import Layout from "./Layout.svelte"; + import Link from "@app/components/Link.svelte"; import List from "@app/components/List.svelte"; import Loading from "@app/components/Loading.svelte"; import ProjectNameHeader from "./Source/ProjectNameHeader.svelte"; + import Separator from "./Separator.svelte"; export let baseUrl: BaseUrl; export let seedingPolicy: SeedingPolicy; @@ -32,6 +34,7 @@ export let project: Project; export let revision: string | undefined; export let tree: Tree; + export let nodeAvatarUrl: string | undefined; const api = new HttpdClient(baseUrl); @@ -89,7 +92,18 @@ } - + + + + + Commits + +
diff --git a/src/views/projects/Issue.svelte b/src/views/projects/Issue.svelte index 173793556..690a2f246 100644 --- a/src/views/projects/Issue.svelte +++ b/src/views/projects/Issue.svelte @@ -15,9 +15,11 @@ import InlineTitle from "@app/views/projects/components/InlineTitle.svelte"; import Labels from "@app/views/projects/Cob/Labels.svelte"; import Layout from "./Layout.svelte"; + import Link from "@app/components/Link.svelte"; import Markdown from "@app/components/Markdown.svelte"; import NodeId from "@app/components/NodeId.svelte"; import Reactions from "@app/components/Reactions.svelte"; + import Separator from "./Separator.svelte"; import Share from "@app/views/projects/Share.svelte"; import ThreadComponent from "@app/components/Thread.svelte"; @@ -26,6 +28,7 @@ export let issue: Issue; export let project: Project; export let rawPath: (commit?: string) => string; + export let nodeAvatarUrl: string | undefined; $: uniqueEmbeds = uniqBy( issue.discussion.flatMap(comment => comment.embeds), @@ -110,6 +113,11 @@ align-items: center; margin-left: -0.25rem; } + .id { + font-size: var(--font-size-small); + font-family: var(--font-family-monospace); + font-weight: var(--font-weight-semibold); + } @media (max-width: 719.98px) { .bottom { padding: 0; @@ -118,11 +126,33 @@ + + + + Issues + + + +
+ {issue.id} +
+
+ {utils.formatObjectId(issue.id)} +
+
+
+
diff --git a/src/views/projects/Issues.svelte b/src/views/projects/Issues.svelte index 8f7b0e2c6..e7f721586 100644 --- a/src/views/projects/Issues.svelte +++ b/src/views/projects/Issues.svelte @@ -25,6 +25,7 @@ import Loading from "@app/components/Loading.svelte"; import Placeholder from "@app/components/Placeholder.svelte"; import Popover from "@app/components/Popover.svelte"; + import Separator from "./Separator.svelte"; import Share from "./Share.svelte"; export let baseUrl: BaseUrl; @@ -32,6 +33,7 @@ export let issues: Issue[]; export let project: Project; export let status: IssueState["status"]; + export let nodeAvatarUrl: string | undefined; let loading = false; let page = 0; @@ -115,7 +117,18 @@ } - + + + + + Issues + +
+ + + + Patches + + + +
+ {patch.id} +
+
+ {utils.formatObjectId(patch.id)} +
+
+
diff --git a/src/views/projects/Patches.svelte b/src/views/projects/Patches.svelte index c03556b19..e62e58484 100644 --- a/src/views/projects/Patches.svelte +++ b/src/views/projects/Patches.svelte @@ -25,6 +25,7 @@ import PatchTeaser from "./Patch/PatchTeaser.svelte"; import Placeholder from "@app/components/Placeholder.svelte"; import Popover, { closeFocused } from "@app/components/Popover.svelte"; + import Separator from "./Separator.svelte"; import Share from "./Share.svelte"; export let baseUrl: BaseUrl; @@ -32,6 +33,7 @@ export let patches: Patch[]; export let project: Project; export let status: PatchState["status"]; + export let nodeAvatarUrl: string | undefined; let loading = false; let page = 0; @@ -123,7 +125,18 @@ } - + + + + + Patches + +
- Collapse -
- - +
+
+ + - - -
-
- - - - + + +
+
+ + + + +
{/if} diff --git a/src/views/projects/Source.svelte b/src/views/projects/Source.svelte index e7c5a8920..a245a6825 100644 --- a/src/views/projects/Source.svelte +++ b/src/views/projects/Source.svelte @@ -16,8 +16,10 @@ import Placeholder from "@app/components/Placeholder.svelte"; import BlobComponent from "./Source/Blob.svelte"; - import TreeComponent from "./Source/Tree.svelte"; + import FilePath from "@app/components/FilePath.svelte"; import ProjectNameHeader from "./Source/ProjectNameHeader.svelte"; + import Separator from "./Separator.svelte"; + import TreeComponent from "./Source/Tree.svelte"; export let baseUrl: BaseUrl; export let blobResult: BlobResult; @@ -26,10 +28,11 @@ export let peer: string | undefined; export let peers: Remote[]; export let project: Project; - export let seedingPolicy: SeedingPolicy; export let rawPath: (commit?: string) => string; export let revision: string | undefined; + export let seedingPolicy: SeedingPolicy; export let tree: Tree; + export let nodeAvatarUrl: string | undefined; let mobileFileTree = false; @@ -117,11 +120,18 @@ + + {#if path !== "/"} + + + {/if} +
diff --git a/src/views/projects/router.ts b/src/views/projects/router.ts index c8b0e0026..7126bcee4 100644 --- a/src/views/projects/router.ts +++ b/src/views/projects/router.ts @@ -120,6 +120,7 @@ export type ProjectLoadedRoute = path: string; rawPath: (commit?: string) => string; blobResult: BlobResult; + nodeAvatarUrl: string | undefined; }; } | { @@ -134,6 +135,7 @@ export type ProjectLoadedRoute = revision: string | undefined; tree: Tree; commitHeaders: CommitHeader[]; + nodeAvatarUrl: string | undefined; }; } | { @@ -143,6 +145,7 @@ export type ProjectLoadedRoute = seedingPolicy: SeedingPolicy; project: Project; commit: Commit; + nodeAvatarUrl: string | undefined; }; } | { @@ -153,6 +156,7 @@ export type ProjectLoadedRoute = project: Project; rawPath: (commit?: string) => string; issue: Issue; + nodeAvatarUrl: string | undefined; }; } | { @@ -163,6 +167,7 @@ export type ProjectLoadedRoute = project: Project; issues: Issue[]; status: IssueState["status"]; + nodeAvatarUrl: string | undefined; }; } | { @@ -173,6 +178,7 @@ export type ProjectLoadedRoute = project: Project; patches: Patch[]; status: PatchState["status"]; + nodeAvatarUrl: string | undefined; }; } | { @@ -185,6 +191,7 @@ export type ProjectLoadedRoute = patch: Patch; stats: Diff["stats"]; view: PatchView; + nodeAvatarUrl: string | undefined; }; }; @@ -264,10 +271,11 @@ export async function loadProjectRoute( } else if (route.resource === "project.history") { return await loadHistoryView(route); } else if (route.resource === "project.commit") { - const [project, commit, seedingPolicy] = await Promise.all([ + const [project, commit, seedingPolicy, node] = await Promise.all([ api.project.getById(route.project), api.project.getCommitBySha(route.project, route.commit), api.getPoliciesById(route.project), + api.getNode(), ]); return { @@ -277,6 +285,7 @@ export async function loadProjectRoute( seedingPolicy, project, commit, + nodeAvatarUrl: node.avatarUrl, }, }; } else if (route.resource === "project.issue") { @@ -310,7 +319,7 @@ async function loadPatchesView( const searchParams = new URLSearchParams(route.search || ""); const status = (searchParams.get("status") as PatchState["status"]) || "open"; - const [project, patches, seedingPolicy] = await Promise.all([ + const [project, patches, seedingPolicy, node] = await Promise.all([ api.project.getById(route.project), api.project.getAllPatches(route.project, { status, @@ -318,6 +327,7 @@ async function loadPatchesView( perPage: PATCHES_PER_PAGE, }), api.getPoliciesById(route.project), + api.getNode(), ]); return { @@ -328,6 +338,7 @@ async function loadPatchesView( patches, status, project, + nodeAvatarUrl: node.avatarUrl, }, }; } @@ -338,7 +349,7 @@ async function loadIssuesView( const api = new HttpdClient(route.node); const status = route.status || "open"; - const [project, issues, seedingPolicy] = await Promise.all([ + const [project, issues, seedingPolicy, node] = await Promise.all([ api.project.getById(route.project), api.project.getAllIssues(route.project, { status, @@ -346,6 +357,7 @@ async function loadIssuesView( perPage: ISSUES_PER_PAGE, }), api.getPoliciesById(route.project), + api.getNode(), ]); return { @@ -356,6 +368,7 @@ async function loadIssuesView( issues, status, project, + nodeAvatarUrl: node.avatarUrl, }, }; } @@ -387,10 +400,11 @@ async function loadTreeView( seedingPolicyPromise = api.getPoliciesById(route.project); } - const [project, peers, seedingPolicy] = await Promise.all([ + const [project, peers, seedingPolicy, node] = await Promise.all([ projectPromise, peersPromise, seedingPolicyPromise, + api.getNode(), ]); let branchMap: Record = { @@ -438,6 +452,7 @@ async function loadTreeView( tree, path, blobResult, + nodeAvatarUrl: node.avatarUrl, }, }; } @@ -496,11 +511,12 @@ async function loadHistoryView( ): Promise { const api = new HttpdClient(route.node); - const [project, peers, seedingPolicy, branchMap] = await Promise.all([ + const [project, peers, seedingPolicy, branchMap, node] = await Promise.all([ api.project.getById(route.project), api.project.getAllRemotes(route.project), api.getPoliciesById(route.project), getPeerBranches(api, route.project, route.peer), + api.getNode(), ]); let commitId; @@ -539,6 +555,7 @@ async function loadHistoryView( revision: route.revision, tree, commitHeaders, + nodeAvatarUrl: node.avatarUrl, }, }; } @@ -552,10 +569,11 @@ async function loadIssueView( route.project }${commit ? `/${commit}` : ""}`; - const [project, issue, seedingPolicy] = await Promise.all([ + const [project, issue, seedingPolicy, node] = await Promise.all([ api.project.getById(route.project), api.project.getIssueById(route.project, route.issue), api.getPoliciesById(route.project), + api.getNode(), ]); return { resource: "project.issue", @@ -565,6 +583,7 @@ async function loadIssueView( project, rawPath, issue, + nodeAvatarUrl: node.avatarUrl, }, }; } @@ -578,10 +597,11 @@ async function loadPatchView( route.project }${commit ? `/${commit}` : ""}`; - const [project, patch, seedingPolicy] = await Promise.all([ + const [project, patch, seedingPolicy, node] = await Promise.all([ api.project.getById(route.project), api.project.getPatchById(route.project, route.patch), api.getPoliciesById(route.project), + api.getNode(), ]); const latestRevision = patch.revisions[patch.revisions.length - 1]; const { diff } = await api.project.getDiff( @@ -643,6 +663,7 @@ async function loadPatchView( patch, stats: diff.stats, view, + nodeAvatarUrl: node.avatarUrl, }, }; } diff --git a/tests/build/smoke.spec.ts b/tests/build/smoke.spec.ts index fa62ca256..7adf93a11 100644 --- a/tests/build/smoke.spec.ts +++ b/tests/build/smoke.spec.ts @@ -1,9 +1,11 @@ import { test, expect } from "@tests/support/fixtures.js"; test("exceptions in production build", async ({ page }) => { - await page.goto("/"); + await page.goto("/nodes/127.0.0.1:8081"); // Wait for scripts to finish executing, there might be exceptions that // happen after the page has been painted. await page.waitForTimeout(2000); - await expect(page.getByText("Explore", { exact: true })).toBeVisible(); + await expect( + page.getByText("Local node browsing not supported"), + ).toBeVisible(); }); diff --git a/tests/e2e/clipboard.spec.ts b/tests/e2e/clipboard.spec.ts index 36f6138f5..7783b4cd3 100644 --- a/tests/e2e/clipboard.spec.ts +++ b/tests/e2e/clipboard.spec.ts @@ -4,7 +4,6 @@ import { expect, sourceBrowsingUrl, sourceBrowsingRid, - nodeRemote, test, } from "@tests/support/fixtures.js"; @@ -60,13 +59,6 @@ test("copy to clipboard", async ({ page, browserName, context }) => { ); } - await page.goto("/nodes/radicle.local"); - // Node address. - { - await page.getByRole("button", { name: "node-id" }).first().click(); - await expectClipboard(`${nodeRemote}`, page); - } - // Clear the system clipboard contents so developers don't wonder why there's // random stuff in their clipboard after running tests. await page.evaluate("navigator.clipboard.writeText('')"); diff --git a/tests/e2e/landingPage.spec.ts b/tests/e2e/landingPage.spec.ts deleted file mode 100644 index 471803b6b..000000000 --- a/tests/e2e/landingPage.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { expect, test } from "@tests/support/fixtures.js"; - -test("show pinned repositories", async ({ context, page }) => { - await context.addInitScript(() => { - localStorage.setItem( - "configuredPreferredSeeds", - JSON.stringify([{ hostname: "127.0.0.1", port: 8081, scheme: "http" }]), - ); - }); - - await page.goto("/"); - // Shows pinned project name. - await expect(page.getByText("source-browsing")).toBeVisible(); - // - // Shows pinned project description. - await expect( - page.getByText("Git repository for source browsing tests"), - ).toBeVisible(); -}); - -test("no duplicate entry for preferred seeds", async ({ page }) => { - await page.goto("/"); - await expect(page.getByText("seed.radicle.garden")).toBeVisible(); - - await page.getByTitle("Switch preferred seeds").getByRole("button").click(); - await expect( - page.getByRole("button", { name: "seed.radicle.garden" }), - ).toBeVisible(); - - await page - .getByPlaceholder("Navigate to seed URL") - .fill("seed.radicle.garden"); - await page.getByPlaceholder("Navigate to seed URL").press("Enter"); - await expect(page.getByText("Seed node already added.")).toBeVisible(); - - await page.getByPlaceholder("Navigate to seed URL").fill(""); - await expect(page.getByText("Seed node already added.")).toBeHidden(); -}); - -test("adding and removing a new preferred seed", async ({ page }) => { - await page.route( - ({ hostname }) => hostname === "seed.rhizoma.dev", - route => route.fulfill({ json: nodeInfo }), - ); - - await page.goto("/"); - await expect(page.getByText("seed.radicle.garden")).toBeVisible(); - - await page.getByTitle("Switch preferred seeds").getByRole("button").click(); - await expect( - page.getByRole("button", { name: "seed.radicle.garden" }), - ).toBeVisible(); - - await page.getByPlaceholder("Navigate to seed URL").fill("seed.rhizoma.dev"); - await page.getByPlaceholder("Navigate to seed URL").press("Enter"); - await expect(page.getByText("seed.rhizoma.dev")).toBeVisible(); - - await page.getByTitle("Switch preferred seeds").getByRole("button").click(); - await expect( - page.getByRole("button", { name: "seed.rhizoma.dev" }), - ).toBeVisible(); - - // Test that removing the selected seed doesn't end in an undefined state. - await page - .getByRole("button", { name: "seed.rhizoma.dev" }) - .getByRole("button") - .click(); - await expect(page.getByText("seed.radicle.garden")).toBeVisible(); - - await page.getByTitle("Switch preferred seeds").getByRole("button").click(); - await expect( - page.getByRole("button", { name: "seed.rhizoma.dev" }), - ).toBeHidden(); -}); - -test("stored custom preferred seeds in local storage", async ({ page }) => { - await page.addInitScript(() => - localStorage.setItem( - "configuredPreferredSeeds", - '[{"hostname":"seed.radicle.xyz","port":443,"scheme":"https"},{"hostname":"seed.rhizoma.dev","port":443,"scheme":"https"}]', - ), - ); - await page.goto("/"); - await expect(page.getByText("seed.radicle.xyz")).toBeVisible(); - - await page.getByTitle("Switch preferred seeds").getByRole("button").click(); - await expect( - page.getByRole("button", { name: "seed.radicle.xyz" }), - ).toBeVisible(); - await expect( - page.getByRole("button", { name: "seed.rhizoma.dev" }), - ).toBeVisible(); - // Check that the fallback node hasn't been added on load. - await expect( - page.getByRole("button", { name: "seed.radicle.garden" }), - ).toBeHidden(); -}); - -const nodeInfo = { - id: "z6MkkGfMNQmjrp66Po2n4snzcSyTFRFw1m1fbYhCURxLxZpD", - agent: "/radicle:1.0.0-rc.11/", - config: { - alias: "rhizoma", - listen: [], - peers: { - type: "dynamic", - }, - connect: [], - externalAddresses: ["seed.rhizoma.dev:8776"], - db: { - journalMode: "wal", - }, - network: "main", - log: "INFO", - relay: "auto", - limits: { - routingMaxSize: 1000, - routingMaxAge: 604800, - gossipMaxAge: 1209600, - fetchConcurrency: 1, - maxOpenFiles: 4096, - rate: { - inbound: { - fillRate: 2, - capacity: 128, - }, - outbound: { - fillRate: 5, - capacity: 256, - }, - }, - connection: { - inbound: 128, - outbound: 16, - }, - }, - workers: 32, - seedingPolicy: { - default: "block", - }, - }, - state: "running", -}; diff --git a/tests/e2e/node.spec.ts b/tests/e2e/node.spec.ts index 8da300359..f98dcba04 100644 --- a/tests/e2e/node.spec.ts +++ b/tests/e2e/node.spec.ts @@ -21,11 +21,8 @@ test("node metadata", async ({ page, peerManager }) => { await page.goto(peer.uiUrl()); - await expect(page.getByRole("link", { name: "127.0.0.1" })).toBeVisible(); - await expect( - page.getByText(`${shortNodeRemote}@seed.radicle.test:8123`), - ).toBeVisible(); - await expect(page.getByText("radicle:1.0.0-rc.11")).toBeVisible(); + await expect(page.getByText(shortNodeRemote).first()).toBeVisible(); + await expect(page.getByText("/radicle:1.0.0-rc.11/")).toBeVisible(); }); test("node projects", async ({ page }) => { @@ -42,3 +39,73 @@ test("node projects", async ({ page }) => { ).toBeVisible(); } }); + +test("show pinned repositories", async ({ page }) => { + await page.goto("/"); + // Shows pinned project name. + await expect(page.getByText("source-browsing")).toBeVisible(); + // + // Shows pinned project description. + await expect( + page.getByText("Git repository for source browsing tests"), + ).toBeVisible(); +}); + +test("no duplicate entry for preferred seeds", async ({ page }) => { + await page.goto("/"); + await expect(page.getByText("127.0.0.1").first()).toBeVisible(); + + await page.getByTitle("Switch preferred seeds").getByRole("button").click(); + await expect(page.getByRole("button", { name: "127.0.0.1" })).toBeVisible(); + + await page.getByPlaceholder("Navigate to seed").fill("127.0.0.1"); + await page.getByPlaceholder("Navigate to seed").press("Enter"); + await expect(page.getByText("Seed node already added.")).toBeVisible(); + + await page.getByPlaceholder("Navigate to seed").fill(""); + await expect(page.getByText("Seed node already added.")).toBeHidden(); +}); + +test("adding and removing a new preferred seed", async ({ page }) => { + // Proxy requests to seed.example.tld to the local test api. + await page.route( + url => url.hostname === "seed.example.tld", + route => + route.fulfill({ + status: 301, + headers: { + Location: route + .request() + .url() + .replace("seed.example.tld", "127.0.0.1"), + }, + }), + ); + + await page.goto("/"); + await expect(page.getByText("127.0.0.1").first()).toBeVisible(); + + await page.getByTitle("Switch preferred seeds").getByRole("button").click(); + await expect(page.getByRole("button", { name: "127.0.0.1" })).toBeVisible(); + + await page.getByPlaceholder("Navigate to seed").fill("seed.example.tld"); + await page.getByPlaceholder("Navigate to seed").press("Enter"); + await expect(page.getByText("seed.example.tld").first()).toBeVisible(); + + await page.getByTitle("Switch preferred seeds").getByRole("button").click(); + await expect( + page.getByRole("button", { name: "seed.example.tld" }), + ).toBeVisible(); + + // Test that removing the selected seed doesn't end in an undefined state. + await page + .getByRole("button", { name: "seed.example.tld" }) + .getByRole("button") + .click(); + await expect(page.getByText("127.0.0.1").first()).toBeVisible(); + + await page.getByTitle("Switch preferred seeds").getByRole("button").click(); + await expect( + page.getByRole("button", { name: "seed.example.tld" }), + ).toBeHidden(); +}); diff --git a/tests/unit/router.test.ts b/tests/unit/router.test.ts index 34c1963f7..ed0b24f7d 100644 --- a/tests/unit/router.test.ts +++ b/tests/unit/router.test.ts @@ -13,9 +13,6 @@ describe("route invariant when parsed", () => { scheme: "http", }; - test("home", () => { - return expectParsingInvariant({ resource: "home" }); - }); test("nodes", () => { expectParsingInvariant({ resource: "nodes", @@ -213,11 +210,6 @@ describe("pathToRoute", () => { expectPathToRoute("/foo/baz/bar", null); }); - test("home", () => { - expectPathToRoute("", { resource: "home" }); - expectPathToRoute("/", { resource: "home" }); - }); - test("nodes", () => { expectPathToRoute("/nodes/example.node.tld", { resource: "nodes", diff --git a/tests/visual/desktop/node.spec.ts b/tests/visual/desktop/node.spec.ts index fa4e7ba13..1f9d95d04 100644 --- a/tests/visual/desktop/node.spec.ts +++ b/tests/visual/desktop/node.spec.ts @@ -17,7 +17,7 @@ test("node page", async ({ page }) => { test("empty pinned projects", async ({ page }) => { await page.route( ({ hostname, pathname }) => - pathname === "/api/v1/projects" && hostname === "seed.radicle.garden", + pathname === "/api/v1/projects" && hostname === "127.0.0.1", async route => { await route.fulfill({ status: 200,