Skip to content

Commit

Permalink
feat: add fiber node map
Browse files Browse the repository at this point in the history
  • Loading branch information
Keith-CY committed Feb 7, 2025
1 parent e5aca33 commit e362b92
Show file tree
Hide file tree
Showing 12 changed files with 365 additions and 27 deletions.
174 changes: 174 additions & 0 deletions src/components/GraphNodeIps/NodesMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { useEffect, useRef } from 'react'
import 'echarts/lib/component/tooltip'
import 'echarts-gl'
import 'echarts/map/js/world' // Load the world map
import echarts from 'echarts/lib/echarts'
import styles from './index.module.scss'
import { getPrimaryColor } from '../../constants/common'

const primaryColor = getPrimaryColor()

export type IpPoint = {
id: string
ip: string
lon: number
lat: number
city: string
connections: string[] // other node id
}

type EchartPoint = [long: number, lat: number, city: string]
type EchartLine = [EchartPoint, EchartPoint]

export const isValidIpPoint = (ip: unknown): ip is IpPoint => {
if (typeof ip !== 'object' || ip === null) return false
if (typeof (ip as IpPoint).ip !== 'string') return false
if (typeof (ip as IpPoint).lon !== 'number') return false
if (typeof (ip as IpPoint).lat !== 'number') return false
if (typeof (ip as IpPoint).city !== 'string') return false
return true
}

const option = {
backgroundColor: '#000',
geo: {
silent: true,
map: 'world',
roam: true,
zoom: 2,
label: {
show: false,
},
itemStyle: {
areaColor: '#1b1b1b',
borderColor: '#555',
},
emphasis: {
areaColor: '#444',
},
environment: '#333',
},
}

export const NodesMap = ({ ips }: { ips: IpPoint[] }) => {
const containerRef = useRef<HTMLDivElement | null>(null)

useEffect(() => {
if (!containerRef.current) return
const points: EchartPoint[] = ips.map(i => [i.lon, i.lat, i.city])
if (!points.length) return
let ins = echarts.getInstanceByDom(containerRef.current)
if (!ins) {
ins = echarts.init(containerRef.current)
}

const lines: EchartLine[] = []

ips.forEach(ip => {
ip.connections.forEach(connId => {
const conn = ips.find(i => i.id === connId)

if (!conn) {
return
}

lines.push([
[ip.lon, ip.lat, ip.city],
[conn.lon, conn.lat, conn.city],
])
})
})

const series = [
{
type: 'effectScatter',
coordinateSystem: 'geo',
data: points,
symbolSize: 8,
rippleEffect: {
scale: 3,
brushType: 'stroke',
},
itemStyle: {
color: primaryColor,
shadowBlur: 10,
shadowColor: primaryColor,
},
tooltip: {
show: true,
},
label: {
show: true,
position: 'right',
formatter: (p: { data: EchartPoint }) => {
return p.data[2]
},
color: '#fff',
fontSize: 8,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
padding: [4, 6],
broderRadius: 3,
},
},
{
type: 'lines',
coordinateSystem: 'geo',
zlevel: 2,
effect: {
show: true,
period: 1,
trailLength: 0.1, // Shorter trail
symbol: 'arrow',
symbolSize: 3,
},
animationEasing: 'cubicOut',

lineStyle: {
curveness: 0.2,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{ offset: 0, color: primaryColor },
{ offset: 0.5, color: '#00ffea' },
{ offset: 1, color: primaryColor },
],
},
width: 1,
opacity: 0.4,
},
data: lines,
},
]

ins.setOption({
...option,
series,
} as any)
}, [ips])

useEffect(() => {
if (!containerRef.current) return
const ins = echarts.getInstanceByDom(containerRef.current)
const handleResize = () => {
if (ins) {
ins.resize()
}
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})

if (!ips) {
return <div>Data not found</div>
}

return <div className={styles.container} ref={containerRef} />
}

export default NodesMap
6 changes: 6 additions & 0 deletions src/components/GraphNodeIps/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.container {
width: 100%;
height: 400px;
border-radius: 4px;
overflow: hidden;
}
59 changes: 59 additions & 0 deletions src/components/GraphNodeIps/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useQuery } from '@tanstack/react-query'
import { explorerService } from '../../services/ExplorerService'
import { fetchIpsInfo } from '../../services/UtilityService'
import NodesMap, { isValidIpPoint, type IpPoint } from './NodesMap'

const IP_REGEXP = /\/ip4\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/

const getIpFromP2pAddr = (p2pAddr: string) => {
const match = p2pAddr.match(IP_REGEXP)
if (match) {
return match[1]
}
return null
}

const GraphNodeIps = () => {
const { data: nodes } = useQuery({
queryKey: ['fiber_graph_node_addresses'],
queryFn: explorerService.api.getGraphNodeIPs,
refetchInterval: 60 * 1000 * 10, // 10 minutes
})

const ips =
(nodes?.data
.map(i => i.addresses)
.flat()
.map(getIpFromP2pAddr)
.filter(ip => !!ip) as string[]) ?? []

const { data: ipInfos } = useQuery({
queryKey: ['fiber_graph_ips_info', ips.join(',')],
queryFn: () => (ips.length ? fetchIpsInfo(ips) : undefined),
enabled: !!ips.length,
})

const list =
nodes?.data
?.map(node => {
const ips = node.addresses.map(getIpFromP2pAddr)

const infos = ips
.map(ip => {
const ipInfo = ipInfos?.ips[ip as keyof typeof ipInfos.ips]
return {
...ipInfo,
ip,
connections: node.connections,
id: node.nodeId,
}
})
.filter(p => isValidIpPoint(p)) as IpPoint[]
return infos
})
.flat() ?? []

return <NodesMap ips={list} />
}

export default GraphNodeIps
2 changes: 2 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,8 @@
"mean_fee_rate": "Mean Fee Rate",
"median_locked_capacity": "Median Locked Capacity",
"median_fee_rate": "Median Fee Rate",
"public_fiber_node_world_map": "Public Fiber Node World Map",
"public_fiber_nodes": "Public Fiber Nodes",
"node": {
"id": "Node ID",
"name": "Name",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,8 @@
"mean_fee_rate": "平均费率",
"median_locked_capacity": "中位数锁定 Capacity",
"median_fee_rate": "中位数费率",
"public_fiber_node_world_map": "公开 Fiber 节点地图",
"public_fiber_nodes": "公开 Fiber 节点",
"node": {
"id": "节点 ID",
"name": "名称",
Expand Down
7 changes: 7 additions & 0 deletions src/pages/Fiber/Graph/MeanAndMedium/index.module.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import '../../../../styles//variables.module.scss';

.container {
font-size: 14px;
background: #fafafa;
Expand Down Expand Up @@ -49,6 +51,11 @@
margin-left: 4px;
}
}

@media screen and (width < $extraLargeBreakPoint) {
display: flex;
gap: 20px;
}
}

hr {
Expand Down
Loading

0 comments on commit e362b92

Please sign in to comment.