From 1bf078c51e2a1089fd76982a46953562ef98cd5a Mon Sep 17 00:00:00 2001 From: Sentiago <76674116+Ssentiago@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:22:50 +0300 Subject: [PATCH] feat(routing): add panel-only comment field to routing rules (#6361) Can annotate rules with a human-readable note for easier management. The comment is stripped before sending the config to xray-core (same pattern as the existing 'enabled' flag). Backend: stripDisabledRules now removes 'comment' from generated config. Frontend: input field in RuleFormModal, column in desktop table, chip with tooltip in mobile card list. Schema and type definitions updated. --- .../src/pages/xray/routing/RoutingTab.css | 30 +++++++++++++++++++ .../src/pages/xray/routing/RoutingTab.tsx | 1 + .../src/pages/xray/routing/RuleCardList.tsx | 7 +++++ .../src/pages/xray/routing/RuleFormModal.tsx | 8 +++++ frontend/src/pages/xray/routing/types.ts | 1 + .../pages/xray/routing/useRoutingColumns.tsx | 16 +++++++++- frontend/src/schemas/routing.ts | 1 + frontend/src/schemas/xray.ts | 1 + internal/web/service/xray.go | 10 +++++-- 9 files changed, 71 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/xray/routing/RoutingTab.css b/frontend/src/pages/xray/routing/RoutingTab.css index d920bafe1..6f09bf680 100644 --- a/frontend/src/pages/xray/routing/RoutingTab.css +++ b/frontend/src/pages/xray/routing/RoutingTab.css @@ -14,6 +14,36 @@ transition: opacity 0.15s; } +.rule-comment-cell { + display: block; + max-width: 140px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; + color: var(--ant-color-text-tertiary); +} + +.rule-comment { + display: flex; + align-items: center; + gap: 4px; + margin-top: 6px; + padding: 2px 6px; + font-size: 12px; + color: var(--ant-color-text-tertiary); + border-radius: 4px; + background: var(--ant-color-fill-tertiary); + max-width: 100%; + overflow: hidden; +} + +.rule-comment-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .drag-handle:hover { opacity: 0.8; } diff --git a/frontend/src/pages/xray/routing/RoutingTab.tsx b/frontend/src/pages/xray/routing/RoutingTab.tsx index b828dc603..845e1639f 100644 --- a/frontend/src/pages/xray/routing/RoutingTab.tsx +++ b/frontend/src/pages/xray/routing/RoutingTab.tsx @@ -89,6 +89,7 @@ export default function RoutingTab({ if (rule.attrs && typeof rule.attrs === 'object' && !Array.isArray(rule.attrs)) { r.attrs = JSON.stringify(rule.attrs, null, 2); } + r.comment = rule.comment || undefined; r.outboundTag = rule.outboundTag; r.balancerTag = rule.balancerTag; return r; diff --git a/frontend/src/pages/xray/routing/RuleCardList.tsx b/frontend/src/pages/xray/routing/RuleCardList.tsx index 3b2f40107..b8e9b8a7a 100644 --- a/frontend/src/pages/xray/routing/RuleCardList.tsx +++ b/frontend/src/pages/xray/routing/RuleCardList.tsx @@ -181,6 +181,13 @@ export default function RuleCardList({ ))} )} + {rule.comment && ( + +
+ {rule.comment} +
+
+ )} )) )} diff --git a/frontend/src/pages/xray/routing/RuleFormModal.tsx b/frontend/src/pages/xray/routing/RuleFormModal.tsx index 08c1189f3..cf46d047c 100644 --- a/frontend/src/pages/xray/routing/RuleFormModal.tsx +++ b/frontend/src/pages/xray/routing/RuleFormModal.tsx @@ -13,6 +13,7 @@ import { buildRemarkByTag, formatInboundTag, isApiRule } from './helpers'; export interface RoutingRule { enabled?: boolean; + comment?: string; type?: string; domain?: string | string[]; ip?: string | string[]; @@ -42,6 +43,7 @@ interface RuleFormModalProps { const initialForm = (): RuleFormValues => ({ enabled: true, + comment: '', domain: '', ip: '', port: '', @@ -104,6 +106,7 @@ export default function RuleFormModal({ if (rule) { methods.reset({ enabled: rule.enabled !== false, + comment: rule.comment || '', domain: Array.isArray(rule.domain) ? rule.domain.join(',') : rule.domain || '', ip: Array.isArray(rule.ip) ? rule.ip.join(',') : rule.ip || '', port: rule.port || '', @@ -132,6 +135,7 @@ export default function RuleFormModal({ const built: Record = { type: 'field', enabled: v.enabled, + comment: v.comment, domain: csv(v.domain), ip: csv(v.ip), port: v.port, @@ -185,6 +189,10 @@ export default function RuleFormModal({ + + + + ), }, + { + title: t('comment'), + align: 'left', + width: 150, + key: 'comment', + render: (_v, record) => + record.comment ? ( + + {record.comment} + + ) : ( + + ), + }, { title: t('pages.inbounds.network'), align: 'left', diff --git a/frontend/src/schemas/routing.ts b/frontend/src/schemas/routing.ts index f5a2d7496..c14047240 100644 --- a/frontend/src/schemas/routing.ts +++ b/frontend/src/schemas/routing.ts @@ -15,6 +15,7 @@ export type RuleWebhook = z.infer; export const RuleObjectSchema = z.object({ type: z.literal('field').default('field'), enabled: z.boolean().optional(), + comment: z.string().optional(), domain: z.array(z.string()).optional(), ip: z.array(z.string()).optional(), port: PortValueSchema.optional(), diff --git a/frontend/src/schemas/xray.ts b/frontend/src/schemas/xray.ts index 984edd3c2..7b7e1c60c 100644 --- a/frontend/src/schemas/xray.ts +++ b/frontend/src/schemas/xray.ts @@ -110,6 +110,7 @@ export const OutboundTestResultListSchema = z.array(OutboundTestResultSchema); export const RuleFormSchema = z.object({ enabled: z.boolean(), + comment: z.string(), domain: z.string(), ip: z.string(), port: z.string(), diff --git a/internal/web/service/xray.go b/internal/web/service/xray.go index 6e11543a0..ecb1d9060 100644 --- a/internal/web/service/xray.go +++ b/internal/web/service/xray.go @@ -1060,9 +1060,9 @@ func resolveXrayLogPaths(logCfg json_util.RawMessage) json_util.RawMessage { } // stripDisabledRules removes routing rules marked `enabled: false` from the -// generated runtime config and strips the panel-only `enabled` key from the -// rest, since xray-core has no such field. The internal api rule is always -// kept (see isApiRule) so traffic stats can't be toggled off. The stored +// generated runtime config and strips panel-only keys (`enabled`, `comment`) +// from the rest, since xray-core has no such fields. The internal api rule is +// always kept (see isApiRule) so traffic stats can't be toggled off. The stored // template is untouched — only the generated config is filtered. func stripDisabledRules(routerCfg json_util.RawMessage) json_util.RawMessage { if len(routerCfg) == 0 { @@ -1097,6 +1097,10 @@ func stripDisabledRules(routerCfg json_util.RawMessage) json_util.RawMessage { delete(rule, "enabled") changed = true } + if _, exists := rule["comment"]; exists { + delete(rule, "comment") + changed = true + } activeRules = append(activeRules, rule) }