UI updates #135
@ -1,6 +1,6 @@
|
|||||||
import "./Relay.css"
|
import "./Relay.css"
|
||||||
|
|
||||||
import { faPlug, faTrash, faSquareCheck, faSquareXmark, faWifi, faUpload, faDownload, faPlugCircleXmark, faEllipsisVertical } from "@fortawesome/free-solid-svg-icons";
|
import { faPlug, faTrash, faSquareCheck, faSquareXmark, faWifi, faPlugCircleXmark, faGear } from "@fortawesome/free-solid-svg-icons";
|
||||||
import useRelayState from "Feed/RelayState";
|
import useRelayState from "Feed/RelayState";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
@ -8,6 +8,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { removeRelay, setRelays } from "State/Login";
|
import { removeRelay, setRelays } from "State/Login";
|
||||||
import { RootState } from "State/Store";
|
import { RootState } from "State/Store";
|
||||||
import { RelaySettings } from "Nostr/Connection";
|
import { RelaySettings } from "Nostr/Connection";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export interface RelayProps {
|
export interface RelayProps {
|
||||||
addr: string
|
addr: string
|
||||||
@ -15,11 +16,11 @@ export interface RelayProps {
|
|||||||
|
|
||||||
export default function Relay(props: RelayProps) {
|
export default function Relay(props: RelayProps) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
const allRelaySettings = useSelector<RootState, Record<string, RelaySettings>>(s => s.login.relays);
|
const allRelaySettings = useSelector<RootState, Record<string, RelaySettings>>(s => s.login.relays);
|
||||||
const relaySettings = allRelaySettings[props.addr];
|
const relaySettings = allRelaySettings[props.addr];
|
||||||
const state = useRelayState(props.addr);
|
const state = useRelayState(props.addr);
|
||||||
const name = useMemo(() => new URL(props.addr).host, [props.addr]);
|
const name = useMemo(() => new URL(props.addr).host, [props.addr]);
|
||||||
const [showExtra, setShowExtra] = useState(false);
|
|
||||||
|
|
||||||
function configure(o: RelaySettings) {
|
function configure(o: RelaySettings) {
|
||||||
dispatch(setRelays({
|
dispatch(setRelays({
|
||||||
@ -62,28 +63,13 @@ export default function Relay(props: RelayProps) {
|
|||||||
<FontAwesomeIcon icon={faPlugCircleXmark} /> {state?.disconnects}
|
<FontAwesomeIcon icon={faPlugCircleXmark} /> {state?.disconnects}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="icon-btn" onClick={() => setShowExtra(s => !s)}>
|
<span className="icon-btn" onClick={() => navigate(name)}>
|
||||||
<FontAwesomeIcon icon={faEllipsisVertical} />
|
<FontAwesomeIcon icon={faGear} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showExtra ? <div className="flex relay-extra w-max">
|
|
||||||
<div className="f-1">
|
|
||||||
<FontAwesomeIcon icon={faUpload} /> {state?.events.send}
|
|
||||||
</div>
|
|
||||||
<div className="f-1">
|
|
||||||
<FontAwesomeIcon icon={faDownload} /> {state?.events.received}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="f-1">
|
|
||||||
Delete
|
|
||||||
<span className="icon-btn" onClick={() => dispatch(removeRelay(props.addr))}>
|
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div> : null}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { default as NEvent } from "Nostr/Event";
|
|||||||
import { DefaultConnectTimeout } from "Const";
|
import { DefaultConnectTimeout } from "Const";
|
||||||
import { ConnectionStats } from "Nostr/ConnectionStats";
|
import { ConnectionStats } from "Nostr/ConnectionStats";
|
||||||
import { RawEvent, TaggedRawEvent, u256 } from "Nostr";
|
import { RawEvent, TaggedRawEvent, u256 } from "Nostr";
|
||||||
|
import { RelayInfo } from "./RelayInfo";
|
||||||
|
|
||||||
export type CustomHook = (state: Readonly<StateSnapshot>) => void;
|
export type CustomHook = (state: Readonly<StateSnapshot>) => void;
|
||||||
|
|
||||||
@ -27,7 +28,8 @@ export type StateSnapshot = {
|
|||||||
events: {
|
events: {
|
||||||
received: number,
|
received: number,
|
||||||
send: number
|
send: number
|
||||||
}
|
},
|
||||||
|
info?: RelayInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Connection {
|
export default class Connection {
|
||||||
@ -36,6 +38,7 @@ export default class Connection {
|
|||||||
Pending: Subscriptions[];
|
Pending: Subscriptions[];
|
||||||
Subscriptions: Map<string, Subscriptions>;
|
Subscriptions: Map<string, Subscriptions>;
|
||||||
Settings: RelaySettings;
|
Settings: RelaySettings;
|
||||||
|
Info?: RelayInfo;
|
||||||
ConnectTimeout: number;
|
ConnectTimeout: number;
|
||||||
Stats: ConnectionStats;
|
Stats: ConnectionStats;
|
||||||
StateHooks: Map<string, CustomHook>;
|
StateHooks: Map<string, CustomHook>;
|
||||||
@ -72,7 +75,29 @@ export default class Connection {
|
|||||||
this.Connect();
|
this.Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
Connect() {
|
async Connect() {
|
||||||
|
try {
|
||||||
|
if (this.Info === undefined) {
|
||||||
|
let u = new URL(this.Address);
|
||||||
|
let rsp = await fetch(`https://${u.host}`, {
|
||||||
|
headers: {
|
||||||
|
"accept": "application/nostr+json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (rsp.ok) {
|
||||||
|
let data = await rsp.json();
|
||||||
|
for (let [k, v] of Object.entries(data)) {
|
||||||
|
if (v === "unset" || v === "") {
|
||||||
|
data[k] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Info = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not load relay information", e);
|
||||||
|
}
|
||||||
|
|
||||||
this.IsClosed = false;
|
this.IsClosed = false;
|
||||||
this.Socket = new WebSocket(this.Address);
|
this.Socket = new WebSocket(this.Address);
|
||||||
this.Socket.onopen = (e) => this.OnOpen(e);
|
this.Socket.onopen = (e) => this.OnOpen(e);
|
||||||
@ -259,6 +284,7 @@ export default class Connection {
|
|||||||
this.CurrentState.events.send = this.Stats.EventsSent;
|
this.CurrentState.events.send = this.Stats.EventsSent;
|
||||||
this.CurrentState.avgLatency = this.Stats.Latency.length > 0 ? (this.Stats.Latency.reduce((acc, v) => acc + v, 0) / this.Stats.Latency.length) : 0;
|
this.CurrentState.avgLatency = this.Stats.Latency.length > 0 ? (this.Stats.Latency.reduce((acc, v) => acc + v, 0) / this.Stats.Latency.length) : 0;
|
||||||
this.CurrentState.disconnects = this.Stats.Disconnects;
|
this.CurrentState.disconnects = this.Stats.Disconnects;
|
||||||
|
this.CurrentState.info = this.Info;
|
||||||
this.Stats.Latency = this.Stats.Latency.slice(-20); // trim
|
this.Stats.Latency = this.Stats.Latency.slice(-20); // trim
|
||||||
this.HasStateChange = true;
|
this.HasStateChange = true;
|
||||||
this._NotifyState();
|
this._NotifyState();
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stats class for tracking metrics per connection
|
* Stats class for tracking metrics per connection
|
||||||
*/
|
*/
|
||||||
|
9
src/Nostr/RelayInfo.ts
Normal file
9
src/Nostr/RelayInfo.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface RelayInfo {
|
||||||
|
name?: string,
|
||||||
|
description?: string,
|
||||||
|
pubkey?: string,
|
||||||
|
contact?: string,
|
||||||
|
supported_nips?: number[],
|
||||||
|
software?: string,
|
||||||
|
version?: string
|
||||||
|
}
|
@ -3,6 +3,7 @@ import SettingsIndex from "Pages/settings/Index";
|
|||||||
import Profile from "Pages/settings/Profile";
|
import Profile from "Pages/settings/Profile";
|
||||||
import Relay from "Pages/settings/Relays";
|
import Relay from "Pages/settings/Relays";
|
||||||
import Preferences from "Pages/settings/Preferences";
|
import Preferences from "Pages/settings/Preferences";
|
||||||
|
import RelayInfo from "Pages/settings/RelayInfo";
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -26,7 +27,11 @@ export const SettingsRoutes: RouteObject[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "relays",
|
path: "relays",
|
||||||
element: <Relay />
|
element: <Relay />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "relays/:addr",
|
||||||
|
element: <RelayInfo />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "preferences",
|
path: "preferences",
|
||||||
|
57
src/Pages/settings/RelayInfo.tsx
Normal file
57
src/Pages/settings/RelayInfo.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import ProfilePreview from "Element/ProfilePreview";
|
||||||
|
import useRelayState from "Feed/RelayState";
|
||||||
|
import { System } from "Nostr/System";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { removeRelay } from "State/Login";
|
||||||
|
import { parseId } from "Util";
|
||||||
|
|
||||||
|
const RelayInfo = () => {
|
||||||
|
const params = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const addr: string = `wss://${params.addr}`;
|
||||||
|
|
||||||
|
const con = System.Sockets.get(addr) ?? System.Sockets.get(`${addr}/`);
|
||||||
|
const stats = useRelayState(con?.Address ?? addr);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3 className="pointer" onClick={() => navigate("/settings/relays")}>Relays</h3>
|
||||||
|
<div className="card">
|
||||||
|
<h3>{stats?.info?.name ?? addr}</h3>
|
||||||
|
<p>{stats?.info?.description}</p>
|
||||||
|
|
||||||
|
{stats?.info?.pubkey && (<>
|
||||||
|
<h4>Owner</h4>
|
||||||
|
<ProfilePreview pubkey={parseId(stats.info.pubkey)} />
|
||||||
|
</>)}
|
||||||
|
{stats?.info?.software && (<div className="flex">
|
||||||
|
<h4 className="f-grow">Software</h4>
|
||||||
|
<div className="flex f-col">
|
||||||
|
{stats.info.software.startsWith("http") ? <a href={stats.info.software} target="_blank" rel="noreferrer">{stats.info.software}</a> : <>{stats.info.software}</>}
|
||||||
|
<small>{!stats.info.version?.startsWith("v") && "v"}{stats.info.version}</small>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
{stats?.info?.contact && (<div className="flex">
|
||||||
|
<h4 className="f-grow">Contact</h4>
|
||||||
|
<a href={`${stats.info.contact.startsWith("mailto:") ? "" : "mailto:"}${stats.info.contact}`} target="_blank" rel="noreferrer">{stats.info.contact}</a>
|
||||||
|
</div>)}
|
||||||
|
{stats?.info?.supported_nips && (<>
|
||||||
|
<h4>Supports</h4>
|
||||||
|
<div className="f-grow">
|
||||||
|
{stats.info.supported_nips.map(a => <span className="pill" onClick={() => navigate(`https://github.com/nostr-protocol/nips/blob/master/${a.toString().padStart(2, "0")}.md`)}>NIP-{a.toString().padStart(2, "0")}</span>)}
|
||||||
|
</div>
|
||||||
|
</>)}
|
||||||
|
<div className="flex mt10 f-end">
|
||||||
|
<div className="btn error" onClick={() => {
|
||||||
|
dispatch(removeRelay(con!.Address));
|
||||||
|
navigate("/settings/relays")
|
||||||
|
}}>Remove</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RelayInfo;
|
@ -52,7 +52,7 @@ const RelaySettingsPage = () => {
|
|||||||
<div className="flex f-col">
|
<div className="flex f-col">
|
||||||
{Object.keys(relays || {}).map(a => <Relay addr={a} key={a} />)}
|
{Object.keys(relays || {}).map(a => <Relay addr={a} key={a} />)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex actions">
|
<div className="flex mt10">
|
||||||
<div className="f-grow"></div>
|
<div className="f-grow"></div>
|
||||||
<div className="btn" onClick={() => saveRelays()}>Save</div>
|
<div className="btn" onClick={() => saveRelays()}>Save</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -259,6 +259,10 @@ textarea:placeholder {
|
|||||||
align-items: flex-start !important;
|
align-items: flex-start !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.f-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.w-max {
|
.w-max {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
width: -moz-available;
|
width: -moz-available;
|
||||||
|
Loading…
Reference in New Issue
Block a user