feat: relay info page
This commit is contained in:
@ -33,10 +33,11 @@ interface CollapsedSectionProps {
|
||||
title: ReactNode;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
startClosed?: boolean;
|
||||
}
|
||||
|
||||
export const CollapsedSection = ({ title, children, className }: CollapsedSectionProps) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
export const CollapsedSection = ({ title, children, className, startClosed }: CollapsedSectionProps) => {
|
||||
const [collapsed, setCollapsed] = useState(startClosed ?? true);
|
||||
const icon = (
|
||||
<div className={classNames("collapse-icon", { flip: !collapsed })}>
|
||||
<Icon name="arrowFront" />
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { RelaySettings } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import useRelayState from "@/Feed/RelayState";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
@ -8,65 +6,30 @@ import RelayUptime from "@/Pages/settings/relays/uptime";
|
||||
import { getRelayName } from "@/Utils";
|
||||
|
||||
import Icon from "../Icons/Icon";
|
||||
import RelayPermissions from "./permissions";
|
||||
import RelayStatusLabel from "./status-label";
|
||||
|
||||
export interface RelayProps {
|
||||
addr: string;
|
||||
}
|
||||
|
||||
export default function Relay(props: RelayProps) {
|
||||
const { state } = useLogin(s => ({ v: s.state.version, state: s.state }));
|
||||
const connection = useRelayState(props.addr);
|
||||
|
||||
const settings = state.relays?.find(a => a.url === props.addr)?.settings;
|
||||
if (!connection || !settings) return;
|
||||
|
||||
async function configure(o: RelaySettings) {
|
||||
await state.updateRelay(props.addr, o);
|
||||
}
|
||||
|
||||
const { state } = useLogin(s => ({ v: s.state.version, state: s.state }));
|
||||
if (!connection) return;
|
||||
const name = connection.info?.name ?? getRelayName(props.addr);
|
||||
return (
|
||||
<tr>
|
||||
<td className="text-ellipsis" title={props.addr}>
|
||||
{name.length > 20 ? <>{name.slice(0, 20)}...</> : name}
|
||||
<Link to={`/settings/relays/${encodeURIComponent(props.addr)}`}>
|
||||
{name.length > 20 ? <>{name.slice(0, 20)}...</> : name}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex gap-1 items-center">
|
||||
<div
|
||||
className={classNames("rounded-full w-4 h-4", {
|
||||
"bg-success": connection.isOpen,
|
||||
"bg-error": !connection.isOpen,
|
||||
})}></div>
|
||||
{connection.isOpen ? (
|
||||
<FormattedMessage defaultMessage="Connected" />
|
||||
) : (
|
||||
<FormattedMessage defaultMessage="Offline" />
|
||||
)}
|
||||
</div>
|
||||
<RelayStatusLabel conn={connection} />
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex gap-2 cursor-pointer select-none justify-center">
|
||||
<div
|
||||
className={settings.read ? "" : "text-gray"}
|
||||
onClick={() =>
|
||||
configure({
|
||||
read: !settings.read,
|
||||
write: settings.write,
|
||||
})
|
||||
}>
|
||||
<FormattedMessage defaultMessage="Read" />
|
||||
</div>
|
||||
<div
|
||||
className={settings.write ? "" : "text-gray"}
|
||||
onClick={() =>
|
||||
configure({
|
||||
read: settings.read,
|
||||
write: !settings.write,
|
||||
})
|
||||
}>
|
||||
<FormattedMessage defaultMessage="Write" />
|
||||
</div>
|
||||
</div>
|
||||
<RelayPermissions conn={connection} />
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<RelayUptime url={props.addr} />
|
||||
|
@ -1,9 +0,0 @@
|
||||
.favicon {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.relay-active {
|
||||
color: var(--highlight);
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
import "./RelaysMetadata.css";
|
||||
|
||||
import { FullRelaySettings } from "@snort/system";
|
||||
import { useState } from "react";
|
||||
|
||||
import Nostrich from "@/assets/img/nostrich.webp";
|
||||
import Icon from "@/Components/Icons/Icon";
|
||||
|
||||
export const RelayFavicon = ({ url }: { url: string }) => {
|
||||
export const RelayFavicon = ({ url, size }: { url: string; size?: number }) => {
|
||||
const cleanUrl = url
|
||||
.replace(/^wss:\/\//, "https://")
|
||||
.replace(/^ws:\/\//, "http://")
|
||||
@ -14,10 +12,12 @@ export const RelayFavicon = ({ url }: { url: string }) => {
|
||||
const [faviconUrl, setFaviconUrl] = useState(`${cleanUrl}/favicon.ico`);
|
||||
return (
|
||||
<img
|
||||
className="circle favicon"
|
||||
className="rounded-full object-cover"
|
||||
src={faviconUrl}
|
||||
onError={() => setFaviconUrl(Nostrich)}
|
||||
alt={`favicon for ${url}`}
|
||||
width={size ?? 20}
|
||||
height={size ?? 20}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
17
packages/app/src/Components/Relay/paid.tsx
Normal file
17
packages/app/src/Components/Relay/paid.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { RelayInfo } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export default function RelayPaymentLabel({ info }: { info: RelayInfo }) {
|
||||
const isPaid = info?.limitation?.payment_required ?? false;
|
||||
return (
|
||||
<div
|
||||
className={classNames("rounded-full px-2 py-1 font-medium", {
|
||||
"bg-[var(--pro)] text-black": isPaid,
|
||||
"bg-[var(--free)]": !isPaid,
|
||||
})}>
|
||||
{isPaid && <FormattedMessage defaultMessage="Paid" />}
|
||||
{!isPaid && <FormattedMessage defaultMessage="Free" />}
|
||||
</div>
|
||||
);
|
||||
}
|
33
packages/app/src/Components/Relay/permissions.tsx
Normal file
33
packages/app/src/Components/Relay/permissions.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { ConnectionType } from "@snort/system/dist/connection-pool";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
|
||||
export default function RelayPermissions({ conn }: { conn: ConnectionType }) {
|
||||
const { state } = useLogin(s => ({ v: s.state.version, state: s.state }));
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 cursor-pointer select-none">
|
||||
<div
|
||||
className={conn.settings.read ? "" : "text-gray"}
|
||||
onClick={async () =>
|
||||
await state.updateRelay(conn.address, {
|
||||
read: !conn.settings.read,
|
||||
write: conn.settings.write,
|
||||
})
|
||||
}>
|
||||
<FormattedMessage defaultMessage="Read" />
|
||||
</div>
|
||||
<div
|
||||
className={conn.settings.write ? "" : "text-gray"}
|
||||
onClick={async () =>
|
||||
await state.updateRelay(conn.address, {
|
||||
read: conn.settings.read,
|
||||
write: !conn.settings.write,
|
||||
})
|
||||
}>
|
||||
<FormattedMessage defaultMessage="Write" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
9
packages/app/src/Components/Relay/software.tsx
Normal file
9
packages/app/src/Components/Relay/software.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function RelaySoftware({ software }: { software: string }) {
|
||||
if (software.includes("git")) {
|
||||
const u = new URL(software);
|
||||
return <Link to={software}>{u.pathname.split("/").at(-1)?.replace(".git", "")}</Link>;
|
||||
}
|
||||
return software;
|
||||
}
|
16
packages/app/src/Components/Relay/status-label.tsx
Normal file
16
packages/app/src/Components/Relay/status-label.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { ConnectionType } from "@snort/system/dist/connection-pool";
|
||||
import classNames from "classnames";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export default function RelayStatusLabel({ conn }: { conn: ConnectionType }) {
|
||||
return (
|
||||
<div className="flex gap-1 items-center">
|
||||
<div
|
||||
className={classNames("rounded-full w-4 h-4", {
|
||||
"bg-success": conn.isOpen,
|
||||
"bg-error": !conn.isOpen,
|
||||
})}></div>
|
||||
{conn.isOpen ? <FormattedMessage defaultMessage="Connected" /> : <FormattedMessage defaultMessage="Offline" />}
|
||||
</div>
|
||||
);
|
||||
}
|
149
packages/app/src/Components/nip.tsx
Normal file
149
packages/app/src/Components/nip.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export default function NipDescription({ nip }: { nip: number }) {
|
||||
switch (nip) {
|
||||
case 1:
|
||||
return <FormattedMessage defaultMessage="Basic protocol flow description" />;
|
||||
case 2:
|
||||
return <FormattedMessage defaultMessage="Follow List" />;
|
||||
case 3:
|
||||
return <FormattedMessage defaultMessage="OpenTimestamps Attestations for Events" />;
|
||||
case 4:
|
||||
return <FormattedMessage defaultMessage="Encrypted Direct Message" />;
|
||||
case 5:
|
||||
return <FormattedMessage defaultMessage="Mapping Nostr keys to DNS-based internet identifiers" />;
|
||||
case 6:
|
||||
return <FormattedMessage defaultMessage="Basic key derivation from mnemonic seed phrase" />;
|
||||
case 7:
|
||||
return <FormattedMessage defaultMessage="window.nostr capability for web browsers" />;
|
||||
case 8:
|
||||
return <FormattedMessage defaultMessage="Handling Mentions" />;
|
||||
case 9:
|
||||
return <FormattedMessage defaultMessage="Event Deletion Request" />;
|
||||
case 10:
|
||||
return <FormattedMessage defaultMessage="Conventions for clients' use of e and p tags in text events" />;
|
||||
case 11:
|
||||
return <FormattedMessage defaultMessage="Relay Information Document" />;
|
||||
case 13:
|
||||
return <FormattedMessage defaultMessage="Proof of Work" />;
|
||||
case 14:
|
||||
return <FormattedMessage defaultMessage="Subject tag in text events" />;
|
||||
case 15:
|
||||
return <FormattedMessage defaultMessage="Nostr Marketplace (for resilient marketplaces)" />;
|
||||
case 17:
|
||||
return <FormattedMessage defaultMessage="Private Direct Messages" />;
|
||||
case 18:
|
||||
return <FormattedMessage defaultMessage="Reposts" />;
|
||||
case 19:
|
||||
return <FormattedMessage defaultMessage="bech32-encoded entities" />;
|
||||
case 21:
|
||||
return <FormattedMessage defaultMessage="nostr: URI scheme" />;
|
||||
case 23:
|
||||
return <FormattedMessage defaultMessage="Long-form Content" />;
|
||||
case 24:
|
||||
return <FormattedMessage defaultMessage="Extra metadata fields and tags" />;
|
||||
case 25:
|
||||
return <FormattedMessage defaultMessage="Reactions" />;
|
||||
case 26:
|
||||
return <FormattedMessage defaultMessage="Delegated Event Signing" />;
|
||||
case 27:
|
||||
return <FormattedMessage defaultMessage="Text Note References" />;
|
||||
case 28:
|
||||
return <FormattedMessage defaultMessage="Public Chat" />;
|
||||
case 29:
|
||||
return <FormattedMessage defaultMessage="Relay-based Groups" />;
|
||||
case 30:
|
||||
return <FormattedMessage defaultMessage="Custom Emoji" />;
|
||||
case 31:
|
||||
return <FormattedMessage defaultMessage="Dealing with Unknown Events" />;
|
||||
case 32:
|
||||
return <FormattedMessage defaultMessage="Labeling" />;
|
||||
case 34:
|
||||
return <FormattedMessage defaultMessage="git stuff" />;
|
||||
case 35:
|
||||
return <FormattedMessage defaultMessage="Torrents" />;
|
||||
case 36:
|
||||
return <FormattedMessage defaultMessage="Sensitive Content" />;
|
||||
case 38:
|
||||
return <FormattedMessage defaultMessage="User Statuses" />;
|
||||
case 39:
|
||||
return <FormattedMessage defaultMessage="External Identities in Profiles" />;
|
||||
case 40:
|
||||
return <FormattedMessage defaultMessage="Expiration Timestamp" />;
|
||||
case 42:
|
||||
return <FormattedMessage defaultMessage="Authentication of clients to relays" />;
|
||||
case 44:
|
||||
return <FormattedMessage defaultMessage="Versioned Encryption" />;
|
||||
case 45:
|
||||
return <FormattedMessage defaultMessage="Counting results" />;
|
||||
case 46:
|
||||
return <FormattedMessage defaultMessage="Nostr Connect" />;
|
||||
case 47:
|
||||
return <FormattedMessage defaultMessage="Wallet Connect" />;
|
||||
case 48:
|
||||
return <FormattedMessage defaultMessage="Proxy Tags" />;
|
||||
case 49:
|
||||
return <FormattedMessage defaultMessage="Private Key Encryption" />;
|
||||
case 50:
|
||||
return <FormattedMessage defaultMessage="Search Capability" />;
|
||||
case 51:
|
||||
return <FormattedMessage defaultMessage="Lists" />;
|
||||
case 52:
|
||||
return <FormattedMessage defaultMessage="Calendar Events" />;
|
||||
case 53:
|
||||
return <FormattedMessage defaultMessage="Live Activities" />;
|
||||
case 54:
|
||||
return <FormattedMessage defaultMessage="Wiki" />;
|
||||
case 55:
|
||||
return <FormattedMessage defaultMessage="Android Signer Application" />;
|
||||
case 56:
|
||||
return <FormattedMessage defaultMessage="Reporting" />;
|
||||
case 57:
|
||||
return <FormattedMessage defaultMessage="Lightning Zaps" />;
|
||||
case 58:
|
||||
return <FormattedMessage defaultMessage="Badges" />;
|
||||
case 59:
|
||||
return <FormattedMessage defaultMessage="Gift Wrap" />;
|
||||
case 64:
|
||||
return <FormattedMessage defaultMessage="Chess (PGN)" />;
|
||||
case 65:
|
||||
return <FormattedMessage defaultMessage="Relay List Metadata" />;
|
||||
case 70:
|
||||
return <FormattedMessage defaultMessage="Protected Events" />;
|
||||
case 71:
|
||||
return <FormattedMessage defaultMessage="Video Events" />;
|
||||
case 72:
|
||||
return <FormattedMessage defaultMessage="Moderated Communities" />;
|
||||
case 73:
|
||||
return <FormattedMessage defaultMessage="External Content IDs" />;
|
||||
case 75:
|
||||
return <FormattedMessage defaultMessage="Zap Goals" />;
|
||||
case 78:
|
||||
return <FormattedMessage defaultMessage="Application-specific data" />;
|
||||
case 84:
|
||||
return <FormattedMessage defaultMessage="Highlights" />;
|
||||
case 89:
|
||||
return <FormattedMessage defaultMessage="Recommended Application Handlers" />;
|
||||
case 90:
|
||||
return <FormattedMessage defaultMessage="Data Vending Machines" />;
|
||||
case 92:
|
||||
return <FormattedMessage defaultMessage="Media Attachments" />;
|
||||
case 94:
|
||||
return <FormattedMessage defaultMessage="File Metadata" />;
|
||||
case 96:
|
||||
return <FormattedMessage defaultMessage="HTTP File Storage Integration" />;
|
||||
case 98:
|
||||
return <FormattedMessage defaultMessage="HTTP Auth" />;
|
||||
case 99:
|
||||
return <FormattedMessage defaultMessage="Classified Listings" />;
|
||||
default:
|
||||
return (
|
||||
<FormattedMessage
|
||||
defaultMessage="Unknown NIP-{x}"
|
||||
values={{
|
||||
x: nip.toString().padStart(2, "0"),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user