Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions webui/src/features/platforms/PlatformDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function PlatformDetailPage() {
const { platformId = "" } = useParams();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState<PlatformDetailTab>("monitor");
const [regexHelpOpen, setRegexHelpOpen] = useState(false);
const { toasts, showToast, dismissToast } = useToast();
const queryClient = useQueryClient();
const formatPlatformMutationError = (error: unknown) => {
Expand Down Expand Up @@ -413,9 +414,12 @@ export function PlatformDetailPage() {
<span>{t("节点名正则过滤规则")}</span>
<span
className="subscription-info-icon"
title={t("满足所有正则表达式的节点才会被选择")}
aria-label={t("满足所有正则表达式的节点才会被选择")}
role="button"
tabIndex={0}
style={{ cursor: "pointer" }}
onClick={() => setRegexHelpOpen(true)}
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") setRegexHelpOpen(true); }}
aria-label={t("正则过滤说明")}
>
<Info size={13} />
</span>
Expand All @@ -426,9 +430,6 @@ export function PlatformDetailPage() {
placeholder={t("每行一条,例如 .*专线.* 或 <订阅名>/.*")}
{...editForm.register("regex_filters_text")}
/>
<p className="muted" style={{ marginTop: 4, fontSize: 12 }}>
{t("技巧:<订阅名>/.* 可筛选来自该订阅的节点。")}
</p>
</div>

<div className="field-group">
Expand Down Expand Up @@ -500,6 +501,39 @@ export function PlatformDetailPage() {
</Card>
</>
) : null}

{regexHelpOpen ? (
<div className="modal-overlay" role="dialog" aria-modal="true" onClick={() => setRegexHelpOpen(false)}>
<Card className="modal-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 520 }}>
<div className="modal-header">
<h3>{t("正则过滤说明")}</h3>
<Button variant="ghost" size="sm" onClick={() => setRegexHelpOpen(false)}>
{t("关闭")}
</Button>
</div>
<div style={{ padding: "0 1.25rem 1.25rem", fontSize: "0.875rem", lineHeight: 1.7 }}>
<p><strong>{t("节点标签格式:<订阅名>/<节点名>")}</strong></p>
<p style={{ color: "var(--danger)" }}>{t("多行正则为 AND 关系(节点须同时满足所有规则)")}</p>
<h4 style={{ marginTop: "1rem", marginBottom: "0.5rem" }}>{t("常见用法")}</h4>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "0.8125rem" }}>
<tbody>
{[
["my_sub/.*", t("匹配整个订阅的所有节点")],
["(sub1|sub2)/.*", t("匹配多个订阅的节点")],
[".*/.*香港.*", t("匹配名称含关键词的节点")],
["^sub/exact-node$", t("精确匹配某个节点")],
].map(([pattern, desc]) => (
<tr key={pattern} style={{ borderBottom: "1px solid var(--border)" }}>
<td style={{ padding: "6px 8px", fontFamily: "monospace", whiteSpace: "nowrap" }}>{pattern}</td>
<td style={{ padding: "6px 8px", color: "var(--text-secondary)" }}>{desc}</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
</div>
) : null}
</section>
);
}
44 changes: 39 additions & 5 deletions webui/src/features/platforms/PlatformPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function PlatformPage() {
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState<number>(24);
const [createModalOpen, setCreateModalOpen] = useState(false);
const [regexHelpOpen, setRegexHelpOpen] = useState(false);
const { toasts, showToast, dismissToast } = useToast();

const queryClient = useQueryClient();
Expand Down Expand Up @@ -324,9 +325,12 @@ export function PlatformPage() {
<span>{t("节点名正则过滤规则(可选)")}</span>
<span
className="subscription-info-icon"
title={t("满足所有正则表达式的节点才会被选择")}
aria-label={t("满足所有正则表达式的节点才会被选择")}
role="button"
tabIndex={0}
style={{ cursor: "pointer" }}
onClick={() => setRegexHelpOpen(true)}
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") setRegexHelpOpen(true); }}
aria-label={t("正则过滤说明")}
>
<Info size={13} />
</span>
Expand All @@ -337,9 +341,6 @@ export function PlatformPage() {
placeholder={t("每行一条,例如 .*专线.* 或 <订阅名>/.*")}
{...createForm.register("regex_filters_text")}
/>
<p className="muted" style={{ marginTop: 4, fontSize: 12 }}>
{t("技巧:<订阅名>/.* 可筛选来自该订阅的节点。")}
</p>
</div>

<div className="field-group">
Expand All @@ -361,6 +362,39 @@ export function PlatformPage() {
</Card>
</div>
) : null}

{regexHelpOpen ? (
<div className="modal-overlay" role="dialog" aria-modal="true" onClick={() => setRegexHelpOpen(false)}>
<Card className="modal-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 520 }}>
<div className="modal-header">
<h3>{t("正则过滤说明")}</h3>
<Button variant="ghost" size="sm" onClick={() => setRegexHelpOpen(false)}>
{t("关闭")}
</Button>
</div>
<div style={{ padding: "0 1.25rem 1.25rem", fontSize: "0.875rem", lineHeight: 1.7 }}>
<p><strong>{t("节点标签格式:<订阅名>/<节点名>")}</strong></p>
<p style={{ color: "var(--danger)" }}>{t("多行正则为 AND 关系(节点须同时满足所有规则)")}</p>
<h4 style={{ marginTop: "1rem", marginBottom: "0.5rem" }}>{t("常见用法")}</h4>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "0.8125rem" }}>
<tbody>
{[
["my_sub/.*", t("匹配整个订阅的所有节点")],
["(sub1|sub2)/.*", t("匹配多个订阅的节点")],
[".*/.*香港.*", t("匹配名称含关键词的节点")],
["^sub/exact-node$", t("精确匹配某个节点")],
].map(([pattern, desc]) => (
<tr key={pattern} style={{ borderBottom: "1px solid var(--border)" }}>
<td style={{ padding: "6px 8px", fontFamily: "monospace", whiteSpace: "nowrap" }}>{pattern}</td>
<td style={{ padding: "6px 8px", color: "var(--text-secondary)" }}>{desc}</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
</div>
) : null}
</section>
);
}
8 changes: 8 additions & 0 deletions webui/src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,14 @@ const EXACT_ZH_TO_EN: Record<string, string> = {
"总请求": "Total requests",
"最近错误:{{message}}": "Recent error: {{message}}",
"配置已更新({{count}} 项变更)": "Config updated ({{count}} changes)",
"正则过滤说明": "Regex Filter Guide",
"节点标签格式:<订阅名>/<节点名>": "Node tag format: <SubscriptionName>/<NodeName>",
"多行正则为 AND 关系(节点须同时满足所有规则)": "Multiple regex lines use AND logic (node must match ALL rules)",
"常见用法": "Common patterns",
"匹配整个订阅的所有节点": "Match all nodes from a subscription",
"匹配多个订阅的节点": "Match nodes from multiple subscriptions",
"匹配名称含关键词的节点": "Match nodes whose name contains a keyword",
"精确匹配某个节点": "Match a specific node exactly",
};

export function translateDocumentTitle(locale: AppLocale): string {
Expand Down