diff --git a/web/src/NetworkExitsPage.jsx b/web/src/NetworkExitsPage.jsx index 2de0925..aecb2b6 100644 --- a/web/src/NetworkExitsPage.jsx +++ b/web/src/NetworkExitsPage.jsx @@ -15,6 +15,7 @@ import { PageState, Select, StatusPill, + Table, conflictMessage, } from "./lib/ui.jsx"; import { useTitle } from "./lib/hooks.js"; @@ -239,50 +240,90 @@ function ExitCreateModal({ open, onClose, onSubmit, busy, error }) { ); } -function ExitCard({ exit, boundAccounts, bindingsError, busy, onAction }) { - return ( - - -
-
- - {exit.protocol}://{exit.host}:{exit.port} - -

- {exit.id} · 用户名 {exit.username || "无"} · 密码{" "} - {exit.password || "无"} -

-
- +function ExitTable({ exits, accountsByExit, bindingsError, busy, onAction }) { + const columns = [ + { + header: "出口", + width: "20%", + render: (exit) => ( +
+ + {exit.protocol}://{exit.host}:{exit.port} + +

{exit.id}

-
- 出口IP: + ), + }, + { + header: "状态", + width: "9%", + render: (exit) => , + }, + { + header: "用户名", + width: "11%", + render: (exit) => ( + {exit.username || "无"} + ), + }, + { + header: "密码", + width: "11%", + render: (exit) => ( + {exit.password || "无"} + ), + }, + { + header: "出口IP", + width: "13%", + render: (exit) => ( + {exit.observed_public_ip || "尚无观测"} + ), + }, + { + header: "最近检测", + width: "15%", + render: (exit) => + exit.last_checked_at + ? new Date(exit.last_checked_at).toLocaleString("zh-CN") + : "未检测", + }, + { + header: "绑定账号", + width: "13%", + render: (exit) => { + if (bindingsError) return 状态未知; + const accounts = accountsByExit.get(exit.id); + return ( - {exit.observed_public_ip || "尚无观测"} + {accounts?.length ? accounts.join("、") : "未绑定"} -
-

- 最近检测: - {exit.last_checked_at - ? new Date(exit.last_checked_at).toLocaleString("zh-CN") - : "未检测"} -

-
- 绑定账号: - {bindingsError ? ( - "状态未知" - ) : ( - - {boundAccounts?.length ? boundAccounts.join("、") : "未绑定"} - - )} -
- - - + ); + }, + }, + { + header: "操作", + width: "8%", + align: "right", + render: (exit) => ( + + ), + }, + ]; + return ( + exit.id} + minWidth="1100px" + /> ); } @@ -402,22 +443,15 @@ export function NetworkExitList() { empty={exits.length === 0} emptyText="暂无网络出口。创建并检测健康后,才能绑定运行环境。" > -
- {exits.map((exit) => ( - - action === "disable" - ? setDisabling(exit) - : runAction(exit, action) - } - /> - ))} -
+ + action === "disable" ? setDisabling(exit) : runAction(exit, action) + } + />