mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-24 20:07:13 +00:00
da01b7637d
* feat(sub): add SubBalancer model and migration Client-side JSON-subscription balancer row: remark, strategy, member inbound ids, sort order, enabled. Registered in allModels and migrationModels so AutoMigrate and SQLite->Postgres copy pick it up. * feat(sub): add SubBalancer service List/Get/Create/Update/Delete over the sub_balancers table with remark trim, strategy allowlist (leastLoad/leastPing/random) and sort-order floor. Rows are read per request by the subscription builder, so mutations need no xray restart. * feat(sub): add SubBalancer API controller and routes GET/POST /panel/api/sub-balancers, POST /:id (update), DELETE /:id and POST /:id/del alias. inboundIds bind from repeated form keys. Mounted under the /panel/api group so the existing API token + CSRF middleware cover it. * feat(sub): emit client-side balancers in JSON subscription For each enabled balancer, append one config document whose outbounds are the selected inbounds' proxy outbounds retagged under a per-balancer prefix, with routing.balancers + burstObservatory selecting it. Balancer entries interleave with inbound entries by sort order; on equal numbers the balancer follows the inbound. Skipped when disabled or no member outbound is present. * test(sub): cover SubBalancer service and JSON output Service: validation gates (remark/strategy/inbound ids/sort order) and CRUD round-trip. JSON: balancer document shape, sort interleaving with inbounds, disabled/empty skip, and member tag dedup. * feat(sub): add sub-balancers i18n keys pages.settings.subBalancers.* block (menu, title, add, desc, field labels, strategy names, sort-order help, validation messages) added to all 13 locales. * feat(sub): add SubBalancer schema and API queries Zod schema (entity + form, strategy enum, validation messages wired to i18n keys), react-query hooks for list/create/update/delete, and the sub-balancers query key. * feat(sub): add subscription balancers settings tab SubscriptionBalancersTab lists balancers (sort order, remark, strategy, inbound count, enabled toggle, edit/delete) with a form modal (remark, strategy, sort order, multi-select inbounds filtered to multi-client protocols, enabled). Wired into SettingsPage under #subscription-balancers, and the sidebar shows the entry only when JSON subscription is enabled. * test(sub): add SubBalancer form modal test Covers add-mode (no validation errors, confirm with parsed values) and edit-mode (seeds from the balancer, preserves strategy/sort order/enabled). * feat(sub): register sub-balancers in API docs and OpenAPI Adds the sub-balancers endpoint group to endpoints.ts (list/create/update/delete + POST del alias) and regenerates frontend/public/openapi.json from it. * docs: sync openapi.json with frontend docs/public/openapi.json had fallen behind frontend/public/openapi.json (fewer paths/schemas). Copy the current frontend spec so the docs site renders the full API. * docs: add subscription balancers API reference Registers the sub-balancers page (generated MDX) and adds the sub-balancers paths to docs/public/openapi.json so the page renders the list/create/update/delete operations. * feat(sub): accept roundRobin balancer strategy Add roundRobin to the model oneof tag and the service strategy allowlist, alongside leastLoad/leastPing/random. Covered by a service-level create test that fails on the old allowlist. * feat(sub): add roundRobin strategy label pages.settings.subBalancers.strategyRoundRobin added to all 13 locales. * feat(sub): expose roundRobin in balancer form Zod strategy enum, form modal label key, and table strategy colour for roundRobin. * docs(sub): list roundRobin in strategy description The create/update strategy param description now mentions roundRobin alongside the other three. * feat(sub): add subJsonObservatory setting Panel-wide JSON string carrying the burstObservatory ping config (destination, connectivity, interval, sampling, timeout, httpMethod) emitted into client-side balancer docs. Stored like subJsonMux/Rules/FinalMask. * feat(sub): wire observatory config through sub controller WithSUBJsonObservatory option; the controller calls SubJsonService.SetObservatoryConfig after construction. * feat(sub): emit observatory conditionally with configurable probes burstObservatory is emitted only for leastPing/leastLoad; random/roundRobin get none (no fallback, so an observatory would only probe for nothing). Probe params come from the subJsonObservatory setting, falling back to the built-in defaults when empty or partial. Test covers the conditional emit and the override. * feat(sub): add subJsonObservatory to AllSetting model Frontend AllSetting model and Zod schema carry the new panel-wide observatory config string. * feat(sub): add balancer observatory config card New Sub Formats tab editing destination/connectivity/interval/sampling/timeout/httpMethod, stored as JSON in subJsonObservatory. Toggle off clears the setting; the backend then falls back to defaults. * fix(sub): hide save/restart header on sub-balancers tab Sub-balancer mutations are incremental (own CRUD API, no Save, no restart), so the page-wide 'every change needs to be saved / restart the panel' banner is misleading there. The in-tab alert already explains it correctly. * feat(sub): add observatory config i18n keys pages.settings.subBalancers.observatory.* (title, desc, probe field labels and help texts) added to all 13 locales. * feat(sub): regenerate openapi for subJsonObservatory openapigen picks up the new AllSetting field; openapi.json synced into docs. * feat(sub): add observatory tab to sub-balancers Mirrors the Xray Balancers page: two tabs (Balancers + Observatory). Wires allSetting/updateSetting into the tab and adds tabBalancers / tabObservatory labels to all locales. The page Save header is shown again on this tab so the observatory config can be saved. * refactor(sub): drop observatory tab from sub-formats Now that the observatory config lives under sub-balancers, remove the duplicate tab plus its state and defaults from sub-formats. * fix(sub): add missing inboundsCount i18n key The sub-balancers table rendered the raw key path in the Inbounds column because pages.settings.subBalancers.inboundsCount was not defined. Added it to all 13 locales. * test(sub): pin disabled-inbound exclusion from balancer The balancer builds its members from the subscriber's already-filtered entry set, so an inbound disabled for that user can never surface as a member. Adds tests for both shapes (one of several disabled, and the only selected one disabled). * fix(sub): make observatory toggle honest, default connectivity off, add balancer fallback Three coupled defects on the balancer observatory surface, flagged in PR review: - The Observatory Switch wrote '' which the Go side treats as "use built-in defaults", so leastPing/leastLoad still shipped a burstObservatory the admin could no longer see or edit. The observatory is mandatory for these strategies (Xray refuses to start leastPing/leastLoad without one — verified against Xray 26.7), so the switch is relabelled to "customise probe parameters vs built-in defaults" rather than on/off: '' keeps the defaults, a stored JSON overrides them. An info Alert explains this. - Connectivity defaulted to http://www.google.com/generate_204 and an explicit {"connectivity":""} restored it, so the UI's "Leave empty to skip" was unreachable and the direct pre-check was dead on arrival on censored client networks. Default to "" and honour an explicit empty value. - routing.balancers had no fallbackTag, so a leastPing/leastLoad balancer whose probes all fail selects nothing and dispatch fails. Emit fallbackTag pointing at the first member so a probe outage degrades instead of breaking. Also skip balancer entries (kind!=0) in the member scan so a balancer can never match another balancer's row id. Tests cover each fix and fail without it. * fix(sub-balancer): localize controller toasts and reject malformed ids Route the new controller's user-facing messages through I18nWeb so non-English admins get localized toasts like every other controller, and switch parseID to strconv.Atoi rejecting ids < 1 so "12abc" and negative ids no longer coerce to a silent no-op delete that reports success. * fix(sub-balancer): enforce remark length cap server-side The model's validate:"max=256" tag was never enforced (parseSubBalancerForm binds an ad-hoc struct without validate.Struct), so a scripted API client could store an unbounded remark that is emitted verbatim as the remarks field of every affected subscriber's config. Reject len > 256 in validate() to match the frontend Zod cap. * fix(sub-balancer): exclude mtproto from balancer member picker SubJsonService.getConfig has no mtproto case, so an mtproto inbound's first outbound is "direct" and the buildBalancerConfig "tag != proxy" guard drops it — an admin could select it, save without error, and get a balancer that silently omits it (or no document at all). Drop it from the picker and fix the comment. * docs(sub-balancers): add nav entry, fix tab pointer, note mirror scope - Add "subscription-balancers" to the en reference/api meta.json pages array so the new MDX page is reachable from the sidebar (fa/ru/zh have no MDX — gen-openapi.ts emits into en only). - Fix the endpoints.ts section description from "Settings -> Subscription" to "Settings -> Sub Balancers" (the feature's own tab) and regenerate the OpenAPI spec + MDX. - Note in docs/lib/xray/subscription.ts that balancer documents are intentionally out of scope for that mirror. * style(model): trim SubBalancer comment to 2-line cap CLAUDE.md caps committed Go comment blocks at 2 lines; this one was 3. * fix(sub-balancer): parse enabled explicitly and preserve it on partial update parseSubBalancerForm treated any non-"false" value as true (so "bogus" silently enabled) and always overwrote Enabled on update, so a PATCH that omitted the toggle reset a disabled balancer back to enabled. Parse the field with strconv.ParseBool and return *bool: absent means "no change" on update and "true" on create; a malformed value is rejected as 400. Update keeps the stored Enabled when the pointer is nil. * fix(sub-balancer): clear deleted inbound from sub_balancers.InboundIds DelInbound cascaded hosts but left the deleted inbound id in every sub_balancers.InboundIds, so the balancer kept emitting a member no subscriber could resolve — a dangling outbound tag with no proxy behind it. Strip the id inside the existing delete transaction (same shape as the hosts cascade, #5648); with the last member gone the balancer stops emitting. * fix(sub-balancer): return not-found when deleting a missing balancer Delete returned the gorm result error only, which is nil when no row matched, so the controller reported success:true for an id that never existed — a stale UI row looked like a clean delete. Check RowsAffected and return a not-found error on 0 so the toast reflects reality. * style(sub): shorten leastPing/leastLoad observatory comments The observatory-emission guard comment and its test comment ran a few lines long; trim them to a couple of lines each without dropping the invariant that leastPing/leastLoad require a burst observatory. * fix(sub): validate observatory setting instead of silently dropping it SetObservatoryConfig applied whatever survived json.Unmarshal with no checks, so a bad probe URL ("not-a-url"), non-duration interval/timeout, or even unparseable JSON was either silently applied or silently ignored. Validate each field: parse durations with time.ParseDuration, require http(s) URLs for destination/connectivity, and log a warning naming the field and the bad value on every fallback — including the unmarshal error, which was a quiet return. Bad values now keep the built-in defaults instead of leaking into the emitted burstObservatory. * fix(sub): deduplicate burst-observatory defaults across Go and frontend The burst-observatory ping defaults lived in three places that had drifted: Go defaultSubBalancerObservatoryConfig (http probe, sampling 3), the Zod PingConfigSchema, and DEFAULT_BURST_OBSERVATORY (both with a connectivity pre-check URL). Align them to one set: https probe destination, sampling 2, and empty connectivity (skip the direct pre-check). The settings tab now parses the stored JSON through PingConfigSchema and seeds its default from DEFAULT_BURST_OBSERVATORY instead of carrying its own literal. * refactor(sub): extract proxy outbounds once before the balancer loop buildBalancerConfig unmarshalled every inbound document and re-extracted its first outbound on each balancer, so with B balancers and N inbound docs the same document was parsed B*N times. Pull each doc's proxy outbound in a single pre-pass over the entries and cache it per entry; buildBalancerConfig now clones the cached map before retagging, so one parse serves every balancer. Output is byte-for-byte unchanged. * fix(sub): form balancer member tags from the inbound protocol, not tcp→vless balancerTransport derived the bal-N tag suffix from the outbound's transport network and hard-coded tcp→vless, so a vmess/tcp or trojan/tcp member was mislabelled "vless" in every client config — the tag lied about the proxy type. Use the outbound's real protocol as the suffix (bal-1-vmess, bal-1-vless, bal-1-trojan, …) so the tag names the actual proxy; the selector prefix and dedup suffix are unchanged. Update the existing tag assertions and add a vmess case that fails under the old mapping. * fix(sub-balancer): default strategy to random in the create form The create-balancer form seeded strategy to 'leastLoad', but the service validate() defaults an empty strategy to 'random' and the API docs say the default is 'random' — so a freshly opened form showed leastLoad while saving without touching the field silently stored random. Align the form default to 'random' so what the admin sees is what gets persisted. * feat(api-docs): document the SubBalancer response schema The five sub-balancer endpoints carried no responseSchema, so the API docs page rendered them without a typed example. Add example: tags to every SubBalancer field, allow the struct through openapigen, and point the list (responseSchemaArray) and single-row endpoints at 'SubBalancer'. Regenerate the Zod/JSON schemas and OpenAPI doc and mirror openapi.json into docs/. * style(sub-balancer): drop whitespace-only separator lines, add final newline subBalancer.ts and SubBalancerFormModal.tsx used single-space blank lines as separators between statements and had no trailing newline. Replace them with clean empty blank lines and end each file with a newline. * fix(i18n): translate sub-balancer toasts and observatory note The sub-balancer toast messages (list/create/update/delete/invalidId) and the observatory note were left in English across 11 non-English locales (ar, es, fa, id, ja, pt-BR, tr, uk, vi, zh-CN, zh-TW) while every other key in the subBalancers block was already translated. Translate them to match the meaning and terminology of the surrounding keys in each file; the JSON structure and keys are unchanged. * fix(sub-balancer): hide disabled inbounds from the member picker The picker offered every protocol-eligible inbound regardless of its enable flag, but getInboundsBySubId filters `AND inbounds.enable = true`. A disabled member is therefore dropped from every subscriber's entries, and when it was the balancer's only member the balancer document silently stops being emitted — with nothing in the UI explaining why. TestSubJson_BalancerSkippedWhenAll MembersDisabled already documents that backend behavior. Filter the way the sibling client picker has since #5645: hide disabled inbounds, but keep one that is already selected so editing an existing balancer cannot silently drop a member. Drop the `?? []` on the useWatch result so the new useMemo dependency stays referentially stable. * style(sub): trim the balancerMemberSuffix comment to the 2-line cap Comment blocks in committed Go are capped at 2 lines; the name already carries what the function picks, so keep only the why. --------- Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
752 lines
16 KiB
TypeScript
752 lines
16 KiB
TypeScript
// Code generated by tools/openapigen. DO NOT EDIT.
|
|
export const EXAMPLES: Record<string, unknown> = {
|
|
"AllSetting": {
|
|
"datepicker": "",
|
|
"expireDiff": 0,
|
|
"externalTrafficInformEnable": false,
|
|
"externalTrafficInformURI": "",
|
|
"ipLimitAllowlist": "",
|
|
"ldapAutoCreate": false,
|
|
"ldapAutoDelete": false,
|
|
"ldapBaseDN": "",
|
|
"ldapBindDN": "",
|
|
"ldapDefaultExpiryDays": 0,
|
|
"ldapDefaultLimitIP": 0,
|
|
"ldapDefaultTotalGB": 0,
|
|
"ldapEnable": false,
|
|
"ldapFlagField": "",
|
|
"ldapHost": "",
|
|
"ldapInboundTags": "",
|
|
"ldapInsecureSkipVerify": false,
|
|
"ldapInvertFlag": false,
|
|
"ldapPassword": "",
|
|
"ldapPort": 0,
|
|
"ldapSyncCron": "",
|
|
"ldapTruthyValues": "",
|
|
"ldapUseTLS": false,
|
|
"ldapUserAttr": "",
|
|
"ldapUserFilter": "",
|
|
"ldapVlessField": "",
|
|
"outboundDownThreshold": 1,
|
|
"pageSize": 0,
|
|
"panelOutbound": "",
|
|
"remarkTemplate": "",
|
|
"restartXrayOnClientDisable": false,
|
|
"sessionMaxAge": 1,
|
|
"smtpCpu": 0,
|
|
"smtpEnable": false,
|
|
"smtpEnabledEvents": "",
|
|
"smtpEncryptionType": "",
|
|
"smtpFrom": "",
|
|
"smtpFromName": "",
|
|
"smtpHost": "",
|
|
"smtpMemory": 0,
|
|
"smtpPassword": "",
|
|
"smtpPort": 1,
|
|
"smtpTo": "",
|
|
"smtpUsername": "",
|
|
"subAnnounce": "",
|
|
"subCertFile": "",
|
|
"subClashAutoDetect": false,
|
|
"subClashEnable": false,
|
|
"subClashEnableRouting": false,
|
|
"subClashPath": "",
|
|
"subClashRules": "",
|
|
"subClashURI": "",
|
|
"subClashUserAgentRegex": "",
|
|
"subDomain": "",
|
|
"subEnable": false,
|
|
"subEnableRouting": false,
|
|
"subEncrypt": false,
|
|
"subHideSettings": false,
|
|
"subIncyEnableRouting": false,
|
|
"subIncyRoutingRules": "",
|
|
"subJsonAlwaysArray": false,
|
|
"subJsonAutoDetect": false,
|
|
"subJsonEnable": false,
|
|
"subJsonFinalMask": "",
|
|
"subJsonMux": "",
|
|
"subJsonObservatory": "",
|
|
"subJsonPath": "",
|
|
"subJsonRules": "",
|
|
"subJsonURI": "",
|
|
"subJsonUserAgentRegex": "",
|
|
"subKeyFile": "",
|
|
"subListen": "",
|
|
"subPath": "",
|
|
"subPort": 1,
|
|
"subProfileUrl": "",
|
|
"subRoutingRules": "",
|
|
"subShowIdentityOnAllLinks": false,
|
|
"subSupportUrl": "",
|
|
"subThemeDir": "",
|
|
"subTitle": "",
|
|
"subURI": "",
|
|
"subUpdates": 0,
|
|
"tgBotAPIServer": "",
|
|
"tgBotBackup": false,
|
|
"tgBotChatId": "",
|
|
"tgBotEnable": false,
|
|
"tgBotProxy": "",
|
|
"tgBotToken": "",
|
|
"tgCpu": 0,
|
|
"tgEnabledEvents": "",
|
|
"tgLang": "",
|
|
"tgMemory": 0,
|
|
"tgRunTime": "",
|
|
"timeLocation": "",
|
|
"trafficDiff": 0,
|
|
"trustedProxyCIDRs": "",
|
|
"twoFactorEnable": false,
|
|
"twoFactorToken": "",
|
|
"warpUpdateInterval": 0,
|
|
"webBasePath": "",
|
|
"webCertFile": "",
|
|
"webDomain": "",
|
|
"webKeyFile": "",
|
|
"webListen": "",
|
|
"webPort": 1
|
|
},
|
|
"AllSettingView": {
|
|
"datepicker": "",
|
|
"expireDiff": 0,
|
|
"externalTrafficInformEnable": false,
|
|
"externalTrafficInformURI": "",
|
|
"hasApiToken": false,
|
|
"hasLdapPassword": false,
|
|
"hasNordSecret": false,
|
|
"hasSmtpPassword": false,
|
|
"hasTgBotToken": false,
|
|
"hasTwoFactorToken": false,
|
|
"hasWarpSecret": false,
|
|
"ipLimitAllowlist": "",
|
|
"ldapAutoCreate": false,
|
|
"ldapAutoDelete": false,
|
|
"ldapBaseDN": "",
|
|
"ldapBindDN": "",
|
|
"ldapDefaultExpiryDays": 0,
|
|
"ldapDefaultLimitIP": 0,
|
|
"ldapDefaultTotalGB": 0,
|
|
"ldapEnable": false,
|
|
"ldapFlagField": "",
|
|
"ldapHost": "",
|
|
"ldapInboundTags": "",
|
|
"ldapInsecureSkipVerify": false,
|
|
"ldapInvertFlag": false,
|
|
"ldapPassword": "",
|
|
"ldapPort": 0,
|
|
"ldapSyncCron": "",
|
|
"ldapTruthyValues": "",
|
|
"ldapUseTLS": false,
|
|
"ldapUserAttr": "",
|
|
"ldapUserFilter": "",
|
|
"ldapVlessField": "",
|
|
"outboundDownThreshold": 1,
|
|
"pageSize": 0,
|
|
"panelOutbound": "",
|
|
"remarkTemplate": "",
|
|
"restartXrayOnClientDisable": false,
|
|
"sessionMaxAge": 1,
|
|
"smtpCpu": 0,
|
|
"smtpEnable": false,
|
|
"smtpEnabledEvents": "",
|
|
"smtpEncryptionType": "",
|
|
"smtpFrom": "",
|
|
"smtpFromName": "",
|
|
"smtpHost": "",
|
|
"smtpMemory": 0,
|
|
"smtpPassword": "",
|
|
"smtpPort": 1,
|
|
"smtpTo": "",
|
|
"smtpUsername": "",
|
|
"subAnnounce": "",
|
|
"subCertFile": "",
|
|
"subClashAutoDetect": false,
|
|
"subClashEnable": false,
|
|
"subClashEnableRouting": false,
|
|
"subClashPath": "",
|
|
"subClashRules": "",
|
|
"subClashURI": "",
|
|
"subClashUserAgentRegex": "",
|
|
"subDomain": "",
|
|
"subEnable": false,
|
|
"subEnableRouting": false,
|
|
"subEncrypt": false,
|
|
"subHideSettings": false,
|
|
"subIncyEnableRouting": false,
|
|
"subIncyRoutingRules": "",
|
|
"subJsonAlwaysArray": false,
|
|
"subJsonAutoDetect": false,
|
|
"subJsonEnable": false,
|
|
"subJsonFinalMask": "",
|
|
"subJsonMux": "",
|
|
"subJsonObservatory": "",
|
|
"subJsonPath": "",
|
|
"subJsonRules": "",
|
|
"subJsonURI": "",
|
|
"subJsonUserAgentRegex": "",
|
|
"subKeyFile": "",
|
|
"subListen": "",
|
|
"subPath": "",
|
|
"subPort": 1,
|
|
"subProfileUrl": "",
|
|
"subRoutingRules": "",
|
|
"subShowIdentityOnAllLinks": false,
|
|
"subSupportUrl": "",
|
|
"subThemeDir": "",
|
|
"subTitle": "",
|
|
"subURI": "",
|
|
"subUpdates": 0,
|
|
"tgBotAPIServer": "",
|
|
"tgBotBackup": false,
|
|
"tgBotChatId": "",
|
|
"tgBotEnable": false,
|
|
"tgBotProxy": "",
|
|
"tgBotToken": "",
|
|
"tgCpu": 0,
|
|
"tgEnabledEvents": "",
|
|
"tgLang": "",
|
|
"tgMemory": 0,
|
|
"tgRunTime": "",
|
|
"timeLocation": "",
|
|
"trafficDiff": 0,
|
|
"trustedProxyCIDRs": "",
|
|
"twoFactorEnable": false,
|
|
"twoFactorToken": "",
|
|
"warpUpdateInterval": 0,
|
|
"webBasePath": "",
|
|
"webCertFile": "",
|
|
"webDomain": "",
|
|
"webKeyFile": "",
|
|
"webListen": "",
|
|
"webPort": 1
|
|
},
|
|
"ApiToken": {
|
|
"createdAt": 0,
|
|
"enabled": false,
|
|
"expiresAt": 0,
|
|
"id": 0,
|
|
"name": "",
|
|
"scope": "",
|
|
"token": ""
|
|
},
|
|
"ApiTokenView": {
|
|
"createdAt": 1736000000,
|
|
"enabled": true,
|
|
"expiresAt": 0,
|
|
"id": 2,
|
|
"name": "central-panel-a",
|
|
"scope": "admin",
|
|
"token": "new-token-string"
|
|
},
|
|
"Client": {
|
|
"adTag": "0123456789abcdef0123456789abcdef",
|
|
"allowedIPs": [
|
|
""
|
|
],
|
|
"auth": "",
|
|
"comment": "",
|
|
"created_at": 0,
|
|
"email": "",
|
|
"enable": false,
|
|
"expiryTime": 0,
|
|
"flow": "",
|
|
"group": "",
|
|
"id": "",
|
|
"keepAlive": 0,
|
|
"limitIp": 0,
|
|
"password": "",
|
|
"preSharedKey": "",
|
|
"privateKey": "",
|
|
"publicKey": "",
|
|
"reset": 0,
|
|
"resetDay": 0,
|
|
"resetMax": 0,
|
|
"reverse": null,
|
|
"secret": "ee1234567890abcdef1234567890abcd7777772e636c6f7564666c6172652e636f6d",
|
|
"security": "",
|
|
"subId": "",
|
|
"tgId": 0,
|
|
"totalGB": 0,
|
|
"trafficReset": "never",
|
|
"trafficResetDay": 1,
|
|
"updated_at": 0
|
|
},
|
|
"ClientInbound": {
|
|
"clientId": 0,
|
|
"createdAt": 0,
|
|
"flowOverride": "",
|
|
"inboundId": 0
|
|
},
|
|
"ClientRecord": {
|
|
"adTag": "",
|
|
"allowedIPs": "",
|
|
"auth": "",
|
|
"comment": "",
|
|
"createdAt": 0,
|
|
"email": "",
|
|
"enable": false,
|
|
"expiryTime": 0,
|
|
"flow": "",
|
|
"group": "",
|
|
"id": 0,
|
|
"keepAlive": 0,
|
|
"limitHwid": 0,
|
|
"limitIp": 0,
|
|
"password": "",
|
|
"preSharedKey": "",
|
|
"privateKey": "",
|
|
"publicKey": "",
|
|
"reset": 0,
|
|
"resetDay": 0,
|
|
"resetMax": 0,
|
|
"reverse": null,
|
|
"secret": "",
|
|
"security": "",
|
|
"subId": "",
|
|
"tgId": 0,
|
|
"totalGB": 0,
|
|
"trafficReset": "",
|
|
"trafficResetDay": 0,
|
|
"updatedAt": 0,
|
|
"uuid": ""
|
|
},
|
|
"ClientReverse": {
|
|
"tag": ""
|
|
},
|
|
"ClientTraffic": {
|
|
"down": 2097152,
|
|
"email": "user1",
|
|
"enable": true,
|
|
"expiryTime": 1735689600000,
|
|
"id": 14825,
|
|
"inboundId": 1,
|
|
"lastOnline": 1735680000000,
|
|
"lastSubFetch": 1735680000000,
|
|
"reset": 0,
|
|
"resetCount": 0,
|
|
"resetDay": 0,
|
|
"resetMax": 0,
|
|
"subId": "i7tvdpeffi0hvvf1",
|
|
"total": 10737418240,
|
|
"up": 1048576,
|
|
"uuid": "e18c9a96-71bf-48d4-933f-8b9a46d4290c"
|
|
},
|
|
"FallbackParentInfo": {
|
|
"masterId": 0,
|
|
"path": ""
|
|
},
|
|
"GeoCategory": {
|
|
"attributes": [
|
|
"ads",
|
|
"cn"
|
|
],
|
|
"code": "google",
|
|
"entries": 1284
|
|
},
|
|
"GeoCategoryPage": {
|
|
"items": [
|
|
{
|
|
"attributes": [
|
|
"ads",
|
|
"cn"
|
|
],
|
|
"code": "google",
|
|
"entries": 1284
|
|
}
|
|
],
|
|
"total": 1043
|
|
},
|
|
"GeoEntry": {
|
|
"kind": "domain",
|
|
"value": "google.com"
|
|
},
|
|
"GeoEntryPage": {
|
|
"items": [
|
|
{
|
|
"kind": "domain",
|
|
"value": "google.com"
|
|
}
|
|
],
|
|
"total": 1284
|
|
},
|
|
"GeoFile": {
|
|
"categories": 1043,
|
|
"error": "",
|
|
"kind": "site",
|
|
"modifiedAt": 1769558400000,
|
|
"name": "geosite.dat",
|
|
"size": 1467392
|
|
},
|
|
"GeodataTokenIssue": {
|
|
"code": "blabla",
|
|
"file": "geosite.dat",
|
|
"reason": "categoryMissing",
|
|
"token": "geosite:blabla"
|
|
},
|
|
"HistoryOfSeeders": {
|
|
"id": 0,
|
|
"seederName": ""
|
|
},
|
|
"Host": {
|
|
"address": "cdn.example.com",
|
|
"allowInsecure": false,
|
|
"alpn": [
|
|
""
|
|
],
|
|
"createdAt": 0,
|
|
"echConfigList": "",
|
|
"excludeFromSubTypes": [
|
|
""
|
|
],
|
|
"finalMask": "",
|
|
"fingerprint": "",
|
|
"groupId": "",
|
|
"hostHeader": "",
|
|
"id": 1,
|
|
"inboundId": 1,
|
|
"isDisabled": false,
|
|
"isHidden": false,
|
|
"keepSniBlank": false,
|
|
"mihomoIpVersion": "dual",
|
|
"mihomoX25519": false,
|
|
"muxParams": null,
|
|
"nodeGuids": [
|
|
""
|
|
],
|
|
"overrideSniFromAddress": false,
|
|
"path": "",
|
|
"pinnedPeerCertSha256": [
|
|
""
|
|
],
|
|
"port": 8443,
|
|
"remark": "cdn-front",
|
|
"security": "same",
|
|
"serverDescription": "",
|
|
"shuffleHost": false,
|
|
"sni": "",
|
|
"sockoptParams": null,
|
|
"sortOrder": 0,
|
|
"tags": [
|
|
""
|
|
],
|
|
"updatedAt": 0,
|
|
"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": [
|
|
{
|
|
"down": 2097152,
|
|
"email": "user1",
|
|
"enable": true,
|
|
"expiryTime": 1735689600000,
|
|
"id": 14825,
|
|
"inboundId": 1,
|
|
"lastOnline": 1735680000000,
|
|
"lastSubFetch": 1735680000000,
|
|
"reset": 0,
|
|
"resetCount": 0,
|
|
"resetDay": 0,
|
|
"resetMax": 0,
|
|
"subId": "i7tvdpeffi0hvvf1",
|
|
"total": 10737418240,
|
|
"up": 1048576,
|
|
"uuid": "e18c9a96-71bf-48d4-933f-8b9a46d4290c"
|
|
}
|
|
],
|
|
"disableFlow": false,
|
|
"down": 0,
|
|
"enable": true,
|
|
"expiryTime": 0,
|
|
"fallbackParent": null,
|
|
"id": 1,
|
|
"lastTrafficResetTime": 0,
|
|
"listen": "",
|
|
"nodeId": null,
|
|
"originNodeGuid": "",
|
|
"port": 443,
|
|
"protocol": "vless",
|
|
"remark": "VLESS-443",
|
|
"settings": null,
|
|
"shareAddr": "",
|
|
"shareAddrStrategy": "node",
|
|
"sniffing": null,
|
|
"streamSettings": null,
|
|
"subSortIndex": 1,
|
|
"tag": "in-443-tcp",
|
|
"total": 0,
|
|
"trafficReset": "never",
|
|
"trafficResetDay": 1,
|
|
"up": 0
|
|
},
|
|
"InboundClientIps": {
|
|
"clientEmail": "",
|
|
"id": 0,
|
|
"ips": null
|
|
},
|
|
"InboundFallback": {
|
|
"alpn": "",
|
|
"childId": 0,
|
|
"dest": "",
|
|
"id": 0,
|
|
"masterId": 0,
|
|
"name": "",
|
|
"path": "",
|
|
"sortOrder": 0,
|
|
"xver": 0
|
|
},
|
|
"InboundOption": {
|
|
"enable": true,
|
|
"id": 1,
|
|
"listen": "",
|
|
"mtprotoDomain": "",
|
|
"nodeAddress": "",
|
|
"nodeId": null,
|
|
"port": 443,
|
|
"protocol": "vless",
|
|
"remark": "VLESS-443",
|
|
"shareAddr": "",
|
|
"shareAddrStrategy": "",
|
|
"ssMethod": "",
|
|
"tag": "in-443-tcp",
|
|
"tlsFlowCapable": true,
|
|
"wgDns": "",
|
|
"wgMtu": 0,
|
|
"wgPublicKey": ""
|
|
},
|
|
"Msg": {
|
|
"msg": "",
|
|
"obj": null,
|
|
"success": false
|
|
},
|
|
"Node": {
|
|
"activeCount": 23,
|
|
"address": "node1.example.com",
|
|
"allowPrivateAddress": false,
|
|
"basePath": "/",
|
|
"clientCount": 27,
|
|
"configDirty": false,
|
|
"configDirtyAt": 0,
|
|
"cpuPct": 23.5,
|
|
"createdAt": 1700000000,
|
|
"depletedCount": 1,
|
|
"disabledCount": 3,
|
|
"enable": true,
|
|
"guid": "",
|
|
"id": 1,
|
|
"inboundCount": 5,
|
|
"inboundSyncMode": "all",
|
|
"inboundTags": [
|
|
""
|
|
],
|
|
"lastError": "",
|
|
"lastHeartbeat": 1700000000,
|
|
"latencyMs": 42,
|
|
"memPct": 45.1,
|
|
"name": "de-fra-1",
|
|
"netDown": 2097152,
|
|
"netUp": 1048576,
|
|
"onlineCount": 3,
|
|
"outboundTag": "",
|
|
"panelVersion": "v3.x.x",
|
|
"parentGuid": "",
|
|
"pinnedCertSha256": "",
|
|
"port": 2053,
|
|
"remark": "",
|
|
"scheme": "https",
|
|
"status": "online",
|
|
"tlsVerifyMode": "verify",
|
|
"transitive": false,
|
|
"updatedAt": 1700000000,
|
|
"uptimeSecs": 86400,
|
|
"xrayError": "",
|
|
"xrayState": "",
|
|
"xrayVersion": "25.10.31"
|
|
},
|
|
"NodeMutationRequest": {
|
|
"address": "",
|
|
"allowPrivateAddress": false,
|
|
"apiToken": null,
|
|
"basePath": "",
|
|
"clearApiToken": false,
|
|
"enable": false,
|
|
"id": 0,
|
|
"inboundSyncMode": "all",
|
|
"inboundTags": [
|
|
""
|
|
],
|
|
"name": "",
|
|
"outboundTag": "",
|
|
"pinnedCertSha256": "",
|
|
"port": 1,
|
|
"remark": "",
|
|
"scheme": "http",
|
|
"tlsVerifyMode": "verify"
|
|
},
|
|
"NodeView": {
|
|
"activeCount": 20,
|
|
"address": "node.example.com",
|
|
"allowPrivateAddress": false,
|
|
"basePath": "/",
|
|
"clientCount": 25,
|
|
"configDirty": false,
|
|
"configDirtyAt": 0,
|
|
"cpuPct": 12.5,
|
|
"createdAt": 1700000000,
|
|
"depletedCount": 1,
|
|
"disabledCount": 2,
|
|
"enable": true,
|
|
"guid": "node-guid",
|
|
"hasApiToken": true,
|
|
"id": 1,
|
|
"inboundCount": 3,
|
|
"inboundSyncMode": "all",
|
|
"inboundTags": [
|
|
"in-443-tcp"
|
|
],
|
|
"lastError": "",
|
|
"lastHeartbeat": 1700000000,
|
|
"latencyMs": 42,
|
|
"memPct": 45.2,
|
|
"name": "edge-1",
|
|
"netDown": 1048576,
|
|
"netUp": 2097152,
|
|
"onlineCount": 5,
|
|
"outboundTag": "direct",
|
|
"panelVersion": "v3.x.x",
|
|
"parentGuid": "",
|
|
"pinnedCertSha256": "",
|
|
"port": 2053,
|
|
"remark": "Primary edge",
|
|
"scheme": "https",
|
|
"status": "online",
|
|
"tlsVerifyMode": "verify",
|
|
"transitive": false,
|
|
"updatedAt": 1700003600,
|
|
"uptimeSecs": 86400,
|
|
"xrayError": "",
|
|
"xrayState": "running",
|
|
"xrayVersion": "25.10.31"
|
|
},
|
|
"OutboundTraffics": {
|
|
"down": 0,
|
|
"id": 0,
|
|
"tag": "",
|
|
"total": 0,
|
|
"up": 0
|
|
},
|
|
"PanelUpdateStatus": {
|
|
"exitCode": 0,
|
|
"finishedAt": 1735689612,
|
|
"runId": "1735689600123456789",
|
|
"state": "success"
|
|
},
|
|
"ProbeResultUI": {
|
|
"cpuPct": 12.5,
|
|
"error": "",
|
|
"latencyMs": 42,
|
|
"memPct": 45.2,
|
|
"panelVersion": "v3.x.x",
|
|
"status": "online",
|
|
"uptimeSecs": 86400,
|
|
"xrayError": "",
|
|
"xrayState": "",
|
|
"xrayVersion": "25.10.31"
|
|
},
|
|
"RealityScanResult": {
|
|
"alpn": "h2",
|
|
"certChainValid": true,
|
|
"certIssuer": "Google Trust Services",
|
|
"certSubject": "cloudflare.com",
|
|
"certValid": true,
|
|
"curveID": "X25519",
|
|
"feasible": true,
|
|
"h2": true,
|
|
"host": "www.cloudflare.com",
|
|
"ip": "104.16.124.96",
|
|
"latencyMs": 180,
|
|
"notAfter": "2026-08-01T00:00:00Z",
|
|
"port": 443,
|
|
"privateTarget": false,
|
|
"reason": "",
|
|
"serverNames": [
|
|
""
|
|
],
|
|
"target": "www.cloudflare.com:443",
|
|
"tls13": true,
|
|
"tlsVersion": "1.3",
|
|
"x25519": true
|
|
},
|
|
"Setting": {
|
|
"id": 0,
|
|
"key": "",
|
|
"value": ""
|
|
},
|
|
"SubBalancer": {
|
|
"createdAt": 1710000000000,
|
|
"enabled": true,
|
|
"id": 1,
|
|
"inboundIds": [
|
|
1,
|
|
3
|
|
],
|
|
"remark": "auto-fastest",
|
|
"sortOrder": 1,
|
|
"strategy": "random",
|
|
"updatedAt": 1710000000000
|
|
},
|
|
"User": {
|
|
"id": 0,
|
|
"password": "",
|
|
"username": ""
|
|
}
|
|
};
|