mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-03 17:07:15 +00:00
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.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -181,6 +181,13 @@ export default function RuleCardList({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{rule.comment && (
|
||||
<Tooltip title={rule.comment}>
|
||||
<div className="rule-comment">
|
||||
<span className="rule-comment-text">{rule.comment}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -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<string, unknown> = {
|
||||
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({
|
||||
<Switch disabled={isApiRule(rule ?? {})} />
|
||||
</FormField>
|
||||
|
||||
<FormField name="comment" label={t('comment')}>
|
||||
<Input maxLength={200} showCount placeholder={t('comment')} />
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
name="sourceIP"
|
||||
label={
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface RuleRow {
|
||||
key: number;
|
||||
enabled?: boolean;
|
||||
comment?: string;
|
||||
domain?: string;
|
||||
ip?: string;
|
||||
port?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Dropdown, Switch, Tag } from 'antd';
|
||||
import { Button, Dropdown, Switch, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
MoreOutlined,
|
||||
EditOutlined,
|
||||
@@ -193,6 +193,20 @@ export function useRoutingColumns({
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('comment'),
|
||||
align: 'left',
|
||||
width: 150,
|
||||
key: 'comment',
|
||||
render: (_v, record) =>
|
||||
record.comment ? (
|
||||
<Tooltip title={record.comment}>
|
||||
<span className="rule-comment-cell">{record.comment}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span className="criterion-empty">—</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('pages.inbounds.network'),
|
||||
align: 'left',
|
||||
|
||||
@@ -15,6 +15,7 @@ export type RuleWebhook = z.infer<typeof RuleWebhookSchema>;
|
||||
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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user