mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-15 00:56:08 +00:00
feat(settings): add subscription format controls
This commit is contained in:
@@ -195,6 +195,10 @@
|
||||
"description": "SSL certificate file for subscription server",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashAutoDetect": {
|
||||
"description": "Opt in to serving Clash YAML from the standard subscription URL for recognized Clash/Mihomo/Stash User-Agents; browsers and other clients keep existing responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subClashEnable": {
|
||||
"description": "Enable Clash/Mihomo subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -215,6 +219,10 @@
|
||||
"description": "Clash/Mihomo subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Clash/Mihomo clients on the standard subscription URL",
|
||||
"type": "string"
|
||||
},
|
||||
"subDomain": {
|
||||
"description": "Domain for subscription server validation",
|
||||
"type": "string"
|
||||
@@ -243,6 +251,14 @@
|
||||
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonAlwaysArray": {
|
||||
"description": "Always return JSON subscriptions as an array, including single-profile responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonAutoDetect": {
|
||||
"description": "Opt in to serving Xray JSON from the standard subscription URL for recognized compatible User-Agents",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonEnable": {
|
||||
"description": "Enable JSON subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -266,6 +282,10 @@
|
||||
"description": "JSON subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Xray JSON subscription clients",
|
||||
"type": "string"
|
||||
},
|
||||
"subKeyFile": {
|
||||
"description": "SSL private key file for subscription server",
|
||||
"type": "string"
|
||||
@@ -459,11 +479,13 @@
|
||||
"smtpUsername",
|
||||
"subAnnounce",
|
||||
"subCertFile",
|
||||
"subClashAutoDetect",
|
||||
"subClashEnable",
|
||||
"subClashEnableRouting",
|
||||
"subClashPath",
|
||||
"subClashRules",
|
||||
"subClashURI",
|
||||
"subClashUserAgentRegex",
|
||||
"subDomain",
|
||||
"subEnable",
|
||||
"subEnableRouting",
|
||||
@@ -471,12 +493,15 @@
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
"subJsonAlwaysArray",
|
||||
"subJsonAutoDetect",
|
||||
"subJsonEnable",
|
||||
"subJsonFinalMask",
|
||||
"subJsonMux",
|
||||
"subJsonPath",
|
||||
"subJsonRules",
|
||||
"subJsonURI",
|
||||
"subJsonUserAgentRegex",
|
||||
"subKeyFile",
|
||||
"subListen",
|
||||
"subPath",
|
||||
@@ -704,6 +729,10 @@
|
||||
"description": "SSL certificate file for subscription server",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashAutoDetect": {
|
||||
"description": "Opt in to serving Clash YAML from the standard subscription URL for recognized Clash/Mihomo/Stash User-Agents; browsers and other clients keep existing responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subClashEnable": {
|
||||
"description": "Enable Clash/Mihomo subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -724,6 +753,10 @@
|
||||
"description": "Clash/Mihomo subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Clash/Mihomo clients on the standard subscription URL",
|
||||
"type": "string"
|
||||
},
|
||||
"subDomain": {
|
||||
"description": "Domain for subscription server validation",
|
||||
"type": "string"
|
||||
@@ -752,6 +785,14 @@
|
||||
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonAlwaysArray": {
|
||||
"description": "Always return JSON subscriptions as an array, including single-profile responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonAutoDetect": {
|
||||
"description": "Opt in to serving Xray JSON from the standard subscription URL for recognized compatible User-Agents",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonEnable": {
|
||||
"description": "Enable JSON subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -775,6 +816,10 @@
|
||||
"description": "JSON subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Xray JSON subscription clients",
|
||||
"type": "string"
|
||||
},
|
||||
"subKeyFile": {
|
||||
"description": "SSL private key file for subscription server",
|
||||
"type": "string"
|
||||
@@ -975,11 +1020,13 @@
|
||||
"smtpUsername",
|
||||
"subAnnounce",
|
||||
"subCertFile",
|
||||
"subClashAutoDetect",
|
||||
"subClashEnable",
|
||||
"subClashEnableRouting",
|
||||
"subClashPath",
|
||||
"subClashRules",
|
||||
"subClashURI",
|
||||
"subClashUserAgentRegex",
|
||||
"subDomain",
|
||||
"subEnable",
|
||||
"subEnableRouting",
|
||||
@@ -987,12 +1034,15 @@
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
"subJsonAlwaysArray",
|
||||
"subJsonAutoDetect",
|
||||
"subJsonEnable",
|
||||
"subJsonFinalMask",
|
||||
"subJsonMux",
|
||||
"subJsonPath",
|
||||
"subJsonRules",
|
||||
"subJsonURI",
|
||||
"subJsonUserAgentRegex",
|
||||
"subKeyFile",
|
||||
"subListen",
|
||||
"subPath",
|
||||
@@ -9044,6 +9094,53 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/api/setting/validateRegex": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Settings"
|
||||
],
|
||||
"summary": "Validate any regular expression with the backend Go RE2 compiler without saving it.",
|
||||
"operationId": "post_panel_api_setting_validateRegex",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
},
|
||||
"example": {
|
||||
"regex": "(?m)^general-purpose$"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"obj": {}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"success": true,
|
||||
"msg": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panel/api/setting/updateUser": {
|
||||
"post": {
|
||||
"tags": [
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -43,11 +43,13 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"smtpUsername": "",
|
||||
"subAnnounce": "",
|
||||
"subCertFile": "",
|
||||
"subClashAutoDetect": false,
|
||||
"subClashEnable": false,
|
||||
"subClashEnableRouting": false,
|
||||
"subClashPath": "",
|
||||
"subClashRules": "",
|
||||
"subClashURI": "",
|
||||
"subClashUserAgentRegex": "",
|
||||
"subDomain": "",
|
||||
"subEnable": false,
|
||||
"subEnableRouting": false,
|
||||
@@ -55,12 +57,15 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subHideSettings": false,
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyRoutingRules": "",
|
||||
"subJsonAlwaysArray": false,
|
||||
"subJsonAutoDetect": false,
|
||||
"subJsonEnable": false,
|
||||
"subJsonFinalMask": "",
|
||||
"subJsonMux": "",
|
||||
"subJsonPath": "",
|
||||
"subJsonRules": "",
|
||||
"subJsonURI": "",
|
||||
"subJsonUserAgentRegex": "",
|
||||
"subKeyFile": "",
|
||||
"subListen": "",
|
||||
"subPath": "",
|
||||
@@ -146,11 +151,13 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"smtpUsername": "",
|
||||
"subAnnounce": "",
|
||||
"subCertFile": "",
|
||||
"subClashAutoDetect": false,
|
||||
"subClashEnable": false,
|
||||
"subClashEnableRouting": false,
|
||||
"subClashPath": "",
|
||||
"subClashRules": "",
|
||||
"subClashURI": "",
|
||||
"subClashUserAgentRegex": "",
|
||||
"subDomain": "",
|
||||
"subEnable": false,
|
||||
"subEnableRouting": false,
|
||||
@@ -158,12 +165,15 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subHideSettings": false,
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyRoutingRules": "",
|
||||
"subJsonAlwaysArray": false,
|
||||
"subJsonAutoDetect": false,
|
||||
"subJsonEnable": false,
|
||||
"subJsonFinalMask": "",
|
||||
"subJsonMux": "",
|
||||
"subJsonPath": "",
|
||||
"subJsonRules": "",
|
||||
"subJsonURI": "",
|
||||
"subJsonUserAgentRegex": "",
|
||||
"subKeyFile": "",
|
||||
"subListen": "",
|
||||
"subPath": "",
|
||||
|
||||
@@ -169,6 +169,10 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "SSL certificate file for subscription server",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashAutoDetect": {
|
||||
"description": "Opt in to serving Clash YAML from the standard subscription URL for recognized Clash/Mihomo/Stash User-Agents; browsers and other clients keep existing responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subClashEnable": {
|
||||
"description": "Enable Clash/Mihomo subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -189,6 +193,10 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "Clash/Mihomo subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Clash/Mihomo clients on the standard subscription URL",
|
||||
"type": "string"
|
||||
},
|
||||
"subDomain": {
|
||||
"description": "Domain for subscription server validation",
|
||||
"type": "string"
|
||||
@@ -217,6 +225,14 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonAlwaysArray": {
|
||||
"description": "Always return JSON subscriptions as an array, including single-profile responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonAutoDetect": {
|
||||
"description": "Opt in to serving Xray JSON from the standard subscription URL for recognized compatible User-Agents",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonEnable": {
|
||||
"description": "Enable JSON subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -240,6 +256,10 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "JSON subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Xray JSON subscription clients",
|
||||
"type": "string"
|
||||
},
|
||||
"subKeyFile": {
|
||||
"description": "SSL private key file for subscription server",
|
||||
"type": "string"
|
||||
@@ -433,11 +453,13 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"smtpUsername",
|
||||
"subAnnounce",
|
||||
"subCertFile",
|
||||
"subClashAutoDetect",
|
||||
"subClashEnable",
|
||||
"subClashEnableRouting",
|
||||
"subClashPath",
|
||||
"subClashRules",
|
||||
"subClashURI",
|
||||
"subClashUserAgentRegex",
|
||||
"subDomain",
|
||||
"subEnable",
|
||||
"subEnableRouting",
|
||||
@@ -445,12 +467,15 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
"subJsonAlwaysArray",
|
||||
"subJsonAutoDetect",
|
||||
"subJsonEnable",
|
||||
"subJsonFinalMask",
|
||||
"subJsonMux",
|
||||
"subJsonPath",
|
||||
"subJsonRules",
|
||||
"subJsonURI",
|
||||
"subJsonUserAgentRegex",
|
||||
"subKeyFile",
|
||||
"subListen",
|
||||
"subPath",
|
||||
@@ -678,6 +703,10 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "SSL certificate file for subscription server",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashAutoDetect": {
|
||||
"description": "Opt in to serving Clash YAML from the standard subscription URL for recognized Clash/Mihomo/Stash User-Agents; browsers and other clients keep existing responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subClashEnable": {
|
||||
"description": "Enable Clash/Mihomo subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -698,6 +727,10 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "Clash/Mihomo subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subClashUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Clash/Mihomo clients on the standard subscription URL",
|
||||
"type": "string"
|
||||
},
|
||||
"subDomain": {
|
||||
"description": "Domain for subscription server validation",
|
||||
"type": "string"
|
||||
@@ -726,6 +759,14 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonAlwaysArray": {
|
||||
"description": "Always return JSON subscriptions as an array, including single-profile responses",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonAutoDetect": {
|
||||
"description": "Opt in to serving Xray JSON from the standard subscription URL for recognized compatible User-Agents",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subJsonEnable": {
|
||||
"description": "Enable JSON subscription endpoint",
|
||||
"type": "boolean"
|
||||
@@ -749,6 +790,10 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"description": "JSON subscription server URI",
|
||||
"type": "string"
|
||||
},
|
||||
"subJsonUserAgentRegex": {
|
||||
"description": "Go RE2 regular expression used to recognize Xray JSON subscription clients",
|
||||
"type": "string"
|
||||
},
|
||||
"subKeyFile": {
|
||||
"description": "SSL private key file for subscription server",
|
||||
"type": "string"
|
||||
@@ -949,11 +994,13 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"smtpUsername",
|
||||
"subAnnounce",
|
||||
"subCertFile",
|
||||
"subClashAutoDetect",
|
||||
"subClashEnable",
|
||||
"subClashEnableRouting",
|
||||
"subClashPath",
|
||||
"subClashRules",
|
||||
"subClashURI",
|
||||
"subClashUserAgentRegex",
|
||||
"subDomain",
|
||||
"subEnable",
|
||||
"subEnableRouting",
|
||||
@@ -961,12 +1008,15 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
"subJsonAlwaysArray",
|
||||
"subJsonAutoDetect",
|
||||
"subJsonEnable",
|
||||
"subJsonFinalMask",
|
||||
"subJsonMux",
|
||||
"subJsonPath",
|
||||
"subJsonRules",
|
||||
"subJsonURI",
|
||||
"subJsonUserAgentRegex",
|
||||
"subKeyFile",
|
||||
"subListen",
|
||||
"subPath",
|
||||
|
||||
@@ -49,11 +49,13 @@ export interface AllSetting {
|
||||
smtpUsername: string;
|
||||
subAnnounce: string;
|
||||
subCertFile: string;
|
||||
subClashAutoDetect: boolean;
|
||||
subClashEnable: boolean;
|
||||
subClashEnableRouting: boolean;
|
||||
subClashPath: string;
|
||||
subClashRules: string;
|
||||
subClashURI: string;
|
||||
subClashUserAgentRegex: string;
|
||||
subDomain: string;
|
||||
subEnable: boolean;
|
||||
subEnableRouting: boolean;
|
||||
@@ -61,12 +63,15 @@ export interface AllSetting {
|
||||
subHideSettings: boolean;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyRoutingRules: string;
|
||||
subJsonAlwaysArray: boolean;
|
||||
subJsonAutoDetect: boolean;
|
||||
subJsonEnable: boolean;
|
||||
subJsonFinalMask: string;
|
||||
subJsonMux: string;
|
||||
subJsonPath: string;
|
||||
subJsonRules: string;
|
||||
subJsonURI: string;
|
||||
subJsonUserAgentRegex: string;
|
||||
subKeyFile: string;
|
||||
subListen: string;
|
||||
subPath: string;
|
||||
@@ -153,11 +158,13 @@ export interface AllSettingView {
|
||||
smtpUsername: string;
|
||||
subAnnounce: string;
|
||||
subCertFile: string;
|
||||
subClashAutoDetect: boolean;
|
||||
subClashEnable: boolean;
|
||||
subClashEnableRouting: boolean;
|
||||
subClashPath: string;
|
||||
subClashRules: string;
|
||||
subClashURI: string;
|
||||
subClashUserAgentRegex: string;
|
||||
subDomain: string;
|
||||
subEnable: boolean;
|
||||
subEnableRouting: boolean;
|
||||
@@ -165,12 +172,15 @@ export interface AllSettingView {
|
||||
subHideSettings: boolean;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyRoutingRules: string;
|
||||
subJsonAlwaysArray: boolean;
|
||||
subJsonAutoDetect: boolean;
|
||||
subJsonEnable: boolean;
|
||||
subJsonFinalMask: string;
|
||||
subJsonMux: string;
|
||||
subJsonPath: string;
|
||||
subJsonRules: string;
|
||||
subJsonURI: string;
|
||||
subJsonUserAgentRegex: string;
|
||||
subKeyFile: string;
|
||||
subListen: string;
|
||||
subPath: string;
|
||||
|
||||
@@ -61,11 +61,13 @@ export const AllSettingSchema = z.object({
|
||||
smtpUsername: z.string(),
|
||||
subAnnounce: z.string(),
|
||||
subCertFile: z.string(),
|
||||
subClashAutoDetect: z.boolean(),
|
||||
subClashEnable: z.boolean(),
|
||||
subClashEnableRouting: z.boolean(),
|
||||
subClashPath: z.string(),
|
||||
subClashRules: z.string(),
|
||||
subClashURI: z.string(),
|
||||
subClashUserAgentRegex: z.string(),
|
||||
subDomain: z.string(),
|
||||
subEnable: z.boolean(),
|
||||
subEnableRouting: z.boolean(),
|
||||
@@ -73,12 +75,15 @@ export const AllSettingSchema = z.object({
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
subJsonAlwaysArray: z.boolean(),
|
||||
subJsonAutoDetect: z.boolean(),
|
||||
subJsonEnable: z.boolean(),
|
||||
subJsonFinalMask: z.string(),
|
||||
subJsonMux: z.string(),
|
||||
subJsonPath: z.string(),
|
||||
subJsonRules: z.string(),
|
||||
subJsonURI: z.string(),
|
||||
subJsonUserAgentRegex: z.string(),
|
||||
subKeyFile: z.string(),
|
||||
subListen: z.string(),
|
||||
subPath: z.string(),
|
||||
@@ -166,11 +171,13 @@ export const AllSettingViewSchema = z.object({
|
||||
smtpUsername: z.string(),
|
||||
subAnnounce: z.string(),
|
||||
subCertFile: z.string(),
|
||||
subClashAutoDetect: z.boolean(),
|
||||
subClashEnable: z.boolean(),
|
||||
subClashEnableRouting: z.boolean(),
|
||||
subClashPath: z.string(),
|
||||
subClashRules: z.string(),
|
||||
subClashURI: z.string(),
|
||||
subClashUserAgentRegex: z.string(),
|
||||
subDomain: z.string(),
|
||||
subEnable: z.boolean(),
|
||||
subEnableRouting: z.boolean(),
|
||||
@@ -178,12 +185,15 @@ export const AllSettingViewSchema = z.object({
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
subJsonAlwaysArray: z.boolean(),
|
||||
subJsonAutoDetect: z.boolean(),
|
||||
subJsonEnable: z.boolean(),
|
||||
subJsonFinalMask: z.string(),
|
||||
subJsonMux: z.string(),
|
||||
subJsonPath: z.string(),
|
||||
subJsonRules: z.string(),
|
||||
subJsonURI: z.string(),
|
||||
subJsonUserAgentRegex: z.string(),
|
||||
subKeyFile: z.string(),
|
||||
subListen: z.string(),
|
||||
subPath: z.string(),
|
||||
|
||||
@@ -29,6 +29,11 @@ export class AllSetting {
|
||||
xrayTemplateConfig = '';
|
||||
subEnable = true;
|
||||
subJsonEnable = false;
|
||||
subJsonAutoDetect = false;
|
||||
subJsonAlwaysArray = false;
|
||||
subJsonUserAgentRegex = '';
|
||||
subClashAutoDetect = false;
|
||||
subClashUserAgentRegex = '';
|
||||
subTitle = '';
|
||||
subSupportUrl = '';
|
||||
subProfileUrl = '';
|
||||
|
||||
@@ -1135,7 +1135,7 @@ export const sections: readonly Section[] = [
|
||||
method: 'POST',
|
||||
path: '/panel/api/setting/all',
|
||||
summary: 'Return every panel setting: web server, Telegram bot, subscription, security, LDAP. The full JSON blob that the Settings page edits.',
|
||||
response: '{\n "success": true,\n "obj": {\n "webPort": 2053,\n "webCertFile": "",\n "webKeyFile": "",\n "webBasePath": "/",\n "subPort": 10882,\n "subPath": "/sub/",\n "tgBotEnable": false,\n "tgBotToken": "",\n ...\n }\n}',
|
||||
response: '{\n "success": true,\n "obj": {\n "webPort": 2053,\n "webCertFile": "",\n "webKeyFile": "",\n "webBasePath": "/",\n "subPort": 10882,\n "subPath": "/sub/",\n "subClashAutoDetect": false,\n "subClashUserAgentRegex": "",\n "subJsonEnable": false,\n "subJsonAutoDetect": false,\n "subJsonAlwaysArray": false,\n "subJsonUserAgentRegex": "",\n "subJsonPath": "/json/",\n "subJsonURI": "https://sub.example.com/json/",\n "subClashEnable": true,\n "subClashPath": "/clash/",\n "subClashURI": "https://sub.example.com/clash/",\n "tgBotEnable": false,\n "tgBotToken": "",\n ...\n }\n}',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -1146,7 +1146,14 @@ export const sections: readonly Section[] = [
|
||||
method: 'POST',
|
||||
path: '/panel/api/setting/update',
|
||||
summary: 'Persist every setting at once. The body mirrors the shape returned by /all. Invalid values (bad ports, missing cert pairs, etc.) are rejected before write.',
|
||||
body: '{\n "webPort": 2053,\n "webBasePath": "/",\n "subPort": 10882,\n "subPath": "/sub/",\n "tgBotEnable": false,\n ...\n}',
|
||||
body: '{\n "webPort": 2053,\n "webBasePath": "/",\n "subPort": 10882,\n "subPath": "/sub/",\n "subClashAutoDetect": false,\n "subClashUserAgentRegex": "",\n "subJsonEnable": false,\n "subJsonAutoDetect": false,\n "subJsonAlwaysArray": false,\n "subJsonUserAgentRegex": "",\n "subJsonPath": "/json/",\n "subJsonURI": "https://sub.example.com/json/",\n "subClashEnable": true,\n "subClashPath": "/clash/",\n "subClashURI": "https://sub.example.com/clash/",\n "tgBotEnable": false,\n ...\n}',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/panel/api/setting/validateRegex',
|
||||
summary: 'Validate any regular expression with the backend Go RE2 compiler without saving it.',
|
||||
body: '{\n "regex": "(?m)^general-purpose$"\n}',
|
||||
response: '{\n "success": true,\n "msg": ""\n}',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
.subscription-format-sections {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.subscription-format-card-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.subscription-format-card .ant-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.format-settings {
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid var(--ant-color-border-secondary);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Card,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
@@ -8,6 +9,8 @@ import {
|
||||
Tabs,
|
||||
} from 'antd';
|
||||
import {
|
||||
FileTextOutlined,
|
||||
NodeIndexOutlined,
|
||||
PartitionOutlined,
|
||||
RocketOutlined,
|
||||
SendOutlined,
|
||||
@@ -15,6 +18,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
import { SettingListItem } from '@/components/ui';
|
||||
import { GoRegexInput } from '@/components/form';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { catTabLabel } from './catTabLabel';
|
||||
import { sanitizePath, normalizePath } from './uriPath';
|
||||
@@ -148,9 +152,18 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
||||
key: '1',
|
||||
label: catTabLabel(<SettingOutlined />, t('pages.settings.panelSettings'), isMobile),
|
||||
children: (
|
||||
<>
|
||||
<div className="subscription-format-sections">
|
||||
{allSetting.subJsonEnable && (
|
||||
<>
|
||||
<Card
|
||||
size="small"
|
||||
className="subscription-format-card"
|
||||
title={(
|
||||
<span className="subscription-format-card-title">
|
||||
<FileTextOutlined />
|
||||
{t('pages.settings.subJsonEnableTitle')}
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
<SettingListItem paddings="small" title={<>JSON {t('pages.settings.subPath')}</>} description={t('pages.settings.subPathDesc')}>
|
||||
<Input
|
||||
value={allSetting.subJsonPath}
|
||||
@@ -166,10 +179,40 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
||||
onChange={(e) => updateSetting({ subJsonURI: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subJsonAlwaysArray')}
|
||||
description={t('pages.settings.subJsonAlwaysArrayDesc')}
|
||||
>
|
||||
<Switch checked={allSetting.subJsonAlwaysArray} onChange={(value) => updateSetting({ subJsonAlwaysArray: value })} />
|
||||
</SettingListItem>
|
||||
<SettingListItem paddings="small" title={t('pages.settings.subJsonAutoDetect')} description={t('pages.settings.subJsonAutoDetectDesc')}>
|
||||
<Switch checked={allSetting.subJsonAutoDetect} onChange={(v) => updateSetting({ subJsonAutoDetect: v })} />
|
||||
</SettingListItem>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subJsonUserAgentRegex')}
|
||||
description={t('pages.settings.subJsonUserAgentRegexDesc')}
|
||||
>
|
||||
<GoRegexInput
|
||||
value={allSetting.subJsonUserAgentRegex}
|
||||
placeholder="(?i)^streisand([ /]|$)"
|
||||
onChange={(value) => updateSetting({ subJsonUserAgentRegex: value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</Card>
|
||||
)}
|
||||
{allSetting.subClashEnable && (
|
||||
<>
|
||||
<Card
|
||||
size="small"
|
||||
className="subscription-format-card"
|
||||
title={(
|
||||
<span className="subscription-format-card-title">
|
||||
<NodeIndexOutlined />
|
||||
{t('pages.settings.subClashEnableTitle')}
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
<SettingListItem paddings="small" title={<>Clash {t('pages.settings.subPath')}</>} description={t('pages.settings.subPathDesc')}>
|
||||
<Input
|
||||
value={allSetting.subClashPath}
|
||||
@@ -185,9 +228,30 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
||||
onChange={(e) => updateSetting({ subClashURI: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subClashAutoDetect')}
|
||||
description={t('pages.settings.subClashAutoDetectDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subClashAutoDetect}
|
||||
onChange={(v) => updateSetting({ subClashAutoDetect: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subClashUserAgentRegex')}
|
||||
description={t('pages.settings.subClashUserAgentRegexDesc')}
|
||||
>
|
||||
<GoRegexInput
|
||||
value={allSetting.subClashUserAgentRegex}
|
||||
placeholder="(?i)(clash|mihomo|stash)"
|
||||
onChange={(value) => updateSetting({ subClashUserAgentRegex: value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Input, InputNumber, Switch, Tabs } from 'antd';
|
||||
import { Alert, Button, Input, InputNumber, Switch, Tabs } from 'antd';
|
||||
import { BranchesOutlined, CompassOutlined, IdcardOutlined, InfoCircleOutlined, NodeIndexOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
import { SettingListItem } from '@/components/ui';
|
||||
import { RemarkTemplateField } from '@/components/form';
|
||||
@@ -15,6 +16,7 @@ interface SubscriptionGeneralTabProps {
|
||||
|
||||
export default function SubscriptionGeneralTab({ allSetting, updateSetting }: SubscriptionGeneralTabProps) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { isMobile } = useMediaQuery();
|
||||
|
||||
return (
|
||||
@@ -33,6 +35,20 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
|
||||
<SettingListItem paddings="small" title={t('pages.settings.subClashEnableTitle')}>
|
||||
<Switch checked={allSetting.subClashEnable} onChange={(v) => updateSetting({ subClashEnable: v })} />
|
||||
</SettingListItem>
|
||||
{(allSetting.subJsonEnable || allSetting.subClashEnable) && (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ margin: '12px 20px' }}
|
||||
title={t('pages.settings.subFormatsTipTitle')}
|
||||
description={t('pages.settings.subFormatsTipDesc')}
|
||||
action={(
|
||||
<Button size="small" onClick={() => navigate('/settings#subscription-formats')}>
|
||||
{t('pages.settings.subFormatsTipAction')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<SettingListItem paddings="small" title={t('pages.settings.subListen')} description={t('pages.settings.subListenDesc')}>
|
||||
<Input value={allSetting.subListen} onChange={(e) => updateSetting({ subListen: e.target.value })} />
|
||||
</SettingListItem>
|
||||
|
||||
@@ -33,6 +33,11 @@ export const AllSettingSchema = z.object({
|
||||
xrayTemplateConfig: z.string().optional(),
|
||||
subEnable: z.boolean().optional(),
|
||||
subJsonEnable: z.boolean().optional(),
|
||||
subJsonAutoDetect: z.boolean().optional(),
|
||||
subJsonAlwaysArray: z.boolean().optional(),
|
||||
subJsonUserAgentRegex: z.string().max(2048).optional(),
|
||||
subClashAutoDetect: z.boolean().optional(),
|
||||
subClashUserAgentRegex: z.string().max(2048).optional(),
|
||||
subTitle: z.string().optional(),
|
||||
subSupportUrl: z.string().optional(),
|
||||
subProfileUrl: z.string().optional(),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { MemoryRouter, useLocation } from 'react-router-dom';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AllSetting } from '@/models/setting';
|
||||
import SubscriptionGeneralTab from '@/pages/settings/SubscriptionGeneralTab';
|
||||
import { renderWithProviders } from './test-utils';
|
||||
|
||||
function LocationProbe() {
|
||||
const location = useLocation();
|
||||
return <output data-testid="location">{location.pathname}{location.hash}</output>;
|
||||
}
|
||||
|
||||
describe('SubscriptionGeneralTab', () => {
|
||||
it('uses router navigation to open subscription format settings', () => {
|
||||
const allSetting = new AllSetting({ subClashEnable: true });
|
||||
|
||||
renderWithProviders(
|
||||
<MemoryRouter initialEntries={['/settings#subscription']}>
|
||||
<SubscriptionGeneralTab allSetting={allSetting} updateSetting={vi.fn()} />
|
||||
<LocationProbe />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Open Sub Formats' }));
|
||||
|
||||
expect(screen.getByTestId('location').textContent).toBe('/settings#subscription-formats');
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -38,6 +39,10 @@ type updateSettingForm struct {
|
||||
ClearSmtpPassword bool `json:"clearSmtpPassword" form:"clearSmtpPassword"`
|
||||
}
|
||||
|
||||
type validateRegexForm struct {
|
||||
Regex string `json:"regex" form:"regex"`
|
||||
}
|
||||
|
||||
// SettingController handles settings and user management operations.
|
||||
type SettingController struct {
|
||||
settingService service.SettingService
|
||||
@@ -61,6 +66,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/all", a.getAllSetting)
|
||||
g.POST("/defaultSettings", a.getDefaultSettings)
|
||||
g.POST("/update", a.updateSetting)
|
||||
g.POST("/validateRegex", a.validateRegex)
|
||||
g.POST("/updateUser", a.updateUser)
|
||||
g.POST("/restartPanel", a.restartPanel)
|
||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||
@@ -72,6 +78,19 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/testTgBot", a.testTgBot)
|
||||
}
|
||||
|
||||
func (a *SettingController) validateRegex(c *gin.Context) {
|
||||
form := &validateRegexForm{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
pureJsonMsg(c, http.StatusOK, false, err.Error())
|
||||
return
|
||||
}
|
||||
if err := service.ValidateRegex(form.Regex); err != nil {
|
||||
pureJsonMsg(c, http.StatusOK, false, err.Error())
|
||||
return
|
||||
}
|
||||
pureJsonMsg(c, http.StatusOK, true, "")
|
||||
}
|
||||
|
||||
// getAllSetting retrieves all current settings as the browser-safe view:
|
||||
// secret values are redacted and surfaced as has* presence flags instead.
|
||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestValidateRegex(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
NewSettingController(router.Group("/panel/api"))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
success bool
|
||||
}{
|
||||
{name: "Go RE2 inline flag", body: `{"regex":"(?m)^general-purpose$"}`, success: true},
|
||||
{name: "invalid expression", body: `{"regex":"["}`, success: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/panel/api/setting/validateRegex", strings.NewReader(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body=%s", resp.Code, resp.Body.String())
|
||||
}
|
||||
needle := `"success":true`
|
||||
if !tt.success {
|
||||
needle = `"success":false`
|
||||
}
|
||||
if !strings.Contains(resp.Body.String(), needle) {
|
||||
t.Fatalf("body = %s, want %s", resp.Body.String(), needle)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,11 @@ type AllSetting struct {
|
||||
// Subscription server settings
|
||||
SubEnable bool `json:"subEnable" form:"subEnable"` // Enable subscription server
|
||||
SubJsonEnable bool `json:"subJsonEnable" form:"subJsonEnable"` // Enable JSON subscription endpoint
|
||||
SubJsonAutoDetect bool `json:"subJsonAutoDetect" form:"subJsonAutoDetect"` // Opt in to serving Xray JSON from the standard subscription URL for recognized compatible User-Agents
|
||||
SubJsonAlwaysArray bool `json:"subJsonAlwaysArray" form:"subJsonAlwaysArray"` // Always return JSON subscriptions as an array, including single-profile responses
|
||||
SubJsonUserAgentRegex string `json:"subJsonUserAgentRegex" form:"subJsonUserAgentRegex"` // Go RE2 regular expression used to recognize Xray JSON subscription clients
|
||||
SubClashAutoDetect bool `json:"subClashAutoDetect" form:"subClashAutoDetect"` // Opt in to serving Clash YAML from the standard subscription URL for recognized Clash/Mihomo/Stash User-Agents; browsers and other clients keep existing responses
|
||||
SubClashUserAgentRegex string `json:"subClashUserAgentRegex" form:"subClashUserAgentRegex"` // Go RE2 regular expression used to recognize Clash/Mihomo clients on the standard subscription URL
|
||||
SubTitle string `json:"subTitle" form:"subTitle"` // Subscription title
|
||||
SubSupportUrl string `json:"subSupportUrl" form:"subSupportUrl"` // Subscription support URL
|
||||
SubProfileUrl string `json:"subProfileUrl" form:"subProfileUrl"` // Subscription profile URL
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -32,6 +33,13 @@ import (
|
||||
//go:embed config.json
|
||||
var xrayTemplateConfig string
|
||||
|
||||
const (
|
||||
DefaultSubClashUserAgentRegex = `(?i)(clash|mihomo|stash)`
|
||||
DefaultSubJsonUserAgentRegex = `(?i)^streisand([ /]|$)`
|
||||
DefaultRemarkTemplate = "{{INBOUND}}-{{EMAIL}}|📊{{TRAFFIC_LEFT}}|⏳{{DAYS_LEFT}}D"
|
||||
maxRegexLength = 2048
|
||||
)
|
||||
|
||||
var defaultValueMap = map[string]string{
|
||||
"xrayTemplateConfig": xrayTemplateConfig,
|
||||
"webListen": "",
|
||||
@@ -57,7 +65,7 @@ var defaultValueMap = map[string]string{
|
||||
"pageSize": "25",
|
||||
"expireDiff": "0",
|
||||
"trafficDiff": "0",
|
||||
"remarkTemplate": "{{INBOUND}}-{{EMAIL}}|📊{{TRAFFIC_LEFT}}|⏳{{DAYS_LEFT}}D",
|
||||
"remarkTemplate": DefaultRemarkTemplate,
|
||||
"timeLocation": "Local",
|
||||
"tgBotEnable": "false",
|
||||
"tgBotToken": "",
|
||||
@@ -73,6 +81,11 @@ var defaultValueMap = map[string]string{
|
||||
"twoFactorToken": "",
|
||||
"subEnable": "true",
|
||||
"subJsonEnable": "false",
|
||||
"subJsonAutoDetect": "false",
|
||||
"subJsonAlwaysArray": "false",
|
||||
"subJsonUserAgentRegex": "",
|
||||
"subClashAutoDetect": "false",
|
||||
"subClashUserAgentRegex": "",
|
||||
"subTitle": "",
|
||||
"subSupportUrl": "",
|
||||
"subProfileUrl": "",
|
||||
@@ -705,6 +718,26 @@ func (s *SettingService) GetSubJsonEnable() (bool, error) {
|
||||
return s.getBool("subJsonEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonAutoDetect() (bool, error) {
|
||||
return s.getBool("subJsonAutoDetect")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonAlwaysArray() (bool, error) {
|
||||
return s.getBool("subJsonAlwaysArray")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonUserAgentRegex() (string, error) {
|
||||
return s.getString("subJsonUserAgentRegex")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubClashAutoDetect() (bool, error) {
|
||||
return s.getBool("subClashAutoDetect")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubClashUserAgentRegex() (string, error) {
|
||||
return s.getString("subClashUserAgentRegex")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubTitle() (string, error) {
|
||||
return s.getString("subTitle")
|
||||
}
|
||||
@@ -1101,6 +1134,9 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting, clears
|
||||
if err := validateSettingsURLs(allSetting); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateSubUserAgentRegexes(allSetting); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := allSetting.CheckValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1141,6 +1177,45 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting, clears
|
||||
})
|
||||
}
|
||||
|
||||
func validateSubUserAgentRegexes(allSetting *entity.AllSetting) error {
|
||||
jsonPattern, err := validateSubUserAgentRegex("Xray JSON", allSetting.SubJsonUserAgentRegex, DefaultSubJsonUserAgentRegex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clashPattern, err := validateSubUserAgentRegex("Clash/Mihomo", allSetting.SubClashUserAgentRegex, DefaultSubClashUserAgentRegex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allSetting.SubJsonUserAgentRegex = jsonPattern
|
||||
allSetting.SubClashUserAgentRegex = clashPattern
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSubUserAgentRegex(name, pattern, defaultPattern string) (string, error) {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
effectivePattern := pattern
|
||||
if effectivePattern == "" {
|
||||
effectivePattern = defaultPattern
|
||||
}
|
||||
if len(effectivePattern) > maxRegexLength {
|
||||
return "", common.NewErrorf("%s User-Agent regex must not exceed %d characters", name, maxRegexLength)
|
||||
}
|
||||
if _, err := regexp.Compile(effectivePattern); err != nil {
|
||||
return "", common.NewError(name+" User-Agent regex is invalid:", err)
|
||||
}
|
||||
return pattern, nil
|
||||
}
|
||||
|
||||
func ValidateRegex(pattern string) error {
|
||||
if len(pattern) > maxRegexLength {
|
||||
return common.NewErrorf("Regular expression must not exceed %d characters", maxRegexLength)
|
||||
}
|
||||
if _, err := regexp.Compile(pattern); err != nil {
|
||||
return common.NewError("Regular expression is invalid:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SettingService) preserveRedactedSecrets(allSetting *entity.AllSetting, clears SecretClears) error {
|
||||
if !clears.TgBotToken && strings.TrimSpace(allSetting.TgBotToken) == "" {
|
||||
value, err := s.GetTgBotToken()
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
func TestValidateRegex(t *testing.T) {
|
||||
for _, pattern := range []string{"", `(?i)^streisand([ /]|$)`, `(?m)^general-purpose$`} {
|
||||
if err := ValidateRegex(pattern); err != nil {
|
||||
t.Errorf("ValidateRegex(%q) returned %v", pattern, err)
|
||||
}
|
||||
}
|
||||
for _, pattern := range []string{"[", strings.Repeat("a", 2049)} {
|
||||
if err := ValidateRegex(pattern); err == nil {
|
||||
t.Errorf("ValidateRegex(%q) accepted an invalid pattern", pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubscriptionAutoDetectDefaultsWithoutStoredRows(t *testing.T) {
|
||||
setupSettingTestDB(t)
|
||||
keys := []string{"subClashAutoDetect", "subClashUserAgentRegex", "subJsonAutoDetect", "subJsonAlwaysArray", "subJsonUserAgentRegex"}
|
||||
if err := database.GetDB().Where("key IN ?", keys).Delete(&model.Setting{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := &SettingService{}
|
||||
clashEnabled, err := s.GetSubClashAutoDetect()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jsonEnabled, err := s.GetSubJsonAutoDetect()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jsonAlwaysArray, err := s.GetSubJsonAlwaysArray()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clashRegex, err := s.GetSubClashUserAgentRegex()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jsonRegex, err := s.GetSubJsonUserAgentRegex()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if clashEnabled || jsonEnabled || jsonAlwaysArray {
|
||||
t.Fatalf("missing subscription flags must default off: clashAuto=%v jsonAuto=%v jsonAlwaysArray=%v", clashEnabled, jsonEnabled, jsonAlwaysArray)
|
||||
}
|
||||
if clashRegex != "" {
|
||||
t.Fatalf("missing Clash regex = %q, want empty inherited value", clashRegex)
|
||||
}
|
||||
if jsonRegex != "" {
|
||||
t.Fatalf("missing JSON regex = %q, want empty inherited value", jsonRegex)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := database.GetDB().Model(&model.Setting{}).Where("key IN ?", keys).Count(&count).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("default lookup unexpectedly persisted %d setting rows", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAllSettingPreservesEmptyUserAgentRegexes(t *testing.T) {
|
||||
setupSettingTestDB(t)
|
||||
s := &SettingService{}
|
||||
settings, err := s.GetAllSetting()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings.SubJsonUserAgentRegex = " "
|
||||
settings.SubClashUserAgentRegex = ""
|
||||
|
||||
if err := s.UpdateAllSetting(settings, SecretClears{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, key := range []string{"subJsonUserAgentRegex", "subClashUserAgentRegex"} {
|
||||
var stored model.Setting
|
||||
if err := database.GetDB().Where("key = ?", key).First(&stored).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stored.Value != "" {
|
||||
t.Fatalf("%s stored value = %q, want empty inherited value", key, stored.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAllSettingPersistsClashSubscriptionSettings(t *testing.T) {
|
||||
setupSettingTestDB(t)
|
||||
s := &SettingService{}
|
||||
|
||||
settings, err := s.GetAllSetting()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if settings.SubClashAutoDetect {
|
||||
t.Fatal("subClashAutoDetect default = true, want false")
|
||||
}
|
||||
if settings.SubJsonAutoDetect {
|
||||
t.Fatal("subJsonAutoDetect default = true, want false")
|
||||
}
|
||||
if settings.SubJsonAlwaysArray {
|
||||
t.Fatal("subJsonAlwaysArray default = true, want false")
|
||||
}
|
||||
if settings.SubJsonUserAgentRegex != "" {
|
||||
t.Fatalf("subJsonUserAgentRegex = %q, want empty inherited value", settings.SubJsonUserAgentRegex)
|
||||
}
|
||||
if settings.SubClashUserAgentRegex != "" {
|
||||
t.Fatalf("subClashUserAgentRegex = %q, want empty inherited value", settings.SubClashUserAgentRegex)
|
||||
}
|
||||
settings.SubClashAutoDetect = true
|
||||
settings.SubClashUserAgentRegex = `(?i)^custom-clash/`
|
||||
settings.SubJsonAutoDetect = true
|
||||
settings.SubJsonAlwaysArray = true
|
||||
settings.SubJsonUserAgentRegex = `(?i)^custom-json/`
|
||||
settings.SubJsonEnable = true
|
||||
settings.SubJsonPath = "/json-custom/"
|
||||
settings.SubJsonURI = "https://subscriptions.example.com/json-custom/"
|
||||
settings.SubClashEnable = true
|
||||
settings.SubClashPath = "/clash-custom/"
|
||||
settings.SubClashURI = "https://subscriptions.example.com/clash-custom/"
|
||||
settings.SubClashEnableRouting = true
|
||||
settings.SubClashRules = "GEOIP,private,DIRECT"
|
||||
|
||||
if err := s.UpdateAllSetting(settings, SecretClears{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := s.GetAllSetting()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !got.SubClashEnable {
|
||||
t.Fatal("subClashEnable = false, want true")
|
||||
}
|
||||
if !got.SubClashAutoDetect {
|
||||
t.Fatal("subClashAutoDetect = false, want true")
|
||||
}
|
||||
if !got.SubJsonAutoDetect {
|
||||
t.Fatal("subJsonAutoDetect = false, want true")
|
||||
}
|
||||
if !got.SubJsonAlwaysArray {
|
||||
t.Fatal("subJsonAlwaysArray = false, want true")
|
||||
}
|
||||
if !got.SubJsonEnable {
|
||||
t.Fatal("subJsonEnable = false, want true")
|
||||
}
|
||||
if got.SubJsonPath != "/json-custom/" {
|
||||
t.Fatalf("subJsonPath = %q, want %q", got.SubJsonPath, "/json-custom/")
|
||||
}
|
||||
if got.SubJsonURI != "https://subscriptions.example.com/json-custom/" {
|
||||
t.Fatalf("subJsonURI = %q, want %q", got.SubJsonURI, "https://subscriptions.example.com/json-custom/")
|
||||
}
|
||||
if got.SubJsonUserAgentRegex != `(?i)^custom-json/` {
|
||||
t.Fatalf("subJsonUserAgentRegex = %q, want %q", got.SubJsonUserAgentRegex, `(?i)^custom-json/`)
|
||||
}
|
||||
if got.SubClashUserAgentRegex != `(?i)^custom-clash/` {
|
||||
t.Fatalf("subClashUserAgentRegex = %q, want %q", got.SubClashUserAgentRegex, `(?i)^custom-clash/`)
|
||||
}
|
||||
if got.SubClashPath != "/clash-custom/" {
|
||||
t.Fatalf("subClashPath = %q, want %q", got.SubClashPath, "/clash-custom/")
|
||||
}
|
||||
if got.SubClashURI != "https://subscriptions.example.com/clash-custom/" {
|
||||
t.Fatalf("subClashURI = %q, want %q", got.SubClashURI, "https://subscriptions.example.com/clash-custom/")
|
||||
}
|
||||
if !got.SubClashEnableRouting {
|
||||
t.Fatal("subClashEnableRouting = false, want true")
|
||||
}
|
||||
if got.SubClashRules != "GEOIP,private,DIRECT" {
|
||||
t.Fatalf("subClashRules = %q, want %q", got.SubClashRules, "GEOIP,private,DIRECT")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAllSettingRejectsInvalidClashUserAgentRegex(t *testing.T) {
|
||||
setupSettingTestDB(t)
|
||||
s := &SettingService{}
|
||||
settings, err := s.GetAllSetting()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings.SubClashUserAgentRegex = "["
|
||||
if err := s.UpdateAllSetting(settings, SecretClears{}); err == nil {
|
||||
t.Fatal("UpdateAllSetting accepted an invalid Clash/Mihomo User-Agent regex")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAllSettingRejectsInvalidJsonUserAgentRegex(t *testing.T) {
|
||||
setupSettingTestDB(t)
|
||||
s := &SettingService{}
|
||||
settings, err := s.GetAllSetting()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
settings.SubJsonUserAgentRegex = "["
|
||||
if err := s.UpdateAllSetting(settings, SecretClears{}); err == nil {
|
||||
t.Fatal("UpdateAllSetting accepted an invalid Xray JSON User-Agent regex")
|
||||
}
|
||||
}
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "تمكين/تعطيل نقطة نهاية اشتراك JSON بشكل مستقل.",
|
||||
"subJsonEnableTitle": "اشتراك JSON",
|
||||
"subClashEnableTitle": "اشتراك Clash / Mihomo",
|
||||
"subFormatsTipTitle": "إعدادات الاشتراك الخاصة بالتنسيق",
|
||||
"subFormatsTipDesc": "اضبط مسارات URL وعناوين URL العكسية والاكتشاف التلقائي للعملاء لكل من JSON وClash / Mihomo بشكل منفصل.",
|
||||
"subFormatsTipAction": "فتح تنسيقات الاشتراك",
|
||||
"subJsonAutoDetect": "اكتشاف عملاء Xray JSON تلقائيًا",
|
||||
"subJsonAutoDetectDesc": "عند التفعيل، يتلقى العملاء المتوافقون المعروفون الذين يطلبون رابط الاشتراك القياسي مصفوفة إعدادات Xray JSON تلقائيًا. يحصل العملاء الآخرون على الاستجابة الخام/Base64. يتطلب تفعيل اشتراك JSON وإعادة تشغيل اللوحة.",
|
||||
"subJsonAlwaysArray": "إرجاع مصفوفة JSON دائمًا",
|
||||
"subJsonAlwaysArrayDesc": "يعيد مسار اشتراك JSON الصريح كمصفوفة حتى عند وجود ملف واحد، وفقًا لمعيار XTLS. تستخدم استجابات JSON المكتشفة تلقائيًا المصفوفات دائمًا. عطّله للاحتفاظ باستجابة الكائن الواحد القديمة.",
|
||||
"subJsonUserAgentRegex": "تعبير User-Agent لعملاء Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "تعبير Go RE2 منتظم للتعرف على عملاء اشتراك Xray JSON. يطابق النمط الافتراضي المحافظ Streisand فقط؛ أضف العملاء الآخرين الذين تم التحقق منهم صراحةً. يستمر v2rayNG وv2rayN وv2rayA في تلقي الاشتراك الخام افتراضيًا. اتركه فارغًا لاستعادة النمط الافتراضي. أعد تشغيل اللوحة بعد التغيير.",
|
||||
"subClashAutoDetect": "اكتشاف عملاء Clash/Mihomo تلقائيًا",
|
||||
"subClashAutoDetectDesc": "عند التفعيل، يتلقى عملاء Clash وMihomo وStash المعروفون الذين يطلبون رابط الاشتراك القياسي إعداد Clash بصيغة YAML تلقائيًا. تستمر المتصفحات في عرض صفحة الاشتراك، ويحصل العملاء الآخرون على الاستجابة الخام/Base64، وتبقى روابط JSON وClash الصريحة متاحة. يتطلب تفعيل اشتراك Clash/Mihomo وإعادة تشغيل اللوحة لتطبيق التغيير.",
|
||||
"subClashUserAgentRegex": "تعبير User-Agent لعملاء Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "تعبير Go RE2 منتظم للتعرف على عملاء Clash وMihomo وStash في رابط الاشتراك القياسي. اتركه فارغًا لاستخدام النمط الافتراضي. أعد تشغيل اللوحة بعد التغيير.",
|
||||
"subTitle": "عنوان الاشتراك",
|
||||
"subTitleDesc": "العنوان اللي هيظهر في عميل VPN",
|
||||
"subSupportUrl": "رابط الدعم",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "أقنعة finalmask في xray (TCP/UDP) وضبط QUIC تُضاف إلى كل تدفق اشتراك JSON. يتطلب إصدار xray حديثًا على العميل.",
|
||||
"finalMaskDesc": "يضيف أقنعة TCP/UDP الخاصة بـ Xray finalmask ومعلمات QUIC إلى كل ملف Xray JSON يتم إنشاؤه. يتطلب تطبيقًا يدعم اشتراكات Xray JSON وإصدارًا حديثًا من نواة Xray.",
|
||||
"packets": "الحزم",
|
||||
"length": "الطول",
|
||||
"interval": "الفاصل",
|
||||
|
||||
@@ -1297,6 +1297,19 @@
|
||||
"subJsonEnable": "Enable/Disable the JSON subscription endpoint independently.",
|
||||
"subJsonEnableTitle": "JSON subscription",
|
||||
"subClashEnableTitle": "Clash / Mihomo subscription",
|
||||
"subFormatsTipTitle": "Format-specific subscription settings",
|
||||
"subFormatsTipDesc": "Configure JSON and Clash / Mihomo URL paths, reverse URLs, and client auto-detection separately.",
|
||||
"subFormatsTipAction": "Open Sub Formats",
|
||||
"subJsonAutoDetect": "Auto-detect Xray JSON clients",
|
||||
"subJsonAutoDetectDesc": "When enabled, recognized compatible clients requesting the standard subscription URL receive an Xray JSON configuration array automatically. Other clients keep the raw/base64 response. Requires JSON subscription to be enabled and a panel restart to apply.",
|
||||
"subJsonAlwaysArray": "Always return a JSON array",
|
||||
"subJsonAlwaysArrayDesc": "Return the explicit JSON subscription endpoint as an array even when it contains one profile, as required by the XTLS subscription standard. Auto-detected JSON responses always use arrays. Leave disabled to preserve the legacy single-object response.",
|
||||
"subJsonUserAgentRegex": "Xray JSON User-Agent regex",
|
||||
"subJsonUserAgentRegexDesc": "Go RE2 regular expression used to recognize Xray JSON subscription clients. The conservative default matches only Streisand; add other verified clients explicitly. v2rayNG, v2rayN, and v2rayA remain on raw subscriptions by default. Leave empty to restore the default pattern. Restart the panel after changes.",
|
||||
"subClashAutoDetect": "Auto-detect Clash/Mihomo clients",
|
||||
"subClashAutoDetectDesc": "When enabled, recognized Clash, Mihomo, and Stash clients requesting the standard subscription URL receive Clash YAML automatically. Browsers still show the subscription page, other clients keep the raw/base64 response, and the explicit JSON and Clash URLs remain available. Requires Clash/Mihomo subscription to be enabled and a panel restart to apply.",
|
||||
"subClashUserAgentRegex": "Clash/Mihomo User-Agent regex",
|
||||
"subClashUserAgentRegexDesc": "Go RE2 regular expression used to recognize Clash, Mihomo, and Stash clients on the standard subscription URL. Leave empty to use the default pattern. Restart the panel after changes.",
|
||||
"subTitle": "Subscription Title",
|
||||
"subTitleDesc": "Title shown in VPN client",
|
||||
"subSupportUrl": "Support URL",
|
||||
@@ -1387,7 +1400,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "xray finalmask masks (TCP/UDP) and QUIC tuning injected into every JSON subscription stream. Requires a recent xray client.",
|
||||
"finalMaskDesc": "Inject Xray finalmask TCP/UDP masks and QUIC parameters into every generated Xray JSON profile. Requires a client app that supports Xray JSON subscriptions and a recent Xray core.",
|
||||
"packets": "Packets",
|
||||
"length": "Length",
|
||||
"interval": "Interval",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "Habilitar/Deshabilitar el endpoint de suscripción JSON de forma independiente.",
|
||||
"subJsonEnableTitle": "Suscripción JSON",
|
||||
"subClashEnableTitle": "Suscripción Clash / Mihomo",
|
||||
"subFormatsTipTitle": "Configuración de suscripción específica por formato",
|
||||
"subFormatsTipDesc": "Configura por separado las rutas URL, las URL inversas y la detección automática de clientes para JSON y Clash / Mihomo.",
|
||||
"subFormatsTipAction": "Abrir formatos de suscripción",
|
||||
"subJsonAutoDetect": "Detectar automáticamente clientes Xray JSON",
|
||||
"subJsonAutoDetectDesc": "Al activarlo, los clientes compatibles reconocidos que soliciten la URL de suscripción estándar recibirán automáticamente una matriz de configuraciones Xray JSON. Los demás clientes conservarán la respuesta sin procesar/Base64. Requiere activar la suscripción JSON y reiniciar el panel.",
|
||||
"subJsonAlwaysArray": "Devolver siempre una matriz JSON",
|
||||
"subJsonAlwaysArrayDesc": "Devuelve el endpoint JSON explícito como una matriz incluso con un solo perfil, según el estándar XTLS. Las respuestas JSON detectadas automáticamente siempre usan matrices. Desactívalo para conservar la respuesta heredada de un solo objeto.",
|
||||
"subJsonUserAgentRegex": "Expresión User-Agent de Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "Expresión regular Go RE2 para reconocer clientes de suscripción Xray JSON. El patrón conservador predeterminado solo coincide con Streisand; añade explícitamente otros clientes verificados. v2rayNG, v2rayN y v2rayA conservan la suscripción sin procesar de forma predeterminada. Déjala vacía para restaurar el patrón predeterminado. Reinicia el panel después de cambiarla.",
|
||||
"subClashAutoDetect": "Detectar automáticamente clientes Clash/Mihomo",
|
||||
"subClashAutoDetectDesc": "Al activarlo, los clientes Clash, Mihomo y Stash reconocidos que soliciten la URL de suscripción estándar recibirán automáticamente YAML de Clash. Los navegadores seguirán mostrando la página de suscripción, los demás clientes conservarán la respuesta sin procesar/Base64 y las URL explícitas de JSON y Clash seguirán disponibles. Requiere activar la suscripción Clash/Mihomo y reiniciar el panel para aplicar el cambio.",
|
||||
"subClashUserAgentRegex": "Expresión User-Agent de Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "Expresión regular Go RE2 para reconocer clientes Clash, Mihomo y Stash en la URL de suscripción estándar. Déjala vacía para usar el patrón predeterminado. Reinicia el panel después de cambiarla.",
|
||||
"subTitle": "Título de la Suscripción",
|
||||
"subTitleDesc": "Título mostrado en el cliente VPN",
|
||||
"subSupportUrl": "URL de soporte",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "Máscaras finalmask de xray (TCP/UDP) y ajustes QUIC inyectados en cada flujo de suscripción JSON. Requiere un cliente xray reciente.",
|
||||
"finalMaskDesc": "Inyecta máscaras TCP/UDP de finalmask de Xray y parámetros QUIC en cada perfil Xray JSON generado. Requiere una aplicación compatible con suscripciones Xray JSON y un núcleo Xray reciente.",
|
||||
"packets": "Paquetes",
|
||||
"length": "Longitud",
|
||||
"interval": "Intervalo",
|
||||
|
||||
@@ -1181,6 +1181,19 @@
|
||||
"subJsonEnable": "فعال/غیرفعالسازی مستقل نقطه دسترسی سابسکریپشن JSON.",
|
||||
"subJsonEnableTitle": "اشتراک JSON",
|
||||
"subClashEnableTitle": "اشتراک Clash / Mihomo",
|
||||
"subFormatsTipTitle": "تنظیمات اشتراک ویژه هر قالب",
|
||||
"subFormatsTipDesc": "مسیرهای URL، آدرسهای معکوس و تشخیص خودکار کلاینت را برای JSON و Clash / Mihomo جداگانه پیکربندی کنید.",
|
||||
"subFormatsTipAction": "باز کردن قالبهای اشتراک",
|
||||
"subJsonAutoDetect": "تشخیص خودکار کلاینتهای Xray JSON",
|
||||
"subJsonAutoDetectDesc": "با فعالسازی، کلاینتهای سازگار و شناختهشده که آدرس استاندارد اشتراک را درخواست میکنند، بهطور خودکار آرایه تنظیمات Xray JSON دریافت میکنند. سایر کلاینتها پاسخ خام/Base64 را دریافت میکنند. برای اعمال تغییر، اشتراک JSON باید فعال باشد و پنل راهاندازی مجدد شود.",
|
||||
"subJsonAlwaysArray": "همیشه آرایه JSON برگردان",
|
||||
"subJsonAlwaysArrayDesc": "مسیر صریح اشتراک JSON را حتی برای یک پروفایل، مطابق استاندارد XTLS بهصورت آرایه برمیگرداند. پاسخهای JSON تشخیصدادهشده همیشه آرایه هستند. برای حفظ پاسخ قدیمی تکشیء غیرفعال بگذارید.",
|
||||
"subJsonUserAgentRegex": "عبارت User-Agent برای Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "عبارت منظم Go RE2 برای شناسایی کلاینتهای اشتراک Xray JSON. الگوی محافظهکارانه پیشفرض فقط با Streisand مطابقت دارد؛ سایر کلاینتهای تأییدشده را صریحاً اضافه کنید. v2rayNG، v2rayN و v2rayA بهطور پیشفرض اشتراک خام را دریافت میکنند. برای بازگرداندن الگوی پیشفرض خالی بگذارید. پس از تغییر، پنل را راهاندازی مجدد کنید.",
|
||||
"subClashAutoDetect": "تشخیص خودکار کلاینتهای Clash/Mihomo",
|
||||
"subClashAutoDetectDesc": "با فعالسازی، کلاینتهای شناختهشده Clash، Mihomo و Stash که آدرس استاندارد اشتراک را درخواست میکنند، بهطور خودکار پیکربندی Clash با فرمت YAML دریافت میکنند. مرورگرها همچنان صفحه اشتراک را نمایش میدهند، سایر کلاینتها پاسخ خام/Base64 را دریافت میکنند و آدرسهای صریح JSON و Clash در دسترس میمانند. برای اعمال تغییر، اشتراک Clash/Mihomo باید فعال باشد و پنل راهاندازی مجدد شود.",
|
||||
"subClashUserAgentRegex": "عبارت User-Agent برای Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "عبارت منظم Go RE2 برای شناسایی کلاینتهای Clash، Mihomo و Stash در آدرس استاندارد اشتراک. برای استفاده از الگوی پیشفرض خالی بگذارید. پس از تغییر، پنل را راهاندازی مجدد کنید.",
|
||||
"subTitle": "عنوان اشتراک",
|
||||
"subTitleDesc": "عنوان نمایش داده شده در کلاینت VPN",
|
||||
"subSupportUrl": "آدرس پشتیبانی",
|
||||
@@ -1271,7 +1284,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "ماسکهای finalmask ایکسری (TCP/UDP) و تنظیمات QUIC که داخل همهی stream های اشتراک JSON تزریق میشوند. به نسخهی جدید هستهی xray در کلاینت نیاز دارد.",
|
||||
"finalMaskDesc": "ماسکهای TCP/UDP مربوط به Xray finalmask و پارامترهای QUIC را به هر پروفایل Xray JSON تولیدشده اضافه میکند. به برنامهای با پشتیبانی از اشتراک Xray JSON و هسته جدید Xray نیاز دارد.",
|
||||
"packets": "بستهها",
|
||||
"length": "طول",
|
||||
"interval": "بازه",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "Aktifkan/Nonaktifkan endpoint langganan JSON secara mandiri.",
|
||||
"subJsonEnableTitle": "Langganan JSON",
|
||||
"subClashEnableTitle": "Langganan Clash / Mihomo",
|
||||
"subFormatsTipTitle": "Pengaturan langganan khusus format",
|
||||
"subFormatsTipDesc": "Konfigurasikan jalur URL, URL proksi balik, dan deteksi otomatis klien untuk JSON serta Clash / Mihomo secara terpisah.",
|
||||
"subFormatsTipAction": "Buka Format Langganan",
|
||||
"subJsonAutoDetect": "Deteksi otomatis klien Xray JSON",
|
||||
"subJsonAutoDetectDesc": "Jika diaktifkan, klien kompatibel yang dikenali dan meminta URL langganan standar akan otomatis menerima larik konfigurasi Xray JSON. Klien lain tetap menerima respons mentah/Base64. Langganan JSON harus diaktifkan dan panel harus dimulai ulang.",
|
||||
"subJsonAlwaysArray": "Selalu kembalikan larik JSON",
|
||||
"subJsonAlwaysArrayDesc": "Mengembalikan endpoint langganan JSON eksplisit sebagai larik meski hanya berisi satu profil, sesuai standar XTLS. Respons JSON yang terdeteksi otomatis selalu menggunakan larik. Nonaktifkan untuk mempertahankan respons objek tunggal lama.",
|
||||
"subJsonUserAgentRegex": "Regex User-Agent Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "Ekspresi reguler Go RE2 untuk mengenali klien langganan Xray JSON. Pola bawaan konservatif hanya cocok dengan Streisand; tambahkan klien terverifikasi lainnya secara eksplisit. v2rayNG, v2rayN, dan v2rayA tetap menerima langganan mentah secara bawaan. Kosongkan untuk memulihkan pola bawaan. Mulai ulang panel setelah mengubahnya.",
|
||||
"subClashAutoDetect": "Deteksi otomatis klien Clash/Mihomo",
|
||||
"subClashAutoDetectDesc": "Jika diaktifkan, klien Clash, Mihomo, dan Stash yang dikenali dan meminta URL langganan standar akan otomatis menerima YAML Clash. Browser tetap menampilkan halaman langganan, klien lain tetap menerima respons mentah/Base64, dan URL JSON serta Clash eksplisit tetap tersedia. Langganan Clash/Mihomo harus diaktifkan dan panel harus dimulai ulang agar perubahan diterapkan.",
|
||||
"subClashUserAgentRegex": "Regex User-Agent Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "Ekspresi reguler Go RE2 untuk mengenali klien Clash, Mihomo, dan Stash pada URL langganan standar. Kosongkan untuk memakai pola bawaan. Mulai ulang panel setelah mengubahnya.",
|
||||
"subTitle": "Judul Langganan",
|
||||
"subTitleDesc": "Judul yang ditampilkan di klien VPN",
|
||||
"subSupportUrl": "URL Dukungan",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "Mask finalmask xray (TCP/UDP) dan penyetelan QUIC yang disuntikkan ke setiap stream langganan JSON. Membutuhkan klien xray terbaru.",
|
||||
"finalMaskDesc": "Menyisipkan mask TCP/UDP Xray finalmask dan parameter QUIC ke setiap profil Xray JSON yang dibuat. Membutuhkan aplikasi klien yang mendukung langganan Xray JSON dan inti Xray terbaru.",
|
||||
"packets": "Paket",
|
||||
"length": "Panjang",
|
||||
"interval": "Interval",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "JSON サブスクリプションのエンドポイントを個別に有効/無効にする。",
|
||||
"subJsonEnableTitle": "JSON サブスクリプション",
|
||||
"subClashEnableTitle": "Clash / Mihomo サブスクリプション",
|
||||
"subFormatsTipTitle": "形式別サブスクリプション設定",
|
||||
"subFormatsTipDesc": "JSON と Clash / Mihomo の URL パス、リバース URL、クライアント自動検出を個別に設定します。",
|
||||
"subFormatsTipAction": "サブスクリプション形式を開く",
|
||||
"subJsonAutoDetect": "Xray JSON クライアントを自動検出",
|
||||
"subJsonAutoDetectDesc": "有効にすると、標準サブスクリプション URL を要求する既知の互換クライアントへ Xray JSON 設定配列を自動的に返します。その他のクライアントには従来の raw/Base64 応答を返します。JSON サブスクリプションを有効にし、適用のためにパネルを再起動する必要があります。",
|
||||
"subJsonAlwaysArray": "常に JSON 配列を返す",
|
||||
"subJsonAlwaysArrayDesc": "プロファイルが 1 件でも、明示的な JSON サブスクリプションを XTLS 標準に従う配列として返します。自動検出された JSON 応答は常に配列です。無効時は従来の単一オブジェクト応答を維持します。",
|
||||
"subJsonUserAgentRegex": "Xray JSON User-Agent 正規表現",
|
||||
"subJsonUserAgentRegexDesc": "Xray JSON サブスクリプションクライアントを識別する Go RE2 正規表現です。保守的な既定値は Streisand のみに一致します。その他の検証済みクライアントは明示的に追加してください。v2rayNG、v2rayN、v2rayA は既定で raw サブスクリプションを使用します。空欄にすると既定のパターンへ戻ります。変更後にパネルを再起動してください。",
|
||||
"subClashAutoDetect": "Clash/Mihomo クライアントを自動検出",
|
||||
"subClashAutoDetectDesc": "有効にすると、標準サブスクリプション URL を要求する既知の Clash、Mihomo、Stash クライアントへ Clash YAML を自動的に返します。ブラウザには引き続きサブスクリプションページを表示し、その他のクライアントには従来の raw/Base64 応答を返します。明示的な JSON および Clash URL も引き続き利用できます。Clash/Mihomo サブスクリプションを有効にし、適用のためにパネルを再起動する必要があります。",
|
||||
"subClashUserAgentRegex": "Clash/Mihomo User-Agent 正規表現",
|
||||
"subClashUserAgentRegexDesc": "標準サブスクリプション URL で Clash、Mihomo、Stash クライアントを識別する Go RE2 正規表現です。空欄の場合は既定のパターンを使用します。変更後にパネルを再起動してください。",
|
||||
"subTitle": "サブスクリプションタイトル",
|
||||
"subTitleDesc": "VPNクライアントに表示されるタイトル",
|
||||
"subSupportUrl": "サポートURL",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "すべての JSON サブスクリプションストリームに注入される xray finalmask マスク(TCP/UDP)と QUIC チューニング。新しい xray クライアントが必要です。",
|
||||
"finalMaskDesc": "生成される各 Xray JSON プロファイルへ Xray finalmask の TCP/UDP マスクと QUIC パラメーターを追加します。Xray JSON サブスクリプション対応アプリと新しい Xray コアが必要です。",
|
||||
"packets": "パケット",
|
||||
"length": "長さ",
|
||||
"interval": "間隔",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "Ativar/Desativar o endpoint de assinatura JSON de forma independente.",
|
||||
"subJsonEnableTitle": "Assinatura JSON",
|
||||
"subClashEnableTitle": "Assinatura Clash / Mihomo",
|
||||
"subFormatsTipTitle": "Configurações de assinatura específicas por formato",
|
||||
"subFormatsTipDesc": "Configure separadamente os caminhos de URL, URLs reversas e a detecção automática de clientes para JSON e Clash / Mihomo.",
|
||||
"subFormatsTipAction": "Abrir formatos de assinatura",
|
||||
"subJsonAutoDetect": "Detectar clientes Xray JSON automaticamente",
|
||||
"subJsonAutoDetectDesc": "Quando ativado, clientes compatíveis reconhecidos que solicitarem a URL de assinatura padrão receberão automaticamente uma matriz de configurações Xray JSON. Os outros clientes manterão a resposta bruta/Base64. Requer a assinatura JSON ativada e a reinicialização do painel.",
|
||||
"subJsonAlwaysArray": "Sempre retornar uma matriz JSON",
|
||||
"subJsonAlwaysArrayDesc": "Retorna o endpoint JSON explícito como matriz mesmo com apenas um perfil, conforme o padrão XTLS. Respostas JSON detectadas automaticamente sempre usam matrizes. Desative para preservar a resposta legada de objeto único.",
|
||||
"subJsonUserAgentRegex": "Expressão User-Agent do Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "Expressão regular Go RE2 para reconhecer clientes de assinatura Xray JSON. O padrão conservador corresponde apenas ao Streisand; adicione explicitamente outros clientes verificados. v2rayNG, v2rayN e v2rayA permanecem na assinatura bruta por padrão. Deixe em branco para restaurar o padrão. Reinicie o painel após alterá-la.",
|
||||
"subClashAutoDetect": "Detectar clientes Clash/Mihomo automaticamente",
|
||||
"subClashAutoDetectDesc": "Quando ativado, clientes Clash, Mihomo e Stash reconhecidos que solicitarem a URL de assinatura padrão receberão automaticamente YAML do Clash. Os navegadores continuarão exibindo a página de assinatura, os outros clientes manterão a resposta bruta/Base64 e as URLs explícitas de JSON e Clash continuarão disponíveis. Requer a assinatura Clash/Mihomo ativada e a reinicialização do painel para aplicar a alteração.",
|
||||
"subClashUserAgentRegex": "Expressão User-Agent do Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "Expressão regular Go RE2 para reconhecer clientes Clash, Mihomo e Stash na URL de assinatura padrão. Deixe em branco para usar o padrão predefinido. Reinicie o painel após alterá-la.",
|
||||
"subTitle": "Título da Assinatura",
|
||||
"subTitleDesc": "Título exibido no cliente VPN",
|
||||
"subSupportUrl": "URL de Suporte",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "Máscaras finalmask do xray (TCP/UDP) e ajustes QUIC injetados em cada fluxo de assinatura JSON. Requer um cliente xray recente.",
|
||||
"finalMaskDesc": "Injeta máscaras TCP/UDP do finalmask do Xray e parâmetros QUIC em cada perfil Xray JSON gerado. Requer um aplicativo compatível com assinaturas Xray JSON e um núcleo Xray recente.",
|
||||
"packets": "Pacotes",
|
||||
"length": "Comprimento",
|
||||
"interval": "Intervalo",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "Включить/отключить JSON-эндпоинт подписки независимо.",
|
||||
"subJsonEnableTitle": "JSON-подписка",
|
||||
"subClashEnableTitle": "Подписка Clash / Mihomo",
|
||||
"subFormatsTipTitle": "Настройки подписки для отдельных форматов",
|
||||
"subFormatsTipDesc": "Настройте отдельно URL-пути, обратные URL и автоматическое определение клиентов для JSON и Clash / Mihomo.",
|
||||
"subFormatsTipAction": "Открыть форматы подписки",
|
||||
"subJsonAutoDetect": "Автоматически определять клиентов Xray JSON",
|
||||
"subJsonAutoDetectDesc": "Если параметр включён, распознанные совместимые клиенты при запросе стандартного URL подписки автоматически получают массив конфигураций Xray JSON. Остальные клиенты получают прежний ответ raw/Base64. Для применения требуется включить JSON-подписку и перезапустить панель.",
|
||||
"subJsonAlwaysArray": "Всегда возвращать массив JSON",
|
||||
"subJsonAlwaysArrayDesc": "Возвращает явную JSON-подписку как массив даже при одном профиле согласно стандарту XTLS. Автоматически определённые JSON-ответы всегда используют массив. Отключите, чтобы сохранить прежний ответ в виде одного объекта.",
|
||||
"subJsonUserAgentRegex": "Регулярное выражение User-Agent Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "Регулярное выражение Go RE2 для распознавания клиентов подписки Xray JSON. Консервативный стандартный шаблон совпадает только со Streisand; другие проверенные клиенты добавляются явно. v2rayNG, v2rayN и v2rayA по умолчанию получают raw-подписку. Оставьте поле пустым, чтобы восстановить стандартный шаблон. После изменения перезапустите панель.",
|
||||
"subClashAutoDetect": "Автоматически определять клиентов Clash/Mihomo",
|
||||
"subClashAutoDetectDesc": "Если параметр включён, распознанные клиенты Clash, Mihomo и Stash при запросе стандартного URL подписки автоматически получают Clash YAML. Браузеры по-прежнему показывают страницу подписки, остальные клиенты получают прежний ответ raw/Base64, а явные URL JSON и Clash остаются доступными. Для применения требуется включить подписку Clash/Mihomo и перезапустить панель.",
|
||||
"subClashUserAgentRegex": "Регулярное выражение User-Agent Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "Регулярное выражение Go RE2 для распознавания клиентов Clash, Mihomo и Stash на стандартном URL подписки. Оставьте поле пустым, чтобы использовать шаблон по умолчанию. После изменения перезапустите панель.",
|
||||
"subTitle": "Заголовок подписки",
|
||||
"subTitleDesc": "Название подписки, которое видит клиент в VPN-клиенте",
|
||||
"subSupportUrl": "URL поддержки",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "Маски finalmask xray (TCP/UDP) и настройки QUIC, добавляемые в каждый поток JSON-подписки. Требуется свежая версия xray на клиенте.",
|
||||
"finalMaskDesc": "Добавляет TCP/UDP-маски finalmask Xray и параметры QUIC в каждый созданный профиль Xray JSON. Требуется приложение с поддержкой подписок Xray JSON и свежая версия ядра Xray.",
|
||||
"packets": "Пакеты",
|
||||
"length": "Длина",
|
||||
"interval": "Интервал",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "JSON abonelik uç noktasını bağımsız olarak etkinleştirir/devre dışı bırakır.",
|
||||
"subJsonEnableTitle": "JSON Aboneliği",
|
||||
"subClashEnableTitle": "Clash / Mihomo Aboneliği",
|
||||
"subFormatsTipTitle": "Biçime özel abonelik ayarları",
|
||||
"subFormatsTipDesc": "JSON ve Clash / Mihomo için URL yollarını, ters URL'leri ve otomatik istemci algılamayı ayrı ayrı yapılandırın.",
|
||||
"subFormatsTipAction": "Abonelik Biçimlerini Aç",
|
||||
"subJsonAutoDetect": "Xray JSON istemcilerini otomatik algıla",
|
||||
"subJsonAutoDetectDesc": "Etkinleştirildiğinde, standart abonelik URL'sini isteyen tanınmış uyumlu istemciler otomatik olarak bir Xray JSON yapılandırma dizisi alır. Diğer istemciler ham/Base64 yanıtını almaya devam eder. JSON aboneliğinin etkinleştirilmesi ve panelin yeniden başlatılması gerekir.",
|
||||
"subJsonAlwaysArray": "Her zaman JSON dizisi döndür",
|
||||
"subJsonAlwaysArrayDesc": "Açık JSON abonelik uç noktasını tek profil olsa bile XTLS standardına uygun bir dizi olarak döndürür. Otomatik algılanan JSON yanıtları her zaman dizi kullanır. Eski tek nesne yanıtını korumak için devre dışı bırakın.",
|
||||
"subJsonUserAgentRegex": "Xray JSON User-Agent düzenli ifadesi",
|
||||
"subJsonUserAgentRegexDesc": "Xray JSON abonelik istemcilerini tanımak için Go RE2 düzenli ifadesi. Muhafazakâr varsayılan desen yalnızca Streisand ile eşleşir; doğrulanmış diğer istemcileri açıkça ekleyin. v2rayNG, v2rayN ve v2rayA varsayılan olarak ham aboneliği almaya devam eder. Varsayılan deseni geri yüklemek için boş bırakın. Değişiklikten sonra paneli yeniden başlatın.",
|
||||
"subClashAutoDetect": "Clash/Mihomo istemcilerini otomatik algıla",
|
||||
"subClashAutoDetectDesc": "Etkinleştirildiğinde, standart abonelik URL'sini isteyen tanınmış Clash, Mihomo ve Stash istemcileri otomatik olarak Clash YAML alır. Tarayıcılar abonelik sayfasını göstermeye devam eder, diğer istemciler ham/Base64 yanıtını almaya devam eder ve açık JSON ile Clash URL'leri kullanılabilir kalır. Uygulanması için Clash/Mihomo aboneliğinin etkinleştirilmesi ve panelin yeniden başlatılması gerekir.",
|
||||
"subClashUserAgentRegex": "Clash/Mihomo User-Agent düzenli ifadesi",
|
||||
"subClashUserAgentRegexDesc": "Standart abonelik URL'sindeki Clash, Mihomo ve Stash istemcilerini tanımak için Go RE2 düzenli ifadesi. Varsayılan deseni kullanmak için boş bırakın. Değişiklikten sonra paneli yeniden başlatın.",
|
||||
"subTitle": "Abonelik Başlığı",
|
||||
"subTitleDesc": "VPN istemcisinde gösterilen başlık.",
|
||||
"subSupportUrl": "Destek URL'si",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "Her JSON abonelik akışına eklenen Xray finalmask maskeleri (TCP/UDP) ve QUIC ayarları. Güncel bir Xray istemcisi gerektirir.",
|
||||
"finalMaskDesc": "Xray finalmask TCP/UDP maskelerini ve QUIC parametrelerini oluşturulan her Xray JSON profiline ekler. Xray JSON aboneliklerini destekleyen bir istemci uygulaması ve güncel bir Xray çekirdeği gerektirir.",
|
||||
"packets": "Paketler",
|
||||
"length": "Uzunluk",
|
||||
"interval": "Aralık",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "Увімкнути/вимкнути JSON-кінець підписки незалежно.",
|
||||
"subJsonEnableTitle": "JSON-підписка",
|
||||
"subClashEnableTitle": "Підписка Clash / Mihomo",
|
||||
"subFormatsTipTitle": "Налаштування підписки для окремих форматів",
|
||||
"subFormatsTipDesc": "Окремо налаштуйте URL-шляхи, зворотні URL-адреси й автоматичне визначення клієнтів для JSON та Clash / Mihomo.",
|
||||
"subFormatsTipAction": "Відкрити формати підписки",
|
||||
"subJsonAutoDetect": "Автоматично визначати клієнтів Xray JSON",
|
||||
"subJsonAutoDetectDesc": "Якщо параметр увімкнено, розпізнані сумісні клієнти під час запиту стандартної URL-адреси підписки автоматично отримують масив конфігурацій Xray JSON. Інші клієнти отримують звичайну відповідь raw/Base64. Для застосування потрібно ввімкнути JSON-підписку та перезапустити панель.",
|
||||
"subJsonAlwaysArray": "Завжди повертати масив JSON",
|
||||
"subJsonAlwaysArrayDesc": "Повертає явну JSON-підписку як масив навіть для одного профілю відповідно до стандарту XTLS. Автоматично визначені JSON-відповіді завжди використовують масив. Вимкніть, щоб зберегти попередню відповідь одним об’єктом.",
|
||||
"subJsonUserAgentRegex": "Регулярний вираз User-Agent Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "Регулярний вираз Go RE2 для розпізнавання клієнтів підписки Xray JSON. Консервативний стандартний шаблон збігається лише зі Streisand; інші перевірені клієнти додаються явно. v2rayNG, v2rayN і v2rayA типово отримують raw-підписку. Залиште поле порожнім, щоб відновити стандартний шаблон. Після зміни перезапустіть панель.",
|
||||
"subClashAutoDetect": "Автоматично визначати клієнтів Clash/Mihomo",
|
||||
"subClashAutoDetectDesc": "Якщо параметр увімкнено, розпізнані клієнти Clash, Mihomo та Stash під час запиту стандартної URL-адреси підписки автоматично отримують Clash YAML. Браузери й надалі показують сторінку підписки, інші клієнти отримують звичайну відповідь raw/Base64, а явні URL-адреси JSON і Clash залишаються доступними. Для застосування потрібно ввімкнути підписку Clash/Mihomo та перезапустити панель.",
|
||||
"subClashUserAgentRegex": "Регулярний вираз User-Agent Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "Регулярний вираз Go RE2 для розпізнавання клієнтів Clash, Mihomo та Stash на стандартній URL-адресі підписки. Залиште поле порожнім для стандартного шаблону. Після зміни перезапустіть панель.",
|
||||
"subTitle": "Назва Підписки",
|
||||
"subTitleDesc": "Назва, яка відображається у VPN-клієнті",
|
||||
"subSupportUrl": "URL підтримки",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "Маски finalmask xray (TCP/UDP) і налаштування QUIC, що додаються до кожного потоку JSON-підписки. Потрібна свіжа версія xray на клієнті.",
|
||||
"finalMaskDesc": "Додає TCP/UDP-маски finalmask Xray і параметри QUIC до кожного створеного профілю Xray JSON. Потрібен застосунок із підтримкою підписок Xray JSON і нова версія ядра Xray.",
|
||||
"packets": "Пакети",
|
||||
"length": "Довжина",
|
||||
"interval": "Інтервал",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "Bật/Tắt điểm cuối đăng ký JSON độc lập.",
|
||||
"subJsonEnableTitle": "Đăng ký JSON",
|
||||
"subClashEnableTitle": "Đăng ký Clash / Mihomo",
|
||||
"subFormatsTipTitle": "Cài đặt đăng ký theo từng định dạng",
|
||||
"subFormatsTipDesc": "Cấu hình riêng đường dẫn URL, URL proxy ngược và tính năng tự động nhận diện ứng dụng cho JSON và Clash / Mihomo.",
|
||||
"subFormatsTipAction": "Mở định dạng đăng ký",
|
||||
"subJsonAutoDetect": "Tự động nhận diện ứng dụng Xray JSON",
|
||||
"subJsonAutoDetectDesc": "Khi bật, các ứng dụng tương thích được nhận diện khi yêu cầu URL đăng ký tiêu chuẩn sẽ tự động nhận một mảng cấu hình Xray JSON. Các ứng dụng khác tiếp tục nhận phản hồi thô/Base64. Cần bật đăng ký JSON và khởi động lại bảng điều khiển.",
|
||||
"subJsonAlwaysArray": "Luôn trả về mảng JSON",
|
||||
"subJsonAlwaysArrayDesc": "Trả endpoint đăng ký JSON tường minh dưới dạng mảng ngay cả khi chỉ có một hồ sơ, theo tiêu chuẩn XTLS. Phản hồi JSON được tự động nhận diện luôn dùng mảng. Tắt để giữ phản hồi một đối tượng kiểu cũ.",
|
||||
"subJsonUserAgentRegex": "Biểu thức User-Agent Xray JSON",
|
||||
"subJsonUserAgentRegexDesc": "Biểu thức chính quy Go RE2 để nhận diện ứng dụng đăng ký Xray JSON. Mẫu mặc định thận trọng chỉ khớp Streisand; hãy thêm rõ ràng các ứng dụng khác đã được xác minh. v2rayNG, v2rayN và v2rayA mặc định tiếp tục nhận đăng ký thô. Để trống để khôi phục mẫu mặc định. Khởi động lại bảng điều khiển sau khi thay đổi.",
|
||||
"subClashAutoDetect": "Tự động nhận diện ứng dụng Clash/Mihomo",
|
||||
"subClashAutoDetectDesc": "Khi bật, các ứng dụng Clash, Mihomo và Stash được nhận diện khi yêu cầu URL đăng ký tiêu chuẩn sẽ tự động nhận cấu hình Clash YAML. Trình duyệt vẫn hiển thị trang đăng ký, các ứng dụng khác tiếp tục nhận phản hồi thô/Base64, còn các URL JSON và Clash riêng vẫn khả dụng. Cần bật đăng ký Clash/Mihomo và khởi động lại bảng điều khiển để áp dụng thay đổi.",
|
||||
"subClashUserAgentRegex": "Biểu thức User-Agent Clash/Mihomo",
|
||||
"subClashUserAgentRegexDesc": "Biểu thức chính quy Go RE2 để nhận diện ứng dụng Clash, Mihomo và Stash trên URL đăng ký tiêu chuẩn. Để trống để dùng mẫu mặc định. Khởi động lại bảng điều khiển sau khi thay đổi.",
|
||||
"subTitle": "Tiêu đề Đăng ký",
|
||||
"subTitleDesc": "Tiêu đề hiển thị trong ứng dụng VPN",
|
||||
"subSupportUrl": "URL Hỗ trợ",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "Mask finalmask của xray (TCP/UDP) và tinh chỉnh QUIC được thêm vào mọi luồng đăng ký JSON. Yêu cầu client xray mới hơn.",
|
||||
"finalMaskDesc": "Thêm mask TCP/UDP của Xray finalmask và tham số QUIC vào mỗi hồ sơ Xray JSON được tạo. Yêu cầu ứng dụng hỗ trợ đăng ký Xray JSON và lõi Xray mới.",
|
||||
"packets": "Gói",
|
||||
"length": "Độ dài",
|
||||
"interval": "Khoảng",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "单独启用/禁用 JSON 订阅端点。",
|
||||
"subJsonEnableTitle": "JSON 订阅",
|
||||
"subClashEnableTitle": "Clash / Mihomo 订阅",
|
||||
"subFormatsTipTitle": "特定格式的订阅设置",
|
||||
"subFormatsTipDesc": "分别配置 JSON 和 Clash / Mihomo 的 URL 路径、反向代理 URL 和客户端自动识别。",
|
||||
"subFormatsTipAction": "打开订阅格式",
|
||||
"subJsonAutoDetect": "自动识别 Xray JSON 客户端",
|
||||
"subJsonAutoDetectDesc": "启用后,使用标准订阅 URL 的已识别兼容客户端将自动获得 Xray JSON 配置数组。其他客户端继续获得原始/Base64 响应。需要启用 JSON 订阅并重启面板才能生效。",
|
||||
"subJsonAlwaysArray": "始终返回 JSON 数组",
|
||||
"subJsonAlwaysArrayDesc": "即使只有一个配置,也将显式 JSON 订阅端点返回为数组,以符合 XTLS 订阅标准。自动识别的 JSON 响应始终使用数组。禁用时保留旧版单对象响应。",
|
||||
"subJsonUserAgentRegex": "Xray JSON User-Agent 正则表达式",
|
||||
"subJsonUserAgentRegexDesc": "用于识别 Xray JSON 订阅客户端的 Go RE2 正则表达式。保守的默认规则仅匹配 Streisand;请显式添加其他已验证客户端。v2rayNG、v2rayN 和 v2rayA 默认继续使用原始订阅。留空可恢复默认规则。更改后请重启面板。",
|
||||
"subClashAutoDetect": "自动识别 Clash/Mihomo 客户端",
|
||||
"subClashAutoDetectDesc": "启用后,使用标准订阅 URL 的已识别 Clash、Mihomo 和 Stash 客户端将自动获得 Clash YAML。浏览器仍显示订阅页面,其他客户端继续获得原始/Base64 响应,独立的 JSON 和 Clash URL 仍然可用。需要启用 Clash/Mihomo 订阅并重启面板才能生效。",
|
||||
"subClashUserAgentRegex": "Clash/Mihomo User-Agent 正则表达式",
|
||||
"subClashUserAgentRegexDesc": "用于在标准订阅 URL 上识别 Clash、Mihomo 和 Stash 客户端的 Go RE2 正则表达式。留空则使用默认规则。更改后请重启面板。",
|
||||
"subTitle": "订阅标题",
|
||||
"subTitleDesc": "在VPN客户端中显示的标题",
|
||||
"subSupportUrl": "支持链接",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "注入到每个 JSON 订阅流的 xray finalmask 掩码(TCP/UDP)和 QUIC 调优。需要较新的 xray 客户端。",
|
||||
"finalMaskDesc": "将 Xray finalmask TCP/UDP 掩码和 QUIC 参数注入每个生成的 Xray JSON 配置。需要支持 Xray JSON 订阅的客户端应用和较新的 Xray 核心。",
|
||||
"packets": "数据包",
|
||||
"length": "长度",
|
||||
"interval": "间隔",
|
||||
|
||||
@@ -1179,6 +1179,19 @@
|
||||
"subJsonEnable": "獨立啟用/停用 JSON 訂閱端點。",
|
||||
"subJsonEnableTitle": "JSON 訂閱",
|
||||
"subClashEnableTitle": "Clash / Mihomo 訂閱",
|
||||
"subFormatsTipTitle": "特定格式的訂閱設定",
|
||||
"subFormatsTipDesc": "分別設定 JSON 和 Clash / Mihomo 的 URL 路徑、反向代理 URL 與用戶端自動識別。",
|
||||
"subFormatsTipAction": "開啟訂閱格式",
|
||||
"subJsonAutoDetect": "自動識別 Xray JSON 用戶端",
|
||||
"subJsonAutoDetectDesc": "啟用後,使用標準訂閱 URL 的已識別相容用戶端將自動取得 Xray JSON 設定陣列。其他用戶端繼續取得原始/Base64 回應。需要啟用 JSON 訂閱並重新啟動面板才能生效。",
|
||||
"subJsonAlwaysArray": "始終回傳 JSON 陣列",
|
||||
"subJsonAlwaysArrayDesc": "即使只有一個設定,也將明確 JSON 訂閱端點回傳為陣列,以符合 XTLS 訂閱標準。自動識別的 JSON 回應始終使用陣列。停用時保留舊版單一物件回應。",
|
||||
"subJsonUserAgentRegex": "Xray JSON User-Agent 正規表示式",
|
||||
"subJsonUserAgentRegexDesc": "用於識別 Xray JSON 訂閱用戶端的 Go RE2 正規表示式。保守的預設規則僅匹配 Streisand;請明確加入其他已驗證的用戶端。v2rayNG、v2rayN 和 v2rayA 預設繼續使用原始訂閱。留空可還原預設規則。變更後請重新啟動面板。",
|
||||
"subClashAutoDetect": "自動識別 Clash/Mihomo 用戶端",
|
||||
"subClashAutoDetectDesc": "啟用後,使用標準訂閱 URL 的已識別 Clash、Mihomo 和 Stash 用戶端將自動取得 Clash YAML。瀏覽器仍會顯示訂閱頁面,其他用戶端繼續取得原始/Base64 回應,獨立的 JSON 和 Clash URL 仍可使用。需要啟用 Clash/Mihomo 訂閱並重新啟動面板才能生效。",
|
||||
"subClashUserAgentRegex": "Clash/Mihomo User-Agent 正規表示式",
|
||||
"subClashUserAgentRegexDesc": "用於在標準訂閱 URL 上識別 Clash、Mihomo 和 Stash 用戶端的 Go RE2 正規表示式。留空則使用預設規則。變更後請重新啟動面板。",
|
||||
"subTitle": "訂閱標題",
|
||||
"subTitleDesc": "在VPN客戶端中顯示的標題",
|
||||
"subSupportUrl": "支援連結",
|
||||
@@ -1269,7 +1282,7 @@
|
||||
},
|
||||
"subFormats": {
|
||||
"finalMask": "Final Mask",
|
||||
"finalMaskDesc": "注入到每個 JSON 訂閱串流的 xray finalmask 遮罩(TCP/UDP)與 QUIC 調校。需要較新的 xray 用戶端。",
|
||||
"finalMaskDesc": "將 Xray finalmask TCP/UDP 遮罩和 QUIC 參數注入每個產生的 Xray JSON 設定。需要支援 Xray JSON 訂閱的用戶端應用程式和較新的 Xray 核心。",
|
||||
"packets": "封包",
|
||||
"length": "長度",
|
||||
"interval": "間隔",
|
||||
|
||||
Reference in New Issue
Block a user