Skip to content
This repository has been archived by the owner on Aug 1, 2022. It is now read-only.

Commit

Permalink
feat: improve diagnostics screen
Browse files Browse the repository at this point in the history
- change shortcut to ⌘ + i
- add "copy all debug info to clipboard" button
- add storage layout tab
- show info about our own peer and identity
- rename NetworkDiagnostics -> Diagnostics
- make the diagnostics screen more legible

Signed-off-by: Rūdolfs Ošiņš <rudolfs@osins.org>
  • Loading branch information
rudolfs committed Nov 3, 2021
1 parent 455ab3f commit b6e607f
Show file tree
Hide file tree
Showing 18 changed files with 383 additions and 255 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions proxy-client/index.ts
Expand Up @@ -30,6 +30,16 @@ interface KeyStoreCreateParams {
passphrase: string;
}

export interface Diagnostics {
gitDirPath: string;
refsTree: string[];
}

export const diagnosticsSchema = zod.object({
gitDirPath: zod.string(),
refsTree: zod.array(zod.string()),
});

export class ProxyClient {
private fetcher: Fetcher;

Expand All @@ -46,6 +56,17 @@ export class ProxyClient {
this.identity = new identity.Client(this.fetcher);
}

async diagnosticsGet(options?: RequestOptions): Promise<Diagnostics> {
return this.fetcher.fetchOk(
{
method: "GET",
path: "diagnostics",
options,
},
diagnosticsSchema
);
}

async sessionGet(options?: RequestOptions): Promise<Session> {
return this.fetcher.fetchOk(
{
Expand Down
1 change: 1 addition & 0 deletions proxy/api/Cargo.toml
Expand Up @@ -43,6 +43,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
thiserror = "1.0"
tokio = { version = "1.2", features = [ "macros", "process", "signal", "time" ] }
url = "2.1"
walkdir = "2"
warp = { version = "0.3", default-features = false }

# radicle-link dependencies. These are patched in the workspace
Expand Down
5 changes: 4 additions & 1 deletion proxy/api/src/http.rs
Expand Up @@ -14,6 +14,7 @@ use link_crypto::{BoxedSigner, PeerId};
use crate::{context, notification::Notification};

mod control;
mod diagnostics;
mod error;
mod identity;
mod keystore;
Expand Down Expand Up @@ -63,10 +64,12 @@ pub fn api(
let project_filter = path("projects").and(project::filters(ctx.clone()));
let session_filter = path("session").and(session::filters(ctx.clone()));
let keystore_filter = path("keystore").and(keystore::filters(ctx.clone()));
let source_filter = path("source").and(source::filters(ctx));
let source_filter = path("source").and(source::filters(ctx.clone()));
let diagnostics_filter = path("diagnostics").and(diagnostics::filters(ctx));

let api = path("v1").and(combine!(
control_filter,
diagnostics_filter,
identity_filter,
notification_filter,
project_filter,
Expand Down
58 changes: 58 additions & 0 deletions proxy/api/src/http/diagnostics.rs
@@ -0,0 +1,58 @@
// Copyright © 2021 The Radicle Upstream Contributors
//
// This file is part of radicle-upstream, distributed under the GPLv3
// with Radicle Linking Exception. For full terms see the included
// LICENSE file.

//! Provide diagnostics information.

use warp::{filters::BoxedFilter, path, Filter, Rejection, Reply};

use crate::{context, http};

/// Combination of all diagnostics filters.
pub fn filters(ctx: context::Context) -> BoxedFilter<(impl Reply,)> {
// I couldn't figure out how to get past the type checker without having chaining.
get_filter(ctx.clone()).or(get_filter(ctx)).boxed()
}

/// `GET /diagnostics`
fn get_filter(
ctx: context::Context,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
path::end()
.and(warp::get())
.and(http::with_context_unsealed(ctx))
.and_then(handler::get)
}

/// Diagnostics handlers for conversion between core domain and http request fullfilment.
mod handler {
use serde_json::json;
use walkdir::WalkDir;
use warp::{reply, Rejection, Reply};

use crate::context;

/// Get diagnostics information.
#[allow(clippy::unused_async)]
pub async fn get(ctx: context::Unsealed) -> Result<impl Reply, Rejection> {
let git_dir = ctx.rest.paths.git_dir();
let refs_tree = WalkDir::new(git_dir.join("refs"))
.into_iter()
.filter_map(std::result::Result::ok)
.map(|e| {
e.path()
.strip_prefix(git_dir)
.expect("Couldn't strip prefix.")
.display()
.to_string()
})
.collect::<Vec<String>>();
let reply = json!({
"gitDirPath": git_dir,
"refsTree": refs_tree,
});
Ok(reply::json(&reply))
}
}
6 changes: 3 additions & 3 deletions ui/App.svelte
Expand Up @@ -37,7 +37,7 @@
import LoadingScreen from "ui/App/LoadingScreen.svelte";
import LockScreen from "ui/App/LockScreen.svelte";
import NetworkDiagnosticsScreen from "ui/App/NetworkDiagnosticsScreen.svelte";
import DiagnosticsScreen from "ui/App/DiagnosticsScreen.svelte";
import NetworkScreen from "ui/App/NetworkScreen.svelte";
import OrgsScreen from "ui/App/OrgsScreen.svelte";
import OrgScreen from "ui/App/OrgScreen.svelte";
Expand Down Expand Up @@ -157,8 +157,8 @@
ownUserUrn={$activeRouteStore.ownUserUrn}
user={$activeRouteStore.user}
projects={$activeRouteStore.projects} />
{:else if $activeRouteStore.type === "networkDiagnostics"}
<NetworkDiagnosticsScreen activeTab={$activeRouteStore.activeTab} />
{:else if $activeRouteStore.type === "diagnostics"}
<DiagnosticsScreen activeTab={$activeRouteStore.activeTab} />
{:else if $activeRouteStore.type === "singleSigOrg"}
<SingleSigOrgScreen
registration={$activeRouteStore.registration}
Expand Down
117 changes: 117 additions & 0 deletions ui/App/DiagnosticsScreen.svelte
@@ -0,0 +1,117 @@
<!--
Copyright © 2021 The Radicle Upstream Contributors
This file is part of radicle-upstream, distributed under the GPLv3
with Radicle Linking Exception. For full terms see the included
LICENSE file.
-->
<script lang="ts">
import { unreachable } from "ui/src/unreachable";
import { status as localPeerState } from "ui/src/localPeer";
import { waitingRoomEventLog, waitingRoomState } from "ui/src/localPeer";
import * as Session from "ui/src/session";
import * as ipc from "ui/src/ipc";
import * as notification from "ui/src/notification";
import * as proxy from "ui/src/proxy";
import * as router from "ui/src/router";
import Button from "design-system/Button.svelte";
import CopyIcon from "design-system/icons/Copy.svelte";
import FolderIcon from "design-system/icons/Folder.svelte";
import NetworkIcon from "design-system/icons/Network.svelte";
import TransactionsIcon from "design-system/icons/Transactions.svelte";
import ScreenLayout from "ui/App/ScreenLayout.svelte";
import ActionBar from "ui/App/ScreenLayout/ActionBar.svelte";
import TabBar from "ui/App/ScreenLayout/TabBar.svelte";
import ConnectionsTab from "./DiagnosticsScreen/Connections.svelte";
import WaitingRoomTab from "./DiagnosticsScreen/WaitingRoom.svelte";
import StorageTab from "./DiagnosticsScreen/Storage.svelte";
export let activeTab: router.DiagnosticsTab;
const tabs = (active: router.DiagnosticsTab) => [
{
title: "Storage",
active: active === "storage",
icon: FolderIcon,
onClick: () => {
router.push({ type: "diagnostics", activeTab: "storage" });
},
},
{
title: "P2P Connections",
active: active === "connections",
icon: NetworkIcon,
onClick: () => {
router.push({ type: "diagnostics", activeTab: "connections" });
},
},
{
title: "Waiting Room",
active: active === "waitingRoom",
icon: TransactionsIcon,
onClick: () => {
router.push({ type: "diagnostics", activeTab: "waitingRoom" });
},
},
];
async function copyEverythingToClipboard() {
ipc.copyToClipboard(
JSON.stringify(
{
storage: await proxy.client.diagnosticsGet(),
identity: Session.unsealed().identity,
localPeerState: $localPeerState,
waitingRoomState: $waitingRoomState,
waitingRoomEventLog: $waitingRoomEventLog,
},
null,
2
)
);
notification.info({ message: "Copied all debug info to clipboard" });
}
</script>

<style>
.container {
min-width: var(--content-min-width);
padding: 1rem var(--content-padding) 2rem var(--content-padding);
}
</style>

<ScreenLayout contentStyle="padding: 0;">
<div slot="header" style="display: flex;">
<h1>Diagnostics</h1>
<Button
variant="outline"
icon={CopyIcon}
style="margin-left: auto; align-self: center"
on:click={copyEverythingToClipboard}>
Copy all debug info to clipboard
</Button>
</div>

<ActionBar>
<div slot="left">
<TabBar tabs={tabs(activeTab)} />
</div>
</ActionBar>

<div class="container">
{#if activeTab === "connections"}
<ConnectionsTab />
{:else if activeTab === "storage"}
<StorageTab />
{:else if activeTab === "waitingRoom"}
<WaitingRoomTab />
{:else}
{unreachable(activeTab)}
{/if}
</div>
</ScreenLayout>
27 changes: 27 additions & 0 deletions ui/App/DiagnosticsScreen/Connections.svelte
@@ -0,0 +1,27 @@
<!--
Copyright © 2021 The Radicle Upstream Contributors
This file is part of radicle-upstream, distributed under the GPLv3
with Radicle Linking Exception. For full terms see the included
LICENSE file.
-->
<script lang="ts">
import * as Session from "ui/src/session";
import { status as localPeerState } from "ui/src/localPeer";
import Json from "./Json.svelte";
const session = Session.unsealed();
</script>

<style>
.container {
gap: 2rem;
display: flex;
flex-direction: column;
}
</style>

<div class="container">
<Json title="Your identity" data={session.identity} />
<Json title="Connection status" data={$localPeerState} />
</div>
38 changes: 38 additions & 0 deletions ui/App/DiagnosticsScreen/Json.svelte
@@ -0,0 +1,38 @@
<!--
Copyright © 2021 The Radicle Upstream Contributors
This file is part of radicle-upstream, distributed under the GPLv3
with Radicle Linking Exception. For full terms see the included
LICENSE file.
-->
<script lang="ts">
export let data: unknown;
export let title: string | undefined = undefined;
</script>

<style>
.container {
display: flex;
flex-direction: column;
gap: 0.5rem;
flex: 1;
overflow: scroll;
}
.json {
font-family: var(--typeface-mono-regular);
font-size: 14px;
background-color: var(--color-foreground-level-1);
border-radius: 0.5rem;
padding: 1rem;
overflow: scroll;
}
</style>

<div class="container">
{#if title}
<h5>{title}</h5>
{/if}

<pre class="json">{JSON.stringify(data, null, 2)}</pre>
</div>
15 changes: 15 additions & 0 deletions ui/App/DiagnosticsScreen/Storage.svelte
@@ -0,0 +1,15 @@
<!--
Copyright © 2021 The Radicle Upstream Contributors
This file is part of radicle-upstream, distributed under the GPLv3
with Radicle Linking Exception. For full terms see the included
LICENSE file.
-->
<script lang="ts">
import * as proxy from "ui/src/proxy";
import Json from "./Json.svelte";
</script>

{#await proxy.client.diagnosticsGet() then result}
<Json title="Storage locations" data={result} />
{/await}

0 comments on commit b6e607f

Please sign in to comment.