From 7baebb87a280b7bc02134d35ae48d9311dab545f Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 23 Sep 2024 17:33:22 +0900 Subject: [PATCH 01/24] feat: add peer list page --- src/locales/en.json | 11 ++ src/pages/Fiber/Channel/index.tsx | 5 + src/pages/Fiber/Peer/index.tsx | 5 + src/pages/Fiber/PeerList/index.module.scss | 123 +++++++++++++++++ src/pages/Fiber/PeerList/index.tsx | 152 +++++++++++++++++++++ src/routes/index.tsx | 17 +++ src/services/ExplorerService/fetcher.ts | 73 ++++++++++ 7 files changed, 386 insertions(+) create mode 100644 src/pages/Fiber/Channel/index.tsx create mode 100644 src/pages/Fiber/Peer/index.tsx create mode 100644 src/pages/Fiber/PeerList/index.module.scss create mode 100644 src/pages/Fiber/PeerList/index.tsx diff --git a/src/locales/en.json b/src/locales/en.json index a2b804a44..71344dc7b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1085,6 +1085,17 @@ "view_address": "View Address", "view_btc_utxo": "View BTC UTXO", "view_cell_info": "View Cell Info" + }, + "fiber": { + "peer": { + "peer_id": "ID", + "name": "Name", + "channels_count": "Channels", + "open_time": "First Channel", + "update_time": "Last Update", + "total_local_balance": "Local Balance", + "rpc_addr": "RPC Address" + } } } } diff --git a/src/pages/Fiber/Channel/index.tsx b/src/pages/Fiber/Channel/index.tsx new file mode 100644 index 000000000..6da23d743 --- /dev/null +++ b/src/pages/Fiber/Channel/index.tsx @@ -0,0 +1,5 @@ +const Channel = () => { + return
Channel
+} + +export default Channel diff --git a/src/pages/Fiber/Peer/index.tsx b/src/pages/Fiber/Peer/index.tsx new file mode 100644 index 000000000..972d07725 --- /dev/null +++ b/src/pages/Fiber/Peer/index.tsx @@ -0,0 +1,5 @@ +const Peer = () => { + return
Channel
+} + +export default Peer diff --git a/src/pages/Fiber/PeerList/index.module.scss b/src/pages/Fiber/PeerList/index.module.scss new file mode 100644 index 000000000..169c42400 --- /dev/null +++ b/src/pages/Fiber/PeerList/index.module.scss @@ -0,0 +1,123 @@ +@import '../../../styles//variables.module.scss'; + +.container { + text-wrap: nowrap; + display: flex; + margin: 24px 120px; + font-size: 1rem; + align-items: start; + + a { + color: var(--primary-color); + } + + table { + width: 100%; + text-align: left; + cursor: default; + + td, + th { + padding-right: 16px; + + &:last-child { + text-align: right; + padding-right: 0; + } + } + } + + svg { + pointer-events: none; + } + + button { + display: flex; + align-items: center; + appearance: none; + padding: 0; + border: none; + background: none; + cursor: pointer; + + &:hover { + color: var(--primary-color); + } + } + + .name { + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + } + + .peerId { + display: flex; + gap: 4px; + } + + .rpc { + display: flex; + align-items: center; + justify-content: end; + flex-wrap: nowrap; + gap: 4px; + + & > span:first-child { + display: block; + max-width: 500px; + overflow: hidden; + text-overflow: ellipsis; + } + } + + @media screen and (width < $extraLargeBreakPoint) { + margin: 24px 20px; + } + + @media screen and (width < 1030px) { + font-size: 14px; + + table { + th, + td { + &:nth-child(4) { + display: none; + } + } + } + } + + @media screen and (width < 810px) { + table { + th, + td { + &:last-child { + display: none; + } + } + } + } + + @media screen and (width < 600px) { + table { + th, + td { + &:nth-child(6) { + display: none; + } + } + } + } + + @media screen and (width < 420px) { + table { + th, + td { + &:nth-child(5) { + display: none; + } + } + } + } +} diff --git a/src/pages/Fiber/PeerList/index.tsx b/src/pages/Fiber/PeerList/index.tsx new file mode 100644 index 000000000..20d95e135 --- /dev/null +++ b/src/pages/Fiber/PeerList/index.tsx @@ -0,0 +1,152 @@ +import { useQuery } from '@tanstack/react-query' +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' +import { Tooltip } from 'antd' +import { CopyIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons' +import Content from '../../../components/Content' +import { useSetToast } from '../../../components/Toast' +import { explorerService } from '../../../services/ExplorerService' +import type { Fiber } from '../../../services/ExplorerService/fetcher' +import { shannonToCkb } from '../../../utils/util' +import { localeNumberString } from '../../../utils/number' +import { parseNumericAbbr } from '../../../utils/chart' +import styles from './index.module.scss' + +const fields = [ + { + key: 'name', + label: 'name', + transformer: (v: unknown, i: Fiber.Peer.ItemInList) => { + if (typeof v !== 'string') return v + return ( + +
+ {v} +
+
+ ) + }, + }, + { + key: 'channelsCount', + label: 'channels_count', + transformer: (v: unknown) => { + if (typeof v !== 'number') return v + return localeNumberString(v) + }, + }, + { + key: 'totalLocalBalance', + label: 'total_local_balance', + transformer: (v: unknown) => { + if (typeof v !== 'string' || Number.isNaN(+v)) return v + const ckb = shannonToCkb(v) + const amount = parseNumericAbbr(ckb) + return ( + + {`${amount} CKB`} + + ) + }, + }, + { + key: 'firstChannelOpenedAt', + label: 'open_time', + transformer: () => { + return Coming soon + }, + }, + { + key: 'lastChannelUpdatedAt', + label: 'update_time', + transformer: () => { + return Coming soon + }, + }, + { + key: 'peerId', + label: 'peer_id', + transformer: (v: unknown) => { + if (typeof v !== 'string') return v + return ( + + + {`${v.slice(0, 8)}...${v.slice(-8)}`} + + + + ) + }, + }, + { + key: 'rpcListeningAddr', + label: 'rpc_addr', + transformer: (v: unknown) => { + if (typeof v !== 'string') return v + return ( + + + {v} + + + + + + + ) + }, + }, +] + +const PeerList = () => { + const [t] = useTranslation() + const setToast = useSetToast() + + const { data } = useQuery({ + queryKey: ['fiber', 'peers'], + queryFn: () => explorerService.api.getFiberPeerList(), + }) + + const list = data?.data.fiberPeers ?? [] + const handleCopy = (e: React.SyntheticEvent) => { + const elm = e.target + if (!(elm instanceof HTMLElement)) return + const { copyText } = elm.dataset + if (!copyText) return + e.stopPropagation() + e.preventDefault() + navigator?.clipboard.writeText(copyText).then(() => setToast({ message: t('common.copied') })) + } + return ( + + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */} +
+ + + {fields.map(f => { + return + })} + + + {list.map(i => { + return ( + + {fields.map(f => { + const v = i[f.key as keyof typeof i] + return + })} + + ) + })} + +
{t(`fiber.peer.${f.label}`)}
{f.transformer?.(v, i) ?? v}
+
+
+ ) +} + +export default PeerList diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 2fbe2276c..d2fe6c81d 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -81,6 +81,11 @@ const Hasher = lazy(() => import('../pages/Tools/Hasher')) const BroadcastTx = lazy(() => import('../pages/Tools/BroadcastTx')) const CamelCase = lazy(() => import('../pages/Tools/CamelCase')) const MoleculeParser = lazy(() => import('../pages/Tools/MoleculeParser')) +// ====== +const FiberPeerList = lazy(() => import('../pages/Fiber/PeerList')) +const FiberPeer = lazy(() => import('../pages/Fiber/Peer')) +const FiberChannel = lazy(() => import('../pages/Fiber/Channel')) +// ====== const routes: RouteProps[] = [ { @@ -346,6 +351,18 @@ const routes: RouteProps[] = [ path: '/tools/molecule-parser', component: MoleculeParser, }, + { + path: '/fiber/peers', + component: FiberPeerList, + }, + { + path: '/fiber/peers/:id', + component: FiberPeer, + }, + { + path: '/fiber/channel/:id', + component: FiberChannel, + }, ] type PageErrorBoundaryState = { diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index bd68e347e..9d81a62c3 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1195,6 +1195,40 @@ export const apiFetcher = { }, }), getBtcTxList, + + // ================== + // Fiber + // ================== + getFiberPeerList: (page = 1, pageSize = 10) => { + return requesterV2 + .get( + `/fiber/peers?${new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + })}`, + ) + .then(res => + toCamelcase< + Response.Response<{ + fiberPeers: Fiber.Peer.ItemInList[] + // meta: { + // total: number + // pageSize: number + // } + }> + >(res.data), + ) + }, + + getFiberPeerDetail: (id: string) => { + return requesterV2 + .get(`/fiber/peers/${id}`) + .then(res => toCamelcase>(res.data)) + }, + + getFiberChannel: (id: string) => { + return requesterV2.get(`/fiber/channels/${id}`).then(res => toCamelcase(res.data)) + }, } // ==================== @@ -1386,3 +1420,42 @@ export interface RGBTransaction { rgbCellChanges: number rgbTxid: string } + +export namespace Fiber { + export namespace Peer { + interface Base { + peerId: string + rpcListeningAddr: string + firstChannelOpenedAt: null // TODO + lastChannelUpdatedAt: null // TODO + } + export interface ItemInList extends Base { + name: string + channelsCount: number + totalLocalBalance: string // shannon amount + } + + export interface Detail extends Base { + fiberChannels: { + peerId: string + channelId: string + stateName: string // TODO: should be enum + state_flags: [] // TODO + }[] + } + } + export namespace Channel { + export interface Detail { + channelId: string + stateName: string // TODO should be name + stateFlags: [] // TODO + shutdownAt: null // TODO + createdAt: string // utc time + updatedAt: string // utc time + localBalance: string // shannon + offeredTlcBalance: string // shannon + receivedTlcBalance: string // shannon + remoteBalance: string // shannon + } + } +} From 7c357e342ebed14beb0159be42a6c7a53aa37117 Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 24 Sep 2024 01:34:47 +0900 Subject: [PATCH 02/24] feat: add peer detail page --- .eslintrc.js | 1 + src/locales/en.json | 5 + src/pages/Fiber/Peer/index.module.scss | 76 +++++++++++++ src/pages/Fiber/Peer/index.tsx | 117 ++++++++++++++++++++- src/pages/Fiber/PeerList/index.module.scss | 12 ++- src/pages/Fiber/PeerList/index.tsx | 13 ++- src/routes/index.tsx | 2 +- 7 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 src/pages/Fiber/Peer/index.module.scss diff --git a/.eslintrc.js b/.eslintrc.js index 47549e586..86d7a97b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -115,6 +115,7 @@ module.exports = { 'jsx-a11y/label-has-associated-control': 'off', 'jsx-a11y/no-static-element-interactions': 'off', 'jsx-a11y/no-noninteractive-element-interactions': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', }, env: { jest: true, diff --git a/src/locales/en.json b/src/locales/en.json index 71344dc7b..97e30800c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1091,10 +1091,15 @@ "peer_id": "ID", "name": "Name", "channels_count": "Channels", + "channels": "Channels", "open_time": "First Channel", "update_time": "Last Update", "total_local_balance": "Local Balance", "rpc_addr": "RPC Address" + }, + "channel": { + "channel_id": "Channel ID", + "state": "State" } } } diff --git a/src/pages/Fiber/Peer/index.module.scss b/src/pages/Fiber/Peer/index.module.scss new file mode 100644 index 000000000..457ac40fb --- /dev/null +++ b/src/pages/Fiber/Peer/index.module.scss @@ -0,0 +1,76 @@ +@import '../../../styles//variables.module.scss'; + +.container { + text-wrap: nowrap; + display: flex; + flex-direction: column; + align-items: stretch; + margin: 24px 120px; + font-size: 1rem; + + a { + color: var(--primary-color); + } + + dl { + display: flex; + + dt, + dd { + display: flex; + align-items: center; + gap: 4px; + margin: 0; + padding: 0; + } + + dt::after { + content: ':'; + margin-right: 4px; + } + } + + table { + width: 100%; + text-align: left; + cursor: default; + + td, + th { + padding: 8px; + padding-right: 16px; + + &:last-child { + text-align: right; + } + } + + tbody { + tr:hover { + background: #ccc; + } + } + } + + svg { + pointer-events: none; + } + + button { + display: flex; + align-items: center; + appearance: none; + padding: 0; + border: none; + background: none; + cursor: pointer; + + &:hover { + color: var(--primary-color); + } + } + + @media screen and (width < $extraLargeBreakPoint) { + margin: 24px 20px; + } +} diff --git a/src/pages/Fiber/Peer/index.tsx b/src/pages/Fiber/Peer/index.tsx index 972d07725..5e2db3930 100644 --- a/src/pages/Fiber/Peer/index.tsx +++ b/src/pages/Fiber/Peer/index.tsx @@ -1,5 +1,120 @@ +import { useTranslation } from 'react-i18next' +import { Link, useParams } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { CopyIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons' +import { Tooltip } from 'antd' +import Content from '../../../components/Content' +import { explorerService } from '../../../services/ExplorerService' +import { useSetToast } from '../../../components/Toast' +// import type { Fiber } from '../../../services/ExplorerService/fetcher' +// import { shannonToCkb } from '../../../utils/util' +// import { localeNumberString } from '../../../utils/number' +// import { parseNumericAbbr } from '../../../utils/chart' +import styles from './index.module.scss' +import Loading from '../../../components/Loading' + const Peer = () => { - return
Channel
+ const [t] = useTranslation() + const { id } = useParams<{ id: string }>() + const setToast = useSetToast() + + const { data, isLoading } = useQuery({ + queryKey: ['fiber', 'peer', id], + queryFn: () => { + return explorerService.api.getFiberPeerDetail(id) + }, + enabled: !!id, + }) + if (isLoading) { + return + } + + if (!data) { + return
Fiber Peer Not Found
+ } + const peer = data.data + const channels = peer.fiberChannels + + const handleCopy = (e: React.SyntheticEvent) => { + const elm = e.target + if (!(elm instanceof HTMLElement)) return + const { copyText } = elm.dataset + if (!copyText) return + e.stopPropagation() + e.preventDefault() + navigator?.clipboard.writeText(copyText).then(() => setToast({ message: t('common.copied') })) + } + + return ( + +
+
+
+
{t('fiber.peer.peer_id')}
+
+ {peer.peerId} + +
+
+
+
{t('fiber.peer.rpc_addr')}
+
+ + {peer.rpcListeningAddr} + + + + + +
+
+
+
{t('fiber.peer.open_time')}
+
+ Coming soon +
+
+
+
{t('fiber.peer.update_time')}
+
+ Coming soon +
+
+
+
+
{`${t('fiber.peer.channels')}(${channels.length})`}
+ + + + + + + + + {channels.map(c => { + return ( + + + + + ) + })} + +
{t('fiber.channel.channel_id')}{t('fiber.channel.state')}
+ + + {`${c.channelId.slice(0, 10)}...${c.channelId.slice(-10)}`} + + + {c.stateName}
+
+
+
+ ) } export default Peer diff --git a/src/pages/Fiber/PeerList/index.module.scss b/src/pages/Fiber/PeerList/index.module.scss index 169c42400..471327740 100644 --- a/src/pages/Fiber/PeerList/index.module.scss +++ b/src/pages/Fiber/PeerList/index.module.scss @@ -5,7 +5,7 @@ display: flex; margin: 24px 120px; font-size: 1rem; - align-items: start; + align-items: flex-start; a { color: var(--primary-color); @@ -18,11 +18,17 @@ td, th { + padding: 8px; padding-right: 16px; &:last-child { text-align: right; - padding-right: 0; + } + } + + tbody { + tr:hover { + background: #ccc; } } } @@ -59,7 +65,7 @@ .rpc { display: flex; align-items: center; - justify-content: end; + justify-content: flex-end; flex-wrap: nowrap; gap: 4px; diff --git a/src/pages/Fiber/PeerList/index.tsx b/src/pages/Fiber/PeerList/index.tsx index 20d95e135..14a93a01a 100644 --- a/src/pages/Fiber/PeerList/index.tsx +++ b/src/pages/Fiber/PeerList/index.tsx @@ -71,7 +71,7 @@ const fields = [ return ( - {`${v.slice(0, 8)}...${v.slice(-8)}`} + {`${v.slice(0, 8)}...${v.slice(-8)}`} + ) } diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 9d81a62c3..84e7a82c5 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1227,7 +1227,27 @@ export const apiFetcher = { }, getFiberChannel: (id: string) => { - return requesterV2.get(`/fiber/channels/${id}`).then(res => toCamelcase(res.data)) + return requesterV2 + .get(`/fiber/channels/${id}`) + .then(res => toCamelcase>(res.data)) + }, + + addFiberPeer: (params: { rpc: string; id: string; name?: string }) => { + return requesterV2 + .post(`/fiber/peers`, { + name: params.name, + rpc_listening_addr: params.rpc, + peer_id: params.id, + }) + .catch(e => { + if (Array.isArray(e.response?.data)) { + const res = e.response.data[0] + if (res) { + throw new Error(res.title) + } + } + throw e + }) }, } From 5e9922b9fe68e8782c105d76b35862f4cf3de3b2 Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 24 Sep 2024 12:58:26 +0900 Subject: [PATCH 04/24] feat: add connect id and qrcode of peer --- src/locales/en.json | 3 +- src/pages/Fiber/Peer/index.module.scss | 3 +- src/pages/Fiber/Peer/index.tsx | 47 ++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 90866b49e..ba2c4785c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1095,7 +1095,8 @@ "open_time": "First Channel", "update_time": "Last Update", "total_local_balance": "Local Balance", - "rpc_addr": "RPC Address" + "rpc_addr": "RPC Address", + "connect_id": "Connect" }, "channel": { "channel_id": "Channel ID", diff --git a/src/pages/Fiber/Peer/index.module.scss b/src/pages/Fiber/Peer/index.module.scss index 2aae9fdbe..9839c7629 100644 --- a/src/pages/Fiber/Peer/index.module.scss +++ b/src/pages/Fiber/Peer/index.module.scss @@ -70,7 +70,8 @@ } } - .id { + .id, + .connectId { overflow: hidden; & > span:first-child { diff --git a/src/pages/Fiber/Peer/index.tsx b/src/pages/Fiber/Peer/index.tsx index b474c791c..955db08de 100644 --- a/src/pages/Fiber/Peer/index.tsx +++ b/src/pages/Fiber/Peer/index.tsx @@ -1,7 +1,9 @@ +import { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { Link, useParams } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' import { CopyIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons' +import QRCode from 'qrcode' import { Tooltip } from 'antd' import Content from '../../../components/Content' import { explorerService } from '../../../services/ExplorerService' @@ -12,6 +14,8 @@ import Loading from '../../../components/Loading' const Peer = () => { const [t] = useTranslation() const { id } = useParams<{ id: string }>() + const qrRef = useRef(null) + const setToast = useSetToast() const { data, isLoading } = useQuery({ @@ -21,14 +25,37 @@ const Peer = () => { }, enabled: !!id, }) + + const peer = data?.data + + const connectId = peer ? `${peer.peerId}@${peer.rpcListeningAddr}` : null + + useEffect(() => { + const cvs = qrRef.current + if (!cvs || !connectId) return + QRCode.toCanvas( + cvs, + connectId, + { + margin: 5, + errorCorrectionLevel: 'H', + width: 144, + }, + err => { + if (err) { + console.error(err) + } + }, + ) + }, [qrRef, connectId]) + if (isLoading) { return } - if (!data) { + if (!peer) { return
Fiber Peer Not Found
} - const peer = data.data const channels = peer.fiberChannels const handleCopy = (e: React.SyntheticEvent) => { @@ -68,6 +95,22 @@ const Peer = () => { +
+
{t('fiber.peer.connect_id')}
+
+ + {connectId} + + +
+
+ {connectId ? ( +
+ +
+ ) : null}
{t('fiber.peer.open_time')}
From 695ab57d76b3906f47cb94b6ce06489888f37125 Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 24 Sep 2024 18:08:12 +0900 Subject: [PATCH 05/24] feat: multiple rpc address --- src/pages/Fiber/Peer/index.tsx | 63 +++++++++++++++------- src/pages/Fiber/PeerList/index.module.scss | 7 +++ src/pages/Fiber/PeerList/index.tsx | 19 +++++-- src/services/ExplorerService/fetcher.ts | 2 +- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/pages/Fiber/Peer/index.tsx b/src/pages/Fiber/Peer/index.tsx index 955db08de..f7e94ee0a 100644 --- a/src/pages/Fiber/Peer/index.tsx +++ b/src/pages/Fiber/Peer/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Link, useParams } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' @@ -13,6 +13,7 @@ import Loading from '../../../components/Loading' const Peer = () => { const [t] = useTranslation() + const [rpcAddr, setRpcAddr] = useState('') const { id } = useParams<{ id: string }>() const qrRef = useRef(null) @@ -28,7 +29,23 @@ const Peer = () => { const peer = data?.data - const connectId = peer ? `${peer.peerId}@${peer.rpcListeningAddr}` : null + const connectId = peer && rpcAddr ? `${peer.peerId}@${rpcAddr}` : null + + const handleRpcAddrSelect = (e: React.ChangeEvent) => { + e.stopPropagation() + e.preventDefault() + const r = e.currentTarget.value + if (r) { + setRpcAddr(r) + } + } + + useEffect(() => { + const firstRpcAddr = peer?.rpcListeningAddr[0] + if (firstRpcAddr) { + setRpcAddr(firstRpcAddr) + } + }, [peer, setRpcAddr]) useEffect(() => { const cvs = qrRef.current @@ -82,30 +99,40 @@ const Peer = () => {
-
{t('fiber.peer.rpc_addr')}
+
+ +
- - {peer.rpcListeningAddr} - + - +
-
-
{t('fiber.peer.connect_id')}
-
- - {connectId} - - -
-
+ {connectId ? ( +
+
{t('fiber.peer.connect_id')}
+
+ + {connectId} + + +
+
+ ) : null} {connectId ? (
diff --git a/src/pages/Fiber/PeerList/index.module.scss b/src/pages/Fiber/PeerList/index.module.scss index 8706ec6c5..a4051c4e2 100644 --- a/src/pages/Fiber/PeerList/index.module.scss +++ b/src/pages/Fiber/PeerList/index.module.scss @@ -75,6 +75,13 @@ overflow: hidden; text-overflow: ellipsis; } + + button, + a, + .more { + display: flex; + align-items: center; + } } .balance { diff --git a/src/pages/Fiber/PeerList/index.tsx b/src/pages/Fiber/PeerList/index.tsx index 7131f0717..6ae8f3648 100644 --- a/src/pages/Fiber/PeerList/index.tsx +++ b/src/pages/Fiber/PeerList/index.tsx @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' import { Tooltip } from 'antd' -import { CopyIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons' +import { CopyIcon, InfoCircledIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons' import Content from '../../../components/Content' import { useSetToast } from '../../../components/Toast' import { explorerService } from '../../../services/ExplorerService' @@ -89,18 +89,27 @@ const fields = [ key: 'rpcListeningAddr', label: 'rpc_addr', transformer: (v: unknown) => { - if (typeof v !== 'string') return v + if (!Array.isArray(v)) return v + const rpcAddr = v[0] + if (!rpcAddr || typeof rpcAddr !== 'string') return v return ( - - {v} + + {rpcAddr} - + + {v.length > 1 ? ( + + + + + + ) : null} ) }, diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 84e7a82c5..104df1535 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1445,7 +1445,7 @@ export namespace Fiber { export namespace Peer { interface Base { peerId: string - rpcListeningAddr: string + rpcListeningAddr: string[] firstChannelOpenedAt: null // TODO lastChannelUpdatedAt: null // TODO } From e5e773c877674d5583d54d40c6b14e08dd10ce9c Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 24 Sep 2024 18:32:12 +0900 Subject: [PATCH 06/24] feat: add peer info on channel page --- src/pages/Fiber/Channel/fiber.module.scss | 11 ++++++++++ src/pages/Fiber/Channel/fiber.tsx | 25 +++++++++++++++++++++++ src/pages/Fiber/Channel/index.module.scss | 14 +++++++++++++ src/pages/Fiber/Channel/index.tsx | 7 ++++--- src/services/ExplorerService/fetcher.ts | 7 +++++++ 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/pages/Fiber/Channel/fiber.module.scss create mode 100644 src/pages/Fiber/Channel/fiber.tsx diff --git a/src/pages/Fiber/Channel/fiber.module.scss b/src/pages/Fiber/Channel/fiber.module.scss new file mode 100644 index 000000000..d2e8e270a --- /dev/null +++ b/src/pages/Fiber/Channel/fiber.module.scss @@ -0,0 +1,11 @@ +.container { + display: flex; + flex-direction: column; + + .id { + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; + } +} diff --git a/src/pages/Fiber/Channel/fiber.tsx b/src/pages/Fiber/Channel/fiber.tsx new file mode 100644 index 000000000..b0c9462b5 --- /dev/null +++ b/src/pages/Fiber/Channel/fiber.tsx @@ -0,0 +1,25 @@ +import { CopyIcon } from '@radix-ui/react-icons' +import { Tooltip } from 'antd' +import { Link } from 'react-router-dom' +import type { Fiber } from '../../../services/ExplorerService/fetcher' +import styles from './fiber.module.scss' + +const FiberPeerInfo = ({ peer }: { peer: Fiber.Channel.Peer }) => { + return ( +
+ + {peer.name || 'Untitled Node'} + +
+ + {`${peer.peerId.slice(0, 8)}...${peer.peerId.slice(-8)}`} + + +
+
+ ) +} + +export default FiberPeerInfo diff --git a/src/pages/Fiber/Channel/index.module.scss b/src/pages/Fiber/Channel/index.module.scss index 0ed140108..7fdd5c4b0 100644 --- a/src/pages/Fiber/Channel/index.module.scss +++ b/src/pages/Fiber/Channel/index.module.scss @@ -8,6 +8,10 @@ margin: 24px 120px; font-size: 1rem; + a { + color: var(--primary-color); + } + svg { pointer-events: none; } @@ -75,6 +79,16 @@ border: 1px solid #ccc; padding: 32px; + dl:first-child { + dt { + align-items: flex-start; + } + + a { + font-weight: 400; + } + } + dl:last-child { margin: 0; } diff --git a/src/pages/Fiber/Channel/index.tsx b/src/pages/Fiber/Channel/index.tsx index eb6782ff3..71139de5f 100644 --- a/src/pages/Fiber/Channel/index.tsx +++ b/src/pages/Fiber/Channel/index.tsx @@ -11,6 +11,7 @@ import { explorerService } from '../../../services/ExplorerService' import styles from './index.module.scss' import { shannonToCkb } from '../../../utils/util' import { localeNumberString } from '../../../utils/number' +import FiberPeerInfo from './fiber' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -99,8 +100,8 @@ const Channel = () => {
Fiber Peer
-
- Coming soon(Local) +
+
@@ -119,7 +120,7 @@ const Channel = () => {
Fiber Peer
- Coming soon(Remote) +
diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 104df1535..78052a6ac 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1465,6 +1465,11 @@ export namespace Fiber { } } export namespace Channel { + export interface Peer { + name?: string + peerId: string + rpcListeningAddr: string[] + } export interface Detail { channelId: string stateName: string // TODO should be name @@ -1476,6 +1481,8 @@ export namespace Fiber { offeredTlcBalance: string // shannon receivedTlcBalance: string // shannon remoteBalance: string // shannon + localPeer: Peer + remotePeer: Peer } } } From db708b12dbd7d1157a5c671d896b81a93b401ced Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 25 Sep 2024 13:04:59 +0900 Subject: [PATCH 07/24] feat: stylize peer list --- .../Fiber/PeerList/AddPeerForm.module.scss | 74 ++++++++++++ src/pages/Fiber/PeerList/AddPeerForm.tsx | 105 ++++++++++++++++++ src/pages/Fiber/PeerList/index.module.scss | 61 +++------- src/pages/Fiber/PeerList/index.tsx | 47 ++------ src/styles/table.module.scss | 43 +++++++ 5 files changed, 248 insertions(+), 82 deletions(-) create mode 100644 src/pages/Fiber/PeerList/AddPeerForm.module.scss create mode 100644 src/pages/Fiber/PeerList/AddPeerForm.tsx create mode 100644 src/styles/table.module.scss diff --git a/src/pages/Fiber/PeerList/AddPeerForm.module.scss b/src/pages/Fiber/PeerList/AddPeerForm.module.scss new file mode 100644 index 000000000..2fa4288b5 --- /dev/null +++ b/src/pages/Fiber/PeerList/AddPeerForm.module.scss @@ -0,0 +1,74 @@ +.container { + font-size: 1rem; + + &::backdrop { + background: rgb(0 0 0 / 40%); + } + + form { + background: #fff; + position: fixed; + transform: translateX(-50%) translateY(-50%); + font-size: 0.875rem; + width: min-content; + margin: 16px auto; + top: 50%; + left: 50%; + padding: 23px 40px; + border-radius: 4px; + } + + h3 { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 22px; + border-bottom: 1px solid #e5e5e5; + margin-bottom: 16px; + + button { + svg { + color: #000; + } + } + } + + fieldset { + display: flex; + flex-direction: column; + margin-bottom: 16px; + } + + label { + display: flex; + align-items: center; + gap: 4px; + color: #666; + margin-bottom: 12px; + + &[data-required]::after { + content: '*'; + color: var(--accent-color); + } + } + + input { + width: 320px; + padding: 9px 12px; + border: 1px solid #e5e5e5; + border-radius: 4px; + font-size: inherit; + } + + button[type='submit'] { + padding: 14px 40px; + color: #fff; + background: var(--primary-color); + border-radius: 4px; + font-size: 1rem; + } + + @media screen and (width < 1030px) { + font-size: 14px; + } +} diff --git a/src/pages/Fiber/PeerList/AddPeerForm.tsx b/src/pages/Fiber/PeerList/AddPeerForm.tsx new file mode 100644 index 000000000..a0d2ae5d2 --- /dev/null +++ b/src/pages/Fiber/PeerList/AddPeerForm.tsx @@ -0,0 +1,105 @@ +import { Cross2Icon } from '@radix-ui/react-icons' +import { type FC, useRef, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useSetToast } from '../../../components/Toast' +import { explorerService } from '../../../services/ExplorerService' +import styles from './AddPeerForm.module.scss' + +interface AddPeerFormProps { + onSuccess: () => void +} +const AddPeerForm: FC = ({ onSuccess }) => { + const dialogRef = useRef(null) + const [t] = useTranslation() + const setToast = useSetToast() + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + dialogRef.current?.close() + } + } + + window.addEventListener('keydown', handleKeyDown) + + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, []) + + const handleClose = () => { + dialogRef.current?.close() + } + + const handleSubmit = async (e: React.SyntheticEvent) => { + e.preventDefault() + e.stopPropagation() + const form = e.currentTarget + + const { peer_id, peer_name, rpc } = form + const params: Parameters[0] = { + rpc: rpc instanceof HTMLInputElement ? rpc.value : '', + id: peer_id instanceof HTMLInputElement ? peer_id.value : '', + name: peer_name instanceof HTMLInputElement ? peer_name.value : undefined, + } + + if (params.rpc && params.id) { + try { + await explorerService.api.addFiberPeer(params) + setToast({ message: 'submitted' }) + onSuccess() + } catch (e) { + const message = e instanceof Error ? e.message : JSON.stringify(e) + setToast({ message }) + } + } + } + + const handleClickOutside = (e: React.MouseEvent) => { + if (e.target === dialogRef.current) { + dialogRef.current?.close() + } + } + + const handleOpen = () => { + dialogRef.current?.showModal() + } + + return ( + <> + + + +
+

+ Add Fiber Peer + +

+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + ) +} + +export default AddPeerForm diff --git a/src/pages/Fiber/PeerList/index.module.scss b/src/pages/Fiber/PeerList/index.module.scss index a4051c4e2..3daa3ca18 100644 --- a/src/pages/Fiber/PeerList/index.module.scss +++ b/src/pages/Fiber/PeerList/index.module.scss @@ -1,36 +1,19 @@ -@import '../../../styles//variables.module.scss'; +@import '../../../styles/variables.module'; +@import '../../../styles/table.module'; .container { text-wrap: nowrap; display: flex; + flex-direction: column; margin: 24px 120px; font-size: 1rem; - align-items: flex-start; a { color: var(--primary-color); } table { - width: 100%; - text-align: left; - cursor: default; - - td, - th { - padding: 8px; - padding-right: 16px; - - &:last-child { - text-align: right; - } - } - - tbody { - tr:hover { - background: #ccc; - } - } + @extend %base-table; } svg { @@ -84,6 +67,20 @@ } } + .header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 1.5rem; + margin-bottom: 20px; + + button { + font-size: 0.875rem; + color: var(--primary-color); + padding-left: 8px; + } + } + .balance { display: flex; flex-direction: column; @@ -139,25 +136,3 @@ } } } - -.addFiberPeer { - width: min-content; - margin: 16px auto; - - fieldset { - margin: 8px 0; - } - - input { - width: 250px; - padding: 4px 8px; - } - - button { - padding: 4px 16px; - } - - @media screen and (width < 1030px) { - font-size: 14px; - } -} diff --git a/src/pages/Fiber/PeerList/index.tsx b/src/pages/Fiber/PeerList/index.tsx index 6ae8f3648..27e421281 100644 --- a/src/pages/Fiber/PeerList/index.tsx +++ b/src/pages/Fiber/PeerList/index.tsx @@ -11,6 +11,7 @@ import { shannonToCkb } from '../../../utils/util' import { localeNumberString } from '../../../utils/number' import { parseNumericAbbr } from '../../../utils/chart' import styles from './index.module.scss' +import AddPeerForm from './AddPeerForm' const fields = [ { @@ -137,33 +138,14 @@ const PeerList = () => { navigator?.clipboard.writeText(copyText).then(() => setToast({ message: t('common.copied') })) } - const handleAddPeer = async (e: React.SyntheticEvent) => { - e.preventDefault() - e.stopPropagation() - const form = e.currentTarget - - const { peer_id, peer_name, rpc } = form - const params: Parameters[0] = { - rpc: rpc instanceof HTMLInputElement ? rpc.value : '', - id: peer_id instanceof HTMLInputElement ? peer_id.value : '', - name: peer_name instanceof HTMLInputElement ? peer_name.value : undefined, - } - - if (params.rpc && params.id) { - try { - await explorerService.api.addFiberPeer(params) - setToast({ message: 'submitted' }) - refetchList() - } catch (e) { - const message = e instanceof Error ? e.message : JSON.stringify(e) - setToast({ message }) - } - } - } - return (
+

+ CKB Fiber Peers + + refetchList()} /> +

@@ -172,6 +154,7 @@ const PeerList = () => { })} +
{list.map(i => { return ( @@ -184,23 +167,9 @@ const PeerList = () => { ) })} + {/*
*/}
-
-
- - -
-
- - -
-
- - -
- -
) } diff --git a/src/styles/table.module.scss b/src/styles/table.module.scss new file mode 100644 index 000000000..c5052bc78 --- /dev/null +++ b/src/styles/table.module.scss @@ -0,0 +1,43 @@ +%base-table { + width: 100%; + text-align: left; + cursor: default; + overflow: hidden; + border-radius: 6px; + box-shadow: rgb(0 0 0 / 12%) 0 2px 6px 0; + font-size: 0.875rem; + + tr { + background: #fff; + } + + td, + th { + padding: 8px; + padding-right: 1rem; + + &:first-child { + padding-left: 40px; + } + + &:last-child { + text-align: right; + } + } + + thead { + th { + height: 3.5rem; + } + } + + tbody { + tr:hover { + background: #ccc; + } + } + + .tableSeparator { + height: 4px; + } +} From 6ab4c64f8c92b36fbb592ebc3d75c59c607d6cde Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 25 Sep 2024 14:57:39 +0900 Subject: [PATCH 08/24] feat: stylize peer page --- src/pages/Fiber/Peer/index.module.scss | 63 ++++++++- src/pages/Fiber/Peer/index.tsx | 168 ++++++++++++------------ src/services/ExplorerService/fetcher.ts | 8 +- 3 files changed, 146 insertions(+), 93 deletions(-) diff --git a/src/pages/Fiber/Peer/index.module.scss b/src/pages/Fiber/Peer/index.module.scss index 9839c7629..7313b7d28 100644 --- a/src/pages/Fiber/Peer/index.module.scss +++ b/src/pages/Fiber/Peer/index.module.scss @@ -70,6 +70,20 @@ } } + .overview { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + background: #fff; + border-radius: 6px; + padding: 16px; + box-shadow: 0 2px 6px 0 #4d4d4d33; + + .fields { + overflow: hidden; + } + } + .id, .connectId { overflow: hidden; @@ -81,15 +95,50 @@ } } - .channels, - .transactions { + .activities { + display: flex; + gap: 16px; margin-top: 16px; - border-top: 1px solid #ccc; - padding-top: 16px; - h3 { - margin: 0; - padding: 0; + .channels, + .transactions { + flex: 1; + background: #fff; + border-radius: 6px; + padding: 16px; + box-shadow: 0 2px 6px 0 #4d4d4d33; + + h3 { + margin: 0; + padding: 0; + } + } + + @media screen and (width < 960px) { + flex-direction: column; + } + + @media screen and (width < 500px) { + thead { + display: none; + } + + tbody { + tr { + display: flex; + flex-direction: column; + padding: 16px 0; + + &:not(:last-child) { + border-bottom: 1px solid #ccc; + } + + td { + text-align: left; + padding: 0; + } + } + } } } diff --git a/src/pages/Fiber/Peer/index.tsx b/src/pages/Fiber/Peer/index.tsx index f7e94ee0a..e096dee4d 100644 --- a/src/pages/Fiber/Peer/index.tsx +++ b/src/pages/Fiber/Peer/index.tsx @@ -88,99 +88,103 @@ const Peer = () => { return (
-
-
-
{t('fiber.peer.peer_id')}
-
- {peer.peerId} - -
-
-
-
- -
-
- - - - - -
-
- {connectId ? ( +
+
-
{t('fiber.peer.connect_id')}
-
- - {connectId} - -
- ) : null} +
+
+ +
+
+ + + + + +
+
+ {connectId ? ( +
+
{t('fiber.peer.connect_id')}
+
+ + {connectId} + + +
+
+ ) : null} +
+
{t('fiber.peer.open_time')}
+
+ Coming soon +
+
+
+
{t('fiber.peer.update_time')}
+
+ Coming soon +
+
+
{connectId ? (
) : null} -
-
{t('fiber.peer.open_time')}
-
- Coming soon -
-
-
-
{t('fiber.peer.update_time')}
-
- Coming soon -
-
-
-
-

{`${t('fiber.peer.channels')}(${channels.length})`}

- - - - - - - - - {channels.map(c => { - return ( - - - - - ) - })} - -
{t('fiber.channel.channel_id')}{t('fiber.channel.state')}
- - - {`${c.channelId.slice(0, 10)}...${c.channelId.slice(-10)}`} - - - {c.stateName}
-
-

Open | Close Transactions

- Coming soon +
+
+

{`${t('fiber.peer.channels')}(${channels.length})`}

+ + + + + + + + + {channels.map(c => { + return ( + + + + + ) + })} + +
{t('fiber.channel.channel_id')}{t('fiber.channel.state')}
+ + + {`${c.channelId.slice(0, 10)}...${c.channelId.slice(-10)}`} + + + {c.stateName}
+
+
+

Open | Close Transactions

+ Coming soon +
diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 78052a6ac..5b29727e3 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1211,10 +1211,10 @@ export const apiFetcher = { toCamelcase< Response.Response<{ fiberPeers: Fiber.Peer.ItemInList[] - // meta: { - // total: number - // pageSize: number - // } + meta: { + total: number + pageSize: number + } }> >(res.data), ) From 7722dc474202d6a7fe72fcf2eb55287b351b6f0e Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 27 Sep 2024 03:27:45 +0000 Subject: [PATCH 09/24] feat: add pagination for peer list --- src/pages/Fiber/Channel/index.module.scss | 3 +- src/pages/Fiber/Pagination/index.module.scss | 95 ++++++++++++++++++++ src/pages/Fiber/Pagination/index.tsx | 68 ++++++++++++++ src/pages/Fiber/Peer/index.module.scss | 9 +- src/pages/Fiber/PeerList/index.module.scss | 14 ++- src/pages/Fiber/PeerList/index.tsx | 11 ++- src/styles/card.module.scss | 6 ++ 7 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 src/pages/Fiber/Pagination/index.module.scss create mode 100644 src/pages/Fiber/Pagination/index.tsx create mode 100644 src/styles/card.module.scss diff --git a/src/pages/Fiber/Channel/index.module.scss b/src/pages/Fiber/Channel/index.module.scss index 7fdd5c4b0..e84dcc914 100644 --- a/src/pages/Fiber/Channel/index.module.scss +++ b/src/pages/Fiber/Channel/index.module.scss @@ -1,4 +1,5 @@ -@import '../../../styles//variables.module.scss'; +@import '../../../styles/variables.module'; +@import '../../../styles/card.module'; .container { text-wrap: nowrap; diff --git a/src/pages/Fiber/Pagination/index.module.scss b/src/pages/Fiber/Pagination/index.module.scss new file mode 100644 index 000000000..2a189997a --- /dev/null +++ b/src/pages/Fiber/Pagination/index.module.scss @@ -0,0 +1,95 @@ +@import '../../../styles/variables.module'; + +.container { + display: flex; + background: #fff; + justify-content: space-between; + height: 34px; + + form { + display: flex; + gap: 20px; + + label { + display: flex; + align-items: center; + } + + button { + display: flex; + justify-content: center; + width: 50px; + background: #f5f5f5; + border-radius: 6px; + } + + input { + width: 100; + padding: 0 10px; + color: #969696; + border-radius: 6px; + background: #f5f5f5; + border: none; + } + } + + .pager { + display: flex; + gap: 20px; + + .pageNo { + display: flex; + align-items: center; + } + } + + a { + display: flex; + justify-content: center; + align-items: center; + color: #969696; + + &:hover { + color: var(--primary-color); + } + + &[aria-disabled='true'] { + pointer-events: none; + opacity: 0.5; + } + + &[data-role='first-page'], + &[data-role='last-page'] { + width: 50px; + background: #f5f5f5; + border-radius: 6px; + } + + &[data-role='prev-page'], + &[data-role='next-page'] { + width: 30px; + height: 30px; + background: #f5f5f5; + border-radius: 6px; + } + } + + @media screen and (width < $mobileBreakPoint) { + font-size: 12px; + + .pager { + gap: 4px; + } + + a { + &[data-role='first-page'], + &[data-role='last-page'] { + display: none; + } + } + + .pageNo { + order: 3; + } + } +} diff --git a/src/pages/Fiber/Pagination/index.tsx b/src/pages/Fiber/Pagination/index.tsx new file mode 100644 index 000000000..5e60d5cba --- /dev/null +++ b/src/pages/Fiber/Pagination/index.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import { Link, useHistory } from 'react-router-dom' +import { ChevronLeftIcon, ChevronRightIcon } from '@radix-ui/react-icons' +import { useSearchParams } from '../../../hooks' +import styles from './index.module.scss' + +interface PaginationProps { + totalPages: number +} + +const getPageUrl = (page: number, search: URLSearchParams) => { + search.set('page', page.toString()) + return `${window.location.pathname}?${search.toString()}` +} + +const Pagination: React.FC = ({ totalPages }) => { + const history = useHistory() + const { page: p } = useSearchParams('page') + + // Get the current page from the URL query parameter, defaulting to 1 if not set + const currentPage = Number(p) || 1 + const search = new URLSearchParams(window.location.search) + + const handleGo = (e: React.SyntheticEvent) => { + e.stopPropagation() + e.preventDefault() + + const { page } = e.currentTarget + if (!(page instanceof HTMLInputElement)) return + const go = page.value + if (+go < 1) { + history.push(getPageUrl(1, search)) + return + } + if (+go > totalPages) { + history.push(getPageUrl(totalPages, search)) + return + } + history.push(getPageUrl(+go, search)) + } + + return ( +
+
+ + First + + + + + {`Page ${currentPage} of ${totalPages}`} + + + + + Last + +
+
+ + + +
+
+ ) +} + +export default Pagination diff --git a/src/pages/Fiber/Peer/index.module.scss b/src/pages/Fiber/Peer/index.module.scss index 7313b7d28..ace74b7cd 100644 --- a/src/pages/Fiber/Peer/index.module.scss +++ b/src/pages/Fiber/Peer/index.module.scss @@ -1,4 +1,5 @@ -@import '../../../styles//variables.module.scss'; +@import '../../../styles/variables.module'; +@import '../../../styles/card.module'; .container { text-wrap: nowrap; @@ -71,13 +72,11 @@ } .overview { + @extend %base-card; + display: flex; justify-content: space-between; flex-wrap: wrap; - background: #fff; - border-radius: 6px; - padding: 16px; - box-shadow: 0 2px 6px 0 #4d4d4d33; .fields { overflow: hidden; diff --git a/src/pages/Fiber/PeerList/index.module.scss b/src/pages/Fiber/PeerList/index.module.scss index 3daa3ca18..25e11803e 100644 --- a/src/pages/Fiber/PeerList/index.module.scss +++ b/src/pages/Fiber/PeerList/index.module.scss @@ -14,6 +14,10 @@ table { @extend %base-table; + + tr[data-role='pagination']:hover { + background: #fff; + } } svg { @@ -105,10 +109,12 @@ @media screen and (width < 810px) { table { - th, - td { - &:last-child { - display: none; + tr:not([data-role='pagination']) { + th, + td { + &:last-child { + display: none; + } } } } diff --git a/src/pages/Fiber/PeerList/index.tsx b/src/pages/Fiber/PeerList/index.tsx index 27e421281..d2620492c 100644 --- a/src/pages/Fiber/PeerList/index.tsx +++ b/src/pages/Fiber/PeerList/index.tsx @@ -12,6 +12,8 @@ import { localeNumberString } from '../../../utils/number' import { parseNumericAbbr } from '../../../utils/chart' import styles from './index.module.scss' import AddPeerForm from './AddPeerForm' +import Pagination from '../Pagination' +import { PAGE_SIZE } from '../../../constants/common' const fields = [ { @@ -127,6 +129,8 @@ const PeerList = () => { }) const list = data?.data.fiberPeers ?? [] + const pageInfo = data?.data.meta ?? { total: 1, pageSize: PAGE_SIZE } + const totalPages = Math.ceil(pageInfo.total / pageInfo.pageSize) const handleCopy = (e: React.SyntheticEvent) => { const elm = e.target @@ -166,8 +170,13 @@ const PeerList = () => { ) })} +
+ + + + + - {/*
*/}
diff --git a/src/styles/card.module.scss b/src/styles/card.module.scss new file mode 100644 index 000000000..6d5d707bd --- /dev/null +++ b/src/styles/card.module.scss @@ -0,0 +1,6 @@ +%base-card { + background: #fff; + border-radius: 6px; + padding: 16px; + box-shadow: 0 2px 6px 0 #4d4d4d33; +} From e02986bee3f61ef82793c0e780f09c1a5ba7e72a Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 27 Sep 2024 03:34:09 +0000 Subject: [PATCH 10/24] feat: stylize channel page --- src/pages/Fiber/Channel/index.module.scss | 14 ++++- src/pages/Fiber/Channel/index.tsx | 76 ++++++++++++----------- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/pages/Fiber/Channel/index.module.scss b/src/pages/Fiber/Channel/index.module.scss index e84dcc914..9d063904b 100644 --- a/src/pages/Fiber/Channel/index.module.scss +++ b/src/pages/Fiber/Channel/index.module.scss @@ -49,6 +49,10 @@ } } + .overview { + @extend %base-card; + } + .id { overflow: hidden; @@ -60,8 +64,9 @@ } .transactions { - margin-top: 16px; - border-top: 1px solid #ccc; + @extend %base-card; + + margin-top: 8px; padding-top: 16px; h3 { @@ -71,12 +76,15 @@ } .peers { + margin-top: 8px; display: flex; flex-wrap: wrap; .local, .remote { - flex: 1 0 50%; + @extend %base-card; + + flex: 1 0 40%; border: 1px solid #ccc; padding: 32px; diff --git a/src/pages/Fiber/Channel/index.tsx b/src/pages/Fiber/Channel/index.tsx index 71139de5f..d5787feda 100644 --- a/src/pages/Fiber/Channel/index.tsx +++ b/src/pages/Fiber/Channel/index.tsx @@ -54,48 +54,50 @@ const Channel = () => {
-
-
{t('fiber.channel.channel_id')}
-
- {channel.channelId} - -
-
-
-
{t('fiber.channel.state')}
-
{channel.stateName}
-
+
+
+
{t('fiber.channel.channel_id')}
+
+ {channel.channelId} + +
+
+
+
{t('fiber.channel.state')}
+
{channel.stateName}
+
-
-
{t('fiber.channel.balance')}
-
{`${localeNumberString( - shannonToCkb(totalBalance.toFormat({ groupSeparator: '' })), - )} CKB(Total) | ${localeNumberString( - shannonToCkb(totalTLCBalance.toFormat({ groupSeparator: '' })), - )} CKB(TLC)`}
-
-
-
{t('fiber.channel.open_time')}
-
- -
-
-
-
{t('fiber.channel.update_time')}
-
- -
-
- {channel.shutdownAt ? (
-
{t('fiber.channel.shutdown_time')}
+
{t('fiber.channel.balance')}
+
{`${localeNumberString( + shannonToCkb(totalBalance.toFormat({ groupSeparator: '' })), + )} CKB(Total) | ${localeNumberString( + shannonToCkb(totalTLCBalance.toFormat({ groupSeparator: '' })), + )} CKB(TLC)`}
+
+
+
{t('fiber.channel.open_time')}
+
+ +
+
+
+
{t('fiber.channel.update_time')}
- +
- ) : null} + {channel.shutdownAt ? ( +
+
{t('fiber.channel.shutdown_time')}
+
+ +
+
+ ) : null} +
From c6c9d0907aa433d8ecaff8cfedbcfb80a4653f8a Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 15 Oct 2024 02:27:42 +0900 Subject: [PATCH 11/24] feat: add graph node list --- src/locales/en.json | 10 + .../Fiber/GraphNodeList/index.module.scss | 156 +++++++++++++++ src/pages/Fiber/GraphNodeList/index.tsx | 188 ++++++++++++++++++ src/pages/Fiber/PeerList/index.tsx | 2 +- src/routes/index.tsx | 5 + src/services/ExplorerService/fetcher.ts | 72 ++++++- 6 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 src/pages/Fiber/GraphNodeList/index.module.scss create mode 100644 src/pages/Fiber/GraphNodeList/index.tsx diff --git a/src/locales/en.json b/src/locales/en.json index ba2c4785c..6e0779a7b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1111,6 +1111,16 @@ "tlc_balance": "TLC Balance", "offered": "Offered", "received": "Received" + }, + "graph": { + "node": { + "name": "Name", + "auto_accept_min_ckb_funding_amount": "Auto-accepting Threshold", + "first_seen": "First Seen", + "node_id": "Node ID", + "chain_hash": "Chain Hash", + "addresses": "Addresses" + } } } } diff --git a/src/pages/Fiber/GraphNodeList/index.module.scss b/src/pages/Fiber/GraphNodeList/index.module.scss new file mode 100644 index 000000000..ba12c0528 --- /dev/null +++ b/src/pages/Fiber/GraphNodeList/index.module.scss @@ -0,0 +1,156 @@ +@import '../../../styles/variables.module'; +@import '../../../styles/table.module'; + +.container { + text-wrap: nowrap; + display: flex; + flex-direction: column; + margin: 24px 120px; + font-size: 1rem; + + a { + color: var(--primary-color); + } + + table { + @extend %base-table; + + tr[data-role='pagination']:hover { + background: #fff; + } + } + + svg { + pointer-events: none; + } + + button { + display: flex; + align-items: center; + appearance: none; + padding: 0; + border: none; + background: none; + cursor: pointer; + + &:hover { + color: var(--primary-color); + } + } + + .name { + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + } + + .nodeId, + .chainHash { + display: flex; + gap: 4px; + } + + .address { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: nowrap; + gap: 4px; + + & > span:first-child { + display: block; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; + } + + button, + a, + .more { + display: flex; + align-items: center; + } + } + + .header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 1.5rem; + margin-bottom: 20px; + + button { + font-size: 0.875rem; + color: var(--primary-color); + padding-left: 8px; + } + } + + .amount { + display: flex; + flex-direction: column; + } + + @media screen and (width < $extraLargeBreakPoint) { + margin: 24px 20px; + } + + @media screen and (width < 1330px) { + font-size: 14px; + + table { + th, + td { + &:nth-child(6) { + display: none; + } + } + } + } + + @media screen and (width < 810px) { + table { + tr:not([data-role='pagination']) { + th, + td { + &:last-child { + display: none; + } + } + } + } + } + + @media screen and (width < 900px) { + table { + th, + td { + &:nth-child(5) { + display: none; + } + } + } + } + + @media screen and (width < 700px) { + table { + th, + td { + &:nth-child(3) { + display: none; + } + } + } + } + + @media screen and (width < 520px) { + table { + th, + td { + &:first-child { + display: none; + } + } + } + } +} diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx new file mode 100644 index 000000000..ca56cb443 --- /dev/null +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -0,0 +1,188 @@ +import { useQuery } from '@tanstack/react-query' +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' +import { Tooltip } from 'antd' +import { CopyIcon, InfoCircledIcon } from '@radix-ui/react-icons' +import dayjs from 'dayjs' +import Content from '../../../components/Content' +import { useSetToast } from '../../../components/Toast' +import { explorerService } from '../../../services/ExplorerService' +import type { Fiber } from '../../../services/ExplorerService/fetcher' +import { shannonToCkb } from '../../../utils/util' +import { localeNumberString } from '../../../utils/number' +import { parseNumericAbbr } from '../../../utils/chart' +import styles from './index.module.scss' +import Pagination from '../Pagination' +import { PAGE_SIZE } from '../../../constants/common' + +const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' + +const fields = [ + { + key: 'alias', + label: 'name', + transformer: (v: unknown, i: Fiber.Graph.Node) => { + if (typeof v !== 'string') return v + return ( + +
+ {v || Untitled} +
+
+ ) + }, + }, + { + key: 'autoAcceptMinCkbFundingAmount', + label: 'auto_accept_min_ckb_funding_amount', + transformer: (v: unknown) => { + if (typeof v !== 'string' || Number.isNaN(+v)) return v + const ckb = shannonToCkb(v) + const amount = parseNumericAbbr(ckb) + return ( +
+ + {`${amount} CKB`} + +
+ ) + }, + }, + { + key: 'timestamp', + label: 'first_seen', + transformer: (v: unknown) => { + if (typeof v !== 'string') return v + return dayjs(+v).format(TIME_TEMPLATE) + }, + }, + { + key: 'nodeId', + label: 'node_id', + transformer: (v: unknown) => { + if (typeof v !== 'string') return v + return ( + + + + {v.length > 16 ? `${v.slice(0, 8)}...${v.slice(-8)}` : v} + + + + + ) + }, + }, + { + key: 'chainHash', + label: 'chain_hash', + transformer: (v: unknown) => { + if (typeof v !== 'string') return v + return ( + + + {v.length > 16 ? `${v.slice(0, 8)}...${v.slice(-8)}` : v} + + + + ) + }, + }, + { + key: 'addresses', + label: 'addresses', + transformer: (v: unknown) => { + if (!Array.isArray(v)) return v + const addr = v[0] + if (!addr || typeof addr !== 'string') return v + return ( + + + {addr} + + + {/* */} + {/* */} + {/* */} + {v.length > 1 ? ( + + + + + + ) : null} + + ) + }, + }, +] + +const GraphNodeList = () => { + const [t] = useTranslation() + const setToast = useSetToast() + + const { data } = useQuery({ + queryKey: ['fiber', 'graph', 'nodes'], + queryFn: () => explorerService.api.getGraphNodes(), + }) + + const list = data?.data.fiberGraphNodes ?? [] + const pageInfo = data?.data.meta ?? { total: 1, pageSize: PAGE_SIZE } + const totalPages = Math.ceil(pageInfo.total / pageInfo.pageSize) + + const handleCopy = (e: React.SyntheticEvent) => { + const elm = e.target + if (!(elm instanceof HTMLElement)) return + const { copyText } = elm.dataset + if (!copyText) return + e.stopPropagation() + e.preventDefault() + navigator?.clipboard.writeText(copyText).then(() => setToast({ message: t('common.copied') })) + } + + return ( + +
+

+ CKB Fiber Graph Nodes +

+ + + + {fields.map(f => { + return + })} + + +
+
+ {list.map(i => { + return ( + + {fields.map(f => { + const v = i[f.key as keyof typeof i] + return + })} + + ) + })} +
+
+ + + +
{t(`fiber.graph.node.${f.label}`)}
{f.transformer?.(v, i) ?? v}
+ +
+
+
+ ) +} + +export default GraphNodeList diff --git a/src/pages/Fiber/PeerList/index.tsx b/src/pages/Fiber/PeerList/index.tsx index d2620492c..a0d27c3ad 100644 --- a/src/pages/Fiber/PeerList/index.tsx +++ b/src/pages/Fiber/PeerList/index.tsx @@ -107,7 +107,7 @@ const fields = [ {v.length > 1 ? ( - + diff --git a/src/routes/index.tsx b/src/routes/index.tsx index dca8dc0d8..fc987d4cc 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -85,6 +85,7 @@ const MoleculeParser = lazy(() => import('../pages/Tools/MoleculeParser')) const FiberPeerList = lazy(() => import('../pages/Fiber/PeerList')) const FiberPeer = lazy(() => import('../pages/Fiber/Peer')) const FiberChannel = lazy(() => import('../pages/Fiber/Channel')) +const FiberGraphNodeList = lazy(() => import('../pages/Fiber/GraphNodeList')) // ====== const routes: RouteProps[] = [ @@ -363,6 +364,10 @@ const routes: RouteProps[] = [ path: '/fiber/channels/:id', component: FiberChannel, }, + { + path: '/fiber/graph/nodes', + component: FiberGraphNodeList, + }, ] type PageErrorBoundaryState = { diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 5b29727e3..8469d1e99 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1249,6 +1249,49 @@ export const apiFetcher = { throw e }) }, + + getGraphNodes: (page = 1, pageSize = 10) => { + return requesterV2 + .get( + `/fiber/graph_nodes?${new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + })}`, + ) + .then(res => + toCamelcase< + Response.Response<{ + fiberGraphNodes: Fiber.Graph.Node[] + meta: { + total: number + pageSize: number + } + }> + >(res.data), + ) + }, + + getGraphNodeDetail: (id: string) => { + return requesterV2 + .get(`/fiber/graph_nodes/${id}`) + .then(res => toCamelcase>(res.data)) + }, + getGraphChannels: (page = 1, pageSize = 10) => { + return requesterV2 + .get( + `/fiber/graph_channels?${new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + })}`, + ) + .then(res => + toCamelcase< + Response.Response<{ + fiberGraphChannels: Fiber.Graph.Channel[] + }> + >(res.data), + ) + }, } // ==================== @@ -1460,7 +1503,7 @@ export namespace Fiber { peerId: string channelId: string stateName: string // TODO: should be enum - state_flags: [] // TODO + stateFlags: [] // TODO }[] } } @@ -1485,4 +1528,31 @@ export namespace Fiber { remotePeer: Peer } } + + export namespace Graph { + export interface Node { + alias: string + nodeId: string + addresses: string[] + timestamp: string + chainHash: string + autoAcceptMinCkbFundingAmount: string + } + + export interface Channel { + channelOutpoint: string + node1: string + node2: string + chainHash: string + fundingTxBlockNumber: string + fundingTxIndex: string // number + lastUpdatedTimestamp: string + node1ToNode2FeeRate: string + node2ToNode1FeeRate: string + capacity: string + } + export interface NodeDetail extends Node { + fiberGraphChannels: Channel[] + } + } } From d5d8f2fe1a7107cec29cb2ff07ab4c630ea98d25 Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 15 Oct 2024 04:11:09 +0900 Subject: [PATCH 12/24] feat: add ckb fiber graph channel list --- .../Fiber/GraphChannelList/index.module.scss | 138 ++++++++++++++ src/pages/Fiber/GraphChannelList/index.tsx | 176 ++++++++++++++++++ src/pages/Fiber/GraphNodeList/index.tsx | 2 +- src/routes/index.tsx | 5 + src/services/ExplorerService/fetcher.ts | 4 + 5 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 src/pages/Fiber/GraphChannelList/index.module.scss create mode 100644 src/pages/Fiber/GraphChannelList/index.tsx diff --git a/src/pages/Fiber/GraphChannelList/index.module.scss b/src/pages/Fiber/GraphChannelList/index.module.scss new file mode 100644 index 000000000..c5a79afc0 --- /dev/null +++ b/src/pages/Fiber/GraphChannelList/index.module.scss @@ -0,0 +1,138 @@ +@import '../../../styles/variables.module'; +@import '../../../styles/table.module'; + +.container { + margin: 24px 120px; + font-size: 1rem; + + .channels { + border-radius: 6px; + box-shadow: rgb(0 0 0 / 12%) 0 2px 6px 0; + overflow: hidden; + + .list { + font-size: 0.875rem; + + a { + color: var(--primary-color); + } + + svg { + pointer-events: none; + } + } + } + + button { + display: flex; + align-items: center; + appearance: none; + padding: 0; + border: none; + background: none; + cursor: pointer; + + &:hover { + color: var(--primary-color); + } + } + + .header { + font-size: 1.5rem; + margin-bottom: 20px; + } + + .channel { + margin-bottom: 4px; + background: #fff; + padding: 8px 40px; + + h1 { + font-size: 1.2rem; + } + + dl { + display: flex; + gap: 4px; + } + + dl, + dd, + dt { + margin: 0; + white-space: pre; + flex-wrap: wrap; + } + + dt { + &::after { + content: ':'; + } + } + + dd { + display: flex; + align-items: center; + gap: 4px; + } + + .general { + dd { + .content { + & > *:first-child { + display: none; + } + + @media screen and (width<800px) { + & > *:first-child { + display: flex; + } + + & > *:last-child { + display: none; + } + } + } + } + } + + .nodesContainer { + border-radius: 6px; + border: 1px solid #ccc; + padding: 8px; + margin-top: 8px; + } + + .nodes { + display: flex; + + h3 { + font-size: 1rem; + } + + gap: 20px; + + .node { + flex: 1; + } + + @media screen and (width<670px) { + flex-direction: column; + } + } + } + + .pagination { + background: #fff; + padding: 8px 40px; + margin-top: 4px; + } + + @media screen and (width < $extraLargeBreakPoint) { + margin: 24px 20px; + } + + @media screen and (width < 1330px) { + font-size: 14px; + } +} diff --git a/src/pages/Fiber/GraphChannelList/index.tsx b/src/pages/Fiber/GraphChannelList/index.tsx new file mode 100644 index 000000000..ce1c91fce --- /dev/null +++ b/src/pages/Fiber/GraphChannelList/index.tsx @@ -0,0 +1,176 @@ +import { useQuery } from '@tanstack/react-query' +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' +import { Tooltip } from 'antd' +import { CopyIcon } from '@radix-ui/react-icons' +import dayjs from 'dayjs' +import Content from '../../../components/Content' +import { useSetToast } from '../../../components/Toast' +import { explorerService } from '../../../services/ExplorerService' +import { shannonToCkb } from '../../../utils/util' +import { localeNumberString } from '../../../utils/number' +import { parseNumericAbbr } from '../../../utils/chart' +import styles from './index.module.scss' +import Pagination from '../Pagination' +import { PAGE_SIZE } from '../../../constants/common' + +const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' + +const GraphNodeList = () => { + const [t] = useTranslation() + const setToast = useSetToast() + + const { data } = useQuery({ + queryKey: ['fiber', 'graph', 'channels'], + queryFn: () => explorerService.api.getGraphChannels(), + }) + + const list = data?.data.fiberGraphChannels ?? [] + const pageInfo = data?.data.meta ?? { total: 1, pageSize: PAGE_SIZE } + const totalPages = Math.ceil(pageInfo.total / pageInfo.pageSize) + + const handleCopy = (e: React.SyntheticEvent) => { + const elm = e.target + if (!(elm instanceof HTMLElement)) return + const { copyText } = elm.dataset + if (!copyText) return + e.stopPropagation() + e.preventDefault() + navigator?.clipboard.writeText(copyText).then(() => setToast({ message: t('common.copied') })) + } + + return ( + +
+

+ CKB Fiber Graph Channels +

+
+
+ {list.map(channel => { + const outPoint = { + txHash: channel.channelOutpoint.slice(0, -8), + index: parseInt(channel.channelOutpoint.slice(-8), 16), + } + + const ckb = shannonToCkb(channel.capacity) + const amount = parseNumericAbbr(ckb) + return ( +
+

General

+
+
+
Out Point
+
+
+ + + {`${outPoint.txHash.slice(0, 6)}...${outPoint.txHash.slice(-6)}#${outPoint.index}`} + + + + {`${outPoint.txHash}#${outPoint.index}`} + +
+ +
+
+ +
+
Capacity
+
+ + {`${amount} CKB`} + +
+
+ +
+
Chain Hash
+
+
+ + {`${channel.chainHash.slice(0, 8)}...${channel.chainHash.slice( + -8, + )}`} + + {channel.chainHash} +
+ +
+
+ +
+
Funded at
+
+ + {localeNumberString(channel.fundingTxBlockNumber)} + + (
{dayjs(+channel.lastUpdatedTimestamp).format(TIME_TEMPLATE)}
) +
+
+
+ +
+

Nodes

+
+
+

First Node

+
+
Public Key
+
+ + {`${channel.node1.slice(0, 8)}...${channel.node1.slice( + -8, + )}`} + + +
+
+
+
Fee Rate
+
{`${localeNumberString(channel.node1ToNode2FeeRate)} shannon/kB`}
+
+
+
+

Second Node

+
+
Public Key
+
+ + {`${channel.node2.slice(0, 8)}...${channel.node2.slice( + -8, + )}`} + + +
+
+
+
Fee Rate
+
{`${localeNumberString(channel.node2ToNode1FeeRate)} shannon/kB`}
+
+
+
+
+
+ ) + })} +
+
+ +
+
+
+
+ ) +} + +export default GraphNodeList diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index ca56cb443..ad22a82f0 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -83,7 +83,7 @@ const fields = [ return ( - {v.length > 16 ? `${v.slice(0, 8)}...${v.slice(-8)}` : v} + {`${v.slice(0, 8)}...${v.slice(-8)}`} + +
+ +
+
Capacity
+
+ + {`${amount} CKB`} + +
+
+ +
+
Chain Hash
+
+
+ + {`${channel.chainHash.slice(0, 8)}...${channel.chainHash.slice( + -8, + )}`} + + {channel.chainHash} +
+ +
+
+ +
+
Funded at
+
+ + {localeNumberString(channel.fundingTxBlockNumber)} + + (
{dayjs(+channel.lastUpdatedTimestamp).format(TIME_TEMPLATE)}
) +
+
+
+ +
+

Nodes

+
+
+

First Node

+
+
Public Key
+
+ + {`0x${channel.node1.slice(0, 8)}...${channel.node1.slice( + -8, + )}`} + + +
+
+
+
Fee Rate
+
{`${localeNumberString(channel.node1ToNode2FeeRate)} shannon/kB`}
+
+
+
+

Second Node

+
+
Public Key
+
+ + {`0x${channel.node2.slice(0, 8)}...${channel.node2.slice( + -8, + )}`} + + +
+
+
+
Fee Rate
+
{`${localeNumberString(channel.node2ToNode1FeeRate)} shannon/kB`}
+
+
+
+
+
+ ) + })} +
+ ) +} + +export default GraphChannelList diff --git a/src/locales/en.json b/src/locales/en.json index 6e0779a7b..da587a434 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1114,7 +1114,9 @@ }, "graph": { "node": { + "id": "Node ID", "name": "Name", + "alias": "Alias", "auto_accept_min_ckb_funding_amount": "Auto-accepting Threshold", "first_seen": "First Seen", "node_id": "Node ID", diff --git a/src/pages/Fiber/GraphChannelList/index.module.scss b/src/pages/Fiber/GraphChannelList/index.module.scss index c5a79afc0..48060a63e 100644 --- a/src/pages/Fiber/GraphChannelList/index.module.scss +++ b/src/pages/Fiber/GraphChannelList/index.module.scss @@ -9,18 +9,6 @@ border-radius: 6px; box-shadow: rgb(0 0 0 / 12%) 0 2px 6px 0; overflow: hidden; - - .list { - font-size: 0.875rem; - - a { - color: var(--primary-color); - } - - svg { - pointer-events: none; - } - } } button { @@ -42,86 +30,6 @@ margin-bottom: 20px; } - .channel { - margin-bottom: 4px; - background: #fff; - padding: 8px 40px; - - h1 { - font-size: 1.2rem; - } - - dl { - display: flex; - gap: 4px; - } - - dl, - dd, - dt { - margin: 0; - white-space: pre; - flex-wrap: wrap; - } - - dt { - &::after { - content: ':'; - } - } - - dd { - display: flex; - align-items: center; - gap: 4px; - } - - .general { - dd { - .content { - & > *:first-child { - display: none; - } - - @media screen and (width<800px) { - & > *:first-child { - display: flex; - } - - & > *:last-child { - display: none; - } - } - } - } - } - - .nodesContainer { - border-radius: 6px; - border: 1px solid #ccc; - padding: 8px; - margin-top: 8px; - } - - .nodes { - display: flex; - - h3 { - font-size: 1rem; - } - - gap: 20px; - - .node { - flex: 1; - } - - @media screen and (width<670px) { - flex-direction: column; - } - } - } - .pagination { background: #fff; padding: 8px 40px; diff --git a/src/pages/Fiber/GraphChannelList/index.tsx b/src/pages/Fiber/GraphChannelList/index.tsx index ce1c91fce..1f62bbe2c 100644 --- a/src/pages/Fiber/GraphChannelList/index.tsx +++ b/src/pages/Fiber/GraphChannelList/index.tsx @@ -1,22 +1,14 @@ import { useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' -import { Link } from 'react-router-dom' -import { Tooltip } from 'antd' -import { CopyIcon } from '@radix-ui/react-icons' -import dayjs from 'dayjs' import Content from '../../../components/Content' import { useSetToast } from '../../../components/Toast' import { explorerService } from '../../../services/ExplorerService' -import { shannonToCkb } from '../../../utils/util' -import { localeNumberString } from '../../../utils/number' -import { parseNumericAbbr } from '../../../utils/chart' import styles from './index.module.scss' import Pagination from '../Pagination' import { PAGE_SIZE } from '../../../constants/common' +import GraphChannelListComp from '../../../components/GraphChannelList' -const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' - -const GraphNodeList = () => { +const GraphChannelList = () => { const [t] = useTranslation() const setToast = useSetToast() @@ -46,124 +38,7 @@ const GraphNodeList = () => { CKB Fiber Graph Channels
-
- {list.map(channel => { - const outPoint = { - txHash: channel.channelOutpoint.slice(0, -8), - index: parseInt(channel.channelOutpoint.slice(-8), 16), - } - - const ckb = shannonToCkb(channel.capacity) - const amount = parseNumericAbbr(ckb) - return ( -
-

General

-
-
-
Out Point
-
-
- - - {`${outPoint.txHash.slice(0, 6)}...${outPoint.txHash.slice(-6)}#${outPoint.index}`} - - - - {`${outPoint.txHash}#${outPoint.index}`} - -
- -
-
- -
-
Capacity
-
- - {`${amount} CKB`} - -
-
- -
-
Chain Hash
-
-
- - {`${channel.chainHash.slice(0, 8)}...${channel.chainHash.slice( - -8, - )}`} - - {channel.chainHash} -
- -
-
- -
-
Funded at
-
- - {localeNumberString(channel.fundingTxBlockNumber)} - - (
{dayjs(+channel.lastUpdatedTimestamp).format(TIME_TEMPLATE)}
) -
-
-
- -
-

Nodes

-
-
-

First Node

-
-
Public Key
-
- - {`${channel.node1.slice(0, 8)}...${channel.node1.slice( - -8, - )}`} - - -
-
-
-
Fee Rate
-
{`${localeNumberString(channel.node1ToNode2FeeRate)} shannon/kB`}
-
-
-
-

Second Node

-
-
Public Key
-
- - {`${channel.node2.slice(0, 8)}...${channel.node2.slice( - -8, - )}`} - - -
-
-
-
Fee Rate
-
{`${localeNumberString(channel.node2ToNode1FeeRate)} shannon/kB`}
-
-
-
-
-
- ) - })} -
+
@@ -173,4 +48,4 @@ const GraphNodeList = () => { ) } -export default GraphNodeList +export default GraphChannelList diff --git a/src/pages/Fiber/GraphNode/index.module.scss b/src/pages/Fiber/GraphNode/index.module.scss new file mode 100644 index 000000000..ace74b7cd --- /dev/null +++ b/src/pages/Fiber/GraphNode/index.module.scss @@ -0,0 +1,151 @@ +@import '../../../styles/variables.module'; +@import '../../../styles/card.module'; + +.container { + text-wrap: nowrap; + display: flex; + flex-direction: column; + align-items: stretch; + margin: 24px 120px; + font-size: 1rem; + + a { + color: var(--primary-color); + } + + dl { + display: flex; + + dt, + dd { + display: flex; + align-items: center; + gap: 4px; + margin: 0; + padding: 0; + } + + dt::after { + content: ':'; + margin-right: 4px; + } + } + + table { + width: 100%; + text-align: left; + cursor: default; + + td, + th { + padding: 8px; + padding-right: 16px; + + &:last-child { + text-align: right; + } + } + + tbody { + tr:hover { + background: #ccc; + } + } + } + + svg { + pointer-events: none; + } + + button { + display: flex; + align-items: center; + appearance: none; + padding: 0; + border: none; + background: none; + cursor: pointer; + + &:hover { + color: var(--primary-color); + } + } + + .overview { + @extend %base-card; + + display: flex; + justify-content: space-between; + flex-wrap: wrap; + + .fields { + overflow: hidden; + } + } + + .id, + .connectId { + overflow: hidden; + + & > span:first-child { + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 1; + } + } + + .activities { + display: flex; + gap: 16px; + margin-top: 16px; + + .channels, + .transactions { + flex: 1; + background: #fff; + border-radius: 6px; + padding: 16px; + box-shadow: 0 2px 6px 0 #4d4d4d33; + + h3 { + margin: 0; + padding: 0; + } + } + + @media screen and (width < 960px) { + flex-direction: column; + } + + @media screen and (width < 500px) { + thead { + display: none; + } + + tbody { + tr { + display: flex; + flex-direction: column; + padding: 16px 0; + + &:not(:last-child) { + border-bottom: 1px solid #ccc; + } + + td { + text-align: left; + padding: 0; + } + } + } + } + } + + @media screen and (width < $extraLargeBreakPoint) { + margin: 24px 20px; + } + + @media screen and (width < 1030px) { + font-size: 14px; + } +} diff --git a/src/pages/Fiber/GraphNode/index.tsx b/src/pages/Fiber/GraphNode/index.tsx new file mode 100644 index 000000000..ecad750b2 --- /dev/null +++ b/src/pages/Fiber/GraphNode/index.tsx @@ -0,0 +1,183 @@ +import { useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useParams } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { CopyIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons' +import { Tooltip } from 'antd' +import QRCode from 'qrcode' +import dayjs from 'dayjs' +import Content from '../../../components/Content' +import { explorerService } from '../../../services/ExplorerService' +import { useSetToast } from '../../../components/Toast' +import styles from './index.module.scss' +import Loading from '../../../components/Loading' +import { shannonToCkb } from '../../../utils/util' +import { parseNumericAbbr } from '../../../utils/chart' +import { localeNumberString } from '../../../utils/number' +import GraphChannelList from '../../../components/GraphChannelList' + +const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' + +const GraphNode = () => { + const [t] = useTranslation() + const [addr, setAddr] = useState('') + const { id } = useParams<{ id: string }>() + const qrRef = useRef(null) + + const setToast = useSetToast() + + const { data, isLoading } = useQuery({ + queryKey: ['fiber', 'graph', 'node', id], + queryFn: () => { + return explorerService.api.getGraphNodeDetail(id) + }, + enabled: !!id, + }) + + const node = data?.data + + const connectId = addr + + const handleAddrSelect = (e: React.ChangeEvent) => { + e.stopPropagation() + e.preventDefault() + const r = e.currentTarget.value + if (r) { + setAddr(r) + } + } + + useEffect(() => { + const firstAddr = node?.addresses[0] + if (firstAddr) { + setAddr(firstAddr) + } + }, [node, setAddr]) + + useEffect(() => { + const cvs = qrRef.current + if (!cvs || !connectId) return + QRCode.toCanvas( + cvs, + connectId, + { + margin: 5, + errorCorrectionLevel: 'H', + width: 144, + }, + err => { + if (err) { + console.error(err) + } + }, + ) + }, [qrRef, connectId]) + + if (isLoading) { + return + } + + if (!node) { + return
Fiber Peer Not Found
+ } + const channels = node.fiberGraphChannels + + const ckb = shannonToCkb(node.autoAcceptMinCkbFundingAmount) + const amount = parseNumericAbbr(ckb) + + const handleCopy = (e: React.SyntheticEvent) => { + const elm = e.target + if (!(elm instanceof HTMLElement)) return + const { copyText } = elm.dataset + if (!copyText) return + e.stopPropagation() + e.preventDefault() + navigator?.clipboard.writeText(copyText).then(() => setToast({ message: t('common.copied') })) + } + + return ( + +
+
+
+ {node.alias ? ( +
+
{t('fiber.graph.alias')}
+
+ {node.alias} + +
+
+ ) : null} +
+
{t('fiber.graph.node.id')}
+
+ {node.nodeId} + +
+
+
+
+ +
+
+ + + + + +
+
+
+
{t('fiber.graph.node.first_seen')}
+
{dayjs(+node.timestamp).format(TIME_TEMPLATE)}
+
+
+
{t('fiber.graph.node.chain_hash')}
+
{node.chainHash}
+
+
+
{t('fiber.graph.node.auto_accept_min_ckb_funding_amount')}
+
+ + {`${amount} CKB`} + +
+
+
+ {connectId ? ( +
+ +
+ ) : null} +
+
+
+

{`${t('fiber.peer.channels')}(${channels.length})`}

+ +
+
+

Open | Close Transactions

+ Coming soon +
+
+
+
+ ) +} + +export default GraphNode diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index ad22a82f0..7546066d2 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -20,7 +20,7 @@ const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' const fields = [ { key: 'alias', - label: 'name', + label: 'alias', transformer: (v: unknown, i: Fiber.Graph.Node) => { if (typeof v !== 'string') return v return ( diff --git a/src/routes/index.tsx b/src/routes/index.tsx index d04639e2f..a88663c4a 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -86,6 +86,7 @@ const FiberPeerList = lazy(() => import('../pages/Fiber/PeerList')) const FiberPeer = lazy(() => import('../pages/Fiber/Peer')) const FiberChannel = lazy(() => import('../pages/Fiber/Channel')) const FiberGraphNodeList = lazy(() => import('../pages/Fiber/GraphNodeList')) +const FiberGraphNode = lazy(() => import('../pages/Fiber/GraphNode')) const FiberGraphChannelList = lazy(() => import('../pages/Fiber/GraphChannelList')) // ====== @@ -369,6 +370,10 @@ const routes: RouteProps[] = [ path: '/fiber/graph/nodes', component: FiberGraphNodeList, }, + { + path: '/fiber/graph/node/:id', + component: FiberGraphNode, + }, { path: '/fiber/graph/channels', component: FiberGraphChannelList, From 6b4608299933a8465a23d428a0e7dba87f8f14cf Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 21 Oct 2024 16:41:49 +0900 Subject: [PATCH 14/24] fix: fix pagination of fiber pages --- src/pages/Fiber/GraphChannelList/index.tsx | 8 +++++--- src/pages/Fiber/GraphNodeList/index.tsx | 8 +++++--- src/pages/Fiber/PeerList/index.tsx | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/pages/Fiber/GraphChannelList/index.tsx b/src/pages/Fiber/GraphChannelList/index.tsx index 1f62bbe2c..9d928ab8b 100644 --- a/src/pages/Fiber/GraphChannelList/index.tsx +++ b/src/pages/Fiber/GraphChannelList/index.tsx @@ -7,18 +7,20 @@ import styles from './index.module.scss' import Pagination from '../Pagination' import { PAGE_SIZE } from '../../../constants/common' import GraphChannelListComp from '../../../components/GraphChannelList' +import { useSearchParams } from '../../../hooks' const GraphChannelList = () => { const [t] = useTranslation() const setToast = useSetToast() + const { page = 1, page_size: pageSize = PAGE_SIZE } = useSearchParams('page', 'page_size') const { data } = useQuery({ - queryKey: ['fiber', 'graph', 'channels'], - queryFn: () => explorerService.api.getGraphChannels(), + queryKey: ['fiber', 'graph', 'channels', +page, +pageSize], + queryFn: () => explorerService.api.getGraphChannels(+page, +pageSize), }) const list = data?.data.fiberGraphChannels ?? [] - const pageInfo = data?.data.meta ?? { total: 1, pageSize: PAGE_SIZE } + const pageInfo = data?.meta ?? { total: 1, pageSize: PAGE_SIZE } const totalPages = Math.ceil(pageInfo.total / pageInfo.pageSize) const handleCopy = (e: React.SyntheticEvent) => { diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index 7546066d2..620df8e7a 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -14,6 +14,7 @@ import { parseNumericAbbr } from '../../../utils/chart' import styles from './index.module.scss' import Pagination from '../Pagination' import { PAGE_SIZE } from '../../../constants/common' +import { useSearchParams } from '../../../hooks' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -126,14 +127,15 @@ const fields = [ const GraphNodeList = () => { const [t] = useTranslation() const setToast = useSetToast() + const { page = 1, page_size: pageSize = PAGE_SIZE } = useSearchParams('page', 'page_size') const { data } = useQuery({ - queryKey: ['fiber', 'graph', 'nodes'], - queryFn: () => explorerService.api.getGraphNodes(), + queryKey: ['fiber', 'graph', 'nodes', +page, +pageSize], + queryFn: () => explorerService.api.getGraphNodes(+page, +pageSize), }) const list = data?.data.fiberGraphNodes ?? [] - const pageInfo = data?.data.meta ?? { total: 1, pageSize: PAGE_SIZE } + const pageInfo = data?.meta ?? { total: 1, pageSize: PAGE_SIZE } const totalPages = Math.ceil(pageInfo.total / pageInfo.pageSize) const handleCopy = (e: React.SyntheticEvent) => { diff --git a/src/pages/Fiber/PeerList/index.tsx b/src/pages/Fiber/PeerList/index.tsx index a0d27c3ad..35aa9476a 100644 --- a/src/pages/Fiber/PeerList/index.tsx +++ b/src/pages/Fiber/PeerList/index.tsx @@ -14,6 +14,7 @@ import styles from './index.module.scss' import AddPeerForm from './AddPeerForm' import Pagination from '../Pagination' import { PAGE_SIZE } from '../../../constants/common' +import { useSearchParams } from '../../../hooks' const fields = [ { @@ -122,14 +123,15 @@ const fields = [ const PeerList = () => { const [t] = useTranslation() const setToast = useSetToast() + const { page = 1, page_size: pageSize = PAGE_SIZE } = useSearchParams('page', 'page_size') const { data, refetch: refetchList } = useQuery({ - queryKey: ['fiber', 'peers'], - queryFn: () => explorerService.api.getFiberPeerList(), + queryKey: ['fiber', 'peers', +page, +pageSize], + queryFn: () => explorerService.api.getFiberPeerList(+page, +pageSize), }) const list = data?.data.fiberPeers ?? [] - const pageInfo = data?.data.meta ?? { total: 1, pageSize: PAGE_SIZE } + const pageInfo = data?.meta ?? { total: 1, pageSize: PAGE_SIZE } const totalPages = Math.ceil(pageInfo.total / pageInfo.pageSize) const handleCopy = (e: React.SyntheticEvent) => { From 369ecd557cab40ce64647710873b0ab72706e109 Mon Sep 17 00:00:00 2001 From: Keith Date: Thu, 24 Oct 2024 11:12:49 +0900 Subject: [PATCH 15/24] feat: add funding thresholds --- public/images/tokens/ckb_token.svg | 9 ++++ src/locales/en.json | 2 +- src/pages/Fiber/GraphNode/index.module.scss | 18 ++++++++ src/pages/Fiber/GraphNode/index.tsx | 26 +++++++----- .../Fiber/GraphNodeList/index.module.scss | 11 +++++ src/pages/Fiber/GraphNodeList/index.tsx | 30 +++++++------ src/pages/Fiber/utils/index.tsx | 42 +++++++++++++++++++ src/services/ExplorerService/fetcher.ts | 14 +++++++ 8 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 public/images/tokens/ckb_token.svg create mode 100644 src/pages/Fiber/utils/index.tsx diff --git a/public/images/tokens/ckb_token.svg b/public/images/tokens/ckb_token.svg new file mode 100644 index 000000000..27302bd6f --- /dev/null +++ b/public/images/tokens/ckb_token.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/locales/en.json b/src/locales/en.json index da587a434..5c2fa5fd2 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1117,7 +1117,7 @@ "id": "Node ID", "name": "Name", "alias": "Alias", - "auto_accept_min_ckb_funding_amount": "Auto-accepting Threshold", + "auto_accept_funding_amount": "Auto-accepting Threshold", "first_seen": "First Seen", "node_id": "Node ID", "chain_hash": "Chain Hash", diff --git a/src/pages/Fiber/GraphNode/index.module.scss b/src/pages/Fiber/GraphNode/index.module.scss index ace74b7cd..600744360 100644 --- a/src/pages/Fiber/GraphNode/index.module.scss +++ b/src/pages/Fiber/GraphNode/index.module.scss @@ -94,6 +94,24 @@ } } + .thresholds { + dt { + align-items: start; + } + + dd { + margin-left: 8px; + display: flex; + flex-direction: column; + + .token { + display: flex; + gap: 8px; + align-items: center; + } + } + } + .activities { display: flex; gap: 16px; diff --git a/src/pages/Fiber/GraphNode/index.tsx b/src/pages/Fiber/GraphNode/index.tsx index ecad750b2..7e4484ad9 100644 --- a/src/pages/Fiber/GraphNode/index.tsx +++ b/src/pages/Fiber/GraphNode/index.tsx @@ -11,10 +11,8 @@ import { explorerService } from '../../../services/ExplorerService' import { useSetToast } from '../../../components/Toast' import styles from './index.module.scss' import Loading from '../../../components/Loading' -import { shannonToCkb } from '../../../utils/util' -import { parseNumericAbbr } from '../../../utils/chart' -import { localeNumberString } from '../../../utils/number' import GraphChannelList from '../../../components/GraphChannelList' +import { getFundingThreshold } from '../utils' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -82,8 +80,9 @@ const GraphNode = () => { } const channels = node.fiberGraphChannels - const ckb = shannonToCkb(node.autoAcceptMinCkbFundingAmount) - const amount = parseNumericAbbr(ckb) + // const ckb = shannonToCkb(node.autoAcceptMinCkbFundingAmount) + // const amount = parseNumericAbbr(ckb) + const thresholds = getFundingThreshold(node) const handleCopy = (e: React.SyntheticEvent) => { const elm = e.target @@ -150,12 +149,19 @@ const GraphNode = () => {
{t('fiber.graph.node.chain_hash')}
{node.chainHash}
-
-
{t('fiber.graph.node.auto_accept_min_ckb_funding_amount')}
+
+
{t('fiber.graph.node.auto_accept_funding_amount')}
- - {`${amount} CKB`} - + {thresholds.map(threshold => { + return ( + + + icon + {threshold.display} + + + ) + })}
diff --git a/src/pages/Fiber/GraphNodeList/index.module.scss b/src/pages/Fiber/GraphNodeList/index.module.scss index ba12c0528..4356a5086 100644 --- a/src/pages/Fiber/GraphNodeList/index.module.scss +++ b/src/pages/Fiber/GraphNodeList/index.module.scss @@ -44,6 +44,17 @@ text-overflow: ellipsis; } + .funding { + display: flex; + flex-direction: column; + + .token { + display: flex; + gap: 8px; + align-items: center; + } + } + .nodeId, .chainHash { display: flex; diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index 620df8e7a..c07802845 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -8,13 +8,11 @@ import Content from '../../../components/Content' import { useSetToast } from '../../../components/Toast' import { explorerService } from '../../../services/ExplorerService' import type { Fiber } from '../../../services/ExplorerService/fetcher' -import { shannonToCkb } from '../../../utils/util' -import { localeNumberString } from '../../../utils/number' -import { parseNumericAbbr } from '../../../utils/chart' -import styles from './index.module.scss' import Pagination from '../Pagination' import { PAGE_SIZE } from '../../../constants/common' import { useSearchParams } from '../../../hooks' +import { getFundingThreshold } from '../utils' +import styles from './index.module.scss' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -35,16 +33,22 @@ const fields = [ }, { key: 'autoAcceptMinCkbFundingAmount', - label: 'auto_accept_min_ckb_funding_amount', - transformer: (v: unknown) => { - if (typeof v !== 'string' || Number.isNaN(+v)) return v - const ckb = shannonToCkb(v) - const amount = parseNumericAbbr(ckb) + label: 'auto_accept_funding_amount', + transformer: (_: unknown, n: Fiber.Graph.Node) => { + const thresholds = getFundingThreshold(n) + return ( -
- - {`${amount} CKB`} - +
+ {thresholds.map(threshold => { + return ( + + + icon + {threshold.display} + + + ) + })}
) }, diff --git a/src/pages/Fiber/utils/index.tsx b/src/pages/Fiber/utils/index.tsx new file mode 100644 index 000000000..2513b3bae --- /dev/null +++ b/src/pages/Fiber/utils/index.tsx @@ -0,0 +1,42 @@ +import { scriptToHash } from '@nervosnetwork/ckb-sdk-utils' +import type { Fiber } from '../../../services/ExplorerService/fetcher' +import { parseNumericAbbr } from '../../../utils/chart' +import { localeNumberString, parseUDTAmount } from '../../../utils/number' +import { shannonToCkb } from '../../../utils/util' + +export const getFundingThreshold = (n: Fiber.Graph.Node) => { + const ckb = shannonToCkb(n.autoAcceptMinCkbFundingAmount) + const amount = parseNumericAbbr(ckb) + + const tokens: { title: string; display: string; id: string; icon?: string }[] = [ + { + title: `${localeNumberString(ckb)} CKB`, + display: `${amount} CKB`, + id: 'ckb', + icon: '/images/tokens/ckb_token.svg', + }, + ] + + n.udtCfgInfos.forEach(udt => { + if (udt && udt.autoAcceptAmount && typeof udt.decimal === 'number' && udt.symbol) { + try { + const udtAmount = parseUDTAmount(udt.autoAcceptAmount, udt.decimal) + const id = scriptToHash({ + codeHash: udt.codeHash, + hashType: udt.hashType, + args: udt.args, + }) + tokens.push({ + title: `${localeNumberString(udtAmount)} ${udt.symbol}`, + display: `${parseNumericAbbr(udtAmount)} ${udt.symbol}`, + icon: udt.iconFile, + id, + }) + } catch (e) { + console.error(e) + } + } + }) + + return tokens +} diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 793febd51..ac72450ea 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1534,6 +1534,17 @@ export namespace Fiber { } export namespace Graph { + interface UdtConfigInfo { + args: string + codeHash: string + hashType: HashType + decimal?: number + fullName?: string + iconFile?: string + symbol?: string + autoAcceptAmount: string + } + export interface Node { alias: string nodeId: string @@ -1541,6 +1552,9 @@ export namespace Fiber { timestamp: string chainHash: string autoAcceptMinCkbFundingAmount: string + udtCfgInfos: UdtConfigInfo[] + totalCapacity: string + connectedNodeIds: string[] } export interface Channel { From 5b8dfa08c1829f0f6ac14688cf54bba2ae5430b0 Mon Sep 17 00:00:00 2001 From: Keith Date: Thu, 24 Oct 2024 12:09:57 +0900 Subject: [PATCH 16/24] feat: add total capacity --- src/locales/en.json | 3 ++- src/pages/Fiber/GraphNode/index.tsx | 8 ++++++++ src/pages/Fiber/GraphNodeList/index.tsx | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/locales/en.json b/src/locales/en.json index 5c2fa5fd2..2c0948f52 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1121,7 +1121,8 @@ "first_seen": "First Seen", "node_id": "Node ID", "chain_hash": "Chain Hash", - "addresses": "Addresses" + "addresses": "Addresses", + "total_capacity": "Capacity" } } } diff --git a/src/pages/Fiber/GraphNode/index.tsx b/src/pages/Fiber/GraphNode/index.tsx index 7e4484ad9..249af31fd 100644 --- a/src/pages/Fiber/GraphNode/index.tsx +++ b/src/pages/Fiber/GraphNode/index.tsx @@ -13,6 +13,8 @@ import styles from './index.module.scss' import Loading from '../../../components/Loading' import GraphChannelList from '../../../components/GraphChannelList' import { getFundingThreshold } from '../utils' +import { shannonToCkb } from '../../../utils/util' +import { parseNumericAbbr } from '../../../utils/chart' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -84,6 +86,8 @@ const GraphNode = () => { // const amount = parseNumericAbbr(ckb) const thresholds = getFundingThreshold(node) + const totalCkb = parseNumericAbbr(shannonToCkb(node.totalCapacity)) + const handleCopy = (e: React.SyntheticEvent) => { const elm = e.target if (!(elm instanceof HTMLElement)) return @@ -149,6 +153,10 @@ const GraphNode = () => {
{t('fiber.graph.node.chain_hash')}
{node.chainHash}
+
+
{t('fiber.graph.node.total_capacity')}
+
{totalCkb}
+
{t('fiber.graph.node.auto_accept_funding_amount')}
diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index c07802845..22ccb77b4 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -13,6 +13,9 @@ import { PAGE_SIZE } from '../../../constants/common' import { useSearchParams } from '../../../hooks' import { getFundingThreshold } from '../utils' import styles from './index.module.scss' +import { shannonToCkb } from '../../../utils/util' +import { parseNumericAbbr } from '../../../utils/chart' +import { localeNumberString } from '../../../utils/number' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -53,6 +56,21 @@ const fields = [ ) }, }, + { + key: 'totalCapacity', + label: 'total_capacity', + transformer: (v: unknown) => { + if (typeof v !== 'string') return v + + const ckb = shannonToCkb(v) + const amount = parseNumericAbbr(ckb) + return ( + + {`${amount} CKB`} + + ) + }, + }, { key: 'timestamp', label: 'first_seen', From 495a4d408ba33c854397245610ccdb25397afca4 Mon Sep 17 00:00:00 2001 From: Keith Date: Thu, 24 Oct 2024 16:35:22 +0900 Subject: [PATCH 17/24] feat: refine graph node --- .../GraphChannelList/index.module.scss | 8 ++++ src/components/GraphChannelList/index.tsx | 38 +++++++++++++------ src/locales/en.json | 3 ++ src/pages/Fiber/GraphNode/index.tsx | 6 +-- src/pages/Fiber/GraphNodeList/index.tsx | 4 +- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/components/GraphChannelList/index.module.scss b/src/components/GraphChannelList/index.module.scss index 63035039d..1f37a1abf 100644 --- a/src/components/GraphChannelList/index.module.scss +++ b/src/components/GraphChannelList/index.module.scss @@ -90,7 +90,15 @@ } h3 { + display: flex; + align-items: center; + gap: 4px; font-size: 1rem; + + span { + display: flex; + align-items: center; + } } gap: 20px; diff --git a/src/components/GraphChannelList/index.tsx b/src/components/GraphChannelList/index.tsx index f95b80c39..e5cc8d113 100644 --- a/src/components/GraphChannelList/index.tsx +++ b/src/components/GraphChannelList/index.tsx @@ -1,4 +1,4 @@ -import { CopyIcon } from '@radix-ui/react-icons' +import { CopyIcon, HomeIcon, GlobeIcon } from '@radix-ui/react-icons' import { Tooltip } from 'antd' import dayjs from 'dayjs' import type { FC } from 'react' @@ -11,10 +11,15 @@ import styles from './index.module.scss' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' -const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean }> = ({ list, isFullWidth = true }) => { +const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean; node?: string }> = ({ + list, + isFullWidth = true, + node, +}) => { if (!list.length) { return
No Channels
} + return (
{list.map(channel => { @@ -25,6 +30,7 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean const ckb = shannonToCkb(channel.capacity) const amount = parseNumericAbbr(ckb) + return (

General

@@ -89,14 +95,18 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean

Nodes

-

First Node

+

+ First Node + {node ? {node === channel.node1 ? : } : null} +

-
Public Key
+
ID
- {`0x${channel.node1.slice(0, 8)}...${channel.node1.slice( - -8, - )}`} + {`0x${channel.node1.slice( + 0, + 8, + )}...${channel.node1.slice(-8)}`}
-

Second Node

+

+ Second Node + {node ? {node === channel.node2 ? : } : null} +

-
Public Key
+
ID
- {`0x${channel.node2.slice(0, 8)}...${channel.node2.slice( - -8, - )}`} + {`0x${channel.node2.slice( + 0, + 8, + )}...${channel.node2.slice(-8)}`}
@@ -182,7 +182,7 @@ const GraphNode = () => {

{`${t('fiber.peer.channels')}(${channels.length})`}

- +

Open | Close Transactions

diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index 22ccb77b4..f9d832b21 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -88,10 +88,10 @@ const fields = [ - {v.length > 16 ? `${v.slice(0, 8)}...${v.slice(-8)}` : v} + {v.length > 16 ? `0x${v.slice(0, 8)}...${v.slice(-8)}` : `0x${v}`} - From 66ce30349df07a00eb30c91f7d741b689801c2f6 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 4 Nov 2024 16:57:50 +0700 Subject: [PATCH 18/24] feat: add funder address --- .../GraphChannelList/index.module.scss | 9 ++++++ src/components/GraphChannelList/index.tsx | 28 +++++++++++++++++++ src/locales/en.json | 3 +- src/pages/Fiber/GraphNodeList/index.tsx | 9 ++++++ src/services/ExplorerService/fetcher.ts | 14 ++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/components/GraphChannelList/index.module.scss b/src/components/GraphChannelList/index.module.scss index 1f37a1abf..6ccecbe1a 100644 --- a/src/components/GraphChannelList/index.module.scss +++ b/src/components/GraphChannelList/index.module.scss @@ -74,6 +74,15 @@ } } + .funding { + display: flex; + + .funder { + flex: 0.4; + overflow: hidden; + } + } + .nodesContainer { border-radius: 6px; border: 1px solid #ccc; diff --git a/src/components/GraphChannelList/index.tsx b/src/components/GraphChannelList/index.tsx index e5cc8d113..901c57c69 100644 --- a/src/components/GraphChannelList/index.tsx +++ b/src/components/GraphChannelList/index.tsx @@ -7,6 +7,7 @@ import type { Fiber } from '../../services/ExplorerService/fetcher' import { parseNumericAbbr } from '../../utils/chart' import { localeNumberString } from '../../utils/number' import { shannonToCkb } from '../../utils/util' +import AddressText from '../AddressText' import styles from './index.module.scss' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -31,6 +32,13 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean; const ckb = shannonToCkb(channel.capacity) const amount = parseNumericAbbr(ckb) + const fundingCkb = shannonToCkb(channel.outpointInfo.fundingCapacity) + const fundingCkbAmount = parseNumericAbbr(fundingCkb) + + const fundingUdtAmount = channel.outpointInfo.fundingUdtAmount + ? parseNumericAbbr(channel.outpointInfo.fundingUdtAmount) + : null + return (

General

@@ -62,6 +70,26 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean;
+
+
Funding
+
+ + {`${fundingCkbAmount} CKB`} + + {fundingUdtAmount ? '&' : null} + {fundingUdtAmount} + from +
+ + {channel.outpointInfo.fundingAddress} + +
+
+
Chain Hash
diff --git a/src/locales/en.json b/src/locales/en.json index 3aa6f7f84..aeab64360 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1122,7 +1122,8 @@ "node_id": "Node ID", "chain_hash": "Chain Hash", "addresses": "Addresses", - "total_capacity": "Capacity" + "total_capacity": "Capacity", + "open_channels": "Open Channels" }, "channel": { "connected_node": "Connected Node" diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index f9d832b21..00d1958ca 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -71,6 +71,15 @@ const fields = [ ) }, }, + { + key: 'openChannelsCount', + label: 'open_channels', + transformer: (v: unknown) => { + if (typeof v !== 'string') return v + + return localeNumberString(v) + }, + }, { key: 'timestamp', label: 'first_seen', diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index ac72450ea..41a085e3d 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1545,6 +1545,16 @@ export namespace Fiber { autoAcceptAmount: string } + interface FundingInfo { + blockNumber: number + blockTimestamp: number + fundingAddress: string + fundingCapacity: string + fundingUdtAmount: string | null + transactionFee: number + txHash: string + } + export interface Node { alias: string nodeId: string @@ -1555,6 +1565,8 @@ export namespace Fiber { udtCfgInfos: UdtConfigInfo[] totalCapacity: string connectedNodeIds: string[] + openChannelsCount: number + channelLinksCount: number } export interface Channel { @@ -1568,7 +1580,9 @@ export namespace Fiber { node1ToNode2FeeRate: string node2ToNode1FeeRate: string capacity: string + outpointInfo: FundingInfo } + export interface NodeDetail extends Node { fiberGraphChannels: Channel[] } From e88a5cd6cb5530c719802ec9e9410e1580f29933 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 4 Nov 2024 17:54:44 +0700 Subject: [PATCH 19/24] feat: add some filters on channels api --- src/components/GraphChannelList/index.tsx | 4 ++-- src/services/ExplorerService/fetcher.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/GraphChannelList/index.tsx b/src/components/GraphChannelList/index.tsx index 901c57c69..480b9705b 100644 --- a/src/components/GraphChannelList/index.tsx +++ b/src/components/GraphChannelList/index.tsx @@ -131,7 +131,7 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean;
ID
- {`0x${channel.node1.slice( + {`0x${channel.node1.slice( 0, 8, )}...${channel.node1.slice(-8)}`} @@ -155,7 +155,7 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean;
ID
- {`0x${channel.node2.slice( + {`0x${channel.node2.slice( 0, 8, )}...${channel.node2.slice(-8)}`} diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 41a085e3d..59d643290 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1282,6 +1282,7 @@ export const apiFetcher = { `/fiber/graph_channels?${new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + status: 'open', })}`, ) .then(res => @@ -1566,7 +1567,6 @@ export namespace Fiber { totalCapacity: string connectedNodeIds: string[] openChannelsCount: number - channelLinksCount: number } export interface Channel { From 68b4c4fc7cc828ed23076be54cf4622c95c20d12 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 2 Dec 2024 21:35:33 +0900 Subject: [PATCH 20/24] feat: optimize ui of fiber graph nodes and channels --- .../GraphChannelList/index.module.scss | 86 +++++++++------ src/components/GraphChannelList/index.tsx | 104 +++++++----------- src/constants/fiberChainHash.ts | 3 + src/locales/en.json | 1 + src/pages/Fiber/GraphNode/index.module.scss | 44 +++++++- src/pages/Fiber/GraphNode/index.tsx | 36 +++++- src/pages/Fiber/GraphNodeList/index.tsx | 8 +- src/styles/text.module.scss | 21 ++++ 8 files changed, 192 insertions(+), 111 deletions(-) create mode 100644 src/constants/fiberChainHash.ts create mode 100644 src/styles/text.module.scss diff --git a/src/components/GraphChannelList/index.module.scss b/src/components/GraphChannelList/index.module.scss index 6ccecbe1a..399a0e332 100644 --- a/src/components/GraphChannelList/index.module.scss +++ b/src/components/GraphChannelList/index.module.scss @@ -1,3 +1,6 @@ +@import '../../styles/variables.module'; +@import '../../styles/text.module'; + .container { font-size: 0.875rem; @@ -12,14 +15,20 @@ dl { display: flex; gap: 4px; + + @media screen and (width <= $mobileBreakPoint) { + display: block; + } } dl, dd, dt { margin: 0; - white-space: pre; - flex-wrap: wrap; + + /* white-space: pre; */ + + /* flex-wrap: wrap; */ } dt { @@ -34,41 +43,15 @@ gap: 4px; } - .general { - dd { - .content { - & > *:first-child { - display: none; - } - - @media screen and (width<800px) { - & > *:first-child { - display: flex; - } - - & > *:last-child { - display: none; - } - } - } - - .content[data-is-full-width='false'] { - & > *:first-child { - display: flex; - } - - & > *:last-child { - display: none; - } - } - } - } - .channel { margin-bottom: 4px; background: #fff; padding: 8px 40px; + @media screen and (width <= $largeBreakPoint) { + padding: 8px; + } + h1 { font-size: 1.2rem; } @@ -76,11 +59,29 @@ .funding { display: flex; + gap: 4px; + flex-wrap: nowrap; + overflow: hidden; + + dd { + overflow: hidden; + } + + a.address { + @extend %hash; - .funder { - flex: 0.4; + min-width: 180px; + } + } + + .outPoint { + dd { overflow: hidden; } + + a { + @extend %hash; + } } .nodesContainer { @@ -89,6 +90,12 @@ padding: 8px; margin-top: 8px; background: rgb(0 0 0 / 3%); + + dt, + dd { + display: flex; + flex-wrap: nowrap; + } } .nodes { @@ -114,9 +121,18 @@ .node { flex: 1; + overflow: hidden; + + dd { + overflow: hidden; + } + + a { + @extend %hash; + } } - @media screen and (width<690px) { + @media screen and (width <= $mobileBreakPoint) { flex-direction: column; } } diff --git a/src/components/GraphChannelList/index.tsx b/src/components/GraphChannelList/index.tsx index 480b9705b..1653f4cac 100644 --- a/src/components/GraphChannelList/index.tsx +++ b/src/components/GraphChannelList/index.tsx @@ -7,23 +7,18 @@ import type { Fiber } from '../../services/ExplorerService/fetcher' import { parseNumericAbbr } from '../../utils/chart' import { localeNumberString } from '../../utils/number' import { shannonToCkb } from '../../utils/util' -import AddressText from '../AddressText' import styles from './index.module.scss' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' -const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean; node?: string }> = ({ - list, - isFullWidth = true, - node, -}) => { +const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; node?: string }> = ({ list, node }) => { if (!list.length) { return
No Channels
} return (
- {list.map(channel => { + {list.map((channel, i) => { const outPoint = { txHash: channel.channelOutpoint.slice(0, -8), index: parseInt(channel.channelOutpoint.slice(-8), 16), @@ -39,23 +34,21 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean; ? parseNumericAbbr(channel.outpointInfo.fundingUdtAmount) : null + const outpoint = `${outPoint.txHash}#${outPoint.index}` + return (
-

General

-
+

Channel #{i + 1}

+
Out Point
-
- - - {`${outPoint.txHash.slice(0, 6)}...${outPoint.txHash.slice(-6)}#${outPoint.index}`} - - + - {`${outPoint.txHash}#${outPoint.index}`} +
{outpoint.slice(0, -15)}
+
{outpoint.slice(-15)}
-
+ @@ -71,57 +64,38 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean;
-
Funding
-
- - {`${fundingCkbAmount} CKB`} - - {fundingUdtAmount ? '&' : null} - {fundingUdtAmount} - from -
- - {channel.outpointInfo.fundingAddress} - -
-
-
- -
-
Chain Hash
+
Source
-
- - {`${channel.chainHash.slice(0, 8)}...${channel.chainHash.slice( - -8, - )}`} + {fundingUdtAmount || ( + + {`${fundingCkbAmount} CKB`} - {channel.chainHash} -
- + )} + from + + +
{channel.outpointInfo.fundingAddress.slice(0, -15)}
+
{channel.outpointInfo.fundingAddress.slice(-15)}
+ +
- -
-
Funded at
+
+
Position
- - {localeNumberString(channel.fundingTxBlockNumber)} - - (
{dayjs(+channel.lastUpdatedTimestamp).format(TIME_TEMPLATE)}
) + On + + + {localeNumberString(channel.fundingTxBlockNumber)} + +

Nodes

-
+

First Node @@ -131,10 +105,10 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; isFullWidth?: boolean;
ID
- {`0x${channel.node1.slice( - 0, - 8, - )}...${channel.node1.slice(-8)}`} + +
{`0x${channel.node1.slice(0, -8)}`}
+
{channel.node1.slice(-8)}
+

-
+
@@ -150,8 +159,10 @@ const GraphNode = () => {
{dayjs(+node.timestamp).format(TIME_TEMPLATE)}
-
{t('fiber.graph.node.chain_hash')}
-
{node.chainHash}
+
{t('fiber.graph.node.chain')}
+
+ {chain} +
{t('fiber.graph.node.total_capacity')}
@@ -185,8 +196,23 @@ const GraphNode = () => {
-

Open | Close Transactions

- Coming soon +

+ Open Transactions + (Close transactions coming soon) +

+
+ {openTxs.map(tx => ( +
+ Open Channel by + + +
{tx.hash.slice(0, -15)}
+
{tx.hash.slice(-15)}
+ +
+
+ ))} +
diff --git a/src/pages/Fiber/GraphNodeList/index.tsx b/src/pages/Fiber/GraphNodeList/index.tsx index 00d1958ca..0dcc283d4 100644 --- a/src/pages/Fiber/GraphNodeList/index.tsx +++ b/src/pages/Fiber/GraphNodeList/index.tsx @@ -16,6 +16,7 @@ import styles from './index.module.scss' import { shannonToCkb } from '../../../utils/util' import { parseNumericAbbr } from '../../../utils/chart' import { localeNumberString } from '../../../utils/number' +import { ChainHash } from '../../../constants/fiberChainHash' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -109,14 +110,13 @@ const fields = [ }, { key: 'chainHash', - label: 'chain_hash', + label: 'chain', transformer: (v: unknown) => { if (typeof v !== 'string') return v + const chain = ChainHash.get(v) ?? '-' return ( - - {`${v.slice(0, 8)}...${v.slice(-8)}`} - + {chain} diff --git a/src/styles/text.module.scss b/src/styles/text.module.scss new file mode 100644 index 000000000..cbb2415db --- /dev/null +++ b/src/styles/text.module.scss @@ -0,0 +1,21 @@ +%monospace { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; +} + +%hash { + @extend %monospace; + + display: flex; + align-items: center; + overflow: hidden; + user-select: none; + + div { + font-family: inherit; + } + + div:first-child { + overflow: hidden; + text-overflow: ellipsis; + } +} From c9c45711203f09e4a81c5b32878aaadd74833e8a Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 2 Dec 2024 23:23:28 +0900 Subject: [PATCH 21/24] feat: support searching fiber node id and peer id --- src/components/GraphChannelList/index.tsx | 14 ++++++------- .../Search/AggregateSearchResults.tsx | 16 +++++++++++++++ src/components/Search/index.tsx | 1 + src/components/Search/utils.ts | 6 ++++++ src/locales/en.json | 7 ++++--- src/locales/zh.json | 3 ++- src/pages/Fiber/GraphNode/index.tsx | 2 +- src/services/ExplorerService/fetcher.ts | 20 +++++++++++++------ 8 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/components/GraphChannelList/index.tsx b/src/components/GraphChannelList/index.tsx index 1653f4cac..a32a02b11 100644 --- a/src/components/GraphChannelList/index.tsx +++ b/src/components/GraphChannelList/index.tsx @@ -27,11 +27,11 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; node?: string }> = ({ const ckb = shannonToCkb(channel.capacity) const amount = parseNumericAbbr(ckb) - const fundingCkb = shannonToCkb(channel.outpointInfo.fundingCapacity) + const fundingCkb = shannonToCkb(channel.openTransactionInfo.capacity) const fundingCkbAmount = parseNumericAbbr(fundingCkb) - const fundingUdtAmount = channel.outpointInfo.fundingUdtAmount - ? parseNumericAbbr(channel.outpointInfo.fundingUdtAmount) + const fundingUdtAmount = channel.openTransactionInfo.udtAmount + ? parseNumericAbbr(channel.openTransactionInfo.udtAmount) : null const outpoint = `${outPoint.txHash}#${outPoint.index}` @@ -72,10 +72,10 @@ const GraphChannelList: FC<{ list: Fiber.Graph.Channel[]; node?: string }> = ({ )} from - - -
{channel.outpointInfo.fundingAddress.slice(0, -15)}
-
{channel.outpointInfo.fundingAddress.slice(-15)}
+ + +
{channel.openTransactionInfo.address.slice(0, -15)}
+
{channel.openTransactionInfo.address.slice(-15)}
diff --git a/src/components/Search/AggregateSearchResults.tsx b/src/components/Search/AggregateSearchResults.tsx index de54979bc..6f0d451ff 100644 --- a/src/components/Search/AggregateSearchResults.tsx +++ b/src/components/Search/AggregateSearchResults.tsx @@ -259,6 +259,22 @@ const SearchResultItem: FC<{ keyword?: string; item: AggregateSearchResult }> = ) } + if (item.type === SearchResultType.FiberGraphNode) { + return ( + +
+ + +
+ + {t('search.fiber_graph_node')} # {localeNumberString(item.attributes.alias)} + +
+
+ + ) + } + return (
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 8123799b5..8d628193a 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -45,6 +45,7 @@ const ALLOW_SEARCH_TYPES = [ SearchResultType.UDT, SearchResultType.DID, SearchResultType.BtcAddress, + SearchResultType.FiberGraphNode, ] async function fetchAggregateSearchResult(searchValue: string): Promise { diff --git a/src/components/Search/utils.ts b/src/components/Search/utils.ts index 0425169ef..0bb9527b8 100644 --- a/src/components/Search/utils.ts +++ b/src/components/Search/utils.ts @@ -54,6 +54,9 @@ export const getURLByAggregateSearchResult = (result: AggregateSearchResult) => case SearchResultType.BtcAddress: return `/address/${attributes.addressHash}` + case SearchResultType.FiberGraphNode: + return `/fiber/graph/node/${attributes.nodeId}` + default: break } @@ -97,4 +100,7 @@ export const getDisplayNameByAggregateSearchResult = (result: AggregateSearchRes if (type === SearchResultType.BtcAddress) { return attributes.addressHash } + if (type === SearchResultType.FiberGraphNode) { + return attributes.peerId + } } diff --git a/src/locales/en.json b/src/locales/en.json index 1e4c299b1..fe766623a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -177,7 +177,7 @@ "sUDT": "sUDT", "inscriptions": "Inscriptions", "docs": "Docs", - "search_placeholder": "Block/Transaction/Address/Script Hash/Args/BTC Address/BTC Txid/DID", + "search_placeholder": "Block/Transaction/Address/Script Hash/Args/BTC Address/BTC Txid/DID/Fiber Peer Id/Fiber Node Id", "search_by_name_placeholder": "Token Name", "more": "More", "mainnet": "LINA", @@ -377,7 +377,7 @@ "loading": "Loading...", "no_search_result": "Oops! Your search did not match any record.", "empty_result": "Oops! Your search did not match any record. \n\nPlease make sure input contains only one of the following items:\n", - "empty_result_items": "Block Number/ Block Hash/ Transaction Hash/ Address/ Script Hash/ Args/ BTC Address/ BTC Txid/DID", + "empty_result_items": "Block Number/ Block Hash/ Transaction Hash/ Address/ Script Hash/ Args/ BTC Address/ BTC Txid/DID/Fiber Peer Id/Fiber Node Id", "address_type_testnet_error": "Testnet address detected,please goto", "address_type_mainnet_error": "Mainnet address detected,please goto", "address_type_testnet_url": "testnet explorer", @@ -401,7 +401,8 @@ "bitcoin_address": "BTC Address", "token_collection": "Token Collection", "token_item": "Token Item", - "did": "DID" + "did": "DID", + "fiber_graph_node": "Fiber Graph Node" }, "cell": { "live_cell": "Live Cell", diff --git a/src/locales/zh.json b/src/locales/zh.json index fae856f2b..605431e21 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -416,7 +416,8 @@ "bitcoin_address": "BTC 地址", "token_collection": "藏品集", "token_item": "藏品", - "did": "分布式数字身份(DID)" + "did": "分布式数字身份(DID)", + "fiber_graph_node": "Fiber Graph Node" }, "address": { "address": "地址", diff --git a/src/pages/Fiber/GraphNode/index.tsx b/src/pages/Fiber/GraphNode/index.tsx index 3019596b9..7a548367a 100644 --- a/src/pages/Fiber/GraphNode/index.tsx +++ b/src/pages/Fiber/GraphNode/index.tsx @@ -103,7 +103,7 @@ const GraphNode = () => { const chain = ChainHash.get(node.chainHash) ?? '-' const openTxs = node.fiberGraphChannels.map(c => ({ - hash: c.outpointInfo.txHash, + hash: c.openTransactionInfo.txHash, index: c.fundingTxIndex, })) diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 59d643290..759e8cb2c 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -70,6 +70,7 @@ export enum SearchResultType { TokenItem = 'token_item', DID = 'did', BtcAddress = 'bitcoin_address', + FiberGraphNode = 'fiber_graph_node', } enum SearchQueryType { @@ -113,6 +114,14 @@ export type AggregateSearchResult = }, SearchResultType.BtcAddress > + | Response.Wrapper< + { + alias: string + nodeId: string + peerId: string + }, + SearchResultType.FiberGraphNode + > export const getBtcTxList = (idList: string[]): Promise> => { if (idList.length === 0) return Promise.resolve({}) @@ -1546,14 +1555,13 @@ export namespace Fiber { autoAcceptAmount: string } - interface FundingInfo { + interface OpenTransactionInfo { + address: string blockNumber: number blockTimestamp: number - fundingAddress: string - fundingCapacity: string - fundingUdtAmount: string | null - transactionFee: number + capacity: string txHash: string + udtAmount?: string } export interface Node { @@ -1580,7 +1588,7 @@ export namespace Fiber { node1ToNode2FeeRate: string node2ToNode1FeeRate: string capacity: string - outpointInfo: FundingInfo + openTransactionInfo: OpenTransactionInfo } export interface NodeDetail extends Node { From 8bc5c4e63d0e87ce66ef07c8d269dd6904364eb6 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 9 Dec 2024 11:45:17 +0800 Subject: [PATCH 22/24] feat: add new chain hash for fiber --- src/constants/fiberChainHash.ts | 1 + src/pages/Fiber/GraphNode/index.module.scss | 28 +++- src/pages/Fiber/GraphNode/index.tsx | 167 +++++++++++++++++--- src/services/ExplorerService/fetcher.ts | 12 ++ 4 files changed, 180 insertions(+), 28 deletions(-) diff --git a/src/constants/fiberChainHash.ts b/src/constants/fiberChainHash.ts index 16b3f6cf3..eaad4f749 100644 --- a/src/constants/fiberChainHash.ts +++ b/src/constants/fiberChainHash.ts @@ -1,3 +1,4 @@ export const ChainHash = new Map([ ['0x0000000000000000000000000000000000000000000000000000000000000000', 'CKB Testnet'], + ['0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606', 'CKB Testnet'], ]) diff --git a/src/pages/Fiber/GraphNode/index.module.scss b/src/pages/Fiber/GraphNode/index.module.scss index 09e26d542..d93a43d4f 100644 --- a/src/pages/Fiber/GraphNode/index.module.scss +++ b/src/pages/Fiber/GraphNode/index.module.scss @@ -131,6 +131,11 @@ border-radius: 6px; padding: 16px; box-shadow: 0 2px 6px 0 #4d4d4d33; + font-size: 0.8em; + + * { + font-size: inherit; + } h3 { margin: 0; @@ -184,10 +189,27 @@ } .tx { - padding: 8px 8px 0; + padding: 8px 40px; display: flex; - align-items: center; - gap: 4px; + flex-direction: column; + + @media screen and (width < $extraLargeBreakPoint) { + padding: 8px; + } + + time { + margin-right: auto; + } + + & > div { + display: flex; + align-items: center; + gap: 4px; + } + + .addr { + @extend %hash; + } a { @extend %monospace; diff --git a/src/pages/Fiber/GraphNode/index.tsx b/src/pages/Fiber/GraphNode/index.tsx index 7a548367a..2e1224ccb 100644 --- a/src/pages/Fiber/GraphNode/index.tsx +++ b/src/pages/Fiber/GraphNode/index.tsx @@ -1,8 +1,8 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' -import { CopyIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons' +import { CopyIcon, Link1Icon, LinkBreak1Icon, OpenInNewWindowIcon } from '@radix-ui/react-icons' import { Tooltip } from 'antd' import QRCode from 'qrcode' import dayjs from 'dayjs' @@ -17,6 +17,7 @@ import { shannonToCkb } from '../../../utils/util' import { parseNumericAbbr } from '../../../utils/chart' import { ChainHash } from '../../../constants/fiberChainHash' import { Link } from '../../../components/Link' +import { localeNumberString } from '../../../utils/number' const TIME_TEMPLATE = 'YYYY/MM/DD hh:mm:ss' @@ -75,6 +76,68 @@ const GraphNode = () => { ) }, [qrRef, connectId]) + const openAndClosedTxs = useMemo(() => { + const list: { + hash: string + index?: string + block: { + number: number + timestamp: number + } + isUdt: boolean + isOpen: boolean + accounts: Record<'amount' | 'address', string>[] + }[] = [] + + if (!node?.fiberGraphChannels) return list + + node.fiberGraphChannels.forEach(c => { + const isUdt = !!c.openTransactionInfo.udtAmount + const open = { + isOpen: true, + isUdt, + hash: c.openTransactionInfo.txHash, + index: c.fundingTxIndex, + block: { + number: c.openTransactionInfo.blockNumber, + timestamp: c.openTransactionInfo.blockTimestamp, + }, + accounts: [ + { + address: c.openTransactionInfo.address, + amount: + c.openTransactionInfo.udtAmount ?? + `${localeNumberString(shannonToCkb(c.openTransactionInfo.capacity))} CKB`, + }, + ], + } + + list.push(open) + + const close = c.closedTransactionInfo + ? { + isOpen: false, + hash: c.closedTransactionInfo.txHash, + block: { + number: c.closedTransactionInfo.blockNumber, + timestamp: c.closedTransactionInfo.blockTimestamp, + }, + isUdt, + accounts: c.closedTransactionInfo.closeAccounts.map(acc => { + return { + amount: acc.udtAmount ?? `${localeNumberString(shannonToCkb(acc.capacity))} CKB`, + address: acc.address, + } + }), + } + : null + if (close) { + list.push(close) + } + }) + return list.sort((a, b) => a.block.timestamp - b.block.timestamp) + }, [node]) + if (isLoading) { return } @@ -82,10 +145,8 @@ const GraphNode = () => { if (!node) { return
Fiber Peer Not Found
} - const channels = node.fiberGraphChannels + const channels = node.fiberGraphChannels.filter(c => !c.closedTransactionInfo) - // const ckb = shannonToCkb(node.autoAcceptMinCkbFundingAmount) - // const amount = parseNumericAbbr(ckb) const thresholds = getFundingThreshold(node) const totalCkb = parseNumericAbbr(shannonToCkb(node.totalCapacity)) @@ -102,11 +163,6 @@ const GraphNode = () => { const chain = ChainHash.get(node.chainHash) ?? '-' - const openTxs = node.fiberGraphChannels.map(c => ({ - hash: c.openTransactionInfo.txHash, - index: c.fundingTxIndex, - })) - return (
@@ -196,22 +252,83 @@ const GraphNode = () => {
-

- Open Transactions - (Close transactions coming soon) -

+

Open & Closed Transactions

- {openTxs.map(tx => ( -
- Open Channel by - - -
{tx.hash.slice(0, -15)}
-
{tx.hash.slice(-15)}
- -
-
- ))} + {openAndClosedTxs.map(tx => { + const key = tx.isOpen ? `${tx.hash}#${tx.index}` : tx.hash + if (tx.isOpen) { + const account = tx.accounts[0]! + return ( +
+
+ + at + + + + + + +
+
+ By + + + +
{account.address.slice(0, -8)}
+
{account.address.slice(-8)}
+ +
+
+ ({account.amount}) +
+
+ ) + } + const [acc1, acc2] = tx.accounts + return ( +
+
+ + at + + + + + + +
+
+ To + + + +
{acc1.address.slice(0, -8)}
+
{acc1.address.slice(-8)}
+ +
+
+ ({acc1.amount}) +
+
+ And + + + +
{acc2.address.slice(0, -8)}
+
{acc2.address.slice(-8)}
+ +
+
+ ({acc2.amount}) +
+
+ ) + })}
diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 759e8cb2c..9f0ed87e8 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -1564,6 +1564,17 @@ export namespace Fiber { udtAmount?: string } + interface ClosedTransactionInfo { + blockNumber: number + blockTimestamp: number + txHash: string + closeAccounts: { + address: string + capacity: string + udtAmount: string | null + }[] + } + export interface Node { alias: string nodeId: string @@ -1589,6 +1600,7 @@ export namespace Fiber { node2ToNode1FeeRate: string capacity: string openTransactionInfo: OpenTransactionInfo + closedTransactionInfo: ClosedTransactionInfo } export interface NodeDetail extends Node { From 925f3d4bdf261589f7459558d26aed03c81a5d63 Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 17 Dec 2024 12:20:42 +0800 Subject: [PATCH 23/24] fix: filter invalid tx out --- src/pages/Fiber/GraphNode/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Fiber/GraphNode/index.tsx b/src/pages/Fiber/GraphNode/index.tsx index 2e1224ccb..cc3ff4390 100644 --- a/src/pages/Fiber/GraphNode/index.tsx +++ b/src/pages/Fiber/GraphNode/index.tsx @@ -114,7 +114,7 @@ const GraphNode = () => { list.push(open) - const close = c.closedTransactionInfo + const close = c.closedTransactionInfo?.txHash ? { isOpen: false, hash: c.closedTransactionInfo.txHash, From 2469b584d3e776845cfb93c7bf6ebf038890dbd5 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 23 Dec 2024 11:31:41 +0800 Subject: [PATCH 24/24] feat: set "find nodes" in banner available --- src/pages/Home/Banner/index.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/pages/Home/Banner/index.tsx b/src/pages/Home/Banner/index.tsx index 686576b48..49f3ca9a0 100644 --- a/src/pages/Home/Banner/index.tsx +++ b/src/pages/Home/Banner/index.tsx @@ -1,7 +1,6 @@ import { useQuery } from '@tanstack/react-query' import { BarChartIcon } from '@radix-ui/react-icons' import { useTranslation } from 'react-i18next' -import { Tooltip } from 'antd' import { Link } from '../../../components/Link' import config from '../../../config' import styles from './index.module.scss' @@ -56,17 +55,9 @@ export default () => { {t(`banner.learn_more`)} - - ) => { - e.preventDefault() - }} - > - {t('banner.find_nodes')} - - + + {t('banner.find_nodes')} +
)