feat(proxies): show how many egress IPs actually served a proxy pool - #13581
Merged
diegosouzapw merged 6 commits intoSep 15, 2026
Merged
diegosouzapw merged 6 commits into
diegosouzapw merged 6 commits into
Conversation
maxmad64bis
force-pushed
the
feat/pool-egress-observation
branch
from
September 13, 2026 19:12
4f5b42b to
d29efb2
Compare
This was referenced Sep 13, 2026
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.
maxmad64bis
force-pushed
the
feat/pool-egress-observation
branch
from
September 15, 2026 21:39
d29efb2 to
8b962d1
Compare
diegosouzapw#13602) Behind `PROXY_SKIP_RECENTLY_FAILED` (from diegosouzapw#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!
…efault off) Registers the switch for the read-only pool egress observation of diegosouzapw#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)
…te the route query Keeps the read-only panel of diegosouzapw#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)
# Conflicts: # src/i18n/messages/ar.json # src/i18n/messages/az.json # src/i18n/messages/bg.json # src/i18n/messages/bn.json # src/i18n/messages/cs.json # src/i18n/messages/da.json # src/i18n/messages/de.json # src/i18n/messages/el.json # src/i18n/messages/en.json # src/i18n/messages/es.json # src/i18n/messages/et.json # src/i18n/messages/fa.json # src/i18n/messages/fi.json # src/i18n/messages/fr.json # src/i18n/messages/ga.json # src/i18n/messages/gu.json # src/i18n/messages/he.json # src/i18n/messages/hi.json # src/i18n/messages/hr.json # src/i18n/messages/hu.json # src/i18n/messages/id.json # src/i18n/messages/it.json # src/i18n/messages/ja.json # src/i18n/messages/km.json # src/i18n/messages/kn.json # src/i18n/messages/ko.json # src/i18n/messages/lt.json # src/i18n/messages/lv.json # src/i18n/messages/ml.json # src/i18n/messages/mr.json # src/i18n/messages/ms.json # src/i18n/messages/mt.json # src/i18n/messages/my.json # src/i18n/messages/ne.json # src/i18n/messages/nl.json # src/i18n/messages/no.json # src/i18n/messages/or.json # src/i18n/messages/pa.json # src/i18n/messages/phi.json # src/i18n/messages/pl.json # src/i18n/messages/pt-BR.json # src/i18n/messages/pt.json # src/i18n/messages/ro.json # src/i18n/messages/ru.json # src/i18n/messages/si.json # src/i18n/messages/sk.json # src/i18n/messages/sl.json # src/i18n/messages/sr.json # src/i18n/messages/sv.json # src/i18n/messages/sw.json # src/i18n/messages/ta.json # src/i18n/messages/te.json # src/i18n/messages/th.json # src/i18n/messages/tr.json # src/i18n/messages/uk-UA.json # src/i18n/messages/ur.json # src/i18n/messages/vi.json # src/i18n/messages/zh-CN.json # src/i18n/messages/zh-TW.json
diegosouzapw
merged commit Sep 15, 2026
ca312d5
into
diegosouzapw:release/v3.8.51
0 of 3 checks passed
5 tasks done
Merged
abhisheksharma2411
added a commit
to abhisheksharma2411/OmniRoute
that referenced
this pull request
Sep 22, 2026
`check:file-size` failed: ProxyRegistryManager.tsx is frozen at 1477 and this branch had grown it to 1493. Most of that was avoidable. The limit's state, its `/api/settings` read and the default now live in `useProxyBulkImportLimit` in proxyRegistryData.ts, beside loadProxyUsage/loadProxyHealth, with the resolver still in shared/constants/proxyBulkImport.ts. The component keeps one import member and one call site, which is the mount point and is irreducible — the same shape the diegosouzapw#13581 rebaseline records for this file. That leaves 1477 -> 1479, rebaselined in config/quality/file-size-baseline.json with the usual justification entry. The value goes in the `frozen` map; my first attempt added it at the top level, where the checker never reads it and the gate kept failing against the old number. The hook also adds a cancellation flag the inline version did not have, so a settings response arriving after unmount no longer sets state. Behaviour is unchanged: 9/9 tests pass, tsc is clean on both files, and the 3 ESLint errors in the component reproduce identically on the base.
5 tasks done
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…iegosouzapw#13581) Behind the new `PROXY_POOL_EGRESS_OBSERVATION` flag (default off): a line under each proxy pool showing how many distinct egress IPs actually served it over 24h, backed by `GET /api/settings/proxies/pool/egress-observation`. Maintainer rework before merge (kept the idea, no default behavior change): - The route validates its query with Zod (unknown `scope` → 400 instead of silently `global`), error bodies go through `errorResponse()`, the OpenAPI entry documents security, parameters and responses, and the three UI strings exist in every locale. 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 d61b804: 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!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A pool of 12 members reads as 12 exits, but several members can share one real exit, so a single refused exit can stall the whole pool at once. The proxy log already records which entry served each connection and which exit it left by, so the pool editor now shows one line under the member list: distinct exits used, connections through them, and the busiest exit over the last 24 h. When the read fails or is turned off, the line stays hidden and the pool screen works exactly as before.
Related Issues
ProxyRegistryManager.tsx)Validation
# pass 19 # fail 0), vitest 6/6 (2 files), file-size / api-docs-refs / route-validation / typechecks green on the commitnpm run lint— ESLint on the touched files is clean (exit 0); the fullnpm run lintwas not replayed, so this stays unchecked238cb1b07, which now includes feat(proxies): stop re-serving a proxy that just failed #13578 and feat(proxy-logs): keep the HTTP status the provider actually returned #13580), 0 behind, single commit; the only conflict wasconfig/quality/file-size-baseline.json, resolved by keeping both rebaseline keysTests Added Or Updated
tests/unit/proxy-pool-egress-observation.test.ts(new, 7 tests: 12 connections through 3 members across 5 exits reading12 / 5 / 4, out-of-pool rows ignored, NULL egress/connection ignored, rows older than 24 h ignored, empty pool returning zeros,keyalias sharing one cache entry withaccount, injected SQL failures returning null)tests/unit/proxy-pool-egress-observation-route.test.ts(new, 5 tests: shape of the JSON keys, zeros contract, 400s on missing params, failure answering null with 200; run together with the pool route suite for 19/19)tests/unit/ui/PoolEgressObservation.test.tsx(new, 5 tests: rendered line with plurals, empty-traffic sentence, null rendering nothing, network error rendering nothing; run with the registry render test for 6/6)Coverage Notes
The query, the service (cache, kill-switch, failure-to-null), the route (auth tier, 400s, null), and the component (line, empty sentence, hidden states) are covered directly. The OpenAPI gates only check the path is present, so the new path entry is deliberately minimal and a documenting follow-up (response schema, 400, 401) is still owed.
i18n-vi-completenessreports# pass 1 # fail 4, identical to the base before this change.Reviewer Notes
src/lib/db/proxyLogs.ts:85, joining log rows to registry members through current assignments and grouping by observed exit IP; only counts leave the function, never addresses.src/app/api/settings/proxies/pool/egress-observation/route.ts:12) sits behind the same management auth as the pool route, validatesscope/scopeIditself (route.ts:18), and the pool route is untouched, so a broken observation can never break the pool editor.src/lib/proxyPoolEgressObservation.ts:29) normalizes the scope exactly like the pool read, caches a result for 30 seconds (200 entries max), caches only successes, and turns every failure into null, which hides the line (PoolEgressObservation.tsx:40).PROXY_POOL_EGRESS_OBSERVATION=false(or0,no,off) turns it off (proxyPoolEgressObservation.ts:24).SEARCH l USING INDEX idx_pl_timestamp (timestamp>?),SEARCH r USING INDEX idx_proxy_registry_host (host=?),SEARCH proxy_assignments USING COVERING INDEX sqlite_autoindex_proxy_assignments_1 (scope=? AND scope_id=?),USE TEMP B-TREE FOR GROUP BY. FullEXPLAIN QUERY PLANoutput:[{"detail": "SEARCH l USING INDEX idx_pl_timestamp (timestamp>?)"}, {"detail": "SEARCH r USING INDEX idx_proxy_registry_host (host=?)"}, {"detail": "LIST SUBQUERY 1"}, {"detail": "SEARCH proxy_assignments USING COVERING INDEX sqlite_autoindex_proxy_assignments_1 (scope=? AND scope_id=?)"}, {"detail": "CREATE BLOOM FILTER"}, {"detail": "USE TEMP B-TREE FOR GROUP BY"}, {"detail": "USE TEMP B-TREE FOR count(DISTINCT)"}]enandvionly (src/i18n/messages/en.json:10967); the 49 other locales don't have these keys, like 9 keys already missing at the base, and the UI sync script was deliberately not run to avoid rewriting unrelated keys.ProxyRegistryManager.tsxgrows by exactly 2 lines (import at:11, mount under the members label at:1242) with its file-size cap raised 1475 → 1477 and a rebaseline note; everything else lives in new files under the cap.OMNIROUTE_*vars), docs-counts sync (provider counts),i18n-vi-completeness(# pass 1 # fail 4, same as base)._rebaseline_…keys and both caps are kept (ProxyRegistryManager.tsx1477 here,proxyFetch.ts1275 from feat(proxy-logs): keep the HTTP status the provider actually returned #13580).ProxyRegistryManager.tsxfar from the import and the mount added here and adds no net line, so the 1477 cap stays right in any merge order. feat(proxies): stop re-serving a proxy that just failed #13578 adds its env var to.env.exampleandENVIRONMENT.mdat a different spot.