shared: add independent IPv4 and IPv6 collection [CI]
This commit is contained in:
1 parent
c974a7dddb
commit
0eda0600cc
60 files changed
+699
-137
No files matched your search
@@ -13,6 +13,7 @@ function EditDialog({ agent, onClose, onSaved }: { agent: AgentDto; onClose: ()
|
||||
const [name, setName] = useState(agent.name)
|
||||
const [labels, setLabels] = useState(agent.labels.join(', '))
|
||||
const [host, setHost] = useState(agent.public_host)
|
||||
const [ipv6Host, setIpv6Host] = useState(agent.ipv6_host ?? '')
|
||||
const [visible, setVisible] = useState(agent.public_visible)
|
||||
return (
|
||||
<Dialog
|
||||
@@ -29,7 +30,7 @@ function EditDialog({ agent, onClose, onSaved }: { agent: AgentDto; onClose: ()
|
||||
disabled={busy}
|
||||
onClick={async () => {
|
||||
const ok = await run(
|
||||
() => api.patch(`/api/admin/agents/${agent.id}`, { name, labels: labels.split(',').map((x) => x.trim()).filter(Boolean), public_host: host, public_visible: visible }),
|
||||
() => api.patch(`/api/admin/agents/${agent.id}`, { name, labels: labels.split(',').map((x) => x.trim()).filter(Boolean), public_host: host.trim(), ipv6_host: ipv6Host.trim(), public_visible: visible }),
|
||||
'已保存',
|
||||
)
|
||||
if (ok) {
|
||||
@@ -50,10 +51,15 @@ function EditDialog({ agent, onClose, onSaved }: { agent: AgentDto; onClose: ()
|
||||
</Field>
|
||||
</div>
|
||||
<div className="wide">
|
||||
<Field label="公开地址" hint="Probe 用它连接这个 Agent:域名或 IP">
|
||||
<Field label="IPv4 地址" hint="Probe 用它测 IPv4:IPv4 地址或域名(A 记录)">
|
||||
<input className="input" value={host} onChange={(e) => setHost(e.target.value)} />
|
||||
</Field>
|
||||
</div>
|
||||
<div className="wide">
|
||||
<Field label="IPv6 地址" hint="IPv6 地址或域名(AAAA 记录);留空只采集 IPv4">
|
||||
<input className="input" value={ipv6Host} onChange={(e) => setIpv6Host(e.target.value)} placeholder="例如 2001:db8::1 或 agent.example.com" />
|
||||
</Field>
|
||||
</div>
|
||||
<div className="wide">
|
||||
<Field label="标签" hint="逗号分隔">
|
||||
<input className="input" value={labels} onChange={(e) => setLabels(e.target.value)} />
|
||||
@@ -167,9 +173,9 @@ export function AgentDetail() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{a.probes.map((p) => (
|
||||
<tr key={p.probe_id}>
|
||||
<tr key={`${p.probe_id}~${p.ip_version}`}>
|
||||
<td>
|
||||
<div style={{ fontWeight: 600 }}>{probeName(p.probe_id)}</div>
|
||||
<div style={{ fontWeight: 600 }}>{probeName(p.probe_id)} <Chip>{p.ip_version === 'ipv6' ? 'IPv6' : 'IPv4'}</Chip></div>
|
||||
<div className="mono faint">{p.probe_id}</div>
|
||||
</td>
|
||||
<td>{p.version ?? '—'}</td>
|
||||
@@ -179,7 +185,7 @@ export function AgentDetail() {
|
||||
</td>
|
||||
<td className="r">
|
||||
<span className="row" style={{ justifyContent: 'flex-end' }}>
|
||||
<Link className="btn small" to={`/tests?agent=${a.id}&probe=${p.probe_id}`}>
|
||||
<Link className="btn small" to={`/tests?agent=${a.id}&probe=${p.probe_id}&ip_version=${p.ip_version}`}>
|
||||
<FlaskConical size={14} />
|
||||
测试
|
||||
</Link>
|
||||
@@ -209,8 +215,12 @@ export function AgentDetail() {
|
||||
<dl className="kv">
|
||||
<dt>ID</dt>
|
||||
<dd className="mono">{a.id}</dd>
|
||||
<dt>公开地址</dt>
|
||||
<dt>IPv4 地址</dt>
|
||||
<dd className="mono">{a.public_host || '—'}</dd>
|
||||
<dt>IPv6 地址</dt>
|
||||
<dd className="mono">{a.ipv6_host || '未配置'}</dd>
|
||||
<dt>IPv6 监听</dt>
|
||||
<dd>{a.ipv6_supported ? '已启用' : '未启用,请升级 Agent 并配置双栈监听'}</dd>
|
||||
<dt>控制端口</dt>
|
||||
<dd className="mono">{a.probe_tcp_port}/tcp</dd>
|
||||
<dt>数据端口</dt>
|
||||
|
||||
@@ -209,7 +209,7 @@ export function Probes() {
|
||||
) : (
|
||||
<>
|
||||
<Chip tone={p.online ? 'ok' : ''}>{p.online ? '已连面板' : '未连面板'}</Chip>
|
||||
<Chip tone={p.connections.length > 0 ? 'ok' : ''}>{p.connections.length > 0 ? `连着 ${p.connections.length} 个 Agent` : '没有连接'}</Chip>
|
||||
<Chip tone={p.connections.length > 0 ? 'ok' : ''}>{p.connections.length > 0 ? `连着 ${new Set(p.connections.map((c) => c.agent_id)).size} 个 Agent` : '没有连接'}</Chip>
|
||||
</>
|
||||
)}
|
||||
<span className="mono faint">{p.id}</span>
|
||||
@@ -252,10 +252,11 @@ export function Probes() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{p.connections.map((c) => (
|
||||
<tr key={c.agent_id}>
|
||||
<tr key={`${c.agent_id}~${c.ip_version}`}>
|
||||
<td>
|
||||
<Link to={`/agents/${c.agent_id}`} style={{ fontWeight: 600 }}>
|
||||
{c.agent_name}
|
||||
<Chip>{c.ip_version === 'ipv6' ? 'IPv6' : 'IPv4'}</Chip>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{c.version ?? '—'}</td>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FlaskConical, Square } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { IpVersionPicker, ipVersionOf } from '@shared/ipVersions'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { api } from '@shared/api/client'
|
||||
import type { AgentDto, Page, PlanDto, ProbeDto, TestDto } from '@shared/api/types'
|
||||
@@ -84,6 +85,7 @@ function Result({ test }: { test: TestDto }) {
|
||||
|
||||
export function Tests() {
|
||||
const [params, setParams] = useSearchParams()
|
||||
const ipVersion = ipVersionOf(params.get('ip_version'))
|
||||
const agents = useQuery(() => api.get<Page<AgentDto>>('/api/admin/agents'), [], 10_000)
|
||||
const probes = useQuery(() => api.get<Page<ProbeDto>>('/api/admin/probes'), [])
|
||||
const plans = useQuery(() => api.get<Page<PlanDto>>('/api/public/catalog/plans'), [])
|
||||
@@ -92,16 +94,18 @@ export function Tests() {
|
||||
const active = (agents.data?.items ?? []).filter((a) => a.status === 'active')
|
||||
const agentId = params.get('agent') ?? active[0]?.id ?? ''
|
||||
const agent = active.find((a) => a.id === agentId)
|
||||
const connected = agent?.probes ?? []
|
||||
const connected = (agent?.probes ?? []).filter((p) => ipVersionOf(p.ip_version) === ipVersion)
|
||||
const probeId = params.get('probe') ?? connected[0]?.probe_id ?? ''
|
||||
const [planId, setPlanId] = useState('')
|
||||
const [selected, setSelected] = useState<string>()
|
||||
const line = agentId && probeId ? `${agentId}~${probeId}` : ''
|
||||
const line = agentId && probeId ? `${agentId}~${probeId}${ipVersion === 'ipv6' ? '~ipv6' : ''}` : ''
|
||||
|
||||
useEffect(() => {
|
||||
if (!planId && plans.data?.items[0]) setPlanId(plans.data.items[0].id)
|
||||
}, [plans.data, planId])
|
||||
|
||||
useEffect(() => { setSelected(undefined) }, [line])
|
||||
|
||||
const tests = useQuery(() => (line ? api.get<Page<TestDto>>(`/api/admin/lines/${encodeURIComponent(line)}/tests`, { limit: 20 }) : Promise.resolve(undefined)), [line], 3000)
|
||||
const detail = useQuery(() => (selected ? api.get<TestDto>(`/api/public/tests/${selected}`) : Promise.resolve(undefined)), [selected, tests.data?.items[0]?.status], 3000)
|
||||
const probeName = (id: string) => probes.data?.items.find((p) => p.id === id)?.name ?? id
|
||||
@@ -118,6 +122,7 @@ export function Tests() {
|
||||
<>
|
||||
<header>
|
||||
<h1>按需测试</h1>
|
||||
<IpVersionPicker value={ipVersion} onChange={(v) => set({ ip_version: v })} />
|
||||
<span className="right">{agents.loading && !agents.data ? <Spinner /> : null}</span>
|
||||
</header>
|
||||
<ErrorNotice error={agents.error ?? plans.error ?? tests.error} />
|
||||
@@ -190,7 +195,7 @@ export function Tests() {
|
||||
) : (
|
||||
<table className="table hover">
|
||||
<tbody>
|
||||
{tests.data?.items.map((t) => (
|
||||
{tests.data?.items.filter((t) => t.agent_id === agentId && t.probe_id === probeId && ipVersionOf(t.ip_version) === ipVersion).map((t) => (
|
||||
<tr key={t.id} onClick={() => setSelected(t.id)} style={selected === t.id ? { background: 'var(--app-card-hover)' } : undefined}>
|
||||
<td>
|
||||
<Chip tone={STATUS[t.status]?.tone ?? ''}>{STATUS[t.status]?.label ?? t.status}</Chip>
|
||||
@@ -221,7 +226,7 @@ export function Tests() {
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="card-title">结果</div>
|
||||
{detail.data ? (
|
||||
{detail.data && detail.data.id === selected && ipVersionOf(detail.data.ip_version) === ipVersion ? (
|
||||
<>
|
||||
<p className="faint" style={{ marginBottom: 12 }}>
|
||||
{detail.data.plan} · {STATUS[detail.data.status]?.label ?? detail.data.status}
|
||||
|
||||
Reference in new issue
Block a user