From e90ebdb6636dcd52b0fd0ae3489dc10189961a6f Mon Sep 17 00:00:00 2001 From: Mohammad Date: Wed, 13 May 2026 19:00:15 +0330 Subject: [PATCH 1/5] Refactor(proxy-settings): remove flow attribute - Removed the flow attribute from various models and settings related to VLESS and user templates. - Updated the subscription generation logic to utilize inbound flow directly. - Adjusted user and user template creation processes to eliminate flow handling. - Modified related tests to reflect the removal of flow settings. - Cleaned up unused imports and code related to flow management across multiple files. --- app/core/hosts.py | 2 +- app/db/crud/bulk.py | 9 -- app/db/crud/user.py | 14 +-- app/models/proxy.py | 1 - app/models/settings.py | 3 +- app/models/user.py | 3 +- app/models/user_template.py | 3 +- app/node/user.py | 6 +- app/operation/user.py | 6 +- app/subscription/clash.py | 3 +- app/subscription/links.py | 3 +- app/subscription/share.py | 5 -- app/subscription/singbox.py | 3 +- app/subscription/xray.py | 3 +- dashboard/src/components/bulk/bulk-flow.tsx | 87 ++++++++----------- .../src/components/dialogs/user-modal.tsx | 37 -------- .../dialogs/user-template-modal.tsx | 31 +------ dashboard/src/components/forms/user-form.ts | 3 - .../components/forms/user-template-form.ts | 4 +- .../src/pages/_dashboard.settings.general.tsx | 46 +--------- .../src/pages/_dashboard.templates.user.tsx | 1 - dashboard/src/service/api/index.ts | 16 ---- tests/api/conftest.py | 2 +- tests/api/helpers.py | 3 +- tests/api/test_bulk.py | 6 +- tests/api/test_user.py | 43 +++++++++ tests/api/test_user_template.py | 5 +- 27 files changed, 110 insertions(+), 238 deletions(-) diff --git a/app/core/hosts.py b/app/core/hosts.py index baa438812..9118d26de 100644 --- a/app/core/hosts.py +++ b/app/core/hosts.py @@ -167,7 +167,7 @@ async def _prepare_subscription_inbound_data( # Get VLESS encryption from inbound encryption = inbound_config.get("encryption", "none") - # Get flow from inbound (user can override later in share.py) + # Get flow from inbound for subscription generation. inbound_flow = inbound_config.get("flow", "") if inbound_flow == "none": inbound_flow = "" diff --git a/app/db/crud/bulk.py b/app/db/crud/bulk.py index 6c8922cfe..14feb9891 100644 --- a/app/db/crud/bulk.py +++ b/app/db/crud/bulk.py @@ -436,13 +436,6 @@ async def update_users_proxy_settings( # Prepare the update statement if dialect == "postgresql": proxy_settings_expr = cast(User.proxy_settings, JSONB) - if bulk_model.flow is not None: - proxy_settings_expr = func.jsonb_set( - proxy_settings_expr, - text("'{vless,flow}'"), - cast(f"{bulk_model.flow.value}", JSONB), - True, - ) if bulk_model.method is not None: proxy_settings_expr = func.jsonb_set( proxy_settings_expr, @@ -452,8 +445,6 @@ async def update_users_proxy_settings( ) else: proxy_settings_expr = User.proxy_settings - if bulk_model.flow is not None: - proxy_settings_expr = func.json_set(proxy_settings_expr, "$.vless.flow", f"{bulk_model.flow.value}") if bulk_model.method is not None: proxy_settings_expr = func.json_set( proxy_settings_expr, "$.shadowsocks.method", f"{bulk_model.method.value}" diff --git a/app/db/crud/user.py b/app/db/crud/user.py index 7acd88a65..8a6525ce4 100644 --- a/app/db/crud/user.py +++ b/app/db/crud/user.py @@ -790,7 +790,7 @@ async def create_user(db: AsyncSession, new_user: UserCreate, groups: list[Group db_user.groups = groups db_user.expire = new_user.expire or None db_user.on_hold_timeout = new_user.on_hold_timeout or None - db_user.proxy_settings = new_user.proxy_settings.dict() + db_user.proxy_settings = new_user.proxy_settings db.add(db_user) await db.flush() @@ -821,7 +821,7 @@ async def create_users_bulk( db_user.groups = list(groups) db_user.expire = new_user.expire or None db_user.on_hold_timeout = new_user.on_hold_timeout or None - db_user.proxy_settings = new_user.proxy_settings.dict() + db_user.proxy_settings = new_user.proxy_settings db_users.append(db_user) db.add_all(db_users) @@ -909,7 +909,7 @@ async def modify_user( remove_expiration_reminder = False if modify.proxy_settings is not None: - db_user.proxy_settings = modify.proxy_settings.dict() + db_user.proxy_settings = modify.proxy_settings if modify.group_ids: db_user.groups = groups or await get_groups_by_ids(db, modify.group_ids, load_users=False, load_inbounds=True) @@ -1062,12 +1062,11 @@ async def bulk_reset_user_data_usage( def _build_revoked_proxy_settings(db_user: User) -> dict: proxy_settings = ProxyTable() - proxy_settings.vless.flow = db_user.proxy_settings.get("vless", {}).get("flow", "") proxy_settings.shadowsocks.method = db_user.proxy_settings.get("shadowsocks", {}).get( "method", "chacha20-ietf-poly1305" ) proxy_settings.wireguard.peer_ips = db_user.proxy_settings.get("wireguard", {}).get("peer_ips", []) or [] - return proxy_settings.dict() + return proxy_settings async def reset_user_by_next(db: AsyncSession, db_user: User, *, clean_chart_data: bool = False) -> User: @@ -1110,11 +1109,6 @@ async def reset_user_by_next(db: AsyncSession, db_user: User, *, clean_chart_dat if db_user.next_plan.user_template.extra_settings: proxy_settings = deepcopy(db_user.proxy_settings) - proxy_settings["vless"]["flow"] = ( - db_user.next_plan.user_template.extra_settings["flow"] - if db_user.next_plan.user_template.extra_settings["flow"] - else "" - ) proxy_settings["shadowsocks"]["method"] = ( db_user.next_plan.user_template.extra_settings["method"] if db_user.next_plan.user_template.extra_settings["method"] diff --git a/app/models/proxy.py b/app/models/proxy.py index 941867aed..dacd8a5cd 100644 --- a/app/models/proxy.py +++ b/app/models/proxy.py @@ -21,7 +21,6 @@ class XTLSFlows(StrEnum): class VlessSettings(BaseModel): id: UUID = Field(default_factory=uuid4) - flow: XTLSFlows = XTLSFlows.NONE class TrojanSettings(BaseModel): diff --git a/app/models/settings.py b/app/models/settings.py index ba20305dd..469b9fa9d 100644 --- a/app/models/settings.py +++ b/app/models/settings.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator -from app.models.proxy import ShadowsocksMethods, XTLSFlows +from app.models.proxy import ShadowsocksMethods from .notification_enable import NotificationEnable from .validators import DiscordValidator, ProxyValidator, URLValidator @@ -286,7 +286,6 @@ def validate_recommended_apps(cls, v: list[Application]) -> list[Application]: class General(BaseModel): - default_flow: XTLSFlows = Field(default=XTLSFlows.NONE) default_method: ShadowsocksMethods = Field(default=ShadowsocksMethods.CHACHA20_POLY1305) diff --git a/app/models/user.py b/app/models/user.py index 7649d5ce6..df8837c6e 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -6,7 +6,7 @@ from app.db.models import DataLimitResetStrategy, UserStatus from app.models.admin import AdminBase, AdminContactInfo -from app.models.proxy import ProxyTable, ShadowsocksMethods, XTLSFlows +from app.models.proxy import ProxyTable, ShadowsocksMethods from app.models.stats import Period from app.utils.helpers import fix_datetime_timezone @@ -413,7 +413,6 @@ class BulkUser(BulkUserFilter): class BulkUsersProxy(BulkUserFilter): - flow: XTLSFlows | None = Field(default=None) method: ShadowsocksMethods | None = Field(default=None) diff --git a/app/models/user_template.py b/app/models/user_template.py index f6f6abdc4..5dcb3816c 100644 --- a/app/models/user_template.py +++ b/app/models/user_template.py @@ -4,13 +4,12 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator from app.db.models import DataLimitResetStrategy, UserStatusCreate -from app.models.proxy import ShadowsocksMethods, XTLSFlows +from app.models.proxy import ShadowsocksMethods from .validators import ListValidator, UserValidator class ExtraSettings(BaseModel): - flow: XTLSFlows | None = Field(XTLSFlows.NONE) method: ShadowsocksMethods | None = Field(ShadowsocksMethods.CHACHA20_POLY1305) def dict(self, *, no_obj=True, **kwargs): diff --git a/app/node/user.py b/app/node/user.py index 19fd7a105..1afffc596 100644 --- a/app/node/user.py +++ b/app/node/user.py @@ -57,11 +57,7 @@ def _serialize_user_for_node( if ProxyProtocol.vmess in allowed_protocols: proxy_kwargs["vmess_id"] = user_settings.get("vmess", {}).get("id") if ProxyProtocol.vless in allowed_protocols: - vless_settings = dict(user_settings.get("vless", {})) - if vless_settings.get("flow") == "xtls-rprx-vision-udp443": - vless_settings["flow"] = "xtls-rprx-vision" - proxy_kwargs["vless_id"] = vless_settings.get("id") - proxy_kwargs["vless_flow"] = vless_settings.get("flow") + proxy_kwargs["vless_id"] = user_settings.get("vless", {}).get("id") if ProxyProtocol.trojan in allowed_protocols: proxy_kwargs["trojan_password"] = user_settings.get("trojan", {}).get("password") if ProxyProtocol.shadowsocks in allowed_protocols: diff --git a/app/operation/user.py b/app/operation/user.py index 2a7813dff..ce1cbbd06 100644 --- a/app/operation/user.py +++ b/app/operation/user.py @@ -945,12 +945,8 @@ def load_base_user_args(template: UserTemplate) -> dict: @staticmethod def apply_settings(user_args: UserCreate | UserModify, template: UserTemplate) -> dict: if template.extra_settings: - flow = template.extra_settings.get("flow", None) method = template.extra_settings.get("method", None) - if flow is not None: - user_args.proxy_settings.vless.flow = flow - if method is not None: user_args.proxy_settings.shadowsocks.method = method @@ -1167,6 +1163,8 @@ async def bulk_modify_datalimit(self, db: AsyncSession, bulk_model: BulkUser): return users_count async def bulk_modify_proxy_settings(self, db: AsyncSession, bulk_model: BulkUsersProxy): + if bulk_model.method is None: + await self.raise_error(message="No supported proxy settings were provided", code=400, db=db) if bulk_model.dry_run: n = await count_bulk_proxy_targets(db, bulk_model) return BulkOperationDryRunResponse(affected_users=n) diff --git a/app/subscription/clash.py b/app/subscription/clash.py index 2e77fa943..9e534682e 100644 --- a/app/subscription/clash.py +++ b/app/subscription/clash.py @@ -432,8 +432,7 @@ def _build_vless(self, remark: str, address: str, inbound: SubscriptionInboundDa if inbound.encryption != "none": node["encryption"] = inbound.encryption - # Only add flow if inbound supports it - if inbound.flow_enabled and (flow := settings.get("flow", "")): + if inbound.flow_enabled and (flow := inbound.inbound_flow): node["flow"] = flow self._apply_tls(node, inbound.tls_config, "vless") diff --git a/app/subscription/links.py b/app/subscription/links.py index ff9f9b236..e4032c2b4 100644 --- a/app/subscription/links.py +++ b/app/subscription/links.py @@ -268,8 +268,7 @@ def _build_vless(self, remark: str, address: str, inbound: SubscriptionInboundDa "headerType": getattr(inbound.transport_config, "header_type", "none"), } - # Only add flow if inbound supports it - if inbound.flow_enabled and (flow := settings.get("flow", "")): + if inbound.flow_enabled and (flow := inbound.inbound_flow): payload["flow"] = flow self._apply_transport_settings(payload, "vless", inbound, path) diff --git a/app/subscription/share.py b/app/subscription/share.py index 4aff4a9c9..2768e02c2 100644 --- a/app/subscription/share.py +++ b/app/subscription/share.py @@ -233,11 +233,6 @@ async def process_host( if user_id is not None: settings["_user_id"] = user_id - # Handle flow: user settings have priority, fall back to inbound flow - if "flow" in settings and settings["flow"] == "": - # User has empty flow, use inbound flow as default - settings["flow"] = inbound.inbound_flow - # Update format variables format_variables.update({"PROTOCOL": inbound.protocol}) format_variables.update({"TRANSPORT": inbound.network}) diff --git a/app/subscription/singbox.py b/app/subscription/singbox.py index d16ebe6b7..8181d4c85 100644 --- a/app/subscription/singbox.py +++ b/app/subscription/singbox.py @@ -273,8 +273,7 @@ def _build_vless(self, remark: str, address: str, inbound: SubscriptionInboundDa id = self.vless_route(id, inbound.vless_route) user_settings = {"uuid": id} - # Only add flow if inbound supports it - if inbound.flow_enabled and (flow := settings.get("flow", "")): + if inbound.flow_enabled and (flow := inbound.inbound_flow): user_settings["flow"] = flow return self._build_outbound( diff --git a/app/subscription/xray.py b/app/subscription/xray.py index 9432780e3..5934e1cb7 100644 --- a/app/subscription/xray.py +++ b/app/subscription/xray.py @@ -419,8 +419,7 @@ def _build_vless(self, address: str, inbound: SubscriptionInboundData, settings: user_settings = {"id": id, "encryption": inbound.encryption} - # Only add flow if inbound supports it - if inbound.flow_enabled and (flow := settings.get("flow", "")): + if inbound.flow_enabled and (flow := inbound.inbound_flow): user_settings["flow"] = flow return self._build_outbound( diff --git a/dashboard/src/components/bulk/bulk-flow.tsx b/dashboard/src/components/bulk/bulk-flow.tsx index 149585335..0e18d7463 100644 --- a/dashboard/src/components/bulk/bulk-flow.tsx +++ b/dashboard/src/components/bulk/bulk-flow.tsx @@ -11,7 +11,6 @@ import { useBulkAddGroupsToUsers, useBulkRemoveUsersFromGroups, useBulkReallocateWireguardPeerIps, - XTLSFlows, ShadowsocksMethods, UserStatus, } from '@/service/api' @@ -27,7 +26,24 @@ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@/components/ui/command' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' -import { Settings, Group, User, Shield, CheckCircle, AlertTriangle, Plus, Minus, X, HardDrive, Calendar, Network, CheckCircle2, ChevronLeft, ChevronRight, Eye, Loader2 } from 'lucide-react' +import { + Settings, + Group, + User, + Shield, + CheckCircle, + AlertTriangle, + Plus, + Minus, + X, + HardDrive, + Calendar, + CheckCircle2, + ChevronLeft, + ChevronRight, + Eye, + Loader2, +} from 'lucide-react' import { BulkExpiredDateFilters } from '@/components/bulk/bulk-expired-date-filters' import { DecimalInput } from '@/components/common/decimal-input' import { SelectorPanel } from '@/components/bulk/selector-panel' @@ -57,7 +73,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1) - const [selectedFlow, setSelectedFlow] = useState(undefined) const [selectedMethod, setSelectedMethod] = useState(undefined) const [dataLimit, setDataLimit] = useState(undefined) @@ -157,7 +172,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { return true } if (operationType === 'proxy') { - return selectedFlow || selectedMethod + return selectedMethod } if (operationType === 'groups') { return selectedGroups.length > 0 @@ -172,7 +187,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { case 2: switch (operationType) { case 'proxy': - return selectedFlow || selectedMethod + return selectedMethod case 'data': return dataLimit !== undefined case 'expire': @@ -246,7 +261,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { case 'proxy': return { ...basePayload, - flow: selectedFlow === 'none' ? ('' as XTLSFlows) : selectedFlow, method: selectedMethod, dry_run: false, } @@ -348,7 +362,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { toast.success(t('operationSuccess', { defaultValue: 'Operation successful!' }), { description }) setCurrentStep(1) - setSelectedFlow(undefined) setSelectedMethod(undefined) setDataLimit(undefined) setExpireSeconds(undefined) @@ -386,7 +399,6 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { case 'proxy': return { ...basePayload, - flow: selectedFlow === 'none' ? ('' as XTLSFlows) : selectedFlow, method: selectedMethod, dry_run: true, } @@ -541,46 +553,23 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
{operationType === 'proxy' && (
-
-
- - -
-
- - -
+
+ +
)} @@ -1019,7 +1008,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { {operationType === 'proxy' && (
{t('bulk.settings', { defaultValue: 'Settings' })}: - {t('bulk.flowMethod', { flow: selectedFlow === 'none' || !selectedFlow ? t('none') : selectedFlow, method: selectedMethod || t('none') })} + {selectedMethod || t('none')}
)} diff --git a/dashboard/src/components/dialogs/user-modal.tsx b/dashboard/src/components/dialogs/user-modal.tsx index 29eb7ae64..89d8904cc 100644 --- a/dashboard/src/components/dialogs/user-modal.tsx +++ b/dashboard/src/components/dialogs/user-modal.tsx @@ -1119,7 +1119,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI }, vless: { id: uuidv4(), - flow: '' as '' | 'xtls-rprx-vision' | 'xtls-rprx-vision-udp443' | undefined, }, trojan: { password: generatePassword(), @@ -1212,9 +1211,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI {} as Record, ) - if (protocol === 'vless' && !cleanedProtocolSettings.flow) { - delete cleanedProtocolSettings.flow - } if (protocol === 'shadowsocks' && !cleanedProtocolSettings.method) { delete cleanedProtocolSettings.method } @@ -1355,7 +1351,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI form.setValue('proxy_settings', undefined) form.setValue('data_limit', 0) if (generalSettings) { - form.setValue('proxy_settings.vless.flow', generalSettings.default_flow || '') const validMethods = ['aes-128-gcm', 'aes-256-gcm', 'chacha20-ietf-poly1305', 'xchacha20-poly1305'] as const const method = validMethods.find(m => m === generalSettings.default_method) if (method) { @@ -1789,38 +1784,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI ) }} /> - ( - - - {t('userDialog.proxySettings.vless')} {t('userDialog.proxySettings.flow')} - - - - -

{t('userDialog.proxySettings.flowDeprecated')}

- -
- )} - /> {/* Trojan */} - ( - - {t('templates.flow')} - - - - )} - /> } />
diff --git a/dashboard/src/components/forms/user-form.ts b/dashboard/src/components/forms/user-form.ts index 715d8f9aa..c67caef5b 100644 --- a/dashboard/src/components/forms/user-form.ts +++ b/dashboard/src/components/forms/user-form.ts @@ -3,7 +3,6 @@ import { z } from 'zod' export const userStatusEnum = z.enum(['active', 'disabled', 'limited', 'expired', 'on_hold']) export const userDataLimitResetStrategyEnum = z.enum(['no_reset', 'day', 'week', 'month', 'year']) -export const xtlsFlowsEnum = z.enum(['', 'xtls-rprx-vision', 'xtls-rprx-vision-udp443']) export const shadowsocksMethodsEnum = z.enum(['aes-128-gcm', 'aes-256-gcm', 'chacha20-ietf-poly1305', 'xchacha20-poly1305']) export const vMessSettingsSchema = z.object({ @@ -11,7 +10,6 @@ export const vMessSettingsSchema = z.object({ }) export const vlessSettingsSchema = z.object({ id: z.string().uuid().optional(), - flow: xtlsFlowsEnum.optional(), }) export const trojanSettingsSchema = z.object({ password: z.string().min(2).max(32).optional(), @@ -108,7 +106,6 @@ export const getDefaultUserForm = async () => { }, vless: { id: undefined, - flow: '', }, trojan: { password: undefined, diff --git a/dashboard/src/components/forms/user-template-form.ts b/dashboard/src/components/forms/user-template-form.ts index 8b2c80675..a8ef6652d 100644 --- a/dashboard/src/components/forms/user-template-form.ts +++ b/dashboard/src/components/forms/user-template-form.ts @@ -1,4 +1,4 @@ -import { DataLimitResetStrategy, ShadowsocksMethods, UserStatusCreate, XTLSFlows } from '@/service/api' +import { DataLimitResetStrategy, ShadowsocksMethods, UserStatusCreate } from '@/service/api' import { zodResolver } from '@hookform/resolvers/zod' import type { TFunction } from 'i18next' import type { FieldError, FieldErrors, Resolver } from 'react-hook-form' @@ -15,7 +15,6 @@ const userTemplateFormObjectSchema = z.object({ method: z .enum([ShadowsocksMethods['aes-128-gcm'], ShadowsocksMethods['aes-256-gcm'], ShadowsocksMethods['chacha20-ietf-poly1305'], ShadowsocksMethods['xchacha20-poly1305']]) .default(ShadowsocksMethods['chacha20-ietf-poly1305']), - flow: z.enum([XTLSFlows[''], XTLSFlows['xtls-rprx-vision'], XTLSFlows['xtls-rprx-vision-udp443']]).default(XTLSFlows['']), groups: z.array(z.number()).min(1, 'validation.required'), data_limit_reset_strategy: z .enum([ @@ -55,7 +54,6 @@ export const userTemplateFormDefaultValues: Partial data_limit: 0, expire_duration: 0, method: ShadowsocksMethods['chacha20-ietf-poly1305'], - flow: XTLSFlows[''], on_hold_timeout: undefined, groups: [], reset_usages: false, diff --git a/dashboard/src/pages/_dashboard.settings.general.tsx b/dashboard/src/pages/_dashboard.settings.general.tsx index b05d7fb9f..3fe88ba84 100644 --- a/dashboard/src/pages/_dashboard.settings.general.tsx +++ b/dashboard/src/pages/_dashboard.settings.general.tsx @@ -6,7 +6,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Separator } from '@/components/ui/separator' import { Skeleton } from '@/components/ui/skeleton' import { DEFAULT_SHADOWSOCKS_METHOD } from '@/constants/Proxies' -import { ShadowsocksMethods, XTLSFlows, useGetGeneralSettings, useReconnectAllNode } from '@/service/api' +import { ShadowsocksMethods, useGetGeneralSettings, useReconnectAllNode } from '@/service/api' import { queryClient } from '@/utils/query-client' import { zodResolver } from '@hookform/resolvers/zod' import { Loader2, RefreshCcw } from 'lucide-react' @@ -17,12 +17,8 @@ import { toast } from 'sonner' import { z } from 'zod' import { useSettingsContext } from './_dashboard.settings' -/** Radix Select forbids `SelectItem value=""`; map API empty flow to this UI value. */ -const DEFAULT_FLOW_SELECT_NONE = '__pg_default_flow_none__' - // general settings validation schema const generalSettingsSchema = z.object({ - default_flow: z.string().default(''), default_method: z.string().default(''), }) @@ -43,28 +39,24 @@ export default function General() { () => generalSettings ? { - default_flow: generalSettings.default_flow || '', default_method: generalSettings.default_method || DEFAULT_SHADOWSOCKS_METHOD, } : { - default_flow: '', default_method: '', }, - [generalSettings?.default_flow, generalSettings?.default_method], + [generalSettings?.default_method], ) const form = useForm({ resolver: zodResolver(generalSettingsSchema), values: generalFormValues, }) - const onSubmit = async (data: GeneralSettingsFormInput) => { try { // Filter out empty values and prepare the payload const filteredData: any = { general: { ...data, - default_flow: data.default_flow || undefined, default_method: data.default_method || DEFAULT_SHADOWSOCKS_METHOD, }, } @@ -78,7 +70,6 @@ export default function General() { const handleCancel = () => { if (!generalSettings) return form.reset({ - default_flow: generalSettings.default_flow ?? '', default_method: generalSettings.default_method || DEFAULT_SHADOWSOCKS_METHOD, }) toast.success(t('settings.general.cancelSuccess')) @@ -171,39 +162,6 @@ export default function General() {
{/* General Settings */}
- ( - - {t('settings.general.defaultFlow.title')} - - - - {t('settings.general.defaultFlow.description')} - - - )} - /> Date: Wed, 13 May 2026 19:08:44 +0330 Subject: [PATCH 2/5] remove(proxy): remove unused XTLSFlows enum --- app/models/proxy.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/models/proxy.py b/app/models/proxy.py index dacd8a5cd..79a770b18 100644 --- a/app/models/proxy.py +++ b/app/models/proxy.py @@ -13,12 +13,6 @@ class VMessSettings(BaseModel): id: UUID = Field(default_factory=uuid4) -class XTLSFlows(StrEnum): - NONE = "" - VISION = "xtls-rprx-vision" - VISION_UDP = "xtls-rprx-vision-udp443" - - class VlessSettings(BaseModel): id: UUID = Field(default_factory=uuid4) From 0b74e80404fa6e010a3b53361a6b887e8a4d7ee1 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Wed, 13 May 2026 21:14:09 +0330 Subject: [PATCH 3/5] fix: convert settings to json --- app/db/crud/user.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/db/crud/user.py b/app/db/crud/user.py index 8a6525ce4..e1356ec31 100644 --- a/app/db/crud/user.py +++ b/app/db/crud/user.py @@ -790,7 +790,7 @@ async def create_user(db: AsyncSession, new_user: UserCreate, groups: list[Group db_user.groups = groups db_user.expire = new_user.expire or None db_user.on_hold_timeout = new_user.on_hold_timeout or None - db_user.proxy_settings = new_user.proxy_settings + db_user.proxy_settings = new_user.proxy_settings.dict() db.add(db_user) await db.flush() @@ -821,7 +821,7 @@ async def create_users_bulk( db_user.groups = list(groups) db_user.expire = new_user.expire or None db_user.on_hold_timeout = new_user.on_hold_timeout or None - db_user.proxy_settings = new_user.proxy_settings + db_user.proxy_settings = new_user.proxy_settings.dict() db_users.append(db_user) db.add_all(db_users) @@ -909,7 +909,7 @@ async def modify_user( remove_expiration_reminder = False if modify.proxy_settings is not None: - db_user.proxy_settings = modify.proxy_settings + db_user.proxy_settings = modify.proxy_settings.dict() if modify.group_ids: db_user.groups = groups or await get_groups_by_ids(db, modify.group_ids, load_users=False, load_inbounds=True) @@ -1066,7 +1066,7 @@ def _build_revoked_proxy_settings(db_user: User) -> dict: "method", "chacha20-ietf-poly1305" ) proxy_settings.wireguard.peer_ips = db_user.proxy_settings.get("wireguard", {}).get("peer_ips", []) or [] - return proxy_settings + return proxy_settings.dict() async def reset_user_by_next(db: AsyncSession, db_user: User, *, clean_chart_data: bool = False) -> User: From 346d2283c39deca1b4728255a53f86398e8c2dd0 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Fri, 15 May 2026 16:19:01 +0330 Subject: [PATCH 4/5] revert(frontend): remove dashboard changes from e90ebdb6 --- dashboard/src/components/bulk/bulk-flow.tsx | 87 +++++++++++-------- .../src/components/dialogs/user-modal.tsx | 37 ++++++++ .../dialogs/user-template-modal.tsx | 31 ++++++- dashboard/src/components/forms/user-form.ts | 3 + .../components/forms/user-template-form.ts | 4 +- .../src/pages/_dashboard.settings.general.tsx | 46 +++++++++- .../src/pages/_dashboard.templates.user.tsx | 1 + dashboard/src/service/api/index.ts | 16 ++++ 8 files changed, 181 insertions(+), 44 deletions(-) diff --git a/dashboard/src/components/bulk/bulk-flow.tsx b/dashboard/src/components/bulk/bulk-flow.tsx index 0e18d7463..149585335 100644 --- a/dashboard/src/components/bulk/bulk-flow.tsx +++ b/dashboard/src/components/bulk/bulk-flow.tsx @@ -11,6 +11,7 @@ import { useBulkAddGroupsToUsers, useBulkRemoveUsersFromGroups, useBulkReallocateWireguardPeerIps, + XTLSFlows, ShadowsocksMethods, UserStatus, } from '@/service/api' @@ -26,24 +27,7 @@ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@/components/ui/command' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' -import { - Settings, - Group, - User, - Shield, - CheckCircle, - AlertTriangle, - Plus, - Minus, - X, - HardDrive, - Calendar, - CheckCircle2, - ChevronLeft, - ChevronRight, - Eye, - Loader2, -} from 'lucide-react' +import { Settings, Group, User, Shield, CheckCircle, AlertTriangle, Plus, Minus, X, HardDrive, Calendar, Network, CheckCircle2, ChevronLeft, ChevronRight, Eye, Loader2 } from 'lucide-react' import { BulkExpiredDateFilters } from '@/components/bulk/bulk-expired-date-filters' import { DecimalInput } from '@/components/common/decimal-input' import { SelectorPanel } from '@/components/bulk/selector-panel' @@ -73,6 +57,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1) + const [selectedFlow, setSelectedFlow] = useState(undefined) const [selectedMethod, setSelectedMethod] = useState(undefined) const [dataLimit, setDataLimit] = useState(undefined) @@ -172,7 +157,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { return true } if (operationType === 'proxy') { - return selectedMethod + return selectedFlow || selectedMethod } if (operationType === 'groups') { return selectedGroups.length > 0 @@ -187,7 +172,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { case 2: switch (operationType) { case 'proxy': - return selectedMethod + return selectedFlow || selectedMethod case 'data': return dataLimit !== undefined case 'expire': @@ -261,6 +246,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { case 'proxy': return { ...basePayload, + flow: selectedFlow === 'none' ? ('' as XTLSFlows) : selectedFlow, method: selectedMethod, dry_run: false, } @@ -362,6 +348,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { toast.success(t('operationSuccess', { defaultValue: 'Operation successful!' }), { description }) setCurrentStep(1) + setSelectedFlow(undefined) setSelectedMethod(undefined) setDataLimit(undefined) setExpireSeconds(undefined) @@ -399,6 +386,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { case 'proxy': return { ...basePayload, + flow: selectedFlow === 'none' ? ('' as XTLSFlows) : selectedFlow, method: selectedMethod, dry_run: true, } @@ -553,23 +541,46 @@ export default function BulkFlow({ operationType }: BulkFlowProps) {
{operationType === 'proxy' && (
-
- - +
+
+ + +
+
+ + +
)} @@ -1008,7 +1019,7 @@ export default function BulkFlow({ operationType }: BulkFlowProps) { {operationType === 'proxy' && (
{t('bulk.settings', { defaultValue: 'Settings' })}: - {selectedMethod || t('none')} + {t('bulk.flowMethod', { flow: selectedFlow === 'none' || !selectedFlow ? t('none') : selectedFlow, method: selectedMethod || t('none') })}
)} diff --git a/dashboard/src/components/dialogs/user-modal.tsx b/dashboard/src/components/dialogs/user-modal.tsx index 89d8904cc..29eb7ae64 100644 --- a/dashboard/src/components/dialogs/user-modal.tsx +++ b/dashboard/src/components/dialogs/user-modal.tsx @@ -1119,6 +1119,7 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI }, vless: { id: uuidv4(), + flow: '' as '' | 'xtls-rprx-vision' | 'xtls-rprx-vision-udp443' | undefined, }, trojan: { password: generatePassword(), @@ -1211,6 +1212,9 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI {} as Record, ) + if (protocol === 'vless' && !cleanedProtocolSettings.flow) { + delete cleanedProtocolSettings.flow + } if (protocol === 'shadowsocks' && !cleanedProtocolSettings.method) { delete cleanedProtocolSettings.method } @@ -1351,6 +1355,7 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI form.setValue('proxy_settings', undefined) form.setValue('data_limit', 0) if (generalSettings) { + form.setValue('proxy_settings.vless.flow', generalSettings.default_flow || '') const validMethods = ['aes-128-gcm', 'aes-256-gcm', 'chacha20-ietf-poly1305', 'xchacha20-poly1305'] as const const method = validMethods.find(m => m === generalSettings.default_method) if (method) { @@ -1784,6 +1789,38 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI ) }} /> + ( + + + {t('userDialog.proxySettings.vless')} {t('userDialog.proxySettings.flow')} + + + + +

{t('userDialog.proxySettings.flowDeprecated')}

+ +
+ )} + /> {/* Trojan */} + ( + + {t('templates.flow')} + + + + )} + /> } />
diff --git a/dashboard/src/components/forms/user-form.ts b/dashboard/src/components/forms/user-form.ts index c67caef5b..715d8f9aa 100644 --- a/dashboard/src/components/forms/user-form.ts +++ b/dashboard/src/components/forms/user-form.ts @@ -3,6 +3,7 @@ import { z } from 'zod' export const userStatusEnum = z.enum(['active', 'disabled', 'limited', 'expired', 'on_hold']) export const userDataLimitResetStrategyEnum = z.enum(['no_reset', 'day', 'week', 'month', 'year']) +export const xtlsFlowsEnum = z.enum(['', 'xtls-rprx-vision', 'xtls-rprx-vision-udp443']) export const shadowsocksMethodsEnum = z.enum(['aes-128-gcm', 'aes-256-gcm', 'chacha20-ietf-poly1305', 'xchacha20-poly1305']) export const vMessSettingsSchema = z.object({ @@ -10,6 +11,7 @@ export const vMessSettingsSchema = z.object({ }) export const vlessSettingsSchema = z.object({ id: z.string().uuid().optional(), + flow: xtlsFlowsEnum.optional(), }) export const trojanSettingsSchema = z.object({ password: z.string().min(2).max(32).optional(), @@ -106,6 +108,7 @@ export const getDefaultUserForm = async () => { }, vless: { id: undefined, + flow: '', }, trojan: { password: undefined, diff --git a/dashboard/src/components/forms/user-template-form.ts b/dashboard/src/components/forms/user-template-form.ts index a8ef6652d..8b2c80675 100644 --- a/dashboard/src/components/forms/user-template-form.ts +++ b/dashboard/src/components/forms/user-template-form.ts @@ -1,4 +1,4 @@ -import { DataLimitResetStrategy, ShadowsocksMethods, UserStatusCreate } from '@/service/api' +import { DataLimitResetStrategy, ShadowsocksMethods, UserStatusCreate, XTLSFlows } from '@/service/api' import { zodResolver } from '@hookform/resolvers/zod' import type { TFunction } from 'i18next' import type { FieldError, FieldErrors, Resolver } from 'react-hook-form' @@ -15,6 +15,7 @@ const userTemplateFormObjectSchema = z.object({ method: z .enum([ShadowsocksMethods['aes-128-gcm'], ShadowsocksMethods['aes-256-gcm'], ShadowsocksMethods['chacha20-ietf-poly1305'], ShadowsocksMethods['xchacha20-poly1305']]) .default(ShadowsocksMethods['chacha20-ietf-poly1305']), + flow: z.enum([XTLSFlows[''], XTLSFlows['xtls-rprx-vision'], XTLSFlows['xtls-rprx-vision-udp443']]).default(XTLSFlows['']), groups: z.array(z.number()).min(1, 'validation.required'), data_limit_reset_strategy: z .enum([ @@ -54,6 +55,7 @@ export const userTemplateFormDefaultValues: Partial data_limit: 0, expire_duration: 0, method: ShadowsocksMethods['chacha20-ietf-poly1305'], + flow: XTLSFlows[''], on_hold_timeout: undefined, groups: [], reset_usages: false, diff --git a/dashboard/src/pages/_dashboard.settings.general.tsx b/dashboard/src/pages/_dashboard.settings.general.tsx index 3fe88ba84..b05d7fb9f 100644 --- a/dashboard/src/pages/_dashboard.settings.general.tsx +++ b/dashboard/src/pages/_dashboard.settings.general.tsx @@ -6,7 +6,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Separator } from '@/components/ui/separator' import { Skeleton } from '@/components/ui/skeleton' import { DEFAULT_SHADOWSOCKS_METHOD } from '@/constants/Proxies' -import { ShadowsocksMethods, useGetGeneralSettings, useReconnectAllNode } from '@/service/api' +import { ShadowsocksMethods, XTLSFlows, useGetGeneralSettings, useReconnectAllNode } from '@/service/api' import { queryClient } from '@/utils/query-client' import { zodResolver } from '@hookform/resolvers/zod' import { Loader2, RefreshCcw } from 'lucide-react' @@ -17,8 +17,12 @@ import { toast } from 'sonner' import { z } from 'zod' import { useSettingsContext } from './_dashboard.settings' +/** Radix Select forbids `SelectItem value=""`; map API empty flow to this UI value. */ +const DEFAULT_FLOW_SELECT_NONE = '__pg_default_flow_none__' + // general settings validation schema const generalSettingsSchema = z.object({ + default_flow: z.string().default(''), default_method: z.string().default(''), }) @@ -39,24 +43,28 @@ export default function General() { () => generalSettings ? { + default_flow: generalSettings.default_flow || '', default_method: generalSettings.default_method || DEFAULT_SHADOWSOCKS_METHOD, } : { + default_flow: '', default_method: '', }, - [generalSettings?.default_method], + [generalSettings?.default_flow, generalSettings?.default_method], ) const form = useForm({ resolver: zodResolver(generalSettingsSchema), values: generalFormValues, }) + const onSubmit = async (data: GeneralSettingsFormInput) => { try { // Filter out empty values and prepare the payload const filteredData: any = { general: { ...data, + default_flow: data.default_flow || undefined, default_method: data.default_method || DEFAULT_SHADOWSOCKS_METHOD, }, } @@ -70,6 +78,7 @@ export default function General() { const handleCancel = () => { if (!generalSettings) return form.reset({ + default_flow: generalSettings.default_flow ?? '', default_method: generalSettings.default_method || DEFAULT_SHADOWSOCKS_METHOD, }) toast.success(t('settings.general.cancelSuccess')) @@ -162,6 +171,39 @@ export default function General() {
{/* General Settings */}
+ ( + + {t('settings.general.defaultFlow.title')} + + + + {t('settings.general.defaultFlow.description')} + + + )} + /> Date: Fri, 15 May 2026 19:08:05 +0330 Subject: [PATCH 5/5] feat(dashboard): remove deprecated VLESS flow settings - Remove default VLESS flow configuration from general settings UI - Remove flow field from VLESS proxy settings in user modal - Remove flow-related localization strings from all language files (en, fa, ru, zh) - Remove flow deprecation warning messages - Simplify VLESS protocol settings initialization by removing flow property - Remove flow cleanup logic from protocol settings validation The VLESS flow setting has been deprecated in favor of inbound.settings.flow and is being removed from the user-facing dashboard interface. --- dashboard/public/statics/locales/en.json | 7 --- dashboard/public/statics/locales/fa.json | 7 --- dashboard/public/statics/locales/ru.json | 7 --- dashboard/public/statics/locales/zh.json | 7 --- .../src/features/users/dialogs/user-modal.tsx | 37 --------------- .../src/features/users/forms/user-form.ts | 3 -- .../src/pages/_dashboard.settings.general.tsx | 45 +------------------ 7 files changed, 2 insertions(+), 111 deletions(-) diff --git a/dashboard/public/statics/locales/en.json b/dashboard/public/statics/locales/en.json index cb887766b..1d2e96c4e 100644 --- a/dashboard/public/statics/locales/en.json +++ b/dashboard/public/statics/locales/en.json @@ -140,11 +140,6 @@ "saveSuccess": "General settings saved successfully", "saveFailed": "Failed to save general settings", "cancelSuccess": "Changes cancelled and original settings restored", - "defaultFlow": { - "title": "Default VLESS Flow", - "description": "Autofill the flow field for new VLESS users", - "none": "None" - }, "defaultMethod": { "title": "Default Shadowsocks Method", "description": "Autofill the encryption method for new Shadowsocks users" @@ -1423,8 +1418,6 @@ "proxySettings.id": "ID", "proxySettings.password": "Password", "proxySettings.method": "Method", - "proxySettings.flow": "Flow", - "proxySettings.flowDeprecated": "Deprecated. This will be removed in future versions. Use inbound.settings.flow", "proxySettings.hysteria": "Hysteria", "proxySettings.hysteriaAuth": "Hysteria Auth", "proxySettings.wireguardPrivateKey": "WireGuard Private key", diff --git a/dashboard/public/statics/locales/fa.json b/dashboard/public/statics/locales/fa.json index 22634902d..8fdf973d5 100644 --- a/dashboard/public/statics/locales/fa.json +++ b/dashboard/public/statics/locales/fa.json @@ -417,11 +417,6 @@ "saveSuccess": "تنظیمات عمومی با موفقیت ذخیره شد", "saveFailed": "ذخیره تنظیمات عمومی ناموفق بود", "cancelSuccess": "تغییرات لغو شد و تنظیمات اصلی بازیابی شد", - "defaultFlow": { - "title": "جریان VLESS پیش‌فرض", - "description": "پر کردن خودکار فیلد جریان برای کاربران جدید VLESS", - "none": "بدون" - }, "defaultMethod": { "title": "روش پیش‌فرض Shadowsocks", "description": "پر کردن خودکار روش رمزنگاری برای کاربران جدید Shadowsocks" @@ -1260,8 +1255,6 @@ "userDialog.proxySettings.id": "شناسه (ID)", "userDialog.proxySettings.password": "رمز عبور", "userDialog.proxySettings.method": "روش رمزنگاری", - "userDialog.proxySettings.flow": "جریان (Flow)", - "userDialog.proxySettings.flowDeprecated": "این مورد منسوخ شده است و در نسخه‌های بعدی حذف می‌شود. به‌جای آن از inbound.settings.flow استفاده کنید", "userDialog.proxySettings.hysteria": "Hysteria", "userDialog.proxySettings.hysteriaAuth": "احراز هویت Hysteria", "userDialog.proxySettings.wireguardPrivateKey": "کلید خصوصی وایرگارد", diff --git a/dashboard/public/statics/locales/ru.json b/dashboard/public/statics/locales/ru.json index 2aa207312..5d0ccb848 100644 --- a/dashboard/public/statics/locales/ru.json +++ b/dashboard/public/statics/locales/ru.json @@ -549,11 +549,6 @@ "saveSuccess": "Общие настройки успешно сохранены", "saveFailed": "Не удалось сохранить общие настройки", "cancelSuccess": "Изменения отменены и исходные настройки восстановлены", - "defaultFlow": { - "title": "Поток VLESS по умолчанию", - "description": "Автоматически заполнять поле потока для новых пользователей VLESS", - "none": "Нет" - }, "defaultMethod": { "title": "Метод Shadowsocks по умолчанию", "description": "Автоматически заполнять метод шифрования для новых пользователей Shadowsocks" @@ -2358,8 +2353,6 @@ "proxySettings.id": "ID", "proxySettings.password": "Пароль", "proxySettings.method": "Метод", - "proxySettings.flow": "Поток (Flow)", - "proxySettings.flowDeprecated": "Устарело. Будет удалено в следующих версиях. Используйте вместо этого inbound.settings.flow", "proxySettings.hysteria": "Hysteria", "proxySettings.hysteriaAuth": "Аутентификация Hysteria", "proxySettings.wireguardPrivateKey": "Приватный ключ WireGuard", diff --git a/dashboard/public/statics/locales/zh.json b/dashboard/public/statics/locales/zh.json index 1cbe2f072..263d7e87c 100644 --- a/dashboard/public/statics/locales/zh.json +++ b/dashboard/public/statics/locales/zh.json @@ -561,11 +561,6 @@ "saveSuccess": "常规设置保存成功", "saveFailed": "常规设置保存失败", "cancelSuccess": "更改已取消,原始设置已恢复", - "defaultFlow": { - "title": "默认 VLESS 流", - "description": "为新 VLESS 用户自动填写流字段", - "none": "无" - }, "defaultMethod": { "title": "默认 Shadowsocks 加密方式", "description": "为新 Shadowsocks 用户自动填写加密方式" @@ -1394,8 +1389,6 @@ "userDialog.proxySettings.id": "ID", "userDialog.proxySettings.password": "密码", "userDialog.proxySettings.method": "加密方式", - "userDialog.proxySettings.flow": "流 (Flow)", - "userDialog.proxySettings.flowDeprecated": "此项已弃用,将在后续版本中移除。请改用 inbound.settings.flow", "userDialog.proxySettings.hysteria": "Hysteria", "userDialog.proxySettings.hysteriaAuth": "Hysteria 认证", "userDialog.proxySettings.wireguardPrivateKey": "WireGuard 私钥", diff --git a/dashboard/src/features/users/dialogs/user-modal.tsx b/dashboard/src/features/users/dialogs/user-modal.tsx index 732c2b870..5d6463091 100644 --- a/dashboard/src/features/users/dialogs/user-modal.tsx +++ b/dashboard/src/features/users/dialogs/user-modal.tsx @@ -1119,7 +1119,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI }, vless: { id: uuidv4(), - flow: '' as '' | 'xtls-rprx-vision' | 'xtls-rprx-vision-udp443' | undefined, }, trojan: { password: generatePassword(), @@ -1212,9 +1211,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI {} as Record, ) - if (protocol === 'vless' && !cleanedProtocolSettings.flow) { - delete cleanedProtocolSettings.flow - } if (protocol === 'shadowsocks' && !cleanedProtocolSettings.method) { delete cleanedProtocolSettings.method } @@ -1355,7 +1351,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI form.setValue('proxy_settings', undefined) form.setValue('data_limit', 0) if (generalSettings) { - form.setValue('proxy_settings.vless.flow', generalSettings.default_flow || '') const validMethods = ['aes-128-gcm', 'aes-256-gcm', 'chacha20-ietf-poly1305', 'xchacha20-poly1305'] as const const method = validMethods.find(m => m === generalSettings.default_method) if (method) { @@ -1789,38 +1784,6 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI ) }} /> - ( - - - {t('userDialog.proxySettings.vless')} {t('userDialog.proxySettings.flow')} - - - - -

{t('userDialog.proxySettings.flowDeprecated')}

- -
- )} - /> {/* Trojan */} { }, vless: { id: undefined, - flow: '', }, trojan: { password: undefined, diff --git a/dashboard/src/pages/_dashboard.settings.general.tsx b/dashboard/src/pages/_dashboard.settings.general.tsx index 6488d7c87..2cf6166f7 100644 --- a/dashboard/src/pages/_dashboard.settings.general.tsx +++ b/dashboard/src/pages/_dashboard.settings.general.tsx @@ -6,7 +6,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Separator } from '@/components/ui/separator' import { Skeleton } from '@/components/ui/skeleton' import { DEFAULT_SHADOWSOCKS_METHOD } from '@/constants/Proxies' -import { ShadowsocksMethods, XTLSFlows, useGetGeneralSettings, useReconnectAllNode } from '@/service/api' +import { ShadowsocksMethods, useGetGeneralSettings, useReconnectAllNode } from '@/service/api' import { queryClient } from '@/utils/query-client' import { zodResolver } from '@hookform/resolvers/zod' import { Loader2, RefreshCcw } from 'lucide-react' @@ -17,12 +17,8 @@ import { toast } from 'sonner' import { z } from 'zod' import { useSettingsContext } from './_dashboard.settings' -/** Radix Select forbids `SelectItem value=""`; map API empty flow to this UI value. */ -const DEFAULT_FLOW_SELECT_NONE = '__pg_default_flow_none__' - // general settings validation schema const generalSettingsSchema = z.object({ - default_flow: z.string().default(''), default_method: z.string().default(''), }) @@ -43,14 +39,12 @@ export default function General() { () => generalSettings ? { - default_flow: generalSettings.default_flow || '', default_method: generalSettings.default_method || DEFAULT_SHADOWSOCKS_METHOD, } : { - default_flow: '', default_method: '', }, - [generalSettings?.default_flow, generalSettings?.default_method], + [generalSettings?.default_method], ) const form = useForm({ @@ -64,7 +58,6 @@ export default function General() { const filteredData: any = { general: { ...data, - default_flow: data.default_flow || undefined, default_method: data.default_method || DEFAULT_SHADOWSOCKS_METHOD, }, } @@ -78,7 +71,6 @@ export default function General() { const handleCancel = () => { if (!generalSettings) return form.reset({ - default_flow: generalSettings.default_flow ?? '', default_method: generalSettings.default_method || DEFAULT_SHADOWSOCKS_METHOD, }) toast.success(t('settings.general.cancelSuccess')) @@ -171,39 +163,6 @@ export default function General() {
{/* General Settings */}
- ( - - {t('settings.general.defaultFlow.title')} - - - - {t('settings.general.defaultFlow.description')} - - - )} - />