chore: 前端代码风格统一并清理未使用导入;移除过时的 PR 流程规则

This commit is contained in:
2026-09-03 17:16:08 +08:00
parent 934d2c1bc5
commit de91d7a882
7 changed files with 1120 additions and 397 deletions
+425 -126
View File
@@ -1,87 +1,242 @@
import { useMemo, useState } from 'react'
import { Link, useParams } from 'react-router'
import { useDataProvider, useList, useOne } from '@refinedev/core'
import { useMemo, useState } from "react";
import { Link, useParams } from "react-router";
import { useDataProvider, useList, useOne } from "@refinedev/core";
import {
Alert, Button, Card, CardContent, ConfirmDialog, DetailList, Field, Input, Modal, PageHeader,
PageState, Select, StatusPill, conflictMessage,
} from './lib/ui.jsx'
import { useTitle } from './lib/hooks.js'
Alert,
Button,
Card,
CardContent,
ConfirmDialog,
DetailList,
Field,
Input,
Modal,
PageHeader,
PageState,
Select,
StatusPill,
conflictMessage,
} from "./lib/ui.jsx";
import { useTitle } from "./lib/hooks.js";
const createInitial = { protocol: 'socks5', host: '', port: '', username: '', password: '', expected_public_ip: '', expected_region: '' }
const healthText = { unchecked: '未检测', healthy: '健康', unhealthy: '不健康', disabled: '已停用' }
const protocolOptions = ['http', 'https', 'socks4', 'socks5'].map(value => ({ value, label: value }))
const createInitial = {
protocol: "socks5",
host: "",
port: "",
username: "",
password: "",
expected_public_ip: "",
expected_region: "",
};
const healthText = {
unchecked: "未检测",
healthy: "健康",
unhealthy: "不健康",
disabled: "已停用",
};
const protocolOptions = ["http", "https", "socks4", "socks5"].map((value) => ({
value,
label: value,
}));
function ExitHealthPill({ exit }) {
const known = Object.hasOwn(healthText, exit.health_status)
const tone = exit.health_status === 'healthy' ? 'success' : exit.health_status === 'disabled' ? 'neutral' : exit.health_status === 'unchecked' ? 'warning' : 'danger'
return <StatusPill tone={tone} label={known ? healthText[exit.health_status] : '未知状态'} sub={known ? undefined : exit.health_status} />
const known = Object.hasOwn(healthText, exit.health_status);
const tone =
exit.health_status === "healthy"
? "success"
: exit.health_status === "disabled"
? "neutral"
: exit.health_status === "unchecked"
? "warning"
: "danger";
return (
<StatusPill
tone={tone}
label={known ? healthText[exit.health_status] : "未知状态"}
sub={known ? undefined : exit.health_status}
/>
);
}
function ExitActions({ exit, busy, onAction }) {
const disabled = busy || exit.health_status === 'disabled'
const disabled = busy || exit.health_status === "disabled";
return (
<div className="flex flex-wrap gap-2">
<Button size="sm" icon="ri-refresh-line" disabled={disabled} onClick={() => onAction(exit, 'check')}>检测</Button>
<Button size="sm" variant="danger" icon="ri-forbid-line" disabled={disabled} onClick={() => onAction(exit, 'disable')}>停用</Button>
<Button
size="sm"
icon="ri-refresh-line"
disabled={disabled}
onClick={() => onAction(exit, "check")}
>
检测
</Button>
<Button
size="sm"
variant="danger"
icon="ri-forbid-line"
disabled={disabled}
onClick={() => onAction(exit, "disable")}
>
停用
</Button>
</div>
)
);
}
function ExitCreateModal({ open, onClose, onSubmit, busy, error }) {
const [form, setForm] = useState(createInitial)
const update = (key, value) => setForm(current => ({ ...current, [key]: value }))
const port = Number(form.port)
const credentialsValid = !form.password || !!form.username
const valid = form.host.trim() && Number.isInteger(port) && port > 0 && port <= 65535 && credentialsValid
const [form, setForm] = useState(createInitial);
const update = (key, value) =>
setForm((current) => ({ ...current, [key]: value }));
const port = Number(form.port);
const credentialsValid = !form.password || !!form.username;
const valid =
form.host.trim() &&
Number.isInteger(port) &&
port > 0 &&
port <= 65535 &&
credentialsValid;
async function submit(event) {
event.preventDefault()
if (!valid) return
event.preventDefault();
if (!valid) return;
const created = await onSubmit({
protocol: form.protocol, host: form.host.trim(), port,
username: form.username, password: form.password,
expected_public_ip: form.expected_public_ip.trim(), expected_region: form.expected_region.trim(),
})
protocol: form.protocol,
host: form.host.trim(),
port,
username: form.username,
password: form.password,
expected_public_ip: form.expected_public_ip.trim(),
expected_region: form.expected_region.trim(),
});
if (created) {
setForm(createInitial)
onClose()
setForm(createInitial);
onClose();
}
}
return (
<Modal open={open} onClose={onClose} title="创建网络出口" labelledBy="exit-create-title" size="lg"
footer={<>
<Button onClick={onClose} disabled={busy}>取消</Button>
<Button variant="primary" type="submit" form="exit-create-form" busy={busy} busyText="创建中…" disabled={!valid}>创建网络出口</Button>
</>}>
<Modal
open={open}
onClose={onClose}
title="创建网络出口"
labelledBy="exit-create-title"
size="lg"
footer={
<>
<Button onClick={onClose} disabled={busy}>
取消
</Button>
<Button
variant="primary"
type="submit"
form="exit-create-form"
busy={busy}
busyText="创建中…"
disabled={!valid}
>
创建网络出口
</Button>
</>
}
>
<form id="exit-create-form" onSubmit={submit} noValidate>
{error ? <Alert variant="destructive" className="mb-4">{conflictMessage(error, '出口地址或认证信息与现有资源冲突;表单内容已保留。')}</Alert> : null}
{error ? (
<Alert variant="destructive" className="mb-4">
{conflictMessage(
error,
"出口地址或认证信息与现有资源冲突;表单内容已保留。",
)}
</Alert>
) : null}
<div className="grid gap-4 sm:grid-cols-3">
<Field id="exit-protocol" label="协议" required helper="代理协议">
<Select id="exit-protocol" value={form.protocol} onChange={event => update('protocol', event.target.value)} options={protocolOptions} />
<Select
id="exit-protocol"
value={form.protocol}
onChange={(event) => update("protocol", event.target.value)}
options={protocolOptions}
/>
</Field>
<Field id="exit-host" label="主机" required helper="代理服务器地址">
<Input id="exit-host" required maxLength={253} value={form.host} onChange={event => update('host', event.target.value)} placeholder="如 proxy.example" />
<Input
id="exit-host"
required
maxLength={253}
value={form.host}
onChange={(event) => update("host", event.target.value)}
placeholder="如 proxy.example"
/>
</Field>
<Field id="exit-port" label="端口" required helper="1..65535">
<Input id="exit-port" type="number" min={1} max={65535} required value={form.port} onChange={event => update('port', event.target.value)} />
<Input
id="exit-port"
type="number"
min={1}
max={65535}
required
value={form.port}
onChange={(event) => update("port", event.target.value)}
/>
</Field>
<Field id="exit-username" label="用户名(可选)" helper="直接保存到系统并用于代理认证">
<Input id="exit-username" maxLength={255} value={form.username} onChange={event => update('username', event.target.value)} />
<Field
id="exit-username"
label="用户名(可选)"
helper="直接保存到系统并用于代理认证"
>
<Input
id="exit-username"
maxLength={255}
value={form.username}
onChange={(event) => update("username", event.target.value)}
/>
</Field>
<Field id="exit-password" label="密码(可选)" error={!credentialsValid ? '填写密码时必须同时填写用户名' : undefined} helper="明文保存并直接用于代理认证">
<Input id="exit-password" maxLength={255} value={form.password} onChange={event => update('password', event.target.value)} invalid={!credentialsValid} />
<Field
id="exit-password"
label="密码(可选)"
error={
credentialsValid ? undefined : "填写密码时必须同时填写用户名"
}
helper="明文保存并直接用于代理认证"
>
<Input
id="exit-password"
maxLength={255}
value={form.password}
onChange={(event) => update("password", event.target.value)}
invalid={!credentialsValid}
/>
</Field>
<Field id="exit-ip" label="出口IP(可选)" helper="健康检测时比对的预期公网 IP">
<Input id="exit-ip" value={form.expected_public_ip} onChange={event => update('expected_public_ip', event.target.value)} />
<Field
id="exit-ip"
label="出口IP(可选)"
helper="健康检测时比对的预期公网 IP"
>
<Input
id="exit-ip"
value={form.expected_public_ip}
onChange={(event) =>
update("expected_public_ip", event.target.value)
}
/>
</Field>
<Field id="exit-region" label="出口地区(可选)" helper="如 CN-Shanghai">
<Input id="exit-region" maxLength={64} value={form.expected_region} onChange={event => update('expected_region', event.target.value)} />
<Field
id="exit-region"
label="出口地区(可选)"
helper="如 CN-Shanghai"
>
<Input
id="exit-region"
maxLength={64}
value={form.expected_region}
onChange={(event) =>
update("expected_region", event.target.value)
}
/>
</Field>
</div>
</form>
</Modal>
)
);
}
function ExitCard({ exit, boundAccounts, bindingsError, busy, onAction }) {
@@ -90,137 +245,281 @@ function ExitCard({ exit, boundAccounts, bindingsError, busy, onAction }) {
<CardContent className="space-y-3">
<div className="flex min-w-0 items-start justify-between gap-3">
<div className="min-w-0">
<Link to={`/network-exits/${encodeURIComponent(exit.id)}`} className="anywhere font-semibold text-ink hover:text-primary">{exit.protocol}://{exit.host}:{exit.port}</Link>
<p className="anywhere text-xs text-muted">{exit.id} · 用户名 {exit.username || '无'} · 密码 {exit.password || '无'}</p>
<Link
to={`/network-exits/${encodeURIComponent(exit.id)}`}
className="anywhere font-semibold text-ink hover:text-primary"
>
{exit.protocol}://{exit.host}:{exit.port}
</Link>
<p className="anywhere text-xs text-muted">
{exit.id} · 用户名 {exit.username || "无"} · 密码{" "}
{exit.password || "无"}
</p>
</div>
<ExitHealthPill exit={exit} />
</div>
<div className="text-sm"><span className="text-muted">出口IP:</span><span className="anywhere">{exit.observed_public_ip || '尚无观测'}</span></div>
<p className="text-xs text-muted">最近检测:{exit.last_checked_at ? new Date(exit.last_checked_at).toLocaleString('zh-CN') : '未检测'}</p>
<div className="text-sm">
<span className="text-muted">出口IP:</span>
<span className="anywhere">
{exit.observed_public_ip || "尚无观测"}
</span>
</div>
<p className="text-xs text-muted">
最近检测:
{exit.last_checked_at
? new Date(exit.last_checked_at).toLocaleString("zh-CN")
: "未检测"}
</p>
<div className="text-sm">
<span className="text-muted">绑定账号:</span>
{bindingsError ? '状态未知' : (
<span className="anywhere">{boundAccounts?.length ? boundAccounts.join('、') : '未绑定'}</span>
{bindingsError ? (
"状态未知"
) : (
<span className="anywhere">
{boundAccounts?.length ? boundAccounts.join("、") : "未绑定"}
</span>
)}
</div>
<ExitActions exit={exit} busy={busy} onAction={onAction} />
</CardContent>
</Card>
)
);
}
export function NetworkExitList() {
const dataProvider = useDataProvider()('default')
const [createOpen, setCreateOpen] = useState(false)
const [busy, setBusy] = useState('')
const [createError, setCreateError] = useState(null)
const [notice, setNotice] = useState(null)
const [disabling, setDisabling] = useState(null)
const { result, query } = useList({ resource: 'network-exits' })
const error = query.error
const isPending = query.isPending
const exits = result.data ?? []
const { result: bindingResult, query: browsersQuery } = useList({ resource: 'browsers', queryOptions: { retry: false } })
const browsers = bindingResult.data ?? []
const bindingsError = browsersQuery.error
const refetchBrowsers = browsersQuery.refetch
const dataProvider = useDataProvider()("default");
const [createOpen, setCreateOpen] = useState(false);
const [busy, setBusy] = useState("");
const [createError, setCreateError] = useState(null);
const [notice, setNotice] = useState(null);
const [disabling, setDisabling] = useState(null);
const { result, query } = useList({ resource: "network-exits" });
const error = query.error;
const isPending = query.isPending;
const exits = result.data ?? [];
const { result: bindingResult, query: browsersQuery } = useList({
resource: "browsers",
queryOptions: { retry: false },
});
const browsers = bindingResult.data ?? [];
const bindingsError = browsersQuery.error;
const refetchBrowsers = browsersQuery.refetch;
const accountsByExit = useMemo(() => {
const map = new Map
const map = new Map();
for (const runtime of browsers) {
if (!runtime.network_exit_id || !runtime.account_id) continue
map.set(runtime.network_exit_id, [...(map.get(runtime.network_exit_id) || []), runtime.account_id])
if (!runtime.network_exit_id || !runtime.account_id) continue;
map.set(runtime.network_exit_id, [
...(map.get(runtime.network_exit_id) || []),
runtime.account_id,
]);
}
return map
}, [browsers])
useTitle('CreatorHub · 网络出口')
return map;
}, [browsers]);
useTitle("CreatorHub · 网络出口");
async function createExit(data) {
setBusy('create')
setCreateError(null)
setBusy("create");
setCreateError(null);
try {
await dataProvider.create({ resource: 'network-exits', variables: data })
await query.refetch()
setNotice({ variant: 'success', text: '网络出口已创建;检测通过后才可用于账号绑定。' })
return true
await dataProvider.create({ resource: "network-exits", variables: data });
await query.refetch();
setNotice({
variant: "success",
text: "网络出口已创建;检测通过后才可用于账号绑定。",
});
return true;
} catch (reason) {
setCreateError(reason)
return false
setCreateError(reason);
return false;
} finally {
setBusy('')
setBusy("");
}
}
async function runAction(exit, action) {
setBusy(exit.id)
setNotice(null)
setBusy(exit.id);
setNotice(null);
try {
await dataProvider.networkExitAction(exit.id, action)
await Promise.all([query.refetch(), refetchBrowsers()])
setNotice({ variant: 'success', text: action === 'check' ? '出口检测完成。' : '出口已停用;绑定账号不会自动恢复。' })
await dataProvider.networkExitAction(exit.id, action);
await Promise.all([query.refetch(), refetchBrowsers()]);
setNotice({
variant: "success",
text:
action === "check"
? "出口检测完成。"
: "出口已停用;绑定账号不会自动恢复。",
});
} catch (reason) {
setNotice({ variant: 'destructive', text: conflictMessage(reason, action === 'check' ? '已停用的出口不能检测。' : '出口当前不能停用。') })
setNotice({
variant: "destructive",
text: conflictMessage(
reason,
action === "check" ? "已停用的出口不能检测。" : "出口当前不能停用。",
),
});
} finally {
setBusy('')
setBusy("");
}
}
return (
<div>
<PageHeader icon="ri-earth-line" title="网络出口" description="维护固定出口、健康观测与账号绑定">
<Button variant="primary" icon="ri-add-line" onClick={() => setCreateOpen(true)}>创建网络出口</Button>
<PageHeader
icon="ri-earth-line"
title="网络出口"
description="维护固定出口、健康观测与账号绑定"
>
<Button
variant="primary"
icon="ri-add-line"
onClick={() => setCreateOpen(true)}
>
创建网络出口
</Button>
</PageHeader>
{notice ? <Alert variant={notice.variant} className="mb-4">{notice.text}</Alert> : null}
{error ? <Alert variant="destructive" className="mb-4">{error.message}</Alert> : null}
{bindingsError ? (
<Alert variant="destructive" className="mb-4">
环境不可用{bindingsError.status ? `(${bindingsError.status})` : ''}:无法读取账号绑定,状态暂时未知。
<Button size="sm" onClick={() => refetchBrowsers()}>重试环境状态</Button>
{notice ? (
<Alert variant={notice.variant} className="mb-4">
{notice.text}
</Alert>
) : null}
<PageState pending={isPending} error={error} empty={exits.length === 0} emptyText="暂无网络出口。创建并检测健康后,才能绑定运行环境。">
{error ? (
<Alert variant="destructive" className="mb-4">
{error.message}
</Alert>
) : null}
{bindingsError ? (
<Alert variant="destructive" className="mb-4">
环境不可用{bindingsError.status ? `(${bindingsError.status})` : ""}
:无法读取账号绑定,状态暂时未知。
<Button size="sm" onClick={() => refetchBrowsers()}>
重试环境状态
</Button>
</Alert>
) : null}
<PageState
pending={isPending}
error={error}
empty={exits.length === 0}
emptyText="暂无网络出口。创建并检测健康后,才能绑定运行环境。"
>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{exits.map(exit => (
<ExitCard key={exit.id} exit={exit} boundAccounts={accountsByExit.get(exit.id)} bindingsError={!!bindingsError}
busy={busy === exit.id} onAction={(exit, action) => (action === 'disable' ? setDisabling(exit) : runAction(exit, action))} />
{exits.map((exit) => (
<ExitCard
key={exit.id}
exit={exit}
boundAccounts={accountsByExit.get(exit.id)}
bindingsError={!!bindingsError}
busy={busy === exit.id}
onAction={(exit, action) =>
action === "disable"
? setDisabling(exit)
: runAction(exit, action)
}
/>
))}
</div>
</PageState>
<ExitCreateModal open={createOpen} onClose={() => { setCreateOpen(false); setCreateError(null) }} onSubmit={createExit} busy={busy === 'create'} error={createError} />
<ConfirmDialog open={disabling !== null} onClose={() => setDisabling(null)} onConfirm={() => { const exit = disabling; setDisabling(null); runAction(exit, 'disable') }}
title="停用网络出口" confirmLabel="确认停用" busy={busy === disabling?.id}
body={`停用网络出口 ${disabling?.id}?绑定账号将暂停,排队任务进入 hold。`} />
<ExitCreateModal
open={createOpen}
onClose={() => {
setCreateOpen(false);
setCreateError(null);
}}
onSubmit={createExit}
busy={busy === "create"}
error={createError}
/>
<ConfirmDialog
open={disabling !== null}
onClose={() => setDisabling(null)}
onConfirm={() => {
const exit = disabling;
setDisabling(null);
runAction(exit, "disable");
}}
title="停用网络出口"
confirmLabel="确认停用"
busy={busy === disabling?.id}
body={`停用网络出口 ${disabling?.id}?绑定账号将暂停,排队任务进入 hold。`}
/>
</div>
)
);
}
export function NetworkExitDetail() {
const { id } = useParams()
const { result, error, isPending } = useOne({ resource: 'network-exits', id, queryOptions: { retry: false } })
const exit = result
useTitle('CreatorHub · 出口详情')
if (isPending) return <PageState pending />
if (error || !exit) return <Alert variant="destructive">{error?.message || '网络出口不存在'}</Alert>
const { id } = useParams();
const { result, error, isPending } = useOne({
resource: "network-exits",
id,
queryOptions: { retry: false },
});
const exit = result;
useTitle("CreatorHub · 出口详情");
if (isPending) return <PageState pending />;
if (error || !exit)
return (
<Alert variant="destructive">{error?.message || "网络出口不存在"}</Alert>
);
return (
<div>
<PageHeader icon="ri-earth-line" title="网络出口详情" description={<span className="anywhere">{exit.protocol}://{exit.host}:{exit.port}</span>}>
<Button icon="ri-arrow-left-line" as={Link} to="/network-exits">返回出口列表</Button>
<PageHeader
icon="ri-earth-line"
title="网络出口详情"
description={
<span className="anywhere">
{exit.protocol}://{exit.host}:{exit.port}
</span>
}
>
<Button icon="ri-arrow-left-line" as={Link} to="/network-exits">
返回出口列表
</Button>
</PageHeader>
<Card>
<CardContent className="space-y-4">
<ExitHealthPill exit={exit} />
<DetailList rows={[
['ID', <span className="anywhere">{exit.id}</span>],
['健康 / 版本', <span className="anywhere">{healthText[exit.health_status] ?? exit.health_status} · {exit.version}</span>],
['用户名', <span className="anywhere">{exit.username || '无'}</span>],
['密码', <span className="anywhere">{exit.password || '无'}</span>],
['出口IP', <span className="anywhere">{exit.observed_public_ip || '尚无观测'}</span>],
['最近检测', exit.last_checked_at ? new Date(exit.last_checked_at).toLocaleString('zh-CN') : '未检测'],
]} />
<DetailList
rows={[
["ID", <span className="anywhere">{exit.id}</span>],
[
"健康 / 版本",
<span className="anywhere">
{healthText[exit.health_status] ?? exit.health_status} ·{" "}
{exit.version}
</span>,
],
[
"用户名",
<span className="anywhere">{exit.username || "无"}</span>,
],
[
"密码",
<span className="anywhere">{exit.password || "无"}</span>,
],
[
"出口IP",
<span className="anywhere">
{exit.observed_public_ip || "尚无观测"}
</span>,
],
[
"最近检测",
exit.last_checked_at
? new Date(exit.last_checked_at).toLocaleString("zh-CN")
: "未检测",
],
]}
/>
<div className="flex flex-wrap gap-3">
<Button as={Link} to={`/audit?network_exit_id=${encodeURIComponent(exit.id)}`}>查看审计</Button>
<Button
as={Link}
to={`/audit?network_exit_id=${encodeURIComponent(exit.id)}`}
>
查看审计
</Button>
</div>
</CardContent>
</Card>
</div>
)
);
}