feat(sub): auto-detect subscription format by User-Agent (Updated) (#5826)

* feat(settings): add subscription format controls

* feat(sub): auto-detect subscription formats

* fix(xray): validate balancer regexes before save

* Revert "fix(xray): validate balancer regexes before save"

This reverts commit 8a208ce71b.

* doc(endpoints): align indent spaces

* doc(settings): improve error message formatting in validateSubUserAgentRegex

- Use NewErrorf with proper formatting instead of NewError with string concatenation
- Add comment explaining the rationale for returning original pattern value
- This preserves the intentional design where empty input is stored as empty
  in the DB and inherited as the runtime default at read time

---------

Co-authored-by: Tomilla <5007859+Tomilla@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
Tomi lla
2026-07-14 19:01:40 +08:00
committed by GitHub
parent f2b17397f4
commit 129f50d92a
40 changed files with 1588 additions and 89 deletions
@@ -0,0 +1,63 @@
import { useRef, useState } from 'react';
import { Input, Typography } from 'antd';
import { HttpUtil } from '@/utils';
interface GoRegexInputProps {
value: string;
ariaLabel?: string;
placeholder?: string;
maxLength?: number;
onChange: (value: string) => void;
externalError?: string;
}
export async function validateGoRegex(value: string): Promise<string> {
const result = await HttpUtil.post(
'/panel/api/setting/validateRegex',
{ regex: value },
{ silent: true },
);
return result.success ? '' : result.msg || 'Invalid Go RE2 regular expression';
}
export default function GoRegexInput({
value,
ariaLabel,
placeholder,
maxLength = 2048,
onChange,
externalError,
}: GoRegexInputProps) {
const [error, setError] = useState('');
const validationSequence = useRef(0);
async function validate() {
const sequence = ++validationSequence.current;
const nextError = await validateGoRegex(value);
if (sequence === validationSequence.current) {
setError(nextError);
}
}
const displayError = externalError ?? error;
return (
<div style={{ width: '100%' }}>
<Input
value={value}
aria-label={ariaLabel}
placeholder={placeholder}
maxLength={maxLength}
status={displayError ? 'error' : undefined}
onChange={(event) => {
validationSequence.current += 1;
setError('');
onChange(event.target.value);
}}
onBlur={() => void validate()}
/>
{displayError && <Typography.Text type="danger">{displayError}</Typography.Text>}
</div>
);
}
+1
View File
@@ -1,6 +1,7 @@
export { default as DateTimePicker } from './DateTimePicker';
export { default as JsonEditor } from './JsonEditor';
export { default as HeaderMapEditor } from './HeaderMapEditor';
export { default as GoRegexInput, validateGoRegex } from './GoRegexInput';
export { default as SelectAllClearButtons } from './SelectAllClearButtons';
export { default as RemarkTemplateField } from './RemarkTemplateField';
export { default as RemarkVarPicker } from './RemarkVarPicker';