feat(hosts): bulk-add multiple hosts to multiple inbounds (#5677)

* feat(hosts): bulk-add multiple hosts to multiple inbounds

Allow users to select multiple inbound IDs and enter multiple host
addresses (with optional per-host port override) in a single form
submission.

- Add BulkAddHostReq entity and POST /panel/api/hosts/bulk/add endpoint
- Add AddHostsBulk service with GORM transaction safety
- Add parseHostAndPort helper (IPv4, bracketed/bracketless IPv6, port)
- Update HostFormModal to multi-select inbounds and tag-input hosts
- Wire bulkCreate mutation in HostsPage with existing-host suggestions
- Register endpoint in api-docs/endpoints.ts and regenerate OpenAPI/Zod

* feat(hosts): group override records by group_id and support group editing

* fix: import Popover in HostList

* fix: use messageApi in HostFormModal

* fix(hosts): resolve 4 bugs found in host-group code review

- fix(schema): allow empty hosts array in BulkAddHostSchema so users can
  save a host without an address (inherits inbound endpoint). The old
  .min(1) was never enforced at runtime since the schema is only used for
  type inference, but the type was incorrect.

- fix(service): validate new inbound IDs in UpdateHostGroup before deleting
  old rows, matching the same check already present in AddHostGroup. Prevents
  orphaned host rows when an invalid inbound ID is supplied on edit.

- fix(service): replace full-table scan in GetHostsByInbound with two
  targeted queries (DISTINCT group_id WHERE inbound_id=?, then
  WHERE group_id IN ?) to avoid loading every host in the DB.

- fix(mutations): remove unused createMut / create export from
  useHostMutations. The /hosts/add endpoint is identical to /hosts/bulk/add;
  only bulkCreate is used by the UI.

* fix(hosts): address code review feedback (optimize bulk inserts, add validation tests, and remove comments)

* fix(fmt): apply gofumpt formatting to model.go and db.go

The previous merge commit incorrectly applied gofmt (tab-aligned) to
these files. The repository's golangci config requires gofumpt+goimports
which produces space-aligned struct fields. This commit restores the
correct gofumpt formatting that matches upstream/main.

* chore(frontend): regenerate API schemas and update lockfile

* fix

* refactor(hosts): dedupe host-group service and tidy frontend

AddHostGroup and UpdateHostGroup shared an identical ~35-field
model.Host construction and hand-rolled transaction boilerplate
(tx.Begin plus a committed flag plus a deferred recover/rollback).
Extract buildHostRows, validateInboundsExist and formatHostAddr, and
run every mutation through db.Transaction. groupHosts collapses its
duplicated address/port formatting and create/append fork into one
path using slices.Contains. Behavior-preserving: host.go drops ~90
lines with the existing service/controller tests green.

Frontend: drop the Partial union and two as-casts in HostsPage.onSave
(the modal always passes a full BulkAddHostValues), and remove the
movable index map in HostList in favor of the table render index arg.

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
AmirRnz
2026-07-09 01:05:20 +03:30
committed by GitHub
parent f431e9cc03
commit 42690e1b8c
33 changed files with 1842 additions and 1108 deletions
+48
View File
@@ -314,6 +314,7 @@ export const EXAMPLES: Record<string, unknown> = {
],
"finalMask": "",
"fingerprint": "",
"groupId": "",
"hostHeader": "",
"id": 1,
"inboundId": 1,
@@ -346,6 +347,53 @@ export const EXAMPLES: Record<string, unknown> = {
"verifyPeerCertByName": "",
"vlessRoute": "443"
},
"HostGroup": {
"allowInsecure": false,
"alpn": [
""
],
"echConfigList": "",
"excludeFromSubTypes": [
""
],
"finalMask": "",
"fingerprint": "",
"groupId": "",
"hostHeader": "",
"hosts": [
""
],
"inboundIds": [
0
],
"isDisabled": false,
"isHidden": false,
"keepSniBlank": false,
"mihomoIpVersion": "dual",
"mihomoX25519": false,
"muxParams": "",
"nodeGuids": [
""
],
"overrideSniFromAddress": false,
"path": "",
"pinnedPeerCertSha256": [
""
],
"port": 0,
"remark": "",
"security": "same",
"serverDescription": "",
"shuffleHost": false,
"sni": "",
"sockoptParams": "",
"sortOrder": 0,
"tags": [
""
],
"verifyPeerCertByName": "",
"vlessRoute": ""
},
"Inbound": {
"clientStats": [
{
+174 -159
View File
@@ -1,23 +1,18 @@
// Code generated by tools/openapigen. DO NOT EDIT.
export const SCHEMAS: Record<string, unknown> = {
"AllSetting": {
"description": "AllSetting contains all configuration settings for the 3x-ui panel including web server, Telegram bot, and subscription settings.",
"properties": {
"datepicker": {
"description": "Date picker format",
"type": "string"
},
"expireDiff": {
"description": "Expiration warning threshold in days",
"minimum": 0,
"type": "integer"
},
"externalTrafficInformEnable": {
"description": "Enable external traffic reporting",
"type": "boolean"
},
"externalTrafficInformURI": {
"description": "URI for external traffic reporting",
"type": "string"
},
"ldapAutoCreate": {
@@ -45,11 +40,9 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "integer"
},
"ldapEnable": {
"description": "LDAP settings",
"type": "boolean"
},
"ldapFlagField": {
"description": "Generic flag configuration",
"type": "string"
},
"ldapHost": {
@@ -82,7 +75,6 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "boolean"
},
"ldapUserAttr": {
"description": "e.g., mail or uid",
"type": "string"
},
"ldapUserFilter": {
@@ -92,299 +84,231 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "string"
},
"pageSize": {
"description": "UI settings\nNumber of items per page in lists (0 disables pagination)",
"maximum": 1000,
"minimum": 0,
"type": "integer"
},
"panelOutbound": {
"description": "Xray outbound tag for the panel's own outbound HTTP (update checks/downloads, Telegram, geo updates, outbound-subscription fetches)",
"type": "string"
},
"remarkTemplate": {
"description": "Subscription remark template ({{VAR}} tokens) rendered per client",
"type": "string"
},
"restartXrayOnClientDisable": {
"description": "Restart Xray when clients are auto-disabled by expiry/traffic limit",
"type": "boolean"
},
"sessionMaxAge": {
"description": "Session maximum age in minutes (cap at one year)",
"maximum": 525600,
"minimum": 1,
"type": "integer"
},
"smtpCpu": {
"description": "CPU threshold for email notifications",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"smtpEnable": {
"description": "Email (SMTP) notification settings\nEnable email notifications",
"type": "boolean"
},
"smtpEnabledEvents": {
"description": "Comma-separated event types to send via email",
"type": "string"
},
"smtpEncryptionType": {
"description": "SMTP encryption: none, starttls, tls",
"type": "string"
},
"smtpHost": {
"description": "SMTP server host",
"type": "string"
},
"smtpMemory": {
"description": "Memory threshold for email notifications",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"smtpPassword": {
"description": "SMTP password",
"type": "string"
},
"smtpPort": {
"description": "SMTP server port",
"maximum": 65535,
"minimum": 1,
"type": "integer"
},
"smtpTo": {
"description": "Comma-separated recipient emails",
"type": "string"
},
"smtpUsername": {
"description": "SMTP username",
"type": "string"
},
"subAnnounce": {
"description": "Subscription announce",
"type": "string"
},
"subCertFile": {
"description": "SSL certificate file for subscription server",
"type": "string"
},
"subClashEnable": {
"description": "Enable Clash/Mihomo subscription endpoint",
"type": "boolean"
},
"subClashEnableRouting": {
"description": "Enable global routing rules for Clash/Mihomo",
"type": "boolean"
},
"subClashPath": {
"description": "Path for Clash/Mihomo subscription endpoint",
"type": "string"
},
"subClashRules": {
"description": "Clash/Mihomo global routing rules",
"type": "string"
},
"subClashURI": {
"description": "Clash/Mihomo subscription server URI",
"type": "string"
},
"subDomain": {
"description": "Domain for subscription server validation",
"type": "string"
},
"subEnable": {
"description": "Subscription server settings\nEnable subscription server",
"type": "boolean"
},
"subEnableRouting": {
"description": "Enable routing for subscription",
"type": "boolean"
},
"subEncrypt": {
"description": "Encrypt subscription responses",
"type": "boolean"
},
"subHideSettings": {
"description": "Hide server settings in happ subscription (Only for Happ)",
"type": "boolean"
},
"subIncyEnableRouting": {
"description": "Enable routing injection for the Incy client",
"type": "boolean"
},
"subIncyRoutingRules": {
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
"type": "string"
},
"subJsonEnable": {
"description": "Enable JSON subscription endpoint",
"type": "boolean"
},
"subJsonFinalMask": {
"description": "JSON subscription global finalmask (tcp/udp masks + quicParams)",
"type": "string"
},
"subJsonMux": {
"description": "JSON subscription mux configuration",
"type": "string"
},
"subJsonPath": {
"description": "Path for JSON subscription endpoint",
"type": "string"
},
"subJsonRules": {
"type": "string"
},
"subJsonURI": {
"description": "JSON subscription server URI",
"type": "string"
},
"subKeyFile": {
"description": "SSL private key file for subscription server",
"type": "string"
},
"subListen": {
"description": "Subscription server listen IP",
"type": "string"
},
"subPath": {
"description": "Base path for subscription URLs",
"type": "string"
},
"subPort": {
"description": "Subscription server port",
"maximum": 65535,
"minimum": 1,
"type": "integer"
},
"subProfileUrl": {
"description": "Subscription profile URL",
"type": "string"
},
"subRoutingRules": {
"description": "Subscription global routing rules (Only for Happ)",
"type": "string"
},
"subSupportUrl": {
"description": "Subscription support URL",
"type": "string"
},
"subThemeDir": {
"description": "Absolute path to a folder containing a custom subscription page template",
"type": "string"
},
"subTitle": {
"description": "Subscription title",
"type": "string"
},
"subURI": {
"description": "Subscription server URI",
"type": "string"
},
"subUpdates": {
"description": "Subscription update interval in minutes",
"maximum": 525600,
"minimum": 0,
"type": "integer"
},
"tgBotAPIServer": {
"description": "Custom API server for Telegram bot",
"type": "string"
},
"tgBotBackup": {
"description": "Enable database backup via Telegram",
"type": "boolean"
},
"tgBotChatId": {
"description": "Telegram chat ID for notifications",
"type": "string"
},
"tgBotEnable": {
"description": "Telegram bot settings\nEnable Telegram bot notifications",
"type": "boolean"
},
"tgBotProxy": {
"description": "Proxy URL for Telegram bot",
"type": "string"
},
"tgBotToken": {
"description": "Telegram bot token",
"type": "string"
},
"tgCpu": {
"description": "CPU usage threshold for alerts (percent)",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"tgEnabledEvents": {
"description": "Comma-separated event types to send via Telegram",
"type": "string"
},
"tgLang": {
"description": "Telegram bot language",
"type": "string"
},
"tgMemory": {
"description": "Memory usage threshold for alerts (percent)",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"tgRunTime": {
"description": "Cron schedule for Telegram notifications",
"type": "string"
},
"timeLocation": {
"description": "Security settings\nTime zone location",
"type": "string"
},
"trafficDiff": {
"description": "Traffic warning threshold percentage",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"trustedProxyCIDRs": {
"description": "Trusted reverse proxy IPs/CIDRs for forwarded headers",
"type": "string"
},
"twoFactorEnable": {
"description": "Enable two-factor authentication",
"type": "boolean"
},
"twoFactorToken": {
"description": "Two-factor authentication token",
"type": "string"
},
"warpUpdateInterval": {
"description": "WARP",
"minimum": 0,
"type": "integer"
},
"webBasePath": {
"description": "Base path for web panel URLs",
"type": "string"
},
"webCertFile": {
"description": "Path to SSL certificate file for web server",
"type": "string"
},
"webDomain": {
"description": "Web server domain for domain validation",
"type": "string"
},
"webKeyFile": {
"description": "Path to SSL private key file for web server",
"type": "string"
},
"webListen": {
"description": "Web server settings\nWeb server listen IP address",
"type": "string"
},
"webPort": {
"description": "Web server port number",
"maximum": 65535,
"minimum": 1,
"type": "integer"
@@ -489,23 +413,18 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "object"
},
"AllSettingView": {
"description": "AllSettingView is the browser-safe settings read model. Secret values\nare redacted from the embedded write model and represented by presence\nflags so the UI can show configured/not configured state.",
"properties": {
"datepicker": {
"description": "Date picker format",
"type": "string"
},
"expireDiff": {
"description": "Expiration warning threshold in days",
"minimum": 0,
"type": "integer"
},
"externalTrafficInformEnable": {
"description": "Enable external traffic reporting",
"type": "boolean"
},
"externalTrafficInformURI": {
"description": "URI for external traffic reporting",
"type": "string"
},
"hasApiToken": {
@@ -554,11 +473,9 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "integer"
},
"ldapEnable": {
"description": "LDAP settings",
"type": "boolean"
},
"ldapFlagField": {
"description": "Generic flag configuration",
"type": "string"
},
"ldapHost": {
@@ -591,7 +508,6 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "boolean"
},
"ldapUserAttr": {
"description": "e.g., mail or uid",
"type": "string"
},
"ldapUserFilter": {
@@ -601,299 +517,231 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "string"
},
"pageSize": {
"description": "UI settings\nNumber of items per page in lists (0 disables pagination)",
"maximum": 1000,
"minimum": 0,
"type": "integer"
},
"panelOutbound": {
"description": "Xray outbound tag for the panel's own outbound HTTP (update checks/downloads, Telegram, geo updates, outbound-subscription fetches)",
"type": "string"
},
"remarkTemplate": {
"description": "Subscription remark template ({{VAR}} tokens) rendered per client",
"type": "string"
},
"restartXrayOnClientDisable": {
"description": "Restart Xray when clients are auto-disabled by expiry/traffic limit",
"type": "boolean"
},
"sessionMaxAge": {
"description": "Session maximum age in minutes (cap at one year)",
"maximum": 525600,
"minimum": 1,
"type": "integer"
},
"smtpCpu": {
"description": "CPU threshold for email notifications",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"smtpEnable": {
"description": "Email (SMTP) notification settings\nEnable email notifications",
"type": "boolean"
},
"smtpEnabledEvents": {
"description": "Comma-separated event types to send via email",
"type": "string"
},
"smtpEncryptionType": {
"description": "SMTP encryption: none, starttls, tls",
"type": "string"
},
"smtpHost": {
"description": "SMTP server host",
"type": "string"
},
"smtpMemory": {
"description": "Memory threshold for email notifications",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"smtpPassword": {
"description": "SMTP password",
"type": "string"
},
"smtpPort": {
"description": "SMTP server port",
"maximum": 65535,
"minimum": 1,
"type": "integer"
},
"smtpTo": {
"description": "Comma-separated recipient emails",
"type": "string"
},
"smtpUsername": {
"description": "SMTP username",
"type": "string"
},
"subAnnounce": {
"description": "Subscription announce",
"type": "string"
},
"subCertFile": {
"description": "SSL certificate file for subscription server",
"type": "string"
},
"subClashEnable": {
"description": "Enable Clash/Mihomo subscription endpoint",
"type": "boolean"
},
"subClashEnableRouting": {
"description": "Enable global routing rules for Clash/Mihomo",
"type": "boolean"
},
"subClashPath": {
"description": "Path for Clash/Mihomo subscription endpoint",
"type": "string"
},
"subClashRules": {
"description": "Clash/Mihomo global routing rules",
"type": "string"
},
"subClashURI": {
"description": "Clash/Mihomo subscription server URI",
"type": "string"
},
"subDomain": {
"description": "Domain for subscription server validation",
"type": "string"
},
"subEnable": {
"description": "Subscription server settings\nEnable subscription server",
"type": "boolean"
},
"subEnableRouting": {
"description": "Enable routing for subscription",
"type": "boolean"
},
"subEncrypt": {
"description": "Encrypt subscription responses",
"type": "boolean"
},
"subHideSettings": {
"description": "Hide server settings in happ subscription (Only for Happ)",
"type": "boolean"
},
"subIncyEnableRouting": {
"description": "Enable routing injection for the Incy client",
"type": "boolean"
},
"subIncyRoutingRules": {
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
"type": "string"
},
"subJsonEnable": {
"description": "Enable JSON subscription endpoint",
"type": "boolean"
},
"subJsonFinalMask": {
"description": "JSON subscription global finalmask (tcp/udp masks + quicParams)",
"type": "string"
},
"subJsonMux": {
"description": "JSON subscription mux configuration",
"type": "string"
},
"subJsonPath": {
"description": "Path for JSON subscription endpoint",
"type": "string"
},
"subJsonRules": {
"type": "string"
},
"subJsonURI": {
"description": "JSON subscription server URI",
"type": "string"
},
"subKeyFile": {
"description": "SSL private key file for subscription server",
"type": "string"
},
"subListen": {
"description": "Subscription server listen IP",
"type": "string"
},
"subPath": {
"description": "Base path for subscription URLs",
"type": "string"
},
"subPort": {
"description": "Subscription server port",
"maximum": 65535,
"minimum": 1,
"type": "integer"
},
"subProfileUrl": {
"description": "Subscription profile URL",
"type": "string"
},
"subRoutingRules": {
"description": "Subscription global routing rules (Only for Happ)",
"type": "string"
},
"subSupportUrl": {
"description": "Subscription support URL",
"type": "string"
},
"subThemeDir": {
"description": "Absolute path to a folder containing a custom subscription page template",
"type": "string"
},
"subTitle": {
"description": "Subscription title",
"type": "string"
},
"subURI": {
"description": "Subscription server URI",
"type": "string"
},
"subUpdates": {
"description": "Subscription update interval in minutes",
"maximum": 525600,
"minimum": 0,
"type": "integer"
},
"tgBotAPIServer": {
"description": "Custom API server for Telegram bot",
"type": "string"
},
"tgBotBackup": {
"description": "Enable database backup via Telegram",
"type": "boolean"
},
"tgBotChatId": {
"description": "Telegram chat ID for notifications",
"type": "string"
},
"tgBotEnable": {
"description": "Telegram bot settings\nEnable Telegram bot notifications",
"type": "boolean"
},
"tgBotProxy": {
"description": "Proxy URL for Telegram bot",
"type": "string"
},
"tgBotToken": {
"description": "Telegram bot token",
"type": "string"
},
"tgCpu": {
"description": "CPU usage threshold for alerts (percent)",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"tgEnabledEvents": {
"description": "Comma-separated event types to send via Telegram",
"type": "string"
},
"tgLang": {
"description": "Telegram bot language",
"type": "string"
},
"tgMemory": {
"description": "Memory usage threshold for alerts (percent)",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"tgRunTime": {
"description": "Cron schedule for Telegram notifications",
"type": "string"
},
"timeLocation": {
"description": "Security settings\nTime zone location",
"type": "string"
},
"trafficDiff": {
"description": "Traffic warning threshold percentage",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"trustedProxyCIDRs": {
"description": "Trusted reverse proxy IPs/CIDRs for forwarded headers",
"type": "string"
},
"twoFactorEnable": {
"description": "Enable two-factor authentication",
"type": "boolean"
},
"twoFactorToken": {
"description": "Two-factor authentication token",
"type": "string"
},
"warpUpdateInterval": {
"description": "WARP",
"minimum": 0,
"type": "integer"
},
"webBasePath": {
"description": "Base path for web panel URLs",
"type": "string"
},
"webCertFile": {
"description": "Path to SSL certificate file for web server",
"type": "string"
},
"webDomain": {
"description": "Web server domain for domain validation",
"type": "string"
},
"webKeyFile": {
"description": "Path to SSL private key file for web server",
"type": "string"
},
"webListen": {
"description": "Web server settings\nWeb server listen IP address",
"type": "string"
},
"webPort": {
"description": "Web server port number",
"maximum": 65535,
"minimum": 1,
"type": "integer"
@@ -1427,7 +1275,6 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "object"
},
"Host": {
"description": "Host is an override endpoint attached to an inbound: at subscription time each\nenabled host renders one share link/proxy with its own address/port/TLS/etc.,\nsuperseding the legacy externalProxy array. Free-JSON fields are stored as\ntext and parsed in the sub layer; slice fields use the json serializer.",
"properties": {
"address": {
"example": "cdn.example.com",
@@ -1461,6 +1308,9 @@ export const SCHEMAS: Record<string, unknown> = {
"fingerprint": {
"type": "string"
},
"groupId": {
"type": "string"
},
"hostHeader": {
"type": "string"
},
@@ -1575,6 +1425,7 @@ export const SCHEMAS: Record<string, unknown> = {
"excludeFromSubTypes",
"finalMask",
"fingerprint",
"groupId",
"hostHeader",
"id",
"inboundId",
@@ -1602,6 +1453,175 @@ export const SCHEMAS: Record<string, unknown> = {
],
"type": "object"
},
"HostGroup": {
"properties": {
"allowInsecure": {
"type": "boolean"
},
"alpn": {
"items": {
"type": "string"
},
"type": "array"
},
"echConfigList": {
"type": "string"
},
"excludeFromSubTypes": {
"items": {
"type": "string"
},
"type": "array"
},
"finalMask": {
"type": "string"
},
"fingerprint": {
"type": "string"
},
"groupId": {
"type": "string"
},
"hostHeader": {
"type": "string"
},
"hosts": {
"items": {
"type": "string"
},
"type": "array"
},
"inboundIds": {
"items": {
"type": "integer"
},
"type": "array"
},
"isDisabled": {
"type": "boolean"
},
"isHidden": {
"type": "boolean"
},
"keepSniBlank": {
"type": "boolean"
},
"mihomoIpVersion": {
"enum": [
"dual",
"ipv4",
"ipv6",
"ipv4-prefer",
"ipv6-prefer"
],
"type": "string"
},
"mihomoX25519": {
"type": "boolean"
},
"muxParams": {
"type": "string"
},
"nodeGuids": {
"items": {
"type": "string"
},
"type": "array"
},
"overrideSniFromAddress": {
"type": "boolean"
},
"path": {
"type": "string"
},
"pinnedPeerCertSha256": {
"items": {
"type": "string"
},
"type": "array"
},
"port": {
"maximum": 65535,
"minimum": 0,
"type": "integer"
},
"remark": {
"maxLength": 256,
"type": "string"
},
"security": {
"enum": [
"same",
"tls",
"none",
"reality"
],
"type": "string"
},
"serverDescription": {
"maxLength": 64,
"type": "string"
},
"shuffleHost": {
"type": "boolean"
},
"sni": {
"type": "string"
},
"sockoptParams": {
"type": "string"
},
"sortOrder": {
"type": "integer"
},
"tags": {
"items": {
"type": "string"
},
"type": "array"
},
"verifyPeerCertByName": {
"type": "string"
},
"vlessRoute": {
"type": "string"
}
},
"required": [
"allowInsecure",
"alpn",
"echConfigList",
"excludeFromSubTypes",
"finalMask",
"fingerprint",
"groupId",
"hostHeader",
"hosts",
"inboundIds",
"isDisabled",
"isHidden",
"keepSniBlank",
"mihomoIpVersion",
"mihomoX25519",
"muxParams",
"nodeGuids",
"overrideSniFromAddress",
"path",
"pinnedPeerCertSha256",
"port",
"remark",
"security",
"serverDescription",
"shuffleHost",
"sni",
"sockoptParams",
"sortOrder",
"tags",
"verifyPeerCertByName",
"vlessRoute"
],
"type": "object"
},
"Inbound": {
"description": "Inbound represents an Xray inbound configuration with traffic statistics and settings.",
"properties": {
@@ -1889,17 +1909,12 @@ export const SCHEMAS: Record<string, unknown> = {
"type": "object"
},
"Msg": {
"description": "Msg represents a standard API response message with success status, message text, and optional data object.",
"properties": {
"msg": {
"description": "Response message text",
"type": "string"
},
"obj": {
"description": "Optional data object"
},
"obj": {},
"success": {
"description": "Indicates if the operation was successful",
"type": "boolean"
}
},
+35
View File
@@ -325,6 +325,7 @@ export interface Host {
excludeFromSubTypes: string[];
finalMask: string;
fingerprint: string;
groupId: string;
hostHeader: string;
id: number;
inboundId: number;
@@ -352,6 +353,40 @@ export interface Host {
vlessRoute: string;
}
export interface HostGroup {
allowInsecure: boolean;
alpn: string[];
echConfigList: string;
excludeFromSubTypes: string[];
finalMask: string;
fingerprint: string;
groupId: string;
hostHeader: string;
hosts: string[];
inboundIds: number[];
isDisabled: boolean;
isHidden: boolean;
keepSniBlank: boolean;
mihomoIpVersion: string;
mihomoX25519: boolean;
muxParams: string;
nodeGuids: string[];
overrideSniFromAddress: boolean;
path: string;
pinnedPeerCertSha256: string[];
port: number;
remark: string;
security: string;
serverDescription: string;
shuffleHost: boolean;
sni: string;
sockoptParams: string;
sortOrder: number;
tags: string[];
verifyPeerCertByName: string;
vlessRoute: string;
}
export interface Inbound {
clientStats: ClientTraffic[];
down: number;
+36
View File
@@ -348,6 +348,7 @@ export const HostSchema = z.object({
excludeFromSubTypes: z.array(z.string()),
finalMask: z.string(),
fingerprint: z.string(),
groupId: z.string(),
hostHeader: z.string(),
id: z.number().int(),
inboundId: z.number().int(),
@@ -376,6 +377,41 @@ export const HostSchema = z.object({
});
export type Host = z.infer<typeof HostSchema>;
export const HostGroupSchema = z.object({
allowInsecure: z.boolean(),
alpn: z.array(z.string()),
echConfigList: z.string(),
excludeFromSubTypes: z.array(z.string()),
finalMask: z.string(),
fingerprint: z.string(),
groupId: z.string(),
hostHeader: z.string(),
hosts: z.array(z.string()),
inboundIds: z.array(z.number().int()),
isDisabled: z.boolean(),
isHidden: z.boolean(),
keepSniBlank: z.boolean(),
mihomoIpVersion: z.enum(['dual', 'ipv4', 'ipv6', 'ipv4-prefer', 'ipv6-prefer']),
mihomoX25519: z.boolean(),
muxParams: z.string(),
nodeGuids: z.array(z.string()),
overrideSniFromAddress: z.boolean(),
path: z.string(),
pinnedPeerCertSha256: z.array(z.string()),
port: z.number().int().min(0).max(65535),
remark: z.string().max(256),
security: z.enum(['same', 'tls', 'none', 'reality']),
serverDescription: z.string().max(64),
shuffleHost: z.boolean(),
sni: z.string(),
sockoptParams: z.string(),
sortOrder: z.number().int(),
tags: z.array(z.string()),
verifyPeerCertByName: z.string(),
vlessRoute: z.string(),
});
export type HostGroup = z.infer<typeof HostGroupSchema>;
export const InboundSchema = z.object({
clientStats: z.array(z.lazy(() => ClientTrafficSchema)),
down: z.number().int(),