feat: apply inbound/outbound/routing changes live via Xray gRPC API

Add a hot-apply layer that computes a diff between the old and new
generated config and applies only the changed parts through the Xray
gRPC HandlerService and RoutingService, avoiding a full process restart
whenever possible. A restart is still performed when sections that have
no reload API (log, dns, policy, observatory, ...) actually change.

Key additions:
- internal/xray/hot_diff.go: ComputeHotDiff with canonical-JSON
  comparison (sorted keys, null=absent, full number precision) so UI
  reformatting never triggers a spurious restart
- internal/xray/api.go: AddOutbound/DelOutbound, ApplyRoutingConfig,
  GetBalancerInfo, SetBalancerTarget, TestRoute gRPC wrappers
- internal/web/service/xray.go: tryHotApply, ensureAPIServices,
  GetBalancersStatus, OverrideBalancer, TestRoute service methods
- internal/web/controller/xray_setting.go: balancerStatus,
  balancerOverride, routeTest API endpoints
- frontend: BalancersTab live-status/override columns, RouteTester
  component, Restart button removed (Save now hot-applies)
- balancer-helpers.ts: syncObservatories never creates observatory
  sections for random/roundRobin balancers (no reload API → restart)
- i18n: balancerLive/Override/routeTester keys added to all 13 locales
This commit is contained in:
MHSanaei
2026-06-10 23:01:33 +02:00
parent 3092326d9e
commit 6b16d8c37a
32 changed files with 2209 additions and 109 deletions
+15
View File
@@ -1183,6 +1183,21 @@
"Balancers": "Балансери",
"balancerTagRequired": "Тег обов'язковий",
"balancerSelectorRequired": "Виберіть принаймні один вихідний",
"balancerLive": "Поточна ціль",
"balancerOverride": "Примусова ціль",
"balancerOverridePh": "Авто (стратегія)",
"balancerLiveRefresh": "Оновити стан балансувальника",
"balancerNotRunning": "Цей балансувальник неактивний у запущеному Xray — збережіть зміни або спочатку запустіть Xray",
"routeTester": "Тест маршруту",
"routeTesterDesc": "Запитайте запущений Xray, через який вихідний буде оброблено з'єднання. Реальний трафік не надсилається — рішення надходить безпосередньо від живого рушія маршрутизації.",
"routeTesterDest": "Домен або IP",
"routeTesterPort": "Порт",
"routeTesterInbound": "Вхідний",
"routeTesterProtocol": "Виявлений протокол",
"routeTesterTest": "Тест маршруту",
"routeTesterMatchedOutbound": "Відповідний вихідний",
"routeTesterViaBalancer": "через балансувальник",
"routeTesterDefaultOutbound": "Жодне правило маршрутизації не збіглося — трафік надходить до вихідного за замовчуванням (першого).",
"OutboundsDesc": "Встановити шлях вихідного трафіку.",
"Routings": "Правила маршрутизації",
"RoutingsDesc": "Пріоритет кожного правила важливий!",