From 052dd85ad3fb4cbbd878ebc677393ccad0fa17e8 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 3 Jul 2026 09:26:06 +0200 Subject: [PATCH] feat(clients): hide disabled inbounds in the client form selector The attach-inbounds select in the client add/edit modal listed every inbound, so panels with many disabled inbounds had to scroll past dead entries. InboundOption now carries the inbound's enable flag and the form drops disabled inbounds from the options, keeping ones the client is already attached to so edit mode still renders existing assignments. Closes #5645 --- frontend/public/openapi.json | 6 ++++++ frontend/src/generated/examples.ts | 1 + frontend/src/generated/schemas.ts | 5 +++++ frontend/src/generated/types.ts | 1 + frontend/src/generated/zod.ts | 1 + frontend/src/pages/clients/ClientFormModal.tsx | 3 ++- internal/web/service/inbound.go | 5 ++++- 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 1d330ba02..c5ecfd17a 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -1824,6 +1824,10 @@ }, "InboundOption": { "properties": { + "enable": { + "example": true, + "type": "boolean" + }, "id": { "example": 1, "type": "integer" @@ -1880,6 +1884,7 @@ } }, "required": [ + "enable", "id", "port", "protocol", @@ -2795,6 +2800,7 @@ "success": true, "obj": [ { + "enable": true, "id": 1, "listen": "", "nodeAddress": "", diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index bdd81af88..ec63d47ea 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -399,6 +399,7 @@ export const EXAMPLES: Record = { "xver": 0 }, "InboundOption": { + "enable": true, "id": 1, "listen": "", "nodeAddress": "", diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 4af04e5d5..81df25b3e 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -1798,6 +1798,10 @@ export const SCHEMAS: Record = { }, "InboundOption": { "properties": { + "enable": { + "example": true, + "type": "boolean" + }, "id": { "example": 1, "type": "integer" @@ -1854,6 +1858,7 @@ export const SCHEMAS: Record = { } }, "required": [ + "enable", "id", "port", "protocol", diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index 186fa5cc7..bcb934360 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -393,6 +393,7 @@ export interface InboundFallback { } export interface InboundOption { + enable: boolean; id: number; listen?: string; nodeAddress?: string; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index 45397a31e..c968699d7 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -420,6 +420,7 @@ export const InboundFallbackSchema = z.object({ export type InboundFallback = z.infer; export const InboundOptionSchema = z.object({ + enable: z.boolean(), id: z.number().int(), listen: z.string().optional(), nodeAddress: z.string().optional(), diff --git a/frontend/src/pages/clients/ClientFormModal.tsx b/frontend/src/pages/clients/ClientFormModal.tsx index 3fa543ef3..633ab32d9 100644 --- a/frontend/src/pages/clients/ClientFormModal.tsx +++ b/frontend/src/pages/clients/ClientFormModal.tsx @@ -376,12 +376,13 @@ export default function ClientFormModal({ const inboundOptions = useMemo( () => (inbounds || []) .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || '')) + .filter((ib) => ib.enable || (form.inboundIds || []).includes(ib.id)) .map((ib) => ({ label: formatInboundLabel(ib.tag, ib.remark), value: ib.id, title: formatInboundLabel(ib.tag, ib.remark), })), - [inbounds], + [inbounds, form.inboundIds], ); const linkRows = useMemo(() => form.externalLinks.filter((r) => r.kind === 'link'), [form.externalLinks]); diff --git a/internal/web/service/inbound.go b/internal/web/service/inbound.go index 7407e31d2..13079a633 100644 --- a/internal/web/service/inbound.go +++ b/internal/web/service/inbound.go @@ -297,6 +297,7 @@ type InboundOption struct { Tag string `json:"tag" example:"in-443-tcp"` Protocol string `json:"protocol" example:"vless"` Port int `json:"port" example:"443"` + Enable bool `json:"enable" example:"true"` TlsFlowCapable bool `json:"tlsFlowCapable" example:"true"` SsMethod string `json:"ssMethod"` WgPublicKey string `json:"wgPublicKey,omitempty"` @@ -325,6 +326,7 @@ func (s *InboundService) GetInboundOptions(userId int) ([]InboundOption, error) Tag string `gorm:"column:tag"` Protocol string `gorm:"column:protocol"` Port int `gorm:"column:port"` + Enable bool `gorm:"column:enable"` StreamSettings string `gorm:"column:stream_settings"` Settings string `gorm:"column:settings"` Listen string `gorm:"column:listen"` @@ -334,7 +336,7 @@ func (s *InboundService) GetInboundOptions(userId int) ([]InboundOption, error) NodeAddress string `gorm:"column:node_address"` } err := db.Table("inbounds"). - Select("inbounds.id, inbounds.remark, inbounds.tag, inbounds.protocol, inbounds.port, inbounds.stream_settings, inbounds.settings, inbounds.listen, inbounds.share_addr, inbounds.share_addr_strategy, inbounds.node_id, COALESCE(nodes.address, '') AS node_address"). + Select("inbounds.id, inbounds.remark, inbounds.tag, inbounds.protocol, inbounds.port, inbounds.enable, inbounds.stream_settings, inbounds.settings, inbounds.listen, inbounds.share_addr, inbounds.share_addr_strategy, inbounds.node_id, COALESCE(nodes.address, '') AS node_address"). Joins("LEFT JOIN nodes ON nodes.id = inbounds.node_id"). Where("inbounds.user_id = ?", userId). Order("inbounds.id ASC"). @@ -355,6 +357,7 @@ func (s *InboundService) GetInboundOptions(userId int) ([]InboundOption, error) Tag: r.Tag, Protocol: r.Protocol, Port: r.Port, + Enable: r.Enable, TlsFlowCapable: inboundCanEnableTlsFlow(r.Protocol, r.StreamSettings, r.Settings), SsMethod: inboundShadowsocksMethod(r.Protocol, r.Settings), WgPublicKey: wgPublicKey,