From 8b962d1ae89d9c073bac500ce65441835ae388c6 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 13 Sep 2026 20:57:39 +0200 Subject: [PATCH 1/5] feat(proxies): show how many egress IPs actually served a proxy pool A pool of N members reads as N exits, but several members can leave through the same egress IP, and the per-IP 429 lockout then pauses every connection behind it at once. The proxy log already records the entry point used (proxy_host, proxy_port) and the egress IP observed, so the pool editor can show what really happened over the last 24 h: distinct egress IPs, connections, and the most connections seen behind one IP. Only numbers are returned. The read has its own route, GET /api/settings/proxies/pool/egress-observation, behind the same management auth as the pool route, which stays unchanged. The scope is normalized like the pool read, a result is cached for 30 seconds, any failure answers null and hides the line, and PROXY_POOL_EGRESS_OBSERVATION=false turns it off. ProxyRegistryManager.tsx grows by two lines (import and mount); its file-size cap is raised with a note, everything else lives in new files under the cap. --- .env.example | 4 + .../features/13581-pool-egress-observation.md | 1 + config/quality/file-size-baseline.json | 3 +- docs/openapi.yaml | 8 + docs/reference/ENVIRONMENT.md | 1 + .../components/PoolEgressObservation.tsx | 75 ++++++++ .../components/ProxyRegistryManager.tsx | 2 + .../proxies/pool/egress-observation/route.ts | 37 ++++ src/i18n/messages/en.json | 3 + src/i18n/messages/vi.json | 3 + src/lib/db/proxyLogs.ts | 46 +++++ src/lib/proxyPoolEgressObservation.ts | 65 +++++++ ...roxy-pool-egress-observation-route.test.ts | 131 +++++++++++++ .../proxy-pool-egress-observation.test.ts | 175 ++++++++++++++++++ tests/unit/ui/PoolEgressObservation.test.tsx | 96 ++++++++++ 15 files changed, 649 insertions(+), 1 deletion(-) create mode 100644 changelog.d/features/13581-pool-egress-observation.md create mode 100644 src/app/(dashboard)/dashboard/settings/components/PoolEgressObservation.tsx create mode 100644 src/app/api/settings/proxies/pool/egress-observation/route.ts create mode 100644 src/lib/proxyPoolEgressObservation.ts create mode 100644 tests/unit/proxy-pool-egress-observation-route.test.ts create mode 100644 tests/unit/proxy-pool-egress-observation.test.ts create mode 100644 tests/unit/ui/PoolEgressObservation.test.tsx diff --git a/.env.example b/.env.example index 4bf685dc94a..202fd40aea6 100644 --- a/.env.example +++ b/.env.example @@ -2237,6 +2237,10 @@ APP_LOG_TO_FILE=true # proxy — only the operator sets active/inactive (a flaky probe must not strand an # assigned proxy; #6246). Set "true" to restore the legacy test-and-set behaviour. # PROXY_HEALTH_AUTO_DEACTIVATE=false +# Show, under a proxy pool in the dashboard, how many observed egress IPs served its +# members over the last 24 h and how many connections used them (read-only, computed +# from the proxy log, never used for routing). Set "false" (or 0, no, off) to turn it off. +# PROXY_POOL_EGRESS_OBSERVATION=true # Allow OAuth and provider validation flows to bypass a pinned proxy and connect # directly when proxy reachability pre-checks fail. Default: false. diff --git a/changelog.d/features/13581-pool-egress-observation.md b/changelog.d/features/13581-pool-egress-observation.md new file mode 100644 index 00000000000..6b8fab657f8 --- /dev/null +++ b/changelog.d/features/13581-pool-egress-observation.md @@ -0,0 +1 @@ +- **feat(proxies):** the proxy pool editor shows, for the last 24 h, how many distinct egress IPs actually served the pool's members, how many connections went through them and the most seen behind one IP, read from the proxy log through a separate route so it can never break the pool screen ([#13581](https://github.com/diegosouzapw/OmniRoute/pull/13581)) — thanks @maxmad64bis diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index b2f30f413a3..472e6655e9c 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,5 +1,6 @@ { "_rebaseline_2026_09_15_13580_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1202. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", + "_rebaseline_2026_09_13_13581_pool_egress_observation": "PR #13581 own growth: src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx 1475->1477 (+2 = the PoolEgressObservation import and its one-line mount under the pool members label). The observation itself lives outside the frozen file, all under cap: PoolEgressObservation.tsx, the dedicated GET /api/settings/proxies/pool/egress-observation route, src/lib/proxyPoolEgressObservation.ts and getPoolEgressObservation in src/lib/db/proxyLogs.ts. Only the mount point is irreducible. Covered by tests/unit/proxy-pool-egress-observation.test.ts, tests/unit/proxy-pool-egress-observation-route.test.ts and tests/unit/ui/PoolEgressObservation.test.tsx.", "_rebaseline_2026_09_15_13439_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/combo/executeTargetAttempt.ts->1228. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13672_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/combo/executeTargetAttempt.ts->1223; open-sse/services/combo/roundRobinCombo.ts->1213. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13441_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/lib/db/core.ts->1770. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", @@ -471,7 +472,7 @@ "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631, "src/app/(dashboard)/dashboard/providers/page.tsx": 2025, "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222, - "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475, + "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1477, "src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271, "src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1607, "src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1597, diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 5e49a388dc8..5056c598b8c 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -11543,6 +11543,14 @@ paths: responses: "200": description: OK + /api/settings/proxies/pool/egress-observation: + get: + tags: + - Settings + summary: "GET settings › proxies › pool › egress observation" + responses: + "200": + description: OK /api/settings/proxy/cloudflare-deploy: post: tags: diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index f29f0ad0aa1..4a65d41ba8e 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -1114,6 +1114,7 @@ Anthropic-compatible provider instead. | `PROXY_HEALTH_TEST_STAGGER_MS` | `100` | `src/lib/proxyHealth/probeTarget.ts` | Delay in ms between two probe departures inside a batch. Without it the whole batch leaves at the same moment and a shared egress IP can trip a rate-limited target. Set to `0` to disable the spacing; capped at 5000. | | `PROXY_HEALTH_USE_PROVIDER_TARGET` | `true` | `src/lib/proxyHealth/providerProbeTarget.ts` | Set "false" to stop probing the real host of a proxy's assigned provider (`GET /models`, no API key) and always use `PROXY_HEALTH_TEST_URL` instead. | | `PROXY_HEALTH_AUTO_DEACTIVATE` | `false` | `src/lib/proxyHealth/statusPolicy.ts` | When `false` (default), automated reachability probes (the scheduler + the `/api/settings/proxies/auto-test` "Test All" button) are **read-only** and never write a proxy's status — only the operator sets active/inactive, so a flaky probe can't strand an assigned proxy (#6246). Set `true` to restore the legacy test-and-set behaviour. | +| `PROXY_POOL_EGRESS_OBSERVATION` | `true` | `src/lib/proxyPoolEgressObservation.ts` | Set `false` (or `0`, `no`, `off`) to turn off the read-only pool egress observation shown under a proxy pool in the dashboard (distinct observed egress IPs and connections over 24 h, from the proxy log). It never affects routing. | | `PROXY_AUTO_REMOVE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler auto-remove proxies after repeated consecutive failures. | | `PROXY_AUTO_REMOVE_AFTER` | `3` | `src/lib/proxyHealth/scheduler.ts` | Consecutive failures before the scheduler auto-removes a proxy (when `PROXY_AUTO_REMOVE=true`). | | `PROXY_AUTO_DISABLE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler soft-disable (status `dead`, never deleted) a proxy after repeated consecutive failures, instead of removing it. Non-destructive alternative to `PROXY_AUTO_REMOVE`: the proxy drops out of pool/rotation resolution immediately (the alive-status filter used by scope-pool resolution already excludes it) and is automatically re-activated once it starts passing probes again. Shares the `PROXY_AUTO_REMOVE_AFTER` threshold. If both flags are `true`, `PROXY_AUTO_REMOVE` wins. | diff --git a/src/app/(dashboard)/dashboard/settings/components/PoolEgressObservation.tsx b/src/app/(dashboard)/dashboard/settings/components/PoolEgressObservation.tsx new file mode 100644 index 00000000000..73e39e9f433 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/PoolEgressObservation.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useTranslations } from "next-intl"; + +type Observation = { + connections: number; + distinctExits: number; + maxConnectionsOnOneExit: number; + windowHours: number; +}; + +const OBSERVATION_KEYS = [ + "connections", + "distinctExits", + "maxConnectionsOnOneExit", + "windowHours", +] as const; + +function isObservation(value: unknown): value is Observation { + if (!value || typeof value !== "object") return false; + const record = value as Record; + return OBSERVATION_KEYS.every((key) => typeof record[key] === "number"); +} + +/** + * One line under a pool's member list: how many observed egress IPs actually served its + * members over the window, and how many connections the busiest one carried. Renders + * nothing when the observation is off, failed, or the request itself errored. + */ +export function PoolEgressObservation({ query }: { query: string }) { + const t = useTranslations("proxyRegistry"); + const [loaded, setLoaded] = useState<{ query: string; observation: Observation | null }>({ + query: "", + observation: null, + }); + + useEffect(() => { + let cancelled = false; + fetch(`/api/settings/proxies/pool/egress-observation?${query}`) + .then((res) => (res.ok ? res.json() : null)) + .then((body: unknown) => { + if (!cancelled) setLoaded({ query, observation: isObservation(body) ? body : null }); + }) + .catch(() => { + if (!cancelled) setLoaded({ query, observation: null }); + }); + return () => { + cancelled = true; + }; + }, [query]); + + const observation = loaded.query === query ? loaded.observation : null; + if (!observation) return null; + + const text = + observation.connections === 0 + ? t("poolEgressObservationEmpty", { hours: observation.windowHours }) + : t("poolEgressObservation", { + exits: observation.distinctExits, + connections: observation.connections, + max: observation.maxConnectionsOnOneExit, + hours: observation.windowHours, + }); + + return ( +

+ {text} +

+ ); +} diff --git a/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx b/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx index 3af5b91361c..2978266e086 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx @@ -8,6 +8,7 @@ import { ProxyStatusBadge } from "./ProxyStatusBadge"; import { ProxyHealthCell } from "./ProxyHealthCell"; import { ProxyBatchActions } from "./ProxyBatchActions"; import { ProxyCheckboxCell } from "./ProxyCheckboxCell"; +import { PoolEgressObservation } from "./PoolEgressObservation"; import { parseBulkImportText, type ParsedProxyEntry, @@ -1238,6 +1239,7 @@ import { + {poolMembers.length === 0 ? (
{t("poolNoMembers")} diff --git a/src/app/api/settings/proxies/pool/egress-observation/route.ts b/src/app/api/settings/proxies/pool/egress-observation/route.ts new file mode 100644 index 00000000000..c646341bcae --- /dev/null +++ b/src/app/api/settings/proxies/pool/egress-observation/route.ts @@ -0,0 +1,37 @@ +import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; +import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; +import { readPoolEgressObservation } from "@/lib/proxyPoolEgressObservation"; + +// Observed egress spread of a proxy pool, read from the proxy log (numbers only). Kept +// apart from GET /api/settings/proxies/pool on purpose: a failure here answers null and +// can never break the pool editor. Same management-auth tier as the pool route. +// +// GET ?scope=&scopeId= -> { connections, distinctExits, maxConnectionsOnOneExit, windowHours } +// | null (read failed or PROXY_POOL_EGRESS_OBSERVATION=false) + +export async function GET(request: Request) { + const authError = await requireManagementAuth(request); + if (authError) return authError; + try { + const { searchParams } = new URL(request.url); + const scope = searchParams.get("scope"); + if (!scope) { + return createErrorResponse({ + status: 400, + message: "scope is required", + type: "invalid_request", + }); + } + const scopeId = searchParams.get("scopeId")?.trim() || null; + if (scope !== "global" && !scopeId) { + return createErrorResponse({ + status: 400, + message: "scopeId is required for provider/account/combo/key scope", + type: "invalid_request", + }); + } + return Response.json(readPoolEgressObservation(scope, scope === "global" ? null : scopeId)); + } catch (error) { + return createErrorResponseFromUnknown(error, "Failed to load pool egress observation"); + } +} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 18d43735ce5..f2ba140f19c 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -10966,6 +10966,9 @@ "strategyLatency": "Latency-optimized", "poolMembersLabel": "Pool members ({count})", "poolNoMembers": "No proxies in this pool yet.", + "poolEgressObservation": "{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "No traffic observed in the last {hours} h", + "poolEgressObservationHint": "Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Remove", "poolRemoveFailed": "Failed to remove the proxy from the pool", "poolAddLabel": "Add a proxy", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index e8e3097dc41..da27606218a 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Tối ưu độ trễ (Latency-optimized)", "poolMembersLabel": "Thành viên nhóm proxy ({count})", "poolNoMembers": "Chưa có proxy nào trong nhóm này.", + "poolEgressObservation": "{exits, plural, other {# lối ra}} được dùng bởi {connections, plural, other {# kết nối}} · tối đa {max} trên một lối ra · {hours} giờ qua", + "poolEgressObservationEmpty": "Không ghi nhận lưu lượng trong {hours} giờ qua", + "poolEgressObservationHint": "Tính từ nhật ký proxy trong khoảng thời gian: số IP đầu ra khác nhau quan sát được của các thành viên nhóm proxy này và số kết nối OmniRoute đã dùng chúng. \"Trên một lối ra\" nghĩa là trong cả khoảng thời gian, không phải cùng lúc. Khóa 429 theo IP chỉ tạm dừng các kết nối cùng họ nhà cung cấp.", "poolRemove": "Gỡ bỏ", "poolRemoveFailed": "Không thể gỡ proxy khỏi nhóm", "poolAddLabel": "Thêm proxy", diff --git a/src/lib/db/proxyLogs.ts b/src/lib/db/proxyLogs.ts index bb1e6ea7b3e..f4b05300885 100644 --- a/src/lib/db/proxyLogs.ts +++ b/src/lib/db/proxyLogs.ts @@ -67,3 +67,49 @@ export function getRecentEgressIpForConnection( if (!row) return null; return { egressIp: row.egress_ip, at: row.timestamp }; } + +export type PoolEgressObservationCounts = { + connections: number; + distinctExits: number; + maxConnectionsOnOneExit: number; +}; + +/** + * How many distinct observed egress IPs served a proxy pool's members since `since`, how + * many OmniRoute connections went through them, and the most connections seen behind one + * egress IP over that window. Members are matched to log rows by host and port, so two + * registry rows sharing one entry point count together. Only numbers leave this function. + * `scope` and `scopeId` must already be normalized (normalizeScope and + * normalizeAssignmentScopeId); an empty pool simply matches no rows. + */ +export function getPoolEgressObservation( + scope: string, + scopeId: string | null, + since: string +): PoolEgressObservationCounts { + const db = getDbInstance(); + const perExit = db + .prepare( + `SELECT COUNT(DISTINCT l.connection_id) AS n + FROM proxy_logs l + JOIN proxy_registry r ON l.proxy_host = r.host AND l.proxy_port = r.port + WHERE r.id IN (SELECT proxy_id FROM proxy_assignments WHERE scope = ? AND scope_id IS ?) + AND l.timestamp >= ? AND l.egress_ip IS NOT NULL AND l.connection_id IS NOT NULL + GROUP BY l.egress_ip` + ) + .all(scope, scopeId, since) as Array<{ n: number }>; + const total = db + .prepare( + `SELECT COUNT(DISTINCT l.connection_id) AS n + FROM proxy_logs l + JOIN proxy_registry r ON l.proxy_host = r.host AND l.proxy_port = r.port + WHERE r.id IN (SELECT proxy_id FROM proxy_assignments WHERE scope = ? AND scope_id IS ?) + AND l.timestamp >= ? AND l.egress_ip IS NOT NULL AND l.connection_id IS NOT NULL` + ) + .get(scope, scopeId, since) as { n: number }; + return { + connections: total.n, + distinctExits: perExit.length, + maxConnectionsOnOneExit: perExit.reduce((max, row) => Math.max(max, row.n), 0), + }; +} diff --git a/src/lib/proxyPoolEgressObservation.ts b/src/lib/proxyPoolEgressObservation.ts new file mode 100644 index 00000000000..3295375b2c7 --- /dev/null +++ b/src/lib/proxyPoolEgressObservation.ts @@ -0,0 +1,65 @@ +/** + * Observed egress spread of a proxy pool, for the dashboard pool editor. Read-only and + * never on the routing path: a failure returns null so it cannot break the pool screen. + * The scope is normalized exactly like the pool read (key -> account, global -> + * "__global__"), and a result is cached for 30 seconds per normalized scope. + */ +import { + EGRESS_IP_LOOKUP_WINDOW_MS, + getPoolEgressObservation, + type PoolEgressObservationCounts, +} from "@/lib/db/proxyLogs"; +import { normalizeAssignmentScopeId, normalizeScope } from "@/lib/db/proxies/mappers"; +import { flushProxyLogsSync } from "@/lib/proxyLogger"; + +export type PoolEgressObservation = PoolEgressObservationCounts & { windowHours: number }; + +const CACHE_TTL_MS = 30_000; +const CACHE_MAX_ENTRIES = 200; +const DISABLED_VALUES = ["false", "0", "no", "off"]; + +const cache = new Map(); + +/** On unless PROXY_POOL_EGRESS_OBSERVATION is false, 0, no or off. */ +export function isPoolEgressObservationEnabled(env: NodeJS.ProcessEnv = process.env): boolean { + const raw = (env.PROXY_POOL_EGRESS_OBSERVATION ?? "").trim().toLowerCase(); + return !DISABLED_VALUES.includes(raw); +} + +export function readPoolEgressObservation( + scope: string, + scopeId: string | null, + nowMs: number = Date.now() +): PoolEgressObservation | null { + if (!isPoolEgressObservationEnabled()) return null; + const normalizedScope = normalizeScope(scope); + const normalizedScopeId = normalizeAssignmentScopeId(normalizedScope, scopeId); + const key = `${normalizedScope}:${normalizedScopeId ?? ""}`; + + const hit = cache.get(key); + if (hit && nowMs - hit.at < CACHE_TTL_MS) return hit.value; + + let value: PoolEgressObservation; + try { + flushProxyLogsSync(); + const since = new Date(nowMs - EGRESS_IP_LOOKUP_WINDOW_MS).toISOString(); + value = { + ...getPoolEgressObservation(normalizedScope, normalizedScopeId, since), + windowHours: EGRESS_IP_LOOKUP_WINDOW_MS / (60 * 60 * 1000), + }; + } catch { + // Observer only: a failed read hides the line instead of failing the pool screen. + return null; + } + + if (!cache.has(key) && cache.size >= CACHE_MAX_ENTRIES) { + const oldest = cache.keys().next().value; + if (oldest !== undefined) cache.delete(oldest); + } + cache.set(key, { at: nowMs, value }); + return value; +} + +export function resetPoolEgressObservationCache(): void { + cache.clear(); +} diff --git a/tests/unit/proxy-pool-egress-observation-route.test.ts b/tests/unit/proxy-pool-egress-observation-route.test.ts new file mode 100644 index 00000000000..151cd4968e7 --- /dev/null +++ b/tests/unit/proxy-pool-egress-observation-route.test.ts @@ -0,0 +1,131 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; + +// The pool egress observation has its own route so that a failure there can never break +// GET /api/settings/proxies/pool. These tests pin its shape, its null-on-failure contract +// and its parameter checks. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-pool-egress-route-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; +delete process.env.INITIAL_PASSWORD; // auth not required in this test env +delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const observation = await import("../../src/lib/proxyPoolEgressObservation.ts"); +const { GET } = await import("../../src/app/api/settings/proxies/pool/egress-observation/route.ts"); +const poolRoute = await import("../../src/app/api/settings/proxies/pool/route.ts"); + +function resetStorage() { + delete process.env.INITIAL_PASSWORD; + delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + observation.resetPoolEgressObservationCache(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(() => { + resetStorage(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function request(base: string, query: Record): Request { + const params = new URLSearchParams(query); + return new Request(`http://localhost${base}?${params.toString()}`, { method: "GET" }); +} + +const OBSERVATION_PATH = "/api/settings/proxies/pool/egress-observation"; + +async function pooledProxyWithTraffic() { + const proxy = await proxiesDb.createProxy({ + name: "member", + type: "http", + host: "10.9.1.1", + port: 21001, + }); + await proxiesDb.addProxyToScopePool("provider", "openai", proxy.id); + core + .getDbInstance() + .prepare( + `INSERT INTO proxy_logs (id, timestamp, status, proxy_type, proxy_host, proxy_port, level, connection_id, egress_ip) + VALUES (?, ?, 'success', 'http', '10.9.1.1', 21001, 'provider', 'c1', '203.0.113.1')` + ) + .run(randomUUID(), new Date(Date.now() - 60_000).toISOString()); +} + +test("returns the observation with exactly the four documented keys", async () => { + await pooledProxyWithTraffic(); + const response = await GET(request(OBSERVATION_PATH, { scope: "provider", scopeId: "openai" })); + assert.equal(response.status, 200); + const body = (await response.json()) as Record; + assert.deepEqual(Object.keys(body).sort(), [ + "connections", + "distinctExits", + "maxConnectionsOnOneExit", + "windowHours", + ]); + assert.deepEqual(body, { + connections: 1, + distinctExits: 1, + maxConnectionsOnOneExit: 1, + windowHours: 24, + }); +}); + +test("returns zeros for an empty pool and leaves the pool route untouched", async () => { + const response = await GET(request(OBSERVATION_PATH, { scope: "provider", scopeId: "nobody" })); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { + connections: 0, + distinctExits: 0, + maxConnectionsOnOneExit: 0, + windowHours: 24, + }); + + const pool = await poolRoute.GET( + request("/api/settings/proxies/pool", { scope: "provider", scopeId: "nobody" }) + ); + assert.equal(pool.status, 200); + const poolBody = (await pool.json()) as Record; + assert.deepEqual(Object.keys(poolBody).sort(), ["members", "strategy", "total"]); +}); + +test("returns null with status 200 when the read fails", async () => { + await pooledProxyWithTraffic(); + const db = core.getDbInstance(); + db.exec("ALTER TABLE proxy_logs RENAME TO proxy_logs_hidden"); + try { + const response = await GET(request(OBSERVATION_PATH, { scope: "provider", scopeId: "openai" })); + assert.equal(response.status, 200); + assert.equal(await response.json(), null); + } finally { + db.exec("ALTER TABLE proxy_logs_hidden RENAME TO proxy_logs"); + } +}); + +test("returns null when the kill switch is off", async () => { + await pooledProxyWithTraffic(); + process.env.PROXY_POOL_EGRESS_OBSERVATION = "false"; + const response = await GET(request(OBSERVATION_PATH, { scope: "provider", scopeId: "openai" })); + assert.equal(response.status, 200); + assert.equal(await response.json(), null); +}); + +test("rejects a missing scope, or a missing scopeId outside global, like the pool route", async () => { + const noScope = await GET(request(OBSERVATION_PATH, {})); + assert.equal(noScope.status, 400); + const noScopeId = await GET(request(OBSERVATION_PATH, { scope: "provider" })); + assert.equal(noScopeId.status, 400); + const global = await GET(request(OBSERVATION_PATH, { scope: "global" })); + assert.equal(global.status, 200); +}); diff --git a/tests/unit/proxy-pool-egress-observation.test.ts b/tests/unit/proxy-pool-egress-observation.test.ts new file mode 100644 index 00000000000..43304dfc71f --- /dev/null +++ b/tests/unit/proxy-pool-egress-observation.test.ts @@ -0,0 +1,175 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; + +// A pool's member count says nothing about how many egress IPs really served it. These +// tests pin the observation read from the proxy log: numbers only, the scope normalized +// the same way the pool itself is read, and any failure isolated as null. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-pool-egress-obs-")); +process.env.DATA_DIR = TEST_DATA_DIR; +delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const observation = await import("../../src/lib/proxyPoolEgressObservation.ts"); + +const HOUR_MS = 60 * 60 * 1000; +let nextPort = 20000; + +function resetStorage() { + observation.resetPoolEgressObservationCache(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(() => { + delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + resetStorage(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +type Member = { host: string; port: number }; + +async function registryProxy(): Promise { + nextPort++; + const proxy = await proxiesDb.createProxy({ + name: `proxy ${nextPort}`, + type: "http", + host: "10.9.0.1", + port: nextPort, + }); + return { id: proxy.id, host: "10.9.0.1", port: nextPort }; +} + +async function poolMember(scope: string, scopeId: string | null): Promise { + const proxy = await registryProxy(); + await proxiesDb.addProxyToScopePool(scope, scopeId, proxy.id); + return proxy; +} + +function logRow( + member: Member, + egressIp: string | null, + connectionId: string | null, + ageMs = HOUR_MS +) { + core + .getDbInstance() + .prepare( + `INSERT INTO proxy_logs (id, timestamp, status, proxy_type, proxy_host, proxy_port, level, connection_id, egress_ip) + VALUES (?, ?, 'success', 'http', ?, ?, 'provider', ?, ?)` + ) + .run( + randomUUID(), + new Date(Date.now() - ageMs).toISOString(), + member.host, + member.port, + connectionId, + egressIp + ); +} + +test("counts distinct exits, connections and the busiest exit of a pool", async () => { + const a = await poolMember("provider", "openai"); + const b = await poolMember("provider", "openai"); + const c = await poolMember("provider", "openai"); + for (const conn of ["c1", "c2", "c3", "c4"]) logRow(a, "203.0.113.1", conn); + for (const conn of ["c5", "c6", "c7"]) logRow(a, "203.0.113.2", conn); + for (const conn of ["c8", "c9"]) logRow(b, "203.0.113.3", conn); + for (const conn of ["c10", "c11"]) logRow(b, "203.0.113.4", conn); + logRow(c, "203.0.113.5", "c12"); + logRow(c, "203.0.113.5", "c1"); + logRow(c, "203.0.113.5", "c12"); + + const outsider = await registryProxy(); + logRow(outsider, "198.51.100.9", "x1"); + logRow(a, null, "x2"); + logRow(a, "203.0.113.9", null); + logRow(a, "203.0.113.8", "x3", 25 * HOUR_MS); + + assert.deepEqual(observation.readPoolEgressObservation("provider", "openai"), { + connections: 12, + distinctExits: 5, + maxConnectionsOnOneExit: 4, + windowHours: 24, + }); +}); + +test("the global pool is read under its stored scope id", async () => { + const member = await poolMember("global", null); + logRow(member, "203.0.113.1", "c1"); + assert.deepEqual(observation.readPoolEgressObservation("global", null), { + connections: 1, + distinctExits: 1, + maxConnectionsOnOneExit: 1, + windowHours: 24, + }); +}); + +test("scope key is the account scope and shares its cache entry", async () => { + const member = await poolMember("account", "acc-1"); + logRow(member, "203.0.113.1", "c1"); + const now = Date.now(); + assert.equal(observation.readPoolEgressObservation("account", "acc-1", now)?.connections, 1); + + logRow(member, "203.0.113.1", "c2"); + assert.equal(observation.readPoolEgressObservation("key", "acc-1", now + 1000)?.connections, 1); + + observation.resetPoolEgressObservationCache(); + assert.equal(observation.readPoolEgressObservation("key", "acc-1", now + 2000)?.connections, 2); +}); + +test("an empty pool yields zeros, not null", async () => { + assert.deepEqual(observation.readPoolEgressObservation("provider", "nobody"), { + connections: 0, + distinctExits: 0, + maxConnectionsOnOneExit: 0, + windowHours: 24, + }); +}); + +test("a result is reused for 30 seconds, then read again", async () => { + const member = await poolMember("provider", "openai"); + logRow(member, "203.0.113.1", "c1"); + const now = Date.now(); + assert.equal(observation.readPoolEgressObservation("provider", "openai", now)?.connections, 1); + + logRow(member, "203.0.113.2", "c2"); + const cached = observation.readPoolEgressObservation("provider", "openai", now + 29_000); + assert.equal(cached?.connections, 1); + const fresh = observation.readPoolEgressObservation("provider", "openai", now + 31_000); + assert.equal(fresh?.connections, 2); +}); + +test("any SQL failure yields null and is not cached", async () => { + const member = await poolMember("provider", "openai"); + logRow(member, "203.0.113.1", "c1"); + const db = core.getDbInstance(); + db.exec("ALTER TABLE proxy_logs RENAME TO proxy_logs_hidden"); + try { + assert.equal(observation.readPoolEgressObservation("provider", "openai"), null); + } finally { + db.exec("ALTER TABLE proxy_logs_hidden RENAME TO proxy_logs"); + } + assert.equal(observation.readPoolEgressObservation("provider", "openai")?.connections, 1); +}); + +test("the kill switch turns the observation off", async () => { + const member = await poolMember("provider", "openai"); + logRow(member, "203.0.113.1", "c1"); + for (const value of ["false", "0", "no", "off", " OFF "]) { + process.env.PROXY_POOL_EGRESS_OBSERVATION = value; + assert.equal(observation.readPoolEgressObservation("provider", "openai"), null, value); + } + process.env.PROXY_POOL_EGRESS_OBSERVATION = "true"; + assert.equal(observation.readPoolEgressObservation("provider", "openai")?.connections, 1); +}); diff --git a/tests/unit/ui/PoolEgressObservation.test.tsx b/tests/unit/ui/PoolEgressObservation.test.tsx new file mode 100644 index 00000000000..209522366bf --- /dev/null +++ b/tests/unit/ui/PoolEgressObservation.test.tsx @@ -0,0 +1,96 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string, values?: Record) => + values ? `${key}:${JSON.stringify(values)}` : key, +})); + +import { PoolEgressObservation } from "@/app/(dashboard)/dashboard/settings/components/PoolEgressObservation"; + +const QUERY = "scope=provider&scopeId=openai"; +let root: Root | null = null; +let container: HTMLElement | null = null; + +async function renderWith(fetchImpl: (...args: unknown[]) => Promise) { + const fetchMock = vi.fn(fetchImpl); + vi.stubGlobal("fetch", fetchMock); + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + await act(async () => { + root!.render(React.createElement(PoolEgressObservation, { query: QUERY })); + }); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + return { fetchMock, element: container }; +} + +function jsonResponse(body: unknown, ok = true) { + return Promise.resolve({ ok, json: () => Promise.resolve(body) }); +} + +describe("PoolEgressObservation", () => { + beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + }); + + afterEach(async () => { + await act(async () => { + root?.unmount(); + }); + root = null; + container?.remove(); + container = null; + vi.unstubAllGlobals(); + }); + + it("reads the dedicated route and shows the counts", async () => { + const { fetchMock, element } = await renderWith(() => + jsonResponse({ + connections: 12, + distinctExits: 5, + maxConnectionsOnOneExit: 4, + windowHours: 24, + }) + ); + expect(fetchMock).toHaveBeenCalledWith( + `/api/settings/proxies/pool/egress-observation?${QUERY}` + ); + expect(element.textContent).toBe( + 'poolEgressObservation:{"exits":5,"connections":12,"max":4,"hours":24}' + ); + }); + + it("says no traffic was observed when the pool had no connection", async () => { + const { element } = await renderWith(() => + jsonResponse({ + connections: 0, + distinctExits: 0, + maxConnectionsOnOneExit: 0, + windowHours: 24, + }) + ); + expect(element.textContent).toBe('poolEgressObservationEmpty:{"hours":24}'); + }); + + it("renders nothing when the route answers null", async () => { + const { element } = await renderWith(() => jsonResponse(null)); + expect(element.textContent).toBe(""); + }); + + it("renders nothing on an error status", async () => { + const { element } = await renderWith(() => jsonResponse({ error: "nope" }, false)); + expect(element.textContent).toBe(""); + }); + + it("renders nothing when the request fails", async () => { + const { element } = await renderWith(() => Promise.reject(new Error("offline"))); + expect(element.textContent).toBe(""); + }); +}); From 692233295904ae32b953ec43f7eef97b17b1ccf4 Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Tue, 15 Sep 2026 23:45:54 +0200 Subject: [PATCH 2/5] feat(proxies): stop re-serving a pool member the provider just refused (#13602) Behind `PROXY_SKIP_RECENTLY_FAILED` (from #13578): a provider 429 received through a pool member sets that member aside and a 2xx clears it, for opencode providers. Maintainer rework before merge (kept the idea, no default behavior change): - `noteProxyOutcome` ran inside the fire-and-forget `safeLogEvents` after awaited dynamic imports, so a concurrent request could still pick the member; it now runs first, synchronously, before any `await`. - The duplicate `177_proxy_logs_upstream_status.sql` the stack still carried alongside the renamed 179 was removed; the regression test the PR body named exists as `pool-ip-quota-429-path.test.ts`. Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests. Thanks @maxmad64bis! --- .../13602-pool-skips-refused-member.md | 1 + config/quality/file-size-baseline.json | 3 +- src/i18n/messages/ar.json | 1 + src/i18n/messages/az.json | 1 + src/i18n/messages/bg.json | 1 + src/i18n/messages/bn.json | 1 + src/i18n/messages/cs.json | 1 + src/i18n/messages/da.json | 1 + src/i18n/messages/de.json | 1 + src/i18n/messages/el.json | 1 + src/i18n/messages/en.json | 1 + src/i18n/messages/es.json | 1 + src/i18n/messages/et.json | 1 + src/i18n/messages/fa.json | 1 + src/i18n/messages/fi.json | 1 + src/i18n/messages/fr.json | 1 + src/i18n/messages/ga.json | 1 + src/i18n/messages/gu.json | 1 + src/i18n/messages/he.json | 1 + src/i18n/messages/hi.json | 1 + src/i18n/messages/hr.json | 1 + src/i18n/messages/hu.json | 1 + src/i18n/messages/id.json | 1 + src/i18n/messages/it.json | 1 + src/i18n/messages/ja.json | 1 + src/i18n/messages/km.json | 1 + src/i18n/messages/kn.json | 1 + src/i18n/messages/ko.json | 1 + src/i18n/messages/lt.json | 1 + src/i18n/messages/lv.json | 1 + src/i18n/messages/ml.json | 1 + src/i18n/messages/mr.json | 1 + src/i18n/messages/ms.json | 1 + src/i18n/messages/mt.json | 1 + src/i18n/messages/my.json | 1 + src/i18n/messages/ne.json | 1 + src/i18n/messages/nl.json | 1 + src/i18n/messages/no.json | 1 + src/i18n/messages/or.json | 1 + src/i18n/messages/pa.json | 1 + src/i18n/messages/phi.json | 1 + src/i18n/messages/pl.json | 1 + src/i18n/messages/pt-BR.json | 1 + src/i18n/messages/pt.json | 1 + src/i18n/messages/ro.json | 1 + src/i18n/messages/ru.json | 1 + src/i18n/messages/si.json | 1 + src/i18n/messages/sk.json | 1 + src/i18n/messages/sl.json | 1 + src/i18n/messages/sr.json | 1 + src/i18n/messages/sv.json | 1 + src/i18n/messages/sw.json | 1 + src/i18n/messages/ta.json | 1 + src/i18n/messages/te.json | 1 + src/i18n/messages/th.json | 1 + src/i18n/messages/tr.json | 1 + src/i18n/messages/uk-UA.json | 1 + src/i18n/messages/ur.json | 1 + src/i18n/messages/vi.json | 1 + src/i18n/messages/zh-CN.json | 1 + src/i18n/messages/zh-TW.json | 1 + src/sse/handlers/chatHelpers.ts | 12 ++ src/sse/handlers/proxyOutcomeMemory.ts | 42 +++++ tests/unit/pool-ip-quota-429-path.test.ts | 152 ++++++++++++++++++ tests/unit/proxy-outcome-memory.test.ts | 100 ++++++++++++ 65 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 changelog.d/features/13602-pool-skips-refused-member.md create mode 100644 src/sse/handlers/proxyOutcomeMemory.ts create mode 100644 tests/unit/pool-ip-quota-429-path.test.ts create mode 100644 tests/unit/proxy-outcome-memory.test.ts diff --git a/changelog.d/features/13602-pool-skips-refused-member.md b/changelog.d/features/13602-pool-skips-refused-member.md new file mode 100644 index 00000000000..2c96150e632 --- /dev/null +++ b/changelog.d/features/13602-pool-skips-refused-member.md @@ -0,0 +1 @@ +- **feat(proxies):** a proxy pool stops re-serving a member the provider just refused through it and tries another member instead, reusing the existing skip cooldown; a later success through the member clears it. Opt-in with the `PROXY_SKIP_RECENTLY_FAILED` feature flag (default off: pool selection unchanged) ([#13602](https://github.com/diegosouzapw/OmniRoute/pull/13602)) — thanks @maxmad64bis diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index b2f30f413a3..5cfee5c3544 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,5 @@ { + "_rebaseline_2026_09_15_13602_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1214. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13580_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1202. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13439_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/combo/executeTargetAttempt.ts->1228. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13672_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/combo/executeTargetAttempt.ts->1223; open-sse/services/combo/roundRobinCombo.ts->1213. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", @@ -342,7 +343,7 @@ "_rebaseline_2026_07_27_3850_relax_filesize_cap": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). cap 800->900 (+100), testCap 800->900 (+100). Targets: decompose-existing-frozen unchanged (frozen still only-shrink); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes. SUPERSEDED by _rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct (v1 +20% buffer) — retained for audit. Tracked via same roadmap issue.", "_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)", "frozen": { - "src/sse/handlers/chatHelpers.ts": 1202, + "src/sse/handlers/chatHelpers.ts": 1214, "_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.", "_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.", "_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \"headroom\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, = 300 || !hasProxyRefusals()) return; + const key = proxyEgressKey(proxyInfo?.proxy); + if (inRefusalScope) noteProxyServed(key); + else noteProxyRecovered(key, "proxy_unreachable"); +} diff --git a/tests/unit/pool-ip-quota-429-path.test.ts b/tests/unit/pool-ip-quota-429-path.test.ts new file mode 100644 index 00000000000..80b5e382239 --- /dev/null +++ b/tests/unit/pool-ip-quota-429-path.test.ts @@ -0,0 +1,152 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import http from "node:http"; +import os from "node:os"; +import path from "node:path"; +import type { AddressInfo } from "node:net"; + +// Through the real pieces of the chat path: a provider dispatch refused through a pool +// member, the capture on the applied-proxy sink, the merge at the log call site and +// safeLogEvents. With PROXY_SKIP_RECENTLY_FAILED on, the next pick of that pool skips the +// member (already before the fire-and-forget log settles); a locally generated failure does +// not; a provider outside the refusal scope does not. With the flag off nothing changes. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-pool-refused-path-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const proxyLogger = await import("../../src/lib/proxyLogger.ts"); +const memory = await import("../../open-sse/utils/proxyRefusalMemory.ts"); +const proxyFetchModule = await import("../../open-sse/utils/proxyFetch.ts"); +const { runWithCapture } = await import("../../open-sse/utils/providerRequestLogging.ts"); +const { mergeAppliedProxySink, safeLogEvents } = + await import("../../src/sse/handlers/chatHelpers.ts"); + +const capture = { capture: () => {}, body: (fallback: unknown) => fallback }; +let server: http.Server; +let baseUrl = ""; + +test.before(async () => { + server = http.createServer((req, res) => { + const code = Number(new URL(req.url ?? "/", "http://local").searchParams.get("code") ?? 200); + res.writeHead(code, { "content-type": "application/json" }); + res.end("{}"); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve())); + baseUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; +}); + +test.after(async () => { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + await new Promise((resolve) => server.close(() => resolve())); + proxyLogger.clearProxyLogs(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test.beforeEach(() => { + memory.__resetProxyRefusalMemoryForTesting(); + process.env.PROXY_SKIP_RECENTLY_FAILED = "true"; + proxyLogger.clearProxyLogs(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +async function twoMemberPool() { + const members = []; + for (const port of [9301, 9302]) { + const proxy = await proxiesDb.createProxy({ + name: `m${port}`, + type: "http", + host: "10.4.1.1", + port, + }); + await proxiesDb.addProxyToScopePool("provider", "opencode", proxy.id); + members.push({ type: "http", host: "10.4.1.1", port }); + } + return members; +} + +async function pickPort() { + const resolved = await proxiesDb.resolveProxyForScopeFromRegistry("provider", "opencode"); + return (resolved as { proxy: { port: number } }).proxy.port; +} + +// One request as chat.ts runs it: the executor pins a proxy on the sink, the provider +// dispatch goes through the patched fetch, then the log call merges the sink. +async function chatRequest( + provider: string, + pinned: unknown, + code: number | null, + onLogCalled: () => void = () => {} +) { + const sink: { proxy: unknown; upstreamStatus?: number } = { proxy: null }; + await proxyFetchModule.runWithAppliedProxyCapture(sink, async () => { + sink.proxy = pinned; + if (code !== null) { + await runWithCapture(capture, async () => { + const res = await fetch(`${baseUrl}/v1/chat/completions?code=${code}`); + await res.text(); + }); + } + }); + // chat.ts fires this without awaiting it. + const logged = safeLogEvents({ + result: { success: code === 200, status: code ?? 429, error: code === 200 ? null : "failed" }, + proxyInfo: mergeAppliedProxySink({ proxy: null, level: "provider", levelId: provider }, sink), + proxyLatency: 1, + provider, + model: "m", + sourceFormat: "openai", + targetFormat: "openai", + credentials: { connectionId: "conn-refused-path" }, + comboName: null, + clientRawRequest: null, + }); + onLogCalled(); + await logged; +} + +test("a received refusal through a pool member makes the next pick skip it", async () => { + const [first, second] = await twoMemberPool(); + assert.equal(await pickPort(), first.port); + await chatRequest("opencode", first, 429); + assert.equal(proxyLogger.getProxyLogs()[0].upstreamStatus, 429); + assert.deepEqual([await pickPort(), await pickPort()], [second.port, second.port]); +}); + +test("the member is set aside as soon as the log call returns, not when the log settles", async () => { + const [first, second] = await twoMemberPool(); + let avoidedAtCallSite: boolean | null = null; + await chatRequest("opencode", first, 429, () => { + avoidedAtCallSite = memory.isProxyAvoided(memory.proxyEgressKey(first)); + }); + assert.equal(avoidedAtCallSite, true, "a concurrent pick must already skip the refused member"); + assert.deepEqual([await pickPort(), await pickPort()], [second.port, second.port]); +}); + +test("with the flag at its default (off) a received refusal leaves the member in rotation", async () => { + const [first, second] = await twoMemberPool(); + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + await chatRequest("opencode", first, 429); + assert.equal(proxyLogger.getProxyLogs()[0].upstreamStatus, 429); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 0); + assert.deepEqual([await pickPort(), await pickPort()], [first.port, second.port]); +}); + +test("a locally generated failure (no dispatch) leaves the member in rotation", async () => { + const [first, second] = await twoMemberPool(); + await chatRequest("opencode", first, null); + assert.equal(proxyLogger.getProxyLogs()[0].upstreamStatus, null); + assert.deepEqual([await pickPort(), await pickPort()], [first.port, second.port]); +}); + +test("a refusal for a provider outside the scope leaves the member in rotation", async () => { + const [first, second] = await twoMemberPool(); + await chatRequest("opencode-zen", first, 429); + assert.deepEqual([await pickPort(), await pickPort()], [first.port, second.port]); +}); diff --git a/tests/unit/proxy-outcome-memory.test.ts b/tests/unit/proxy-outcome-memory.test.ts new file mode 100644 index 00000000000..67ea235c925 --- /dev/null +++ b/tests/unit/proxy-outcome-memory.test.ts @@ -0,0 +1,100 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// With PROXY_SKIP_RECENTLY_FAILED on, a pool member is set aside only on a refusal the +// provider really returned through it, and only for a provider inside the refusal scope. +// Locally generated failures carry no upstream outcome and never set a member aside. With +// the flag off (the default) nothing is ever written. + +const memory = await import("../../open-sse/utils/proxyRefusalMemory.ts"); +const { noteProxyOutcome } = await import("../../src/sse/handlers/proxyOutcomeMemory.ts"); +const { egressBucketedLockProviders } = await import("../../open-sse/config/providerErrorRules.ts"); + +const PROXY = { type: "http", host: "10.4.0.1", port: 8080 }; +const KEY = memory.proxyEgressKey(PROXY); + +test.beforeEach(() => { + memory.__resetProxyRefusalMemoryForTesting(); + process.env.PROXY_SKIP_RECENTLY_FAILED = "true"; +}); + +test.after(() => { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; +}); + +test("a received refusal from an in-scope provider sets the member aside", () => { + for (const provider of [...egressBucketedLockProviders(), "OpenCode"]) { + memory.__resetProxyRefusalMemoryForTesting(); + noteProxyOutcome(provider, { proxy: PROXY, upstreamStatus: 429 }); + assert.equal(memory.isProxyAvoided(KEY), true, provider); + } +}); + +test("a refusal from a provider outside the scope writes nothing", () => { + for (const provider of ["opencode-zen", "openai", null]) { + noteProxyOutcome(provider, { proxy: PROXY, upstreamStatus: 429 }); + } + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 0); +}); + +test("no received status, no proxy or no proxyInfo writes nothing", () => { + noteProxyOutcome("opencode", { proxy: PROXY }); + noteProxyOutcome("opencode", { proxy: PROXY, upstreamStatus: null }); + noteProxyOutcome("opencode", { proxy: null, upstreamStatus: 429 }); + noteProxyOutcome("opencode", null); + noteProxyOutcome("opencode", undefined); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 0); +}); + +test("a success from an in-scope provider clears the member, other outcomes leave it", () => { + noteProxyOutcome("opencode", { proxy: PROXY, upstreamStatus: 429 }); + noteProxyOutcome("opencode", { proxy: PROXY, upstreamStatus: 403 }); + noteProxyOutcome("opencode", { proxy: PROXY, upstreamStatus: 500 }); + assert.equal(memory.isProxyAvoided(KEY), true); + noteProxyOutcome("opencode-go", { proxy: PROXY, upstreamStatus: 200 }); + assert.equal(memory.isProxyAvoided(KEY), false); +}); + +test("a success from another provider only ends an unreachable period, not a refusal one", () => { + noteProxyOutcome("opencode", { proxy: PROXY, upstreamStatus: 429 }); + noteProxyOutcome("openai", { proxy: PROXY, upstreamStatus: 200 }); + assert.equal( + memory.isProxyAvoided(KEY), + true, + "a shared pool must not clear another scope's refusal" + ); + + memory.__resetProxyRefusalMemoryForTesting(); + memory.noteProxyRefusal(KEY, "proxy_unreachable"); + noteProxyOutcome("openai", { proxy: PROXY, upstreamStatus: 200 }); + assert.equal(memory.isProxyAvoided(KEY), false); +}); + +test("an edge relay is ignored: its status is the relay's", () => { + noteProxyOutcome("opencode", { + proxy: { type: "vercel", host: "relay.example.vercel.app", port: 443 }, + upstreamStatus: 429, + }); + noteProxyOutcome("opencode", { + proxy: { type: "Vercel", host: "relay.example.vercel.app", port: 443 }, + upstreamStatus: 429, + }); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 0); +}); + +test("a second note while the proxy is already set aside changes nothing", () => { + const start = Date.now(); + const periodMs = memory.noteProxyRefusal(KEY, "ip_quota_429", start) ?? 0; + noteProxyOutcome("opencode", { proxy: PROXY, upstreamStatus: 429 }); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 1); + assert.equal(memory.isProxyAvoided(KEY, start + periodMs - 1000), true); + assert.equal(memory.isProxyAvoided(KEY, start + periodMs + 1000), false); +}); + +test("with the flag at its default (off) a received refusal writes nothing", () => { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + for (const provider of egressBucketedLockProviders()) { + noteProxyOutcome(provider, { proxy: PROXY, upstreamStatus: 429 }); + } + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 0); +}); From d9600654c0b234c697e609c940a9810b856eb840 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 15 Sep 2026 04:27:57 -0300 Subject: [PATCH 3/5] feat(flags): add opt-in PROXY_POOL_EGRESS_OBSERVATION feature flag (default off) Registers the switch for the read-only pool egress observation of #13581 as a regular feature flag (network category, defaultValue "false"), so the new dashboard line stays hidden unless an operator opts in. The environment variable of the same name keeps working through the normal flag resolution (DB override > env > default). Adds the catalog row in FEATURE_FLAGS.md, the description i18n key (vi and pt-BR translated, other locales deferred with the __MISSING__ marker) and bumps the flag-count assertions from 55 to 56. (cherry picked from commit 09459b31a84f68dbcc9486b4137f29e161df6871) --- docs/reference/FEATURE_FLAGS.md | 7 ++++--- src/i18n/messages/ar.json | 1 + src/i18n/messages/az.json | 1 + src/i18n/messages/bg.json | 1 + src/i18n/messages/bn.json | 1 + src/i18n/messages/cs.json | 1 + src/i18n/messages/da.json | 1 + src/i18n/messages/de.json | 1 + src/i18n/messages/el.json | 1 + src/i18n/messages/en.json | 1 + src/i18n/messages/es.json | 1 + src/i18n/messages/et.json | 1 + src/i18n/messages/fa.json | 1 + src/i18n/messages/fi.json | 1 + src/i18n/messages/fr.json | 1 + src/i18n/messages/ga.json | 1 + src/i18n/messages/gu.json | 1 + src/i18n/messages/he.json | 1 + src/i18n/messages/hi.json | 1 + src/i18n/messages/hr.json | 1 + src/i18n/messages/hu.json | 1 + src/i18n/messages/id.json | 1 + src/i18n/messages/it.json | 1 + src/i18n/messages/ja.json | 1 + src/i18n/messages/km.json | 1 + src/i18n/messages/kn.json | 1 + src/i18n/messages/ko.json | 1 + src/i18n/messages/lt.json | 1 + src/i18n/messages/lv.json | 1 + src/i18n/messages/ml.json | 1 + src/i18n/messages/mr.json | 1 + src/i18n/messages/ms.json | 1 + src/i18n/messages/mt.json | 1 + src/i18n/messages/my.json | 1 + src/i18n/messages/ne.json | 1 + src/i18n/messages/nl.json | 1 + src/i18n/messages/no.json | 1 + src/i18n/messages/or.json | 1 + src/i18n/messages/pa.json | 1 + src/i18n/messages/phi.json | 1 + src/i18n/messages/pl.json | 1 + src/i18n/messages/pt-BR.json | 1 + src/i18n/messages/pt.json | 1 + src/i18n/messages/ro.json | 1 + src/i18n/messages/ru.json | 1 + src/i18n/messages/si.json | 1 + src/i18n/messages/sk.json | 1 + src/i18n/messages/sl.json | 1 + src/i18n/messages/sr.json | 1 + src/i18n/messages/sv.json | 1 + src/i18n/messages/sw.json | 1 + src/i18n/messages/ta.json | 1 + src/i18n/messages/te.json | 1 + src/i18n/messages/th.json | 1 + src/i18n/messages/tr.json | 1 + src/i18n/messages/uk-UA.json | 1 + src/i18n/messages/ur.json | 1 + src/i18n/messages/vi.json | 1 + src/i18n/messages/zh-CN.json | 1 + src/i18n/messages/zh-TW.json | 1 + src/shared/constants/featureFlagDefinitions.ts | 12 ++++++++++++ src/shared/utils/featureFlags.ts | 16 ++++++++++++++++ tests/unit/feature-flags-settings.test.ts | 12 +++++++++++- tests/unit/server-owned-tool-loop-flag.test.ts | 2 +- 64 files changed, 103 insertions(+), 5 deletions(-) diff --git a/docs/reference/FEATURE_FLAGS.md b/docs/reference/FEATURE_FLAGS.md index 7803e19937e..2523b51bc22 100644 --- a/docs/reference/FEATURE_FLAGS.md +++ b/docs/reference/FEATURE_FLAGS.md @@ -46,7 +46,7 @@ A boolean flag is considered **enabled** when its effective value is `"true"`, ## Flag Catalog -62 flags across 6 categories. **Default** is the definition default — the value +63 flags across 6 categories. **Default** is the definition default — the value used when neither a DB override nor an environment variable is present. ### Security (10) @@ -64,7 +64,7 @@ used when neither a DB override nor an environment variable is present. | `AUTH_LOG_INCLUDE_ACCOUNT_ID` | boolean | `false` | Include account prefix in AUTH log lines (e.g. "Using account: abc12345..."). Disabled by default so account identifiers are redacted from shared/multi-tenant process logs. Independent from Debug Mode; flipping Debug Mode does not reveal this. | | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | boolean | `false` | When OIDC is enabled, disable password login so users can only authenticate via OIDC Single Sign-On. When disabled (default), both password login and OIDC are available. | -### Network (10) +### Network (11) | Key | Type | Default | Restart | Description | | ----------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -74,6 +74,7 @@ used when neither a DB override nor an environment variable is present. | `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | boolean | `false` | | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Off by default because this can change egress IP. | | `NETWORK_ROTATION_SHARED_EGRESS_GUARD` | boolean | `true` | | On a network exception (timeout, connection refused/reset) for a multi-account rotation executor, when the failing account has no dedicated proxy, apply a short cooldown and skip other proxy-less accounts for the rest of the request instead of retrying each one. On by default (safe: no egress IP change, only reduces latency/cooldown risk on shared-egress accounts). Disable to restore immediate propagation on the first proxy-less throw. | | `PROXY_SKIP_RECENTLY_FAILED` | boolean | `false` | | Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default. | +| `PROXY_POOL_EGRESS_OBSERVATION` | boolean | `false` | | Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h and how many connections used them. Read-only, computed from the proxy log, never used for routing. Off by default. | | `MITM_DISABLE_TLS_VERIFY` | boolean | `false` | ✓ | Disable TLS certificate verification for the MITM proxy. **Danger.** | | `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | boolean | `false` | | Allow provider URLs pointing to private/internal networks. | | `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | boolean | `true` | | Allow adding/validating providers on local/private addresses (127.0.0.1, localhost, LAN). On by default (local-first); disable for strict public-only blocking. Cloud-metadata stays blocked. | @@ -202,7 +203,7 @@ Returns every flag with its effective value, source, and a summary. "requiresRestart": false, "warningLevel": "caution", }, - // ... all 62 flags + // ... all 63 flags ], "summary": { "total": 56, diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 46fbcbdce81..b42e4175716 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "فعّل مسارات القبول الافتراضية التكيفية لكل مستأجر (tenant) لتوزيع المزودين (#9654): لم يعد انفجار حركة أحد المستأجرين يسبب خطأ 503 لمستأجر آخر. متغير البيئة OMNIROUTE_CHAT_VIRTUAL_LANES له الأولوية على هذا الإعداد في لوحة التحكم؛ تصبح التغييرات سارية بعد إعادة تشغيل الخادم.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "الصفحة الرئيسية", "dashboard": "لوحة القيادة", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index e4dced592d1..173620b910d 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Təchizatçı göndərişi üçün hər bir icarəçi (tenant) üzrə adaptiv virtual qəbul zolaqlarını aktivləşdirin (#9654): bir icarəçinin ani yükü artıq digərinə 503 qaytarmır. OMNIROUTE_CHAT_VIRTUAL_LANES mühit dəyişəni bu idarəetmə paneli ayarından üstündür; dəyişikliklər server yenidən işə salındıqda qüvvəyə minir.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index abac4d16fdc..c4f7b3679f9 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Активирайте адаптивни виртуални ленти за допускане за всеки наемател (tenant) при изпращане към доставчици (#9654): скокът в натоварването на един наемател вече не връща 503 на друг. Променливата на средата OMNIROUTE_CHAT_VIRTUAL_LANES има предимство пред тази настройка в таблото; промените влизат в сила след рестартиране на сървъра.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Начало", "dashboard": "Табло", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 9c7315ccc01..667d06ded27 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "প্রোভাইডার ডিসপ্যাচের জন্য প্রতি-টেন্যান্ট অ্যাডাপ্টিভ ভার্চুয়াল অ্যাডমিশন লেন সক্ষম করুন (#9654): এক টেন্যান্টের বিস্ফোরণ আর অন্য টেন্যান্টে 503 ফেরায় না। OMNIROUTE_CHAT_VIRTUAL_LANES এনভায়রনমেন্ট ভেরিয়েবল এই ড্যাশবোর্ড সেটিংয়ের উপরে প্রাধান্য পায়; পরিবর্তনগুলি সার্ভার পুনরায় চালু হলে কার্যকর হয়।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 0556c02b581..4ae01f332e6 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Povolte adaptivní virtuální vstupní pruhy pro každého tenanta při odesílání poskytovatelům (#9654): špička jednoho tenanta už nezpůsobí 503 u jiného. Proměnná prostředí OMNIROUTE_CHAT_VIRTUAL_LANES má přednost před tímto nastavením na řídicím panelu; změny se projeví po restartu serveru.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Domov", "dashboard": "Nástěnka", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 207d508bebe..3a166ec6746 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktivér adaptive virtuelle adgangsbaner pr. tenant til providerudlevering (#9654): en tenants burst giver ikke længere en anden 503. Miljøvariablen OMNIROUTE_CHAT_VIRTUAL_LANES har forrang over denne dashboard-indstilling; ændringer træder i kraft ved genstart af serveren.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Hjem", "dashboard": "Dashboard", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 4590be67df0..822530e2d7d 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktivieren Sie adaptive virtuelle Zulassungsspuren pro Tenant für die Provider-Zustellung (#9654): Ein Burst eines Tenants führt nicht mehr zu 503 bei einem anderen. Die Umgebungsvariable OMNIROUTE_CHAT_VIRTUAL_LANES hat Vorrang vor dieser Dashboard-Einstellung; Änderungen werden erst nach einem Serverneustart wirksam.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Zuhause", "dashboard": "Dashboard", diff --git a/src/i18n/messages/el.json b/src/i18n/messages/el.json index 45308965375..5b0ce92757d 100644 --- a/src/i18n/messages/el.json +++ b/src/i18n/messages/el.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ενεργοποίηση προσαρμοστικών εικονικών λωρίδων αποδοχής ανά ενοικιαστή για αποστολή παρόχου (#9654): η έκρηξη ενός ενοικιαστή δεν προκαλεί πλέον 503 σε άλλον. Η μεταβλητή περιβάλλοντος OMNIROUTE_CHAT_VIRTUAL_LANES υπερισχύει αυτής της παράκαμψης του πίνακα ελέγχου· οι αλλαγές τίθενται σε ισχύ κατά την επανεκκίνηση του διακομιστή.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Αρχική", "dashboard": "Πίνακας Ελέγχου", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index f2ba140f19c..35d13a17a6e 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Enable per-tenant adaptive virtual admission lanes for provider dispatch (#9654): one tenant's burst no longer 503s another. The OMNIROUTE_CHAT_VIRTUAL_LANES env var wins over this dashboard override; changes take effect at server restart.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 23b2690575c..9c19cd1c23e 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Activa carriles de admisión virtuales adaptativos por tenant para el envío de proveedores (#9654): el pico de un tenant ya no devuelve 503 a otro. La variable de entorno OMNIROUTE_CHAT_VIRTUAL_LANES tiene prioridad sobre esta opción del panel; los cambios surten efecto al reiniciar el servidor.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Inicio", "dashboard": "Panel de control", diff --git a/src/i18n/messages/et.json b/src/i18n/messages/et.json index 646b22d336c..79a6a772e34 100644 --- a/src/i18n/messages/et.json +++ b/src/i18n/messages/et.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Lubage pakkujale edastamiseks rentnikupõhised kohanduvad virtuaalsed vastuvõturajad (#9654): ühe rentniku koormushoog ei põhjusta enam teisele tõrget 503. Keskkonnamuutuja OMNIROUTE_CHAT_VIRTUAL_LANES alistab selle juhtpaneeli sätte; muudatused jõustuvad serveri taaskäivitamisel.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Avaleht", "dashboard": "Juhtpaneel", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 5b103f0686f..cc589196884 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "خط‌های پذیرش مجازی تطبیقی به‌ازای هر مستاجر (tenant) را برای ارسال به ارائه‌دهندگان فعال کنید (#9654): افزایش ناگهانی بار یک مستاجر دیگر خطای 503 را برای مستاجر دیگر ایجاد نمی‌کند. متغیر محیطی OMNIROUTE_CHAT_VIRTUAL_LANES بر این تنظیم داشبورد اولویت دارد؛ تغییرات پس از راه‌اندازی مجدد سرور اعمال می‌شوند.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 834bfa9e09b..aff12f888ba 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ota käyttöön mukautuvat virtuaaliset sisäänottokaistat vuokraajaa (tenant) kohti palveluntarjoajien välitystä varten (#9654): yhden vuokraajan kuormapiikki ei enää aiheuta 503-virhettä toiselle. Ympäristömuuttuja OMNIROUTE_CHAT_VIRTUAL_LANES ohittaa tämän hallintapaneelin asetuksen; muutokset tulevat voimaan palvelimen uudelleenkäynnistyksessä.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Kotiin", "dashboard": "Kojelauta", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 60c9fc904f7..a0dea7b953e 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Activez des voies d'admission virtuelles adaptatives par tenant pour la répartition des fournisseurs (#9654) : le pic d'un tenant ne renvoie plus 503 à un autre. La variable d'environnement OMNIROUTE_CHAT_VIRTUAL_LANES prime sur ce réglage du tableau de bord ; les modifications prennent effet au redémarrage du serveur.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Accueil", "dashboard": "Tableau de bord", diff --git a/src/i18n/messages/ga.json b/src/i18n/messages/ga.json index 6c8eddea57e..5f5d29a5504 100644 --- a/src/i18n/messages/ga.json +++ b/src/i18n/messages/ga.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Cumasaigh lánaí iontrála oiriúnaitheacha fíorúla in aghaidh an tionónta le haghaidh seolta soláthraí (#9654): ní chruthaíonn pléascadh tionónta amháin 503 do thionónta eile a thuilleadh. Tá an athróg timpeallachta OMNIROUTE_CHAT_VIRTUAL_LANES níos cumhachtaí ná an sárú deais seo; tagann athruithe i bhfeidhm ag atosú freastalaí.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Baile", "dashboard": "Deais", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 142a92fe652..7ea2f8bc0bc 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "પ્રોવાઇડર ડિસ્પેચ માટે પ્રતિ-ટેનન્ટ અનુકૂલનશીલ વર્ચ્યુઅલ એડમિશન લેન સક્ષમ કરો (#9654): એક ટેનન્ટનો બર્સ્ટ હવે બીજા ટેનન્ટને 503 આપતો નથી. OMNIROUTE_CHAT_VIRTUAL_LANES એન્વાયર્નમેન્ટ વેરિયેબલ આ ડેશબોર્ડ સેટિંગ કરતાં વધુ પ્રાધાન્ય ધરાવે છે; ફેરફારો સર્વર પુનઃપ્રારંભ પર અસરકારક થાય છે.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 07517d0d21e..26e0edf38c1 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "הפעל נתיבי קבלה וירטואליים אדפטיביים לכל דייר (tenant) עבור שליחת ספקים (#9654): פרץ עומס של דייר אחד כבר לא מחזיר 503 לדייר אחר. משתנה הסביבה OMNIROUTE_CHAT_VIRTUAL_LANES גובר על הגדרה זו בלוח הבקרה; השינויים נכנסים לתוקף לאחר הפעלת השרת מחדש.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "בית", "dashboard": "לוח מחוונים", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 2e36081dc84..5da9d40a433 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "प्रदाता डिस्पैच के लिए प्रति-टेनेंट अनुकूली वर्चुअल एडमिशन लेन सक्षम करें (#9654): एक टेनेंट का बर्स्ट अब दूसरे टेनेंट को 503 नहीं देता। OMNIROUTE_CHAT_VIRTUAL_LANES पर्यावरण चर इस डैशबोर्ड सेटिंग पर प्राथमिकता रखता है; परिवर्तन सर्वर पुनः आरंभ पर प्रभावी होते हैं।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "घर", "dashboard": "डैशबोर्ड", diff --git a/src/i18n/messages/hr.json b/src/i18n/messages/hr.json index eea19d6876e..311155e4c8f 100644 --- a/src/i18n/messages/hr.json +++ b/src/i18n/messages/hr.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Omogući adaptivne virtualne prijamne trake po korisniku za raspodjelu pružatelja (#9654): opterećenje jednog korisnika više neće uzrokovati 503 grešku drugome. Varijabla okoline OMNIROUTE_CHAT_VIRTUAL_LANES ima prednost nad ovim nadjačavanjem nadzorne ploče; promjene stupaju na snagu pri ponovnom pokretanju poslužitelja.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Početna", "dashboard": "Nadzorna ploča", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index c9de63e1c91..58232e38a68 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Tegye lehetővé a bérlőnkénti adaptív virtuális beléptetősávokat a szolgáltatók felé történő továbbításhoz (#9654): az egyik bérlő kiugró terhelése már nem okoz 503-as hibát egy másiknál. Az OMNIROUTE_CHAT_VIRTUAL_LANES környezeti változó felülírja ezt a vezérlőpult-beállítást; a változtatások a szerver újraindításakor lépnek életbe.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Otthon", "dashboard": "Irányítópult", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 8f404143f1d..a59fda7a66f 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktifkan jalur penerimaan virtual adaptif per-tenant untuk pengiriman penyedia (#9654): lonjakan satu tenant tidak lagi mengembalikan 503 ke tenant lain. Variabel lingkungan OMNIROUTE_CHAT_VIRTUAL_LANES menang atas pengaturan dasbor ini; perubahan berlaku setelah server dimulai ulang.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Rumah", "dashboard": "Dasbor", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index dc197fd4cae..b5f702bf5b0 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Attiva corsie di ammissione virtuali adattive per tenant per l'invio ai provider (#9654): il picco di un tenant non restituisce più 503 a un altro. La variabile d'ambiente OMNIROUTE_CHAT_VIRTUAL_LANES ha la precedenza su questa impostazione della dashboard; le modifiche hanno effetto al riavvio del server.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Casa", "dashboard": "Pannello di controllo", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 1e6473a22ff..4475d093509 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "プロバイダーへのディスパッチ用に、テナントごとの適応型仮想受付レーンを有効にします(#9654):あるテナントのバーストが他のテナントに503を返さなくなります。OMNIROUTE_CHAT_VIRTUAL_LANES環境変数はこのダッシュボード設定より優先されます。変更はサーバー再起動時に反映されます。", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "ホーム", "dashboard": "ダッシュボード", diff --git a/src/i18n/messages/km.json b/src/i18n/messages/km.json index f1f71b0bcbb..41a2325e66f 100644 --- a/src/i18n/messages/km.json +++ b/src/i18n/messages/km.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "បើកផ្លូវចូលនិម្មិតដែលសម្របខ្លួនតាម tenant នីមួយៗ សម្រាប់ការបញ្ជូនទៅ provider (#9654)៖ ការកើនឡើងខ្លាំងភ្លាមៗរបស់ tenant មួយ នឹងលែងបណ្ដាលឱ្យ tenant មួយទៀតទទួល 503។ env var OMNIROUTE_CHAT_VIRTUAL_LANES មានអាទិភាពលើការកំណត់ជំនួសពី dashboard នេះ ហើយការផ្លាស់ប្ដូរនឹងមានប្រសិទ្ធភាពនៅពេលចាប់ផ្ដើម server ឡើងវិញ។", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "ទំព័រដើម", "dashboard": "ផ្ទាំងគ្រប់គ្រង", diff --git a/src/i18n/messages/kn.json b/src/i18n/messages/kn.json index eaf37481ffd..db265ad5c08 100644 --- a/src/i18n/messages/kn.json +++ b/src/i18n/messages/kn.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "ಪ್ರೊವೈಡರ್ ಡಿಸ್ಪ್ಯಾಚ್ ಗಾಗಿ ಪ್ರತಿ-ಟೆನಂಟ್ ಅಡಾಪ್ಟಿವ್ ವರ್ಚುವಲ್ ಅಡ್ಮಿಷನ್ ಲೇನ್ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ (#9654): ಒಂದು ಟೆನಂಟ್ನ ಬರ್ಸ್ಟ್ ಇನ್ನು ಮುಂದೆ ಮತ್ತೊಂದನ್ನು 503 ಮಾಡುವುದಿಲ್ಲ. OMNIROUTE_CHAT_VIRTUAL_LANES ಎನ್ವಿ ವೇರಿಯಬಲ್ ಈ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಓವರ್ರೈಡ್ ಮೇಲೆ ಗೆಲ್ಲುತ್ತದೆ; ಬದಲಾವಣೆಗಳು ಸರ್ವರ್ ರೀಸ್ಟಾರ್ಟ್ ನಲ್ಲಿ ಜಾರಿಗೆ ಬರುತ್ತವೆ.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "ಹೋಮ್", "dashboard": "ಡ್ಯಾಶ್ಬೋರ್ಡ್", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index e0f776c67b3..f261cee2511 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "공급자 디스패치를 위해 테넌트별 적응형 가상 승인 레인을 활성화합니다(#9654): 한 테넌트의 폭증이 더 이상 다른 테넌트에 503을 반환하지 않습니다. OMNIROUTE_CHAT_VIRTUAL_LANES 환경 변수가 이 대시보드 설정보다 우선하며, 변경 사항은 서버 재시작 시 적용됩니다.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "홈", "dashboard": "대시보드", diff --git a/src/i18n/messages/lt.json b/src/i18n/messages/lt.json index 39769831c4f..58f81532aea 100644 --- a/src/i18n/messages/lt.json +++ b/src/i18n/messages/lt.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Įjungti kiekvienam nuomotojui pritaikomas adaptyvias virtualias priėmimo juostas teikėjų siuntimui (#9654): vieno nuomotojo srautas nebesukelia 503 klaidos kitam. Aplinkos kintamasis OMNIROUTE_CHAT_VIRTUAL_LANES turi pirmenybę prieš šį skydelio nustatymą; pakeitimai įsigalioja po serverio paleidimo iš naujo.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Pradžia", "dashboard": "Suvestinė", diff --git a/src/i18n/messages/lv.json b/src/i18n/messages/lv.json index 24a0ed893eb..92df748d5ce 100644 --- a/src/i18n/messages/lv.json +++ b/src/i18n/messages/lv.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Iespējot katra nomnieka adaptīvas virtuālās uzņemšanas joslas nodrošinātāju izsūtīšanai (#9654): viena nomnieka slodzes lēciens vairs neizraisa 503 kļūdu citam. OMNIROUTE_CHAT_VIRTUAL_LANES vides mainīgais ir prioritārāks par šo paneļa iestatījumu; izmaiņas stājas spēkā pēc servera pārstartēšanas.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Sākums", "dashboard": "Panelis", diff --git a/src/i18n/messages/ml.json b/src/i18n/messages/ml.json index 8656959e310..369ef3ffba8 100644 --- a/src/i18n/messages/ml.json +++ b/src/i18n/messages/ml.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "പ്രൊവൈഡർ ഡിസ്പാച്ചിനായി ഓരോ ടെനന്റിനും അനുയോജ്യമായി മാറുന്ന വെർച്വൽ അഡ്മിഷൻ ലെയിനുകൾ പ്രവർത്തനക്ഷമമാക്കുക (#9654): ഇനി ഒരു ടെനന്റിന്റെ പെട്ടെന്നുള്ള അഭ്യർത്ഥന വർധന മറ്റൊരാൾക്ക് 503 പിശക് സൃഷ്ടിക്കില്ല. ഈ ഡാഷ്ബോർഡ് ഓവർറൈഡിനേക്കാൾ OMNIROUTE_CHAT_VIRTUAL_LANES env var-ന് മുൻഗണനയുണ്ട്; സെർവർ പുനരാരംഭിക്കുമ്പോൾ മാറ്റങ്ങൾ പ്രാബല്യത്തിൽ വരും.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "ഹോം", "dashboard": "ഡാഷ്ബോർഡ്", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 222a45d58c6..87e267b28cb 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "प्रदाता डिस्पॅचसाठी प्रति-टेनंट अनुकूली व्हर्च्युअल अॅडमिशन लेन सक्षम करा (#9654): एका टेनंटचा बर्स्ट यापुढे दुसऱ्या टेनंटला 503 देत नाही. OMNIROUTE_CHAT_VIRTUAL_LANES पर्यावरण चल या डॅशबोर्ड सेटिंगपेक्षा वरचढ आहे; बदल सर्व्हर रीस्टार्ट केल्यावर प्रभावी होतात.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index ecb934436a9..0b41853bf1d 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktifkan lorong kemasukan maya adaptif setiap-tenant untuk penghantaran pembekal (#9654): lonjakan satu tenant tidak lagi memberikan 503 kepada tenant lain. Pemboleh ubah persekitaran OMNIROUTE_CHAT_VIRTUAL_LANES mengatasi tetapan papan pemuka ini; perubahan berkuat kuasa apabila pelayan dimulakan semula.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Rumah", "dashboard": "Papan pemuka", diff --git a/src/i18n/messages/mt.json b/src/i18n/messages/mt.json index 24e69a8d675..5cc3115be38 100644 --- a/src/i18n/messages/mt.json +++ b/src/i18n/messages/mt.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ippermetti korsiji virtwali adattivi tad-dħul għal kull tenant għad-dispaċċ tal-fornituri (#9654): żieda f'daqqa fit-traffiku ta' tenant wieħed ma tibqax tikkawża żball 503 għal ieħor. Il-varjabbli tal-ambjent OMNIROUTE_CHAT_VIRTUAL_LANES jieħu preċedenza fuq din is-sovrasKitba tad-dashboard; il-bidliet jidħlu fis-seħħ meta jerġa' jinbeda s-server.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Paġna Ewlenija", "dashboard": "Dashboard", diff --git a/src/i18n/messages/my.json b/src/i18n/messages/my.json index 47aee660532..67fbf66e522 100644 --- a/src/i18n/messages/my.json +++ b/src/i18n/messages/my.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "provider dispatch (#9654) အတွက် tenant တစ်ခုချင်းစီအလိုက် အလိုက်သင့်ပြောင်းလဲနိုင်သော virtual admission lanes များကို ဖွင့်ပါ။ tenant တစ်ခု၏ ရုတ်တရက်မြင့်တက်လာသော အသုံးပြုမှုကြောင့် အခြား tenant တွင် 503 ဖြစ်ပေါ်တော့မည်မဟုတ်ပါ။ OMNIROUTE_CHAT_VIRTUAL_LANES env var သည် ဤ dashboard override ထက် ဦးစားပေးသက်ရောက်ပြီး ပြောင်းလဲမှုများသည် server ပြန်လည်စတင်ချိန်တွင် အသက်ဝင်မည်ဖြစ်သည်။", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "ပင်မစာမျက်နှာ", "dashboard": "ဒက်ရှ်ဘုတ်", diff --git a/src/i18n/messages/ne.json b/src/i18n/messages/ne.json index 247a0c51dd6..af05e08c98e 100644 --- a/src/i18n/messages/ne.json +++ b/src/i18n/messages/ne.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "प्रदायक डिस्प्याच (#9654) का लागि प्रत्येक टेनेन्टअनुसार अनुकूल हुने भर्चुअल एडमिसन लेनहरू सक्षम गर्नुहोस्: अब एउटा टेनेन्टको अचानक बढेको ट्राफिकले अर्कोलाई 503 गराउँदैन। OMNIROUTE_CHAT_VIRTUAL_LANES env var ले यस ड्यासबोर्ड ओभरराइडभन्दा प्राथमिकता पाउँछ; परिवर्तनहरू सर्भर पुनः सुरु भएपछि लागू हुन्छन्।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "गृहपृष्ठ", "dashboard": "ड्यासबोर्ड", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index fa08eb71e22..5136b39c4b8 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Schakel adaptieve virtuele toegangsbanen per tenant in voor provider-dispatch (#9654): een piek van de ene tenant geeft de andere niet langer een 503. De omgevingsvariabele OMNIROUTE_CHAT_VIRTUAL_LANES wint het van deze dashboard-instelling; wijzigingen gaan in bij een serverherstart.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Thuis", "dashboard": "Dashboard", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 8412d3f6a57..e64eb9e7271 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktiver adaptive virtuelle tilgangsfelt per tenant for leverandørdistribusjon (#9654): et utbrudd fra én tenant gir ikke lenger en annen 503. Miljøvariabelen OMNIROUTE_CHAT_VIRTUAL_LANES overstyrer denne innstillingen i dashbordet; endringer trer i kraft ved omstart av serveren.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Hjem", "dashboard": "Dashbord", diff --git a/src/i18n/messages/or.json b/src/i18n/messages/or.json index 0e78e170510..da3ba5b100d 100644 --- a/src/i18n/messages/or.json +++ b/src/i18n/messages/or.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "ପ୍ରଦାତା ଡିସ୍ପାଚ୍ (#9654) ପାଇଁ ପ୍ରତି-ଟେନାଣ୍ଟ ଅନୁକୂଳନଶୀଳ ଭର୍ଚୁଆଲ୍ ଆଡମିଶନ୍ ଲେନ୍ଗୁଡ଼ିକ ସକ୍ଷମ କରନ୍ତୁ: ଗୋଟିଏ ଟେନାଣ୍ଟର ହଠାତ୍ ଟ୍ରାଫିକ୍ ବୃଦ୍ଧି ଆଉ ଅନ୍ୟ ଟେନାଣ୍ଟ ପାଇଁ 503 ତ୍ରୁଟି ସୃଷ୍ଟି କରିବ ନାହିଁ। OMNIROUTE_CHAT_VIRTUAL_LANES env var ଏହି ଡ୍ୟାସ୍ବୋର୍ଡ ଓଭର୍ରାଇଡ୍ଠାରୁ ପ୍ରାଥମିକତା ପାଏ; ସର୍ଭର୍ ପୁନଃଚାଳନ ପରେ ପରିବର୍ତ୍ତନଗୁଡ଼ିକ କାର୍ଯ୍ୟକାରୀ ହୁଏ।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "ମୂଳପୃଷ୍ଠା", "dashboard": "ଡ୍ୟାସ୍ବୋର୍ଡ", diff --git a/src/i18n/messages/pa.json b/src/i18n/messages/pa.json index cfabb1ef6e1..8ecc7f2f611 100644 --- a/src/i18n/messages/pa.json +++ b/src/i18n/messages/pa.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "ਪ੍ਰਦਾਤਾ ਡਿਸਪੈਚ (#9654) ਲਈ ਪ੍ਰਤੀ-ਟੈਨੈਂਟ ਅਨੁਕੂਲ ਵਰਚੁਅਲ ਐਡਮਿਸ਼ਨ ਲੇਨ ਸਮਰੱਥ ਕਰੋ: ਹੁਣ ਇੱਕ ਟੈਨੈਂਟ ਦਾ ਅਚਾਨਕ ਵਧਿਆ ਲੋਡ ਦੂਜੇ ਲਈ 503 ਪੈਦਾ ਨਹੀਂ ਕਰੇਗਾ। OMNIROUTE_CHAT_VIRTUAL_LANES env var ਨੂੰ ਇਸ ਡੈਸ਼ਬੋਰਡ ਓਵਰਰਾਈਡ ਉੱਤੇ ਤਰਜੀਹ ਮਿਲਦੀ ਹੈ; ਤਬਦੀਲੀਆਂ ਸਰਵਰ ਮੁੜ ਚਾਲੂ ਹੋਣ 'ਤੇ ਲਾਗੂ ਹੁੰਦੀਆਂ ਹਨ।", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "ਮੁੱਖ ਪੰਨਾ", "dashboard": "ਡੈਸ਼ਬੋਰਡ", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 504ee6b35c6..84c197dc9bd 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Paganahin ang adaptive virtual admission lanes para sa bawat tenant sa pagpapadala ng provider (#9654): ang pag-akyat ng trapiko ng isang tenant ay hindi na nagbibigay ng 503 sa iba. Ang environment variable na OMNIROUTE_CHAT_VIRTUAL_LANES ay mas nangingibabaw sa setting na ito sa dashboard; magkakabisa ang mga pagbabago sa pag-restart ng server.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Bahay", "dashboard": "Dashboard", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 817d2373b0d..b2ce0f03dd9 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Włącz adaptacyjne wirtualne pasma przyjęć dla każdego tenanta przy wysyłce do dostawców (#9654): przeciążenie jednego tenanta nie powoduje już błędu 503 u innego. Zmienna środowiskowa OMNIROUTE_CHAT_VIRTUAL_LANES ma pierwszeństwo przed tym ustawieniem w panelu; zmiany wchodzą w życie po restarcie serwera.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Strona główna", "dashboard": "Dashboard", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index d572e16f7ba..68724e46196 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -995,6 +995,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ative faixas de admissão virtuais adaptativas por tenant para o despacho de provedores (#9654): o pico de um tenant não gera mais 503 para outro. A variável de ambiente OMNIROUTE_CHAT_VIRTUAL_LANES tem precedência sobre esta configuração do painel; as alterações entram em vigor ao reiniciar o servidor.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "Pools de proxy e a rotação por conta do opencode deixam de reutilizar um proxy que acabou de falhar (sonda TCP recusada, ou um 429 recebido por ele) por um período por processo que dobra a cada repetição, até um limite. Nenhum status de proxy é gravado; com todos os candidatos deixados de lado, a escolha não muda. Desligado por padrão: a ordem de seleção é exatamente a rotação simples.", + "featureFlagProxyPoolEgressObservationDescription": "Mostra, abaixo de um pool de proxy no painel, quantos IPs de saída observados atenderam seus membros nas últimas 24 h, quantas conexões os usaram e o máximo visto atrás de um mesmo IP. Somente leitura, calculado a partir do log de proxy, nunca usado para roteamento. Desligado por padrão: o editor de pool não muda e a rota de observação responde null.", "sidebar": { "home": "Início", "dashboard": "Painel", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 1a272d0c5bb..5dae492f9c0 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -995,6 +995,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Ative filas de admissão virtuais adaptativas por tenant para o encaminhamento de fornecedores (#9654): um pico de tráfego de um tenant já não gera 503 noutro. A variável de ambiente OMNIROUTE_CHAT_VIRTUAL_LANES sobrepõe-se a esta definição do painel; as alterações entram em vigor ao reiniciar o servidor.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Página inicial", "dashboard": "Painel", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 1f8edbe9891..165ce734c5f 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Activați benzile de admitere virtuale adaptive per-tenant pentru expedierea către furnizori (#9654): un vârf de trafic al unui tenant nu mai returnează 503 altui tenant. Variabila de mediu OMNIROUTE_CHAT_VIRTUAL_LANES are prioritate față de această setare din panou; modificările intră în vigoare la repornirea serverului.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Acasă", "dashboard": "Tabloul de bord", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index d6bb9090a40..d66680d76b0 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Включите адаптивные виртуальные полосы допуска для каждого тенанта при маршрутизации к провайдерам (#9654): всплеск нагрузки одного тенанта больше не вызывает 503 у другого. Переменная окружения OMNIROUTE_CHAT_VIRTUAL_LANES имеет приоритет над этой настройкой в панели; изменения вступают в силу после перезапуска сервера.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Главная", "dashboard": "Панель управления", diff --git a/src/i18n/messages/si.json b/src/i18n/messages/si.json index 2b5a55a0573..533c84b7490 100644 --- a/src/i18n/messages/si.json +++ b/src/i18n/messages/si.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "සපයන්නා වෙත යැවීම සඳහා එක් එක් ටෙනන්ට්ට අනුව අනුවර්තනය වන අතථ්ය ප්රවේශ මංතීරු සබල කරන්න (#9654): එක් ටෙනන්ට් කෙනෙකුගේ හදිසි ඉල්ලීම් වැඩිවීමක් තවදුරටත් වෙනත් අයෙකුට 503 දෝෂයක් ඇති නොකරයි. OMNIROUTE_CHAT_VIRTUAL_LANES පරිසර විචල්යය මෙම උපකරණ පුවරු අතික්රමණයට වඩා ප්රමුඛ වේ; වෙනස්කම් සේවාදායකය නැවත ආරම්භ කළ විට ක්රියාත්මක වේ.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "මුල් පිටුව", "dashboard": "උපකරණ පුවරුව", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index f128d317766..13c25e18bd8 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Povoľte adaptívne virtuálne vstupné pruhy pre každého nájomcu (tenant) pri odosielaní poskytovateľom (#9654): špička jedného nájomcu už nespôsobí 503 u iného. Premenná prostredia OMNIROUTE_CHAT_VIRTUAL_LANES má prednosť pred týmto nastavením v riadiacom paneli; zmeny sa prejavia po reštarte servera.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Domov", "dashboard": "Dashboard", diff --git a/src/i18n/messages/sl.json b/src/i18n/messages/sl.json index ea882a203e8..e5c1e9bf55c 100644 --- a/src/i18n/messages/sl.json +++ b/src/i18n/messages/sl.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Omogoči prilagodljive navidezne sprejemne pasove za posameznega najemnika pri posredovanju ponudniku (#9654): nenaden porast zahtev enega najemnika ne povzroča več napak 503 pri drugem. Spremenljivka okolja OMNIROUTE_CHAT_VIRTUAL_LANES ima prednost pred to nastavitvijo nadzorne plošče; spremembe začnejo veljati po ponovnem zagonu strežnika.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Domov", "dashboard": "Nadzorna plošča", diff --git a/src/i18n/messages/sr.json b/src/i18n/messages/sr.json index 7aab7eba245..2abcfb39a2e 100644 --- a/src/i18n/messages/sr.json +++ b/src/i18n/messages/sr.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Омогући по-закупцу адаптивне виртуелне линије пријема за расподелу провајдера (#9654): нагли скок захтева једног закупца више не изазива 503 грешку код другог. Env варијабла OMNIROUTE_CHAT_VIRTUAL_LANES има приоритет над овим прекидачем у контролној табли; промене се примењују при поновном покретању сервера.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Почетна", "dashboard": "Контролна табла", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 68a85a7ad3a..28620ef1829 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Aktivera adaptiva virtuella åtkomstfiler per tenant för providerutskick (#9654): en tenants burst ger inte längre en annan 503. Miljövariabeln OMNIROUTE_CHAT_VIRTUAL_LANES har företräde framför den här inställningen i instrumentpanelen; ändringarna träder i kraft vid omstart av servern.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Hem", "dashboard": "Instrumentpanel", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 334ffd8dcb0..c3619929597 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Washa njia za uandikishaji pepe zinazobadilika kwa kila mpangaji (tenant) kwa utumaji wa watoa huduma (#9654): mlipuko wa mpangaji mmoja hautoi tena 503 kwa mwingine. Kigezo cha mazingira cha OMNIROUTE_CHAT_VIRTUAL_LANES kinashinda mpangilio huu wa dashibodi; mabadiliko yanatumika wakati seva inapoanzishwa upya.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 726f0f82c03..e3f042204f9 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "வழங்குநர் அனுப்பீட்டிற்கு ஒவ்வொரு குத்தகைதாரருக்கும் (tenant) தகவமைப்பு மெய்நிகர் சேர்க்கைப் பாதைகளை இயக்கு (#9654): ஒரு குத்தகைதாரரின் அதிகரிப்பு இனி மற்றொருவருக்கு 503 ஐ அளிக்காது. OMNIROUTE_CHAT_VIRTUAL_LANES சூழல் மாறி இந்த டாஷ்போர்டு அமைப்பை விட முன்னுரிமை பெறுகிறது; மாற்றங்கள் சேவையகம் மறுதொடக்கத்தில் நடைமுறைக்கு வரும்.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index ef792351b96..3b1ee5caf1b 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "ప్రొవైడర్ డిస్పాచ్ కోసం ప్రతి-టెనెంట్ అడాప్టివ్ వర్చువల్ అడ్మిషన్ లేన్లను ప్రారంభించండి (#9654): ఒక టెనెంట్ బర్స్ట్ ఇకపై మరొక టెనెంట్కు 503 ఇవ్వదు. OMNIROUTE_CHAT_VIRTUAL_LANES ఎన్విరాన్మెంట్ వేరియబుల్ ఈ డాష్బోర్డ్ సెట్టింగ్ కంటే ప్రాధాన్యత పొందుతుంది; మార్పులు సర్వర్ పునఃప్రారంభంలో ప్రభావం చూపుతాయి.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 58819f0537c..57d4a1d2aaa 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "เปิดใช้เลนรับเข้าเสมือนแบบปรับตัวต่อเทนแนนต์สำหรับการส่งไปยังผู้ให้บริการ (#9654): การพุ่งสูงของเทนแนนต์หนึ่งจะไม่ทำให้อีกเทนแนนต์ได้รับ 503 อีกต่อไป ตัวแปรสภาพแวดล้อม OMNIROUTE_CHAT_VIRTUAL_LANES มีผลเหนือการตั้งค่าแดชบอร์ดนี้ การเปลี่ยนแปลงมีผลเมื่อรีสตาร์ทเซิร์ฟเวอร์", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "บ้าน", "dashboard": "แดชบอร์ด", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 054900127bf..0ccd1cac704 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Sağlayıcı gönderimi için kiracı başına uyarlanabilir sanal kabul şeritlerini etkinleştirin (#9654): bir kiracının ani yükü artık diğerinde 503 hatasına neden olmaz. OMNIROUTE_CHAT_VIRTUAL_LANES ortam değişkeni bu panel ayarına göre önceliklidir; değişiklikler sunucu yeniden başlatıldığında geçerli olur.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Ana Sayfa", "dashboard": "Kontrol Paneli", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 91e911e4ae9..71d21b3a0a3 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Увімкніть адаптивні віртуальні смуги допуску для кожного тенанта під час надсилання провайдерам (#9654): сплеск навантаження одного тенанта більше не викликає 503 в іншого. Змінна середовища OMNIROUTE_CHAT_VIRTUAL_LANES має пріоритет над цим налаштуванням у панелі; зміни набувають чинності після перезапуску сервера.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "додому", "dashboard": "Приладова панель", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 0b646d7420e..c07db81e6fe 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "پرووائیڈر بھیجنے کے لیے فی ٹیننٹ انکولی ورچوئل ایڈمیشن لین فعال کریں (#9654): ایک ٹیننٹ کا اچانک بوجھ اب دوسرے ٹیننٹ کو 503 نہیں دیتا۔ OMNIROUTE_CHAT_VIRTUAL_LANES ماحولیاتی متغیر اس ڈیش بورڈ سیٹنگ پر فوقیت رکھتا ہے؛ تبدیلیاں سرور دوبارہ شروع ہونے پر اثر انداز ہوتی ہیں۔", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "Home", "dashboard": "Dashboard", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index da27606218a..060cf69cd23 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -995,6 +995,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "Bật làn tiếp nhận ảo thích ứng cho từng đối tượng thuê (tenant) để phân phối nhà cung cấp (#9654): một đợt bùng phát của tenant này không còn trả 503 cho tenant khác. Biến môi trường OMNIROUTE_CHAT_VIRTUAL_LANES được ưu tiên hơn cài đặt bảng điều khiển này; các thay đổi có hiệu lực khi khởi động lại máy chủ.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "Các pool proxy và cơ chế xoay vòng theo tài khoản của opencode ngừng dùng lại một proxy vừa thất bại (thăm dò TCP bị từ chối, hoặc nhận 429 qua proxy đó) trong một khoảng thời gian theo tiến trình, tăng gấp đôi sau mỗi lần lặp lại, tối đa đến một giới hạn. Không ghi trạng thái proxy nào; khi mọi ứng viên đều bị tạm gác, lựa chọn vẫn như cũ. Tắt theo mặc định: thứ tự chọn giữ nguyên xoay vòng thông thường.", + "featureFlagProxyPoolEgressObservationDescription": "Hiển thị, bên dưới một nhóm proxy trong bảng điều khiển, số IP đầu ra quan sát được đã phục vụ các thành viên của nhóm trong 24 giờ qua, số kết nối đã dùng chúng và số lớn nhất thấy sau cùng một IP. Chỉ đọc, tính từ nhật ký proxy, không bao giờ dùng để định tuyến. Tắt theo mặc định: trình chỉnh sửa nhóm không đổi và tuyến quan sát trả về null.", "sidebar": { "home": "Trang chủ", "dashboard": "Trang tổng quan", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 09bfa410a9d..efd1599e17d 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "为提供者调度启用按租户的自适应虚拟准入通道(#9654):一个租户的突发流量不再导致另一个租户收到 503。OMNIROUTE_CHAT_VIRTUAL_LANES 环境变量优先于此仪表板设置;更改在服务器重启后生效。", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "首页", "dashboard": "仪表板", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 5f2a271e69b..99bac08c5b0 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -994,6 +994,7 @@ "featureFlagChatVirtualLanesEnabledDescription": "為提供者調度啟用按租戶的自適應虛擬准入通道(#9654):一個租戶的突發流量不再導致另一個租戶收到 503。OMNIROUTE_CHAT_VIRTUAL_LANES 環境變數優先於此儀表板設定;變更在伺服器重新啟動後生效。", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", "sidebar": { "home": "首頁", "dashboard": "儀表板", diff --git a/src/shared/constants/featureFlagDefinitions.ts b/src/shared/constants/featureFlagDefinitions.ts index 28a10a01b54..ef1f5d7c6b9 100644 --- a/src/shared/constants/featureFlagDefinitions.ts +++ b/src/shared/constants/featureFlagDefinitions.ts @@ -203,6 +203,18 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [ requiresRestart: false, warningLevel: "caution", }, + { + key: "PROXY_POOL_EGRESS_OBSERVATION", + label: "Proxy Pool Egress Observation", + description: + "Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null.", + descriptionI18nKey: "featureFlagProxyPoolEgressObservationDescription", + category: "network", + defaultValue: "false", + type: "boolean", + requiresRestart: false, + warningLevel: "info", + }, { key: "MITM_DISABLE_TLS_VERIFY", label: "Disable TLS Verify (MITM)", diff --git a/src/shared/utils/featureFlags.ts b/src/shared/utils/featureFlags.ts index 875b158cf9a..d108dbe5273 100644 --- a/src/shared/utils/featureFlags.ts +++ b/src/shared/utils/featureFlags.ts @@ -189,6 +189,22 @@ export function isProxySkipRecentlyFailedEnabled(): boolean { } } +/** + * Pool egress observation panel (#13581): read-only dashboard line under a proxy pool. + * Opt-in; an unreadable flag store keeps it hidden. + */ +export function isPoolEgressObservationEnabled(): boolean { + try { + return isFeatureFlagEnabled("PROXY_POOL_EGRESS_OBSERVATION"); + } catch (error) { + console.error( + "[featureFlags] Failed to resolve PROXY_POOL_EGRESS_OBSERVATION, defaulting to disabled:", + error instanceof Error ? error.message : error + ); + return false; + } +} + export function isServerOwnedToolLoopEnabled( reader: (key: string) => boolean = isFeatureFlagEnabled ): boolean { diff --git a/tests/unit/feature-flags-settings.test.ts b/tests/unit/feature-flags-settings.test.ts index a5b1f3b785f..c31eab41469 100644 --- a/tests/unit/feature-flags-settings.test.ts +++ b/tests/unit/feature-flags-settings.test.ts @@ -40,7 +40,7 @@ const { // the dead ONEPROXY_ENABLED (readerless since the 1proxy purge, #12091) // brought it back to 53. UNIVERSAL_CONTEXT_HANDOFF_ENABLED bumped it to 54. // #13641 added SEARCH_STATS_HIDE_DELETED_CONNECTIONS, bumping the count to 56. -const EXPECTED_FEATURE_FLAG_COUNT = 62; +const EXPECTED_FEATURE_FLAG_COUNT = 63; // ────────────────────────────────────────────────────── // Test group 1 — Flag definitions registry @@ -225,6 +225,16 @@ describe("featureFlagDefinitions", () => { assert.strictEqual(def.requiresRestart, false); }); + it("defines the pool egress observation as a network boolean flag disabled by default", () => { + // Guards the UI default: the read-only panel under a proxy pool stays hidden unless opted in. + const def = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === "PROXY_POOL_EGRESS_OBSERVATION"); + assert.ok(def, "PROXY_POOL_EGRESS_OBSERVATION should exist"); + assert.strictEqual(def.category, "network"); + assert.strictEqual(def.type, "boolean"); + assert.strictEqual(def.defaultValue, "false"); + assert.strictEqual(def.requiresRestart, false); + }); + it("defines remote audio provider nodes as a network boolean flag disabled by default", () => { // Guards the egress default: with this on, /v1/audio/* may reach a provider node // hosted outside localhost. It must never become an implicit default (cf. #3963). diff --git a/tests/unit/server-owned-tool-loop-flag.test.ts b/tests/unit/server-owned-tool-loop-flag.test.ts index 9aa46264d8f..8b5c6c43156 100644 --- a/tests/unit/server-owned-tool-loop-flag.test.ts +++ b/tests/unit/server-owned-tool-loop-flag.test.ts @@ -68,7 +68,7 @@ describe("isServerOwnedToolLoopEnabled wrapper", () => { describe("feature-flags-settings count update", () => { it("flag count matches updated expected value", () => { - assert.equal(FEATURE_FLAG_DEFINITIONS.length, 62); + assert.equal(FEATURE_FLAG_DEFINITIONS.length, 63); }); }); From 1b08e97a001a3871c4940ba5511534d1ef5ff1ca Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 15 Sep 2026 04:41:38 -0300 Subject: [PATCH 4/5] fix(proxies): gate the pool egress observation on its flag and validate the route query Keeps the read-only panel of #13581 and fixes the review findings: - readPoolEgressObservation() reads the PROXY_POOL_EGRESS_OBSERVATION feature flag (default off, fail closed) instead of a default-on env switch, so the line under a proxy pool only appears once an operator opts in; the route answers null while it is off. - The route validates its query with a Zod schema: an unknown scope (or an over-long scopeId) is a 400 instead of being normalized to the global pool, and every error goes through errorResponse()/buildErrorBody() so no raw error message can reach the body. - docs/openapi.yaml now documents the security tier, both query parameters, the nullable response object and the 400/401/500 responses. - The three dashboard strings reach every locale (pt-BR translated, vi kept, the rest deferred with the __MISSING__ marker the i18n gates accept). - Re-apply the ProxyRegistryManager.tsx file-size note, document the flag in .env.example and ENVIRONMENT.md with its real default, and cover flag default/off/on, the DB override and the new 400 paths in the tests. (cherry picked from commit 5e9716f63925aa1ce9c28198d9b6483e96530449) --- .env.example | 9 +-- .../features/13581-pool-egress-observation.md | 2 +- docs/openapi.yaml | 61 ++++++++++++++++++- docs/reference/ENVIRONMENT.md | 2 +- .../proxies/pool/egress-observation/route.ts | 44 ++++++------- src/i18n/messages/ar.json | 3 + src/i18n/messages/az.json | 3 + src/i18n/messages/bg.json | 3 + src/i18n/messages/bn.json | 3 + src/i18n/messages/cs.json | 3 + src/i18n/messages/da.json | 3 + src/i18n/messages/de.json | 3 + src/i18n/messages/el.json | 3 + src/i18n/messages/es.json | 3 + src/i18n/messages/et.json | 3 + src/i18n/messages/fa.json | 3 + src/i18n/messages/fi.json | 3 + src/i18n/messages/fr.json | 3 + src/i18n/messages/ga.json | 3 + src/i18n/messages/gu.json | 3 + src/i18n/messages/he.json | 3 + src/i18n/messages/hi.json | 3 + src/i18n/messages/hr.json | 3 + src/i18n/messages/hu.json | 3 + src/i18n/messages/id.json | 3 + src/i18n/messages/it.json | 3 + src/i18n/messages/ja.json | 3 + src/i18n/messages/km.json | 3 + src/i18n/messages/kn.json | 3 + src/i18n/messages/ko.json | 3 + src/i18n/messages/lt.json | 3 + src/i18n/messages/lv.json | 3 + src/i18n/messages/ml.json | 3 + src/i18n/messages/mr.json | 3 + src/i18n/messages/ms.json | 3 + src/i18n/messages/mt.json | 3 + src/i18n/messages/my.json | 3 + src/i18n/messages/ne.json | 3 + src/i18n/messages/nl.json | 3 + src/i18n/messages/no.json | 3 + src/i18n/messages/or.json | 3 + src/i18n/messages/pa.json | 3 + src/i18n/messages/phi.json | 3 + src/i18n/messages/pl.json | 3 + src/i18n/messages/pt-BR.json | 3 + src/i18n/messages/pt.json | 3 + src/i18n/messages/ro.json | 3 + src/i18n/messages/ru.json | 3 + src/i18n/messages/si.json | 3 + src/i18n/messages/sk.json | 3 + src/i18n/messages/sl.json | 3 + src/i18n/messages/sr.json | 3 + src/i18n/messages/sv.json | 3 + src/i18n/messages/sw.json | 3 + src/i18n/messages/ta.json | 3 + src/i18n/messages/te.json | 3 + src/i18n/messages/th.json | 3 + src/i18n/messages/tr.json | 3 + src/i18n/messages/uk-UA.json | 3 + src/i18n/messages/ur.json | 3 + src/i18n/messages/zh-CN.json | 3 + src/i18n/messages/zh-TW.json | 3 + src/lib/proxyPoolEgressObservation.ts | 13 ++-- src/shared/validation/schemas/proxy.ts | 17 ++++++ ...roxy-pool-egress-observation-route.test.ts | 46 +++++++++++--- .../proxy-pool-egress-observation.test.ts | 20 ++++-- 66 files changed, 335 insertions(+), 50 deletions(-) diff --git a/.env.example b/.env.example index 202fd40aea6..e5888d0852f 100644 --- a/.env.example +++ b/.env.example @@ -2237,10 +2237,11 @@ APP_LOG_TO_FILE=true # proxy — only the operator sets active/inactive (a flaky probe must not strand an # assigned proxy; #6246). Set "true" to restore the legacy test-and-set behaviour. # PROXY_HEALTH_AUTO_DEACTIVATE=false -# Show, under a proxy pool in the dashboard, how many observed egress IPs served its -# members over the last 24 h and how many connections used them (read-only, computed -# from the proxy log, never used for routing). Set "false" (or 0, no, off) to turn it off. -# PROXY_POOL_EGRESS_OBSERVATION=true +# Opt-in feature flag (default off; a dashboard DB override wins over this value): show, +# under a proxy pool in the dashboard, how many observed egress IPs served its members over +# the last 24 h and how many connections used them (read-only, computed from the proxy log, +# never used for routing). "true" (or 1, yes) enables it. +# PROXY_POOL_EGRESS_OBSERVATION=false # Allow OAuth and provider validation flows to bypass a pinned proxy and connect # directly when proxy reachability pre-checks fail. Default: false. diff --git a/changelog.d/features/13581-pool-egress-observation.md b/changelog.d/features/13581-pool-egress-observation.md index 6b8fab657f8..c535c5f2d5e 100644 --- a/changelog.d/features/13581-pool-egress-observation.md +++ b/changelog.d/features/13581-pool-egress-observation.md @@ -1 +1 @@ -- **feat(proxies):** the proxy pool editor shows, for the last 24 h, how many distinct egress IPs actually served the pool's members, how many connections went through them and the most seen behind one IP, read from the proxy log through a separate route so it can never break the pool screen ([#13581](https://github.com/diegosouzapw/OmniRoute/pull/13581)) — thanks @maxmad64bis +- **feat(proxies):** the proxy pool editor shows, for the last 24 h, how many distinct egress IPs actually served the pool's members, how many connections went through them and the most seen behind one IP, read from the proxy log through a separate route so it can never break the pool screen; opt-in with the `PROXY_POOL_EGRESS_OBSERVATION` feature flag (default off) ([#13581](https://github.com/diegosouzapw/OmniRoute/pull/13581)) — thanks @maxmad64bis diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 5056c598b8c..95d9778f486 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -11548,9 +11548,68 @@ paths: tags: - Settings summary: "GET settings › proxies › pool › egress observation" + description: >- + Read-only observation of how many distinct egress IPs actually served the members of a + proxy pool over the last 24 h, read from the proxy log (numbers only, never used for + routing). Opt-in through the PROXY_POOL_EGRESS_OBSERVATION feature flag: while it is + off, or when the read fails, the body is JSON null. Results are cached for 30 seconds + per normalized scope (key is read as account, global as the stored global pool). + security: + - ManagementSessionAuth: [] + parameters: + - name: scope + in: query + required: true + schema: + type: string + enum: + - global + - provider + - account + - combo + - key + - name: scopeId + in: query + required: false + description: Required for every scope except global; ignored for global. + schema: + type: string + maxLength: 256 responses: "200": - description: OK + description: The observation, or null when the feature flag is off or the read failed. + content: + application/json: + schema: + type: object + nullable: true + required: + - connections + - distinctExits + - maxConnectionsOnOneExit + - windowHours + properties: + connections: + type: integer + minimum: 0 + description: Distinct OmniRoute connections logged through the pool members. + distinctExits: + type: integer + minimum: 0 + description: Distinct egress IPs observed for those members. + maxConnectionsOnOneExit: + type: integer + minimum: 0 + description: Most connections seen behind one egress IP over the window. + windowHours: + type: integer + example: 24 + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/ManagementAuthenticationRequired" + "500": + $ref: "#/components/responses/InternalError" /api/settings/proxy/cloudflare-deploy: post: tags: diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 4a65d41ba8e..691e21d0194 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -1114,7 +1114,7 @@ Anthropic-compatible provider instead. | `PROXY_HEALTH_TEST_STAGGER_MS` | `100` | `src/lib/proxyHealth/probeTarget.ts` | Delay in ms between two probe departures inside a batch. Without it the whole batch leaves at the same moment and a shared egress IP can trip a rate-limited target. Set to `0` to disable the spacing; capped at 5000. | | `PROXY_HEALTH_USE_PROVIDER_TARGET` | `true` | `src/lib/proxyHealth/providerProbeTarget.ts` | Set "false" to stop probing the real host of a proxy's assigned provider (`GET /models`, no API key) and always use `PROXY_HEALTH_TEST_URL` instead. | | `PROXY_HEALTH_AUTO_DEACTIVATE` | `false` | `src/lib/proxyHealth/statusPolicy.ts` | When `false` (default), automated reachability probes (the scheduler + the `/api/settings/proxies/auto-test` "Test All" button) are **read-only** and never write a proxy's status — only the operator sets active/inactive, so a flaky probe can't strand an assigned proxy (#6246). Set `true` to restore the legacy test-and-set behaviour. | -| `PROXY_POOL_EGRESS_OBSERVATION` | `true` | `src/lib/proxyPoolEgressObservation.ts` | Set `false` (or `0`, `no`, `off`) to turn off the read-only pool egress observation shown under a proxy pool in the dashboard (distinct observed egress IPs and connections over 24 h, from the proxy log). It never affects routing. | +| `PROXY_POOL_EGRESS_OBSERVATION` | `false` | `src/shared/utils/featureFlags.ts` | Opt-in feature flag (see [FEATURE_FLAGS.md](./FEATURE_FLAGS.md); a dashboard DB override wins). `true` (or `1`, `yes`) shows the read-only pool egress observation under a proxy pool in the dashboard (distinct egress IPs, connections and the most seen behind one IP over the last 24 h, from the proxy log). Never used for routing. | | `PROXY_AUTO_REMOVE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler auto-remove proxies after repeated consecutive failures. | | `PROXY_AUTO_REMOVE_AFTER` | `3` | `src/lib/proxyHealth/scheduler.ts` | Consecutive failures before the scheduler auto-removes a proxy (when `PROXY_AUTO_REMOVE=true`). | | `PROXY_AUTO_DISABLE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler soft-disable (status `dead`, never deleted) a proxy after repeated consecutive failures, instead of removing it. Non-destructive alternative to `PROXY_AUTO_REMOVE`: the proxy drops out of pool/rotation resolution immediately (the alive-status filter used by scope-pool resolution already excludes it) and is automatically re-activated once it starts passing probes again. Shares the `PROXY_AUTO_REMOVE_AFTER` threshold. If both flags are `true`, `PROXY_AUTO_REMOVE` wins. | diff --git a/src/app/api/settings/proxies/pool/egress-observation/route.ts b/src/app/api/settings/proxies/pool/egress-observation/route.ts index c646341bcae..25c523e30b8 100644 --- a/src/app/api/settings/proxies/pool/egress-observation/route.ts +++ b/src/app/api/settings/proxies/pool/egress-observation/route.ts @@ -1,37 +1,39 @@ -import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; +import { errorResponse } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { readPoolEgressObservation } from "@/lib/proxyPoolEgressObservation"; +import { proxyPoolEgressObservationQuerySchema } from "@/shared/validation/schemas"; +import { + formatValidationMessage, + isValidationFailure, + validateBody, +} from "@/shared/validation/helpers"; // Observed egress spread of a proxy pool, read from the proxy log (numbers only). Kept // apart from GET /api/settings/proxies/pool on purpose: a failure here answers null and // can never break the pool editor. Same management-auth tier as the pool route. // -// GET ?scope=&scopeId= -> { connections, distinctExits, maxConnectionsOnOneExit, windowHours } -// | null (read failed or PROXY_POOL_EGRESS_OBSERVATION=false) +// GET ?scope=global|provider|account|combo|key&scopeId= +// -> { connections, distinctExits, maxConnectionsOnOneExit, windowHours } +// | null (read failed, or the PROXY_POOL_EGRESS_OBSERVATION feature flag is off) +// -> 400 on an unknown scope or a missing scopeId outside global export async function GET(request: Request) { const authError = await requireManagementAuth(request); if (authError) return authError; try { const { searchParams } = new URL(request.url); - const scope = searchParams.get("scope"); - if (!scope) { - return createErrorResponse({ - status: 400, - message: "scope is required", - type: "invalid_request", - }); + const validation = validateBody(proxyPoolEgressObservationQuerySchema, { + scope: searchParams.get("scope") ?? undefined, + scopeId: searchParams.get("scopeId"), + }); + if (isValidationFailure(validation)) { + return errorResponse(400, formatValidationMessage(validation.error)); } - const scopeId = searchParams.get("scopeId")?.trim() || null; - if (scope !== "global" && !scopeId) { - return createErrorResponse({ - status: 400, - message: "scopeId is required for provider/account/combo/key scope", - type: "invalid_request", - }); - } - return Response.json(readPoolEgressObservation(scope, scope === "global" ? null : scopeId)); - } catch (error) { - return createErrorResponseFromUnknown(error, "Failed to load pool egress observation"); + const { scope, scopeId } = validation.data; + return Response.json( + readPoolEgressObservation(scope, scope === "global" ? null : (scopeId ?? null)) + ); + } catch { + return errorResponse(500, "Failed to load pool egress observation"); } } diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index b42e4175716..7747b92b8ee 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -10960,6 +10960,9 @@ "strategyLatency": "محسن لزمن الاستجابة", "poolMembersLabel": "أعضاء التجمع ({count})", "poolNoMembers": "لا توجد وكلاء في هذا التجمع بعد.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "إزالة", "poolRemoveFailed": "فشل إزالة الوكيل من التجمع", "poolAddLabel": "إضافة وكيل", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index 173620b910d..88c9efc1d88 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Gecikməyə görə optimallaşdırılmış", "poolMembersLabel": "Hovuz üzvləri ({count})", "poolNoMembers": "Bu hovuzda hələ proksi yoxdur.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Çıxar", "poolRemoveFailed": "Proksini hovuzdan çıxarmaq mümkün olmadı", "poolAddLabel": "Proksi əlavə et", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index c4f7b3679f9..4f2d67f3fdb 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Оптимизирано по латентност (Latency-optimized)", "poolMembersLabel": "Членове на пула ({count})", "poolNoMembers": "Все още няма проксита в този пул.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Премахване", "poolRemoveFailed": "Неуспешно премахване на проксито от пула", "poolAddLabel": "Добавяне на прокси", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 667d06ded27..fe9cc5559e9 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -10960,6 +10960,9 @@ "strategyLatency": "লেটেন্সি-অপ্টিমাইজড", "poolMembersLabel": "পুলের সদস্য ({count})", "poolNoMembers": "এই পুলে এখনও কোনো প্রক্সি নেই।", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "সরান", "poolRemoveFailed": "পুল থেকে প্রক্সি সরাতে ব্যর্থ হয়েছে", "poolAddLabel": "একটি প্রক্সি যোগ করুন", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 4ae01f332e6..ca234e1cea4 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Optimalizováno podle latence", "poolMembersLabel": "Členové poolu ({count})", "poolNoMembers": "V tomto poolu zatím nejsou žádné proxy.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Odebrat", "poolRemoveFailed": "Nepodařilo se odebrat proxy z poolu", "poolAddLabel": "Přidat proxy", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 3a166ec6746..bc91a6f40a2 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latensoptimeret", "poolMembersLabel": "Pool-medlemmer ({count})", "poolNoMembers": "Ingen proxyer i denne pool endnu.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Fjern", "poolRemoveFailed": "Kunne ikke fjerne proxyen fra poolen", "poolAddLabel": "Tilføj en proxy", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 822530e2d7d..33f06cb903c 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latenzoptimiert", "poolMembersLabel": "Pool-Mitglieder ({count})", "poolNoMembers": "Noch keine Proxys in diesem Pool.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Entfernen", "poolRemoveFailed": "Fehler beim Entfernen des Proxys aus dem Pool", "poolAddLabel": "Proxy hinzufügen", diff --git a/src/i18n/messages/el.json b/src/i18n/messages/el.json index 5b0ce92757d..4d59f236a5c 100644 --- a/src/i18n/messages/el.json +++ b/src/i18n/messages/el.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Βελτιστοποιημένο βάσει καθυστέρησης", "poolMembersLabel": "Μέλη pool ({count})", "poolNoMembers": "Δεν υπάρχουν ακόμα proxy σε αυτό το pool.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Αφαίρεση", "poolRemoveFailed": "Αποτυχία αφαίρεσης του proxy από το pool", "poolAddLabel": "Προσθήκη διαμεσολαβητή", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 9c19cd1c23e..c9c28fb624c 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latency-optimized", "poolMembersLabel": "Pool members ({count})", "poolNoMembers": "No proxies in this pool yet.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Remove", "poolRemoveFailed": "Failed to remove the proxy from the pool", "poolAddLabel": "Add a proxy", diff --git a/src/i18n/messages/et.json b/src/i18n/messages/et.json index 79a6a772e34..d4a3e947aed 100644 --- a/src/i18n/messages/et.json +++ b/src/i18n/messages/et.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Latentsusoptimeeritud", "poolMembersLabel": "Kogumi liikmed ({count})", "poolNoMembers": "Selles kogumis pole veel ühtegi puhverserverit.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Eemalda", "poolRemoveFailed": "Puhverserveri eemaldamine kogumist nurjus", "poolAddLabel": "Lisa puhverserver", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index cc589196884..0292452fc54 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -10960,6 +10960,9 @@ "strategyLatency": "بهینه‌شده برای تاخیر", "poolMembersLabel": "اعضای استخر ({count})", "poolNoMembers": "هنوز هیچ پروکسی در این استخر وجود ندارد.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "حذف", "poolRemoveFailed": "حذف پروکسی از استخر ناموفق بود", "poolAddLabel": "افزودن یک پروکسی", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index aff12f888ba..4b734f1169d 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Viiveoptimoitu", "poolMembersLabel": "Poolin jäsenet ({count})", "poolNoMembers": "Tässä poolissa ei ole vielä proxyja.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Poista", "poolRemoveFailed": "Proxyn poistaminen poolista epäonnistui", "poolAddLabel": "Lisää proxy", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index a0dea7b953e..867a0d8c06d 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Optimisé pour la latence", "poolMembersLabel": "Membres du pool ({count})", "poolNoMembers": "Aucun proxy dans ce pool pour le moment.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Retirer", "poolRemoveFailed": "Échec du retrait du proxy du pool", "poolAddLabel": "Ajouter un proxy", diff --git a/src/i18n/messages/ga.json b/src/i18n/messages/ga.json index 5f5d29a5504..19f1c770a3e 100644 --- a/src/i18n/messages/ga.json +++ b/src/i18n/messages/ga.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Optamaithe ag moill", "poolMembersLabel": "Comhaltaí na linne ({count})", "poolNoMembers": "Níl aon seachfhreastalaithe sa linn seo fós.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Bain", "poolRemoveFailed": "Theip ar bhaint an seachfhreastalaí as an linn", "poolAddLabel": "Cuir procsí leis", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 7ea2f8bc0bc..c47a3ace72f 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -10960,6 +10960,9 @@ "strategyLatency": "લેટન્સી-ઓપ્ટિમાઇઝ્ડ", "poolMembersLabel": "પૂલ સભ્યો ({count})", "poolNoMembers": "આ પૂલમાં હજી સુધી કોઈ પ્રોક્સી નથી.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "દૂર કરો", "poolRemoveFailed": "પૂલમાંથી પ્રોક્સી દૂર કરવામાં નિષ્ફળ", "poolAddLabel": "પ્રોક્સી ઉમેરો", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 26e0edf38c1..adc8d93f304 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -10960,6 +10960,9 @@ "strategyLatency": "מותאם השהיה", "poolMembersLabel": "חברי המאגר ({count})", "poolNoMembers": "אין עדיין שרתי פרוקסי במאגר זה.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "הסר", "poolRemoveFailed": "הסרת הפרוקסי מהמאגר נכשלה", "poolAddLabel": "הוסף פרוקסי", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 5da9d40a433..e030c90a3ab 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -10960,6 +10960,9 @@ "strategyLatency": "लेटेंसी-ऑप्टिमाइज़्ड", "poolMembersLabel": "पूल सदस्य ({count})", "poolNoMembers": "इस पूल में अभी तक कोई प्रॉक्सी नहीं है।", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "हटाएं", "poolRemoveFailed": "पूल से प्रॉक्सी को हटाने में विफल", "poolAddLabel": "एक प्रॉक्सी जोड़ें", diff --git a/src/i18n/messages/hr.json b/src/i18n/messages/hr.json index 311155e4c8f..386ebbb54e3 100644 --- a/src/i18n/messages/hr.json +++ b/src/i18n/messages/hr.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Optimizirano prema latenciji", "poolMembersLabel": "Članovi poola ({count})", "poolNoMembers": "Još nema proxyja u ovom poolu.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Ukloni", "poolRemoveFailed": "Uklanjanje proxyja iz poola nije uspjelo", "poolAddLabel": "Dodaj proxy", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 58232e38a68..a84fb855a1b 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Késleltetésre optimalizált", "poolMembersLabel": "Készlet tagjai ({count})", "poolNoMembers": "Még nincsenek proxyk ebben a készletben.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Eltávolítás", "poolRemoveFailed": "Nem sikerült eltávolítani a proxyt a készletből", "poolAddLabel": "Proxy hozzáadása", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index a59fda7a66f..0e9d35c1f53 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Dioptimalkan untuk latensi", "poolMembersLabel": "Anggota pool ({count})", "poolNoMembers": "Belum ada proksi di pool ini.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Hapus", "poolRemoveFailed": "Gagal menghapus proksi dari pool", "poolAddLabel": "Tambahkan proksi", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index b5f702bf5b0..94faf0facab 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Ottimizzato per la latenza", "poolMembersLabel": "Membri del pool ({count})", "poolNoMembers": "Nessun proxy ancora presente in questo pool.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Rimuovi", "poolRemoveFailed": "Impossibile rimuovere il proxy dal pool", "poolAddLabel": "Aggiungi un proxy", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 4475d093509..8fbdf938278 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -10960,6 +10960,9 @@ "strategyLatency": "レイテンシー最適化", "poolMembersLabel": "プールメンバー ({count})", "poolNoMembers": "このプールにはまだプロキシがありません。", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "削除", "poolRemoveFailed": "プールからプロキシを削除できませんでした", "poolAddLabel": "プロキシを追加", diff --git a/src/i18n/messages/km.json b/src/i18n/messages/km.json index 41a2325e66f..33181749fe4 100644 --- a/src/i18n/messages/km.json +++ b/src/i18n/messages/km.json @@ -10967,6 +10967,9 @@ "strategyLatency": "បង្កើនប្រសិទ្ធភាពតាមភាពយឺតយ៉ាវ", "poolMembersLabel": "សមាជិកក្រុម ({count})", "poolNoMembers": "មិនទាន់មានប្រូកស៊ីនៅក្នុងក្រុមនេះទេ។", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ដកចេញ", "poolRemoveFailed": "បានបរាជ័យក្នុងការដកប្រូកស៊ីចេញពីក្រុម", "poolAddLabel": "បន្ថែមប្រូកស៊ី", diff --git a/src/i18n/messages/kn.json b/src/i18n/messages/kn.json index db265ad5c08..2e58bb9853a 100644 --- a/src/i18n/messages/kn.json +++ b/src/i18n/messages/kn.json @@ -10967,6 +10967,9 @@ "strategyLatency": "ಲೇಟೆನ್ಸಿ-ಆಪ್ಟಿಮೈಸ್ಡ್", "poolMembersLabel": "ಪೂಲ್ ಸದಸ್ಯರು ({count})", "poolNoMembers": "ಈ ಪೂಲ್ನಲ್ಲಿ ಇನ್ನೂ ಯಾವುದೇ ಪ್ರಾಕ್ಸಿಗಳಿಲ್ಲ.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ತೆಗೆದುಹಾಕಿ", "poolRemoveFailed": "ಪೂಲ್ನಿಂದ ಪ್ರಾಕ್ಸಿಯನ್ನು ತೆಗೆದುಹಾಕಲು ವಿಫಲವಾಗಿದೆ", "poolAddLabel": "ಪ್ರಾಕ್ಸಿಯನ್ನು ಸೇರಿಸಿ", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index f261cee2511..eb0d508dc4c 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -10960,6 +10960,9 @@ "strategyLatency": "지연 시간 최적화", "poolMembersLabel": "풀 멤버 ({count})", "poolNoMembers": "이 풀에 아직 프록시가 없습니다.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "제거", "poolRemoveFailed": "풀에서 프록시를 제거하지 못했습니다", "poolAddLabel": "프록시 추가", diff --git a/src/i18n/messages/lt.json b/src/i18n/messages/lt.json index 58f81532aea..077af0c27d0 100644 --- a/src/i18n/messages/lt.json +++ b/src/i18n/messages/lt.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Optimizuota pagal delsą", "poolMembersLabel": "Telkinio nariai ({count})", "poolNoMembers": "Šiame telkinyje dar nėra tarpinių serverių.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Pašalinti", "poolRemoveFailed": "Nepavyko pašalinti tarpinio serverio iš telkinio", "poolAddLabel": "Pridėti tarpinį serverį", diff --git a/src/i18n/messages/lv.json b/src/i18n/messages/lv.json index 92df748d5ce..54a09d29c21 100644 --- a/src/i18n/messages/lv.json +++ b/src/i18n/messages/lv.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Ar aizkaves optimizāciju", "poolMembersLabel": "Kopas dalībnieki ({count})", "poolNoMembers": "Šajā kopā vēl nav starpniekserveru.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Noņemt", "poolRemoveFailed": "Neizdevās noņemt starpniekserveri no kopas", "poolAddLabel": "Pievienot proksi", diff --git a/src/i18n/messages/ml.json b/src/i18n/messages/ml.json index 369ef3ffba8..26f0db9549b 100644 --- a/src/i18n/messages/ml.json +++ b/src/i18n/messages/ml.json @@ -10967,6 +10967,9 @@ "strategyLatency": "ലേറ്റൻസി-ഒപ്റ്റിമൈസ്ഡ്", "poolMembersLabel": "പൂൾ അംഗങ്ങൾ ({count})", "poolNoMembers": "ഈ പൂളിൽ ഇതുവരെ പ്രോക്സികളൊന്നുമില്ല.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "നീക്കം ചെയ്യുക", "poolRemoveFailed": "പൂളിൽ നിന്ന് പ്രോക്സി നീക്കം ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", "poolAddLabel": "ഒരു പ്രോക്സി ചേർക്കുക", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 87e267b28cb..da89d442fe0 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -10960,6 +10960,9 @@ "strategyLatency": "लेटन्सी-ऑप्टिमाइझ्ड", "poolMembersLabel": "पूल सदस्य ({count})", "poolNoMembers": "या पूलमध्ये अद्याप कोणतेही प्रॉक्सी नाहीत.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "काढून टाका", "poolRemoveFailed": "पूलमधून प्रॉक्सी काढून टाकण्यात अयशस्वी", "poolAddLabel": "प्रॉक्सी जोडा", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 0b41853bf1d..0345321b590 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Dioptimumkan kependaman", "poolMembersLabel": "Ahli kolam ({count})", "poolNoMembers": "Belum ada proksi dalam kolam ini.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Alih keluar", "poolRemoveFailed": "Gagal mengalih keluar proksi daripada kolam", "poolAddLabel": "Tambah proksi", diff --git a/src/i18n/messages/mt.json b/src/i18n/messages/mt.json index 5cc3115be38..be7e1c2a4ef 100644 --- a/src/i18n/messages/mt.json +++ b/src/i18n/messages/mt.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Ottimizzata għal-latenza", "poolMembersLabel": "Membri tal-pool ({count})", "poolNoMembers": "Għad m’hemm l-ebda proxy f’dan il-pool.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Neħħi", "poolRemoveFailed": "Ma rnexxiex it-tneħħija tal-proxy mill-pool", "poolAddLabel": "Żid proxy", diff --git a/src/i18n/messages/my.json b/src/i18n/messages/my.json index 67fbf66e522..f582cfcba9e 100644 --- a/src/i18n/messages/my.json +++ b/src/i18n/messages/my.json @@ -10967,6 +10967,9 @@ "strategyLatency": "တုံ့ပြန်ချိန်အကောင်းဆုံး", "poolMembersLabel": "ပူးလ်အဖွဲ့ဝင်များ ({count})", "poolNoMembers": "ဤပူးလ်တွင် ပရောက်စီများ မရှိသေးပါ။", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ဖယ်ရှားရန်", "poolRemoveFailed": "ပရောက်စီကို ပူးလ်မှ ဖယ်ရှား၍ မရပါ", "poolAddLabel": "ပရောက်စီတစ်ခု ထည့်ရန်", diff --git a/src/i18n/messages/ne.json b/src/i18n/messages/ne.json index af05e08c98e..3ed57268d7f 100644 --- a/src/i18n/messages/ne.json +++ b/src/i18n/messages/ne.json @@ -10967,6 +10967,9 @@ "strategyLatency": "लेटेन्सी-अनुकूलित", "poolMembersLabel": "पुल सदस्यहरू ({count})", "poolNoMembers": "यस पुलमा अहिलेसम्म कुनै प्रोक्सी छैन।", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "हटाउनुहोस्", "poolRemoveFailed": "पुलबाट प्रोक्सी हटाउन असफल भयो", "poolAddLabel": "प्रोक्सी थप्नुहोस्", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 5136b39c4b8..68667ae781c 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latentie-geoptimaliseerd", "poolMembersLabel": "Poolleden ({count})", "poolNoMembers": "Nog geen proxy's in deze pool.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Verwijderen", "poolRemoveFailed": "Kan de proxy niet uit de pool verwijderen", "poolAddLabel": "Een proxy toevoegen", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index e64eb9e7271..199e198685d 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latensoptimalisert", "poolMembersLabel": "Pool-medlemmer ({count})", "poolNoMembers": "Ingen proxyer i denne poolen ennå.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Fjern", "poolRemoveFailed": "Kunne ikke fjerne proxyen fra poolen", "poolAddLabel": "Legg til en proxy", diff --git a/src/i18n/messages/or.json b/src/i18n/messages/or.json index da3ba5b100d..1d0640470d1 100644 --- a/src/i18n/messages/or.json +++ b/src/i18n/messages/or.json @@ -10967,6 +10967,9 @@ "strategyLatency": "ଲେଟେନ୍ସି-ଅପ୍ଟିମାଇଜ୍ଡ", "poolMembersLabel": "ପୁଲ୍ ସଦସ୍ୟ ({count})", "poolNoMembers": "ଏହି ପୁଲ୍ରେ ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ପ୍ରକ୍ସି ନାହିଁ।", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ହଟାନ୍ତୁ", "poolRemoveFailed": "ପୁଲ୍ରୁ ପ୍ରକ୍ସିକୁ ହଟାଇବାରେ ବିଫଳ", "poolAddLabel": "ଏକ ପ୍ରକ୍ସି ଯୋଡ଼ନ୍ତୁ", diff --git a/src/i18n/messages/pa.json b/src/i18n/messages/pa.json index 8ecc7f2f611..fc2235233c8 100644 --- a/src/i18n/messages/pa.json +++ b/src/i18n/messages/pa.json @@ -10967,6 +10967,9 @@ "strategyLatency": "ਲੇਟੈਂਸੀ-ਅਨੁਕੂਲਿਤ", "poolMembersLabel": "ਪੂਲ ਮੈਂਬਰ ({count})", "poolNoMembers": "ਇਸ ਪੂਲ ਵਿੱਚ ਹਾਲੇ ਕੋਈ ਪ੍ਰੌਕਸੀ ਨਹੀਂ ਹੈ।", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ਹਟਾਓ", "poolRemoveFailed": "ਪੂਲ ਵਿੱਚੋਂ ਪ੍ਰੌਕਸੀ ਹਟਾਉਣ ਵਿੱਚ ਅਸਫਲ", "poolAddLabel": "ਇੱਕ ਪ੍ਰੌਕਸੀ ਜੋੜੋ", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 84c197dc9bd..98d4d474057 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latency-optimized", "poolMembersLabel": "Mga miyembro ng pool ({count})", "poolNoMembers": "Wala pang mga proxy sa pool na ito.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Alisin", "poolRemoveFailed": "Hindi naalis ang proxy mula sa pool", "poolAddLabel": "Magdagdag ng proxy", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index b2ce0f03dd9..e146eb5e1fd 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latency-optimized", "poolMembersLabel": "Członkowie puli ({count})", "poolNoMembers": "Brak proxy w tej puli.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Usuń", "poolRemoveFailed": "Nie udało się usunąć proxy z puli", "poolAddLabel": "Dodaj proxy", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 68724e46196..0cb27d5becb 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -10968,6 +10968,9 @@ "strategyLatency": "Otimizado por latência", "poolMembersLabel": "Membros do pool ({count})", "poolNoMembers": "Ainda não há proxies neste pool.", + "poolEgressObservation": "{exits, plural, one {# saída} other {# saídas}} usada(s) por {connections, plural, one {# conexão} other {# conexões}} · até {max} em uma saída · últimas {hours} h", + "poolEgressObservationEmpty": "Nenhum tráfego observado nas últimas {hours} h", + "poolEgressObservationHint": "Contado a partir do log de proxy na janela: IPs de saída distintos observados para os membros deste pool e as conexões do OmniRoute que os usaram. \"Em uma saída\" significa ao longo da janela, não ao mesmo tempo. O bloqueio de 429 por IP só pausa conexões da mesma família de provedor.", "poolRemove": "Remover", "poolRemoveFailed": "Falha ao remover o proxy do pool", "poolAddLabel": "Adicionar um proxy", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 5dae492f9c0..cc6b4ec6b18 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -10961,6 +10961,9 @@ "strategyLatency": "Otimizado para latência", "poolMembersLabel": "Membros do pool ({count})", "poolNoMembers": "Ainda não existem proxies neste pool.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Remover", "poolRemoveFailed": "Falha ao remover o proxy do pool", "poolAddLabel": "Adicionar um proxy", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 165ce734c5f..9bc9e12a84e 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Optimizat pentru latență", "poolMembersLabel": "Membrii pool-ului ({count})", "poolNoMembers": "Nu există încă proxy-uri în acest pool.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Elimină", "poolRemoveFailed": "Nu s-a putut elimina proxy-ul din pool", "poolAddLabel": "Adaugă un proxy", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index d66680d76b0..d1d5f14d8d5 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Оптимизация по задержке", "poolMembersLabel": "Участники пула ({count})", "poolNoMembers": "В этом пуле пока нет прокси.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Удалить", "poolRemoveFailed": "Не удалось удалить прокси из пула", "poolAddLabel": "Добавить прокси", diff --git a/src/i18n/messages/si.json b/src/i18n/messages/si.json index 533c84b7490..4f513ccf60d 100644 --- a/src/i18n/messages/si.json +++ b/src/i18n/messages/si.json @@ -10967,6 +10967,9 @@ "strategyLatency": "ප්රමාදයට ප්රශස්ත කළ", "poolMembersLabel": "සංචිත සාමාජිකයන් ({count})", "poolNoMembers": "මෙම සංචිතයේ තවමත් ප්රොක්සි නොමැත.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ඉවත් කරන්න", "poolRemoveFailed": "සංචිතයෙන් ප්රොක්සිය ඉවත් කිරීමට අසමත් විය", "poolAddLabel": "ප්රොක්සියක් එක් කරන්න", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 13c25e18bd8..89cf618d931 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Optimalizované podľa latencie", "poolMembersLabel": "Členovia poolu ({count})", "poolNoMembers": "V tomto poole zatiaľ nie sú žiadne proxy servery.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Odstrániť", "poolRemoveFailed": "Nepodarilo sa odstrániť proxy z poolu", "poolAddLabel": "Pridať proxy", diff --git a/src/i18n/messages/sl.json b/src/i18n/messages/sl.json index e5c1e9bf55c..0bbd30eaba7 100644 --- a/src/i18n/messages/sl.json +++ b/src/i18n/messages/sl.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Optimizirano glede na zakasnitev", "poolMembersLabel": "Člani skupine ({count})", "poolNoMembers": "V tej skupini še ni posredniških strežnikov.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Odstrani", "poolRemoveFailed": "Posredniškega strežnika ni bilo mogoče odstraniti iz skupine", "poolAddLabel": "Dodaj posredniški strežnik", diff --git a/src/i18n/messages/sr.json b/src/i18n/messages/sr.json index 2abcfb39a2e..aadacc8cdc4 100644 --- a/src/i18n/messages/sr.json +++ b/src/i18n/messages/sr.json @@ -10967,6 +10967,9 @@ "strategyLatency": "Оптимизовано по кашњењу", "poolMembersLabel": "Чланови групе ({count})", "poolNoMembers": "Нема proxy-ja у овој групи још.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Уклони", "poolRemoveFailed": "Уклањање proxy-ja из групе није успело", "poolAddLabel": "Додај proxy", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 28620ef1829..73319a7a16a 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latensoptimerad", "poolMembersLabel": "Poolmedlemmar ({count})", "poolNoMembers": "Inga proxyservrar i denna pool ännu.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Ta bort", "poolRemoveFailed": "Misslyckades med att ta bort proxyn från poolen", "poolAddLabel": "Lägg till en proxy", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index c3619929597..b0d48cec89f 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Iliyoboreshwa kwa ucheleweshaji", "poolMembersLabel": "Wanachama wa dimbwi ({count})", "poolNoMembers": "Hakuna proksi katika dimbwi hili bado.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Ondoa", "poolRemoveFailed": "Imeshindwa kuondoa proksi kwenye dimbwi", "poolAddLabel": "Ongeza proksi", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index e3f042204f9..a42af45272e 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Latency-optimized", "poolMembersLabel": "தொகுப்பு உறுப்பினர்கள் ({count})", "poolNoMembers": "இந்தத் தொகுப்பில் இன்னும் proxy-கள் எதுவும் இல்லை.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "அகற்று", "poolRemoveFailed": "தொகுப்பிலிருந்து proxy-ஐ அகற்றுவது தோல்வியடைந்தது", "poolAddLabel": "ஒரு proxy-ஐச் சேர்", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index 3b1ee5caf1b..69fb9b04e59 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -10960,6 +10960,9 @@ "strategyLatency": "లేటెన్సీ-ఆప్టిమైజ్డ్", "poolMembersLabel": "పూల్ సభ్యులు ({count})", "poolNoMembers": "ఈ పూల్‌లో ఇంకా ప్రాక్సీలు లేవు.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "తొలగించు", "poolRemoveFailed": "పూల్ నుండి ప్రాక్సీని తొలగించడం విఫలమైంది", "poolAddLabel": "ప్రాక్సీని జోడించండి", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 57d4a1d2aaa..03c1583c76c 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -10960,6 +10960,9 @@ "strategyLatency": "ปรับตามเวลาแฝงให้เหมาะสม", "poolMembersLabel": "สมาชิกในพูล ({count})", "poolNoMembers": "ยังไม่มีพร็อกซีในพูลนี้", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ลบออก", "poolRemoveFailed": "ลบพร็อกซีออกจากพูลไม่สำเร็จ", "poolAddLabel": "เพิ่มพร็อกซี", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 0ccd1cac704..d496ccbc56a 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Gecikme optimizasyonlu", "poolMembersLabel": "Havuz üyeleri ({count})", "poolNoMembers": "Bu havuzda henüz proxy yok.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Kaldır", "poolRemoveFailed": "Proxy havuzdan kaldırılamadı", "poolAddLabel": "Proxy ekle", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 71d21b3a0a3..3a873b20777 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -10960,6 +10960,9 @@ "strategyLatency": "Оптимізований за затримкою", "poolMembersLabel": "Учасники пулу ({count})", "poolNoMembers": "У цьому пулі ще немає проксі.", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "Видалити", "poolRemoveFailed": "Не вдалося видалити проксі з пулу", "poolAddLabel": "Додати проксі", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index c07db81e6fe..d9cfaab5923 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -10960,6 +10960,9 @@ "strategyLatency": "لیٹنسی-آپٹمائزڈ", "poolMembersLabel": "پول کے ارکان ({count})", "poolNoMembers": "اس پول میں ابھی تک کوئی پروکسی نہیں ہے۔", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "ہٹائیں", "poolRemoveFailed": "پول سے پروکسی کو ہٹانے میں ناکامی", "poolAddLabel": "ایک پروکسی شامل کریں", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index efd1599e17d..c882de4878f 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -10960,6 +10960,9 @@ "strategyLatency": "延迟优化", "poolMembersLabel": "池成员 ({count})", "poolNoMembers": "此池中尚无代理。", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "移除", "poolRemoveFailed": "从池中移除代理失败", "poolAddLabel": "添加代理", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 99bac08c5b0..727804eb8d2 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -10960,6 +10960,9 @@ "strategyLatency": "延遲最佳化", "poolMembersLabel": "池成員({count})", "poolNoMembers": "此池中尚無代理。", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family.", "poolRemove": "移除", "poolRemoveFailed": "從池中移除代理失敗", "poolAddLabel": "新增代理", diff --git a/src/lib/proxyPoolEgressObservation.ts b/src/lib/proxyPoolEgressObservation.ts index 3295375b2c7..f7210fc931b 100644 --- a/src/lib/proxyPoolEgressObservation.ts +++ b/src/lib/proxyPoolEgressObservation.ts @@ -1,8 +1,9 @@ /** * Observed egress spread of a proxy pool, for the dashboard pool editor. Read-only and * never on the routing path: a failure returns null so it cannot break the pool screen. - * The scope is normalized exactly like the pool read (key -> account, global -> - * "__global__"), and a result is cached for 30 seconds per normalized scope. + * Opt-in through the PROXY_POOL_EGRESS_OBSERVATION feature flag (default off: null, the + * line stays hidden). The scope is normalized exactly like the pool read (key -> account, + * global -> "__global__"), and a result is cached for 30 seconds per normalized scope. */ import { EGRESS_IP_LOOKUP_WINDOW_MS, @@ -11,21 +12,15 @@ import { } from "@/lib/db/proxyLogs"; import { normalizeAssignmentScopeId, normalizeScope } from "@/lib/db/proxies/mappers"; import { flushProxyLogsSync } from "@/lib/proxyLogger"; +import { isPoolEgressObservationEnabled } from "@/shared/utils/featureFlags"; export type PoolEgressObservation = PoolEgressObservationCounts & { windowHours: number }; const CACHE_TTL_MS = 30_000; const CACHE_MAX_ENTRIES = 200; -const DISABLED_VALUES = ["false", "0", "no", "off"]; const cache = new Map(); -/** On unless PROXY_POOL_EGRESS_OBSERVATION is false, 0, no or off. */ -export function isPoolEgressObservationEnabled(env: NodeJS.ProcessEnv = process.env): boolean { - const raw = (env.PROXY_POOL_EGRESS_OBSERVATION ?? "").trim().toLowerCase(); - return !DISABLED_VALUES.includes(raw); -} - export function readPoolEgressObservation( scope: string, scopeId: string | null, diff --git a/src/shared/validation/schemas/proxy.ts b/src/shared/validation/schemas/proxy.ts index 9fc03916528..baff42ae861 100644 --- a/src/shared/validation/schemas/proxy.ts +++ b/src/shared/validation/schemas/proxy.ts @@ -234,6 +234,23 @@ export const proxyPoolMemberSchema = z } }); +// GET /api/settings/proxies/pool/egress-observation query (#13581). Same scope vocabulary and +// scopeId rule as the pool routes: an unknown scope is rejected, never read as "global". +export const proxyPoolEgressObservationQuerySchema = z + .object({ + scope: z.enum(["global", "provider", "account", "combo", "key"]), + scopeId: z.string().trim().max(256).nullable().optional(), + }) + .superRefine((value, ctx) => { + if (value.scope !== "global" && !value.scopeId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "scopeId is required for provider/account/combo/key scope", + path: ["scopeId"], + }); + } + }); + // Set a scope pool's rotation strategy. Optional sticky window (minutes) only // applies to the `sticky` strategy; ignored otherwise. export const proxyRotationStrategySchema = z diff --git a/tests/unit/proxy-pool-egress-observation-route.test.ts b/tests/unit/proxy-pool-egress-observation-route.test.ts index 151cd4968e7..f3d63b76563 100644 --- a/tests/unit/proxy-pool-egress-observation-route.test.ts +++ b/tests/unit/proxy-pool-egress-observation-route.test.ts @@ -6,14 +6,13 @@ import path from "node:path"; import { randomUUID } from "node:crypto"; // The pool egress observation has its own route so that a failure there can never break -// GET /api/settings/proxies/pool. These tests pin its shape, its null-on-failure contract -// and its parameter checks. +// GET /api/settings/proxies/pool. These tests pin its shape, its null-on-failure contract, +// its opt-in flag (PROXY_POOL_EGRESS_OBSERVATION, default off) and its Zod parameter checks. const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-pool-egress-route-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret"; delete process.env.INITIAL_PASSWORD; // auth not required in this test env -delete process.env.PROXY_POOL_EGRESS_OBSERVATION; const core = await import("../../src/lib/db/core.ts"); const proxiesDb = await import("../../src/lib/db/proxies.ts"); @@ -23,7 +22,7 @@ const poolRoute = await import("../../src/app/api/settings/proxies/pool/route.ts function resetStorage() { delete process.env.INITIAL_PASSWORD; - delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + process.env.PROXY_POOL_EGRESS_OBSERVATION = "true"; observation.resetPoolEgressObservationCache(); core.resetDbInstance(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); @@ -35,6 +34,7 @@ test.beforeEach(() => { }); test.after(() => { + delete process.env.PROXY_POOL_EGRESS_OBSERVATION; core.resetDbInstance(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); @@ -113,12 +113,40 @@ test("returns null with status 200 when the read fails", async () => { } }); -test("returns null when the kill switch is off", async () => { +test("returns null when the feature flag is at its default (off) or turned off", async () => { await pooledProxyWithTraffic(); - process.env.PROXY_POOL_EGRESS_OBSERVATION = "false"; - const response = await GET(request(OBSERVATION_PATH, { scope: "provider", scopeId: "openai" })); - assert.equal(response.status, 200); - assert.equal(await response.json(), null); + for (const value of [undefined, "false"]) { + if (value === undefined) delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + else process.env.PROXY_POOL_EGRESS_OBSERVATION = value; + observation.resetPoolEgressObservationCache(); + const response = await GET(request(OBSERVATION_PATH, { scope: "provider", scopeId: "openai" })); + assert.equal(response.status, 200); + assert.equal(await response.json(), null, String(value)); + } +}); + +test("rejects an unknown scope with 400 instead of reading the global pool", async () => { + const global = await proxiesDb.createProxy({ + name: "global member", + type: "http", + host: "10.9.2.2", + port: 21002, + }); + await proxiesDb.addProxyToScopePool("global", null, global.id); + for (const scope of ["globals", "GLOBAL", "tenant", " "]) { + const response = await GET(request(OBSERVATION_PATH, { scope, scopeId: "openai" })); + assert.equal(response.status, 400, scope); + const body = (await response.json()) as { error?: { message?: string } }; + assert.equal(typeof body?.error?.message, "string"); + assert.ok(!body.error!.message!.includes("at /"), "no stack trace in the error body"); + } +}); + +test("rejects an over-long scopeId with 400", async () => { + const response = await GET( + request(OBSERVATION_PATH, { scope: "provider", scopeId: "x".repeat(300) }) + ); + assert.equal(response.status, 400); }); test("rejects a missing scope, or a missing scopeId outside global, like the pool route", async () => { diff --git a/tests/unit/proxy-pool-egress-observation.test.ts b/tests/unit/proxy-pool-egress-observation.test.ts index 43304dfc71f..a9d4ea62695 100644 --- a/tests/unit/proxy-pool-egress-observation.test.ts +++ b/tests/unit/proxy-pool-egress-observation.test.ts @@ -7,7 +7,8 @@ import { randomUUID } from "node:crypto"; // A pool's member count says nothing about how many egress IPs really served it. These // tests pin the observation read from the proxy log: numbers only, the scope normalized -// the same way the pool itself is read, and any failure isolated as null. +// the same way the pool itself is read, and any failure isolated as null. The observation is +// opt-in (PROXY_POOL_EGRESS_OBSERVATION feature flag, default off): tests opt in explicitly. const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-pool-egress-obs-")); process.env.DATA_DIR = TEST_DATA_DIR; @@ -28,11 +29,12 @@ function resetStorage() { } test.beforeEach(() => { - delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + process.env.PROXY_POOL_EGRESS_OBSERVATION = "true"; resetStorage(); }); test.after(() => { + delete process.env.PROXY_POOL_EGRESS_OBSERVATION; core.resetDbInstance(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); @@ -163,13 +165,23 @@ test("any SQL failure yields null and is not cached", async () => { assert.equal(observation.readPoolEgressObservation("provider", "openai")?.connections, 1); }); -test("the kill switch turns the observation off", async () => { +test("the observation stays off unless the feature flag is on", async () => { const member = await poolMember("provider", "openai"); logRow(member, "203.0.113.1", "c1"); - for (const value of ["false", "0", "no", "off", " OFF "]) { + delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + assert.equal(observation.readPoolEgressObservation("provider", "openai"), null, "default"); + for (const value of ["false", "0", "no", "off"]) { process.env.PROXY_POOL_EGRESS_OBSERVATION = value; assert.equal(observation.readPoolEgressObservation("provider", "openai"), null, value); } process.env.PROXY_POOL_EGRESS_OBSERVATION = "true"; assert.equal(observation.readPoolEgressObservation("provider", "openai")?.connections, 1); }); + +test("a DB override turning the flag off wins over the environment", async () => { + const flagsDb = await import("../../src/lib/db/featureFlags.ts"); + const member = await poolMember("provider", "openai"); + logRow(member, "203.0.113.1", "c1"); + flagsDb.setFeatureFlagOverride("PROXY_POOL_EGRESS_OBSERVATION", "false"); + assert.equal(observation.readPoolEgressObservation("provider", "openai"), null); +}); From f3855d1245b7dd6c14a5ec164fe2e3f113a546ad Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:39:04 -0300 Subject: [PATCH 5/5] i18n: carry #13581's new keys into the locales added on the release tip --- src/i18n/messages/am.json | 8 ++++++-- src/i18n/messages/ha.json | 8 ++++++-- src/i18n/messages/hy.json | 8 ++++++-- src/i18n/messages/ig.json | 8 ++++++-- src/i18n/messages/ka.json | 8 ++++++-- src/i18n/messages/uz.json | 8 ++++++-- src/i18n/messages/yo.json | 8 ++++++-- 7 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/i18n/messages/am.json b/src/i18n/messages/am.json index 5adfdbefb69..b603a8b0cc0 100644 --- a/src/i18n/messages/am.json +++ b/src/i18n/messages/am.json @@ -10970,7 +10970,10 @@ "poolAddMember": "ጨምር", "poolAddFailed": "ፕሮክሲውን ወደ ጥምረቱ ማከል አልተሳካም", "poolSelectProxy": "ፕሮክሲ ይምረጡ…", - "poolSaveFailed": "የጥምረት ምደባውን ማስቀመጥ አልተሳካም" + "poolSaveFailed": "የጥምረት ምደባውን ማስቀመጥ አልተሳካም", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family." }, "playground": { "title": "የሞዴል መሞከሪያ", @@ -14160,5 +14163,6 @@ "featureFlagOmnirouteDisableThinkingLevelVariantsDescription": "በ/v1/models ካታሎግ ውስጥ የአስተሳሰብ ደረጃ ተለዋጮችን (ለምሳሌ -low, -medium, -high) ማመንጨትን አሰናክል።", "featureFlagSearchStatsHideDeletedConnectionsDescription": "__MISSING__:Search stats and recent searches only count providers that still have a live connection (keyless providers such as duckduckgo-free always count). Off keeps every retained search row with a provider id.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "__MISSING__:Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation." + "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null." } diff --git a/src/i18n/messages/ha.json b/src/i18n/messages/ha.json index b001e3b88b5..e66a08d2cbf 100644 --- a/src/i18n/messages/ha.json +++ b/src/i18n/messages/ha.json @@ -10970,7 +10970,10 @@ "poolAddMember": "Ƙara", "poolAddFailed": "An kasa ƙara proxy ɗin zuwa rukuni", "poolSelectProxy": "Zaɓi proxy…", - "poolSaveFailed": "An kasa adana rabon rukuni" + "poolSaveFailed": "An kasa adana rabon rukuni", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family." }, "playground": { "title": "Filin Gwajin Model", @@ -14160,5 +14163,6 @@ "featureFlagOmnirouteDisableThinkingLevelVariantsDescription": "Kashe samar da nau'ikan matakan tunani (misali -low, -medium, -high) a cikin kundin /v1/models.", "featureFlagSearchStatsHideDeletedConnectionsDescription": "__MISSING__:Search stats and recent searches only count providers that still have a live connection (keyless providers such as duckduckgo-free always count). Off keeps every retained search row with a provider id.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "__MISSING__:Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation." + "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null." } diff --git a/src/i18n/messages/hy.json b/src/i18n/messages/hy.json index b1f75934d6a..5e3f84831fe 100644 --- a/src/i18n/messages/hy.json +++ b/src/i18n/messages/hy.json @@ -10970,7 +10970,10 @@ "poolAddMember": "Ավելացնել", "poolAddFailed": "Չհաջողվեց պրոքսին ավելացնել պուլում", "poolSelectProxy": "Ընտրեք պրոքսի…", - "poolSaveFailed": "Չհաջողվեց պահպանել պուլին վերագրումը" + "poolSaveFailed": "Չհաջողվեց պահպանել պուլին վերագրումը", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family." }, "playground": { "title": "Մոդելների փորձարկման հարթակ", @@ -14160,5 +14163,6 @@ "featureFlagOmnirouteDisableThinkingLevelVariantsDescription": "Անջատել մտածողության մակարդակի տարբերակների (օր.՝ -low, -medium, -high) ստեղծումը /v1/models կատալոգում։", "featureFlagSearchStatsHideDeletedConnectionsDescription": "__MISSING__:Search stats and recent searches only count providers that still have a live connection (keyless providers such as duckduckgo-free always count). Off keeps every retained search row with a provider id.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "__MISSING__:Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation." + "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null." } diff --git a/src/i18n/messages/ig.json b/src/i18n/messages/ig.json index 410ce9ecac2..3ae1d07e2e9 100644 --- a/src/i18n/messages/ig.json +++ b/src/i18n/messages/ig.json @@ -10970,7 +10970,10 @@ "poolAddMember": "Tinye", "poolAddFailed": "Ịtinye proxy ahụ na pool dara", "poolSelectProxy": "Họrọ proxy…", - "poolSaveFailed": "Ịchekwa nkesa pool dara" + "poolSaveFailed": "Ịchekwa nkesa pool dara", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family." }, "playground": { "title": "Ebe Nnwale Model", @@ -14160,5 +14163,6 @@ "featureFlagOmnirouteDisableThinkingLevelVariantsDescription": "Gbanyụọ imepụta ụdị ọkwa echiche dị iche iche (dịka -low, -medium, -high) na katalọgụ /v1/models.", "featureFlagSearchStatsHideDeletedConnectionsDescription": "__MISSING__:Search stats and recent searches only count providers that still have a live connection (keyless providers such as duckduckgo-free always count). Off keeps every retained search row with a provider id.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "__MISSING__:Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation." + "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null." } diff --git a/src/i18n/messages/ka.json b/src/i18n/messages/ka.json index df26793a989..319da64cc87 100644 --- a/src/i18n/messages/ka.json +++ b/src/i18n/messages/ka.json @@ -10970,7 +10970,10 @@ "poolAddMember": "დამატება", "poolAddFailed": "პროქსის პულში დამატება ვერ მოხერხდა", "poolSelectProxy": "აირჩიეთ პროქსი…", - "poolSaveFailed": "პულზე მინიჭების შენახვა ვერ მოხერხდა" + "poolSaveFailed": "პულზე მინიჭების შენახვა ვერ მოხერხდა", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family." }, "playground": { "title": "მოდელების სატესტო გარემო", @@ -14160,5 +14163,6 @@ "featureFlagOmnirouteDisableThinkingLevelVariantsDescription": "გამორთეთ აზროვნების დონის ვარიანტების (მაგ. -low, -medium, -high) გენერირება /v1/models კატალოგში.", "featureFlagSearchStatsHideDeletedConnectionsDescription": "__MISSING__:Search stats and recent searches only count providers that still have a live connection (keyless providers such as duckduckgo-free always count). Off keeps every retained search row with a provider id.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "__MISSING__:Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation." + "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null." } diff --git a/src/i18n/messages/uz.json b/src/i18n/messages/uz.json index 3c79019870d..46f3f68f83c 100644 --- a/src/i18n/messages/uz.json +++ b/src/i18n/messages/uz.json @@ -10970,7 +10970,10 @@ "poolAddMember": "Qo‘shish", "poolAddFailed": "Proksini pulga qo‘shib bo‘lmadi", "poolSelectProxy": "Proksini tanlang…", - "poolSaveFailed": "Pulga biriktirishni saqlab bo‘lmadi" + "poolSaveFailed": "Pulga biriktirishni saqlab bo‘lmadi", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family." }, "playground": { "title": "Model sinov maydoni", @@ -14160,5 +14163,6 @@ "featureFlagOmnirouteDisableThinkingLevelVariantsDescription": "/v1/models katalogida fikrlash darajasi variantlarini (masalan, -low, -medium, -high) yaratishni o‘chirib qo‘ying.", "featureFlagSearchStatsHideDeletedConnectionsDescription": "__MISSING__:Search stats and recent searches only count providers that still have a live connection (keyless providers such as duckduckgo-free always count). Off keeps every retained search row with a provider id.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "__MISSING__:Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation." + "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null." } diff --git a/src/i18n/messages/yo.json b/src/i18n/messages/yo.json index b24a3e3406e..a39d82398d4 100644 --- a/src/i18n/messages/yo.json +++ b/src/i18n/messages/yo.json @@ -10970,7 +10970,10 @@ "poolAddMember": "Ṣàfikún", "poolAddFailed": "Kùnà láti ṣàfikún aṣojú sí àkójọpọ̀ náà", "poolSelectProxy": "Yan aṣojú kan…", - "poolSaveFailed": "Kùnà láti fi ìpín àkójọpọ̀ pamọ́" + "poolSaveFailed": "Kùnà láti fi ìpín àkójọpọ̀ pamọ́", + "poolEgressObservation": "__MISSING__:{exits, plural, one {# exit} other {# exits}} used by {connections, plural, one {# connection} other {# connections}} · up to {max} on one exit · last {hours} h", + "poolEgressObservationEmpty": "__MISSING__:No traffic observed in the last {hours} h", + "poolEgressObservationHint": "__MISSING__:Counted from the proxy log over the window: distinct egress IPs observed for the members of this pool, and the OmniRoute connections that used them. \"On one exit\" means over the window, not at the same time. The per-IP 429 lockout only pauses connections of the same provider family." }, "playground": { "title": "Pápá Ìdánwò Àwòṣe", @@ -14160,5 +14163,6 @@ "featureFlagOmnirouteDisableThinkingLevelVariantsDescription": "Mú ṣíṣẹ̀dá àwọn ẹ̀yà ìpele ìrònú (fún àpẹẹrẹ -low, -medium, -high) nínú àkójọ /v1/models ṣiṣẹ́ mọ́.", "featureFlagSearchStatsHideDeletedConnectionsDescription": "__MISSING__:Search stats and recent searches only count providers that still have a live connection (keyless providers such as duckduckgo-free always count). Off keeps every retained search row with a provider id.", "featureFlagFreeBadgeRequiresProviderFreeTierDescription": "__MISSING__:Dashboard provider pages: show the Free badge only on signals the provider honors — drops the display-name heuristic, non-boolean free fields and :free suffixes on registered providers without a documented free tier. Off keeps the historical badge rule.", - "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation." + "featureFlagProxySkipRecentlyFailedDescription": "__MISSING__:Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. Off by default: selection order is exactly the plain rotation.", + "featureFlagProxyPoolEgressObservationDescription": "__MISSING__:Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h, how many connections used them and the most seen behind one IP. Read-only, computed from the proxy log, never used for routing. Off by default: the pool editor is unchanged and the observation route answers null." }