From 76b8bb7ee2ec6c477cadc9e4f229656caa69f830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sat, 10 Oct 2026 20:56:36 +0200 Subject: [PATCH] v0.31.0 --- AGENTS.md | 2 +- CHANGELOG.md | 15 + Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 21 +- ROADMAP.md | 7 + SHA256SUMS | 132 +- dev.sh | 4 + docs/API.md | 23 +- docs/CACHE.md | 55 +- docs/CHANGELOG_TECHNICAL_ARCHIVE.md | 6 + docs/IMPLEMENTATION_0.31.0.md | 152 + docs/README.md | 4 +- docs/SECURITY_AUDIT_0.31.0.md | 148 + docs/SETTINGS.md | 50 +- docs/TEST_REPORT.md | 42 +- langs/en_US.json | 4640 +++++++++-------- langs/pl_pl.json | 4640 +++++++++-------- src/admin/documentation/documents.rs | 2 + src/admin/logging_api.rs | 26 +- src/admin/openapi.json | 170 +- src/admin/operations.rs | 34 +- src/admin/panel_assets.rs | 57 + src/admin/panel_content.rs | 56 + src/admin/preferences.rs | 99 +- src/admin/preferences_columns.rs | 92 + src/admin/public.rs | 45 +- src/admin/public/tests.rs | 57 + src/admin/refresh_preferences.rs | 41 + src/admin/router.rs | 220 +- src/admin/status.rs | 72 +- src/admin/status_live.rs | 43 + src/admin/status_system.rs | 70 + src/admin/tests.rs | 2 + src/admin/tests/part_04.rs | 25 + src/admin/tests/part_09.rs | 69 + src/admin/tests/part_10.rs | 71 + src/admin/web/acme.js | 20 +- src/admin/web/app/core.js | 136 +- src/admin/web/app/preferences.js | 65 +- src/admin/web/app/refresh.js | 161 + src/admin/web/app/runtime.js | 6 +- src/admin/web/app/settings.js | 3 +- src/admin/web/cache-panel.js | 28 +- src/admin/web/docker.js | 41 +- src/admin/web/drafts.js | 6 +- src/admin/web/index.html | 85 +- src/admin/web/settings.js | 4 +- src/admin/web/style.css | 162 +- src/admin/web/traffic-panel.js | 24 +- src/admin/web/transform-panel.js | 28 +- src/cache/model.rs | 1 + src/cache/model/entry.rs | 1 + src/cache/model/http_invalidation.rs | 31 + src/cache/model/settings.rs | 4 + src/cache/tests.rs | 3 + src/cache/tests/http_invalidation_tests.rs | 41 + src/compression.rs | 4 +- src/proxy/cache_http_invalidation.rs | 162 + src/proxy/forward_entry.rs | 31 +- src/proxy/mod.rs | 4 + src/proxy/tests/http_invalidation_tests.rs | 69 + src/runtime/constructors.rs | 1 + src/runtime/tests.rs | 1 + src/runtime/types.rs | 3 + src/settings.rs | 16 + tools/checks/admin_surface_audit_0310.py | 114 + tools/checks/check_source.py | 26 +- .../checks/dashboard_startup_0310_ui_test.mjs | 92 + tools/checks/docs_check.py | 5 +- .../checks/panel_cache_http_0310_ui_test.mjs | 111 + tools/checks/preflight.py | 4 +- tools/checks/refresh_0310_ui_test.mjs | 229 + tools/checks/release_smoke.py | 21 +- tools/checks/system_info_0301_ui_test.mjs | 6 +- .../system_info_no_subprocess_contracts.py | 3 +- tools/checks/version_contracts.py | 4 +- tools/dev/memory_load.py | 2 +- 78 files changed, 7750 insertions(+), 5204 deletions(-) create mode 100644 docs/IMPLEMENTATION_0.31.0.md create mode 100644 docs/SECURITY_AUDIT_0.31.0.md create mode 100644 src/admin/panel_assets.rs create mode 100644 src/admin/panel_content.rs create mode 100644 src/admin/preferences_columns.rs create mode 100644 src/admin/public/tests.rs create mode 100644 src/admin/refresh_preferences.rs create mode 100644 src/admin/status_live.rs create mode 100644 src/admin/status_system.rs create mode 100644 src/admin/tests/part_09.rs create mode 100644 src/admin/tests/part_10.rs create mode 100644 src/admin/web/app/refresh.js create mode 100644 src/cache/model/http_invalidation.rs create mode 100644 src/cache/tests/http_invalidation_tests.rs create mode 100644 src/proxy/cache_http_invalidation.rs create mode 100644 src/proxy/tests/http_invalidation_tests.rs create mode 100755 tools/checks/admin_surface_audit_0310.py create mode 100644 tools/checks/dashboard_startup_0310_ui_test.mjs create mode 100644 tools/checks/panel_cache_http_0310_ui_test.mjs create mode 100644 tools/checks/refresh_0310_ui_test.mjs diff --git a/AGENTS.md b/AGENTS.md index ac5cae0..0cc9e6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes. The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for -compatibility. Current source version: 0.30.1. +compatibility. Current source version: 0.31.0. ## Delivery diff --git a/CHANGELOG.md b/CHANGELOG.md index 16fc38a..e18c800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ > test-only changes and packaging implementation notes were moved to > [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). +## 0.31.0 + +- Repair the reported HTTP cache-maintenance Rust E0716 temporary-borrow compilation error. Harden BAN/PURGE with scheme/host/vhost isolation, unambiguous headers and bounded background cache-maintenance workers (shared with authenticated admin purge/invalidation); audit BAN-rule removals before mutation. Add a localized curl-examples tooltip to Settings → Cache. See the [0.31.0 API and panel security review](docs/SECURITY_AUDIT_0.31.0.md) for assessed controls and remaining runtime security gates. + +- Add browser-negotiated Zstd/Brotli/gzip compression for panel HTML and static assets, configurable under Settings → Administration without an extra environment variable; cached representations and `Vary: Accept-Encoding` work with or without a reverse proxy (subject to forwarded headers). +- Add strictly opt-in, IP/CIDR-gated HTTP `PURGE` (exact URL) and `BAN` (lazy URL prefix) on public vhost listeners, using the existing invalidation engine, ACL/rate limits, audit and CIDR validation. Empty allowlist keeps the feature disabled; no DB or cache-format migration. +- Align the System information manual Refresh control directly next to the API badge. +- Make the authenticated panel visible during initialization: replace the previous fully hidden body with an accessible loading indicator, a delayed reload action and an explicit startup-failure state. Paint the Overview shell before slow preferences/config/status requests complete. +- Place **Refresh now** beside the Overview title at the far right, including on narrow mobile screens. Serve panel HTML/assets using the new configurable negotiated codec policy, with an uncompressed fallback. No new endpoint, API change or proxy traffic change. +- Add per-administrator Refresh preferences under Settings → Appearance, including Live enable/disable and supported intervals for status/SSE (1/2/5/10 s), Traffic/Cache (0/3/5/10/30 s), and system information (0/15/30/60/300 s). Reset and manual refresh remain available, even when automatic updates are off. +- Replace duplicated dashboard timers with one coordinator for SSE, fallback polling, Traffic, Cache, Transform, Docker, ACME, tunnel diagnostics and editor-draft presentation. Poll only active views, prevent overlapping requests, abort stale views, pause when hidden and stop on logout. +- Keep the full authenticated status API for compatibility; add a compact live-only status view and a separate authenticated `/api/system-info`. SSE snapshots exclude full configuration and expensive system diagnostics. SSE interval is per client and does not affect other sessions. +- Update English and Polish UI labels, OpenAPI, documentation and release checks. No database schema, cache format or proxy data-plane changes. +- The Rust compiler and real protocol/load acceptance remain mandatory where the toolchain is unavailable. + ## 0.30.1 - Fix protocol chart legend dots for HTTP and WebSocket (H1/H2/H3) so their colors always follow the configured line palette. diff --git a/Cargo.lock b/Cargo.lock index 80d86de..ffbc15e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1716,7 +1716,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.30.1" +version = "0.31.0" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index a29b3a1..15627a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.30.1" +version = "0.31.0" edition = "2021" rust-version = "1.88" description = "RustProxy - independent reverse proxy with local administration" diff --git a/README.md b/README.md index 45fc079..60bc716 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ -# RustProxy 0.30.1 +# RustProxy 0.31.0 + +**0.31.0 Axum E0277 follow-up:** the public BAN/PURGE async handler now takes ownership of `Request` and drops the body before its audit/cache awaits. This avoids a non-`Send` borrow through Axum 0.8 `Router::fallback(forward)`; the rest of the request pipeline is unchanged. A source regression guard checks the ownership boundary; real `cargo check --locked --all-targets` is still required. + +**0.31.0 security/compilation follow-up:** resolve reported E0716 lifetime error in cache maintenance; bound concurrent BAN/PURGE/admin invalidations, preserve HTTP/HTTPS and vhost cache isolation, reject ambiguous maintenance headers and add an EN/PL curl-examples tooltip under Settings → Cache. [Security review and runtime test plan](docs/SECURITY_AUDIT_0.31.0.md). Rust compilation is not verified in this source snapshot. + +**0.31.0 panel/cache additions:** browser-negotiated Zstd/Brotli/gzip for HTML, JS and CSS (Settings → Administration); optional HTTP BAN/PURGE restricted to explicit IP/CIDR allowlist (Settings → Cache, disabled by default); System information Refresh button beside the API badge. See [Settings](docs/SETTINGS.md) and [Cache](docs/CACHE.md). Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration, streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools. -**0.30.1 release source.** +**0.31.0 release source.** ## Layer-4 streams @@ -11,7 +17,16 @@ Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, a Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners. -## What changed in 0.30.1 +## What changed in 0.31.0 + +- **Panel startup/mobile follow-up:** replace the former white loading state with a RustProxy spinner, an automatic delayed reload suggestion and an error screen with reload link. Paint the shell before waiting for preferences/configuration/initial status; the header's **Refresh now** action sits beside the Overview title without adding a row. Negotiate Zstd/Brotli/gzip for fixed panel HTML and assets, with one-time encoding per resource; preserve public login and identity clients. +- Refresh Overview through a shared, authenticated SSE channel with per-administrator live pacing (1, 2, 5 or 10 seconds); fallback status/log polling is active only after SSE failure. Full configuration and system diagnostics are not serialized on every live event. +- **Settings → Appearance → Data refresh** now stores per-account Live enable/disable and independent Traffic, Cache and System information intervals. Manual mode (`0`) and the global Live switch disable automatic requests while preserving manual refresh, charts, filters and pagination. +- Poll only the current Traffic/Cache view, and fetch system diagnostics independently from `/api/system-info` when Overview or the relevant Settings section needs them. The previous full `/api/status` response remains supported, while `?view=live` provides compact fallback. +- Centralize dashboard polling with cancellation/in-flight guards, hidden-tab pause and logout cleanup; retain SQLite schema 9, previous preferences, proxy traffic and the existing authentication model. +- Rust compilation and live wire/performance acceptance remain required on an equipped target; see [verification report](docs/TEST_REPORT.md). + +## Previously in 0.30.1 - Match H1/H2/H3 legend markers to the corresponding protocol-chart line colors across chart palettes. - Keep **About** focused on the product, author, license and source/package links. Move Python/ACME diagnostics out of the About dialog. diff --git a/ROADMAP.md b/ROADMAP.md index 731a0af..e3f29b3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,12 @@ # RustProxy roadmap +## 0.31.0 - Panel refresh orchestration + +- [x] Centralize authenticated polling with source IDs, view lifecycle, hidden-tab pause, abort, in-flight protection and cleanup. +- [x] Separate compact live telemetry from infrequent cached system diagnostics; allow per-client SSE pacing and reconnect-only fallback. +- [x] Add per-account refresh preferences, reset/manual actions, language packs, endpoint contracts and regression tests. +- [ ] Compile/test on Rust toolchain and perform real multi-session SSE, latency and long-running browser acceptance before production release. + ## 0.30.1 - Protocol legend and system information - [x] Color the H1/H2/H3 legend dots consistently with their themed chart strokes. diff --git a/SHA256SUMS b/SHA256SUMS index 9e6c6aa..9f946fc 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,18 +1,18 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -2af2dd8a13e8e1f16639fbca5b72ad0d82f6abf3a2e3ac146341a6a007547904 AGENTS.md -64179555a0e536fa58e4e265ad98cdd09e3809b81cdf19ad0ba8609c35d28ddc CHANGELOG.md -e0409a737959eb4b8122efa279401eda5b3b78155a6e9d2a4fdc891bfb4a1754 Cargo.lock -125a8a3c3f785675190518b398f64aee12fb0c82120d29be61012865391b3636 Cargo.toml +5ef98bbda0c22bd928ad4e11a1b6a8d3de117d3b492228c4a707efe2ecf861f0 AGENTS.md +e1652d430065765a610eb413c89b8711586a495db7e6de1545262a2670d96453 CHANGELOG.md +0afa3c54b9fa6790295b93ddd4afb957d9043da2621dc224188f916fdddd8cbc Cargo.lock +b1b58b9500b20de1e3c97e2cbb4cafac8c0cbca159059df7b5f28591f18527fd Cargo.toml 75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile -b8aca9e4be342f43b64a2eb04522c6582243edbfb764457afdf58ee894131480 README.md -40f21bcecdf85e951d07059fe5bd5a49b844a9f22e943410534a0d8b308a26ac ROADMAP.md +af4f275c91d8783c496438078fd8e22515c0248c26fba8af803aa085accf6a56 README.md +9e14f638490fd2e4a11abf24a27c38f939850c0cb63a2b748ce214ccc0a927e9 ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md 4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service -550197e512e5a3b3c1dec66f69ab31368e82a317dfac653ecd60b11dac45195e dev.sh +b665af2aade88286628fce98fb61b48c37fef9823fbd2eb05e8898fada6c58d2 dev.sh 5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md 7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go 5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example @@ -27,10 +27,10 @@ e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/n 5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go 875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md -248bd040dcbda23e223a439e17232cd681deed3a77f273615a6ffa22ddfe51e4 docs/API.md +8ded74f422426606238fa9876b49277a7bc56ddd96062a836cdd6ddbf78bd335 docs/API.md f054206a09bed81018708608cdeecbfd09018ee7cec9d3abc038f8a37d47b815 docs/API_ACCESS.md 4c5288c9ed427cfb919bd023ed9a321dcf6d64bc4cad3e1768393356c5d835ba docs/ARCHITECTURE.md -598298303036912c19e0a4be1431acc7b7d3df7333f3e756e8268ac0e8c16921 docs/CACHE.md +e9e2327684a4ae2d1ca4a0b3fb67d0ff83b5a150d38d4de4cfad2c5d3102eab6 docs/CACHE.md 9113f3abbfe81bafc4855d8680df8aa2a381ecc358cc3e1a0f78b6b756421117 docs/CACHE_DIAGNOSTICS.md 29cc1237ab205fdbe1cc0afc0e905c211048d9b4de2c349d1e4a8427bd53c001 docs/CHANGELOG_TECHNICAL_ARCHIVE.md c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md @@ -39,6 +39,7 @@ befa50579e3475b22589f26380d85b3345b80e4fa947d49649a3b91a8dd8b8c2 docs/CUSTOM_LO 5324b215075214a5bb9b388bc82d6491eacfc507df95856d1aecedd184e25b80 docs/ERROR_LOGGING.md 35b7784afa9a2f7d805f7cd1bd188f93050fc0ebaea61b30152075fc7a2549bb docs/FILE_LOGS.md 8dfc89a8b3512103b87864bd7c24a3dc3de4465edf8f9874e6bed1f4634dd930 docs/HTTP3.md +5301e321611d494fd97b04ab648df1f66a6a0d1f1f4c3475eab485e138d5605b docs/IMPLEMENTATION_0.31.0.md a00ec3c1d3b28ba10677435128e031e5f18fe8c5cef2ac90eb0f6779cefeb434 docs/INSTALL.md 3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md 4bb56add544f15d54695cf797cd16a51c09024360cdeef803a88988f4a54b3a5 docs/LIVE_LOGS.md @@ -52,7 +53,7 @@ e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND e68d5a5cf0991de9308f78afb0264e02979f7463a5f395eb7a6e9ec2bd3a3d54 docs/PHP_FPM.md bc068a176056f5ea4d3b637071e644ed58a2140494c233ef22aa66e9700e3299 docs/PHP_FPM_ISOLATION.md 572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md -f0786a65c93ba00830604fcc52004566199e757941ccc9481eb7b7b4232472e1 docs/README.md +c496cb868d9838376daa0d900c3a6d60e1772129669b183848d46715986f3b21 docs/README.md 6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md 0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md d85c5567fc6a7714b96ad7cfe4ee9ff2085f84c4f31534a6484340d03a73e0f7 docs/RENEWAL.md @@ -60,9 +61,10 @@ a2bb837cdd3559d58158880c1f832badf64035376a3623050bb4e40c2bca104c docs/ROADMAP_H 759bf2987f2b602488829d1856fa33d57c2a3b23bc681ef5a931b9633af3a533 docs/SECRET_STORE_BACKUP.md 5dbdf45fddba198a28f511dbbd2f12bf7d2b88c41c3cbedbc20dfea3858779d3 docs/SECURITY.md 620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md -4c584a657606f0423d40670b72c8c95c814368be4f6635b4d6adc8c729780d0f docs/SETTINGS.md +007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md +2cd9dadc49a5474e4ab06d5c01fa38c27cab436578cb9c9f327a37b3406d2e13 docs/SETTINGS.md 3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md -ff24c09a3bde9607001fdc49acbb71b413a1c6479c8a59252da821492d7593f5 docs/TEST_REPORT.md +87b1e631bfec6a6b33d7b8718e06b50019586974c0714b1f1e902e4682c9fb8b docs/TEST_REPORT.md cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md 751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md 660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md @@ -134,8 +136,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r 96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh 3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh 71cde48daa538431dfac51e438b1ed23b061abfdb4e144ba704237d75ac6270c langs/README.md -86b95d536c1e81883f9e4002c7e72f19976436d5ece285cb103432b195ac648f langs/en_US.json -6a4e4fd967d29c38ee7e9a373004db85400bb8a1ecb51e1842f2da93b6247353 langs/pl_pl.json +70c85a656bac197b7dcf7048188ec2d3320c3da39908f79477a6484b968f6901 langs/en_US.json +0352d8d3c65ba5f60870a2fd3e945a79333e69a94d3349799ef285b22c3a9476 langs/pl_pl.json a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql @@ -178,43 +180,53 @@ e38331a1b186b49fe9d04051de52b603e1fa028def37e4e2f52e386b4d7b29ac src/acme/tests bc9eb32f0d2785bac122d5eff44742cd24306c503daa065564383d46416b2211 src/admin/access.rs 019f4aa33e3596bc9c11ef8cf5703bcfffe46c15288c0eba83c8dd9e38055fb2 src/admin/certificates.rs 1de4c7c116eeab2452ccccbaba3f2b7b63c6eb1c1e146ab870d19c5a5ca16f80 src/admin/documentation.rs -f84cdf7dc40553b6c5086446bafd5c700496f048230ebd95a01839858adff071 src/admin/documentation/documents.rs +ff158db7bed945d69a6e78e00ec84692114af035854628e39cf31c01acf52fc0 src/admin/documentation/documents.rs 67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs 002922c91aa07cbea17cb0ee11127bb6e7e015de108ee032761b34507dcfa06d src/admin/file_log_search.rs 2ebaf24e9d0d87383d2eea073ee2d3ac37962eeb503a0f1ee2c2de4f3f6d1b7a src/admin/file_logs.rs d7aa68a78a4cb5a61342d92c1f3209af230d0f9ca3fea1115f67078372e6f37c src/admin/file_logs_io.rs -b3e2c11dbc11bb8930117afdac64779111acc2ca7b2857f6e359a5108fc9ced8 src/admin/logging_api.rs +899e87a73bfe46c4078a46f9e1eb1c17b384472e8a30d2bab5486872b43fc809 src/admin/logging_api.rs 58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs -53347ce9fcb8ce297ff3565ac3d2af5060a56eb6fff51eeac4791c0bd9f5b296 src/admin/openapi.json -6c2b9a0ecb2f4172f110a363c22a6be32895dbcff9605fe7aa94efe91e0a536a src/admin/operations.rs -5287fff6b3ad436ac695e9b66feb1b0b5705b744a9f00cec7ea6a3e951e5eebf src/admin/preferences.rs -31bd90ee5f5ec2984920225ff6b57e07db3e0758092fb55d7c230e3590c1b015 src/admin/public.rs -9edf51cfbeb51ec5062fa24803247f6117a45dfe9ac731215470c3d034296020 src/admin/router.rs +916d2121a6719f82b86cd59ad3c62d3d41f70812decae21998a93ff89710e43a src/admin/openapi.json +0d1a5324e8ad32f5ab0861b722663f50f066e256bc5fd5dcced6a63f2bd399d1 src/admin/operations.rs +2b0ec6df0ed9e1bdc5c9e4ff1758b59dd14abc150f98365ac3edcf4672908e48 src/admin/panel_assets.rs +75bbedd54fdabb2b489642a642ee7c6533f69cdacb85e251763a56ba11174d5c src/admin/panel_content.rs +7c113cd9d8642a808c73ebee24e695f7a318995e39dc2b81e747d2b2246e277f src/admin/preferences.rs +d9e90166d2199152585a5208feedfc1dd1395dc32ddbd2a094ae4a1aee45e115 src/admin/preferences_columns.rs +9baf3c0ec4c543bda51605930ac57d6425c5e03e06d29ff755a7ce3ff611aeaf src/admin/public.rs +5135f743f628103deda9f50629065ca343f7a5bf872dcd3cc0033ac2971e361b src/admin/public/tests.rs +6125419b099cf4d330767bc07424d52a2b400f273d31084c058c43a72a6427b7 src/admin/refresh_preferences.rs +d45fbec28e2e3aa6e045ce7783545da319ad04d086ea22350609f1ab777f3096 src/admin/router.rs 8708427263cd4caf8454b9115e89acb09172483a090c6b0bd43811dd9e52c7f6 src/admin/session.rs -c7dd05f83efccb8df5a8e4ea22bda072643e9d9cd52610d4c8a68f072270903b src/admin/status.rs +6fa2e586feb335ea9ce2c4dc523e422e4b2aa52c823ba8f8851f9e0f37cd2fef src/admin/status.rs +e79d3dc0c4fde1ca4a69c204fb41cc5aa1db02aefdabff3435f27b5db0c6eb8d src/admin/status_live.rs +c97881b17b7a42511868b0e48e2f25aab6fb5b613d538f5e1f1967508454e486 src/admin/status_system.rs cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail.rs -5a8e2c4d0065c98f4686105c6f00362dd515f70f1ed662187f943ba4b76c54ad src/admin/tests.rs +3277a08915eb2e272ad4c0f3aa1ffa0c7fed4e6a1d1fb66d4bfd8a4a4ea515aa src/admin/tests.rs 5df98246c46135a176b4b8b705c92fd328719e82eb7342f088256abb976d5ae7 src/admin/tests/part_01.rs 6f8c1e30babb2cc4cb7accf4bfa28338651a469b16341f6e7eaec881dd9abf2b src/admin/tests/part_02.rs 34cf08cafb63cf7a8a6336e5c81fc99abfce8669662e387a3702c3a532be8056 src/admin/tests/part_03.rs -46c6d04fb6a125ebb00f7295d3ad3c16071477fc40f2b377455ed7a6eb1370d2 src/admin/tests/part_04.rs +a1e14bfad1ba42045421fb761e0cc490cc30530409b22416db7e727548c06b93 src/admin/tests/part_04.rs 5f0062abc42a3b81c79088dc0824ab4c81a22c6ea765e77d2527d07d13bffd58 src/admin/tests/part_05.rs 311c4ad16b3ad5b295a339241afd4ca8ea0d3c7f54f609c27518b4de1b4a4b71 src/admin/tests/part_06.rs 5fa006413fd93529ac9aaa4ffb28bf6b0b88d0a55a0bf0c49576db4a06fe9fc3 src/admin/tests/part_07.rs 54ed2130e85239ffa4576417dd68fbb9349e396210b75f5da862897137d7e876 src/admin/tests/part_08.rs -2ae5bc857dd62882f16d1cfcee9691c9ba8afc149f4c0435addd48b564487517 src/admin/web/acme.js +225a29e2e560e42e06e19c3d41c0196c4a42e1e84caa7c83446cab62e3ac30dc src/admin/tests/part_09.rs +bffa6611f3776e97d0065b510b63f083aabf7b3429fcc5cb26b1883fdb7c47dd src/admin/tests/part_10.rs +48fcd818c522fe80f2c7b2e218f87de6a7772ffd0a24e93502780b03ec78e335 src/admin/web/acme.js 446011c2c7afddf7734ceb268c75a3b4146dbe8e915b11986495e956b6c62c69 src/admin/web/app/config-history.js -bd55186a8b02507cffe9fbd2dfb3cc5d3c54509a2a13853b5d93c5f6ec76e688 src/admin/web/app/core.js -cb5c4c2dd4964795b61393b05537cc1528c72d7c1390953f4d119a5e55c65c7c src/admin/web/app/preferences.js -de17987bda89dfab54b6751007474ff9e7cc8da35f7e9c46a2eff922a50fb085 src/admin/web/app/runtime.js -887e34c7e4b3e197e54151f4bb48932e4f7b9f8e64e3d3e8c9c7ee0835fbe4b0 src/admin/web/app/settings.js -74777adaba5a448f3a9abf3a46d91efc6eb81ff6f3b5441e5b99a9e1d6c09af0 src/admin/web/cache-panel.js +8afd3356ba99843d28c107346b0f2f86fbbdf57d846554455fd374c4899bf2f2 src/admin/web/app/core.js +23721bfbac850c4f77b116cc32bb9d2db6361ac4dd7e310a8c8070ab906997f6 src/admin/web/app/preferences.js +344faee969e9d3b8c2f59a443d8ab1090c556ceff325a109e745663baa5e644e src/admin/web/app/refresh.js +31063816894c8efca08ff1c162a7fa2af71268c6deb8063146def2d0baabb17a src/admin/web/app/runtime.js +86ac42bd4a6a84bb137d5a3c3dc3d1f4e404a543447845e6ea6e6e8210e268ce src/admin/web/app/settings.js +4fec1344be3cd540d1e6d0b87fb0aa94e1bef2968c4f86366a62c56d4085f452 src/admin/web/cache-panel.js 50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html -720e201a29d4b3d2e487aa9b857ddcfe785f6f26190827ae5c265ef8c308a86a src/admin/web/docker.js +40cbf3a19827adbff179009b9189263a58a469319db25cf4adf0e4dc441c6532 src/admin/web/docker.js d4aef376cb44b2a13174dca1cb9383f64c04b0b93279f4f0b4805e76002548a1 src/admin/web/docs.css 60a4944a0a24b889296991b8af44b467f776fc85ec3597eb2e2940ef1f6fc713 src/admin/web/docs.html 6adb291fcc98d516fd9745d9673db16e408854a32cbf41335ae956bda11d46de src/admin/web/docs.js -4356ef9c862bd1095ab396b644aee227be51a2a3c960cc3fa1a02eafbc712429 src/admin/web/drafts.js +c52c430b9a6e584012e6014709e1e16ae7e775b2a5aedff8eb767fa867c1cbdd src/admin/web/drafts.js 232b766f278ae4424705af351910677fcd7ac9a20bb8320d9a12b763c91219a2 src/admin/web/error_401.html 16293363c7848471d866092fec5e83023df087aa824989e87450cee7b7ca1263 src/admin/web/error_403.html f7d0955ab18c15e04369204e789f341d2e832f927c1ff20a3b2b9e35e7898f00 src/admin/web/error_404.html @@ -224,18 +236,18 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/ 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js -8a7f5ba3a7d5f0810351a540b829377b8a4a20fd40f6a955529e2d2839ffe328 src/admin/web/index.html +6da5aec18be66ef9f4f5b325bd235f632993a06f26bb41defa1e448881c15a2e src/admin/web/index.html 24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json af0fbd94d9b88ce1cce28e39dfd2c35de031adcba412dd4a9a17d9b416c256dd src/admin/web/login.html 99fe9b8ba7a8a24e5bbe9ae40999484fe13b0faf5eb1d7a9193701f667e51ca5 src/admin/web/login.js 810e7e5bf1af302b51d90771c3ec3c20227eca392f8c7d91d027e11a999bc236 src/admin/web/management.js 90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js -f5ba448163d462ab1f0d421686d1af7ae465b1e5bd78e38fc0899cf0f6f5d13f src/admin/web/settings.js +53cdc1d9d136bbea44a2cf9f06193e90392beb3544c687fbec0fff896565a3c2 src/admin/web/settings.js bb67e77af5514478485cfb69f339d63d7eb0cbadd093fa054814a416260ccc1d src/admin/web/streams.js -b12ce34bcd2617b52bb38f9dfe9be5e1f5c11304c6c18b77a7cb379fa1cafd88 src/admin/web/style.css -a8a73a4fa849ae49391fcd6143e900c515c8b577f71b55cbae9701fa9f5bbe47 src/admin/web/traffic-panel.js -e686d68fd6a368110a85dc4c5dc64e8093950d20d63e9aa44790c03dea6fc193 src/admin/web/transform-panel.js +727ad8dceae6f44ed2ebdb998fe29169bd61406700980b04390c0713c8d8b86c src/admin/web/style.css +d01b615635c7373981b0633cc27298a34e67fe0cbe9d1384d277d46f0cafb1b4 src/admin/web/traffic-panel.js +a6e7bbff4fd2d50ecf759ed28161a672d7b483af2b35c9f912746a7a06a9f476 src/admin/web/transform-panel.js 57a2b975716bdab32c33ec1bf9427b25998550e81ce191440ee66ffb7e2a249e src/admin/web/vhosts/core.js 7e0dc397ce6b2f90dfadf0f724688cb952b89183d06042c0e28d716301e5e7ea src/admin/web/vhosts/editor.js 30a6344e76de77b60cd7247a33c68f1cb5d972aebfde56469f2ebd44440c7bf9 src/admin/web/vhosts/health.js @@ -282,22 +294,24 @@ bd831807ca4720b3a41a6ea1d9a6c0ed2d935225e2dacbd9313be064f2e4a20e src/cache/look 1779abcee77600c13050a2882f896a5b35625b1ae53737c2554179492ce0e40d src/cache/lookup/insert.rs f3e026a3c0e953dca3e5c9def43d864a7706afcd7cbeeaf0f21c059a335b30cc src/cache/lookup/tiered.rs 58c03fde09e5eab40968679793b364f4f1cf5fd832bd79ebcdc92bb23871ad82 src/cache/mod.rs -58ede9780b70ac46fe8b35b6a85f02f56f9a2c75b045e3435062cceb3921f3aa src/cache/model.rs +0c8b0d550e94d4474dcdf991aa0340228dc5f2f904f39350e6e666a16f16fe87 src/cache/model.rs ba94226addfd6cdab67d666dc15b8412c4d117111e44639e503e89a95175d0a2 src/cache/model/cache.rs 0ec4f6d0adea1ed80a3ca17c68c9c85502491da2904d9d690b4169ab9b025b21 src/cache/model/cold_metadata.rs -753d0a316169847f905ecdee0ab6f445e43a3c247d891a08936e19e992f9e3e0 src/cache/model/entry.rs +29bfce96b2ffd102d9c848c7b717a45fedb9aa44511f62f7a1f28a53dafd063a src/cache/model/entry.rs +88d74fc1b7b9ca15a54a8f4893ad3993105952148bcfee099e9bc9727b622aa3 src/cache/model/http_invalidation.rs 09a4417f82984eca8c040ea28eb82b3f129c347d951d4b4fab704bb194d9b985 src/cache/model/maintenance.rs -e655c9e23069ceb553f6fcc7aa3824b08e2e5ceda5c39cd800dc79507856a94d src/cache/model/settings.rs +5441fa3a384add28b6fdd15300cf2648f8237dbfc211cad9117754203be76911 src/cache/model/settings.rs 55dcc0ad84d6b73689ba43e759d5685c406419c7733e23e78d5b072891ab17d7 src/cache/model/views.rs e26dce02df9c043329ca164fed215d271781bce8fb03240f528331bf3df6b9d7 src/cache/policy.rs 989df11e246123f760ef31689370f3500e0913fcc6b70d7ac0c2c95e2f412fb0 src/cache/policy/request.rs d426d341a7678f8ec70406d86052c97d0f49804bbcd8402951103d61a735b9da src/cache/policy/response.rs 7e2bcfbb2b57732cdc704d9df7a9298fb80e735e37984431bc936630e41e7f8a src/cache/range.rs -efde2bd046ed073900cd3e67051f0542c802a06e8f5e38c2c20ee92604080c36 src/cache/tests.rs +7ad96b3baae36ec49c90d03b380e749944fb3d54abb045c90db3a35f6b89b1c1 src/cache/tests.rs 13dab7b7a587cfc146826a92ab94ec9e04f7e500b7a84e678d77226829844923 src/cache/tests/cold_tier_tests.rs b4f609ee900c556b613136bbd3fce11c242a415d277ac3d25bdcb72275b66832 src/cache/tests/cold_tier_tests/part_01.rs f43aa6c231f2c85ee3390dddab74aab2eea2a3f874acb35f145c23866266cb52 src/cache/tests/cold_tier_tests/part_02.rs 22cb9bf20a0d25bee30c1e2213dfe8278ad7d0d5d294275d47d4a6ff9ac646ab src/cache/tests/dashboard_tests.rs +d7a02eb7bad37e6a5524600aabb5e60ccf50e26c7a487e2806891c9d8acd48f3 src/cache/tests/http_invalidation_tests.rs 1db70ea5f3cc1f1543504a68498ba23cafce5f9bb48c4616a4bf70d9a536790e src/cache/tests/scope_tests.rs 578b18cac918ba51fad775eca188f645b228500e59eb1790f783a0b8d0245b7b src/cache/tests/tests.rs c33013089bf9e75a07a5b43f1030acaba8aba4098796e393ed4bad784f4fff96 src/cache/tests/tests/part_01.rs @@ -308,7 +322,7 @@ cdc41a7241739b89297494323f29e0230fa06abbd74e219fedfda11df1db4334 src/cache/test 15e85357c3ddf4fc2283d621f5f4cd8ca309275e06677020299d4105f0475949 src/cache/tests/v022_cache_tests/part_01.rs f262c4e946421b06198ab73ff6e36e6826848f53ce8396eada666c64935c2e70 src/cache/tests/v022_cache_tests/part_02.rs a8a0ce446eb53ffdb8f8d97efa3e1344410dc3798b25f62b8817e8e160d28940 src/cache/tickets.rs -3fc79b09474fe23b5b6fc98e4a376469afb02a41d6cc2dbbba59dc6091c790c8 src/compression.rs +28f904cf4827294f2f759e6d3624608ed90059b64e42864e55dc9213824eb655 src/compression.rs e2f58279499b7c3b47c65d3a7814bb5c7cd6506544d939875fbb0f91fa9aa59d src/compression/tests.rs 7bc8285cb2012506fd1a38a71dffa561f885f4ab65a9b2a7c8344319e3cd1cf6 src/config.rs 7ba52d3de4d77d1f3cad1b10de418953adc33901727a0791980847ae223929aa src/config/backend.rs @@ -379,6 +393,7 @@ e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus b5856fd38a957b2ca9c7e3606d84d13d5cfcf3a45b487b15ae1d2dc248cbda57 src/proxy/body.rs 16b9d4db3b20b2ab14c47ac14fcd18d3d8ca77c3ad9a1f37866ebfa60f379862 src/proxy/body/tests.rs f0cb26ed34c35bacc74b914457acac8a611390de14cf46e2e6d3bcc848e914e8 src/proxy/cache_helpers.rs +7ad068bca5397d1337f153fa26b9ca7ae1403abf4fa059b85362d15247717a67 src/proxy/cache_http_invalidation.rs 771cc1a38927f96e593b144dbab66cd64a5dc0ed42d54b1615c359845b2b7223 src/proxy/error_fallback.rs bf2e6bbd64fb45fceed54af0bff1c1e00da22b79c64dd8f5c23d2af0dbab0b7d src/proxy/forward/cache_lookup.rs 526592b613fbe20f532f11be5a3236acaeb8cad400f70e1d28cc1c2dfc126c52 src/proxy/forward/response.rs @@ -387,12 +402,13 @@ cea9e47ecb31b0e4c0bbe0eb83e0e53b5e7bdcb9930107b67a8c34eef01a3f49 src/proxy/forw 902cbf8dc3c1ec8d25411bc7fe0400d301f3a7cc885afada594e4eaee3bedd08 src/proxy/forward/success_prepare.rs 23b6bd26f6175b52c13d2571318fddd12550ba34ee77b87b5ac0e4811f8e2bee src/proxy/forward/upstream.rs 8961d8d649d8f4708f443f1b8b8a73c8b5f42811d2cad8cc6f596833511933f4 src/proxy/forward/web_source.rs -c39090511e8057d43f2620175958c3dbd9b686036306a0060bb64eaab4bf6c93 src/proxy/forward_entry.rs +a164fb389f38b0172db38803e9a49dd1cb61a4f2db49c72e220bebd96ea631ff src/proxy/forward_entry.rs c438066616527a3108f2ad53e3698ae9b2a80961714394cdd0a450c92f99d992 src/proxy/headers.rs 5cb7522ceb9a9f083a15596ac4898559295e38d1138c80099577211de8a868ec src/proxy/headers/tests.rs -f202c0fe0bc4e786a906d7337d368cd81bcac15ffdbbfd35d5b0d49126d9895a src/proxy/mod.rs +42155c7742da4173f71dc4ef488288e1fd665fbb295f02745421785571926295 src/proxy/mod.rs 55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs cc682f0d5754e0b806a4eb5950e4cc00611aca5ce4668414fddef1d692489805 src/proxy/tests/authority_tests.rs +d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs e80c7fe44abb7607c64ea02d56542753dc25290b3bcf4c4d45b75f2db6ca90d0 src/proxy/types.rs 26afb4dcda99a54abdd85f7950ecac6d608eca78067323f695a43382d21fa2fb src/proxy/upstream_helpers.rs 69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs @@ -406,10 +422,10 @@ d40ad63939097d2b91339127d5835a74b618d83b7ba31db0c512f05a82107d6e src/redirect.r 811c83ae9fd0f5a13d35b5443cd37843f82b9d600bee4bb136ba9b87732a5347 src/redirect/tests.rs d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs 4e0b25c2b86eefddd76c64b39dc1c1cd6d243addc32cdea99e6c3ac84ed879a6 src/runtime/certificates.rs -ad79a64f0fb31881aa93a2305fa3084d7e02e15d464c71dbba38123bd6a81fc5 src/runtime/constructors.rs +2752a1122f22cab7af9ccdd23cd0aa6d99a73f1d82614814217a3875927d7eef src/runtime/constructors.rs 31dc5cdea00efd63594dc6becd5a9103594696145cbc8e7b7594fb407214ad9d src/runtime/docker.rs -467e07b21075fe513786aa465dc2d2307f0ad76198d0166f03e6bc5b4a19868e src/runtime/tests.rs -9671e128bfd35179cbadf4230e23c4868f13ac44957b98644301799b8acdecee src/runtime/types.rs +e90df3fff8a25465425b099ae358d19085809e56d948ba47e01e53f8b93d5dd0 src/runtime/tests.rs +0046525317ffbd5d3b34845656b0f58d9d31f38b3d8acd71e90d94bd7cdfb8ab src/runtime/types.rs 88bc546fdda20d3f1f8f5e7d7872ede1fa334b8bb5102c729bd72f56e3fb94b9 src/runtime/updates.rs b4c17b304166592e63c81884076589fbaf725c3f57043f8924c46167b061ac5b src/secret_store.rs 6dca8da072a3bd610704a391bb6e25c9042971f4ebc60edb47665ce1d74cbc58 src/secret_store/tests.rs @@ -421,7 +437,7 @@ f2f6196632b2fdb2444189624f961edaf5bdfff57f700b0207b57e2491c1daa5 src/server/htt 71899a9b1897fa22ab9e8eb6a670054a6d382bad78714006b9b218d07ba99902 src/server/serve.rs e9e478cf45b88994eaffd115f51764b6b07852ee3c941cafdabb20584d3dabc0 src/server/serve/tests.rs 048f2c7a8ef698a172ba7ed844558f4118b367b2cf340748c5c28da7723d8e85 src/server/tls_runtime.rs -392e4321e5079bd94c795e9bcc08d0abe4b40e721d0e6a36e115f9fad7e86478 src/settings.rs +623e1b24c17f2149c712bc9fe38ca23c503e88891f5eef1b5ceef6cbf8cf9093 src/settings.rs 3b3893d899b8e82969588f30c1e0aeff250449e4c94627ff5ad94d29f227ceca src/settings/tests.rs 566daf9c0e1a30da2dc18e51090541a85ced3320a0fc00ed1d1de3e2b4da5072 src/stream.rs 46d9f63ce13a634e00f38d36ca3bdb217f2e37f04d75a4ef118e7dac9896a489 src/stream/model.rs @@ -502,30 +518,34 @@ eecf680f001f2110dde4eceaccf2f922859a16c946a644f8854e8de4d3771f7c tests/policies 728999110a6d8cacb62bc0399bc333fa23a66526ee23f563a3c93bf00e5fad19 tests/transport_config.rs b04362ad082a32c9ccc4cf38c46f44d71347adecf7aba3224b3af3ea4872e3d0 tools/README.md 358c1e94dd2b4a756430e5335747f0b437345b1713670a9972c5bcf78b837ccb tools/audit/admin_audit.py +b9d7b622127841f7abe6f87ce6bb769c95539de7c52824cc691a47be2d6ab65f tools/checks/admin_surface_audit_0310.py 412255d383fcc8306c6086784fb416d9814e3ec790e440a0927431ca615f3411 tools/checks/backend_cache_0297_ui_test.mjs 2a493dbce2991a23d807c01952f58608a364f541560836b4cb385e75e108e9e7 tools/checks/backend_health_0300_ui_test.mjs -84c090840926fb8fc5ed74aa779e7ca028bc0c684ca5ca15f5ae105e65a8dd3b tools/checks/check_source.py -5362f6c455cf697aacb59336d6664df0fd2a36956bedbbdcb090887ff20b7049 tools/checks/docs_check.py +e3591f3aa47c595ad6d93509217734b90f829d8d3eac6c572936aeea1793a1cd tools/checks/check_source.py +998ae8674598b8c8c937a323303f1ea0cccd4f8df2dfe804ddc24b5f8a6d6da3 tools/checks/dashboard_startup_0310_ui_test.mjs +8005a9098f5ae142d8a54038506d8939725bf46c74bd07d2de3ecb916e390e9d tools/checks/docs_check.py e33a9cc51d58bc92a637c0e6a1c88cbd24ba4605be55748212e296c3c35a4060 tools/checks/high_traffic_memory_model.py 4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py f8f87ba891338b565249d02c3cf5f02cec44e606b4205de99fcbea1eb2c24d57 tools/checks/memory_contracts.py -c2e9b21e6c4acdff5d846843fbf7a3babeeed10b9b59ed001c519feea815d90e tools/checks/preflight.py -32ae78d788aa841e2c01b792aab9c2cf3f075a1508f8abcde4499153c5f778df tools/checks/release_smoke.py +5b7901747d879e12a39ed82b6efc513bcdddd15d508b5828210d2c83398fb6a4 tools/checks/panel_cache_http_0310_ui_test.mjs +4bf7e733f8a2c32c3ea857e680604af39ed26f9dc9b3659744d6ec21024e9f2f tools/checks/preflight.py +3485681d369b5f486dfc2d14660f19b4098df670bafd9a296cc9dabe68003a51 tools/checks/refresh_0310_ui_test.mjs +1f4f834761a4107422e39243e39e352fff4e73eac923b6f26eb4d8e5b3e8a5c1 tools/checks/release_smoke.py f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py 23b9250245e6bec5ef728c73cee4c88f64924afc3597f8a9b58a1924235df98e tools/checks/security_dependency_contracts.py 2e125a2a807553ac81d04428f337f5042eed968cb807244487114fdd8ea6b5d9 tools/checks/source_module_contracts.py -37dafd2db24beba50fa155c302299dc4a8a8171531f86ac5b4e53bb0e269c1f7 tools/checks/system_info_0301_ui_test.mjs -7505e10a00969ea705987dd68c6ba6f5c556e0b2d5bde9926ac4f41e72381700 tools/checks/system_info_no_subprocess_contracts.py -987b4c593b51a7c340d8f01f9a2eea1387043cdd997eeb932c8341bbb8c0bd64 tools/checks/version_contracts.py +bbd9726e11ce1324b7df6a012bac51262e22f2617bd3d36974aa151dce5c1c34 tools/checks/system_info_0301_ui_test.mjs +543a488951ea6ec8fe06e04f168ccc4d4f2af3cfc281c9a728ab7bd87377fa98 tools/checks/system_info_no_subprocess_contracts.py +614ed0c775e32ec5bfb4ee75aaa90206a10da0f43659747a7ec76fd93e33767a tools/checks/version_contracts.py 2020e171be1a044f7ca2ab83af7604a014c36fe4ff04791d0620ccbf1bf8e000 tools/checks/vhost_origin_errors_0297_ui_test.mjs 0c071c26fc316934bdfd2bfd19f5fed9521790d8c887760eaabda81a9be96703 tools/checks/vhost_www_ui_test.mjs 934dce837a13c8e9eee8e161aeb8b63dc771e2197c61639cf59e33b5ceaca833 tools/checks/web_assets.py 0398892be30fe416a00578915c588f360fd93244e3af77ece834e560e547d1c8 tools/checks/web_server_contracts.py 35f76060e585a96c623ef83bd5f771969f5be16a0e1ae626a3092d7458b84e84 tools/checks/web_service_ui_test.mjs adaa1ed9e5958739df40190055c780c80d0a8e270ec8dc105ed3fa2dbb8fb74f tools/checks/www_redirect_ui_test.mjs -eb4e9a72bdc8e134ee67712a656b6e42c4bbc1f1602a1039052977055cfc6a64 tools/dev/memory_load.py +f16f69dabab98fe468aa90cc8f3c447c0588d7bd18163080d3d7b15c67190e4c tools/dev/memory_load.py d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a tools/dev/mock_auth.py 2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 tools/dev/mock_server.py 538b5f6cfa738355a729940ee3d92e7ab3dc25ca69ae330ddaa08f831775b8ba tools/dev/test_compression.py diff --git a/dev.sh b/dev.sh index 16a065f..b39ba3c 100755 --- a/dev.sh +++ b/dev.sh @@ -99,12 +99,16 @@ if [[ "$mode" == "test" ]]; then python3 tools/checks/high_traffic_memory_model.py python3 tools/checks/web_server_contracts.py python3 tools/checks/security_contracts.py + python3 tools/checks/admin_surface_audit_0310.py python3 tools/checks/security_dependency_contracts.py if command -v node >/dev/null 2>&1; then node tools/checks/web_service_ui_test.mjs node tools/checks/backend_cache_0297_ui_test.mjs node tools/checks/backend_health_0300_ui_test.mjs node tools/checks/system_info_0301_ui_test.mjs + node tools/checks/refresh_0310_ui_test.mjs + node tools/checks/dashboard_startup_0310_ui_test.mjs + node tools/checks/panel_cache_http_0310_ui_test.mjs node tools/checks/vhost_origin_errors_0297_ui_test.mjs node tools/checks/www_redirect_ui_test.mjs node tools/checks/vhost_www_ui_test.mjs diff --git a/docs/API.md b/docs/API.md index 5059566..7dcc460 100644 --- a/docs/API.md +++ b/docs/API.md @@ -7,6 +7,23 @@ certificate and persist a non-loopback bind; plain remote HTTP requires the expl startup `--allow-remote-admin` gate. Responses carry no-store, CSP and other browser protection headers. +## Public-vhost HTTP cache invalidation (opt-in) + +`PURGE /path?query` and `BAN /prefix` operate on the normal public vhost +listener and **do not** use an admin session. They are independently denied by +default unless the administrator explicitly populates the cache's +`http_invalidation_allowed_cidrs` allowlist in **Settings → Cache**. Authorization +uses the existing trusted client-IP forwarding logic, vhost/host ACL and rate +limits. BAN is a lazy prefix rule; PURGE evicts an exact URL. Both are restricted +to the request scheme (`http` vs `https`), Host and matched vhost. A shared +two-worker maintenance guard also bounds administrator-driven invalidation; +additional requests receive 429. Ambiguous duplicate control headers and +request bodies are rejected. See +[CACHE](CACHE.md#http-ban--purge-from-authorized-client-ips) for request examples, +scoping, confirmation and reverse-proxy requirements, and +[0.31.0 security review](SECURITY_AUDIT_0.31.0.md) for audit findings. The authenticated +`/api/cache/invalidate` endpoint remains unchanged. + ## Browser documentation and anonymous surface Open `/docs` **on the administration listener** after signing in. The default **Reader** @@ -72,6 +89,8 @@ framework extraction failures are not guaranteed to have that JSON shape. | `GET /api/backends` | Authenticated per-vhost backend observations and active/desired frontend plans | | `GET /api/status` | Uptime, active revision, configured counts, listener addresses, feature flags, aggregate metrics, active/desired TCP/UDP streams with per-stream L4 counters, active/desired administration state, TLS policy/cipher inventory and structured-log sink counters | +`GET /api/status?view=live` returns only volatile telemetry suitable for SSE fallback, without the heavy full configuration/OS/runtime objects. `GET /api/system-info` returns cached system diagnostics, host/runtime metadata and memory accounting under `system`, `runtime` and `memory_accounting`. Both are protected by the same cookie/origin admin guard as full status. + `status.streams` contains `active`, `desired`, `restart_required` and a `metrics` map keyed by startup-active stream ID. Stream counters include active/total/completed sessions, rejects, errors, RX/TX bytes and UDP packet counts; the dashboard derives rates from byte-counter deltas. | `GET /api/logs` | Recent bounded access entries, ring capacity and contention-drop count | | `GET /api/audit` | Last 100 audit entries; database retention is 10,000 | @@ -83,9 +102,9 @@ Except for health and authentication/bootstrap routes, these endpoints require a session. These administrative previews are not anonymous. The separate configurable exporter uses its own CIDRs and optional bearer token; see the scrape section below. -SSE has eight viewer slots, generates a snapshot approximately every two seconds +SSE has eight viewer slots, generates a snapshot every 1/2/5/10 seconds per authenticated client (via `?interval=`, default 2) and rechecks session validity. It does not queue an unlimited per-viewer event log -or guarantee replay. The browser falls back to polling. An active dashboard renews +or guarantee replay. The browser falls back to compact status/log polling only after SSE fails and stops polling after its recovery. Invalid SSE intervals are rejected with 422. An active dashboard renews idle activity, not the absolute session deadline. Configuration history, introduced in SQLite schema 8 and retained in current schema 9, keeps the newest 512 successful manual/configuration revisions. `GET /api/config/history` is session-protected like the rest of the administration API; `limit` is bounded to 512 and `revision` selects one retained revision for diff/inspection. `POST /api/config/history/restore` also requires the normal CSRF/request-marker protections, rejects stale `expected_revision`, and never rewinds the revision counter: restoring revision 85 while current revision is 120 creates revision 121. diff --git a/docs/CACHE.md b/docs/CACHE.md index 3fa529a..dfb8955 100644 --- a/docs/CACHE.md +++ b/docs/CACHE.md @@ -13,7 +13,7 @@ different vhost/config scopes internally and uses response `Vary` to select repr variants. This is not a VCL implementation. Generic Varnish grace, ESI, public unauthenticated -BAN/PURGE and distributed cache state are not implemented. RustProxy 0.22.1 adds bounded +unrestricted public BAN/PURGE and distributed cache state are not implemented. RustProxy 0.22.1 adds bounded foreground request collapsing, authenticated selective PURGE/BAN and optional proactive refresh while keeping directive-controlled stale delivery through RFC 5861 and request `max-stale`; it is not an unconditional availability/grace layer. RustProxy also keeps its @@ -36,6 +36,48 @@ proxy origins, range requests and conditional requests can reuse fresh stored representations. Do not rely on stale-if-error remote-backend refresh behavior for local Web Server routes. +## HTTP BAN / PURGE from authorized client IPs + +The **Settings → Cache → HTTP BAN / PURGE IP allowlist** control sets +`cache.http_invalidation_allowed_cidrs`. The default is **empty (disabled)**. +Allow individual client IPs or small CIDR ranges, one per line. Saves validate +IPv4/IPv6 CIDR syntax and reject `/0` networks. An allowed request must also +pass the vhost listener binding, global/vhost ACL, and rate limits. The +operation is restricted to the matched external HTTP/HTTPS scheme, `Host` and +vhost; it cannot choose other tenants or the opposite scheme through headers. Commands use the existing +cache invalidation engine, are audit-logged before execution and share a +two-worker maintenance limit with authenticated admin invalidation (429 when +busy). They are never forwarded upstream. + +- `PURGE /path?query` immediately invalidates exact cached representations of + that URL. Returns `200` with JSON result/count. Other query variants are not + matched. +- `BAN /prefix` records a lazy prefix ban of older cached representations + for that host/vhost. Returns `202` and ban ID, normally expires after one hour. + Optional `X-RustProxy-Ban-Ttl: 30` sets 1..604800 seconds. Newly filled + representations are allowed (Varnish-like lazy BAN). +- `BAN /` requires `X-RustProxy-Confirm-Wide: 1` to prevent accidental + whole-host invalidation. Request bodies, upgrades, arbitrary selectors and + wildcard cross-vhost operations are rejected. + +Examples from an allowlisted machine (replace the domain and path): + +```sh +curl -i -X PURGE 'https://site.example/assets/app.js?v=3' +curl -i -X BAN 'https://site.example/assets/' +curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1' +``` + +**Behind a reverse proxy:** forward nonstandard `BAN`/`PURGE` methods to the +RustProxy public listener; do not let an upstream cache intercept them. Configure +RustProxy's existing `forwarding.trusted_proxies` accurately and ensure the +edge sanitizes/replaces untrusted `Forwarded`/`X-Forwarded-For` headers. When a +trusted proxy does not provide a usable real-client chain, BAN/PURGE fails +closed. Do **not** allowlist an entire proxy NAT network unless all its users +should have destructive cache access. Unlike full Varnish VCL, this interface +intentionally provides only exact PURGE and bounded URL-prefix BAN, not an +arbitrary untrusted regex/VCL expression. + ## Settings | Field | Default | Bounds / meaning | @@ -66,6 +108,7 @@ for local Web Server routes. | max_ttl_seconds | 86400 | 1..604800; maximum configured retention/freshness cap | | bypass_query | false | Bypass requests containing a query when enabled | | bypass_prefixes | [] | Up to 64 normalized absolute path prefixes | +| http_invalidation_allowed_cidrs | [] | Opt-in IP/CIDR allowlist (max 32) for HTTP BAN/PURGE. Empty blocks all HTTP methods; `/0` is invalid. | | static_ignore_cookies | true | For configured static extensions only, ignore request Cookie for eligibility/keying | | expose_status | false | Emit `X-Cache: HIT`, `MISS` or `BYPASS` | | expose_hits | false | Emit per-object `X-Cache-Hits` | @@ -175,9 +218,13 @@ Cache invalidation remains explicit: - regex rules and prefix `/` are treated as broad operations and require explicit confirmation, - only cache-compatibility changes advance the normal cache generation/revision; unrelated logging/TLS/admin/metrics/response-presentation saves retain objects, and cache request-bypass edits plus response-only `X-Cache`/`Cache-Status`/resident-age/`Via` diagnostics also apply without purging stored objects. -There is no public unauthenticated PURGE/BAN endpoint. If an application changes a GET -resource after POST/PUT/PATCH/DELETE, use origin freshness/validators or the authenticated -administration invalidation path; RustProxy does not guess the affected URLs. +Unrestricted anonymous cache-maintenance access is not exposed: the public +HTTP BAN/PURGE interface is explicitly IP/CIDR-authorized (empty by default), +separate from the administrator-only selective invalidation API. If an +application changes a GET resource after POST/PUT/PATCH/DELETE, use origin +freshness/validators or one of the explicit invalidation paths; RustProxy +does not guess the affected URLs. For exposure review and test scenarios, see +[0.31.0 security audit](SECURITY_AUDIT_0.31.0.md). ## Eligibility and freshness diff --git a/docs/CHANGELOG_TECHNICAL_ARCHIVE.md b/docs/CHANGELOG_TECHNICAL_ARCHIVE.md index e80b25d..7d9616b 100644 --- a/docs/CHANGELOG_TECHNICAL_ARCHIVE.md +++ b/docs/CHANGELOG_TECHNICAL_ARCHIVE.md @@ -7,6 +7,12 @@ # Changelog +## 0.31.0 Axum fallback handler E0277 source correction + +- Correct the reported `Handler<_, _>` error at `src/proxy/types.rs:53` after the public cache-maintenance path was added. The asynchronous `cache_http_invalidation` helper previously accepted `&Request` while awaiting database audit and cache invalidation; Axum's body is not `Sync`, so the borrowed request can make the `forward` handler future non-`Send`. +- Pass ownership of `Request` to the maintenance helper, perform the same body/header/allowlist validation, copy the HTTP method for the audit entry, and release the request body before the first asynchronous operation. The `forward` router and public API semantics are unchanged. +- Add an offline source regression asserting that the async helper owns its request. Pre-Rust `./dev.sh test` checks and Go tests pass; full Rust parsing, type checking, tests and runtime maintenance calls cannot be validated without Cargo/Rustc. + ## 0.30.1 same-version sysinfo compiler feature correction - Fix four compiler `E0433` errors in the system information endpoint by enabling `sysinfo`'s `system` feature alongside `network` with `default-features = false` unchanged. No dependency version or API change. diff --git a/docs/IMPLEMENTATION_0.31.0.md b/docs/IMPLEMENTATION_0.31.0.md new file mode 100644 index 0000000..51ea18a --- /dev/null +++ b/docs/IMPLEMENTATION_0.31.0.md @@ -0,0 +1,152 @@ +# RustProxy 0.31.0 – implementation and verification report + +### Security/compilation follow-up + +- Correct the user-reported `E0716` lifetime error in the HTTP BAN/PURGE + error path by returning an owned-independent static response and logging the + detailed validation error. +- Restrict invalidation to the selected HTTP/HTTPS scheme, Host and vhost; + reject duplicate maintenance headers, invalid bodies and broad BAN without + confirmation. Execute shared cache maintenance in a bounded blocking pool + of two operations, rather than scanning on Tokio core workers. Audit + authenticated BAN-rule deletion before mutation. +- Add localized BAN/PURGE `curl` help next to the Cache allowlist and Rust + API/auth/security regression tests. Full review, exposure matrix and live + test plan: [SECURITY_AUDIT_0.31.0](SECURITY_AUDIT_0.31.0.md). Source-level + coverage is **not** Rust runtime/compile verification. + +### Panel compression and cache HTTP invalidation follow-up + +The 0.31.0 maintenance follow-up introduces one shared, precompressed panel +content handler for authenticated HTML, CSS, JS and language bundles (negotiated +Zstd/Brotli/gzip with identity fallback; settings under Administration), plus +optional IP/CIDR-authorized public `BAN`/`PURGE` scoped to the selected vhost. +Existing cache invalidation, revisioned settings and auth/ACL behavior are reused. +The System information Refresh button now sits next to the API badge. See +[SETTINGS](SETTINGS.md), [CACHE](CACHE.md) and [TEST_REPORT](TEST_REPORT.md) for +behavior and verification limitations. This note does not assert a Rust build. + + +Date: 2026-10-10. Input: user-provided `RustProxy-0.30.1.zip`, which **does not contain `.git` metadata**. This is a complete edited source snapshot, **not** a verified checkout of remote `master` or a compiled/released binary. No commit or tag was created. + +## Changes implemented + +1. **One dashboard coordinator:** `src/admin/web/app/refresh.js` replaces recurring independent panel timers. Every task has a stable ID, active-view guard, sequential `setTimeout` scheduling after completion, `AbortController`, request epoch/generation guards, independent error handling, and manual execution. Auto polling and SSE pause on hidden tabs and stop on logout; timers and `visibilitychange` listener are removed. +2. **SSE with fallback:** `/api/events?interval=1|2|5|10` preserves one shared live channel per browser with individually selected frequency, session checking and heartbeats. It carries lightweight status and the existing log snapshot. `/api/status?view=live` and `/api/logs` polling run only while SSE is unavailable, and are stopped on recovery. The full `/api/status` remains available to existing clients. +3. **Traffic / Cache:** retained `/api/traffic` and `/api/cache`; they poll only in their active view, while both per-view Auto and account Live switches allow it. The interval comes from per-account preferences. `0` selects manual operation without disabling refresh buttons, paging, charts, filters or operations. +4. **System diagnostics:** new authenticated `/api/system-info` sourced from cached `sysinfo`/diagnostic and existing ACME-runtime metadata; OS identification is cached. Normal live snapshots no longer include costly system information, memory accounting, certificate runtime details or full configuration. System info polls at the selected lower cadence for Overview and the relevant Settings tabs; manual refresh is always available. +5. **Preferences and UI:** existing `/api/preferences` and account-specific SQLite storage extended with defaulted `refresh` settings; strict server/browser allowlists; UI controls, reset, persistence and immediate rescheduling. English/Polish translations maintained. Old preference documents are accepted without migration. +6. **Release:** main Cargo manifest/lock package, OpenAPI/version checks and active docs identify `0.31.0`. Historical changelog entries and third-party crate version numbers remain intact. Existing SQLite schema 9, cold-cache format 3 and proxy data path are unchanged. + +## Refresh defaults and boundaries + +| Key | Default | Allowed values (seconds) | +|---|---:|---| +| `live_enabled` | `true` | `true`, `false` | +| `overview_seconds` | 2 | 1, 2, 5, 10 | +| `traffic_seconds` | 5 | 0, 3, 5, 10, 30 | +| `cache_seconds` | 5 | 0, 3, 5, 10, 30 | +| `system_info_seconds` | 60 | 0, 15, 30, 60, 300 | + +`live_enabled=false` pauses **all automatic** fetching, including SSE and system info. Every manual refresh remains possible. Server stream cadence is client-local, not global. The existing `/api/live-logs` remains independent, in accordance with its existing logs subscription model. + +## Verification executed + +| Action | Result | Scope/limit | +|---|---|---| +| `./dev.sh test` | **Partial, exit 127** | All Rust-independent preflight, source/module/security/version checks, SQLite schema-9 migration/upgrade, 8 Node UI tests (including new refresh test), docs and language checks passed. **Rust toolchain check then failed**: `cargo`/`rustc` unavailable. | +| `node tools/checks/refresh_0310_ui_test.mjs` | **PASS** | Deterministic simulated browser clock: 2/3/5/0-s cadence, manual mode, Live toggle, 20 rapid tab transitions, no duplicate requests/timers, aborting obsolete work, hidden/visible, SSE error/recovery fallback, independent failures, logout/cleanup, browser preference validation. | +| `go test ./...` (in `docker-agent/`) | **PASS** | Go agent unit tests. | +| `node --check` over all `src/admin/web/**/*.js` | **PASS** | JavaScript syntax only, not execution in Chrome/Firefox. | +| `python3 tools/checks/check_source.py` | **PASS** | Static contracts (not Rust type checking). | +| `python3 tools/checks/release_smoke.py` | **PASS** | Offline SQLite/migration and release integration/source contracts. | +| `bash -n dev.sh` and Python compile checks | **PASS** | Shell and Python syntax. | +| `./dev.sh check`, `./dev.sh fmt`, `./dev.sh release` | **NOT EXECUTED (exit 127)** | Rust/Cargo/rustfmt absent. No format, typecheck, Rust unit/integration suite, linked release binary. | +| Live Rust SSE/API/Traffic/Cache + multi-user persistence | **NOT EXECUTED** | Cannot start the Rust service in this environment. Rust integration regression tests were added to source but not run. | +| Real bandwidth/serialized SSE bytes/CPU, long-running resource metrics | **NOT MEASURED** | No Rust server. The repo's 2,000 HTTP-request memory-load smoke uses a Python **peer**, not the RustProxy binary; it is not RustProxy performance evidence. | + +**Important:** all Rust runtime, compile and real end-to-end security, SSE heartbeat/reconnect and production proxy regressions remain open; this source archive cannot be declared production-ready before those checks. + +## Request-rate analysis (source-derived forecast, not wire measurement) + +Rates below are **steady-state theoretical** at default intervals, for a single visible administrator with no manual clicks; the first fetch after entering a view is extra. They are not byte-size/CPU benchmark results. + +| Mode | Previously | v0.31.0 default | +|---|---|---| +| Overview status + logs | ~30 full-status SSE snapshots/min (each contained system data), with log payload | ~30 compact SSE snapshots/min with logs; no concurrent fallback | +| Overview system diagnostics | embedded in each SSE status | ~1 GET `/api/system-info`/min | +| Traffic active | ~20 GET `/api/traffic`/min at 3 s | ~12 GET/min at 5 s | +| Cache active | ~20 GET `/api/cache`/min at 3 s | ~12 GET/min at 5 s | +| Other views / hidden tab | older per-view guard behavior; background SSE not globally paused | zero scheduled Traffic/Cache; hidden tab closes SSE and pauses all automatic polling | +| Settings not requiring diagnostics | independent legacy status data carried by SSE | no automatic GET `/api/system-info` | + +Actual SSE payload lengths, encoding/cost, network traffic and memory footprint **must be measured against a running binary**. No byte reduction percentage is asserted. + +## Changed files (40 edited, 8 new, 0 deleted) + +- `AGENTS.md` +- `CHANGELOG.md` +- `Cargo.lock` +- `Cargo.toml` +- `README.md` +- `ROADMAP.md` +- `SHA256SUMS` +- `dev.sh` +- `docs/API.md` +- `docs/README.md` +- `docs/SETTINGS.md` +- `docs/TEST_REPORT.md` +- `langs/en_US.json` +- `langs/pl_pl.json` +- `src/admin/documentation/documents.rs` +- `src/admin/logging_api.rs` +- `src/admin/openapi.json` +- `src/admin/preferences.rs` +- `src/admin/router.rs` +- `src/admin/status.rs` +- `src/admin/tests.rs` +- `src/admin/tests/part_04.rs` +- `src/admin/web/acme.js` +- `src/admin/web/app/core.js` +- `src/admin/web/app/preferences.js` +- `src/admin/web/app/runtime.js` +- `src/admin/web/cache-panel.js` +- `src/admin/web/docker.js` +- `src/admin/web/drafts.js` +- `src/admin/web/index.html` +- `src/admin/web/settings.js` +- `src/admin/web/traffic-panel.js` +- `src/admin/web/transform-panel.js` +- `tools/checks/check_source.py` +- `tools/checks/docs_check.py` +- `tools/checks/preflight.py` +- `tools/checks/release_smoke.py` +- `tools/checks/system_info_0301_ui_test.mjs` +- `tools/checks/system_info_no_subprocess_contracts.py` +- `tools/dev/memory_load.py` +- `docs/IMPLEMENTATION_0.31.0.md` +- `src/admin/preferences_columns.rs` +- `src/admin/refresh_preferences.rs` +- `src/admin/status_live.rs` +- `src/admin/status_system.rs` +- `src/admin/tests/part_09.rs` +- `src/admin/web/app/refresh.js` +- `tools/checks/refresh_0310_ui_test.mjs` + +## Required remaining release gate + +On a host with stable Rust toolchain and a configured development environment, run: + +```sh +./dev.sh fmt +./dev.sh check +./dev.sh test +./dev.sh release +cd docker-agent && go test ./... && cd .. +``` + +Test real session login and per-account persistence, permission denial for `/api/system-info`, SSE connect/heartbeat/reconnect/disconnect, active-vs-hidden browser polling via DevTools, real `/api/status` backwards compatibility and live/proxy/cache/traffic behavior. Measure serialized SSE bytes and generation time, CPU and request counts by Overview/Traffic/Cache/Settings/hidden tab. Correct any Rust compilation/runtime failures before deploying or creating a release tag. + +## Same-version mobile startup follow-up (2026-10-10) + +The authenticated dashboard replaces the previous all-body `visibility: hidden` with a visible CSS-only spinner, a 12-second stalled-start hint with reload link, and an EN/PL explicit failure screen. It reveals the shell and starts the existing refresh coordinator before awaiting noncritical preferences/configuration/status; failed configuration cannot hide the shell. `Refresh now` is now aligned with the Overview heading. The fixed authenticated HTML is gzip-compressed once into cached bytes, negotiated per request; unchanged identity responses and public login remain. Browser UI/source checks are documented in `docs/TEST_REPORT.md`; Rust compilation and real wire/mobile tests remain open. diff --git a/docs/README.md b/docs/README.md index 11a5c36..72a4a61 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,8 @@ This is the canonical index for the current project documentation. The authentic - [0.26.3 implementation note](V0.26.3_IMPLEMENTATION.md) - global interface scaling, chart themes, square draft dock and Settings navigation polish. - [0.26.2 implementation note](V0.26.2_IMPLEMENTATION.md) - unified Logs navigation, field-query search, dynamic JSON columns and RAW parsing. - [0.26.1 implementation note](V0.26.1_IMPLEMENTATION.md) - historical vhost-aware protocol logging and Stored Logs UX milestone. +- [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) - current API/panel/BAN/PURGE attack-surface review, hardening, compile fix and mandatory runtime gates. +- [0.31.0 implementation report](IMPLEMENTATION_0.31.0.md) - consolidated changes, exact file inventory, validation results and outstanding Rust runtime acceptance. - [TEST_REPORT](TEST_REPORT.md) - verification commands, results and environment limitations. ## Functional documentation @@ -78,7 +80,7 @@ This is the canonical index for the current project documentation. The authentic ## Historical or version-labelled context -Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md); [V0.29.0_IMPLEMENTATION.md](V0.29.0_IMPLEMENTATION.md) is retained as the prior redirect/health/cache/log milestone; [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) is retained as the prior certificate-hardening milestone; [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability. +Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [IMPLEMENTATION_0.31.0.md](IMPLEMENTATION_0.31.0.md); [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md) is retained as historical context, and [V0.29.0_IMPLEMENTATION.md](V0.29.0_IMPLEMENTATION.md) is retained as the prior redirect/health/cache/log milestone; [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) is retained as the prior certificate-hardening milestone; [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability. Older context documents such as [INTEGRATIONS_040](INTEGRATIONS_040.md), [MANAGEMENT](MANAGEMENT.md), [SECURITY_AUDIT](SECURITY_AUDIT.md), [ACME_RFC2136](ACME_RFC2136.md) and older version audits remain available for traceability but should not override the current functional/technical documents above. diff --git a/docs/SECURITY_AUDIT_0.31.0.md b/docs/SECURITY_AUDIT_0.31.0.md new file mode 100644 index 0000000..1a026ca --- /dev/null +++ b/docs/SECURITY_AUDIT_0.31.0.md @@ -0,0 +1,148 @@ +# RustProxy 0.31.0 - API, panel and HTTP cache-maintenance security review + +Scope: source snapshot supplied for the v0.31.0 maintenance follow-up, reviewed +2026-10-10. This is a **code/contract review with offline regressions**, not a +penetration test or proof of production safety. The supplied archive has no +`.git` directory; the review does not establish parity with remote `master`. + +## Interfaces and trust boundaries + +| Surface | Exposure | Authentication and mutation rules | Assessment | +|---|---|---|---| +| Public administration bootstrap (`/`, login JS/CSS, `/api/auth/status`, setup/login POST) | Login/public administration listener | Host/origin validation; request marker for unsafe bootstrap actions; one-time setup capability and login throttling | Source-reviewed; live browser flow pending | +| Private panel HTML, JS, documentation, OpenAPI | Administration listener | Administrator session; `Cache-Control: no-store`; CSP, frame and MIME protections | Source and anonymous-response test reviewed; runtime pending | +| `/api/status`, `/api/system-info`, `/api/traffic`, `/api/cache`, logs, config, preferences, audit | Administration listener | Administrator cookie session; sensitive reads not on anonymous allowlist | Source and regression tests added; runtime pending | +| `/api/events`, live-log SSE | Administration listener | Session on subscribe and repeated validity checks; bounded viewers; heartbeat/disconnect cleanup | Source-reviewed; reconnect/load pending | +| State-changing admin APIs (including cache/config/preferences) | Administration listener | Valid session, Origin/Host, `X-RustProxy-Request: 1`, CSRF token; audit on cache commands | Source and regression tests added; runtime pending | +| Certificate API (`/api/certificates/{id}/reload`, deploy) | Administration listener | Narrow-scoped Key ID + Bearer grant; no general read access; HTTPS required remotely; origin validation when supplied | Source-reviewed; token rotation/revocation live tests pending | +| Public `BAN`/`PURGE` | Configured public vhost listener | **No admin session**: separate explicit IP/CIDR allowlist (empty by default), matched listener/vhost/ACL and optional rate limit | Hardened below; reverse/HTTP tests pending | +| Separate Prometheus metrics exporter | Distinct optional listener | Configured IP CIDRs and bearer-token policy; not the admin session | Unchanged; deployment exposure audit pending | + +## Findings addressed in this follow-up + +1. **E0716 / compilation defect (high functional impact).** `error()` accepts + `&'static str`; a reference to `format!(...)` was a temporary. Invalid + selector errors now log the detailed reason server-side and return a fixed, + non-sensitive message with status 422. This removes the reported lifetime + violation; the Rust compiler must still verify the entire crate. +2. **Cache protocol-scope confusion (medium).** Path-only `BAN`/`PURGE` + selectors could affect representations of the same host/path across both + HTTP and HTTPS. Targets are now built with the trusted external scheme, + validated to `http`/`https`, and the matched Host/vhost. IPv6 literal Hosts + are bracketed for the URL parser. Other tenants, schemes and hosts remain + outside the selector. +3. **Unbounded blocking maintenance work (medium availability risk).** Cache + invalidation may scan hot/cold entries. Both public HTTP and authenticated + admin cache purge/selective invalidation share two maintenance slots and + execute scans with `spawn_blocking`. Bursts receive 429 instead of piling + up unbounded blocking work on Tokio core threads. The permit stays with the + worker after a client disconnects. This is a concurrency bound, **not** a + per-client rate quota or a hard time limit for disk work. +4. **Ambiguous maintenance input (medium).** Duplicate TTL/confirmation or + `Content-Length` headers, transfer-encoded bodies, nonempty bodies and + upgrade requests are rejected; `Content-Length: 0` remains compatible with + CLI clients. TTL is limited to 1..604800 s. Broad `BAN /` requires the + explicit confirmation header; `PURGE` cannot use BAN-only controls. +5. **Audit ordering (low/medium).** Removal of an administrator-created BAN + rule now records the audit entry before mutating state. Unavailable audit + storage blocks the mutation rather than allowing an unaudited removal. +6. **Operator safety/usability (low).** An EN/PL help popover beside the HTTP + allowlist gives exact `curl` syntax, IP-only authentication semantics and + reverse-proxy identity requirements. No new global config variable is added. + +## Existing protections checked in source + +- Admin router's exact anonymous method/path allowlist contains only the + expected authentication bootstrap APIs; private docs and assets remain + session-protected. Request guard rejects duplicate Host/Origin and checks + browser `Sec-Fetch-Site` cross-site signals. +- Mutation guard uses an Origin/Host match, per-session CSRF comparison and + `X-RustProxy-Request` marker; cookie sessions are HttpOnly/SameSite=Strict + and use Secure under HTTPS/trusted forwarded protocol. +- Secure response policy includes CSP, `X-Frame-Options: DENY`, + `X-Content-Type-Options: nosniff`, referrer restrictions and no-store for + private material. A static scan checked 27 first-party browser JS files for + direct unsafe HTML injection and dynamic `eval`/`Function` sinks; this scan + **cannot establish that the entire DOM is XSS-free**. +- IP trust comes from the transport peer or a validated forwarding chain only + when the peer is configured trusted. An untrusted client-supplied XFF does + not independently grant BAN/PURGE permission. The cache allowlist rejects + `/0`, invalid CIDRs, duplicates and more than 32 entries. Requests are never + forwarded upstream. +- Certificate API tokens have explicit operation/ID grants and cannot use the + general admin read API; Bearer grant operations are intentionally separate + from cookie + CSRF authentication. + +## HTTP maintenance CLI (allowlisted client only) + +```sh +# One URL, including the query string: 200 with JSON result. +curl -i -X PURGE 'https://site.example/assets/app.js?v=3' + +# Lazily ban previously cached objects under this path: 202 with JSON result. +curl -i -X BAN 'https://site.example/assets/' + +# Entire host: explicit opt-in confirmation header. +curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1' + +# Optional BAN expiration (30 seconds). +curl -i -X BAN 'https://site.example/assets/' -H 'X-RustProxy-Ban-Ttl: 30' +``` + +Use **Settings -> Cache -> HTTP BAN / PURGE IP allowlist** to grant the +caller IP/CIDR. This HTTP interface does not accept admin cookies/tokens as an +alternative to the allowlist. The domain, Host header, external scheme and +listener must select the intended vhost. Behind a reverse proxy, forward the +non-standard methods untouched and **replace**, rather than append untrusted, +XFF/Forwarded headers at the edge. Never add the entire reverse-proxy subnet +as a maintenance client merely because it is trusted for forwarding. + +## Release security test plan (requires compiled RustProxy) + +1. On the deployment host, run `cargo fmt --all -- --check`, + `./dev.sh check`, `./dev.sh test`, and `./dev.sh release`. Address any + compiler/regression errors before installation. `cargo audit` or an + equivalent advisory scan requires an up-to-date advisory DB and was not run + in this offline environment. +2. Start a disposable instance with a populated cache under both HTTP and + HTTPS for the same vhost/URL. Confirm `PURGE` returns 200 and drops the + exact query variant only; `BAN` returns 202 and invalidates only **older** + entries on the URL prefix. Confirm the *other* scheme and vhost stay hot. +3. From a denied IP, expect 403 on `BAN` and `PURGE`. With an empty allowlist, + all clients must be denied. Send duplicate TTL/confirmation/Content-Length, + TE/body and `BAN /` without confirmation: expect 400. On a trusted-proxy + hop with missing real client identity, expect 403. Test both IPv4 and IPv6. +4. From an allowed IP, send 10 concurrent broad maintenance requests; confirm + that only two workers run simultaneously and excess return 429. Verify + server CPU/IO and audit entries while the cold tier is populated. +5. Send GET requests without admin cookies to `/api/config`, `/api/status`, + `/api/system-info`, `/api/events`, `/api/logs`, `/docs/openapi.json` and + `/assets/app.js`; expect 401 and protective headers. With valid cookie but + missing/bad Origin, marker or CSRF on PUT/POST/DELETE, expect 403. Test + logout and password/session invalidation, live SSE reconnect/expiry. +6. Test the Certificate API with no grant, a valid certificate-specific grant, + revoked token, HTTP remote peer, and misuse against `/api/config`/logs. + Check errors for token disclosure and audit coverage. +7. Test actual browser startup, CSP console, output escaping and form save + with malicious HTML strings; inspect reverse-proxy Host/Origin forwarding. + Prefer HTTPS administration with a strict `allowed_hosts` list. + +## Residual risk / non-claims + +- **Not executed in this environment:** Cargo format/check/tests/release, + actual Rust listener requests, HTTP/2/3, fuzzing, penetration tests, + session revocation under load, proxy deployment configuration, dependency + CVE scanning and long-running cold-cache resource measurements. +- Proxy trust configuration is security-critical. A misconfigured edge that + accepts forged forwarded client addresses can let external users impersonate + an allowlisted IP. Restrict direct listener reachability and sanitize headers. +- A public IP allowlist is not user authentication; anyone using an allowed + IP/network can issue destructive maintenance commands. Use narrow CIDRs and + optionally enforce additional controls on the edge (mTLS, firewall). +- The two-worker concurrency bound mitigates bursts, not all denial-of-service + scenarios: configured cache size and storage latency still determine scan + cost. Per-IP quotas are not provided here. +- Proxy protocol path, certificate verification, arbitrary upstream code, + all application content and third-party packages are **not certified safe** + by this audit. Production acceptance remains open until the runtime test + plan is completed. diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 6a766d5..2f64eea 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -12,9 +12,25 @@ The Settings page includes a local search field that filters category tabs using Unfinished Web Server, Vhost, Redirect and Stream forms use a session-only draft dock. Closing a dirty editor minimizes its exact browser state to the bottom-right dock so another editor can be opened independently. The dock follows the square-corner panel styling of the dashboard and intentionally avoids rounded/glass presentation. Drafts are intentionally not written to SQLite because form state can contain credentials or other secrets; they are discarded on logout/session reset. +## Refresh preferences (0.31.0) + +**Settings -> Appearance -> Data refresh** saves an additional `refresh` object in the existing `admin_preferences` document for the signed-in account. It does not modify proxy configuration or create any migration. Older documents, including partially populated `refresh` objects, use defaults. Browser and server enforce exact allowed values: + +| Preference | Default | Allowed seconds | +|---|---:|---| +| `refresh.live_enabled` | `true` | `true`, `false` | +| `refresh.overview_seconds` | 2 | 1, 2, 5, 10 | +| `refresh.traffic_seconds` | 5 | 0, 3, 5, 10, 30 | +| `refresh.cache_seconds` | 5 | 0, 3, 5, 10, 30 | +| `refresh.system_info_seconds` | 60 | 0, 15, 30, 60, 300 | + +`live_enabled=false` disables automatic SSE **and all polling**, including system information. `0` disables only that source's automatic polling. Overview and System information have **Refresh now** buttons; Traffic and Cache retain their own buttons, filters, charts and pagination. The per-view Auto checkboxes for Traffic/Cache/Transform and the Docker live switch remain independent opt-outs, and none changes another administrator's preferences. **Reset section** restores refresh defaults without changing theme or unrelated preferences. + +The main `/api/events?interval=N` connection (allowlisted 1/2/5/10, default 2 seconds) carries compact live status plus the existing bounded log snapshot. The server checks the session on every event and retains its heartbeat, viewer cap and normal disconnect cleanup. On an SSE error the browser polls `/api/status?view=live` and `/api/logs` until snapshots resume, not concurrently during healthy streaming. The old full `/api/status` response is kept for API clients and initial diagnostics. The separate authenticated `/api/system-info` reuses cached diagnostics and ACME metadata, never runs diagnostic subprocesses per request, and is fetched automatically only for Overview or the relevant Settings pages. A hidden browser tab pauses the channel and all automatic polling; returning revalidates its session before normal operation. All interval changes take effect immediately, without reloading or changing any server-global cadence. + ## System information (0.30.1) -**Settings -> System information** (`/settings?tab=system-info`) is a read-only view of the authenticated `/api/status` endpoint. It reports the OS name/kernel on Linux when available, executable architecture, logical CPU count, CPU and RAM telemetry, host/process uptime, RustProxy version, process RSS, thread and file-descriptor counts, and the existing statistics source and data/database locations. Values not supported by the host/platform display as unavailable, never as zero. +**Settings -> System information** (`/settings?tab=system-info`) is a read-only view of the authenticated `/api/system-info` endpoint (and the initial, backwards-compatible full `/api/status` response). It reports the OS name/kernel on Linux when available, executable architecture, logical CPU count, CPU and RAM telemetry, host/process uptime, RustProxy version, process RSS, thread and file-descriptor counts, and the existing statistics source and data/database locations. Values not supported by the host/platform display as unavailable, never as zero. ACME diagnostics formerly shown in About and under Administration are now on this tab: mode/state, certificate environment path, and the active Python/pip/Certbot versions. Versions are probed by the ACME runtime setup with bounded subprocess output/timeouts and published from stored runtime status. They are not queried at every page refresh or status API request. Failed/unavailable probes leave the version empty. No new persistent settings or permissions are introduced. @@ -98,11 +114,34 @@ accepted only when `administration.tls` is true. Plain remote HTTP remains avail "tls": true, "certificate_id": "admin-example", "allowed_hosts": ["admin.example.com"], - "session_days": 30 + "session_days": 30, + "panel_compression": {"zstd": true, "br": true, "gzip": true} } } ``` +**Administration → Panel content compression** (next to the admin listener/TLS settings) +controls Zstd, Brotli and gzip for the authenticated HTML document, JavaScript, +CSS and language bundles. Each browser request is negotiated using +`Accept-Encoding` quality values: the highest positive `q` wins, and ties +prefer **Zstd → Brotli → gzip**. Codecs can be enabled independently; the +identity representation is returned when none is acceptable or all are off. +The source is encoded only once per embedded asset and shared across visitors. +Settings persist in the existing revisioned administration document, without +additional CLI flags or a separate environment/config variable, and take effect +without restart. Each compressed response includes `Content-Encoding`, +`Vary: Accept-Encoding` and the representation-specific `Content-Length`. +The response compression of proxied websites remains independent; API responses, +SSE and WebSocket streams are not affected. + +**Reverse proxy:** an upstream may remove or replace `Accept-Encoding`. RustProxy +negotiates from the header it actually receives, not the original browser request. +A reverse proxy normally passes through already encoded responses instead of +recompressing them; if the reverse should exclusively manage encoding, configure +it to strip `Accept-Encoding` on requests upstream, or turn the panel codecs off. +This provides compatible HTTP negotiation but does not guarantee identical +compression with every reverse proxy configuration. + `certificate_id: null` uses `data/certs/default.pem` and `default.key`, generated by the application. A selected inventory certificate is loaded from the same private certificate directory. `session_days` controls the absolute browser-session lifetime @@ -169,8 +208,13 @@ Version 0.22 adds foreground fill collapsing (`collapse_enabled`, (`proactive_refresh_enabled`, `proactive_refresh_before_expiry_seconds`, `proactive_refresh_percent`, `proactive_refresh_max_inflight`). Defaults preserve the previous stale-if-error behavior; proactive refresh is disabled by default. Selective -PURGE/BAN is an operational API/panel action rather than persisted configuration. See +PURGE/BAN is available in the authenticated API/panel, and HTTP BAN/PURGE has an optional, separately stored CIDR allowlist (`cache.http_invalidation_allowed_cidrs`, empty by default). See [CACHE.md](CACHE.md) for request-collapse, Range and invalidation semantics. +The adjacent **info (`i`) popover** in Settings → Cache shows ready-to-copy +`curl -X PURGE` and `curl -X BAN` examples in English and Polish, including +the explicit `BAN /` confirmation. These public methods authorize by the +source IP/CIDR only; they do not accept a browser admin session as a bypass. +See [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) for reverse-proxy trust and tests. ## Static-response transformation diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md index 7cffed7..81c0feb 100644 --- a/docs/TEST_REPORT.md +++ b/docs/TEST_REPORT.md @@ -1,4 +1,44 @@ -# Verification report - RustProxy 0.30.1 +# Verification report - RustProxy 0.31.0 + +## 0.31.0 Axum handler `Send` correction (2026-10-10) + +- **Reported failure:** `error[E0277]` at `src/proxy/types.rs:53`, `.fallback(forward)`, on Axum 0.8.9. `forward` calls asynchronous public BAN/PURGE handling. That helper accepted `&Request` across `.await`, which requires a `Sync` body to produce the `Send` future that `Handler` expects. +- **Source correction:** move `Request` into `cache_http_invalidation`, validate it synchronously, copy the method for audit logging and explicitly drop the body before the first `.await`. No cache selectors, IP permissions, status codes, API routes or session flows were changed. Strengthen `tools/checks/admin_surface_audit_0310.py` to reject a reintroduced borrowed request in the async handler. +- **Executed:** `./dev.sh test` — all pre-Rust checks **PASS** (source/security/SQLite, UI Node, docs, language packs); command terminates **127** when Cargo is required. `./dev.sh check` — **127**, Cargo absent. `cd docker-agent && go test ./...` — **PASS**. `bash -n dev.sh`, Python source compile and manifest version probe — **PASS** (`0.31.0`). +- **Not executed:** Rust `cargo check --locked --all-targets`, `cargo test --locked --all-targets`, `cargo build --locked --release`, rustfmt or live HTTP regression. No `cargo`/`rustc` or download access is available in this environment. This source correction is not claimed as a compiler-verified release. + +## 0.31.0 BAN/PURGE compilation fix, security review and UI help (2026-10-10) + +- Addressed reported Rust E0716 (`format!` temporary borrowed as `&'static str`) by returning a fixed 422 response and logging the specific rejection. +- Restricted public HTTP maintenance to one external scheme and matched Host/vhost, rejected duplicate controls and invalid bodies, and added a shared two-slot limit plus `spawn_blocking` for heavy HTTP/admin cache invalidation work; audit removal before BAN-rule deletion. +- Added localized EN/PL `curl` help popover beside the Cache allowlist; added protocol/host/scheme/headers Rust regression sources, admin anonymous/CSRF test sources and an offline source audit script. The detailed findings, attack-surface table and operator acceptance checklist are in [SECURITY_AUDIT_0.31.0.md](SECURITY_AUDIT_0.31.0.md). +- **Executed this follow-up:** `./dev.sh test` passed every pre-Rust static/SQLite/security/JS/docs/translation check, then exited **127** on unavailable Cargo. `./dev.sh check` also exited **127**. `go test ./...` under `docker-agent/`, syntax checking all 27 browser JS files, OpenAPI JSON parse, shell syntax and separate source audits passed. A separate Chromium screenshot attempt timed out and does **not** count as verified UI rendering. +- **Rust compiler and runtime tests NOT RUN HERE**: `cargo`, `rustc` and `rustfmt` are absent; the only safe conclusion about E0716 is that its offending lifetime pattern was removed in source. Live BAN/PURGE over H1/H2/H3, TLS, reverse trust, performance and regression gates remain open. + +## 0.31.0 negotiated panel content compression and HTTP BAN/PURGE follow-up (2026-10-10) + +- `administration.panel_compression` defaults to `{zstd:true,br:true,gzip:true}` and uses the **existing Administration form/config revisions**. Authenticated panel HTML and embedded `/assets/` CSS/JS/language bundles are precompressed only once per resource; per-request browser `Accept-Encoding`/`q` selects zstd, br, gzip or identity. The public login HTML remains identity (static login assets can negotiate compression). `Vary`, `Content-Encoding`, `Content-Length` are set per response. Compression for public proxied responses is unchanged. An upstream reverse proxy can override `Accept-Encoding` and therefore the negotiated result. +- Empty-by-default `cache.http_invalidation_allowed_cidrs` restricts public-vhost `PURGE /path?query` exact removal and `BAN /prefix` lazy invalidation to allowed client IPv4/IPv6 ranges; `/0` rejected. Existing vhost/host ACL, rate limit and trusted forwarding logic remain in force. `BAN /` requires explicit confirmation; operation is audited, uses `spawn_blocking` around the existing cache engine and is never sent upstream. CIDR changes do not rotate cached objects. +- System information Refresh button is next to the API badge, without another row. EN/PL locales, OpenAPI and settings/cache/API docs were updated; SQLite schema remains 9 and Cold format remains 3. +- **PASS:** `./dev.sh test` completed all pre-Rust checks (SQLite migrations, release smoke, security, translations, Node UI, source contracts) then exited **127** because `cargo`/`rustc` are missing. `./dev.sh check` likewise exits **127**. Standalone `go test ./...` in `docker-agent/`, JavaScript syntax checks, OpenAPI JSON parse and Node form roundtrip passed. Chromium headless verified that API badge and System information Refresh button remain on the same row at viewport widths 320, 390, 1024 px. +- **Source-byte measurements (not wire captures)**: authenticated HTML after version substitution **320,756 B**; Zstd level 3 around **50,526 B**, Brotli level 5 around **43,654 B**, gzip level 6 around **47,343 B** using local utilities. The actual wire choice follows browser `q` values, not necessarily the smallest encoded representation. FCP/LCP and mobile real-browser startup were not measured on a running RustProxy. +- **Rust NOT RUN:** no Cargo/Rustc; Rust codec decoding, actual vhost BAN/PURGE authorization/HTTP/1-H2-H3, live proxy integration and exact wire metrics require a Rust-capable host. Production acceptance remains open. + +## 0.31.0 mobile startup / HTML payload follow-up (2026-10-10) + +- Removed `html.app-loading body { visibility: hidden }`. An initial CSS-driven overlay shows a loading spinner before the application code runs, a delayed (12 s) retry link when scripts or requests stall, and an explicit localized EN/PL failure state. No inline scripts or styles were introduced; authenticated CSP and the public login document are preserved. Reduced-motion preferences disable the spinner animation. +- Dashboard shell and live SSE start once the authenticated session and language bundle are resolved, **without blocking on** preferences, configuration, certificate inventory or initial `/api/status`. Unavailable configuration shows a visible toast and does not blank the page. This improves perceived startup but is not a measured FCP improvement. +- Moved the Overview **Refresh now** button into the title row, with no additional top-level row; the button remains hidden outside Overview. +- Private HTML is gzip-compressed and cached once in memory (no per-request recompression); `Accept-Encoding: gzip` yields `Content-Encoding: gzip`, `Vary: Accept-Encoding` and compressed `Content-Length`. Without gzip or with `gzip;q=0`, the existing identity HTML remains. Public login is not compressed/altered by this handler; API, SSE and proxy paths are not changed. +- **Measured from source bytes:** HTML source **318,905 B**, authenticated response after substituting two version placeholders **318,875 B** (previous packaged authenticated response **317,723 B**); Python gzip level 6 of the response **46,821 B** (about **85.3% less HTML transfer**). These are source-based estimates, **not HTTP wire captures from running RustProxy**, so browser-delivered Content-Length remains to be confirmed with a compiled server. +- **PASS:** deterministic Node startup/placement test, Chromium 320/390/1024 px rendering and title/button alignment, existing static checks and Node coordinator regressions. **NOT RUN:** Rust compilation or Rust tests (`cargo`/`rustc` unavailable), live HTTP gzip/identity/auth, real mobile hardware reproduction and field measurements (FCP/LCP/white-screen rate). Acceptance remains open. + +## 0.31.0 dashboard refresh implementation and source verification (2026-10-10) + +- Source delivery based on the supplied `RustProxy-0.30.1.zip` (no `.git`; remote `master` not verifiable). Implemented one per-browser scheduler, per-account/validated preferences, per-client SSE interval and fallback isolation, active-view Traffic/Cache, separated `/api/system-info`, compatible full `/api/status`, version/docs and English/Polish UI. +- **PASS:** source/version/module/security checks, SQLite schema-9/upgrade smoke, translations/docs, all bundled Node UI tests including deterministic refresh scheduler, Go agent unit tests, all admin JS syntax. Browser simulation checks 20 view transitions, no duplicated timers/in-flight requests, 0/manual, hidden tab, SSE failure/recovery, independent errors and logout. +- **NOT RUN:** Rust compilation, Rust tests and release build because `cargo`/`rustc`/`rustfmt` are missing (`./dev.sh test`/`check`/`fmt`/`release` exit 127 at toolchain gate). Real RustProxy API/SSE/traffic/regression tests and SSE bandwidth/CPU measurement remain unverified. The stress suite's Python HTTP test peer is **not** a RustProxy runtime test. +- See [full 0.31.0 implementation and verification report](IMPLEMENTATION_0.31.0.md). No database schema changes, git commit or tag. ## 0.30.1 sysinfo compiler-feature fix (2026-10-10) diff --git a/langs/en_US.json b/langs/en_US.json index 617752c..fe639d7 100644 --- a/langs/en_US.json +++ b/langs/en_US.json @@ -1,2309 +1,2335 @@ { - "meta": { - "locale": "en-US", - "name": "English", - "native_name": "English", - "direction": "ltr" - }, - "translations": { - "overview": "Overview", - "mainMenu": "Menu", - "routing": "Legacy routes", - "configuration": "Configuration", - "manualConfig": "Manual config", - "runtime": "RUNTIME", - "localOnly": "ADMINISTRATION", - "logout": "Sign out", - "firstSlice": "INDEPENDENT CORE / EXPLICIT ACCESS", - "loginTitle": "Administration", - "unlock": "Unlock administration", - "tokenHelp": "Run this command in a second terminal and paste the token below.", - "token": "Local access token", - "connect": "Connect to proxy", - "tokenNote": "Administration defaults to loopback HTTP. Enable Administration HTTPS before using a non-loopback bind.", - "requests": "Requests", - "activeExchanges": "Active exchanges", - "bodyLifetime": "Full body lifetime", - "bandwidth": "Payload transferred", - "bothDirections": "Both directions", - "errors": "Errors", - "errorDescription": "5xx / body / cancelled", - "liveTraffic": "LIVE TRAFFIC", - "liveTransformed": "Transformed", - "requestsPerSecond": "Requests per second", - "live": "LIVE", - "lastMinute": "Recent samples / resets on reload", - "coreState": "CORE STATE", - "configurationRevision": "Applied revision", - "backends": "Backends", - "routes": "Routes", - "connections": "Open connections", - "uptime": "Uptime", - "activity": "ACTIVITY", - "accessLog": "Access log", - "boundedLog": "Last 50 / retained ring 4096", - "time": "Time", - "request": "Request", - "status": "Status", - "duration": "Duration", - "outcome": "Outcome", - "noTraffic": "No traffic yet. Open the proxy to make your first request.", - "reload": "Reload configuration", - "addBackend": "Add / edit backend", - "backendUrl": "Backend origin (HTTP / HTTPS, IP / DNS)", - "allowPrivate": "Explicitly allow private / loopback IP", - "preserveHost": "Preserve original Host header", - "saveBackend": "Save backend", - "clear": "Clear", - "addRoute": "Add / edit route", - "hostnames": "Hostnames and aliases, comma-separated", - "pathPrefix": "Path prefix (not stripped)", - "backend": "Backend", - "enabled": "Enabled", - "saveRoute": "Save route", - "rawConfig": "Configuration document", - "export": "Export JSON", - "configHelp": "A valid save is applied atomically. Invalid or stale changes leave the running configuration untouched.", - "apply": "Validate, save and apply", - "noRestart": "Routing and response policies apply live; listener changes require restart.", - "roadmapHelp": "Release details and planned features are documented in ROADMAP.md.", - "overviewTitle": "Traffic, in focus.", - "overviewDescription": "Traffic, cache and operating-system measurements in one compact dashboard.", - "routingTitle": "Connect the right dots.", - "routingDescription": "Existing backend/route format remains supported. New deployments can use one vhost with its own frontend and pool.", - "configurationTitle": "One source of truth.", - "configurationDescription": "One canonical configuration: vhosts, listeners and shared policies.", - "connected": "CONNECTED TO LOCAL CORE", - "disconnected": "CORE NOT REACHABLE", - "edit": "Edit", - "remove": "Delete", - "privateAllowed": "PRIVATE IP APPROVED", - "configured": "CONFIGURED / NOT PROBED", - "disabled": "DISABLED", - "emptyBackends": "No backends configured. Add an explicit HTTP or HTTPS origin.", - "emptyRoutes": "No routes configured. Unknown hosts return 404.", - "saving": "Saving…", - "unsavedChanges": "Unsaved changes", - "saved": "Configuration applied.", - "confirmDelete": "Delete this item? The change takes effect immediately.", - "confirmTitle": "Confirm action", - "confirmationLabel": "CONFIRMATION", - "warningLabel": "WARNING", - "continueAction": "Continue", - "cancelAction": "Cancel", - "discardAction": "Discard changes", - "errorContext": "{area}: {message}", - "stale": "Configuration changed elsewhere. Reload before saving.", - "busy": "A save is already in progress.", - "loginError": "Sign-in failed.", - "fresh": "Configuration reloaded.", - "locked": "Signed out. The session was revoked.", - "backendUsed": "This backend is referenced by a route. Delete or change the route first.", - "renaming": "IDs cannot be renamed in this form. Use the JSON editor to update references atomically.", - "complete": "complete", - "cancelled": "cancelled", - "body_error": "body error", - "proxy_or_upstream_error": "proxy/upstream error", - "security": "Security", - "sessionNote": "Local HTTP administration. Passwords use Argon2id and session cookies are HttpOnly. Do not expose the panel publicly.", - "setupHelp": "Create the first administrator using a one-time capability from the local terminal.", - "setupToken": "One-time setup token", - "username": "Username", - "password": "Password", - "confirmPassword": "Confirm password", - "setupTitle": "Create your administrator", - "signIn": "Sign in", - "createAndSignIn": "Create account and sign in", - "securityTitle": "Access stays deliberate.", - "securityDescription": "One administrator. Revocable sessions. Explicit host and route access policies.", - "manualBackends": "MANUAL BACKENDS", - "routePolicies": "Access policy and custom headers", - "useRouteAcl": "Enable route ACL", - "aclDefault": "Default action", - "allow": "Allow", - "deny": "Deny", - "aclRules": "One rule per line: allow/deny followed by CIDR", - "aclHelp": "Use IPv4/IPv6 addresses or CIDRs, e.g. 192.168.1.0/24 or 10.0.0.5. Deny wins. With an allow-only country policy, an allowed country OR an allowed IP can pass this scope; with a block-country policy, a blocked country OR denied IP is rejected. Authentication still applies.", - "routeHeaders": "Request / response headers (JSON)", - "headersHelp": "Operations: set, append, remove. Transport, body encoding and client-identity headers are protected.", - "currentSession": "CURRENT SESSION", - "recoveryHelp": "Lost access? Run rust-proxy admin reset-password locally. Existing sessions will be revoked.", - "revokeAll": "Sign out all sessions", - "changePassword": "Change password", - "currentPassword": "Current password", - "newPassword": "New password (12-128 UTF-8 bytes)", - "savePassword": "Change and sign out", - "hostPolicies": "Host access policies", - "editHostPolicy": "Add / replace host policy", - "savePolicy": "Apply host policy", - "auditRetention": "SQLITE / LAST 100 / RETAINED 10,000", - "audit": "Administration audit", - "reloadAudit": "Reload", - "actor": "Actor", - "action": "Action", - "detail": "Detail", - "noAudit": "No audit entries.", - "noPolicies": "No host ACLs. Route ACLs may still restrict access.", - "expires": "Absolute session expiry: ", - "passwordChanged": "Password changed. All sessions were revoked. Sign in again.", - "passwordMismatch": "The passwords do not match.", - "ruleSyntax": "Use one rule per line, for example: allow 127.0.0.1/32", - "confirmRevoke": "Sign out every administrator session, including this one?", - "streamLive": "SSE live", - "streamFallback": "Polling fallback", - "auth.required": "Your session expired or was revoked. Sign in again.", - "auth.invalid_credentials": "Incorrect username or password.", - "auth.password_policy": "The password must contain 12-128 UTF-8 bytes and no line breaks.", - "auth.invalid_username": "Use 3-64 letters, digits, dots, dashes or underscores.", - "auth.invalid_setup_token": "The setup token is invalid. Read it from the local terminal.", - "auth.already_initialized": "An administrator already exists. Sign in.", - "auth.rate_limited": "Too many attempts. Retry after 60 seconds or use local password recovery.", - "auth.busy": "Password workers are busy. Retry shortly.", - "auth.csrf": "Request protection failed. Reload the page and retry.", - "auth.invalid_host": "Use the configured administration address and allowed hostname.", - "auth.invalid_origin": "Cross-origin administration is blocked.", - "auth.credentials_changed": "Credentials changed concurrently. Sign in again.", - "auth.unavailable": "Administration storage is temporarily unavailable.", - "events.too_many_viewers": "Live viewer limit reached; polling is available.", - "protocol": "Upstream protocol", - "protocolHelp": "HTTP/2 and auto use the backend authority. Preserve Host is available with HTTP/1.1 only. RFC 8441 can use an explicit HTTP/2 upstream; other WebSocket transports bridge through HTTP/1.1.", - "addressAllowlist": "Optional address allowlist (CIDR, one per line)", - "caPem": "Additional trusted CA / certificate (PEM, optional)", - "backendTlsHelp": "TLS encryption remains available, but certificate/hostname verification is off unless enabled explicitly. Private addresses still need explicit approval, including DNS answers.", - "advancedTransport": "Transport and trust", - "tls": "Certificates", - "tlsTitle": "Encrypted, deliberately.", - "tlsDescription": "Exact SNI names. Private local certificate files. Apply bindings without restarting.", - "certificateBindings": "Certificate bindings", - "addCertificate": "Add certificate", - "certificateId": "Certificate ID", - "certificateFile": "Certificate filename / path", - "privateKeyFile": "Private key filename / path", - "defaultCertificate": "Use as the default certificate", - "saveCertificate": "Apply certificate", - "reloadCertificates": "Reload certificate files", - "certificateHelp": "Use RustProxy-managed files, absolute external paths, uploaded PEM pairs, or ACME. Managed filenames are loaded from /certs. External paths are read in place and can be reloaded after an outside renewal without changing the configuration revision.", - "certificateHint": "External renewal: keep fullchain.pem/privkey.pem paths in the certificate entry, renew them with Certbot/acme.sh, then use Reload certificate or POST /api/certificates/{certificate_id}/reload.", - "emptyCertificates": "No explicit SNI bindings. The generated default certificate is used while the TLS listener is enabled.", - "certificateSafety": "Certificates secure TLS connections, not routing. Assign a certificate to a vhost. Changes affect new handshakes.", - "wsActive": "WebSocket tunnels", - "h2Requests": "HTTP/2 requests", - "h2ActiveConnections": "Active H2 connections", - "tlsFailures": "TLS handshake errors", - "tlsListener": "TLS listener", - "notEnabled": "Disabled", - "websocket_closed": "WebSocket closed", - "defaultLabel": "DEFAULT", - "language": "Language", - "settings": "Settings", - "settingsTitle": "Configure your edge.", - "settingsDescription": "Switch between function tabs. Unsaved changes stay in their forms.", - "cacheHitRatio": "Cache hit ratio", - "processMemory": "Process memory", - "systemLoad": "SYSTEM LOAD", - "systemChart": "Recent system CPU usage", - "systemMemory": "RAM used / total", - "systemSwap": "System swap", - "systemUptime": "System uptime", - "loadAverage": "Load 1 / 5 / 15 min", - "cacheOverview": "RAM cache", - "cacheObjects": "Objects / memory", - "cacheEvictions": "Evicted / expired", - "processDiagnostics": "Process", - "platform": "Platform", - "processCpu": "CPU (100% = 1 core)", - "fileDescriptors": "File descriptors / limit", - "processThreads": "OS threads", - "processVirtualMemory": "Virtual memory / limit", - "processStartTime": "Process start", - "containerMemory": "cgroup RAM / limit", - "networkDiagnostics": "Network and responses", - "hostScope": "OS scope", - "networkRate": "RX / TX per second", - "meanLatency": "Mean exchange duration", - "filterLogs": "Filter host or path", - "filterResult": "Filter result", - "allResults": "All results", - "routeCache": "Route cache policy", - "inheritCache": "Inherit global policy", - "bypassCache": "Always bypass", - "listeners": "Listeners", - "httpConnections": "HTTP connections", - "httpConnectionsHelp": "Connection lifecycle and transport limits for HTTP/2 and HTTP/3.", - "http2Tab": "HTTP/2", - "http3Tab": "HTTP/3", - "http2Settings": "HTTP/2 connections", - "http2SettingsHelp": "HTTP/2 enablement is controlled per listener. Tune idle, maximum lifetime and graceful GOAWAY drain limits here.", - "http2IdleTimeout": "Idle timeout (seconds)", - "http2MaxLifetime": "Maximum connection lifetime (seconds, 0 = disabled)", - "http2DrainTimeout": "Drain timeout (seconds)", - "http2DrainHelp": "These limits apply to this listener. Idle timeout applies only to unused connections; maximum lifetime is absolute; drain bounds active streams after GOAWAY. Changes require restart.", - "http2Save": "Save HTTP/2 settings", - "http2NoEnabledListeners": "No listener has HTTP/2 enabled. Enable HTTP/2 or h2c in Settings > Listeners first.", - "http2ListenersDirty": "Save pending listener changes before editing HTTP/2 settings.", - "restartLabel": "RESTART", - "listenersHelp": "Named IP and port bindings. Changes require restart. The administration endpoint remains separate.", - "httpAddress": "HTTP bind address and port", - "httpsAddress": "HTTPS bind address and port", - "enableHttps": "Enable HTTPS listener", - "lowPortsHelp": "Privileged ports may need CAP_NET_BIND_SERVICE. Do not run the whole application as root.", - "saveListeners": "Save listeners", - "inheritListeners": "Use startup defaults", - "responseSettings": "Client response", - "hotApply": "LIVE APPLY", - "serverHeader": "Server header", - "serverPreserve": "Preserve origin value", - "serverHide": "Hide completely", - "serverCustom": "Replace / add custom value", - "serverValue": "Custom Server value", - "compression": "Streaming compression", - "compressionMin": "Minimum response size (bytes)", - "compressionHelp": "Negotiates Accept-Encoding using client q-values and zstd → br → gzip → deflate as the server preference for equal quality. Cookies, Authorization, Set-Cookie, private and no-store responses may be compressed. Existing Content-Encoding, ranges, SSE, upgrades and no-transform are passed through.", - "saveResponse": "Apply response settings", - "cacheSettings": "Cache", - "enableCache": "Enable cache", - "cacheSafety": "Complete public-safe GET responses are cached. Authorization, cookies and private/no-store responses still bypass. One byte range can be served from a complete cached object; multipart/unsupported ranges pass through.", - "cacheMemory": "HOT allocated budget (MiB)", - "cacheObject": "Maximum object (KiB)", - "cacheEntries": "Maximum entries", - "cacheMaxTtl": "Maximum TTL (seconds)", - "cacheDefaultTtl": "Fallback TTL for public responses (0 = off)", - "bypassQuery": "Bypass URLs with a query string", - "cachePrefixes": "Bypass path prefixes, one per line", - "cacheAgeHelp": "Age is sent on cache hits unless an explicit response-header rule removes or replaces it. Such Age rules are wire-only and do not change cache freshness. Counters remain available even when diagnostic headers are hidden. Only cache-affecting configuration changes invalidate incompatible objects; managed software updates hand hot objects to cold storage when the cold tier is enabled.", - "saveCache": "Apply cache policy", - "purgeCache": "Purge all cache", - "customHeaders": "Global custom headers", - "globalHeadersHelp": "Request rules go to the backend; response rules go to the client. Route rules run after global rules. Server policy takes final precedence.", - "responseHeaders": "Response to client", - "requestHeaders": "Request to backend", - "addHeader": "Add header rule", - "saveHeaders": "Apply header rules", - "unavailable": "N/A", - "threads": "threads", - "realSample": "Measured", - "noKnownLimit": "unknown / unlimited", - "unlimited": "unlimited", - "restartPending": "Saved listener changes are pending. Restart RustProxy to apply them.", - "activeListeners": "Currently active", - "headerLimit": "At most 32 rules per direction.", - "headerOperation": "Operation", - "header_set": "Set", - "header_append": "Append", - "header_remove": "Remove", - "headerName": "Header name", - "headerValue": "Header value", - "confirmInherit": "Restore listener defaults from startup flags and environment on the next restart?", - "confirmPurge": "Purge every cached object and cancel all pending cache fills?", - "cachePurged": "Cache purged; removed entries", - "logsDropped": "dropped", - "updateAvailable": "New version", - "vhosts": "Vhosts", - "vhostsTitle": "Virtual hosts & backends", - "vhostsDescription": "Manage hostnames, frontend bindings, backends and per-host policies.", - "vhHealthTitle": "Backends", - "vhManage": "Manage vhosts", - "vhHealthEmpty": "No backend diagnostics yet.", - "vhVhost": "Vhost", - "vhActive": "Active", - "vhRequests": "Requests", - "vhErrors": "Errors", - "vhLatency": "Latency (EWMA)", - "vhTraffic": "Traffic/s", - "vhClientGeneration": "Client gen.", - "vhGenerationRequests": "Gen. requests", - "vhGenerationAge": "Gen. age", - "vhClientRotations": "Rotations", - "vhIntro": "Each vhost owns its bound IPs, paths and backends. Unlisted addresses cannot reach it. Listener changes require restart.", - "vhList": "Virtual hosts", - "vhSearch": "Search vhosts", - "vhSearchPlaceholder": "Search hostname, vhost or backend", - "vhSearchEmpty": "No vhosts match this search.", - "vhNew": "New vhost", - "vhMigrate": "Migrate a legacy route group", - "vhImport": "Load into editor", - "vhMigrationHint": "Migration is saved atomically. Routes sharing one host and one backend are grouped; ambiguous host ownership is rejected.", - "vhEditor": "Vhost configuration", - "vhReset": "Discard changes", - "vhSafetyHint": "Private IP binding is not authentication. Keep firewall/ACL rules. HSTS preload is opt-in; affinity cookies are not login sessions.", - "vhId": "Vhost ID", - "vhHosts": "Hostnames", - "vhEnabled": "Enabled", - "vhFrontends": "Frontends / bound addresses", - "vhAddFrontend": "Add frontend", - "vhAddress": "IP:port (IPv6: [address]:port)", - "vhTls": "TLS enabled", - "vhHttp2": "Prefer HTTP/2 over TLS; retain HTTP/1.1", - "vhBackendPool": "Backends", - "vhAddBackend": "Add backend", - "vhMemberId": "Backend ID", - "vhUrl": "Origin URL", - "vhWeight": "Weight", - "vhBackup": "Backup only", - "vhDrain": "Drain new sessions", - "vhPrivate": "Allow private origin IP", - "vhPreserveHost": "Preserve client Host (HTTP/1.1 only)", - "vhProtocol": "Origin protocol", - "vhProtocolHttp1": "HTTP/1.1", - "vhProtocolAuto": "TLS ALPN: HTTP/2 + HTTP/1.1", - "vhProtocolHttp2": "HTTP/2 (TLS: with HTTP/1.1 fallback; plaintext: h2c)", - "vhAddressAllowlist": "Origin IP/CIDR allowlist (optional)", - "vhTlsServerName": "TLS server name / SNI (optional)", - "vhTlsVerify": "Verify backend certificate and hostname", - "vhTlsVerifyHelp": "Disabled when omitted. Enable it when RustProxy must validate the backend certificate chain and identity. If verification fails, set TLS server name to a DNS name present in the certificate and/or add the private CA PEM. Do not use an IP address as SNI.", - "vhCa": "Additional trusted CA (PEM, optional)", - "vhBalancing": "Balancing & connection reuse", - "vhAlgorithm": "Algorithm", - "vhRoundRobin": "Smooth weighted round-robin", - "vhLeastConnections": "Weighted least active exchanges", - "vhLeastTraffic": "Weighted least traffic (EWMA)", - "vhAdaptive": "Adaptive: activity + latency + traffic", - "vhIpHash": "Client IP hash", - "vhSlowStart": "Recovery slow-start (seconds)", - "vhRetryMax": "Failover retries", - "vhRetryConnect": "Retry connection failures", - "vhRetryTimeouts": "Retry response-header timeouts", - "vhRetryStatuses": "Retry HTTP statuses", - "vhRetryHelp": "Retries are limited to body-free GET, HEAD and OPTIONS requests, use a different healthy backend and never replay WebSocket or write requests.", - "vhKeepalive": "Reuse upstream connections (keepalive)", - "vhIdleTimeout": "Idle pool timeout (seconds)", - "vhMaxIdle": "Max idle connections / origin", - "vhTcpKeepalive": "TCP / HTTP/2 keepalive interval (seconds)", - "vhHealth": "Backend health", - "vhHealthMode": "Check type", - "vhOff": "Off", - "vhTcp": "TCP connect", - "vhHttp": "HTTP status + response time", - "vhHealthPath": "HTTP check path", - "vhHealthHost": "HTTP Host override (optional)", - "vhInterval": "Interval (ms)", - "vhTimeout": "Timeout (ms)", - "vhSlow": "Slow threshold (ms; 0 = off)", - "vhFall": "Failures before down", - "vhRise": "Successes before recovery", - "vhStatusMin": "Minimum HTTP status", - "vhStatusMax": "Maximum HTTP status", - "vhHealthNote": "HTTP probes use HTTP/1.1 and measure time to headers, not body duration. When Host override is blank, RustProxy automatically uses the first exact DNS hostname of the vhost; if none exists it falls back to the backend host. Use TCP for h2c-only services. New backends wait for successful probes. Active health is independent from passive health.", - "vhPassiveHealth": "Passive Backend Health", - "vhPassiveEnabled": "Allow normal request failures to mark a backend unhealthy", - "vhPassiveHelp": "Off by default. Errors are still logged and counted, but normal user traffic cannot change backend health until this is explicitly enabled.", - "vhPassiveConnect": "Count connection errors", - "vhPassiveTimeouts": "Count timeouts", - "vhPassiveIo": "Count body/read/write I/O errors", - "vhPassive5xx": "Count HTTP 5xx responses", - "vhPassiveSlow": "Count slow responses", - "vhPassiveFall": "Passive failures before down (empty = active fall)", - "vhPassiveRise": "Passive successes before recovery (empty = active rise)", - "vhPassiveWarning": "Enabling HTTP 5xx or slow-response failures can quarantine the whole backend after repeated errors from a single endpoint.", - "vhSticky": "Sticky sessions", - "vhStickyMode": "Affinity mode", - "vhCookie": "Opaque proxy cookie", - "vhCookieName": "Cookie name (empty = per-vhost default)", - "vhCookieTtl": "Cookie lifetime (seconds)", - "vhStickyNote": "Cookie affinity bypasses shared cache. Drain preserves existing cookie sessions. IP affinity uses the verified client IP under the configured trusted-proxy policy.", - "vhSecurity": "Security", - "vhHsts": "Enable HSTS (TLS responses only)", - "vhHstsAge": "HSTS max-age (seconds)", - "vhHstsSub": "HSTS includeSubDomains", - "vhHstsPreload": "HSTS preload (explicit opt-in)", - "vhNosniff": "X-Content-Type-Options: nosniff", - "vhFrame": "X-Frame-Options", - "vhPreserve": "Preserve origin value", - "vhDeny": "DENY", - "vhSameorigin": "SAMEORIGIN", - "vhReferrer": "Referrer-Policy (optional)", - "vhCsp": "Content-Security-Policy (optional)", - "vhPermissions": "Permissions-Policy (optional)", - "vhAdvanced": "Advanced: paths, ACL and custom headers", - "vhPaths": "Paths (JSON; optional backend_id pins a path to a single pool member)", - "vhAcl": "Vhost ACL (JSON or null)", - "vhHeaders": "Request / response header rules (JSON)", - "vhEmpty": "No vhosts yet. Existing routes continue to work.", - "vhUnsaved": "Discard unsaved vhost changes?", - "vhDelete": "Remove this vhost? Bound sockets are closed after restart.", - "vhConflict": "That vhost ID already exists.", - "vhRowLimit": "The maximum number of rows has been reached.", - "vhMigrationAmbiguous": "Hostnames overlap other legacy routes. Resolve ownership in JSON before migration.", - "vhMigrationPending": "Legacy migration will be committed with this save.", - "vhMigrationNone": "Select a legacy route", - "vhJsonInvalid": "Invalid JSON or data type in paths, ACL or headers.", - "vhUp": "Up", - "vhDown": "Down", - "vhChecking": "Checking", - "vhDraining": "Draining", - "vhDisabled": "Disabled", - "vhHealthDisabled": "Checks off", - "vhHealthUnknown": "Unknown", - "vhNewLabel": "NEW", - "vhEditLabel": "EDIT", - "vhFrontendNote": "Use an IP assigned to this machine. A wildcard accepts every IP in its address family; host routing still checks the local destination. TLS certificates are configured in the TLS tab.", - "close": "Close", - "inherit": "Inherit", - "liveLogs": "Live logs", - "logVhost": "Filter by vhost", - "logAllVhosts": "All vhosts", - "logAllMethods": "All methods", - "logPause": "Pause", - "logPaused": "PAUSED", - "logResume": "Resume", - "logPin": "Dock", - "logUndock": "Open as modal", - "logColumns": "Columns", - "logColumnsRequired": "At least one Live Logs column must remain visible.", - "logLoadHistory": "Increase buffer", - "logHistoryLoaded": "Live buffer increased to {count} retained entries.", - "logHost": "Host", - "logBufferHint": "Live view starts with the latest 256 completed exchanges. Increase buffer to seed it with retained history and keep accumulating live entries up to 4096 until the log window is closed. Export includes the currently filtered rows.", - "settingsSecurityHint": "Accounts and ACLs are global. SSL, HSTS, headers and cache are also configured inside each vhost.", - "theme": "Theme", - "themeDark": "Dark", - "themeLight": "Light", - "density": "Interface density", - "densityCompact": "Compact", - "densityDense": "Dense", - "densityComfortable": "Comfortable", - "globalPages": "Default responses", - "pagesHelp": "Pages for direct IP, unknown hosts and errors. An empty status selector preserves the original status. Static pages do not contact the upstream.", - "pageInherit": "Default / inherited", - "pageWelcome": "Congratulations page", - "pageEmpty": "Empty response", - "pageHtml": "Custom HTML", - "pageStatus": "Response status", - "pageOriginalStatus": "Original status", - "pageHtmlSource": "HTML source", - "pageUploadHtml": "Optional HTML file (32 KiB maximum)", - "pageTooLarge": "HTML must not exceed 32 KiB in UTF-8.", - "customErrorPages": "Custom error pages", - "pageStaticHost": "Static / maintenance host", - "pageMissingPath": "Unmatched path", - "pageDirectIp": "Direct IP access", - "pageUnknownHost": "Unknown hostname", - "pagesSafetyHint": "HTML is public content, not an admin template. ACLs still run first. Explicit HTTP 200 for an error masks its status to clients and monitoring. Total configuration size is limited.", - "vhDetailsTab": "Details", - "vhSsl": "SSL certificates", - "vhCertificate": "Named certificate", - "vhCertificateDefault": "Global SNI / default certificate", - "vhCertificateFile": "Certificate / full chain (PEM)", - "vhPrivateKeyFile": "Private key (PEM)", - "vhUploadCertificate": "Upload and validate pair", - "vhSelectCertificateFiles": "Choose both a certificate and private key file.", - "vhCertificateTooLarge": "Each PEM file is limited to 128 KiB.", - "vhCertificateNeedsHosts": "Enter vhost domain names in Details first.", - "vhCertificateStaged": "Pair verified and staged. Save this vhost to activate its certificate.", - "vhCertificateUploadHint": "Optional upload verifies key match and domain coverage. Private keys are stored in private files, never returned by the API. Save the vhost to activate the selection. Uploads do not save other changes. Listener changes require restart.", - "vhCompression": "Compression", - "vhCompressionHelp": "Compression and each codec inherit by default. A scope can filter globally enabled codecs; it cannot activate a codec disabled in Settings → Response. Minimum size remains global.", - "compressionEnabled": "Dynamic compression", - "compressionZstd": "Zstandard (zstd)", - "compressionBr": "Brotli (br)", - "compressionGzip": "gzip", - "compressionDeflate": "deflate", - "vhCache": "Cache", - "vhCacheTtl": "Maximum TTL (seconds, blank inherits)", - "vhCacheDefault": "Public default TTL (blank inherits)", - "vhCacheObject": "Object limit (KiB, blank inherits)", - "cacheEnabled": "Enable cache", - "cacheBypassQuery": "Bypass query strings", - "cacheStatusHeader": "Expose X-Cache", - "cacheHitsHeader": "Expose X-Cache-Hits", - "cacheRfcHeader": "Expose Cache-Status", - "cacheBypassPrefixes": "Bypass path prefixes", - "vhCacheCustomPrefixes": "Override inherited bypass paths", - "vhCacheHint": "Per-host policy can enable caching independently of the global default. All vhosts share global RAM and entry ceilings. Cookie affinity, private responses and authorization still bypass cache.", - "vhPurgeCache": "Purge host cache", - "vhPurgeConfirm": "Purge only this vhost cache? In-progress fills for this host will also be invalidated.", - "vhPages": "Error pages", - "vhHeadersOrder": "Order: global rules, vhost rules, path rules, then security/Server policy. Advanced JSON remains available.", - "vhNewHealthHint": "Save the vhost to start observing its backend health.", - "listenerAdd": "Add listener", - "listenerMigrate": "Convert defaults to named listeners", - "listenerScopeHint": "Internal / public is a label, not a firewall. New listeners do not expose legacy routes unless explicitly selected.", - "listenerLimit": "Maximum 64 named listeners.", - "listenerEndpoint": "Listener", - "listenerName": "Display name", - "listenerScope": "Label", - "listenerInternal": "Internal", - "listenerPublic": "Public", - "listenerLegacy": "Allow legacy routes", - "listenerInUse": "Listener is assigned to vhosts", - "listenerDuplicate": "Listener IDs must be unique.", - "listenerChooseHint": "Select named listeners configured in Settings > Listeners. A disabled listener grants no access.", - "listenerMissing": "Missing listener", - "http3Settings": "HTTP/3 / QUIC", - "http3SettingsHelp": "HTTP/3 enablement is controlled in Settings > Listeners. Tune advertisement, connection and stream limits here.", - "http3NoTlsListeners": "No TLS listeners are configured. Add and save a TLS listener first.", - "http3NoEnabledListeners": "No TLS listener has HTTP/3 enabled. Enable HTTP/3 in Settings > Listeners first.", - "http3ListenersDirty": "Save pending listener changes before editing HTTP/3 settings.", - "http3Save": "Save HTTP/3 settings", - "h2Configure": "Configure HTTP/2 limits", - "h3Enable": "Enable HTTP/3 over QUIC (UDP)", - "h3Configure": "Configure HTTP/3 limits", - "h3Limits": "HTTP/3 limits and advertisement", - "h3_alt_svc_max_age": "Alt-Svc lifetime (s)", - "h3_max_concurrent_connections": "Connections per listener", - "h3_max_concurrent_bidistream": "Bidirectional streams per connection", - "h3_max_concurrent_unistream": "Unidirectional streams per connection", - "h3_max_idle_timeout": "QUIC idle timeout (s; 0 = unlimited)", - "h3Help": "HTTP/3 requires TLS. Keep TCP plus the same UDP port available; HTTP/1.1 and HTTP/2 remain. Zero disables the local QUIC idle timeout. Changes require restart.", - "h3TuningHelp": "These limits apply only when HTTP/3 is enabled for this TLS listener. Changes require restart.", - "h3Requests": "HTTP/3 requests", - "certImport": "Import certificate", - "certImportSave": "Import and save in library", - "certLibraryHint": "Upload fullchain/certificate and private key in PEM. Save once, then assign the certificate to one or more vhosts.", - "certCheckDomains": "Domains to verify (comma-separated)", - "certAcmeLater": "Issued certificates are imported into the library and assigned per vhost.", - "certAdvanced": "Existing files / advanced", - "certLegacyNames": "Legacy SNI names (optional; prefer per-vhost assignment)", - "certAssigned": "Assigned vhosts", - "certUnassigned": "Not assigned", - "certInUse": "Certificate is in use. Reassign vhosts and change the default before removal.", - "certDuplicate": "This certificate ID exists. Use a new ID for safe rotation.", - "certManage": "Manage certificate library", - "certSelectHint": "Select an imported certificate. Uploads and rotation are in the Certificates tab. Assignments are validated against vhost names.", - "vhTinyauth": "Authentication and IP access", - "vhTabAuth": "Authentication", - "taEnable": "Enable Tinyauth protection", - "taUrl": "Tinyauth URL / verification endpoint", - "taLogin": "Public login URL", - "taRedirectForbidden": "Accept same-origin provider redirects for 403", - "taConnect": "Connect timeout (s)", - "taTimeout": "Total verification timeout (s)", - "taRequestHeaders": "Client headers sent to Tinyauth", - "taResponseHeaders": "Verified identity headers sent to application", - "taHelp": "An origin URL adds /api/auth/nginx automatically. Verification is body-free and runs before cache and WebSocket upgrades. Protected responses use private, no-store. Configure Tinyauth to trust the actual RustProxy IP.", - "vhResponsePolicy": "Response identity and diagnostics", - "vhInherit": "Inherit global settings", - "diagnosticHeaders": "Diagnostic response headers", - "diagInherit": "Inherit diagnostic headers", - "diagRequestId": "Expose X-Request-ID", - "hideMockOrigin": "Hide X-Mock-Origin", - "diagHelp": "X-Request-ID is opt-in. Other upstream response headers are controlled with ordinary response-header rules.", - "forwardingSettings": "Client identity / forwarding", - "forwardingHelp": "By default, client-supplied forwarding headers are discarded. Add only immediate proxies you control. Trusted chains are evaluated right-to-left, stopping at the first untrusted address.", - "forwardingCanonical": "RustProxy generates Forwarded, X-Forwarded-For, X-Real-IP, X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port and X-Forwarded-URI. Socket destination never comes from a header.", - "trustedProxies": "Trusted proxy CIDRs (one per line; /32 for one IPv4, /128 for one IPv6)", - "trustForwardedProto": "Trust X-Forwarded-Proto only from trusted proxies", - "listenerId": "Listener ID", - "cache": "Cache", - "cacheTitle": "Cache, explained.", - "cacheDescription": "Objects, domain effectiveness and precise decisions.", - "cacheRefresh": "Refresh", - "cacheLive": "Live (3 s)", - "cachePurgeScope": "Purge selected scope", - "cacheObjectCount": "Resident objects", - "cacheSaved": "Body bytes served from RAM", - "cacheCounterScope": "Domain counters since configuration revision", - "cacheDomains": "Domains and effectiveness", - "cacheScopeHint": "Memory limits, fills and evictions are global. Domain counters reset on configuration publication; purging objects does not reset counters.", - "cacheDomain": "Domain", - "cacheReasons": "Decision counters", - "cacheNoDomains": "No cache decisions yet. Send traffic to a configured host.", - "cacheObjectList": "Stored objects", - "cachePrevious": "Previous", - "cacheNext": "Next", - "cacheObjectHint": "Most recently used first. Click Target or Cache key / tags to copy the full value. Bodies and request headers are not exposed.", - "cachePath": "Path", - "cacheBodySize": "Body", - "cacheAge": "Age", - "cacheTtlRemaining": "Remaining TTL", - "cacheNoObjects": "No fresh objects in this scope. Inspect the decision counters above.", - "cacheLegacy": "Unassigned", - "cacheGlobalLimit": "Global limit", - "cacheFills": "Active fills (global)", - "cacheRejected": "Rejected fills (global)", - "cacheUpdated": "Updated", - "acmeTitle": "Let's Encrypt / ACME", - "acmeHelp": "Issue an ACME certificate, add it to the library, then assign it to a vhost. Automatic renewal starts after import.", - "acmeEmail": "ACME contact email", - "acmeDomains": "DNS names, comma-separated (wildcards only with DNS-01)", - "acmeEnvironment": "CA environment", - "acmeStaging": "Staging - not browser-trusted", - "acmeProduction": "Production", - "acmeDnsServer": "DNS update server IP", - "acmeDnsPort": "DNS update port", - "acmeKeyName": "TSIG key name", - "acmeSecret": "TSIG secret (base64)", - "acmeAlgorithm": "TSIG algorithm", - "acmePropagation": "DNS propagation wait (seconds)", - "acmeSignQuery": "Sign SOA discovery query", - "acmeTos": "I accept the Let's Encrypt subscriber agreement and authorize domain validation.", - "acmeConfirmProduction": "I confirm production issuance (CA rate limits apply).", - "acmeIssue": "Request certificate", - "acmeIdle": "No issuance job in this process.", - "acmeImport": "Add issued certificate to library", - "acmeRenewalHint": "Initial issuance/import remains explicit. Managed certificate profiles are then checked automatically while RustProxy is running; saved renewal status is restored from SQLite after restart.", - "acmeReplace": "Replace the existing library certificate? Current vhost assignments will be retained and revalidated.", - "acmeImported": "Certificate saved. Assign it in the vhost SSL tab.", - "certFuture": "Managed automatic renewal is available for supported ACME providers; saved renewal observations are persisted in SQLite.", - "cacheReason_cache_hit": "Fresh object reused", - "cacheReason_cache_miss": "No matching fresh object", - "cacheReason_cache_disabled": "Cache disabled", - "cacheReason_route_disabled": "Route bypass", - "cacheReason_request_cookie": "Application cookie", - "cacheReason_request_authorization": "Authorization header", - "cacheReason_request_cache_control": "Client cache directive / hard refresh", - "cacheReason_request_pragma": "Client Pragma", - "cacheReason_request_method": "Method other than GET", - "cacheReason_request_body": "Nonempty request body", - "cacheReason_request_range": "Range request", - "cacheReason_request_conditional": "Conditional request", - "cacheReason_request_upgrade": "Protocol upgrade", - "cacheReason_query_excluded": "Query excluded", - "cacheReason_path_excluded": "Path excluded", - "cacheReason_request_cache_control_invalid": "Invalid request cache directives", - "cacheReason_response_status": "Non-200 response", - "cacheReason_response_set_cookie": "Origin Set-Cookie", - "cacheReason_response_cache_control": "Origin privacy/cache directive", - "cacheReason_response_cache_control_invalid": "Invalid origin cache directives", - "cacheReason_response_vary": "Uncacheable Vary", - "cacheReason_response_range": "Partial response", - "cacheReason_response_trailers": "Response trailers", - "cacheReason_response_ttl_size_or_metadata": "TTL, size or metadata rejected", - "cacheReason_response_headers_policy": "Response rule prevents storage", - "cacheReason_configuration_changed": "Configuration changed", - "cacheReason_not_proxy_response": "Local page / rejected request", - "cacheReason_tinyauth": "Tinyauth protected content", - "cacheReason_sticky_cookie": "Sticky-session cookie mode", - "acme.working": "Certificate request is running.", - "acme.ready_to_import": "Certificate validated; ready to add to the library.", - "acme.certbot_not_installed": "Certbot is not installed or is not in the service PATH. Install Certbot and certbot-dns-rfc2136.", - "acme.certbot_failed_check_private_log": "Certbot failed. Check the private ACME logs; the exact path is shown with the failed operation.", - "acme.production_confirmation": "Explicit production confirmation is required.", - "acme.consent_required": "Accept the subscriber agreement and confirm the selected CA environment.", - "acme.provider_timeout": "Certificate provider deadline exceeded. Check the Certbot log path shown with the failed operation before retrying.", - "acme.invalid_key_secret": "TSIG secret must be valid base64 (16-512 decoded bytes).", - "acme.job_busy": "Another certificate request is already running.", - "acme.cancelled": "Certificate job cancelled on shutdown.", - "acme.certificate_validation_failed": "Issued certificate/key or domain coverage validation failed. Nothing was activated.", - "acme.invalid_request": "ACME: invalid request.", - "acme.unknown_field": "ACME: unknown field.", - "acme.invalid_id": "ACME: invalid id.", - "acme.invalid_email": "ACME: invalid email.", - "acme.invalid_domains": "ACME: invalid domains.", - "acme.invalid_domain": "ACME: invalid domain.", - "acme.dns_names_only": "ACME: dns names only.", - "acme.invalid_environment": "ACME: invalid environment.", - "acme.tos_required": "ACME: tos required.", - "acme.invalid_dns_server": "ACME: invalid dns server.", - "acme.invalid_limits": "ACME: invalid limits.", - "acme.invalid_key_algorithm": "ACME: invalid key algorithm.", - "acme.invalid_sign_query": "ACME: invalid sign query.", - "acme.unsafe_directory": "ACME: unsafe directory.", - "acme.unsafe_output_path": "ACME: unsafe output path.", - "acme.invalid_pem_size": "ACME: invalid pem size.", - "acme.invalid_pem": "ACME: invalid pem.", - "acme.profile_limit": "ACME: profile limit.", - "acme.profile_busy": "ACME: profile busy.", - "acme.staging_limit": "ACME: staging limit.", - "acme.request_too_large": "ACME: request too large.", - "acme.local_io_or_json_error": "ACME: local io or json error.", - "acme.no_certificate_directory": "ACME: no certificate directory.", - "acme.python_not_available": "ACME: python not available.", - "acme.worker_io": "ACME: worker io.", - "acme.invalid_worker_response": "ACME: invalid worker response.", - "acme.worker_failed": "ACME: worker failed.", - "acme.validation_worker_failed": "ACME: validation worker failed.", - "cacheResidentLabel": "Cache memory", - "certificateFuture": "ACME providers: HTTP-01, Cloudflare, OVH and RFC2136. Renewal is manual.", - "prometheusTitle": "Prometheus", - "prometheusHelp": "A dedicated read-only HTTP listener, separate from admin sessions. Binding, enabling or changing the token variable requires a restart; CIDRs, path and collectors apply live.", - "prometheusEnable": "Enable the scrape endpoint", - "prometheusListen": "Exporter bind IP and port", - "prometheusPath": "Metrics path", - "prometheusCidrs": "Allowed scraper IPs (CIDR, one per line)", - "prometheusTokenEnv": "Environment variable holding the bearer token (optional on loopback)", - "prometheusTokenHelp": "Enter a variable NAME, not the secret. Set its value to a random 32+ character token in the service environment. Non-loopback binding requires it. This endpoint is HTTP: use a private network or a TLS tunnel. The Prometheus credential file must contain the same token.", - "prometheusCollectors": "Metric groups", - "prometheusPreview": "Preview current metrics", - "prometheusPreviewFailed": "Could not fetch metrics", - "prometheusActive": "Active endpoint", - "prometheusDisabled": "Exporter disabled. Authenticated preview remains available.", - "certificateProvider": "Certificate source / validation method", - "providerHttp01": "Let's Encrypt - domain validation (HTTP-01)", - "providerCloudflare": "Let's Encrypt - Cloudflare API (DNS-01)", - "providerOvh": "Let's Encrypt - OVH API (DNS-01)", - "providerRfc2136": "Let's Encrypt - RFC2136 / TSIG (DNS-01)", - "providerImport": "Import certificate and private key (PEM)", - "providerExisting": "Existing server files / external paths (advanced)", - "providerHint_http01": "RustProxy runs Certbot HTTP-01. You do not provide certificate file paths here. Every domain must resolve to this proxy and public TCP port 80 must reach its HTTP listener. The issued pair is stored in RustProxy-managed certificate storage. Wildcards require DNS-01.", - "providerHint_cloudflare": "RustProxy runs Certbot DNS-01 with the Cloudflare plugin. You do not provide certificate file paths here. Use a restricted Zone:DNS:Edit API token; the issued pair is stored in RustProxy-managed certificate storage and can be renewed automatically.", - "providerHint_ovh": "RustProxy runs Certbot DNS-01 with the OVH plugin. You do not provide certificate file paths here. Supply credentials limited to the required DNS zones; the issued pair is stored in RustProxy-managed certificate storage and can be renewed automatically.", - "providerHint_rfc2136": "RustProxy runs Certbot DNS-01 with RFC2136/TSIG. You do not provide certificate file paths here. The DNS server must accept signed updates for the required challenge records; the issued pair is stored in RustProxy-managed certificate storage.", - "providerHint_import": "Choose certificate/fullchain and private-key PEM files from your computer. RustProxy validates the pair and copies it into /certs; the browser-side source path is not saved.", - "providerHint_existing": "Reference files already present on the RustProxy server. Choose managed filenames from /certs or absolute external paths such as /etc/letsencrypt/live/example.com/fullchain.pem and privkey.pem. External files are not copied into RustProxy backups.", - "acmeCfToken": "Cloudflare API token", - "acmeOvhEndpoint": "OVH API region", - "acmeOvhApplicationKey": "OVH application key", - "acmeOvhApplicationSecret": "OVH application secret", - "acmeOvhConsumerKey": "OVH consumer key", - "acme.invalid_provider": "Select a supported certificate provider.", - "acme.invalid_api_credential": "API credential is missing or has invalid characters.", - "acme.invalid_ovh_endpoint": "Select OVH Europe or Canada.", - "acme.http01_no_wildcard": "HTTP-01 cannot issue wildcard names. Select a DNS-01 provider.", - "acme.http01_listener_required": "HTTP-01 needs an active plaintext HTTP listener reachable on public port 80.", - "cacheScopeLegacy": "Unassigned / old scope", - "certificateLibrary": "Certificates", - "certificateWorkspaceHelp": "Manage installed certificates, automatic renewal, and generation/import in separate views.", - "certificateTabInstalled": "Installed", - "certificateTabRenewal": "Renewal", - "certificateTabGenerate": "Generate", - "certificateInstalledTitle": "Installed certificates", - "certificateInstalledHelp": "Certificates available to vhosts, redirects, 404 hosts, and the administration listener.", - "certificateGenerateTitle": "Generate and import", - "certificateGenerateHelp": "Choose Let's Encrypt/ACME issuance, PEM upload, or existing files from RustProxy's private certificate directory.", - "certificateOperationStatus": "Latest ACME operation", - "certificateNoRenewalOperation": "No renewal operation in this process.", - "certificateStateIdle": "Idle", - "certificateStateRunning": "Running", - "certificateStateReady": "Ready", - "certificateStateComplete": "Complete", - "certificateStateFailed": "Failed", - "certbotLogs": "Certbot logs", - "systemDiagnostics": "System / process", - "vhWebsockets": "WebSocket support", - "vhLocations": "Custom locations", - "locationPath": "Location prefix", - "locationPool": "Vhost backends", - "locationMissing": "Missing backend", - "locationLimit": "A host supports up to 64 locations.", - "locationAccess": "Location access control", - "locationHeaders": "Location headers", - "locationAddBackend": "Add dedicated backend", - "locationAdd": "Add location", - "locationHelp": "The longest matching prefix wins. The original path and query are retained. Select a backend from this host or add a dedicated backend. Host authentication and ACL always apply.", - "aclUse": "Enable IP access list", - "aclAllow": "Allow", - "aclNetwork": "IP network (CIDR)", - "aclAction": "Action", - "aclAdd": "Add IP rule", - "authMode": "Authentication", - "authNone": "None", - "authTinyauth": "Tinyauth", - "authBasic": "HTTP Basic Auth", - "basicRealm": "Realm (login prompt)", - "basicRequireTls": "Require HTTPS for Basic Auth", - "basicForward": "Forward verified Authorization to backend", - "basicPassword": "Password (12-128 bytes)", - "basicKeepPassword": "Leave empty to keep the password", - "basicHashSaved": "Password set", - "basicHashRequired": "Set a password for each user.", - "basicAddUser": "Add user", - "basicHelp": "Only Argon2id hashes are stored. Verified credentials are removed from upstream requests unless forwarding is enabled. Enable forwarding only to a trusted HTTPS backend or an isolated network. Protected hosts bypass shared cache.", - "basic.too_many_users": "At most 64 users per host.", - "basic.invalid_username": "Invalid username.", - "basic.duplicate_username": "Duplicate username.", - "basic.unsupported_hash": "Use an Argon2id password hash generated by RustProxy.", - "basic.invalid_hash": "Invalid password hash.", - "basic.invalid_realm": "Invalid login realm.", - "basic.users_required": "Basic Auth requires at least one user.", - "basic.busy": "Password workers are busy; try again.", - "renewalTitle": "Automatic renewal", - "renewalHelp": "Managed ACME certificates are checked about every 12 hours while RustProxy is running. Certbot decides when renewal is due. Validated replacements are deployed without restarting listeners. Keep provider credentials and HTTP-01 port 80 available.", - "renewalNone": "No managed ACME certificates in the library.", - "renewalEnabled": "Automatic renewal enabled", - "renewalDisabled": "Automatic renewal disabled", - "renewalEnable": "Enable automatic renewal", - "renewalDisable": "Disable automatic renewal", - "renewalNow": "Check now", - "renewalDryRun": "Validate renewal (dry run)", - "renewalLast": "Last check", - "renewalNext": "Next check", - "renewalExpires": "Valid until", - "acme.renewing": "Checking certificate renewal", - "acme.renewed": "Certificate renewed and deployed", - "acme.not_due": "Certificate does not need renewal yet", - "acme.renewal_test_ok": "Renewal validation passed; active certificate unchanged", - "acme.renewal_profile_missing": "Saved renewal profile is missing or unreadable. Restore the private ACME profile.", - "acme.configuration_changed": "Certificate configuration changed during renewal; nothing deployed.", - "acme.deployment_failed": "Renewed certificate failed configuration validation; previous certificate remains active.", - "acme.not_managed": "Only managed ACME certificates can be renewed automatically.", - "acme.certificate_not_found": "Certificate no longer exists.", - "acme.renewal_disabled": "Automatic renewal is disabled.", - "acme.openssl_not_installed": "OpenSSL is required to inspect certificate validity.", - "cacheReason_basic_auth": "HTTP Basic authentication", - "locationPoolEligible": "Include in the default load-balancing pool", - "locationId": "Location ID", - "vhIpAccess": "Vhost IP access list", - "apiDocs": "Documentation", - "auth.invalid_request": "Invalid request. Check the supplied fields.", - "locationProxyHeaders": "Proxy request headers (to backend)", - "locationResponseHeaders": "Response headers (to client)", - "locationHeadersHelp": "Set replaces a header, append adds a value, remove deletes it. Applied after global and vhost rules, only for this location. Values are literal (no nginx variables). Transport and verified identity headers stay system-managed.", - "headersAdvancedJson": "Advanced header rules (JSON)", - "headersInvalid": "Invalid header rule. Check name, operation and value.", - "headersProtected": "Transport and proxy identity headers cannot be overridden here. Use forwarding, authentication or response settings.", - "docsTitle": "Documentation", - "docsSession": "Uses your administrator session. API writes require CSRF protection.", - "docsSignIn": "Sign in to view documentation", - "docsPanel": "Back to panel", - "docsWrites": "Enable write operations", - "docsWriteConfirm": "This sends a real change to RustProxy. Continue?", - "docsExpired": "Session ended. Sign in again.", - "docsError": "Documentation could not be loaded.", - "docsAccount": "Signed in as", - "docsReadOnly": "Read-only execution by default. SSE streams and login/setup are not executed here.", - "docsLogout": "Sign out", - "authPrivateNote": "Access is restricted to authorized administrators.", - "advanced": "Advanced", - "docsLicenses": "Swagger UI licenses", - "vhTrafficPolicy": "Traffic policy", - "vhHttp3Enabled": "Enable HTTP/3 for this vhost", - "vhCloudflareClientIp": "Use verified Cloudflare client IP", - "vhTimingHeaders": "Expose Server-Timing diagnostics", - "vhRateEnabled": "Enable per-client IP rate limit", - "vhRateRequests": "Requests per window", - "vhRateWindow": "Window (seconds)", - "vhRateBurst": "Burst allowance", - "vhCountryMode": "Country access policy", - "vhCountryBlock": "Block listed countries", - "vhCountryAllow": "Allow only listed countries", - "vhCountryList": "Countries (ISO 3166-1 alpha-2)", - "vhCountryPolicyHelp": "Country rules use the installed GeoIP database. In allow-only mode, a listed country OR an explicitly allowed IP can pass this vhost scope. In block mode, a listed country OR explicitly denied IP is blocked. An unresolved country fails the country branch.", - "vhCountry": "Country", - "vhAddCountryRate": "Add country rate limit", - "vhCountryRateLimit": "At most 64 country-specific rate limits are allowed.", - "vhRateHelp": "Country-specific limits override the base per-IP limit. Exceeded limits return 429; denied countries return 403.", - "vhHealthOffWarning": "Active backend probes are disabled. Passive Backend Health is configured separately and remains off unless explicitly enabled.", - "vhFrontendSelectionOnly": "Select named listeners for this vhost. Listener IP addresses, ports and H2/H3 capabilities are configured only in Settings > Listeners.", - "vhForceSsl": "Force SSL (redirect HTTP to HTTPS)", - "vhForceSslHelp": "Plain HTTP requests for this vhost receive a 308 redirect to HTTPS while preserving path and query. Select at least one plain HTTP listener and at least one TLS listener. If several TLS ports are selected, port 443 is used; otherwise the TLS target port must be unambiguous.", - "vhForceSslNeedsTls": "Force SSL requires at least one enabled TLS listener for this vhost.", - "vhForceSslPortConflict": "Force SSL cannot choose the HTTPS redirect target because selected TLS listeners use multiple non-standard ports. Select one TLS port or include port 443.", - "vhForwardClientIp": "Forward client IP to backend", - "vhForwardClientIpHelp": "Keep enabled for normal reverse-proxy behavior. Disable to withhold Forwarded, X-Forwarded-For and X-Real-IP from the application while RustProxy, ACLs, rate limits and Tinyauth still use the real client IP.", - "vhClientIpHidden": "Client IP hidden from backend", - "listenerMigrateInline": "Existing vhost listener (will migrate)", - "vhDebugOriginPreset": "Apply recommended preset", - "vhDebugOriginTlsWarning": "This preset keeps backend TLS certificate verification disabled. The connection is encrypted, but CA and hostname identity are not checked. Continue?", - "vhOriginPresetHelp": "Debug origin defaults for new backend rows: HTTPS by IP, default LB pool ON, private origin ON, HTTP/1.1, Preserve client Host ON and TLS verification OFF. Re-enable TLS verification whenever the origin certificate can be validated.", - "vhDebugOriginMismatch": "This HTTPS-by-IP origin does not match the debug forwarding preset (pool ON, private origin ON, HTTP/1.1, preserve Host ON, TLS verification OFF). If you enable verification, set TLS server name to the certificate DNS name and add its private CA when needed. Continue with the current values?", - "vhCacheStaticCookie": "Ignore request cookies for selected static file extensions", - "vhCacheStaticCustom": "Use a custom static extension list", - "vhCacheStaticExtensions": "Static extensions (one per line, without dot)", - "vhCacheStaticHelp": "Inherit the global policy or override it for this vhost. Ignore Cookie only for files whose bytes do not depend on the session; Set-Cookie, private/no-store and Vary: Cookie still block shared storage.", - "cacheStaticTitle": "Static asset cache boost", - "cacheStaticCookie": "Ignore request cookies for static files", - "cacheStaticExtensions": "Global static extensions (one per line, without dot)", - "cacheStaticHelp": "Enable this only for files whose bytes do not depend on the session. Request Cookie then does not affect the shared cache key for these extensions; responses with Set-Cookie, private/no-store or Vary: Cookie are still never stored.", - "vhHttp2Enabled": "Enable HTTP/2 for this vhost", - "vhFrontendProtocolsHelp": "H2 and H3 are per-vhost opt-outs. A switch is available only when at least one selected listener has that protocol enabled in Settings > Listeners.", - "administration": "Administration", - "administrationActiveHttp": "Active HTTP administration", - "administrationActiveHttps": "Active HTTPS administration", - "administrationCertificate": "Certificate", - "administrationGeneratedCertificate": "Application-generated default certificate", - "administrationHelp": "HTTPS allows the administration listener to bind beyond loopback. Plain HTTP on the HTTPS port is redirected automatically. Listener and certificate changes take effect after restart.", - "administrationHosts": "Allowed DNS hostnames (optional strict allowlist)", - "administrationHttps": "Enable HTTPS for administration", - "administrationListen": "Listen address", - "cacheOverhead": "Overhead", - "cacheResidentAge": "X-Cache-Resident-Age", - "controlPlane": "CONTROL PLANE", - "logging": "Logging", - "loggingTitle": "Structured access logging", - "loggingTabOutputs": "Outputs", - "loggingTabFormat": "JSON format", - "loggingTabErrors": "Error log", - "loggingDescription": "Write configurable JSON access logs to a file, Logstash or Loki.", - "loggingHelp": "Live log viewing stays in memory. External sinks use a bounded background queue and do not block request I/O.", - "loggingFile": "JSON file", - "loggingPath": "File path", - "loggingAddress": "TCP address", - "loggingTokenEnv": "Bearer token environment variable", - "loggingLabels": "Labels (JSON object)", - "loggingFormat": "Configurable JSON format", - "loggingFormatHelp": "Map output names to source fields. Leave mapping empty for the complete standard entry.", - "loggingFields": "Field mapping (JSON object)", - "loggingStatic": "Static fields (JSON object)", - "loggingJsonInvalid": "Logging JSON fields must contain valid JSON objects.", - "loggingQueue": "Sink queue", - "loggingDropped": "dropped", - "loggingSent": "sent", - "loggingCapacity": "capacity", - "errorLogTitle": "Error file", - "errorLogRoutingHelp": "Warnings, errors and worker panics go to this file when enabled, or to the console/systemd journal when disabled. File failures fall back to the console.", - "errorLogPath": "Error log path", - "errorLogFormat": "Format", - "tls12": "TLS 1.2", - "tls13": "TLS 1.3", - "tlsPolicy": "TLS policy", - "tlsPolicyHelp": "This is the global TLS policy inherited by every TLS host in the application. Per-vhost policy may only narrow it. HTTP/3 requires TLS 1.3.", - "tlsCiphers": "Cipher suites (one per line; empty = rustls defaults)", - "tlsAvailableCiphers": "Available cipher suites", - "tlsPolicyVersionRequired": "Enable at least one TLS version.", - "vhTlsOverride": "Override global TLS policy", - "vhTlsOverrideHelp": "Use only when this vhost needs different TLS versions or cipher suites. Empty cipher list keeps rustls defaults.", - "vhTlsHttp3Conflict": "HTTP/3 uses the global TLS policy. Disable HTTP/3 on this vhost before using a different TLS override.", - "vhListeners": "Listeners", - "vhProtocols": "Protocols", - "loggingMode": "JSON / ASYNC / BOUNDED", - "loggingPushUrl": "Push URL", - "cacheHotObjects": "Hot objects", - "cacheHotMemory": "Hot memory", - "cacheColdObjects": "Cold objects", - "cacheColdDisk": "Cold disk", - "cacheTierMoves": "Tier moves", - "cacheColdHits": "cold hits", - "cacheTier": "Tier", - "cacheStorage": "Memory / disk", - "cacheColdTitle": "Persistent cold cache", - "cacheColdEnable": "Enable persistent disk tier", - "cacheColdHelp": "Choose what is moved from hot RAM to persistent disk. Cold hits are read asynchronously and promoted back to RAM. Metadata is readable JSON; bodies are raw .body files.", - "cacheColdPath": "Cold path (inside data directory)", - "cacheColdBytes": "Cold disk budget (MiB)", - "cacheColdEntries": "Cold object limit", - "cacheColdAfter": "Demote after idle seconds", - "cacheHot": "Hot", - "cacheCold": "Cold", - "cacheReason_request_no_cache": "Client no-cache / hard reload requires validation", - "cacheReason_request_max_age": "Client max-age requires validation", - "cacheReason_request_min_fresh": "Client min-fresh requires validation", - "cacheReason_request_pragma_no_cache": "Client Pragma no-cache / legacy hard reload", - "cacheReason_cache_revalidated": "Stored object validated by origin", - "cacheReason_cache_conditional_hit": "Fresh object satisfied a conditional request", - "cacheReason_request_no_store": "Client no-store bypass", - "cacheReason_request_precondition": "Client precondition requires origin", - "loggingPreset": "Format preset", - "loggingPresetCustom": "Custom", - "loggingPresetStandard": "RustProxy standard", - "loggingPresetNginx": "Nginx-style JSON", - "loggingPresetEcs": "ECS-style field names", - "loggingPresetMinimal": "Minimal", - "loggingBuilder": "Visual field builder", - "loggingBuilderOpen": "Configure fields", - "loggingAdvancedJson": "Advanced JSON mapping", - "loggingBuilderHelp": "Select source fields and optionally rename their JSON keys. Applying the visual editor normalizes each source field to at most one output key; use advanced JSON for duplicate aliases.", - "loggingSourceField": "Source field", - "loggingOutputField": "Output JSON key", - "loggingPreview": "Preview", - "loggingOutputRequired": "Every enabled log field needs an output JSON key.", - "loggingOutputDuplicate": "Output JSON keys must be unique.", - "aclProfiles": "ACL profiles", - "acl-profiles": "ACL profiles", - "acl-profilesTitle": "Reusable ACL profiles", - "acl-profilesDescription": "Define reusable IP and country access rules, then attach them to one or more virtual hosts.", - "aclProfilesIntro": "Reusable access profiles can be attached to multiple vhosts. Per-vhost ACL and country rules remain active and are enforced together with the profile.", - "aclProfilesReusable": "REUSABLE POLICY", - "aclProfileNew": "New profile", - "aclProfileEdit": "Editing profile", - "aclProfileEditor": "Profile configuration", - "aclProfileId": "Profile ID", - "aclProfileName": "Display name", - "aclProfileIpEnabled": "Enable IP ACL", - "aclProfileRules": "IP rules — one “allow|deny CIDR” per line", - "aclProfileCombineHelp": "Country and IP exceptions are combined with OR inside this profile. Explicit deny rules and blocked countries win; attached profile, vhost and location policies must still all pass.", - "aclProfilePolicyMode": "Choose the base policy", - "aclProfilePolicyModeHelp": "Use a guided allow-list or block-list for normal cases. Custom mode keeps full control for mixed policies.", - "aclProfileModeAllowExceptions": "Block by default", - "aclProfileModeAllowExceptionsHelp": "Allow selected countries OR selected IP/CIDR ranges.", - "aclProfileModeBlockExceptions": "Allow by default", - "aclProfileModeBlockExceptionsHelp": "Block selected countries OR selected IP/CIDR ranges.", - "aclProfileModeCustom": "Custom", - "aclProfileModeCustomHelp": "Control default action, country mode and every IP action manually.", - "aclProfileCountryExceptions": "Country exceptions", - "aclProfileUseCountries": "Use countries", - "aclProfileAllowedCountries": "Allowed countries (ISO codes)", - "aclProfileBlockedCountries": "Blocked countries (ISO codes)", - "aclProfileAllowedCountriesHelp": "A request from any listed country is allowed even when its IP is not listed.", - "aclProfileBlockedCountriesHelp": "A request from any listed country is blocked even when its IP is otherwise allowed.", - "aclProfileCustomCountryHelp": "Custom country behavior is controlled below. Blocked countries always deny.", - "aclProfileIpExceptions": "IP / CIDR exceptions", - "aclProfileAddIpException": "Add IP exception", - "aclProfileAllowedIpsHelp": "Add an IPv4/IPv6 address or CIDR. These addresses are allowed in addition to the selected countries.", - "aclProfileBlockedIpsHelp": "Add an IPv4/IPv6 address or CIDR. These addresses are blocked in addition to the selected countries.", - "aclProfileCustomIpHelp": "Each IP rule can allow or deny independently. Plain IP addresses are accepted as host rules.", - "aclProfileBulkExceptions": "Paste many IP addresses / CIDRs", - "aclProfilePreview": "Effective policy", - "aclProfilePreviewDefaultDeny": "DEFAULT: BLOCK", - "aclProfilePreviewDefaultAllow": "DEFAULT: ALLOW", - "aclProfilePreviewAllowedCountries": "ALLOW COUNTRY", - "aclProfilePreviewBlockedCountries": "BLOCK COUNTRY", - "aclProfilePreviewAllowedIps": "ALLOW IP", - "aclProfilePreviewBlockedIps": "BLOCK IP", - "aclProfilePreviewIpRules": "IP RULES", - "aclProfilePreviewNoExceptions": "No exceptions configured", - "aclProfilePreviewBlocksAll": "This profile currently blocks all traffic. Add a country or IP exception to allow access.", - "aclProfilePreviewAllowsAll": "This profile currently allows all traffic. Add a country or IP exception to block access.", - "aclProfilePreviewAllowNote": "Access is granted when the country OR the client IP matches an allowed exception. Explicit deny rules still win in custom policies.", - "aclProfilePreviewBlockNote": "Access is denied when the country OR the client IP matches a blocked exception.", - "aclProfilePreviewCustomNote": "Custom mode uses backend precedence: explicit IP deny and blocked country deny; allow-only countries and explicit IP allows can satisfy the same policy scope.", - "aclProfileOr": "OR", - "aclProfileCountriesShortAllow": "Allowed countries", - "aclProfileCountriesShortBlock": "Blocked countries", - "aclProfileIpsShortAllow": "Allowed IPs", - "aclProfileIpsShortBlock": "Blocked IPs", - "aclProfileIpRulesShort": "IP rules", - "aclProfileIpOff": "IP ACL disabled", - "aclProfileCountriesOff": "Country filtering disabled", - "aclProfileAttached": "Attached to", - "aclProfileInUse": "Profile is attached to vhosts", - "aclProfilesEmpty": "No reusable ACL profiles yet.", - "aclProfileCountryInvalid": "Country codes must use two-letter ISO codes.", - "aclProfileCountryRequired": "Add at least one country or disable country exceptions.", - "aclProfileDuplicate": "An ACL profile with this ID already exists.", - "vhAclProfile": "ACL profile", - "vhAclProfileNone": "No reusable profile", - "vhAclProfileHelp": "Optional reusable profile. Inline vhost ACL and country rules below are still enforced in addition to the profile.", - "vhHostAdd": "Add hostname", - "vhHostsHelp": "Add multiple hostnames (e.g. domain.local i subdomain.local) with Enter or comma. They share this vhost and its backend/settings. Wildcards are supported only as leading *. Exact hosts take priority.", - "vhHostInvalid": "Invalid hostname or wildcard pattern.", - "vhHostRequired": "Add at least one hostname.", - "vhLifetimeChart": "Lifetime request rate", - "vhReqPerSecond": "req/s", - "vhPeak": "peak", - "listenerAddresses": "Bind addresses", - "listenerAddressesInvalid": "Provide 1–16 unique listener addresses.", - "listenerAddressesExplicit": "When a listener uses multiple addresses, select explicit IPs; 0.0.0.0 and :: are not allowed.", - "ruleSyntaxLine": "Invalid ACL rule on line", - "vhForceSslNeedsHttp": "Force SSL needs at least one selected plain HTTP (non-TLS) listener. A listener named “HTTP” may still be configured as TLS in Settings > Listeners.", - "aclProfileRuleBuilder": "IP rules", - "aclProfileBulkRules": "Paste / import rules", - "aclProfileBulkHelp": "Paste one “allow|deny CIDR” rule per line. Invisible Unicode spacing is normalized during import and errors identify the exact line.", - "aclProfileImportRules": "Import rules", - "aclProfileRulesImported": "ACL rules imported into the visual editor.", - "streamHosts": "Stream hosts", - "streamsHelp": "Raw TCP/UDP forwarding on dedicated ports. Stream traffic bypasses HTTP vhosts, ACL, cache, headers and compression.", - "streamAdd": "Add stream host", - "saveStreams": "Save stream hosts", - "streamRestartHelp": "Changing a stream listener requires a RustProxy restart.", - "streamRestartPending": "Saved stream changes are pending. Restart RustProxy to apply them.", - "streamActive": "Currently active streams", - "streamNone": "No stream hosts active", - "streamEndpoint": "Stream endpoint", - "streamId": "Stream ID", - "streamName": "Display name", - "streamProtocol": "Protocol", - "streamListen": "Listen address", - "streamUpstream": "Upstream address", - "streamConnectTimeout": "TCP connect timeout (seconds)", - "streamIdleTimeout": "Idle/session timeout (seconds)", - "streamMaxConnections": "Max connections/sessions", - "streamTcp": "TCP", - "streamUdp": "UDP", - "streamLimit": "At most 128 stream hosts are supported.", - "streamDuplicate": "Stream host IDs must be unique.", - "streams": "Streams", - "streamsTitle": "TCP / UDP streams", - "streamsDescription": "Manage raw Layer-4 listeners, upstreams and live traffic telemetry.", - "streamsPageIntro": "TCP/UDP streams are independent Layer-4 proxies. Each stream owns a dedicated listener and upstream and bypasses the HTTP vhost, ACL and cache stack.", - "streamLayer4": "LAYER 4 / TCP + UDP", - "streamList": "Streams", - "streamSearch": "Search streams", - "streamSearchPlaceholder": "ID, name, listener or upstream…", - "streamSearchEmpty": "No streams match this search.", - "streamEmpty": "No TCP/UDP streams yet. Add a listener to start forwarding Layer-4 traffic.", - "streamNew": "New stream", - "streamAddListener": "Add TCP/UDP listener", - "streamEditor": "Stream configuration", - "streamEditLabel": "EDITING", - "streamNewLabel": "NEW", - "streamDetailsTab": "Details", - "streamNetworkTab": "Listener & upstream", - "streamLimitsTab": "Limits", - "streamNetwork": "Listener & upstream", - "streamLimits": "Connection / session limits", - "streamDedicatedListenerHelp": "A stream listener is created together with the stream and is separate from HTTP/HTTPS listeners.", - "streamLimitsHelp": "Limits apply to concurrent TCP connections or per-client UDP sessions. TCP connect timeout is not used by UDP.", - "streamListenAdd": "Add listen address", - "streamListenHelp": "Enter one IP:port listener address and press Enter. Listener changes become active after RustProxy restarts.", - "streamListenInvalid": "The listener address must be one IP:port value without spaces or commas.", - "streamListenRequired": "Add a listener address.", - "streamDelete": "Delete this stream? The active listener remains until RustProxy restarts.", - "streamPendingRestart": "Pending restart", - "streamRunning": "Running", - "streamActiveSessions": "Active connections / sessions", - "streamSessionsTotal": "Connections / sessions", - "streamErrors": "Errors", - "streamRejected": "Rejected", - "streamTransferred": "Transferred", - "streamTrafficChart": "Live traffic rate", - "streamClientToUpstream": "client → upstream", - "streamUpstreamToClient": "upstream → client", - "streamNoLiveMetrics": "No live metrics until this stream is active after restart.", - "streamIdleShort": "Idle timeout", - "listenerAddressAdd": "Add bind address", - "listenerAddressesHelp": "Press Enter or comma to add a bind address. Multiple addresses use the same listener policy; wildcard 0.0.0.0/:: cannot be mixed with explicit addresses.", - "cacheColdOnIdle": "Move old/idle objects to cold storage", - "cacheColdOnLru": "Move hot LRU evictions to cold storage", - "cacheColdOnShutdown": "Flush hot cache to cold storage during graceful shutdown", - "certificateCovers": "Certificate domains", - "certificateCoverageUnknown": "Domain data (DNS SAN) is unavailable. Reload certificate files or import the certificate again.", - "certificateDetails": "Certificate details", - "certificateFiles": "Certificate / key files", - "redirects": "Redirects", - "redirectsTitle": "Redirect with intent.", - "redirectsDescription": "Local redirect hosts with explicit match, target, status, path and query behavior.", - "redirectsPageIntro": "Redirect hosts answer locally without opening an upstream connection. Rules can match scheme and path, then redirect to a URL, another host or a path.", - "redirectLayer": "HTTP REDIRECTION", - "redirectList": "Redirect hosts", - "redirectSearch": "Search redirects", - "redirectSearchPlaceholder": "ID, hostname, target…", - "redirectNew": "New redirect", - "redirectEmpty": "No redirect hosts configured.", - "redirectSearchEmpty": "No redirects match this search.", - "redirectEditor": "Redirect configuration", - "redirectId": "Redirect ID", - "redirectName": "Display name", - "redirectHosts": "Source hostnames", - "redirectFrontend": "FRONTEND", - "redirectListeners": "Listeners", - "redirectBehavior": "BEHAVIOR", - "redirectRules": "Rules", - "redirectAddRule": "Add rule", - "redirectRulesHelp": "Longest matching path wins. 307/308 preserve the request method; 301/302/303 may change it in clients.", - "redirectSafetyHint": "Redirect hosts do not contact a backend. Global host ACLs still apply. Avoid self-referential rules that can create browser redirect loops.", - "redirectRule": "Rule", - "redirectRuleId": "Rule ID", - "redirectSourceScheme": "Incoming scheme", - "redirectSchemeAny": "HTTP or HTTPS", - "redirectSchemeHttp": "HTTP", - "redirectSchemeHttps": "HTTPS", - "redirectSchemePreserve": "Preserve incoming scheme", - "redirectMatchType": "Path match", - "redirectMatchPrefix": "Prefix", - "redirectMatchExact": "Exact", - "redirectSourcePath": "Source path", - "redirectTargetType": "Target type", - "redirectTargetUrl": "Absolute URL", - "redirectTargetHost": "Host / application", - "redirectTargetPath": "Path on the same host", - "redirectTarget": "Target", - "redirectTargetScheme": "Target scheme", - "redirectPathMode": "Path handling", - "redirectPathAppend": "Append full source path", - "redirectPathStrip": "Strip matched prefix and append remainder", - "redirectPathReplace": "Use configured target path only", - "redirectQueryMode": "Query handling", - "redirectQueryPreserve": "Preserve source query", - "redirectQueryDrop": "Drop source query", - "redirectStatus": "HTTP status", - "redirect301": "301 Moved Permanently", - "redirect302": "302 Found", - "redirect303": "303 See Other", - "redirect307": "307 Temporary Redirect (preserve method)", - "redirect308": "308 Permanent Redirect (preserve method)", - "redirectCacheSeconds": "Browser/shared cache max-age (seconds; 0 = no-store)", - "redirectHostsRequired": "Add at least one source hostname.", - "redirectListenersRequired": "Select at least one listener.", - "redirectRulesRequired": "Add at least one redirect rule.", - "redirectDuplicate": "A redirect with this ID already exists.", - "redirectUnsaved": "Discard unsaved redirect changes?", - "redirectNewLabel": "NEW", - "administrationSessionDays": "Administrator session lifetime (days)", - "administrationSessionDaysHelp": "Browser sessions use an absolute 7–30 day lifetime. Changing this value applies immediately; password changes and “revoke all sessions” still invalidate sessions at once.", - "vhLocalListenerRisk": "Allow local RustProxy listener as backend (risk)", - "vhLocalListenerRiskHelp": "Shown when the backend URL directly targets the configured or active RustProxy administration listener. localhost, 127.0.0.1 and ::1 are treated as equivalent loopback forms on the same administration port. Enabling it can expose the control plane or create a proxy loop; private/loopback targets still require “Allow private origin IP”.", - "vhLocalListenerRiskConfirm": "This can route requests into a local RustProxy listener, including the administration port, and may create a loop or expose the control plane. Accept this risk?", - "cacheResidentHistory": "Resident RAM history", - "networkHistory": "RX / TX history", - "responseLatencyHistory": "Response time history", - "sourceIp": "Source IP", - "cacheColdOnAge": "Move objects to cold storage after a maximum hot-tier age", - "cacheColdMaxHot": "Maximum hot-tier age (seconds)", - "cacheColdPolicyHelp": "Idle time measures how long an object has not been used. Hot-tier age measures continuous residency in RAM even if the object is still requested. LRU pressure and graceful-shutdown handoff are independent policies.", - "documentation": "Documentation", - "locationPathPattern": "Path / regex pattern", - "locationMatchType": "Match type", - "locationMatchPrefix": "Prefix", - "locationMatchPriority": "Priority prefix (^~)", - "locationMatchExact": "Exact path (=)", - "locationMatchRegex": "Regex (~)", - "locationMatchRegexI": "Regex, case-insensitive (~*)", - "locationMatch_prefix": "prefix", - "locationMatch_prefix_priority": "priority prefix", - "locationMatch_exact": "exact", - "locationMatch_regex": "regex", - "locationMatch_regex_insensitive": "regex i", - "locationAction": "Action", - "locationProxyAction": "Proxy to backend", - "locationReturnAction": "Custom response (return)", - "locationProxyShort": "proxy", - "locationReturnShort": "local response", - "locationHandling": "Request handling", - "locationRewriteMode": "Backend path rewrite", - "locationRewritePreserve": "Keep original path", - "locationRewriteStrip": "Strip matched prefix", - "locationRewriteReplace": "Replace matched prefix", - "locationRewriteRegex": "Regex replacement", - "locationRewriteValue": "Replacement path / expression", - "locationRewriteHelp": "Query parameters are preserved. Prefix rewrites operate on the normalized routing path. Regex replacement supports Rust regex captures such as $1. Rewritten paths are validated before forwarding.", - "locationResponseStatus": "Response status", - "locationResponseType": "Content-Type", - "locationResponseBody": "Response body", - "headersVisualBuilder": "Visual configurator", - "locationDocsTitle": "Custom locations documentation", - "headersDocsTitle": "Header rules documentation", - "locationDocsIntro": "Custom locations are evaluated inside the selected vhost after host authentication and ACL. They never bypass vhost security.", - "locationDocsPrefix": "Prefix: matches a complete path segment prefix. The longest prefix wins. Priority prefix behaves like nginx ^~ and prevents regex locations from taking precedence.", - "locationDocsExact": "Exact: matches only the exact normalized path, like nginx location = /path.", - "locationDocsRegex": "Regex: first matching regex wins after exact/priority-prefix processing. Case-sensitive and case-insensitive modes are supported. Rust regex syntax is used; look-around/backreferences in the pattern are intentionally unsupported.", - "locationDocsReturn": "Custom response: answer locally with a chosen HTTP status, Content-Type and body without opening a backend connection. Location response-header rules still apply.", - "locationDocsRewrite": "Backend path rewrite: keep the original target, strip/replace a matched prefix, or use regex capture replacement. The original query string is preserved.", - "locationDocsAccess": "Per-location IP ACL, cache override and request/response header rules are composed with the vhost policy. A location cannot disable host authentication or host ACL.", - "locationDocsLimits": "Up to 64 locations per vhost. Regexes and response bodies are bounded; transport, forwarding identity and WebSocket protocol headers remain system-managed.", - "headersDocsIntro": "Header rules are applied in order: global → vhost → matching location. Use the visual configurator for normal changes; JSON remains an advanced escape hatch.", - "headersDocsSet": "Set: replace all existing values of a header with the configured literal value.", - "headersDocsAppend": "Append: add another value without deleting existing values.", - "headersDocsRemove": "Remove: delete the header before forwarding or before sending the response.", - "headersDocsProtected": "Protected transport/identity headers (Host, Connection, Content-Length, Forwarded/X-Forwarded-*, WebSocket proof headers and similar) cannot be overridden here.", - "headersDocsOrder": "Values are literal; nginx variables are not interpreted. Request rules run before canonical forwarding metadata is applied, and response rules run before final RustProxy security/diagnostic policy.", - "cacheColdPolicyTitle": "Hot → cold policy", - "headerRule": "Header rule", - "headerRulesEmpty": "No rules yet. Add a rule below.", - "headerRuleHelp": "Choose Set, Append or Remove. Transport, forwarding identity and WebSocket protocol headers remain protected.", - "locationPreviewTitle": "Effective behavior preview", - "locationPreviewMatch": "Matching", - "locationPreviewHandling": "Handling", - "locationPreviewRewrite": "Backend path", - "locationPreviewCache": "Cache", - "locationPreviewAccess": "Access", - "locationPreviewHeaders": "Header rules", - "locationPreviewDisabled": "This location is disabled and will not match requests.", - "locationPreviewBackendSkipped": "backend is not contacted", - "locationPreviewVhostAcl": "Vhost authentication and ACL only", - "locationPreviewCustomAcl": "Vhost policy + location ACL", - "locationPreviewRequestRules": "request", - "locationPreviewResponseRules": "response", - "locationPreviewQueryPreserved": "query string is preserved", - "locationDocsMatchHeading": "1. Matching and precedence", - "locationDocsActionHeading": "2. Proxy, rewrite and local response", - "locationDocsPolicyHeading": "3. Access, cache and headers", - "locationDocsExamplesHeading": "Common examples", - "locationDocsExampleExact": "Health endpoint: Exact (=) /health + Custom response 200 answers locally without a backend connection.", - "locationDocsExampleStatic": "Static assets: Priority prefix (^~) /static/ keeps regex locations from overriding it; cache can be enabled for this location.", - "locationDocsExampleRegex": "Versioned API: Regex (~) ^/api/v[0-9]+/ can select a backend and use regex replacement groups such as $1.", - "cacheOutcomeHistory": "HIT / MISS / BYPASS activity", - "cacheHotMemoryHistory": "HOT RAM usage / limit", - "cacheTransientMemoryHistory": "Transient RAM usage / limit", - "cacheColdStorageHistory": "COLD storage usage / limit", - "processCpuHistory": "Process CPU history", - "processRssHistory": "Process RSS history", - "cacheMemoryBreakdown": "Hot / transient cache RAM", - "cacheTransientBreakdown": "Fill / cold queue / cold I/O", - "cacheColdStorageUsage": "COLD storage / budget", - "cacheColdStorageObjects": "objects", - "cacheTransientMemory": "Transient cache RAM", - "cacheTransientShort": "TOTAL", - "cacheFillShort": "fill", - "cacheQueueShort": "cold queue", - "cacheIoShort": "cold I/O", - "processRssTotal": "RSS total", - "memoryCacheHot": "HOT cache RAM / limit", - "memoryCacheTransient": "Cache transient RAM / limit", - "memoryTransform": "Transform reserved RAM / budget", - "memoryTransformInflight": "Active transforms / limit", - "memoryNonCacheRss": "Non-cache process RSS", - "transformMemoryUsage": "Transform RAM reservation", - "transformConcurrencyUsage": "Active transform slots", - "transformBudgetShort": "budget", - "processRssAnon": "Anonymous RSS", - "processRssFile": "File-backed / shared RSS", - "processDataSwap": "Data address space / swap", - "memoryAccountingHint": "RSS is the real process footprint. HOT/transient cache values are RustProxy-accounted allocations; transform memory is a conservative active reservation. Non-cache RSS is RSS minus accounted cache and includes transforms, connections, TLS, logging, Tokio/runtime state, allocator overhead and other buffers, so these rows are not all additive.", - "memoryAccountingInfo": "Memory accounting information", - "logTransport": "Transport", - "logWebSocket": "WebSocket", - "logUpstreamError": "Upstream error", - "listenerDefaultPortsHint": "New vhosts automatically select the standard HTTP :80 listener when it is available.", - "vhBackendDiagnostics": "Backend diagnostics", - "vhBackendHeader": "Expose selected backend (X-RustProxy-Backend)", - "vhBackendTimingHeader": "Expose backend response time (X-RustProxy-Backend-Time)", - "vhBackendDiagnosticsHelp": "Opt-in response headers. Backend time measures origin request-to-response-headers latency; responses served without contacting an origin do not report a backend.", - "authModeHelp": "Choose one authentication provider. The IP access list below is independent and can be combined with either provider.", - "taAdvanced": "Advanced Tinyauth settings", - "notfound": "404 hosts", - "notfoundTitle": "404 hosts, under control.", - "notfoundDescription": "Reserve domains that should answer locally with a status, placeholder or custom HTML.", - "notfoundPageIntro": "404 hosts reserve selected domains and answer locally without contacting a backend. They can use the same listeners and certificates as vhosts.", - "notfoundLayer": "LOCAL RESPONSE", - "notfoundList": "404 hosts", - "notfoundSearch": "Search 404 hosts", - "notfoundSearchPlaceholder": "ID, name or hostname…", - "notfoundNew": "New 404 host", - "notfoundEditor": "404 host configuration", - "notfoundId": "Host ID", - "notfoundName": "Display name", - "notfoundFrontend": "FRONTEND", - "notfoundBehavior": "RESPONSE", - "notfoundResponse": "Local response", - "notfoundMode": "Response type", - "notfoundModeWelcome": "RustProxy placeholder", - "notfoundModeEmpty": "Status only / empty", - "notfoundModeHtml": "Custom HTML", - "notfoundStatus": "HTTP status", - "notfoundHtml": "Custom HTML", - "notfoundResponseHelp": "The host never opens an upstream connection. The built-in placeholder is no-store and intentionally omits optional RustProxy diagnostic/server headers.", - "notfoundHosts": "Hostnames", - "notfoundNewLabel": "NEW", - "notfoundHostsRequired": "Add at least one hostname.", - "notfoundListenersRequired": "Select at least one listener.", - "notfoundHtmlRequired": "Custom HTML cannot be blank.", - "notfound204Empty": "HTTP 204, 205 and 304 responses must use Empty mode.", - "notfoundDuplicate": "A 404 host with this ID already exists.", - "notfoundUnsaved": "Discard unsaved 404 host changes?", - "notfoundEmpty": "No 404 hosts configured.", - "notfoundSearchEmpty": "No 404 hosts match the search.", - "prometheusTrustedNoToken": "Allow token-free reads from trusted CIDRs", - "prometheusTrustedNoTokenHelp": "Clients still must match Allowed client CIDRs. Enable only on a private, trusted monitoring network; forwarded headers are never trusted for this check.", - "docker-vhosts": "Docker vhosts", - "docker-vhostsTitle": "Docker services, named.", - "docker-vhostsDescription": "Discover published Docker ports and map services to base-domain names, custom domains and aliases without copying observed state into SQLite.", - "dockerVhosts": "Docker vhosts", - "dockerVhostsIntro": "Published TCP ports discovered by configured Docker agents. Map each service to a short name under the agent base domain or to any FQDN, add aliases, choose the Docker host IP, and inspect or tune live backend health.", - "dockerDiscovery": "DOCKER DISCOVERY", - "dockerRefresh": "Refresh agents", - "dockerAgentsSettings": "Docker agents settings", - "dockerAgents": "Docker agents", - "dockerDesiredState": "DESIRED STATE / SQLITE", - "dockerAgentsHelp": "Configure one agent per Docker host. HTTPS with a persistent self-signed certificate is the agent default; paste its SHA-256 fingerprint to pin it. Each agent keeps its own base domain and optional default backend IP.", - "dockerAgentAdd": "Add Docker agent", - "dockerAgentsSave": "Save and connect agents", - "dockerAgent": "DOCKER AGENT", - "dockerAgentNew": "New agent", - "dockerAgentId": "Agent ID", - "dockerAgentName": "Display name", - "dockerAgentEndpoint": "Agent endpoint", - "dockerAgentToken": "Bearer token", - "dockerBaseDomain": "Base domain", - "dockerTargetOverride": "Target IP override", - "dockerTargetAuto": "automatic", - "dockerListeners": "Listeners", - "dockerListenersRequired": "Select at least one listener for the Docker agent.", - "dockerNoAgents": "No Docker agents configured.", - "dockerNoAgentsConfigured": "Configure a Docker agent in Settings first.", - "dockerPublish": "Publish", - "dockerPreserveHost": "Preserve Host", - "dockerServiceMissing": "Not present in the latest inventory", - "dockerConnected": "CONNECTED", - "dockerError": "ERROR", - "dockerWaiting": "WAITING", - "dockerAgentDisabled": "This Docker agent is disabled.", - "dockerNoObservation": "No observation yet. Refresh the agents.", - "dockerHost": "Docker host", - "dockerDetectedIp": "Detected IP", - "dockerEffectiveIp": "Routing IP", - "dockerNoPublishedPorts": "No published TCP ports were reported.", - "dockerAssignmentsSave": "Save Docker vhosts", - "dockerDefaultTarget": "Default backend IP", - "dockerDetectedIps": "Detected host IPs", - "dockerHostname": "Hostname", - "dockerHostnameHelp": "Short names use {domain}; a full FQDN uses its own domain.", - "dockerAliases": "Aliases", - "dockerAliasAdd": "Add alias", - "dockerAliasesHelp": "Add aliases with Enter or comma. Short names use the agent base domain; full FQDNs are kept as entered.", - "dockerBackendIp": "Backend host IP", - "dockerBackendIpAuto": "Auto / agent default", - "dockerBackendScheme": "Backend protocol", - "dockerBindingIp": "Docker binding", - "dockerAgentDefaultIp": "agent default", - "dockerAgentEndpointHelp": "Use https:// for the default agent setup. Plain HTTP is supported only for compatibility and is visibly marked insecure.", - "dockerTlsFingerprint": "TLS certificate SHA-256", - "dockerTlsFingerprintHelp": "For the default self-signed agent certificate, paste the fingerprint printed by install.sh or rustproxy-docker-agent --print-fingerprint. Leave empty only for a certificate trusted by a public CA.", - "dockerTlsPinned": "TLS PINNED", - "dockerTlsPublicCa": "TLS / PUBLIC CA", - "dockerTlsPlain": "PLAIN HTTP", - "dockerTlsPlainHelp": "The agent token and inventory are not protected by TLS on this endpoint.", - "dockerTlsPublicCaHelp": "Certificate chain and hostname are verified against the system/public CA roots.", - "dockerAgentConnectionFailed": "Agent connection failed", - "dockerAgentVersion": "Agent version", - "dockerLastChecked": "Last checked", - "dockerNeverChecked": "never", - "dockerJustNow": "just now", - "dockerSecondsAgo": "{seconds}s ago", - "dockerMinutesAgo": "{minutes}m ago", - "dockerBackendMissing": "SERVICE MISSING", - "dockerBackendNotActive": "NOT PUBLISHED", - "dockerBackendChecking": "CHECKING", - "dockerBackendHealthOff": "HEALTH OFF", - "dockerBackendUp": "UP", - "dockerBackendDown": "DOWN", - "dockerBackendDraining": "DRAINING", - "dockerBackendNoMetrics": "Runtime metrics appear after the dynamic backend is active.", - "dockerHealthCheck": "Backend health check", - "dockerHealthSummary": "Active TCP/HTTP probes; passive health is configured separately", - "dockerPassiveHealth": "Passive backend health", - "dockerPassiveHealthSummary": "Off by default; opt in to health decisions from normal requests", - "dockerHealthMode": "Mode", - "dockerHealthHttp": "HTTP", - "dockerHealthTcp": "TCP", - "dockerHealthOff": "Off", - "dockerHealthPath": "HTTP path", - "dockerHealthHost": "Health Host header", - "dockerHealthHostAuto": "backend host / automatic", - "dockerHealthInterval": "Interval (ms)", - "dockerHealthTimeout": "Timeout (ms)", - "dockerHealthSlow": "Slow threshold (ms)", - "dockerHealthFall": "Failures to mark down", - "dockerHealthRise": "Successes to mark up", - "dockerHealthStatusMin": "Min HTTP status", - "dockerHealthStatusMax": "Max HTTP status", - "dockerHealthLatency": "Latency", - "dockerHealthResult": "Last probe", - "dockerHealthRequests": "Requests", - "dockerHealthErrors": "Errors", - "dockerHealthActive": "Active", - "loggingPerVhost": "Separate file per vhost", - "loggingDirectory": "Per-vhost directory", - "loggingPerVhostHelp": "When enabled, each vhost writes to its own JSONL file. Unmatched traffic uses _unmatched.jsonl.", - "listenerStartupLocked": "This startup/systemd address cannot be changed or removed here. Other addresses and listener options remain editable.", - "administrationStartupLocked": "This address is fixed by the process startup/systemd configuration and cannot be changed here.", - "notfoundStatusInvalid": "Select a supported HTTP error status.", - "tlsEffectiveCiphers": "Currently effective cipher suites", - "certificateManaged": "ACME managed", - "certManagedReadOnly": "ACME-managed certificates are renewed automatically and cannot be edited as custom file certificates.", - "vhWebsocketTransport": "WebSocket transport", - "vhWebsocketHttp1": "HTTP/1.1 Upgrade (recommended)", - "vhWebsocketRfc9220": "RFC 9220 Extended CONNECT over HTTP/3", - "vhWebsocketTransportHelp": "Enable WebSocket with the separate switch, then choose the transport. H2/H3 modes use Extended CONNECT while HTTP/1.1 Upgrade remains the fallback.", - "dockerLiveView": "Live view", - "dockerPollInterval": "Agent poll interval (s)", - "dockerPollIntervalHelp": "How often RustProxy contacts this agent for Docker inventory. Live view only reads RustProxy memory.", - "dockerAgentPollEvery": "Agent poll interval", - "dockerSearch": "Search Docker vhosts", - "dockerSearchPlaceholder": "Search service, host, alias or agent...", - "dockerShowHidden": "Show Hidden", - "dockerHideHidden": "Hide Hidden", - "dockerHideVhost": "Hide from view", - "dockerUnhideVhost": "Show in view", - "dockerCertificate": "Frontend certificate", - "dockerCertificateHelp": "Certificate assigned to every generated vhost for this agent. Default uses normal TLS/SNI certificate selection.", - "dockerDefaultAcl": "Default ACL profile", - "dockerDefaultAclNone": "No default ACL profile", - "dockerDefaultAclHelp": "Inherited by generated Docker vhosts that do not select their own per-app ACL profile.", - "dockerApplyService": "Apply changes", - "dockerAccessControl": "Access control", - "dockerAclSummary": "Optional ACL profile and per-app IP rules", - "dockerAclProfile": "ACL profile", - "dockerAclProfileNone": "No ACL profile", - "dockerAclProfileInherit": "Agent default", - "dockerAclInline": "Use additional per-app ACL", - "dockerAclDefault": "Default ACL action", - "dockerAclRules": "IP/CIDR rules", - "dockerAclHelp": "One rule per line, for example allow 10.0.0.0/8. Profile and per-app ACL are both enforced.", - "dockerProxyTimeouts": "Proxy timeouts", - "dockerProxyTimeoutsHelp": "Explicit per-app limits: connect 10 s, response headers 120 s, upstream read 60 s and request send 60 s by default.", - "dockerHostnameRequired": "Hostname is required before publishing.", - "dockerForceSsl": "Redirect HTTP to HTTPS", - "dockerForceSslUnavailable": "Requires an active plain HTTP listener and one unambiguous TLS listener on this agent.", - "dockerWebsockets": "WebSockets", - "dockerCompression": "Compression", - "dockerCompressionHelp": "Enable or disable compression for this Docker vhost. The enabled codecs and their priority come from global Response settings.", - "dockerCache": "Cache", - "vhProxyTimeouts": "Proxy timeouts", - "vhProxyConnectTimeout": "Proxy connect timeout (s)", - "vhProxyHeaderTimeout": "Proxy response header timeout (s)", - "vhProxyReadTimeout": "Proxy read timeout (s)", - "vhProxySendTimeout": "Proxy send timeout (s)", - "vhProxyTimeoutsHelp": "Defaults: connect 10 s, response headers 120 s, read 60 s and send 60 s. Read/send are idle-gap timeouts for streaming bodies.", - "notfoundModeBuiltin": "Application-style status page", - "pageBuiltin": "Built-in status page", - "vhOpenBrowser": "Open", - "securityPasswordHint": "Requires the current password and signs out all sessions.", - "securityAccessControl": "ACCESS CONTROL", - "securityPoliciesHint": "Keep the policy list visible; open the editor only when adding or changing a rule set.", - "securityPolicyEditorHint": "Hostname scope, default action and CIDR rules.", - "securityAuditOpen": "Administration audit", - "securityAuditHint": "Recent administration changes are loaded only when this window is opened.", - "dockerConfigureService": "Configure vhost", - "dockerAliasesShort": "aliases", - "dockerAgentDetails": "Agent details", - "dockerServices": "Services", - "dockerOpenApp": "Open app", - "networkInterfacesHistory": "Traffic by interface", - "networkInterfaceRate": "RX / TX", - "networkNoInterfaces": "No non-loopback network interfaces are available.", - "networkInterfacesCount": "{count} interfaces", - "logExpandBuffer": "Increase buffer", - "logExpandedBuffer": "Buffer 4096", - "logBufferExpanded": "Live buffer increased to {count} retained entries.", - "vhIdRenameHelp": "Changing the vhost ID starts a new metrics/log/cache identity and resets its live health/traffic history. Hostnames and configured backend IDs are unchanged.", - "vhIdRenameConfirm": "Change vhost ID from {old} to {new}? Live health/traffic history and metric/cache identity for this vhost will restart.", - "cacheDomainFilter": "Show", - "cacheDomainFilterAll": "All domains", - "cacheDomainFilterEnabled": "Cache enabled", - "cacheDomainFilterDisabled": "Cache disabled", - "cacheNoDomainsFiltered": "No domains match this filter.", - "cacheColdCleanup": "Clean cold cache", - "cacheColdCleanupHelp": "Runs asynchronously and uses the same eviction mechanism as automatic cold-cache GC.", - "cacheColdUnusedSeconds": "Unused for at least (seconds, optional)", - "cacheColdOldestCount": "Remove up to oldest objects", - "cacheColdHitsZero": "Only objects with hits = 0", - "cacheColdCleanupStart": "Start cleanup", - "cacheColdCleanupRunning": "Cold-cache cleanup is running…", - "cacheColdCleanupDone": "Removed {count} objects and freed {bytes}.", - "cacheColdCleanupFailed": "Cold-cache cleanup failed.", - "cacheColdCleanupInvalidJob": "Cold-cache cleanup did not return a job identifier.", - "cacheColdGcInterval": "Cold-cache GC interval (seconds)", - "cacheColdGcMaxDelete": "Maximum deletes per GC pass", - "transformSettings": "Static response transformation", - "transformEnable": "Enable transformation engine globally", - "transformHelp": "Transformation happens after upstream decoding and before cache storage and client compression. Each vhost must opt in separately.", - "transformJavascript": "JavaScript minification", - "transformCss": "CSS minification", - "transformHtml": "HTML minification", - "transformSvg": "SVG optimization", - "transformJson": "JSON compaction", - "transformExtensions": "Allowed file extensions, one per line", - "transformMaxBody": "Maximum transformable response (KiB)", - "transformResourcePresetTitle": "Resource preset", - "transformResourcePresetHelp": "Presets tune the temporary memory ceiling, maximum transformable response size and global concurrent transformation limit. Content types and extension allow-lists are left unchanged.", - "transformPresetConservative": "RAM saver", - "transformPresetRecommended": "Recommended", - "transformPresetPerformance": "Performance", - "transformPresetUber": "Uber performance", - "transformMemoryBudget": "Temporary transform memory budget (MiB)", - "transformMaxInflight": "Maximum concurrent transformations", - "transformMaxInflightShort": "concurrent", - "transformResourceCurrent": "Current limits", - "transformBudgetTooSmallWarning": "The memory budget is smaller than the worst-case temporary buffers for one maximum-size compressed response. Such responses can be skipped with memory_pressure.", - "transformBudgetHighWarning": "The transform memory ceiling is more than 50% of effective host/cgroup RAM:", - "transformSafety": "Cache-Control: no-transform is always respected. Transformed representations receive a new ETag and are cached before gzip, Brotli or zstd encoding.", - "saveTransform": "Apply transformation policy", - "vhTransform": "Transform", - "vhTransformEnable": "Enable transformations for this vhost", - "vhTransformExtensionsCustom": "Override global transform extensions", - "vhTransformHelp": "The global engine must also be enabled. Format selectors inherit the global policy unless explicitly enabled or disabled here.", - "dockerTransform": "Transform static responses", - "vhPassive5xxWarning": "Counting HTTP 5xx can quarantine the whole backend after repeated application errors from one endpoint.", - "vhPassiveSlowWarning": "Counting slow responses can quarantine the backend after repeated responses that are correct but exceed the slow-response threshold.", - "transform": "Transform", - "transformTitle": "Static response transformation.", - "transformDescription": "Runtime transformation effectiveness, savings, skips and cache reuse.", - "transformApplied": "Transformed responses", - "transformExamined": "Examined", - "transformCacheHits": "Transformed cache HITs", - "transformCacheHitsHelp": "Served without running the transformer again", - "transformSaved": "Bytes saved before compression", - "transformReduction": "Average reduction", - "transformReductionHelp": "For responses that changed representation", - "transformSkipped": "Skipped / fail-open", - "transformErrors": "Body / transport errors", - "transformAvgTime": "Avg time", - "transformByType": "Transformations by type", - "transformType": "Type", - "transformSkipReasons": "Skip / fail-open reasons", - "transformNoSkips": "No skipped transformations in this scope.", - "transformNoSkipsShort": "No skips", - "transformVhosts": "Vhost effectiveness", - "transformScopeHint": "Counters are in-memory runtime diagnostics. A transformed cache HIT does not execute the transformer again.", - "transformNoData": "No transform activity yet.", - "transformBadge": "transformed", - "cacheLiveChartTitle": "Live cache activity", - "cacheLiveChartHelp": "HIT / MISS / BYPASS per second for the selected scope", - "liveChartSamples": "Samples", - "transformLiveChartTitle": "Live transformation activity", - "transformLiveChartHelp": "Applied / cache HIT / skipped / errors per second for the selected scope", - "transformChartApplied": "Applied", - "transformChartCacheHits": "Cache HIT", - "transformChartSkipped": "Skipped", - "cacheInvalidationTitle": "Cache purge / ban", - "cacheInvalidationOperation": "Operation", - "cacheInvalidationSelector": "Selector", - "cacheInvalidationValue": "Value", - "cacheInvalidationHost": "Host (optional)", - "cacheBanTtl": "BAN TTL (seconds)", - "cacheInvalidationHelp": "The selected vhost limits the operation. Exact and prefix values include the query string. Regex and root-prefix / operations require an additional confirmation.", - "cacheRunInvalidation": "Run operation", - "cacheActiveBans": "Active BAN rules", - "cacheBanExpires": "Expires", - "cacheNoBans": "No active BAN rules.", - "cacheTarget": "Target", - "cacheKeyTags": "Cache key / tags", - "cachePurgeUrl": "Purge URL", - "cacheRemoveBan": "Remove BAN", - "cacheBanCreated": "BAN created", - "cacheConfirmPurgeUrl": "Purge this cached URL from RAM and cold storage?", - "cacheConfirmRemoveBan": "Remove this BAN rule?", - "cacheWideInvalidationConfirm": "This broad invalidation may affect many cached objects. Continue?", - "cacheCollapseWaiters": "waiting", - "cacheCollapsed": "collapsed", - "actions": "Actions", - "cacheCollapseTitle": "Request collapsing", - "cacheCollapseEnable": "Collapse concurrent fills for one cache key", - "cacheCollapseTimeout": "Follower wait timeout (ms)", - "cacheCollapseFallback": "Timeout fallback", - "cacheCollapseFallbackOrigin": "Origin", - "cacheCollapseFallbackStale": "Stale if available", - "cacheStaleErrorTitle": "Stale-if-error policy", - "cacheSieTimeout": "Timeout", - "cacheSieConnection": "Connection failure", - "cacheSieDns": "DNS failure", - "cacheSieTls": "TLS failure", - "cacheSieStatuses": "HTTP 5xx statuses eligible for stale fallback", - "cacheProactiveTitle": "Proactive refresh", - "cacheProactiveEnable": "Refresh sampled cached objects before expiry", - "cacheProactiveBefore": "Refresh before expiry (seconds)", - "cacheProactivePercent": "Sampled objects (%)", - "cacheProactiveInflight": "Maximum concurrent refreshes", - "cacheProactiveHelp": "Uses validator revalidation, deterministic sampling and a global concurrency cap to avoid refresh storms.", - "cacheInvalidationOpen": "Purge / Ban", - "cacheBanListOpen": "Active BANs", - "cacheBanRemoved": "BAN rule removed", - "tunnelsOpen": "Active tunnels", - "tunnelsTitle": "Active WebSocket / tunnels", - "tunnelsReload": "Reload", - "tunnelsEmpty": "No active tunnels.", - "tunnelId": "ID", - "tunnelClient": "Client", - "tunnelHost": "Host", - "tunnelUpstream": "Upstream", - "tunnelProtocol": "Protocol", - "tunnelDuration": "Duration", - "tunnelRx": "RX", - "tunnelTx": "TX", - "tunnelKill": "Kill", - "tunnelKilled": "Tunnel closed by administrator", - "tunnelKillConfirm": "Close this tunnel?", - "tunnelKillReason": "Close reason", - "tunnelKillReasonPlaceholder": "Administrative close", - "tunnelKillReasonDefault": "admin panel", - "listenerH2c": "h2c cleartext HTTP/2 (prior knowledge + HTTP/1.1 Upgrade)", - "vhWebsocketRfc8441": "RFC 8441 Extended CONNECT over HTTP/2", - "wsH1": "H1 Upgrade", - "wsH2Rfc8441": "H2 RFC 8441", - "wsH3Rfc9220": "H3 RFC 9220", - "wsEstablished": "Established", - "httpProtocolMix": "HTTP protocol mix", - "wsProtocolMix": "WebSocket protocol mix", - "vhWebsocketMode": "WebSocket transport", - "vhWebsocketDisabled": "Disabled", - "vhWebsocketH1": "HTTP/1.1 Upgrade", - "vhWebsocketH2Fallback": "HTTP/2 RFC 8441 + H1 fallback", - "vhWebsocketH3Fallback": "HTTP/3 RFC 9220 + H1 fallback", - "vhWebsocketH2H3Fallback": "HTTP/2 + HTTP/3 + H1 fallback", - "vhMaxConcurrentRequests": "Max concurrent requests", - "vhBackendQueueTimeout": "Capacity queue timeout (ms)", - "vhBackendCapacityHelp": "0 concurrent requests means unlimited. A queue timeout of 0 rejects immediately with HTTP 503 when the backend is at capacity.", - "vhLastFailure": "Last failure", - "vhLastRecovery": "Last recovery", - "certificateIssuerName": "Certificate issuer", - "certificateValidFrom": "Valid from", - "certificateDaysRemaining": "Days remaining", - "certificateExpired": "Certificate expired", - "certificateExpiresSoon": "Certificate expires soon", - "certificateRenewalStatus": "Renewal status", - "certificateRenewalError": "Last renewal error", - "vhActiveHealth": "Active health", - "vhPassiveFailures": "Passive failures", - "certificateAdminRestartRequired": "Administration TLS uses a startup-fixed listener; restart RustProxy to refresh that certificate.", - "cacheTabGeneral": "General", - "cacheTabResilience": "Resilience", - "cacheTabCold": "Cold cache", - "cacheTabDiagnostics": "Static & diagnostics", - "certificateApiTitle": "Certificate API", - "certificateApiHelp": "Create separate Key ID + Bearer Token credentials for certificate automation. New tokens are reload-only by default and are restricted to the listed certificate IDs. Enable deploy only for automation that must upload certificate material.", - "certificateApiKeyId": "Key ID", - "certificateApiToken": "Bearer token (optional custom value)", - "certificateApiTokenHelp": "Leave the field empty to generate a strong token. Each cleartext token is shown only once; RustProxy stores only its hash.", - "certificateApiRotate": "Generate / rotate token", - "certificateApiRevoke": "Revoke token", - "certificateApiOneTimeToken": "Copy this token now", - "certificateApiCurlHelp": "External automation sends the Key ID header and Bearer token over HTTPS (or loopback HTTP). Reload tokens must use the targeted /api/certificates/{id}/reload endpoint; a token cannot call the global reload endpoint.", - "certificateApiUnknown": "Loading certificate API status…", - "certificateApiConfigured": "Certificate API enabled", - "certificateApiDisabled": "Certificate API disabled", - "certificateApiLastUsed": "last used", - "certificateApiRotated": "Certificate API token created / rotated. Copy the token now.", - "certificateApiRevoked": "Certificate API token revoked.", - "certificateApiRevokeConfirm": "Revoke the external certificate API token? Certbot/API deployments using it will stop immediately.", - "certificate_api.invalid_token": "Invalid certificate API Key ID or Bearer Token.", - "certificate_api.https_required": "External certificate API tokens require HTTPS unless the request comes from loopback.", - "certificate_api.invalid_token_format": "Certificate API tokens must be 32–128 URL-safe characters.", - "certificate_api.invalid_key_id": "Invalid certificate API Key ID.", - "vhWebsocketLegacySupport": "Enable WebSocket legacy support", - "vhWebsocketLegacySupportHelp": "For older appliances and embedded servers. WebSocket handshakes use HTTP/1.1 upstream, Host and Origin are aligned to the configured backend authority, and legacy 101 response framing is tolerated. Ordinary HTTP traffic is unchanged.", - "vhWebsocketLegacyBadge": "WebSocket legacy", - "runtimeInformation": "Runtime information", - "runtimeInformationHelp": "Reported by the running RustProxy process and available only from the authenticated administrator status API.", - "runtimePlatform": "Platform", - "statisticsSource": "Statistics source", - "dataDirectory": "Data directory", - "databaseLocation": "Database", - "connectionActivity": "CONNECTION ACTIVITY", - "reloadCertificate": "Reload certificate", - "certificateApiComment": "Comment", - "certificateApiCertificateIds": "Allowed certificate IDs", - "certificateApiCertificateIdsHelp": "One or more certificate IDs separated by commas or new lines. The token is rejected for every other certificate ID.", - "certificateApiCertificateIdsRequired": "Enter at least one certificate ID for this token.", - "certificateApiAllowDeploy": "Also allow deploy", - "certificateApiAllowDeployHelp": "Leave disabled for Certbot/acme.sh renewal hooks. Reload-only is the recommended least-privilege mode.", - "certificateApiScopeReloadOnly": "reload only", - "certificateApiScopeDeployReload": "deploy + reload", - "certificateApiAllowedIds": "allowed IDs", - "certificateApiConfiguredCount": "Certificate API tokens: {count} / {max}", - "certificateApiNeverUsed": "never used", - "certificateApiRevokeAll": "Revoke all tokens", - "certificateApiRevokeOneConfirm": "Revoke Certificate API token {key}? Any automation using it will stop immediately.", - "certificateApiRevokeAllConfirm": "Revoke every Certificate API token? All external certificate automation using these tokens will stop immediately.", - "certificate_api.invalid_comment": "Certificate API comments must be at most 256 characters and contain no control characters.", - "certificate_api.limit_reached": "The Certificate API token limit has been reached.", - "certificate_api.not_found": "Certificate API token not found.", - "certificate_api.invalid_certificate_ids": "Certificate API tokens require 1-32 valid certificate IDs.", - "certificate_api.target_required": "Certificate API tokens cannot reload every certificate at once; use the targeted certificate reload endpoint.", - "certificate_api.certificate_forbidden": "This Certificate API token is not allowed to operate on that certificate ID.", - "certificate_api.deploy_forbidden": "This Certificate API token is reload-only. Rotate it with deploy permission only if upload/deploy automation is required.", - "networkInterfacesVisibleCount": "{visible} / {total} interfaces", - "hideMetric": "Hide", - "showMetric": "Show", - "hiddenInterfaces": "Hidden interfaces", - "networkNoVisibleInterfaces": "All detected interfaces are hidden in this browser.", - "cacheMemorySizingTitle": "Memory sizing", - "cacheMemorySizingHelp": "Presets use effective memory available to RustProxy (host RAM capped by cgroup when present). They tune HOT, transient RAM, object size, entry count and the demotion queue; the COLD disk budget is left unchanged.", - "cachePresetConservative": "Conservative", - "cachePresetRecommended": "Recommended", - "cachePresetPerformance": "Performance", - "cachePresetSelected": "Selected", - "cachePresetCustom": "Custom", - "cacheColdPresetTitle": "COLD policy preset", - "cacheColdPresetHelp": "The recommended policy enables idle/LRU/shutdown demotion, sizes the COLD queue from transient RAM and uses conservative GC timings. Disk path, disk budget and object limit are left unchanged.", - "cacheTransientLimit": "Transient RAM budget (MiB)", - "cacheEvictionLimit": "LRU removals per admission", - "cacheColdQueueLimit": "COLD queue RAM budget (MiB)", - "cacheMemoryNotDetected": "Memory not detected", - "cacheMemoryPresetUnavailable": "Automatic presets require host or cgroup memory telemetry.", - "cacheMemoryCgroup": "Effective cgroup RAM", - "cacheMemoryDetected": "Detected RAM", - "cacheQueueOverTransientWarning": "COLD queue budget cannot exceed the transient RAM budget.", - "cacheMemoryHighWarning": "Warning: HOT + transient cache budgets are a large share of effective RAM:", - "cachePresetTransientShort": "transient", - "cachePresetObjectShort": "max object", - "cachePresetQueueShort": "COLD queue", - "cacheLogicalShort": "logical", - "cacheLimitShort": "limit", - "cacheMemoryPressureShort": "memory pressure", - "cacheNukeLimitShort": "LRU limit", - "cacheMemoryPresetTooSmall": "Automatic cache presets are disabled below 256 MiB of effective RAM; configure the cache manually if this is intentional.", - "cacheMemoryRecommendedFor": "Recommended for detected memory:", - "cacheMemoryHeadroomWarning": "Warning: the additional cache budget is large compared with currently available host/cgroup memory:", - "cacheObjectOverTransientWarning": "Maximum object exceeds the transient RAM budget; objects near that size will pass through instead of being cached.", - "vhCacheGlobalDisabled": "Global cache is disabled. This vhost cannot enable cache until the global cache is enabled.", - "responseLatencyInfo": "Response time information", - "responseLatencyHint": "The value is the cumulative mean duration of completed HTTP exchanges since process start. The chart shows successive samples of that mean; it is not a percentile or the latency of one request.", - "web-server": "Web Server", - "web-serverTitle": "Serve local files.", - "web-serverDescription": "Create reusable Web Services and attach them to vhosts or Custom locations.", - "webServer": "Web Server", - "webServerIntro": "Create reusable local Web Services here, then attach them to a vhost or Custom location.", - "webServiceList": "Web Services", - "webServiceSearch": "Search Web Services", - "webServiceSearchPlaceholder": "Search name, ID or document root", - "webServiceNew": "New Web Service", - "webServiceEditor": "Web Service configuration", - "webServiceTabGeneral": "General", - "webServiceTabRouting": "Routing", - "webServiceTabMime": "MIME", - "webServiceTabCacheHeaders": "Cache & headers", - "webServiceTabDelivery": "Delivery", - "webServiceTabPhp": "PHP-FPM", - "webServiceTabSecurity": "Security", - "webServiceId": "Service ID", - "webServiceName": "Name", - "webServiceRoot": "Document root", - "webServiceIndex": "Index files", - "webServiceDirectoryListing": "Directory listing", - "webServiceMimeOverrides": "MIME overrides", - "webServiceMimeBuiltin": "Built-in mappings", - "webServiceMimeHelp": "Optional extension-to-Content-Type overrides, one per line. Use: ext = media/type.", - "webServiceMimeInvalid": "MIME overrides must use one unique 'ext = media/type' entry per line.", - "webServiceDefaultMime": "Default MIME type", - "webServiceCacheControl": "Cache-Control", - "webServiceCacheRules": "Cache-Control rules", - "webServiceCacheRulesHelp": "Specific rules are evaluated in order before the default policy. Match ext:css or mime:image/.", - "webServiceCacheRulesInvalid": "Cache-Control rules must use matcher = value, for example ext:css = public, max-age=3600.", - "webServiceResponseHeaders": "Response headers", - "webServiceResponseHeadersHelp": "Fixed headers for this Web Service. Vhost/location response headers run later and can override them.", - "webServiceResponseHeadersInvalid": "Response headers must use one unique Header-Name = value entry per line.", - "webServiceCacheHelp": "Eligible Web Server responses use RustProxy object cache when vhost caching is enabled. Private and no-store responses are not cached.", - "webServiceTryFiles": "try_files", - "webServiceTryFilesHelp": "One candidate per line. Supports $uri, $query_string and a final =404/other error status.", - "webServiceRewrites": "Rewrite rules", - "webServiceRewritesHelp": "One regex rule per line. Use pattern => /target for an internal rewrite or pattern => 301:/target for a redirect.", - "webServiceRewritesInvalid": "Rewrite rules must use pattern => /target or pattern => 301:/target (301/302/307/308).", - "webServiceCompression": "Dynamic compression", - "webServiceCompressionHelp": "Inherits the vhost/global policy by default. Individual codecs can be filtered here; globally disabled codecs remain unavailable and minimum size stays global.", - "webServicePrecompressed": "Serve precompressed sidecars", - "webServicePrecompressedBr": "Brotli (.br)", - "webServicePrecompressedGzip": "Gzip (.gz)", - "webServiceDownloadExtensions": "Download extensions", - "webServiceDownloadExtensionsHelp": "Files with these extensions receive Content-Disposition: attachment.", - "webServiceRangeHelp": "Static and precompressed representations keep validators, single and multipart byte-range requests, 206/416 and streaming delivery.", - "webServicePhpEnabled": "Enable PHP-FPM / FastCGI", - "webServicePhpEndpoint": "PHP-FPM endpoint", - "webServicePhpEndpointHelp": "Unix: unix:/run/php/php-fpm.sock. TCP: tcp:127.0.0.1:9000 or tcp:[::1]:9000. TCP is intentionally limited to loopback addresses.", - "webServicePhpExtensions": "PHP extensions", - "webServicePhpPassAuthorization": "Forward Authorization header", - "webServicePhpConnectTimeout": "Connect timeout (ms)", - "webServicePhpRequestTimeout": "Total FastCGI request timeout (ms)", - "webServicePhpConcurrency": "Max concurrent PHP requests", - "webServicePhpBodyLimit": "PHP request body limit (bytes, 0 = unlimited)", - "webServicePhpHeaderLimit": "Max FastCGI response header bytes", - "webServicePhpStderrLimit": "Max PHP-FPM stderr log bytes", - "webServicePhpCustomParams": "Custom FastCGI parameters", - "webServicePhpCustomParamsHelp": "One NAME = value per line, e.g. APP_ENV = production. Names: A-Z, digits, underscores; no HTTP_*, PHP_VALUE, PHP_ADMIN_VALUE or core CGI parameters.", - "webServicePhpCustomParamsInvalid": "Custom FastCGI parameters must use one unique NAME = value entry per line.", - "webServicePhpCustomDataTooLarge": "Custom FastCGI parameters and PHP INI overrides may use at most 128 KiB combined.", - "webServicePhpValues": "PHP INI overrides (PHP_VALUE)", - "webServicePhpValuesHelp": "One directive = value per line, e.g. memory_limit = 256M, upload_max_filesize = 64M, post_max_size = 64M; PHP_FPM decides which can be changed.", - "webServicePhpValuesInvalid": "PHP INI overrides must use one unique directive = value entry per line.", - "webServicePhpDocsButton": "FastCGI syntax and safety", - "webServicePhpDocsTitle": "PHP-FPM / FastCGI configuration", - "webServicePhpDocsIntro": "RustProxy connects directly to an existing PHP-FPM pool. Prefer a Unix socket on the same host. TCP is accepted only on loopback and is not a public/network FastCGI transport.", - "webServicePhpDocsEndpointTitle": "Endpoint syntax", - "webServicePhpDocsEndpoint": "Use unix: plus full socket path, or tcp: plus a literal loopback IP and port. TCP hostnames/public addresses are rejected. Use distinct FPM sockets for applications with different PHP INI restrictions.", - "webServicePhpDocsCustomTitle": "Custom FastCGI parameters", - "webServicePhpDocsCustom": "One NAME = value per line (NAME starts with a letter and contains A-Z, digits or underscore). Static values only. SCRIPT_FILENAME, HTTP_* and PHP_VALUE/PHP_ADMIN_VALUE are reserved.", - "webServicePhpDocsValuesTitle": "PHP INI overrides", - "webServicePhpDocsValues": "These lines go into PHP_VALUE. Use the separate PHP_ADMIN_VALUE field for trusted administrative settings; neither one isolates workers sharing the same PHP-FPM pool.", - "webServicePhpDocsLimitsTitle": "Limits and headers", - "webServicePhpDocsLimits": "Request timeout covers the FastCGI exchange. The body limit is enforced while streaming and returns 413 when exceeded; 0 disables the RustProxy-side body limit. Response-header and stderr limits bound memory/diagnostic data. Up to 64 custom FastCGI parameters and 64 PHP INI overrides are accepted, with at most 128 KiB of combined custom data. Authorization forwarding can be disabled for applications that do not need HTTP_AUTHORIZATION.", - "webServicePhpDocsSecurity": "Do not put secrets in custom parameters or PHP_VALUE unless you accept storing them in normal Web Service configuration. Use a dedicated non-root PHP-FPM pool identity and keep PHP-FPM off public networks.", - "webServicePhpHelp": "RustProxy speaks FastCGI directly to a long-running PHP-FPM pool. PHP is not spawned per request and PHP source is never served when PHP handling is selected.", - "webServicePhpSymlinkConflict": "PHP-FPM currently requires the symlink policy to be Deny.", - "webServiceSymlinkPolicy": "Symlink policy", - "webServiceSymlinkDeny": "Deny", - "webServiceSymlinkInsideRoot": "Allow only inside document root (Linux)", - "webServiceSafety": "Document root is a strict filesystem boundary. The default denies symlinks. Linux can optionally follow only targets that resolve inside the root; PHP-FPM currently requires the deny policy.", - "webServiceSelect": "Select Web Service", - "webService": "Web Service", - "webServiceUsedBy": "Used by", - "webServiceUnused": "Not attached", - "webServiceEmpty": "No Web Services yet.", - "webServiceSearchEmpty": "No Web Services match this search.", - "webServiceInUse": "Web Service is still used by", - "webServiceDelete": "Delete this Web Service?", - "webServiceIndexRequired": "Index files are optional.", - "webServiceDuplicate": "A Web Service with this ID already exists.", - "webServiceRequired": "Select a Web Service.", - "sourceType": "Source type", - "sourceReverseProxy": "Reverse Proxy", - "sourceWebServer": "Web Server", - "sourceHelp": "The vhost stores only the source choice. Web Server settings remain in the selected reusable Web Service.", - "locationWebServerAction": "Web Server", - "locationWebServerShort": "Web Server", - "docsReader": "Reader", - "docsApi": "API", - "docsReaderSearch": "Search documentation", - "docsReaderSearchPlaceholder": "Search title or path", - "docsReaderEmpty": "No documentation matches this search.", - "docsCategoryProject": "Project", - "docsCategoryGuides": "Functional guides", - "docsCategoryTechnical": "Technical", - "docsCategoryAudits": "Audits and history", - "docsCategoryHistorical": "Historical", - "webServiceIndexHelp": "Optional. Leave empty to skip index lookup; with directory listing enabled, the file list is shown directly.", - "webServiceNoIndex": "No index files", - "vhBackendRequired": "Reverse Proxy requires at least one backend. Add a backend or change the source to Web Server.", - "vhListenerRequired": "Select at least one listener for this vhost.", - "traffic": "Traffic", - "trafficTitle": "Traffic, per vhost.", - "trafficDescription": "Bounded request analytics: countries, User-Agents, client IPs, paths, routes and protocol outcomes.", - "trafficSinceStart": "Since process start", - "trafficResponseBytes": "Response bytes", - "trafficRequestBytes": "Request bytes", - "trafficMeanLatency": "Mean latency", - "trafficDroppedSamples": "Dropped analytics samples", - "trafficBoundedLabel": "BOUNDED / ASYNC", - "trafficDimensionsTitle": "Traffic dimensions", - "trafficApproxHelp": "Top lists are approximate and bounded so high-cardinality traffic cannot grow proxy memory without limit.", - "trafficTopCountries": "Top countries", - "trafficTopUserAgents": "Top User-Agents", - "trafficTopClientIps": "Top client IPs", - "trafficClientIp": "Client IP", - "trafficTopPaths": "Top paths", - "trafficTopRoutes": "Top routes", - "trafficMethods": "Methods", - "trafficStatusClasses": "Status classes", - "trafficCacheOutcomes": "Cache outcomes", - "trafficValue": "Value", - "trafficOther": "Other", - "trafficNoCountryData": "Country collection is disabled or no GeoIP data is available.", - "trafficNoUserAgentData": "No User-Agent data collected.", - "trafficNoData": "No traffic data yet.", - "trafficUserAgentEnabled": "User-Agent collection enabled", - "trafficUserAgentDisabled": "User-Agent collection disabled", - "trafficCountryEnabled": "Country collection enabled", - "trafficCountryDisabled": "Country collection disabled", - "trafficCountryNoDb": "Country collection enabled, GeoIP database unavailable", - "trafficUpdated": "Updated", - "loggingCaptureTitle": "Captured request dimensions", - "loggingCaptureUserAgent": "Capture User-Agent", - "loggingCaptureCountry": "Capture country from GeoIP", - "loggingCaptureCountryHelp": "Country lookup is optional and disabled by default. Enable it only when country logs/statistics are needed; country ACL/rate-limit rules still resolve GeoIP when required.", - "cacheAutoTrimEnabled": "Automatically release idle HOT cache memory", - "cacheAutoTrimAfter": "Idle age before HOT trim (seconds)", - "cacheAutoTrimTarget": "HOT trim target (%)", - "cacheAutoTrimHelp": "When HOT memory is above the target, only entries idle for the configured age are removed in bounded passes. Active objects are not purged.", - "webServiceTabHtaccess": ".htaccess", - "webServiceHtaccessEnabled": "Enable .htaccess compatibility", - "webServiceHtaccessFilename": "Configuration file", - "webServiceHtaccessRecursive": "Scan subdirectories", - "webServiceHtaccessStrict": "Strict unsupported-directive mode", - "webServiceHtaccessDocs": "Compatibility documentation", - "webServiceHtaccessHelp": "RustProxy compiles a bounded Apache-compatible subset when the Web Service is applied. Files are not parsed on every request; apply the Web Service again after editing .htaccess on disk.", - "webServiceHtaccessDocsTitle": ".htaccess compatibility", - "webServiceHtaccessDocsIntro": "This is a migration-oriented compatibility layer, not a complete Apache runtime. Supported directives are compiled into RustProxy Web Server rules.", - "webServiceHtaccessDocsSupportedTitle": "Supported subset", - "webServiceHtaccessDocsRewriteTitle": "Rewrite compatibility", - "webServiceHtaccessDocsRewrite": "Rewrite conditions support REQUEST_FILENAME file tests (-f, -d, -s), REQUEST_URI, REQUEST_METHOD, QUERY_STRING, HTTP_HOST, HTTPS, DOCUMENT_ROOT, THE_REQUEST and HTTP headers. Flags: L, END, R, R=301/302/307/308, QSA, NC, F, OR. PT/NE are accepted as no-ops. E= is accepted as a compatibility no-op because request HTTP headers already reach PHP-FPM.", - "webServiceHtaccessDocsPhpTitle": "PHP-FPM", - "webServiceHtaccessDocsPhp": ".htaccess may add PHP extensions with AddHandler, but the PHP-FPM endpoint, timeouts and concurrency remain controlled by the Web Service PHP-FPM tab. Apache SetHandler proxy socket syntax is intentionally not accepted.", - "webServiceHtaccessDocsReloadTitle": "Loading and limits", - "webServiceHtaccessDocsReload": "Files are read and compiled on Validate, save and apply. RustProxy does not parse .htaccess per request. Re-apply after changing a file. The scan is bounded to 128 files, depth 16, 128 KiB per file and 1 MiB total.", - "webServiceHtaccessDocsUnsupportedTitle": "Not Apache-complete", - "webServiceHtaccessDocsUnsupported": "ErrorDocument, SetEnvIf, Redirect/RedirectMatch and FallbackResource are not implemented in this compatibility layer. General Files/FilesMatch, unsupported authentication/authorization directives, non-PHP handlers and SetHandler are rejected even when Strict mode is off because silently ignoring them could weaken access control or expose source. .htaccess/.htpasswd files are always blocked from HTTP delivery. Other unsupported non-security directives are logged and ignored unless Strict mode is enabled.", - "webServiceHtaccessDocsSecurity": "Vhost/location ACL, authentication, error pages and response rules still remain authoritative. .htaccess cannot escape the document root, select an arbitrary FastCGI socket or override protected framing headers.", - "webServicePresetTitle": "Configuration preset", - "webServicePreset": "Preset", - "webServicePresetCustom": "Custom", - "webServicePresetStatic": "Static site", - "webServicePresetSpa": "SPA", - "webServicePresetPhpGeneric": "PHP generic", - "webServicePresetDownloads": "Downloads / file repository", - "webServicePresetAction": "Preset action", - "webServicePresetApply": "Apply preset", - "webServicePresetHelp": "Presets fill normal Web Service settings and remain fully editable. Document root, service ID/name and PHP-FPM endpoint are not replaced.", - "webServicePresetChoose": "Choose a preset before applying it.", - "webServicePresetApplied": "Preset applied. Review the generated settings before saving.", - "webServicePresetHintStatic": "Static site: HTML indexes, precompressed sidecars and cache rules for CSS/JS/images/fonts.", - "webServicePresetHintSpa": "SPA: index.html fallback via try_files, precompressed assets and long-lived static asset caching.", - "webServicePresetHintPhpGeneric": "Generic PHP: PHP-FPM enabled, index.php first and only existing files/directories are served directly.", - "webServicePresetHintWordpress": "WordPress: PHP-FPM, index.php and front-controller routing. Keep document root on the WordPress public directory.", - "webServicePresetHintDrupal": "Drupal: PHP-FPM, index.php and front-controller routing with static asset caching.", - "webServicePresetHintLaravel": "Laravel: PHP-FPM and front-controller routing. Point document root at the application public/ directory.", - "webServicePresetHintSymfony": "Symfony: PHP-FPM and front-controller routing. Point document root at the application public/ directory.", - "webServicePresetHintJoomla": "Joomla: PHP-FPM, index.php and front-controller routing with static asset caching.", - "webServicePresetHintPrestashop": "PrestaShop: PHP-FPM, index.php and front-controller routing with static asset caching.", - "webServicePresetHintDownloads": "Downloads: directory listing enabled, Range-friendly static delivery and common archive/document extensions forced as attachments.", - "webServiceHardeningTitle": "Hardening", - "webServiceHardeningAttacks": "Block common attack probes", - "webServiceHardeningHeaders": "Strip unnecessary fingerprint headers", - "webServiceHardeningSecurityHelp": "Hardening switches are shown beside the preset selector on General. They remain independent settings after a preset is applied.", - "trafficTabOverview": "Overview", - "trafficTabClients": "Clients", - "trafficTabRequests": "Requests", - "trafficTabDelivery": "Delivery", - "trafficVolume": "Traffic volume", - "vhMethodMode": "HTTP method policy", - "vhMethodList": "HTTP methods (one per line)", - "vhMethodHelp": "Allow only listed methods or block listed methods for this vhost. The policy stacks with location rules.", - "locationAclProfile": "ACL profile", - "locationMethodMode": "HTTP method policy", - "locationMethodList": "HTTP methods (one per line)", - "locationMethodAllow": "Allow only", - "locationMethodBlock": "Block listed", - "locationMethodHelp": "The location policy stacks with the vhost method policy. Example: GET, HEAD and POST.", - "locationCountryMode": "Country access policy", - "locationCountryList": "Country codes (one per line)", - "locationCountryHelp": "Location country policy stacks with vhost/profile policy. GeoIP is resolved only when required.", - "configHistory": "Revision history", - "configHistoryRetention": "SQLITE / LAST 512 REVISIONS", - "configHistoryHelp": "Select a marked day to inspect retained configuration revisions and their changes. History retention starts with 0.25.3.", - "configHistoryChanges": "CHANGES", - "configHistorySelectDay": "Select a day.", - "configHistoryRevision": "Revision", - "configHistoryRevisionShort": "rev", - "configHistoryBefore": "Before", - "configHistoryAfter": "After", - "configHistoryBaseline": "This is the oldest retained baseline; no earlier revision is available for comparison.", - "configHistoryNoChanges": "No configuration differences were found.", - "configHistoryDiffLimit": "Diff truncated after 200 changed paths.", - "configHistoryMissing": "(not present)", - "configHistoryEntries": "revisions", - "configHistorySummary": "{count} retained revisions", - "configHistoryEmpty": "No retained revisions.", - "configHistoryLoading": "Loading revision history...", - "configHistoryFailed": "Could not load revision history.", - "configHistoryRestoreAction": "Restore revision", - "configHistoryRestoreConfirm": "Restore revision {revision}? It will be validated and saved as a new revision after current revision {current}. The existing history will be preserved.", - "configHistoryRestored": "Revision {revision} restored as new revision {new_revision}.", - "about": "About", - "aboutTitle": "About RustProxy", - "aboutAuthor": "Author", - "aboutLicense": "License", - "aboutLicenseValue": "Open Source", - "aboutOpenSourceText": "RustProxy is an open-source project. Its source code is publicly available for inspection, development and community contribution.", - "aboutRepository": "Official source repository", - "aboutPackagesRepository": "Official package repository", - "aboutPythonRuntime": "Certificate Python runtime", - "runtimePython": "Python", - "runtimePip": "pip", - "runtimeCertbot": "Certbot", - "runtimeAcmeMode": "ACME mode", - "runtimeAvailable": "Available", - "runtimeUnavailable": "Unavailable", - "vhPoolRecycleRequests": "Recycle upstream pool after requests (0 = off)", - "vhPoolRecycleSeconds": "Maximum upstream pool age (seconds, 0 = off)", - "vhHttp1MaxBuffer": "HTTP/1 upstream max read buffer (bytes)", - "vhPoolRecycleHelp": "Recycling replaces only the Hyper client generation for each backend; active exchanges continue normally. Defaults bound long-lived pool/TLS buffer retention without reloading the vhost or cache. Request recycling accepts 0 or 1000..10000000; age accepts 0 or 60..604800.", - "backendSearch": "Search backends", - "backendSearchPlaceholder": "Search vhost, backend or state", - "backendSortBy": "Sort by", - "backendSortAscending": "Ascending sort", - "backendSortDescending": "Descending sort", - "backendColumns": "Columns", - "backendSearchEmpty": "No backends match the current search.", - "vhQuickEnable": "Enable", - "vhQuickDisable": "Disable", - "vhEnableHint": "Controls whether this vhost accepts traffic.", - "backendColumnsRequired": "At least one backend table column must remain visible.", - "logs": "Logs", - "storedLogs": "Stored logs", - "fileLogsSearchHelp": "Search syntax", - "fileLogsSearchHelpTitle": "Kibana-style field search", - "fileLogsSearchHelpText": "Use field:value clauses. Clauses are joined with AND; prefix a clause with ! to exclude it. Quotes keep spaces together.", - "fileLogsParsedFields": "Parsed fields", - "fileLogsRawJson": "Raw JSON", - "filelogs": "Logs", - "filelogsTitle": "Stored log explorer", - "filelogsDescription": "Search configured on-disk RustProxy logs without coupling to the Live Logs memory buffer.", - "fileLogsTitle": "Stored log explorer", - "fileLogsMode": "LOCAL / READ-ONLY / BOUNDED", - "fileLogsHelp": "Searches bounded RustProxy access/error logs and parses structured fields without exposing filesystem paths.", - "fileLogsSettingsHelp": "Browse configured access and error files with server-side filters. File logging can be off while Live Logs continues to use its memory buffer.", - "openLogs": "Open Logs", - "fileLogsSource": "Source", - "fileLogsAllSources": "All configured files", - "fileLogsNoSources": "No configured log files are available.", - "fileLogsEvent": "Event", - "fileLogsPageSize": "Page size", - "fileLogsRecord": "Request / reason", - "fileLogsEmpty": "No records match the current filters.", - "fileLogsDetails": "Record details", - "fileLogsSelectRecord": "Select a record to inspect its JSON.", - "fileLogsSearchPlaceholder": "req: GET AND !country: Poland", - "fileLogsTruncated": "scan limit reached", - "fileLogsSummary": "{from}–{to} of {total} matches · scanned {bytes} / {lines} lines", - "fileLogsMatchedBy": "Matched by", - "fileLogsSni": "SNI", - "fileLogsAuthority": "Authority / Host", - "fileLogsColumnsRequired": "At least one Stored Logs column must remain visible.", - "fileLogsSourcesRead": "Configured files", - "search": "Search", - "all": "All", - "clientIp": "Client IP", - "vhost": "Vhost", - "previous": "Previous", - "next": "Next", - "refresh": "Refresh", - "applyFilters": "Apply filters", - "appearance": "Appearance", - "appearanceEyebrow": "INTERFACE", - "appearanceHelp": "These preferences are saved for your administrator account and follow you between sessions.", - "languageAutomatic": "Automatic (browser)", - "interfaceScale": "Interface scale", - "interfaceScaleHelp": "Scale the entire administration interface, including dialogs and overlays, without changing browser zoom.", - "timeFormat": "Time format", - "timeFormat24": "24-hour", - "timeFormat12": "12-hour", - "chartStyle": "Chart style", - "chartStyleDefault": "Default", - "chartStyleSoft": "Soft", - "chartStyleTechnical": "Technical", - "chartStyleHighContrast": "High contrast", - "chartStylePreview": "Preview", - "draftDockTitle": "Drafts", - "draftUntitled": "New", - "draftRestore": "Restore draft", - "draftDiscard": "Discard draft", - "draftDiscardConfirm": "Discard this unsaved draft?", - "draftMinimized": "Draft moved to the dock.", - "chartStyleNeon": "Neon", - "chartStyleMonochrome": "Monochrome", - "chartStyleWarm": "Warm", - "chartStyleCool": "Cool", - "settingsSearch": "Find settings", - "settingsSearchPlaceholder": "Search settings, sections and options...", - "settingsSearchResults": "Matching sections", - "appearanceReset": "Reset appearance", - "appearanceResetDone": "Appearance preferences reset", - "appearanceInterfaceSection": "INTERFACE", - "appearanceInterfaceTitle": "Interface", - "appearanceChartsSection": "CHARTS", - "appearanceDataSection": "TABLES / LOGS", - "appearanceDashboardSection": "DASHBOARD", - "resetSection": "Reset section", - "appearanceSectionResetDone": "Section preferences reset", - "chartStylePalettes": "Chart style palettes", - "tableDensity": "Tables and logs density", - "densityNormal": "Normal", - "densityUltra": "Ultra compact", - "tableDensityHelp": "Changes row height and log spacing independently from interface scale.", - "dashboardLayout": "Dashboard layout", - "dashboardDensity": "Dashboard density", - "dashboardDensityHelp": "Controls Overview telemetry card size without changing the rest of the interface.", - "dashboardWidgets": "Overview cards", - "dashboardWidgetsHelp": "Show, hide and reorder the telemetry cards at the top of Overview.", - "dashboardWidgetVisible": "Visible", - "moveUp": "Move up", - "moveDown": "Move down", - "draftDockCollapse": "Collapse drafts", - "draftDockExpand": "Expand drafts", - "draftAgeNow": "now", - "draftAgeMinutes": "{count} min", - "draftAgeHours": "{count} h", - "draftAgeDays": "{count} d", - "buttonStyle": "Button style", - "buttonStyleNormal": "Normal", - "buttonStyleCompact": "Compact", - "buttonStyleHelp": "Normal uses the refreshed controls; Compact restores the smaller source-style buttons across all actions and dialog tabs.", - "entityEnableHint": "Controls whether this item is active.", - "setupCommandExample": "Standard service installation example:", - "certificateRuntimeTitle": "ACME runtime", - "certificateRuntimePreparing": "Preparing", - "certificateRuntimeManaged": "Managed Python", - "certificateRuntimeSystem": "System fallback", - "certificateRuntimeUnavailable": "Unavailable", - "certificateRuntimePath": "Managed environment", - "acme.runtime_checking": "Checking the local Python environment for certificate management.", - "acme.runtime_initializing": "Creating a private Python environment in the RustProxy data directory and installing Certbot modules with pip.", - "acme.runtime_ready": "The private Python/pip environment for certificate management is ready.", - "acme.runtime_fallback": "The private environment could not be used. RustProxy is using the distribution Python and Certbot available in PATH.", - "acme.runtime_python_missing": "Python is not available on this system. Automatic ACME generation and renewal are unavailable; PEM import and existing certificate files still work.", - "acme.runtime_pip_missing": "Python is available, but RustProxy could not create a local pip environment and no distribution Certbot is available. Install Python with venv/pip or Certbot packages. PEM import and existing files still work.", - "acme.runtime_setup_failed": "RustProxy could not create the local Python/pip environment and no distribution Certbot is available. Check network/package access or install Certbot from the distribution. PEM import and existing files still work.", - "acme.runtime_venv_failed": "Python and pip are available, but RustProxy could not create its private environment in the data directory. Check venv/virtualenv support and data-directory permissions. No system Certbot is available.", - "acme.runtime_venv_pip_failed": "The private Python environment was created, but pip/ensurepip could not be started inside it. Check the Python venv/ensurepip installation. No system Certbot is available.", - "acme.runtime_certbot_install_failed": "The private Python environment works, but pip could not install the Certbot packages. Check PyPI/proxy/DNS/TLS access. No system Certbot is available.", - "acme.runtime_certbot_validation_failed": "ACME packages were installed, but Certbot or one of the required plugins failed validation. Remove the acme-python directory and restart RustProxy, or check compatibility with the local Python.", - "acme.runtime_data_dir_missing": "The RustProxy data directory is unavailable, so a private Python environment cannot be created. PEM import and existing certificate files still work.", - "geoipUpdatesTitle": "GeoIP updates", - "geoipUpdatesHelp": "Download direct GeoIP country sources in the background and hot-reload them without restarting proxy traffic. Publisher SHA-256 verification is optional per file.", - "geoipUpdatesEnable": "Enable automatic GeoIP updates", - "geoipReleaseUrl": "Release files base URL", - "geoipChecksumUrl": "Checksum files base URL", - "geoipMaxFileMiB": "Maximum file size (MiB)", - "geoipIntervalHours": "Update interval (hours)", - "geoipStatusLoaded": "GeoIP database loaded", - "geoipStatusMissing": "GeoIP database is not loaded yet. The background worker will retry automatically.", - "geoipStatusDisabled": "Automatic GeoIP updates are disabled.", - "cacheViaEnable": "Add Via response header", - "cacheViaValue": "Via header value", - "cacheViaHelp": "Defaults to Via: RustProxy. Bypass and response-only diagnostic header changes apply immediately without purging cached objects.", - "geoipUpdateNow": "Download now", - "geoipUpdateNowHelp": "Uses the saved direct source URLs and optional checksum URLs. Manual download works even when automatic updates are disabled and hot-reloads the validated database.", - "geoipUpdateSaveFirst": "Save GeoIP settings before running a manual download.", - "geoipUpdateWorking": "Downloading and validating GeoIP data…", - "geoipUpdateUpdated": "GeoIP database downloaded and hot-reloaded.", - "geoipUpdateCurrent": "GeoIP database is already current and loaded.", - "geoipUpdateBusy": "Another GeoIP update is already running; try again shortly.", - "geoipFormat": "Database format", - "geoipFormatCsvPair": "CSV ranges (separate IPv4 + IPv6)", - "geoipFormatMmdb": "MaxMind DB (.mmdb, IPv4 + IPv6)", - "geoipIpv4Url": "IPv4 CSV direct URL", - "geoipIpv4ChecksumUrl": "IPv4 SHA-256 URL (optional)", - "geoipIpv6Url": "IPv6 CSV direct URL", - "geoipIpv6ChecksumUrl": "IPv6 SHA-256 URL (optional)", - "geoipMmdbUrl": "MaxMind DB direct URL", - "geoipMmdbChecksumUrl": "MaxMind DB SHA-256 URL (optional)", - "geoipSourceHelp": "CSV mode uses separate IPv4 and IPv6 range files. MMDB mode uses one native MaxMind DB file and supports standard country.iso_code, registered_country.iso_code, or country_code records. Leave a checksum URL empty to skip publisher-checksum verification; every download is still size-bounded and format-validated before atomic publish. For a local custom MMDB, place it at /geoip/country.mmdb, select MMDB and disable automatic updates.", - "certificateFileSource": "File location", - "certificateSourceManaged": "RustProxy certificate directory (/certs)", - "certificateSourceExternal": "External files (absolute paths)", - "certificateSourceManagedHelp": "Enter filenames only, for example app.pem and app.key. RustProxy loads them from /certs (normally /var/lib/rustproxy/certs for a packaged service).", - "certificateSourceExternalHelp": "Enter absolute server paths. Certbot example: /etc/letsencrypt/live/example.com/fullchain.pem and /etc/letsencrypt/live/example.com/privkey.pem. live/ symlinks are resolved on every reload. POSIX ACL read access is supported; RustProxy does not require an exact chmod. The private key must not be world-readable or group/world-writable, and the path must stay under root- or rustproxy-owned non-writable directories.", - "certificateExternalAclExample": "ACL example (grant traversal, not directory read):\nsetfacl -m u:rustproxy:--x /etc/ssl/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/\nsetfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem /etc/letsencrypt/archive/example.com/privkey*.pem\nAfter renewal, reapply read ACL to the current resolved files before API reload:\nsetfacl -m u:rustproxy:r-- \"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)\" \"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)\"", - "certificateExternalFiles": "External file paths", - "vhWwwRedirect": "Redirect www ↔ non-www", - "vhWwwRedirectTarget": "Choose the domain and redirect direction (308 destination)", - "vhWwwRedirectHelp": "Only the selected www/non-www pair redirects (308). Other names use the same backend without cross-domain redirects. TLS must cover both variants.", - "vhCacheForceEnabled": "Unsafe forced cache for selected URLs", - "vhCacheForceTargets": "Forced cache path/query prefixes (one per line)", - "vhCacheForceHelp": "Per-vhost only. Matching GET/HEAD targets may ignore query bypass, request Cookie/Authorization and client no-cache/no-store. Cookie/Authorization remain separate cache variants. Origin Set-Cookie, private and no-store responses are still never stored.", - "vhCacheForceConfirm": "Forced cache can serve stale or user-specific content if configured incorrectly. Enable it only for explicitly reviewed URL prefixes?", - "locationRedirectAction": "Redirect to URL", - "locationRedirectShort": "redirect", - "locationRedirectStatus": "Redirect status", - "locationRedirectTarget": "Target URL", - "locationRedirectPattern": "Optional rewrite regex for captures", - "locationRedirectQuery": "Preserve request query string", - "locationRedirectHelp": "Target must be an absolute http/https URL. Optional rewrite regex supports $1/${name} replacements in the path/query portion, for example ^/(.*)$ → https://storage.example/o/$1.", - "locationRedirect301": "301 Permanent", - "locationRedirect302": "302 Found", - "locationRedirect303": "303 See Other", - "locationRedirect307": "307 Temporary", - "locationRedirect308": "308 Permanent", - "cacheReason_cache_miss_forced": "Forced-cache target miss", - "vhWwwPanelTitle": "WWW address routing", - "vhWwwInactive": "Redirect is off. Choose a destination below and then enable the switch above. All hostnames currently serve the same vhost.", - "vhWwwOtherDomains": "Other domains (no redirect):", - "vhWwwNoHosts": "Add an exact hostname above to enable a WWW redirect. Wildcards cannot be the destination.", - "vhWwwSharedCount": "{count} names / one vhost", - "vhWwwEnable": "Enable 308 redirect", - "vhErrorFallbackTarget": "Main error fallback target", - "vhErrorFallbackStatuses": "Intercepted status codes", - "vhErrorFallbackHelp": "Target must be an enabled internal Custom Location. Supported statuses: 403, 404, 500, 502, 503, 504. The request is dispatched internally without an HTTP redirect.", - "locationInternal": "Internal named location", - "locationErrorFallbackTarget": "Error fallback target", - "locationErrorFallbackStatuses": "Intercepted status codes", - "locationErrorFallbackHelp": "Optional Nginx-style error fallback. Target an enabled internal location by ID. Supported statuses: 403, 404, 500, 502, 503, 504.", - "webServicePhpPathInfo": "Enable PHP PATH_INFO splitting", - "phpProfileSelect": "PHP-FPM configuration", - "phpProfileLocal": "Local configuration", - "phpProfileHelp": "Select a shared PHP-FPM profile or configure this Web Service directly. Shared edits affect all attached services.", - "phpProfileManage": "Manage PHP-FPM profiles", - "phpProfileToolbar": "PHP-FPM profiles", - "phpProfileTitle": "Shared PHP-FPM profiles", - "phpProfileDescription": "Expand a profile to edit it; save applies immediately to every attached Web Service.", - "phpProfileAdd": "Add profile", - "phpProfileNew": "New PHP-FPM profile", - "phpProfileId": "Profile ID", - "phpProfileName": "Profile name", - "phpProfileAdminValues": "PHP administrator INI overrides (PHP_ADMIN_VALUE)", - "phpProfileInUse": "Profile is used by", - "phpProfileDelete": "Delete this PHP-FPM profile?", - "phpProfileInvalid": "Invalid PHP-FPM profile fields", - "phpProfileDuplicate": "Duplicate PHP-FPM profile ID", - "phpProfileShared": "shared profile", - "backendHealthEnabled": "Monitor backend health (active and passive)", - "backendHealthPerBackend": "Individual backend health checks", - "backendHealthSelect": "Backend to configure", - "backendHealthSelectionHelp": "Choose a backend with monitoring enabled in Backends. Each backend keeps its own unsaved edits when you switch. Turn on Custom health check to override the shared settings above.", - "backendHealthNoEnabled": "No monitored backends. Enable “Monitor backend health” on at least one backend in the Backends tab.", - "backendHealthOverride": "Custom health check for this backend", - "backendHealthOverridesHelp": "When unchecked, inherit the vhost health policy. Disabling monitoring keeps the backend available for explicitly pinned locations, without active probes or passive ejection. Use a custom check for an individual backend.", - "cacheOnlyHits": "Expose cache diagnostics only on HIT (hide MISS and BYPASS)", - "phpHintEnabled": "Run configured PHP file extensions through an already-running PHP-FPM pool. RustProxy does not start PHP; non-PHP files remain static.", - "phpHintPathInfo": "Split /static.php/skins/file.css into SCRIPT_FILENAME=static.php and PATH_INFO=/skins/file.css. Useful for Roundcube; usually unnecessary for WordPress. Off by default.", - "phpHintPassAuthorization": "Pass the incoming Authorization header as HTTP_AUTHORIZATION to PHP (API/Bearer/Basic authentication). Disable if not needed.", - "phpHintExtensions": "Comma-separated extensions without leading dots: php, phtml. Only these extensions run as PHP.", - "phpHintConnectTimeout": "PHP-FPM socket connection timeout in milliseconds: 1000 = 1 second. Range: 50–60000.", - "phpHintRequestTimeout": "Timeout for the complete FastCGI request in milliseconds: 30000 = 30 seconds. Range: 100–3600000.", - "phpHintConcurrency": "Maximum concurrent PHP requests per service: e.g. 64. Exhaustion returns 503. Range: 1–4096.", - "phpHintBodyLimit": "Request-body limit in bytes: 67108864 = 64 MiB. 0 means no RustProxy-side limit. PHP post_max_size is separate.", - "phpHintHeaderLimit": "Maximum FastCGI response header length in bytes: 65536 = 64 KiB. Range: 4096–1048576.", - "phpHintStderrLimit": "Maximum PHP-FPM STDERR bytes logged per request: e.g. 4096. 0 disables stderr capture (max 65536).", - "phpHintAdminValues": "Trusted administrative INI settings, e.g. open_basedir = /var/www/webmail/:/tmp/. Use a dedicated PHP-FPM pool/socket to prevent cross-vhost INI leakage.", - "phpDocsScope": "These fields and examples apply to local PHP settings in a Web Service and to a reusable PHP-FPM profile. The chosen profile supplies PHP settings to all attached services; RustProxy connects to an already-running FPM pool.", - "phpDocsSwitchesTitle": "Enable PHP and handle PATH_INFO", - "phpDocsLimitsTitle": "Execution and size limits", - "phpDocsLimitsText": "PHP extensions: php, phtml (comma-separated). Connect timeout: 1000 ms = 1 second. Full request timeout: 30000 ms = 30 seconds. Maximum concurrent requests: 64 (exhaustion returns 503). Request body limit: 67108864 bytes = 64 MiB; 0 is unlimited in RustProxy. Response headers: 65536 bytes. STDERR capture: 4096 bytes; 0 disables it. Enter whole numbers; PHP INI limits are configured separately.", - "phpDocsValuesText": "Enter one directive = value per line in PHP INI overrides (PHP_VALUE), NOT in custom FastCGI parameters. Use php.ini syntax without [section] headers or PHP tags. Units such as M/G, booleans On/Off and timeouts in seconds are interpreted by PHP. Some PHP directives cannot be changed via PHP_VALUE. Never duplicate a key in PHP_ADMIN_VALUE.", - "phpDocsAdminTitle": "PHP_ADMIN_VALUE — privileged INI overrides", - "phpDocsAdminText": "Enter these in PHP administrator INI overrides (PHP_ADMIN_VALUE). On Unix, open_basedir is a colon-separated list of application-specific allowed paths, e.g. /var/www/webmail/:/tmp/. Prefer php_admin_value[...] defined in a dedicated PHP-FPM pool for sensitive directives. Do not apply Roundcube restrictions to WordPress workers.", - "phpDocsOtherTitle": "Validation, storage and troubleshooting", - "phpDocsOtherText": "INI names may contain letters, digits, dot, underscore and hyphen (max 64 bytes). Values must be nonempty (max 8192 bytes). At most 64 custom FastCGI parameters and 64 INI entries per map; combined data at most 128 KiB. Newlines/control characters inside values are rejected. All values are saved in standard configuration; avoid secrets. A syntactically valid option can still be rejected or ignored by PHP-FPM.", - "phpDocsPoolWarning": "Important: PHP_VALUE/PHP_ADMIN_VALUE do NOT isolate vhosts sharing PHP-FPM workers. WordPress and Roundcube must use distinct pool sockets if their INI policies differ. Restart old PHP-FPM workers after separating them. PATH_INFO is not a workaround for open_basedir.", - "vhostInterceptOriginErrors": "Replace application error bodies with RustProxy error pages", - "vhostInterceptOriginStatuses": "Intercept HTTP statuses (comma-separated)", - "vhostInterceptOriginErrorsHelp": "Example: 404, 500, 502, 503, 504. For selected codes, RustProxy keeps the HTTP error status and returns the vhost/global custom error page, or a built-in one. Application content/cookies are removed; the replacement is no-store. Off by default. Internal named-location fallbacks take precedence. Applies to reverse proxy and Web Server (including PHP).", - "vhostInterceptOriginInvalid": "Select one or more unique supported HTTP codes: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504.", - "systemInformation": "System information", - "systemInformationHelp": "Live, read-only information from the authenticated status API. Unavailable metrics are not estimated.", - "systemHostHeading": "Operating system and hardware", - "systemOsVersion": "Operating system", - "systemKernel": "Kernel", - "systemArchitecture": "Architecture", - "systemLogicalCpus": "Logical CPUs", - "systemCpuLoad": "CPU usage", - "systemProcessHeading": "RustProxy process", - "systemAppVersion": "RustProxy version", - "systemProcessUptime": "Process uptime", - "systemProcessMemory": "Process RAM (RSS)", - "systemProcessThreads": "Process threads", - "systemOpenFds": "Open file descriptors", - "systemCertificateHeading": "ACME / Python environment", - "systemCertificateHelp": "Versions are detected from the interpreter and tools actually selected for certificate management.", - "systemAcmeStatus": "ACME status", - "systemPathsHeading": "Data locations" - } + "meta": { + "locale": "en-US", + "name": "English", + "native_name": "English", + "direction": "ltr" + }, + "translations": { + "overview": "Overview", + "mainMenu": "Menu", + "routing": "Legacy routes", + "configuration": "Configuration", + "manualConfig": "Manual config", + "runtime": "RUNTIME", + "localOnly": "ADMINISTRATION", + "logout": "Sign out", + "firstSlice": "INDEPENDENT CORE / EXPLICIT ACCESS", + "loginTitle": "Administration", + "unlock": "Unlock administration", + "tokenHelp": "Run this command in a second terminal and paste the token below.", + "token": "Local access token", + "connect": "Connect to proxy", + "tokenNote": "Administration defaults to loopback HTTP. Enable Administration HTTPS before using a non-loopback bind.", + "requests": "Requests", + "activeExchanges": "Active exchanges", + "bodyLifetime": "Full body lifetime", + "bandwidth": "Payload transferred", + "bothDirections": "Both directions", + "errors": "Errors", + "errorDescription": "5xx / body / cancelled", + "liveTraffic": "LIVE TRAFFIC", + "liveTransformed": "Transformed", + "requestsPerSecond": "Requests per second", + "live": "LIVE", + "lastMinute": "Recent samples / resets on reload", + "coreState": "CORE STATE", + "configurationRevision": "Applied revision", + "backends": "Backends", + "routes": "Routes", + "connections": "Open connections", + "uptime": "Uptime", + "activity": "ACTIVITY", + "accessLog": "Access log", + "boundedLog": "Last 50 / retained ring 4096", + "time": "Time", + "request": "Request", + "status": "Status", + "duration": "Duration", + "outcome": "Outcome", + "noTraffic": "No traffic yet. Open the proxy to make your first request.", + "reload": "Reload configuration", + "addBackend": "Add / edit backend", + "backendUrl": "Backend origin (HTTP / HTTPS, IP / DNS)", + "allowPrivate": "Explicitly allow private / loopback IP", + "preserveHost": "Preserve original Host header", + "saveBackend": "Save backend", + "clear": "Clear", + "addRoute": "Add / edit route", + "hostnames": "Hostnames and aliases, comma-separated", + "pathPrefix": "Path prefix (not stripped)", + "backend": "Backend", + "enabled": "Enabled", + "saveRoute": "Save route", + "rawConfig": "Configuration document", + "export": "Export JSON", + "configHelp": "A valid save is applied atomically. Invalid or stale changes leave the running configuration untouched.", + "apply": "Validate, save and apply", + "noRestart": "Routing and response policies apply live; listener changes require restart.", + "roadmapHelp": "Release details and planned features are documented in ROADMAP.md.", + "overviewTitle": "Traffic, in focus.", + "overviewDescription": "Traffic, cache and operating-system measurements in one compact dashboard.", + "routingTitle": "Connect the right dots.", + "routingDescription": "Existing backend/route format remains supported. New deployments can use one vhost with its own frontend and pool.", + "configurationTitle": "One source of truth.", + "configurationDescription": "One canonical configuration: vhosts, listeners and shared policies.", + "connected": "CONNECTED TO LOCAL CORE", + "disconnected": "CORE NOT REACHABLE", + "edit": "Edit", + "remove": "Delete", + "privateAllowed": "PRIVATE IP APPROVED", + "configured": "CONFIGURED / NOT PROBED", + "disabled": "DISABLED", + "emptyBackends": "No backends configured. Add an explicit HTTP or HTTPS origin.", + "emptyRoutes": "No routes configured. Unknown hosts return 404.", + "saving": "Saving…", + "unsavedChanges": "Unsaved changes", + "saved": "Configuration applied.", + "confirmDelete": "Delete this item? The change takes effect immediately.", + "confirmTitle": "Confirm action", + "confirmationLabel": "CONFIRMATION", + "warningLabel": "WARNING", + "continueAction": "Continue", + "cancelAction": "Cancel", + "discardAction": "Discard changes", + "errorContext": "{area}: {message}", + "stale": "Configuration changed elsewhere. Reload before saving.", + "busy": "A save is already in progress.", + "loginError": "Sign-in failed.", + "fresh": "Configuration reloaded.", + "locked": "Signed out. The session was revoked.", + "backendUsed": "This backend is referenced by a route. Delete or change the route first.", + "renaming": "IDs cannot be renamed in this form. Use the JSON editor to update references atomically.", + "complete": "complete", + "cancelled": "cancelled", + "body_error": "body error", + "proxy_or_upstream_error": "proxy/upstream error", + "security": "Security", + "sessionNote": "Local HTTP administration. Passwords use Argon2id and session cookies are HttpOnly. Do not expose the panel publicly.", + "setupHelp": "Create the first administrator using a one-time capability from the local terminal.", + "setupToken": "One-time setup token", + "username": "Username", + "password": "Password", + "confirmPassword": "Confirm password", + "setupTitle": "Create your administrator", + "signIn": "Sign in", + "createAndSignIn": "Create account and sign in", + "securityTitle": "Access stays deliberate.", + "securityDescription": "One administrator. Revocable sessions. Explicit host and route access policies.", + "manualBackends": "MANUAL BACKENDS", + "routePolicies": "Access policy and custom headers", + "useRouteAcl": "Enable route ACL", + "aclDefault": "Default action", + "allow": "Allow", + "deny": "Deny", + "aclRules": "One rule per line: allow/deny followed by CIDR", + "aclHelp": "Use IPv4/IPv6 addresses or CIDRs, e.g. 192.168.1.0/24 or 10.0.0.5. Deny wins. With an allow-only country policy, an allowed country OR an allowed IP can pass this scope; with a block-country policy, a blocked country OR denied IP is rejected. Authentication still applies.", + "routeHeaders": "Request / response headers (JSON)", + "headersHelp": "Operations: set, append, remove. Transport, body encoding and client-identity headers are protected.", + "currentSession": "CURRENT SESSION", + "recoveryHelp": "Lost access? Run rust-proxy admin reset-password locally. Existing sessions will be revoked.", + "revokeAll": "Sign out all sessions", + "changePassword": "Change password", + "currentPassword": "Current password", + "newPassword": "New password (12-128 UTF-8 bytes)", + "savePassword": "Change and sign out", + "hostPolicies": "Host access policies", + "editHostPolicy": "Add / replace host policy", + "savePolicy": "Apply host policy", + "auditRetention": "SQLITE / LAST 100 / RETAINED 10,000", + "audit": "Administration audit", + "reloadAudit": "Reload", + "actor": "Actor", + "action": "Action", + "detail": "Detail", + "noAudit": "No audit entries.", + "noPolicies": "No host ACLs. Route ACLs may still restrict access.", + "expires": "Absolute session expiry: ", + "passwordChanged": "Password changed. All sessions were revoked. Sign in again.", + "passwordMismatch": "The passwords do not match.", + "ruleSyntax": "Use one rule per line, for example: allow 127.0.0.1/32", + "confirmRevoke": "Sign out every administrator session, including this one?", + "streamLive": "SSE live", + "streamFallback": "Polling fallback", + "auth.required": "Your session expired or was revoked. Sign in again.", + "auth.invalid_credentials": "Incorrect username or password.", + "auth.password_policy": "The password must contain 12-128 UTF-8 bytes and no line breaks.", + "auth.invalid_username": "Use 3-64 letters, digits, dots, dashes or underscores.", + "auth.invalid_setup_token": "The setup token is invalid. Read it from the local terminal.", + "auth.already_initialized": "An administrator already exists. Sign in.", + "auth.rate_limited": "Too many attempts. Retry after 60 seconds or use local password recovery.", + "auth.busy": "Password workers are busy. Retry shortly.", + "auth.csrf": "Request protection failed. Reload the page and retry.", + "auth.invalid_host": "Use the configured administration address and allowed hostname.", + "auth.invalid_origin": "Cross-origin administration is blocked.", + "auth.credentials_changed": "Credentials changed concurrently. Sign in again.", + "auth.unavailable": "Administration storage is temporarily unavailable.", + "events.too_many_viewers": "Live viewer limit reached; polling is available.", + "protocol": "Upstream protocol", + "protocolHelp": "HTTP/2 and auto use the backend authority. Preserve Host is available with HTTP/1.1 only. RFC 8441 can use an explicit HTTP/2 upstream; other WebSocket transports bridge through HTTP/1.1.", + "addressAllowlist": "Optional address allowlist (CIDR, one per line)", + "caPem": "Additional trusted CA / certificate (PEM, optional)", + "backendTlsHelp": "TLS encryption remains available, but certificate/hostname verification is off unless enabled explicitly. Private addresses still need explicit approval, including DNS answers.", + "advancedTransport": "Transport and trust", + "tls": "Certificates", + "tlsTitle": "Encrypted, deliberately.", + "tlsDescription": "Exact SNI names. Private local certificate files. Apply bindings without restarting.", + "certificateBindings": "Certificate bindings", + "addCertificate": "Add certificate", + "certificateId": "Certificate ID", + "certificateFile": "Certificate filename / path", + "privateKeyFile": "Private key filename / path", + "defaultCertificate": "Use as the default certificate", + "saveCertificate": "Apply certificate", + "reloadCertificates": "Reload certificate files", + "certificateHelp": "Use RustProxy-managed files, absolute external paths, uploaded PEM pairs, or ACME. Managed filenames are loaded from /certs. External paths are read in place and can be reloaded after an outside renewal without changing the configuration revision.", + "certificateHint": "External renewal: keep fullchain.pem/privkey.pem paths in the certificate entry, renew them with Certbot/acme.sh, then use Reload certificate or POST /api/certificates/{certificate_id}/reload.", + "emptyCertificates": "No explicit SNI bindings. The generated default certificate is used while the TLS listener is enabled.", + "certificateSafety": "Certificates secure TLS connections, not routing. Assign a certificate to a vhost. Changes affect new handshakes.", + "wsActive": "WebSocket tunnels", + "h2Requests": "HTTP/2 requests", + "h2ActiveConnections": "Active H2 connections", + "tlsFailures": "TLS handshake errors", + "tlsListener": "TLS listener", + "notEnabled": "Disabled", + "websocket_closed": "WebSocket closed", + "defaultLabel": "DEFAULT", + "language": "Language", + "settings": "Settings", + "settingsTitle": "Configure your edge.", + "settingsDescription": "Switch between function tabs. Unsaved changes stay in their forms.", + "cacheHitRatio": "Cache hit ratio", + "processMemory": "Process memory", + "systemLoad": "SYSTEM LOAD", + "systemChart": "Recent system CPU usage", + "systemMemory": "RAM used / total", + "systemSwap": "System swap", + "systemUptime": "System uptime", + "loadAverage": "Load 1 / 5 / 15 min", + "cacheOverview": "RAM cache", + "cacheObjects": "Objects / memory", + "cacheEvictions": "Evicted / expired", + "processDiagnostics": "Process", + "platform": "Platform", + "processCpu": "CPU (100% = 1 core)", + "fileDescriptors": "File descriptors / limit", + "processThreads": "OS threads", + "processVirtualMemory": "Virtual memory / limit", + "processStartTime": "Process start", + "containerMemory": "cgroup RAM / limit", + "networkDiagnostics": "Network and responses", + "hostScope": "OS scope", + "networkRate": "RX / TX per second", + "meanLatency": "Mean exchange duration", + "filterLogs": "Filter host or path", + "filterResult": "Filter result", + "allResults": "All results", + "routeCache": "Route cache policy", + "inheritCache": "Inherit global policy", + "bypassCache": "Always bypass", + "listeners": "Listeners", + "httpConnections": "HTTP connections", + "httpConnectionsHelp": "Connection lifecycle and transport limits for HTTP/2 and HTTP/3.", + "http2Tab": "HTTP/2", + "http3Tab": "HTTP/3", + "http2Settings": "HTTP/2 connections", + "http2SettingsHelp": "HTTP/2 enablement is controlled per listener. Tune idle, maximum lifetime and graceful GOAWAY drain limits here.", + "http2IdleTimeout": "Idle timeout (seconds)", + "http2MaxLifetime": "Maximum connection lifetime (seconds, 0 = disabled)", + "http2DrainTimeout": "Drain timeout (seconds)", + "http2DrainHelp": "These limits apply to this listener. Idle timeout applies only to unused connections; maximum lifetime is absolute; drain bounds active streams after GOAWAY. Changes require restart.", + "http2Save": "Save HTTP/2 settings", + "http2NoEnabledListeners": "No listener has HTTP/2 enabled. Enable HTTP/2 or h2c in Settings > Listeners first.", + "http2ListenersDirty": "Save pending listener changes before editing HTTP/2 settings.", + "restartLabel": "RESTART", + "listenersHelp": "Named IP and port bindings. Changes require restart. The administration endpoint remains separate.", + "httpAddress": "HTTP bind address and port", + "httpsAddress": "HTTPS bind address and port", + "enableHttps": "Enable HTTPS listener", + "lowPortsHelp": "Privileged ports may need CAP_NET_BIND_SERVICE. Do not run the whole application as root.", + "saveListeners": "Save listeners", + "inheritListeners": "Use startup defaults", + "responseSettings": "Client response", + "hotApply": "LIVE APPLY", + "serverHeader": "Server header", + "serverPreserve": "Preserve origin value", + "serverHide": "Hide completely", + "serverCustom": "Replace / add custom value", + "serverValue": "Custom Server value", + "compression": "Streaming compression", + "compressionMin": "Minimum response size (bytes)", + "compressionHelp": "Negotiates Accept-Encoding using client q-values and zstd → br → gzip → deflate as the server preference for equal quality. Cookies, Authorization, Set-Cookie, private and no-store responses may be compressed. Existing Content-Encoding, ranges, SSE, upgrades and no-transform are passed through.", + "saveResponse": "Apply response settings", + "cacheSettings": "Cache", + "enableCache": "Enable cache", + "cacheSafety": "Complete public-safe GET responses are cached. Authorization, cookies and private/no-store responses still bypass. One byte range can be served from a complete cached object; multipart/unsupported ranges pass through.", + "cacheMemory": "HOT allocated budget (MiB)", + "cacheObject": "Maximum object (KiB)", + "cacheEntries": "Maximum entries", + "cacheMaxTtl": "Maximum TTL (seconds)", + "cacheDefaultTtl": "Fallback TTL for public responses (0 = off)", + "bypassQuery": "Bypass URLs with a query string", + "cachePrefixes": "Bypass path prefixes, one per line", + "cacheAgeHelp": "Age is sent on cache hits unless an explicit response-header rule removes or replaces it. Such Age rules are wire-only and do not change cache freshness. Counters remain available even when diagnostic headers are hidden. Only cache-affecting configuration changes invalidate incompatible objects; managed software updates hand hot objects to cold storage when the cold tier is enabled.", + "saveCache": "Apply cache policy", + "purgeCache": "Purge all cache", + "customHeaders": "Global custom headers", + "globalHeadersHelp": "Request rules go to the backend; response rules go to the client. Route rules run after global rules. Server policy takes final precedence.", + "responseHeaders": "Response to client", + "requestHeaders": "Request to backend", + "addHeader": "Add header rule", + "saveHeaders": "Apply header rules", + "unavailable": "N/A", + "threads": "threads", + "realSample": "Measured", + "noKnownLimit": "unknown / unlimited", + "unlimited": "unlimited", + "restartPending": "Saved listener changes are pending. Restart RustProxy to apply them.", + "activeListeners": "Currently active", + "headerLimit": "At most 32 rules per direction.", + "headerOperation": "Operation", + "header_set": "Set", + "header_append": "Append", + "header_remove": "Remove", + "headerName": "Header name", + "headerValue": "Header value", + "confirmInherit": "Restore listener defaults from startup flags and environment on the next restart?", + "confirmPurge": "Purge every cached object and cancel all pending cache fills?", + "cachePurged": "Cache purged; removed entries", + "logsDropped": "dropped", + "updateAvailable": "New version", + "vhosts": "Vhosts", + "vhostsTitle": "Virtual hosts & backends", + "vhostsDescription": "Manage hostnames, frontend bindings, backends and per-host policies.", + "vhHealthTitle": "Backends", + "vhManage": "Manage vhosts", + "vhHealthEmpty": "No backend diagnostics yet.", + "vhVhost": "Vhost", + "vhActive": "Active", + "vhRequests": "Requests", + "vhErrors": "Errors", + "vhLatency": "Latency (EWMA)", + "vhTraffic": "Traffic/s", + "vhClientGeneration": "Client gen.", + "vhGenerationRequests": "Gen. requests", + "vhGenerationAge": "Gen. age", + "vhClientRotations": "Rotations", + "vhIntro": "Each vhost owns its bound IPs, paths and backends. Unlisted addresses cannot reach it. Listener changes require restart.", + "vhList": "Virtual hosts", + "vhSearch": "Search vhosts", + "vhSearchPlaceholder": "Search hostname, vhost or backend", + "vhSearchEmpty": "No vhosts match this search.", + "vhNew": "New vhost", + "vhMigrate": "Migrate a legacy route group", + "vhImport": "Load into editor", + "vhMigrationHint": "Migration is saved atomically. Routes sharing one host and one backend are grouped; ambiguous host ownership is rejected.", + "vhEditor": "Vhost configuration", + "vhReset": "Discard changes", + "vhSafetyHint": "Private IP binding is not authentication. Keep firewall/ACL rules. HSTS preload is opt-in; affinity cookies are not login sessions.", + "vhId": "Vhost ID", + "vhHosts": "Hostnames", + "vhEnabled": "Enabled", + "vhFrontends": "Frontends / bound addresses", + "vhAddFrontend": "Add frontend", + "vhAddress": "IP:port (IPv6: [address]:port)", + "vhTls": "TLS enabled", + "vhHttp2": "Prefer HTTP/2 over TLS; retain HTTP/1.1", + "vhBackendPool": "Backends", + "vhAddBackend": "Add backend", + "vhMemberId": "Backend ID", + "vhUrl": "Origin URL", + "vhWeight": "Weight", + "vhBackup": "Backup only", + "vhDrain": "Drain new sessions", + "vhPrivate": "Allow private origin IP", + "vhPreserveHost": "Preserve client Host (HTTP/1.1 only)", + "vhProtocol": "Origin protocol", + "vhProtocolHttp1": "HTTP/1.1", + "vhProtocolAuto": "TLS ALPN: HTTP/2 + HTTP/1.1", + "vhProtocolHttp2": "HTTP/2 (TLS: with HTTP/1.1 fallback; plaintext: h2c)", + "vhAddressAllowlist": "Origin IP/CIDR allowlist (optional)", + "vhTlsServerName": "TLS server name / SNI (optional)", + "vhTlsVerify": "Verify backend certificate and hostname", + "vhTlsVerifyHelp": "Disabled when omitted. Enable it when RustProxy must validate the backend certificate chain and identity. If verification fails, set TLS server name to a DNS name present in the certificate and/or add the private CA PEM. Do not use an IP address as SNI.", + "vhCa": "Additional trusted CA (PEM, optional)", + "vhBalancing": "Balancing & connection reuse", + "vhAlgorithm": "Algorithm", + "vhRoundRobin": "Smooth weighted round-robin", + "vhLeastConnections": "Weighted least active exchanges", + "vhLeastTraffic": "Weighted least traffic (EWMA)", + "vhAdaptive": "Adaptive: activity + latency + traffic", + "vhIpHash": "Client IP hash", + "vhSlowStart": "Recovery slow-start (seconds)", + "vhRetryMax": "Failover retries", + "vhRetryConnect": "Retry connection failures", + "vhRetryTimeouts": "Retry response-header timeouts", + "vhRetryStatuses": "Retry HTTP statuses", + "vhRetryHelp": "Retries are limited to body-free GET, HEAD and OPTIONS requests, use a different healthy backend and never replay WebSocket or write requests.", + "vhKeepalive": "Reuse upstream connections (keepalive)", + "vhIdleTimeout": "Idle pool timeout (seconds)", + "vhMaxIdle": "Max idle connections / origin", + "vhTcpKeepalive": "TCP / HTTP/2 keepalive interval (seconds)", + "vhHealth": "Backend health", + "vhHealthMode": "Check type", + "vhOff": "Off", + "vhTcp": "TCP connect", + "vhHttp": "HTTP status + response time", + "vhHealthPath": "HTTP check path", + "vhHealthHost": "HTTP Host override (optional)", + "vhInterval": "Interval (ms)", + "vhTimeout": "Timeout (ms)", + "vhSlow": "Slow threshold (ms; 0 = off)", + "vhFall": "Failures before down", + "vhRise": "Successes before recovery", + "vhStatusMin": "Minimum HTTP status", + "vhStatusMax": "Maximum HTTP status", + "vhHealthNote": "HTTP probes use HTTP/1.1 and measure time to headers, not body duration. When Host override is blank, RustProxy automatically uses the first exact DNS hostname of the vhost; if none exists it falls back to the backend host. Use TCP for h2c-only services. New backends wait for successful probes. Active health is independent from passive health.", + "vhPassiveHealth": "Passive Backend Health", + "vhPassiveEnabled": "Allow normal request failures to mark a backend unhealthy", + "vhPassiveHelp": "Off by default. Errors are still logged and counted, but normal user traffic cannot change backend health until this is explicitly enabled.", + "vhPassiveConnect": "Count connection errors", + "vhPassiveTimeouts": "Count timeouts", + "vhPassiveIo": "Count body/read/write I/O errors", + "vhPassive5xx": "Count HTTP 5xx responses", + "vhPassiveSlow": "Count slow responses", + "vhPassiveFall": "Passive failures before down (empty = active fall)", + "vhPassiveRise": "Passive successes before recovery (empty = active rise)", + "vhPassiveWarning": "Enabling HTTP 5xx or slow-response failures can quarantine the whole backend after repeated errors from a single endpoint.", + "vhSticky": "Sticky sessions", + "vhStickyMode": "Affinity mode", + "vhCookie": "Opaque proxy cookie", + "vhCookieName": "Cookie name (empty = per-vhost default)", + "vhCookieTtl": "Cookie lifetime (seconds)", + "vhStickyNote": "Cookie affinity bypasses shared cache. Drain preserves existing cookie sessions. IP affinity uses the verified client IP under the configured trusted-proxy policy.", + "vhSecurity": "Security", + "vhHsts": "Enable HSTS (TLS responses only)", + "vhHstsAge": "HSTS max-age (seconds)", + "vhHstsSub": "HSTS includeSubDomains", + "vhHstsPreload": "HSTS preload (explicit opt-in)", + "vhNosniff": "X-Content-Type-Options: nosniff", + "vhFrame": "X-Frame-Options", + "vhPreserve": "Preserve origin value", + "vhDeny": "DENY", + "vhSameorigin": "SAMEORIGIN", + "vhReferrer": "Referrer-Policy (optional)", + "vhCsp": "Content-Security-Policy (optional)", + "vhPermissions": "Permissions-Policy (optional)", + "vhAdvanced": "Advanced: paths, ACL and custom headers", + "vhPaths": "Paths (JSON; optional backend_id pins a path to a single pool member)", + "vhAcl": "Vhost ACL (JSON or null)", + "vhHeaders": "Request / response header rules (JSON)", + "vhEmpty": "No vhosts yet. Existing routes continue to work.", + "vhUnsaved": "Discard unsaved vhost changes?", + "vhDelete": "Remove this vhost? Bound sockets are closed after restart.", + "vhConflict": "That vhost ID already exists.", + "vhRowLimit": "The maximum number of rows has been reached.", + "vhMigrationAmbiguous": "Hostnames overlap other legacy routes. Resolve ownership in JSON before migration.", + "vhMigrationPending": "Legacy migration will be committed with this save.", + "vhMigrationNone": "Select a legacy route", + "vhJsonInvalid": "Invalid JSON or data type in paths, ACL or headers.", + "vhUp": "Up", + "vhDown": "Down", + "vhChecking": "Checking", + "vhDraining": "Draining", + "vhDisabled": "Disabled", + "vhHealthDisabled": "Checks off", + "vhHealthUnknown": "Unknown", + "vhNewLabel": "NEW", + "vhEditLabel": "EDIT", + "vhFrontendNote": "Use an IP assigned to this machine. A wildcard accepts every IP in its address family; host routing still checks the local destination. TLS certificates are configured in the TLS tab.", + "close": "Close", + "inherit": "Inherit", + "liveLogs": "Live logs", + "logVhost": "Filter by vhost", + "logAllVhosts": "All vhosts", + "logAllMethods": "All methods", + "logPause": "Pause", + "logPaused": "PAUSED", + "logResume": "Resume", + "logPin": "Dock", + "logUndock": "Open as modal", + "logColumns": "Columns", + "logColumnsRequired": "At least one Live Logs column must remain visible.", + "logLoadHistory": "Increase buffer", + "logHistoryLoaded": "Live buffer increased to {count} retained entries.", + "logHost": "Host", + "logBufferHint": "Live view starts with the latest 256 completed exchanges. Increase buffer to seed it with retained history and keep accumulating live entries up to 4096 until the log window is closed. Export includes the currently filtered rows.", + "settingsSecurityHint": "Accounts and ACLs are global. SSL, HSTS, headers and cache are also configured inside each vhost.", + "theme": "Theme", + "themeDark": "Dark", + "themeLight": "Light", + "density": "Interface density", + "densityCompact": "Compact", + "densityDense": "Dense", + "densityComfortable": "Comfortable", + "globalPages": "Default responses", + "pagesHelp": "Pages for direct IP, unknown hosts and errors. An empty status selector preserves the original status. Static pages do not contact the upstream.", + "pageInherit": "Default / inherited", + "pageWelcome": "Congratulations page", + "pageEmpty": "Empty response", + "pageHtml": "Custom HTML", + "pageStatus": "Response status", + "pageOriginalStatus": "Original status", + "pageHtmlSource": "HTML source", + "pageUploadHtml": "Optional HTML file (32 KiB maximum)", + "pageTooLarge": "HTML must not exceed 32 KiB in UTF-8.", + "customErrorPages": "Custom error pages", + "pageStaticHost": "Static / maintenance host", + "pageMissingPath": "Unmatched path", + "pageDirectIp": "Direct IP access", + "pageUnknownHost": "Unknown hostname", + "pagesSafetyHint": "HTML is public content, not an admin template. ACLs still run first. Explicit HTTP 200 for an error masks its status to clients and monitoring. Total configuration size is limited.", + "vhDetailsTab": "Details", + "vhSsl": "SSL certificates", + "vhCertificate": "Named certificate", + "vhCertificateDefault": "Global SNI / default certificate", + "vhCertificateFile": "Certificate / full chain (PEM)", + "vhPrivateKeyFile": "Private key (PEM)", + "vhUploadCertificate": "Upload and validate pair", + "vhSelectCertificateFiles": "Choose both a certificate and private key file.", + "vhCertificateTooLarge": "Each PEM file is limited to 128 KiB.", + "vhCertificateNeedsHosts": "Enter vhost domain names in Details first.", + "vhCertificateStaged": "Pair verified and staged. Save this vhost to activate its certificate.", + "vhCertificateUploadHint": "Optional upload verifies key match and domain coverage. Private keys are stored in private files, never returned by the API. Save the vhost to activate the selection. Uploads do not save other changes. Listener changes require restart.", + "vhCompression": "Compression", + "vhCompressionHelp": "Compression and each codec inherit by default. A scope can filter globally enabled codecs; it cannot activate a codec disabled in Settings → Response. Minimum size remains global.", + "compressionEnabled": "Dynamic compression", + "compressionZstd": "Zstandard (zstd)", + "compressionBr": "Brotli (br)", + "compressionGzip": "gzip", + "compressionDeflate": "deflate", + "vhCache": "Cache", + "vhCacheTtl": "Maximum TTL (seconds, blank inherits)", + "vhCacheDefault": "Public default TTL (blank inherits)", + "vhCacheObject": "Object limit (KiB, blank inherits)", + "cacheEnabled": "Enable cache", + "cacheBypassQuery": "Bypass query strings", + "cacheStatusHeader": "Expose X-Cache", + "cacheHitsHeader": "Expose X-Cache-Hits", + "cacheRfcHeader": "Expose Cache-Status", + "cacheBypassPrefixes": "Bypass path prefixes", + "vhCacheCustomPrefixes": "Override inherited bypass paths", + "vhCacheHint": "Per-host policy can enable caching independently of the global default. All vhosts share global RAM and entry ceilings. Cookie affinity, private responses and authorization still bypass cache.", + "vhPurgeCache": "Purge host cache", + "vhPurgeConfirm": "Purge only this vhost cache? In-progress fills for this host will also be invalidated.", + "vhPages": "Error pages", + "vhHeadersOrder": "Order: global rules, vhost rules, path rules, then security/Server policy. Advanced JSON remains available.", + "vhNewHealthHint": "Save the vhost to start observing its backend health.", + "listenerAdd": "Add listener", + "listenerMigrate": "Convert defaults to named listeners", + "listenerScopeHint": "Internal / public is a label, not a firewall. New listeners do not expose legacy routes unless explicitly selected.", + "listenerLimit": "Maximum 64 named listeners.", + "listenerEndpoint": "Listener", + "listenerName": "Display name", + "listenerScope": "Label", + "listenerInternal": "Internal", + "listenerPublic": "Public", + "listenerLegacy": "Allow legacy routes", + "listenerInUse": "Listener is assigned to vhosts", + "listenerDuplicate": "Listener IDs must be unique.", + "listenerChooseHint": "Select named listeners configured in Settings > Listeners. A disabled listener grants no access.", + "listenerMissing": "Missing listener", + "http3Settings": "HTTP/3 / QUIC", + "http3SettingsHelp": "HTTP/3 enablement is controlled in Settings > Listeners. Tune advertisement, connection and stream limits here.", + "http3NoTlsListeners": "No TLS listeners are configured. Add and save a TLS listener first.", + "http3NoEnabledListeners": "No TLS listener has HTTP/3 enabled. Enable HTTP/3 in Settings > Listeners first.", + "http3ListenersDirty": "Save pending listener changes before editing HTTP/3 settings.", + "http3Save": "Save HTTP/3 settings", + "h2Configure": "Configure HTTP/2 limits", + "h3Enable": "Enable HTTP/3 over QUIC (UDP)", + "h3Configure": "Configure HTTP/3 limits", + "h3Limits": "HTTP/3 limits and advertisement", + "h3_alt_svc_max_age": "Alt-Svc lifetime (s)", + "h3_max_concurrent_connections": "Connections per listener", + "h3_max_concurrent_bidistream": "Bidirectional streams per connection", + "h3_max_concurrent_unistream": "Unidirectional streams per connection", + "h3_max_idle_timeout": "QUIC idle timeout (s; 0 = unlimited)", + "h3Help": "HTTP/3 requires TLS. Keep TCP plus the same UDP port available; HTTP/1.1 and HTTP/2 remain. Zero disables the local QUIC idle timeout. Changes require restart.", + "h3TuningHelp": "These limits apply only when HTTP/3 is enabled for this TLS listener. Changes require restart.", + "h3Requests": "HTTP/3 requests", + "certImport": "Import certificate", + "certImportSave": "Import and save in library", + "certLibraryHint": "Upload fullchain/certificate and private key in PEM. Save once, then assign the certificate to one or more vhosts.", + "certCheckDomains": "Domains to verify (comma-separated)", + "certAcmeLater": "Issued certificates are imported into the library and assigned per vhost.", + "certAdvanced": "Existing files / advanced", + "certLegacyNames": "Legacy SNI names (optional; prefer per-vhost assignment)", + "certAssigned": "Assigned vhosts", + "certUnassigned": "Not assigned", + "certInUse": "Certificate is in use. Reassign vhosts and change the default before removal.", + "certDuplicate": "This certificate ID exists. Use a new ID for safe rotation.", + "certManage": "Manage certificate library", + "certSelectHint": "Select an imported certificate. Uploads and rotation are in the Certificates tab. Assignments are validated against vhost names.", + "vhTinyauth": "Authentication and IP access", + "vhTabAuth": "Authentication", + "taEnable": "Enable Tinyauth protection", + "taUrl": "Tinyauth URL / verification endpoint", + "taLogin": "Public login URL", + "taRedirectForbidden": "Accept same-origin provider redirects for 403", + "taConnect": "Connect timeout (s)", + "taTimeout": "Total verification timeout (s)", + "taRequestHeaders": "Client headers sent to Tinyauth", + "taResponseHeaders": "Verified identity headers sent to application", + "taHelp": "An origin URL adds /api/auth/nginx automatically. Verification is body-free and runs before cache and WebSocket upgrades. Protected responses use private, no-store. Configure Tinyauth to trust the actual RustProxy IP.", + "vhResponsePolicy": "Response identity and diagnostics", + "vhInherit": "Inherit global settings", + "diagnosticHeaders": "Diagnostic response headers", + "diagInherit": "Inherit diagnostic headers", + "diagRequestId": "Expose X-Request-ID", + "hideMockOrigin": "Hide X-Mock-Origin", + "diagHelp": "X-Request-ID is opt-in. Other upstream response headers are controlled with ordinary response-header rules.", + "forwardingSettings": "Client identity / forwarding", + "forwardingHelp": "By default, client-supplied forwarding headers are discarded. Add only immediate proxies you control. Trusted chains are evaluated right-to-left, stopping at the first untrusted address.", + "forwardingCanonical": "RustProxy generates Forwarded, X-Forwarded-For, X-Real-IP, X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port and X-Forwarded-URI. Socket destination never comes from a header.", + "trustedProxies": "Trusted proxy CIDRs (one per line; /32 for one IPv4, /128 for one IPv6)", + "trustForwardedProto": "Trust X-Forwarded-Proto only from trusted proxies", + "listenerId": "Listener ID", + "cache": "Cache", + "cacheTitle": "Cache, explained.", + "cacheDescription": "Objects, domain effectiveness and precise decisions.", + "cacheRefresh": "Refresh", + "cacheLive": "Live (3 s)", + "cachePurgeScope": "Purge selected scope", + "cacheObjectCount": "Resident objects", + "cacheSaved": "Body bytes served from RAM", + "cacheCounterScope": "Domain counters since configuration revision", + "cacheDomains": "Domains and effectiveness", + "cacheScopeHint": "Memory limits, fills and evictions are global. Domain counters reset on configuration publication; purging objects does not reset counters.", + "cacheDomain": "Domain", + "cacheReasons": "Decision counters", + "cacheNoDomains": "No cache decisions yet. Send traffic to a configured host.", + "cacheObjectList": "Stored objects", + "cachePrevious": "Previous", + "cacheNext": "Next", + "cacheObjectHint": "Most recently used first. Click Target or Cache key / tags to copy the full value. Bodies and request headers are not exposed.", + "cachePath": "Path", + "cacheBodySize": "Body", + "cacheAge": "Age", + "cacheTtlRemaining": "Remaining TTL", + "cacheNoObjects": "No fresh objects in this scope. Inspect the decision counters above.", + "cacheLegacy": "Unassigned", + "cacheGlobalLimit": "Global limit", + "cacheFills": "Active fills (global)", + "cacheRejected": "Rejected fills (global)", + "cacheUpdated": "Updated", + "acmeTitle": "Let's Encrypt / ACME", + "acmeHelp": "Issue an ACME certificate, add it to the library, then assign it to a vhost. Automatic renewal starts after import.", + "acmeEmail": "ACME contact email", + "acmeDomains": "DNS names, comma-separated (wildcards only with DNS-01)", + "acmeEnvironment": "CA environment", + "acmeStaging": "Staging - not browser-trusted", + "acmeProduction": "Production", + "acmeDnsServer": "DNS update server IP", + "acmeDnsPort": "DNS update port", + "acmeKeyName": "TSIG key name", + "acmeSecret": "TSIG secret (base64)", + "acmeAlgorithm": "TSIG algorithm", + "acmePropagation": "DNS propagation wait (seconds)", + "acmeSignQuery": "Sign SOA discovery query", + "acmeTos": "I accept the Let's Encrypt subscriber agreement and authorize domain validation.", + "acmeConfirmProduction": "I confirm production issuance (CA rate limits apply).", + "acmeIssue": "Request certificate", + "acmeIdle": "No issuance job in this process.", + "acmeImport": "Add issued certificate to library", + "acmeRenewalHint": "Initial issuance/import remains explicit. Managed certificate profiles are then checked automatically while RustProxy is running; saved renewal status is restored from SQLite after restart.", + "acmeReplace": "Replace the existing library certificate? Current vhost assignments will be retained and revalidated.", + "acmeImported": "Certificate saved. Assign it in the vhost SSL tab.", + "certFuture": "Managed automatic renewal is available for supported ACME providers; saved renewal observations are persisted in SQLite.", + "cacheReason_cache_hit": "Fresh object reused", + "cacheReason_cache_miss": "No matching fresh object", + "cacheReason_cache_disabled": "Cache disabled", + "cacheReason_route_disabled": "Route bypass", + "cacheReason_request_cookie": "Application cookie", + "cacheReason_request_authorization": "Authorization header", + "cacheReason_request_cache_control": "Client cache directive / hard refresh", + "cacheReason_request_pragma": "Client Pragma", + "cacheReason_request_method": "Method other than GET", + "cacheReason_request_body": "Nonempty request body", + "cacheReason_request_range": "Range request", + "cacheReason_request_conditional": "Conditional request", + "cacheReason_request_upgrade": "Protocol upgrade", + "cacheReason_query_excluded": "Query excluded", + "cacheReason_path_excluded": "Path excluded", + "cacheReason_request_cache_control_invalid": "Invalid request cache directives", + "cacheReason_response_status": "Non-200 response", + "cacheReason_response_set_cookie": "Origin Set-Cookie", + "cacheReason_response_cache_control": "Origin privacy/cache directive", + "cacheReason_response_cache_control_invalid": "Invalid origin cache directives", + "cacheReason_response_vary": "Uncacheable Vary", + "cacheReason_response_range": "Partial response", + "cacheReason_response_trailers": "Response trailers", + "cacheReason_response_ttl_size_or_metadata": "TTL, size or metadata rejected", + "cacheReason_response_headers_policy": "Response rule prevents storage", + "cacheReason_configuration_changed": "Configuration changed", + "cacheReason_not_proxy_response": "Local page / rejected request", + "cacheReason_tinyauth": "Tinyauth protected content", + "cacheReason_sticky_cookie": "Sticky-session cookie mode", + "acme.working": "Certificate request is running.", + "acme.ready_to_import": "Certificate validated; ready to add to the library.", + "acme.certbot_not_installed": "Certbot is not installed or is not in the service PATH. Install Certbot and certbot-dns-rfc2136.", + "acme.certbot_failed_check_private_log": "Certbot failed. Check the private ACME logs; the exact path is shown with the failed operation.", + "acme.production_confirmation": "Explicit production confirmation is required.", + "acme.consent_required": "Accept the subscriber agreement and confirm the selected CA environment.", + "acme.provider_timeout": "Certificate provider deadline exceeded. Check the Certbot log path shown with the failed operation before retrying.", + "acme.invalid_key_secret": "TSIG secret must be valid base64 (16-512 decoded bytes).", + "acme.job_busy": "Another certificate request is already running.", + "acme.cancelled": "Certificate job cancelled on shutdown.", + "acme.certificate_validation_failed": "Issued certificate/key or domain coverage validation failed. Nothing was activated.", + "acme.invalid_request": "ACME: invalid request.", + "acme.unknown_field": "ACME: unknown field.", + "acme.invalid_id": "ACME: invalid id.", + "acme.invalid_email": "ACME: invalid email.", + "acme.invalid_domains": "ACME: invalid domains.", + "acme.invalid_domain": "ACME: invalid domain.", + "acme.dns_names_only": "ACME: dns names only.", + "acme.invalid_environment": "ACME: invalid environment.", + "acme.tos_required": "ACME: tos required.", + "acme.invalid_dns_server": "ACME: invalid dns server.", + "acme.invalid_limits": "ACME: invalid limits.", + "acme.invalid_key_algorithm": "ACME: invalid key algorithm.", + "acme.invalid_sign_query": "ACME: invalid sign query.", + "acme.unsafe_directory": "ACME: unsafe directory.", + "acme.unsafe_output_path": "ACME: unsafe output path.", + "acme.invalid_pem_size": "ACME: invalid pem size.", + "acme.invalid_pem": "ACME: invalid pem.", + "acme.profile_limit": "ACME: profile limit.", + "acme.profile_busy": "ACME: profile busy.", + "acme.staging_limit": "ACME: staging limit.", + "acme.request_too_large": "ACME: request too large.", + "acme.local_io_or_json_error": "ACME: local io or json error.", + "acme.no_certificate_directory": "ACME: no certificate directory.", + "acme.python_not_available": "ACME: python not available.", + "acme.worker_io": "ACME: worker io.", + "acme.invalid_worker_response": "ACME: invalid worker response.", + "acme.worker_failed": "ACME: worker failed.", + "acme.validation_worker_failed": "ACME: validation worker failed.", + "cacheResidentLabel": "Cache memory", + "certificateFuture": "ACME providers: HTTP-01, Cloudflare, OVH and RFC2136. Renewal is manual.", + "prometheusTitle": "Prometheus", + "prometheusHelp": "A dedicated read-only HTTP listener, separate from admin sessions. Binding, enabling or changing the token variable requires a restart; CIDRs, path and collectors apply live.", + "prometheusEnable": "Enable the scrape endpoint", + "prometheusListen": "Exporter bind IP and port", + "prometheusPath": "Metrics path", + "prometheusCidrs": "Allowed scraper IPs (CIDR, one per line)", + "prometheusTokenEnv": "Environment variable holding the bearer token (optional on loopback)", + "prometheusTokenHelp": "Enter a variable NAME, not the secret. Set its value to a random 32+ character token in the service environment. Non-loopback binding requires it. This endpoint is HTTP: use a private network or a TLS tunnel. The Prometheus credential file must contain the same token.", + "prometheusCollectors": "Metric groups", + "prometheusPreview": "Preview current metrics", + "prometheusPreviewFailed": "Could not fetch metrics", + "prometheusActive": "Active endpoint", + "prometheusDisabled": "Exporter disabled. Authenticated preview remains available.", + "certificateProvider": "Certificate source / validation method", + "providerHttp01": "Let's Encrypt - domain validation (HTTP-01)", + "providerCloudflare": "Let's Encrypt - Cloudflare API (DNS-01)", + "providerOvh": "Let's Encrypt - OVH API (DNS-01)", + "providerRfc2136": "Let's Encrypt - RFC2136 / TSIG (DNS-01)", + "providerImport": "Import certificate and private key (PEM)", + "providerExisting": "Existing server files / external paths (advanced)", + "providerHint_http01": "RustProxy runs Certbot HTTP-01. You do not provide certificate file paths here. Every domain must resolve to this proxy and public TCP port 80 must reach its HTTP listener. The issued pair is stored in RustProxy-managed certificate storage. Wildcards require DNS-01.", + "providerHint_cloudflare": "RustProxy runs Certbot DNS-01 with the Cloudflare plugin. You do not provide certificate file paths here. Use a restricted Zone:DNS:Edit API token; the issued pair is stored in RustProxy-managed certificate storage and can be renewed automatically.", + "providerHint_ovh": "RustProxy runs Certbot DNS-01 with the OVH plugin. You do not provide certificate file paths here. Supply credentials limited to the required DNS zones; the issued pair is stored in RustProxy-managed certificate storage and can be renewed automatically.", + "providerHint_rfc2136": "RustProxy runs Certbot DNS-01 with RFC2136/TSIG. You do not provide certificate file paths here. The DNS server must accept signed updates for the required challenge records; the issued pair is stored in RustProxy-managed certificate storage.", + "providerHint_import": "Choose certificate/fullchain and private-key PEM files from your computer. RustProxy validates the pair and copies it into /certs; the browser-side source path is not saved.", + "providerHint_existing": "Reference files already present on the RustProxy server. Choose managed filenames from /certs or absolute external paths such as /etc/letsencrypt/live/example.com/fullchain.pem and privkey.pem. External files are not copied into RustProxy backups.", + "acmeCfToken": "Cloudflare API token", + "acmeOvhEndpoint": "OVH API region", + "acmeOvhApplicationKey": "OVH application key", + "acmeOvhApplicationSecret": "OVH application secret", + "acmeOvhConsumerKey": "OVH consumer key", + "acme.invalid_provider": "Select a supported certificate provider.", + "acme.invalid_api_credential": "API credential is missing or has invalid characters.", + "acme.invalid_ovh_endpoint": "Select OVH Europe or Canada.", + "acme.http01_no_wildcard": "HTTP-01 cannot issue wildcard names. Select a DNS-01 provider.", + "acme.http01_listener_required": "HTTP-01 needs an active plaintext HTTP listener reachable on public port 80.", + "cacheScopeLegacy": "Unassigned / old scope", + "certificateLibrary": "Certificates", + "certificateWorkspaceHelp": "Manage installed certificates, automatic renewal, and generation/import in separate views.", + "certificateTabInstalled": "Installed", + "certificateTabRenewal": "Renewal", + "certificateTabGenerate": "Generate", + "certificateInstalledTitle": "Installed certificates", + "certificateInstalledHelp": "Certificates available to vhosts, redirects, 404 hosts, and the administration listener.", + "certificateGenerateTitle": "Generate and import", + "certificateGenerateHelp": "Choose Let's Encrypt/ACME issuance, PEM upload, or existing files from RustProxy's private certificate directory.", + "certificateOperationStatus": "Latest ACME operation", + "certificateNoRenewalOperation": "No renewal operation in this process.", + "certificateStateIdle": "Idle", + "certificateStateRunning": "Running", + "certificateStateReady": "Ready", + "certificateStateComplete": "Complete", + "certificateStateFailed": "Failed", + "certbotLogs": "Certbot logs", + "systemDiagnostics": "System / process", + "vhWebsockets": "WebSocket support", + "vhLocations": "Custom locations", + "locationPath": "Location prefix", + "locationPool": "Vhost backends", + "locationMissing": "Missing backend", + "locationLimit": "A host supports up to 64 locations.", + "locationAccess": "Location access control", + "locationHeaders": "Location headers", + "locationAddBackend": "Add dedicated backend", + "locationAdd": "Add location", + "locationHelp": "The longest matching prefix wins. The original path and query are retained. Select a backend from this host or add a dedicated backend. Host authentication and ACL always apply.", + "aclUse": "Enable IP access list", + "aclAllow": "Allow", + "aclNetwork": "IP network (CIDR)", + "aclAction": "Action", + "aclAdd": "Add IP rule", + "authMode": "Authentication", + "authNone": "None", + "authTinyauth": "Tinyauth", + "authBasic": "HTTP Basic Auth", + "basicRealm": "Realm (login prompt)", + "basicRequireTls": "Require HTTPS for Basic Auth", + "basicForward": "Forward verified Authorization to backend", + "basicPassword": "Password (12-128 bytes)", + "basicKeepPassword": "Leave empty to keep the password", + "basicHashSaved": "Password set", + "basicHashRequired": "Set a password for each user.", + "basicAddUser": "Add user", + "basicHelp": "Only Argon2id hashes are stored. Verified credentials are removed from upstream requests unless forwarding is enabled. Enable forwarding only to a trusted HTTPS backend or an isolated network. Protected hosts bypass shared cache.", + "basic.too_many_users": "At most 64 users per host.", + "basic.invalid_username": "Invalid username.", + "basic.duplicate_username": "Duplicate username.", + "basic.unsupported_hash": "Use an Argon2id password hash generated by RustProxy.", + "basic.invalid_hash": "Invalid password hash.", + "basic.invalid_realm": "Invalid login realm.", + "basic.users_required": "Basic Auth requires at least one user.", + "basic.busy": "Password workers are busy; try again.", + "renewalTitle": "Automatic renewal", + "renewalHelp": "Managed ACME certificates are checked about every 12 hours while RustProxy is running. Certbot decides when renewal is due. Validated replacements are deployed without restarting listeners. Keep provider credentials and HTTP-01 port 80 available.", + "renewalNone": "No managed ACME certificates in the library.", + "renewalEnabled": "Automatic renewal enabled", + "renewalDisabled": "Automatic renewal disabled", + "renewalEnable": "Enable automatic renewal", + "renewalDisable": "Disable automatic renewal", + "renewalNow": "Check now", + "renewalDryRun": "Validate renewal (dry run)", + "renewalLast": "Last check", + "renewalNext": "Next check", + "renewalExpires": "Valid until", + "acme.renewing": "Checking certificate renewal", + "acme.renewed": "Certificate renewed and deployed", + "acme.not_due": "Certificate does not need renewal yet", + "acme.renewal_test_ok": "Renewal validation passed; active certificate unchanged", + "acme.renewal_profile_missing": "Saved renewal profile is missing or unreadable. Restore the private ACME profile.", + "acme.configuration_changed": "Certificate configuration changed during renewal; nothing deployed.", + "acme.deployment_failed": "Renewed certificate failed configuration validation; previous certificate remains active.", + "acme.not_managed": "Only managed ACME certificates can be renewed automatically.", + "acme.certificate_not_found": "Certificate no longer exists.", + "acme.renewal_disabled": "Automatic renewal is disabled.", + "acme.openssl_not_installed": "OpenSSL is required to inspect certificate validity.", + "cacheReason_basic_auth": "HTTP Basic authentication", + "locationPoolEligible": "Include in the default load-balancing pool", + "locationId": "Location ID", + "vhIpAccess": "Vhost IP access list", + "apiDocs": "Documentation", + "auth.invalid_request": "Invalid request. Check the supplied fields.", + "locationProxyHeaders": "Proxy request headers (to backend)", + "locationResponseHeaders": "Response headers (to client)", + "locationHeadersHelp": "Set replaces a header, append adds a value, remove deletes it. Applied after global and vhost rules, only for this location. Values are literal (no nginx variables). Transport and verified identity headers stay system-managed.", + "headersAdvancedJson": "Advanced header rules (JSON)", + "headersInvalid": "Invalid header rule. Check name, operation and value.", + "headersProtected": "Transport and proxy identity headers cannot be overridden here. Use forwarding, authentication or response settings.", + "docsTitle": "Documentation", + "docsSession": "Uses your administrator session. API writes require CSRF protection.", + "docsSignIn": "Sign in to view documentation", + "docsPanel": "Back to panel", + "docsWrites": "Enable write operations", + "docsWriteConfirm": "This sends a real change to RustProxy. Continue?", + "docsExpired": "Session ended. Sign in again.", + "docsError": "Documentation could not be loaded.", + "docsAccount": "Signed in as", + "docsReadOnly": "Read-only execution by default. SSE streams and login/setup are not executed here.", + "docsLogout": "Sign out", + "authPrivateNote": "Access is restricted to authorized administrators.", + "advanced": "Advanced", + "docsLicenses": "Swagger UI licenses", + "vhTrafficPolicy": "Traffic policy", + "vhHttp3Enabled": "Enable HTTP/3 for this vhost", + "vhCloudflareClientIp": "Use verified Cloudflare client IP", + "vhTimingHeaders": "Expose Server-Timing diagnostics", + "vhRateEnabled": "Enable per-client IP rate limit", + "vhRateRequests": "Requests per window", + "vhRateWindow": "Window (seconds)", + "vhRateBurst": "Burst allowance", + "vhCountryMode": "Country access policy", + "vhCountryBlock": "Block listed countries", + "vhCountryAllow": "Allow only listed countries", + "vhCountryList": "Countries (ISO 3166-1 alpha-2)", + "vhCountryPolicyHelp": "Country rules use the installed GeoIP database. In allow-only mode, a listed country OR an explicitly allowed IP can pass this vhost scope. In block mode, a listed country OR explicitly denied IP is blocked. An unresolved country fails the country branch.", + "vhCountry": "Country", + "vhAddCountryRate": "Add country rate limit", + "vhCountryRateLimit": "At most 64 country-specific rate limits are allowed.", + "vhRateHelp": "Country-specific limits override the base per-IP limit. Exceeded limits return 429; denied countries return 403.", + "vhHealthOffWarning": "Active backend probes are disabled. Passive Backend Health is configured separately and remains off unless explicitly enabled.", + "vhFrontendSelectionOnly": "Select named listeners for this vhost. Listener IP addresses, ports and H2/H3 capabilities are configured only in Settings > Listeners.", + "vhForceSsl": "Force SSL (redirect HTTP to HTTPS)", + "vhForceSslHelp": "Plain HTTP requests for this vhost receive a 308 redirect to HTTPS while preserving path and query. Select at least one plain HTTP listener and at least one TLS listener. If several TLS ports are selected, port 443 is used; otherwise the TLS target port must be unambiguous.", + "vhForceSslNeedsTls": "Force SSL requires at least one enabled TLS listener for this vhost.", + "vhForceSslPortConflict": "Force SSL cannot choose the HTTPS redirect target because selected TLS listeners use multiple non-standard ports. Select one TLS port or include port 443.", + "vhForwardClientIp": "Forward client IP to backend", + "vhForwardClientIpHelp": "Keep enabled for normal reverse-proxy behavior. Disable to withhold Forwarded, X-Forwarded-For and X-Real-IP from the application while RustProxy, ACLs, rate limits and Tinyauth still use the real client IP.", + "vhClientIpHidden": "Client IP hidden from backend", + "listenerMigrateInline": "Existing vhost listener (will migrate)", + "vhDebugOriginPreset": "Apply recommended preset", + "vhDebugOriginTlsWarning": "This preset keeps backend TLS certificate verification disabled. The connection is encrypted, but CA and hostname identity are not checked. Continue?", + "vhOriginPresetHelp": "Debug origin defaults for new backend rows: HTTPS by IP, default LB pool ON, private origin ON, HTTP/1.1, Preserve client Host ON and TLS verification OFF. Re-enable TLS verification whenever the origin certificate can be validated.", + "vhDebugOriginMismatch": "This HTTPS-by-IP origin does not match the debug forwarding preset (pool ON, private origin ON, HTTP/1.1, preserve Host ON, TLS verification OFF). If you enable verification, set TLS server name to the certificate DNS name and add its private CA when needed. Continue with the current values?", + "vhCacheStaticCookie": "Ignore request cookies for selected static file extensions", + "vhCacheStaticCustom": "Use a custom static extension list", + "vhCacheStaticExtensions": "Static extensions (one per line, without dot)", + "vhCacheStaticHelp": "Inherit the global policy or override it for this vhost. Ignore Cookie only for files whose bytes do not depend on the session; Set-Cookie, private/no-store and Vary: Cookie still block shared storage.", + "cacheStaticTitle": "Static asset cache boost", + "cacheStaticCookie": "Ignore request cookies for static files", + "cacheStaticExtensions": "Global static extensions (one per line, without dot)", + "cacheStaticHelp": "Enable this only for files whose bytes do not depend on the session. Request Cookie then does not affect the shared cache key for these extensions; responses with Set-Cookie, private/no-store or Vary: Cookie are still never stored.", + "vhHttp2Enabled": "Enable HTTP/2 for this vhost", + "vhFrontendProtocolsHelp": "H2 and H3 are per-vhost opt-outs. A switch is available only when at least one selected listener has that protocol enabled in Settings > Listeners.", + "administration": "Administration", + "administrationActiveHttp": "Active HTTP administration", + "administrationActiveHttps": "Active HTTPS administration", + "administrationCertificate": "Certificate", + "administrationGeneratedCertificate": "Application-generated default certificate", + "administrationHelp": "HTTPS allows the administration listener to bind beyond loopback. Plain HTTP on the HTTPS port is redirected automatically. Listener and certificate changes take effect after restart.", + "administrationHosts": "Allowed DNS hostnames (optional strict allowlist)", + "administrationHttps": "Enable HTTPS for administration", + "administrationListen": "Listen address", + "cacheOverhead": "Overhead", + "cacheResidentAge": "X-Cache-Resident-Age", + "controlPlane": "CONTROL PLANE", + "logging": "Logging", + "loggingTitle": "Structured access logging", + "loggingTabOutputs": "Outputs", + "loggingTabFormat": "JSON format", + "loggingTabErrors": "Error log", + "loggingDescription": "Write configurable JSON access logs to a file, Logstash or Loki.", + "loggingHelp": "Live log viewing stays in memory. External sinks use a bounded background queue and do not block request I/O.", + "loggingFile": "JSON file", + "loggingPath": "File path", + "loggingAddress": "TCP address", + "loggingTokenEnv": "Bearer token environment variable", + "loggingLabels": "Labels (JSON object)", + "loggingFormat": "Configurable JSON format", + "loggingFormatHelp": "Map output names to source fields. Leave mapping empty for the complete standard entry.", + "loggingFields": "Field mapping (JSON object)", + "loggingStatic": "Static fields (JSON object)", + "loggingJsonInvalid": "Logging JSON fields must contain valid JSON objects.", + "loggingQueue": "Sink queue", + "loggingDropped": "dropped", + "loggingSent": "sent", + "loggingCapacity": "capacity", + "errorLogTitle": "Error file", + "errorLogRoutingHelp": "Warnings, errors and worker panics go to this file when enabled, or to the console/systemd journal when disabled. File failures fall back to the console.", + "errorLogPath": "Error log path", + "errorLogFormat": "Format", + "tls12": "TLS 1.2", + "tls13": "TLS 1.3", + "tlsPolicy": "TLS policy", + "tlsPolicyHelp": "This is the global TLS policy inherited by every TLS host in the application. Per-vhost policy may only narrow it. HTTP/3 requires TLS 1.3.", + "tlsCiphers": "Cipher suites (one per line; empty = rustls defaults)", + "tlsAvailableCiphers": "Available cipher suites", + "tlsPolicyVersionRequired": "Enable at least one TLS version.", + "vhTlsOverride": "Override global TLS policy", + "vhTlsOverrideHelp": "Use only when this vhost needs different TLS versions or cipher suites. Empty cipher list keeps rustls defaults.", + "vhTlsHttp3Conflict": "HTTP/3 uses the global TLS policy. Disable HTTP/3 on this vhost before using a different TLS override.", + "vhListeners": "Listeners", + "vhProtocols": "Protocols", + "loggingMode": "JSON / ASYNC / BOUNDED", + "loggingPushUrl": "Push URL", + "cacheHotObjects": "Hot objects", + "cacheHotMemory": "Hot memory", + "cacheColdObjects": "Cold objects", + "cacheColdDisk": "Cold disk", + "cacheTierMoves": "Tier moves", + "cacheColdHits": "cold hits", + "cacheTier": "Tier", + "cacheStorage": "Memory / disk", + "cacheColdTitle": "Persistent cold cache", + "cacheColdEnable": "Enable persistent disk tier", + "cacheColdHelp": "Choose what is moved from hot RAM to persistent disk. Cold hits are read asynchronously and promoted back to RAM. Metadata is readable JSON; bodies are raw .body files.", + "cacheColdPath": "Cold path (inside data directory)", + "cacheColdBytes": "Cold disk budget (MiB)", + "cacheColdEntries": "Cold object limit", + "cacheColdAfter": "Demote after idle seconds", + "cacheHot": "Hot", + "cacheCold": "Cold", + "cacheReason_request_no_cache": "Client no-cache / hard reload requires validation", + "cacheReason_request_max_age": "Client max-age requires validation", + "cacheReason_request_min_fresh": "Client min-fresh requires validation", + "cacheReason_request_pragma_no_cache": "Client Pragma no-cache / legacy hard reload", + "cacheReason_cache_revalidated": "Stored object validated by origin", + "cacheReason_cache_conditional_hit": "Fresh object satisfied a conditional request", + "cacheReason_request_no_store": "Client no-store bypass", + "cacheReason_request_precondition": "Client precondition requires origin", + "loggingPreset": "Format preset", + "loggingPresetCustom": "Custom", + "loggingPresetStandard": "RustProxy standard", + "loggingPresetNginx": "Nginx-style JSON", + "loggingPresetEcs": "ECS-style field names", + "loggingPresetMinimal": "Minimal", + "loggingBuilder": "Visual field builder", + "loggingBuilderOpen": "Configure fields", + "loggingAdvancedJson": "Advanced JSON mapping", + "loggingBuilderHelp": "Select source fields and optionally rename their JSON keys. Applying the visual editor normalizes each source field to at most one output key; use advanced JSON for duplicate aliases.", + "loggingSourceField": "Source field", + "loggingOutputField": "Output JSON key", + "loggingPreview": "Preview", + "loggingOutputRequired": "Every enabled log field needs an output JSON key.", + "loggingOutputDuplicate": "Output JSON keys must be unique.", + "aclProfiles": "ACL profiles", + "acl-profiles": "ACL profiles", + "acl-profilesTitle": "Reusable ACL profiles", + "acl-profilesDescription": "Define reusable IP and country access rules, then attach them to one or more virtual hosts.", + "aclProfilesIntro": "Reusable access profiles can be attached to multiple vhosts. Per-vhost ACL and country rules remain active and are enforced together with the profile.", + "aclProfilesReusable": "REUSABLE POLICY", + "aclProfileNew": "New profile", + "aclProfileEdit": "Editing profile", + "aclProfileEditor": "Profile configuration", + "aclProfileId": "Profile ID", + "aclProfileName": "Display name", + "aclProfileIpEnabled": "Enable IP ACL", + "aclProfileRules": "IP rules — one “allow|deny CIDR” per line", + "aclProfileCombineHelp": "Country and IP exceptions are combined with OR inside this profile. Explicit deny rules and blocked countries win; attached profile, vhost and location policies must still all pass.", + "aclProfilePolicyMode": "Choose the base policy", + "aclProfilePolicyModeHelp": "Use a guided allow-list or block-list for normal cases. Custom mode keeps full control for mixed policies.", + "aclProfileModeAllowExceptions": "Block by default", + "aclProfileModeAllowExceptionsHelp": "Allow selected countries OR selected IP/CIDR ranges.", + "aclProfileModeBlockExceptions": "Allow by default", + "aclProfileModeBlockExceptionsHelp": "Block selected countries OR selected IP/CIDR ranges.", + "aclProfileModeCustom": "Custom", + "aclProfileModeCustomHelp": "Control default action, country mode and every IP action manually.", + "aclProfileCountryExceptions": "Country exceptions", + "aclProfileUseCountries": "Use countries", + "aclProfileAllowedCountries": "Allowed countries (ISO codes)", + "aclProfileBlockedCountries": "Blocked countries (ISO codes)", + "aclProfileAllowedCountriesHelp": "A request from any listed country is allowed even when its IP is not listed.", + "aclProfileBlockedCountriesHelp": "A request from any listed country is blocked even when its IP is otherwise allowed.", + "aclProfileCustomCountryHelp": "Custom country behavior is controlled below. Blocked countries always deny.", + "aclProfileIpExceptions": "IP / CIDR exceptions", + "aclProfileAddIpException": "Add IP exception", + "aclProfileAllowedIpsHelp": "Add an IPv4/IPv6 address or CIDR. These addresses are allowed in addition to the selected countries.", + "aclProfileBlockedIpsHelp": "Add an IPv4/IPv6 address or CIDR. These addresses are blocked in addition to the selected countries.", + "aclProfileCustomIpHelp": "Each IP rule can allow or deny independently. Plain IP addresses are accepted as host rules.", + "aclProfileBulkExceptions": "Paste many IP addresses / CIDRs", + "aclProfilePreview": "Effective policy", + "aclProfilePreviewDefaultDeny": "DEFAULT: BLOCK", + "aclProfilePreviewDefaultAllow": "DEFAULT: ALLOW", + "aclProfilePreviewAllowedCountries": "ALLOW COUNTRY", + "aclProfilePreviewBlockedCountries": "BLOCK COUNTRY", + "aclProfilePreviewAllowedIps": "ALLOW IP", + "aclProfilePreviewBlockedIps": "BLOCK IP", + "aclProfilePreviewIpRules": "IP RULES", + "aclProfilePreviewNoExceptions": "No exceptions configured", + "aclProfilePreviewBlocksAll": "This profile currently blocks all traffic. Add a country or IP exception to allow access.", + "aclProfilePreviewAllowsAll": "This profile currently allows all traffic. Add a country or IP exception to block access.", + "aclProfilePreviewAllowNote": "Access is granted when the country OR the client IP matches an allowed exception. Explicit deny rules still win in custom policies.", + "aclProfilePreviewBlockNote": "Access is denied when the country OR the client IP matches a blocked exception.", + "aclProfilePreviewCustomNote": "Custom mode uses backend precedence: explicit IP deny and blocked country deny; allow-only countries and explicit IP allows can satisfy the same policy scope.", + "aclProfileOr": "OR", + "aclProfileCountriesShortAllow": "Allowed countries", + "aclProfileCountriesShortBlock": "Blocked countries", + "aclProfileIpsShortAllow": "Allowed IPs", + "aclProfileIpsShortBlock": "Blocked IPs", + "aclProfileIpRulesShort": "IP rules", + "aclProfileIpOff": "IP ACL disabled", + "aclProfileCountriesOff": "Country filtering disabled", + "aclProfileAttached": "Attached to", + "aclProfileInUse": "Profile is attached to vhosts", + "aclProfilesEmpty": "No reusable ACL profiles yet.", + "aclProfileCountryInvalid": "Country codes must use two-letter ISO codes.", + "aclProfileCountryRequired": "Add at least one country or disable country exceptions.", + "aclProfileDuplicate": "An ACL profile with this ID already exists.", + "vhAclProfile": "ACL profile", + "vhAclProfileNone": "No reusable profile", + "vhAclProfileHelp": "Optional reusable profile. Inline vhost ACL and country rules below are still enforced in addition to the profile.", + "vhHostAdd": "Add hostname", + "vhHostsHelp": "Add multiple hostnames (e.g. domain.local i subdomain.local) with Enter or comma. They share this vhost and its backend/settings. Wildcards are supported only as leading *. Exact hosts take priority.", + "vhHostInvalid": "Invalid hostname or wildcard pattern.", + "vhHostRequired": "Add at least one hostname.", + "vhLifetimeChart": "Lifetime request rate", + "vhReqPerSecond": "req/s", + "vhPeak": "peak", + "listenerAddresses": "Bind addresses", + "listenerAddressesInvalid": "Provide 1–16 unique listener addresses.", + "listenerAddressesExplicit": "When a listener uses multiple addresses, select explicit IPs; 0.0.0.0 and :: are not allowed.", + "ruleSyntaxLine": "Invalid ACL rule on line", + "vhForceSslNeedsHttp": "Force SSL needs at least one selected plain HTTP (non-TLS) listener. A listener named “HTTP” may still be configured as TLS in Settings > Listeners.", + "aclProfileRuleBuilder": "IP rules", + "aclProfileBulkRules": "Paste / import rules", + "aclProfileBulkHelp": "Paste one “allow|deny CIDR” rule per line. Invisible Unicode spacing is normalized during import and errors identify the exact line.", + "aclProfileImportRules": "Import rules", + "aclProfileRulesImported": "ACL rules imported into the visual editor.", + "streamHosts": "Stream hosts", + "streamsHelp": "Raw TCP/UDP forwarding on dedicated ports. Stream traffic bypasses HTTP vhosts, ACL, cache, headers and compression.", + "streamAdd": "Add stream host", + "saveStreams": "Save stream hosts", + "streamRestartHelp": "Changing a stream listener requires a RustProxy restart.", + "streamRestartPending": "Saved stream changes are pending. Restart RustProxy to apply them.", + "streamActive": "Currently active streams", + "streamNone": "No stream hosts active", + "streamEndpoint": "Stream endpoint", + "streamId": "Stream ID", + "streamName": "Display name", + "streamProtocol": "Protocol", + "streamListen": "Listen address", + "streamUpstream": "Upstream address", + "streamConnectTimeout": "TCP connect timeout (seconds)", + "streamIdleTimeout": "Idle/session timeout (seconds)", + "streamMaxConnections": "Max connections/sessions", + "streamTcp": "TCP", + "streamUdp": "UDP", + "streamLimit": "At most 128 stream hosts are supported.", + "streamDuplicate": "Stream host IDs must be unique.", + "streams": "Streams", + "streamsTitle": "TCP / UDP streams", + "streamsDescription": "Manage raw Layer-4 listeners, upstreams and live traffic telemetry.", + "streamsPageIntro": "TCP/UDP streams are independent Layer-4 proxies. Each stream owns a dedicated listener and upstream and bypasses the HTTP vhost, ACL and cache stack.", + "streamLayer4": "LAYER 4 / TCP + UDP", + "streamList": "Streams", + "streamSearch": "Search streams", + "streamSearchPlaceholder": "ID, name, listener or upstream…", + "streamSearchEmpty": "No streams match this search.", + "streamEmpty": "No TCP/UDP streams yet. Add a listener to start forwarding Layer-4 traffic.", + "streamNew": "New stream", + "streamAddListener": "Add TCP/UDP listener", + "streamEditor": "Stream configuration", + "streamEditLabel": "EDITING", + "streamNewLabel": "NEW", + "streamDetailsTab": "Details", + "streamNetworkTab": "Listener & upstream", + "streamLimitsTab": "Limits", + "streamNetwork": "Listener & upstream", + "streamLimits": "Connection / session limits", + "streamDedicatedListenerHelp": "A stream listener is created together with the stream and is separate from HTTP/HTTPS listeners.", + "streamLimitsHelp": "Limits apply to concurrent TCP connections or per-client UDP sessions. TCP connect timeout is not used by UDP.", + "streamListenAdd": "Add listen address", + "streamListenHelp": "Enter one IP:port listener address and press Enter. Listener changes become active after RustProxy restarts.", + "streamListenInvalid": "The listener address must be one IP:port value without spaces or commas.", + "streamListenRequired": "Add a listener address.", + "streamDelete": "Delete this stream? The active listener remains until RustProxy restarts.", + "streamPendingRestart": "Pending restart", + "streamRunning": "Running", + "streamActiveSessions": "Active connections / sessions", + "streamSessionsTotal": "Connections / sessions", + "streamErrors": "Errors", + "streamRejected": "Rejected", + "streamTransferred": "Transferred", + "streamTrafficChart": "Live traffic rate", + "streamClientToUpstream": "client → upstream", + "streamUpstreamToClient": "upstream → client", + "streamNoLiveMetrics": "No live metrics until this stream is active after restart.", + "streamIdleShort": "Idle timeout", + "listenerAddressAdd": "Add bind address", + "listenerAddressesHelp": "Press Enter or comma to add a bind address. Multiple addresses use the same listener policy; wildcard 0.0.0.0/:: cannot be mixed with explicit addresses.", + "cacheColdOnIdle": "Move old/idle objects to cold storage", + "cacheColdOnLru": "Move hot LRU evictions to cold storage", + "cacheColdOnShutdown": "Flush hot cache to cold storage during graceful shutdown", + "certificateCovers": "Certificate domains", + "certificateCoverageUnknown": "Domain data (DNS SAN) is unavailable. Reload certificate files or import the certificate again.", + "certificateDetails": "Certificate details", + "certificateFiles": "Certificate / key files", + "redirects": "Redirects", + "redirectsTitle": "Redirect with intent.", + "redirectsDescription": "Local redirect hosts with explicit match, target, status, path and query behavior.", + "redirectsPageIntro": "Redirect hosts answer locally without opening an upstream connection. Rules can match scheme and path, then redirect to a URL, another host or a path.", + "redirectLayer": "HTTP REDIRECTION", + "redirectList": "Redirect hosts", + "redirectSearch": "Search redirects", + "redirectSearchPlaceholder": "ID, hostname, target…", + "redirectNew": "New redirect", + "redirectEmpty": "No redirect hosts configured.", + "redirectSearchEmpty": "No redirects match this search.", + "redirectEditor": "Redirect configuration", + "redirectId": "Redirect ID", + "redirectName": "Display name", + "redirectHosts": "Source hostnames", + "redirectFrontend": "FRONTEND", + "redirectListeners": "Listeners", + "redirectBehavior": "BEHAVIOR", + "redirectRules": "Rules", + "redirectAddRule": "Add rule", + "redirectRulesHelp": "Longest matching path wins. 307/308 preserve the request method; 301/302/303 may change it in clients.", + "redirectSafetyHint": "Redirect hosts do not contact a backend. Global host ACLs still apply. Avoid self-referential rules that can create browser redirect loops.", + "redirectRule": "Rule", + "redirectRuleId": "Rule ID", + "redirectSourceScheme": "Incoming scheme", + "redirectSchemeAny": "HTTP or HTTPS", + "redirectSchemeHttp": "HTTP", + "redirectSchemeHttps": "HTTPS", + "redirectSchemePreserve": "Preserve incoming scheme", + "redirectMatchType": "Path match", + "redirectMatchPrefix": "Prefix", + "redirectMatchExact": "Exact", + "redirectSourcePath": "Source path", + "redirectTargetType": "Target type", + "redirectTargetUrl": "Absolute URL", + "redirectTargetHost": "Host / application", + "redirectTargetPath": "Path on the same host", + "redirectTarget": "Target", + "redirectTargetScheme": "Target scheme", + "redirectPathMode": "Path handling", + "redirectPathAppend": "Append full source path", + "redirectPathStrip": "Strip matched prefix and append remainder", + "redirectPathReplace": "Use configured target path only", + "redirectQueryMode": "Query handling", + "redirectQueryPreserve": "Preserve source query", + "redirectQueryDrop": "Drop source query", + "redirectStatus": "HTTP status", + "redirect301": "301 Moved Permanently", + "redirect302": "302 Found", + "redirect303": "303 See Other", + "redirect307": "307 Temporary Redirect (preserve method)", + "redirect308": "308 Permanent Redirect (preserve method)", + "redirectCacheSeconds": "Browser/shared cache max-age (seconds; 0 = no-store)", + "redirectHostsRequired": "Add at least one source hostname.", + "redirectListenersRequired": "Select at least one listener.", + "redirectRulesRequired": "Add at least one redirect rule.", + "redirectDuplicate": "A redirect with this ID already exists.", + "redirectUnsaved": "Discard unsaved redirect changes?", + "redirectNewLabel": "NEW", + "administrationSessionDays": "Administrator session lifetime (days)", + "administrationSessionDaysHelp": "Browser sessions use an absolute 7–30 day lifetime. Changing this value applies immediately; password changes and “revoke all sessions” still invalidate sessions at once.", + "vhLocalListenerRisk": "Allow local RustProxy listener as backend (risk)", + "vhLocalListenerRiskHelp": "Shown when the backend URL directly targets the configured or active RustProxy administration listener. localhost, 127.0.0.1 and ::1 are treated as equivalent loopback forms on the same administration port. Enabling it can expose the control plane or create a proxy loop; private/loopback targets still require “Allow private origin IP”.", + "vhLocalListenerRiskConfirm": "This can route requests into a local RustProxy listener, including the administration port, and may create a loop or expose the control plane. Accept this risk?", + "cacheResidentHistory": "Resident RAM history", + "networkHistory": "RX / TX history", + "responseLatencyHistory": "Response time history", + "sourceIp": "Source IP", + "cacheColdOnAge": "Move objects to cold storage after a maximum hot-tier age", + "cacheColdMaxHot": "Maximum hot-tier age (seconds)", + "cacheColdPolicyHelp": "Idle time measures how long an object has not been used. Hot-tier age measures continuous residency in RAM even if the object is still requested. LRU pressure and graceful-shutdown handoff are independent policies.", + "documentation": "Documentation", + "locationPathPattern": "Path / regex pattern", + "locationMatchType": "Match type", + "locationMatchPrefix": "Prefix", + "locationMatchPriority": "Priority prefix (^~)", + "locationMatchExact": "Exact path (=)", + "locationMatchRegex": "Regex (~)", + "locationMatchRegexI": "Regex, case-insensitive (~*)", + "locationMatch_prefix": "prefix", + "locationMatch_prefix_priority": "priority prefix", + "locationMatch_exact": "exact", + "locationMatch_regex": "regex", + "locationMatch_regex_insensitive": "regex i", + "locationAction": "Action", + "locationProxyAction": "Proxy to backend", + "locationReturnAction": "Custom response (return)", + "locationProxyShort": "proxy", + "locationReturnShort": "local response", + "locationHandling": "Request handling", + "locationRewriteMode": "Backend path rewrite", + "locationRewritePreserve": "Keep original path", + "locationRewriteStrip": "Strip matched prefix", + "locationRewriteReplace": "Replace matched prefix", + "locationRewriteRegex": "Regex replacement", + "locationRewriteValue": "Replacement path / expression", + "locationRewriteHelp": "Query parameters are preserved. Prefix rewrites operate on the normalized routing path. Regex replacement supports Rust regex captures such as $1. Rewritten paths are validated before forwarding.", + "locationResponseStatus": "Response status", + "locationResponseType": "Content-Type", + "locationResponseBody": "Response body", + "headersVisualBuilder": "Visual configurator", + "locationDocsTitle": "Custom locations documentation", + "headersDocsTitle": "Header rules documentation", + "locationDocsIntro": "Custom locations are evaluated inside the selected vhost after host authentication and ACL. They never bypass vhost security.", + "locationDocsPrefix": "Prefix: matches a complete path segment prefix. The longest prefix wins. Priority prefix behaves like nginx ^~ and prevents regex locations from taking precedence.", + "locationDocsExact": "Exact: matches only the exact normalized path, like nginx location = /path.", + "locationDocsRegex": "Regex: first matching regex wins after exact/priority-prefix processing. Case-sensitive and case-insensitive modes are supported. Rust regex syntax is used; look-around/backreferences in the pattern are intentionally unsupported.", + "locationDocsReturn": "Custom response: answer locally with a chosen HTTP status, Content-Type and body without opening a backend connection. Location response-header rules still apply.", + "locationDocsRewrite": "Backend path rewrite: keep the original target, strip/replace a matched prefix, or use regex capture replacement. The original query string is preserved.", + "locationDocsAccess": "Per-location IP ACL, cache override and request/response header rules are composed with the vhost policy. A location cannot disable host authentication or host ACL.", + "locationDocsLimits": "Up to 64 locations per vhost. Regexes and response bodies are bounded; transport, forwarding identity and WebSocket protocol headers remain system-managed.", + "headersDocsIntro": "Header rules are applied in order: global → vhost → matching location. Use the visual configurator for normal changes; JSON remains an advanced escape hatch.", + "headersDocsSet": "Set: replace all existing values of a header with the configured literal value.", + "headersDocsAppend": "Append: add another value without deleting existing values.", + "headersDocsRemove": "Remove: delete the header before forwarding or before sending the response.", + "headersDocsProtected": "Protected transport/identity headers (Host, Connection, Content-Length, Forwarded/X-Forwarded-*, WebSocket proof headers and similar) cannot be overridden here.", + "headersDocsOrder": "Values are literal; nginx variables are not interpreted. Request rules run before canonical forwarding metadata is applied, and response rules run before final RustProxy security/diagnostic policy.", + "cacheColdPolicyTitle": "Hot → cold policy", + "headerRule": "Header rule", + "headerRulesEmpty": "No rules yet. Add a rule below.", + "headerRuleHelp": "Choose Set, Append or Remove. Transport, forwarding identity and WebSocket protocol headers remain protected.", + "locationPreviewTitle": "Effective behavior preview", + "locationPreviewMatch": "Matching", + "locationPreviewHandling": "Handling", + "locationPreviewRewrite": "Backend path", + "locationPreviewCache": "Cache", + "locationPreviewAccess": "Access", + "locationPreviewHeaders": "Header rules", + "locationPreviewDisabled": "This location is disabled and will not match requests.", + "locationPreviewBackendSkipped": "backend is not contacted", + "locationPreviewVhostAcl": "Vhost authentication and ACL only", + "locationPreviewCustomAcl": "Vhost policy + location ACL", + "locationPreviewRequestRules": "request", + "locationPreviewResponseRules": "response", + "locationPreviewQueryPreserved": "query string is preserved", + "locationDocsMatchHeading": "1. Matching and precedence", + "locationDocsActionHeading": "2. Proxy, rewrite and local response", + "locationDocsPolicyHeading": "3. Access, cache and headers", + "locationDocsExamplesHeading": "Common examples", + "locationDocsExampleExact": "Health endpoint: Exact (=) /health + Custom response 200 answers locally without a backend connection.", + "locationDocsExampleStatic": "Static assets: Priority prefix (^~) /static/ keeps regex locations from overriding it; cache can be enabled for this location.", + "locationDocsExampleRegex": "Versioned API: Regex (~) ^/api/v[0-9]+/ can select a backend and use regex replacement groups such as $1.", + "cacheOutcomeHistory": "HIT / MISS / BYPASS activity", + "cacheHotMemoryHistory": "HOT RAM usage / limit", + "cacheTransientMemoryHistory": "Transient RAM usage / limit", + "cacheColdStorageHistory": "COLD storage usage / limit", + "processCpuHistory": "Process CPU history", + "processRssHistory": "Process RSS history", + "cacheMemoryBreakdown": "Hot / transient cache RAM", + "cacheTransientBreakdown": "Fill / cold queue / cold I/O", + "cacheColdStorageUsage": "COLD storage / budget", + "cacheColdStorageObjects": "objects", + "cacheTransientMemory": "Transient cache RAM", + "cacheTransientShort": "TOTAL", + "cacheFillShort": "fill", + "cacheQueueShort": "cold queue", + "cacheIoShort": "cold I/O", + "processRssTotal": "RSS total", + "memoryCacheHot": "HOT cache RAM / limit", + "memoryCacheTransient": "Cache transient RAM / limit", + "memoryTransform": "Transform reserved RAM / budget", + "memoryTransformInflight": "Active transforms / limit", + "memoryNonCacheRss": "Non-cache process RSS", + "transformMemoryUsage": "Transform RAM reservation", + "transformConcurrencyUsage": "Active transform slots", + "transformBudgetShort": "budget", + "processRssAnon": "Anonymous RSS", + "processRssFile": "File-backed / shared RSS", + "processDataSwap": "Data address space / swap", + "memoryAccountingHint": "RSS is the real process footprint. HOT/transient cache values are RustProxy-accounted allocations; transform memory is a conservative active reservation. Non-cache RSS is RSS minus accounted cache and includes transforms, connections, TLS, logging, Tokio/runtime state, allocator overhead and other buffers, so these rows are not all additive.", + "memoryAccountingInfo": "Memory accounting information", + "logTransport": "Transport", + "logWebSocket": "WebSocket", + "logUpstreamError": "Upstream error", + "listenerDefaultPortsHint": "New vhosts automatically select the standard HTTP :80 listener when it is available.", + "vhBackendDiagnostics": "Backend diagnostics", + "vhBackendHeader": "Expose selected backend (X-RustProxy-Backend)", + "vhBackendTimingHeader": "Expose backend response time (X-RustProxy-Backend-Time)", + "vhBackendDiagnosticsHelp": "Opt-in response headers. Backend time measures origin request-to-response-headers latency; responses served without contacting an origin do not report a backend.", + "authModeHelp": "Choose one authentication provider. The IP access list below is independent and can be combined with either provider.", + "taAdvanced": "Advanced Tinyauth settings", + "notfound": "404 hosts", + "notfoundTitle": "404 hosts, under control.", + "notfoundDescription": "Reserve domains that should answer locally with a status, placeholder or custom HTML.", + "notfoundPageIntro": "404 hosts reserve selected domains and answer locally without contacting a backend. They can use the same listeners and certificates as vhosts.", + "notfoundLayer": "LOCAL RESPONSE", + "notfoundList": "404 hosts", + "notfoundSearch": "Search 404 hosts", + "notfoundSearchPlaceholder": "ID, name or hostname…", + "notfoundNew": "New 404 host", + "notfoundEditor": "404 host configuration", + "notfoundId": "Host ID", + "notfoundName": "Display name", + "notfoundFrontend": "FRONTEND", + "notfoundBehavior": "RESPONSE", + "notfoundResponse": "Local response", + "notfoundMode": "Response type", + "notfoundModeWelcome": "RustProxy placeholder", + "notfoundModeEmpty": "Status only / empty", + "notfoundModeHtml": "Custom HTML", + "notfoundStatus": "HTTP status", + "notfoundHtml": "Custom HTML", + "notfoundResponseHelp": "The host never opens an upstream connection. The built-in placeholder is no-store and intentionally omits optional RustProxy diagnostic/server headers.", + "notfoundHosts": "Hostnames", + "notfoundNewLabel": "NEW", + "notfoundHostsRequired": "Add at least one hostname.", + "notfoundListenersRequired": "Select at least one listener.", + "notfoundHtmlRequired": "Custom HTML cannot be blank.", + "notfound204Empty": "HTTP 204, 205 and 304 responses must use Empty mode.", + "notfoundDuplicate": "A 404 host with this ID already exists.", + "notfoundUnsaved": "Discard unsaved 404 host changes?", + "notfoundEmpty": "No 404 hosts configured.", + "notfoundSearchEmpty": "No 404 hosts match the search.", + "prometheusTrustedNoToken": "Allow token-free reads from trusted CIDRs", + "prometheusTrustedNoTokenHelp": "Clients still must match Allowed client CIDRs. Enable only on a private, trusted monitoring network; forwarded headers are never trusted for this check.", + "docker-vhosts": "Docker vhosts", + "docker-vhostsTitle": "Docker services, named.", + "docker-vhostsDescription": "Discover published Docker ports and map services to base-domain names, custom domains and aliases without copying observed state into SQLite.", + "dockerVhosts": "Docker vhosts", + "dockerVhostsIntro": "Published TCP ports discovered by configured Docker agents. Map each service to a short name under the agent base domain or to any FQDN, add aliases, choose the Docker host IP, and inspect or tune live backend health.", + "dockerDiscovery": "DOCKER DISCOVERY", + "dockerRefresh": "Refresh agents", + "dockerAgentsSettings": "Docker agents settings", + "dockerAgents": "Docker agents", + "dockerDesiredState": "DESIRED STATE / SQLITE", + "dockerAgentsHelp": "Configure one agent per Docker host. HTTPS with a persistent self-signed certificate is the agent default; paste its SHA-256 fingerprint to pin it. Each agent keeps its own base domain and optional default backend IP.", + "dockerAgentAdd": "Add Docker agent", + "dockerAgentsSave": "Save and connect agents", + "dockerAgent": "DOCKER AGENT", + "dockerAgentNew": "New agent", + "dockerAgentId": "Agent ID", + "dockerAgentName": "Display name", + "dockerAgentEndpoint": "Agent endpoint", + "dockerAgentToken": "Bearer token", + "dockerBaseDomain": "Base domain", + "dockerTargetOverride": "Target IP override", + "dockerTargetAuto": "automatic", + "dockerListeners": "Listeners", + "dockerListenersRequired": "Select at least one listener for the Docker agent.", + "dockerNoAgents": "No Docker agents configured.", + "dockerNoAgentsConfigured": "Configure a Docker agent in Settings first.", + "dockerPublish": "Publish", + "dockerPreserveHost": "Preserve Host", + "dockerServiceMissing": "Not present in the latest inventory", + "dockerConnected": "CONNECTED", + "dockerError": "ERROR", + "dockerWaiting": "WAITING", + "dockerAgentDisabled": "This Docker agent is disabled.", + "dockerNoObservation": "No observation yet. Refresh the agents.", + "dockerHost": "Docker host", + "dockerDetectedIp": "Detected IP", + "dockerEffectiveIp": "Routing IP", + "dockerNoPublishedPorts": "No published TCP ports were reported.", + "dockerAssignmentsSave": "Save Docker vhosts", + "dockerDefaultTarget": "Default backend IP", + "dockerDetectedIps": "Detected host IPs", + "dockerHostname": "Hostname", + "dockerHostnameHelp": "Short names use {domain}; a full FQDN uses its own domain.", + "dockerAliases": "Aliases", + "dockerAliasAdd": "Add alias", + "dockerAliasesHelp": "Add aliases with Enter or comma. Short names use the agent base domain; full FQDNs are kept as entered.", + "dockerBackendIp": "Backend host IP", + "dockerBackendIpAuto": "Auto / agent default", + "dockerBackendScheme": "Backend protocol", + "dockerBindingIp": "Docker binding", + "dockerAgentDefaultIp": "agent default", + "dockerAgentEndpointHelp": "Use https:// for the default agent setup. Plain HTTP is supported only for compatibility and is visibly marked insecure.", + "dockerTlsFingerprint": "TLS certificate SHA-256", + "dockerTlsFingerprintHelp": "For the default self-signed agent certificate, paste the fingerprint printed by install.sh or rustproxy-docker-agent --print-fingerprint. Leave empty only for a certificate trusted by a public CA.", + "dockerTlsPinned": "TLS PINNED", + "dockerTlsPublicCa": "TLS / PUBLIC CA", + "dockerTlsPlain": "PLAIN HTTP", + "dockerTlsPlainHelp": "The agent token and inventory are not protected by TLS on this endpoint.", + "dockerTlsPublicCaHelp": "Certificate chain and hostname are verified against the system/public CA roots.", + "dockerAgentConnectionFailed": "Agent connection failed", + "dockerAgentVersion": "Agent version", + "dockerLastChecked": "Last checked", + "dockerNeverChecked": "never", + "dockerJustNow": "just now", + "dockerSecondsAgo": "{seconds}s ago", + "dockerMinutesAgo": "{minutes}m ago", + "dockerBackendMissing": "SERVICE MISSING", + "dockerBackendNotActive": "NOT PUBLISHED", + "dockerBackendChecking": "CHECKING", + "dockerBackendHealthOff": "HEALTH OFF", + "dockerBackendUp": "UP", + "dockerBackendDown": "DOWN", + "dockerBackendDraining": "DRAINING", + "dockerBackendNoMetrics": "Runtime metrics appear after the dynamic backend is active.", + "dockerHealthCheck": "Backend health check", + "dockerHealthSummary": "Active TCP/HTTP probes; passive health is configured separately", + "dockerPassiveHealth": "Passive backend health", + "dockerPassiveHealthSummary": "Off by default; opt in to health decisions from normal requests", + "dockerHealthMode": "Mode", + "dockerHealthHttp": "HTTP", + "dockerHealthTcp": "TCP", + "dockerHealthOff": "Off", + "dockerHealthPath": "HTTP path", + "dockerHealthHost": "Health Host header", + "dockerHealthHostAuto": "backend host / automatic", + "dockerHealthInterval": "Interval (ms)", + "dockerHealthTimeout": "Timeout (ms)", + "dockerHealthSlow": "Slow threshold (ms)", + "dockerHealthFall": "Failures to mark down", + "dockerHealthRise": "Successes to mark up", + "dockerHealthStatusMin": "Min HTTP status", + "dockerHealthStatusMax": "Max HTTP status", + "dockerHealthLatency": "Latency", + "dockerHealthResult": "Last probe", + "dockerHealthRequests": "Requests", + "dockerHealthErrors": "Errors", + "dockerHealthActive": "Active", + "loggingPerVhost": "Separate file per vhost", + "loggingDirectory": "Per-vhost directory", + "loggingPerVhostHelp": "When enabled, each vhost writes to its own JSONL file. Unmatched traffic uses _unmatched.jsonl.", + "listenerStartupLocked": "This startup/systemd address cannot be changed or removed here. Other addresses and listener options remain editable.", + "administrationStartupLocked": "This address is fixed by the process startup/systemd configuration and cannot be changed here.", + "notfoundStatusInvalid": "Select a supported HTTP error status.", + "tlsEffectiveCiphers": "Currently effective cipher suites", + "certificateManaged": "ACME managed", + "certManagedReadOnly": "ACME-managed certificates are renewed automatically and cannot be edited as custom file certificates.", + "vhWebsocketTransport": "WebSocket transport", + "vhWebsocketHttp1": "HTTP/1.1 Upgrade (recommended)", + "vhWebsocketRfc9220": "RFC 9220 Extended CONNECT over HTTP/3", + "vhWebsocketTransportHelp": "Enable WebSocket with the separate switch, then choose the transport. H2/H3 modes use Extended CONNECT while HTTP/1.1 Upgrade remains the fallback.", + "dockerLiveView": "Live view", + "dockerPollInterval": "Agent poll interval (s)", + "dockerPollIntervalHelp": "How often RustProxy contacts this agent for Docker inventory. Live view only reads RustProxy memory.", + "dockerAgentPollEvery": "Agent poll interval", + "dockerSearch": "Search Docker vhosts", + "dockerSearchPlaceholder": "Search service, host, alias or agent...", + "dockerShowHidden": "Show Hidden", + "dockerHideHidden": "Hide Hidden", + "dockerHideVhost": "Hide from view", + "dockerUnhideVhost": "Show in view", + "dockerCertificate": "Frontend certificate", + "dockerCertificateHelp": "Certificate assigned to every generated vhost for this agent. Default uses normal TLS/SNI certificate selection.", + "dockerDefaultAcl": "Default ACL profile", + "dockerDefaultAclNone": "No default ACL profile", + "dockerDefaultAclHelp": "Inherited by generated Docker vhosts that do not select their own per-app ACL profile.", + "dockerApplyService": "Apply changes", + "dockerAccessControl": "Access control", + "dockerAclSummary": "Optional ACL profile and per-app IP rules", + "dockerAclProfile": "ACL profile", + "dockerAclProfileNone": "No ACL profile", + "dockerAclProfileInherit": "Agent default", + "dockerAclInline": "Use additional per-app ACL", + "dockerAclDefault": "Default ACL action", + "dockerAclRules": "IP/CIDR rules", + "dockerAclHelp": "One rule per line, for example allow 10.0.0.0/8. Profile and per-app ACL are both enforced.", + "dockerProxyTimeouts": "Proxy timeouts", + "dockerProxyTimeoutsHelp": "Explicit per-app limits: connect 10 s, response headers 120 s, upstream read 60 s and request send 60 s by default.", + "dockerHostnameRequired": "Hostname is required before publishing.", + "dockerForceSsl": "Redirect HTTP to HTTPS", + "dockerForceSslUnavailable": "Requires an active plain HTTP listener and one unambiguous TLS listener on this agent.", + "dockerWebsockets": "WebSockets", + "dockerCompression": "Compression", + "dockerCompressionHelp": "Enable or disable compression for this Docker vhost. The enabled codecs and their priority come from global Response settings.", + "dockerCache": "Cache", + "vhProxyTimeouts": "Proxy timeouts", + "vhProxyConnectTimeout": "Proxy connect timeout (s)", + "vhProxyHeaderTimeout": "Proxy response header timeout (s)", + "vhProxyReadTimeout": "Proxy read timeout (s)", + "vhProxySendTimeout": "Proxy send timeout (s)", + "vhProxyTimeoutsHelp": "Defaults: connect 10 s, response headers 120 s, read 60 s and send 60 s. Read/send are idle-gap timeouts for streaming bodies.", + "notfoundModeBuiltin": "Application-style status page", + "pageBuiltin": "Built-in status page", + "vhOpenBrowser": "Open", + "securityPasswordHint": "Requires the current password and signs out all sessions.", + "securityAccessControl": "ACCESS CONTROL", + "securityPoliciesHint": "Keep the policy list visible; open the editor only when adding or changing a rule set.", + "securityPolicyEditorHint": "Hostname scope, default action and CIDR rules.", + "securityAuditOpen": "Administration audit", + "securityAuditHint": "Recent administration changes are loaded only when this window is opened.", + "dockerConfigureService": "Configure vhost", + "dockerAliasesShort": "aliases", + "dockerAgentDetails": "Agent details", + "dockerServices": "Services", + "dockerOpenApp": "Open app", + "networkInterfacesHistory": "Traffic by interface", + "networkInterfaceRate": "RX / TX", + "networkNoInterfaces": "No non-loopback network interfaces are available.", + "networkInterfacesCount": "{count} interfaces", + "logExpandBuffer": "Increase buffer", + "logExpandedBuffer": "Buffer 4096", + "logBufferExpanded": "Live buffer increased to {count} retained entries.", + "vhIdRenameHelp": "Changing the vhost ID starts a new metrics/log/cache identity and resets its live health/traffic history. Hostnames and configured backend IDs are unchanged.", + "vhIdRenameConfirm": "Change vhost ID from {old} to {new}? Live health/traffic history and metric/cache identity for this vhost will restart.", + "cacheDomainFilter": "Show", + "cacheDomainFilterAll": "All domains", + "cacheDomainFilterEnabled": "Cache enabled", + "cacheDomainFilterDisabled": "Cache disabled", + "cacheNoDomainsFiltered": "No domains match this filter.", + "cacheColdCleanup": "Clean cold cache", + "cacheColdCleanupHelp": "Runs asynchronously and uses the same eviction mechanism as automatic cold-cache GC.", + "cacheColdUnusedSeconds": "Unused for at least (seconds, optional)", + "cacheColdOldestCount": "Remove up to oldest objects", + "cacheColdHitsZero": "Only objects with hits = 0", + "cacheColdCleanupStart": "Start cleanup", + "cacheColdCleanupRunning": "Cold-cache cleanup is running…", + "cacheColdCleanupDone": "Removed {count} objects and freed {bytes}.", + "cacheColdCleanupFailed": "Cold-cache cleanup failed.", + "cacheColdCleanupInvalidJob": "Cold-cache cleanup did not return a job identifier.", + "cacheColdGcInterval": "Cold-cache GC interval (seconds)", + "cacheColdGcMaxDelete": "Maximum deletes per GC pass", + "transformSettings": "Static response transformation", + "transformEnable": "Enable transformation engine globally", + "transformHelp": "Transformation happens after upstream decoding and before cache storage and client compression. Each vhost must opt in separately.", + "transformJavascript": "JavaScript minification", + "transformCss": "CSS minification", + "transformHtml": "HTML minification", + "transformSvg": "SVG optimization", + "transformJson": "JSON compaction", + "transformExtensions": "Allowed file extensions, one per line", + "transformMaxBody": "Maximum transformable response (KiB)", + "transformResourcePresetTitle": "Resource preset", + "transformResourcePresetHelp": "Presets tune the temporary memory ceiling, maximum transformable response size and global concurrent transformation limit. Content types and extension allow-lists are left unchanged.", + "transformPresetConservative": "RAM saver", + "transformPresetRecommended": "Recommended", + "transformPresetPerformance": "Performance", + "transformPresetUber": "Uber performance", + "transformMemoryBudget": "Temporary transform memory budget (MiB)", + "transformMaxInflight": "Maximum concurrent transformations", + "transformMaxInflightShort": "concurrent", + "transformResourceCurrent": "Current limits", + "transformBudgetTooSmallWarning": "The memory budget is smaller than the worst-case temporary buffers for one maximum-size compressed response. Such responses can be skipped with memory_pressure.", + "transformBudgetHighWarning": "The transform memory ceiling is more than 50% of effective host/cgroup RAM:", + "transformSafety": "Cache-Control: no-transform is always respected. Transformed representations receive a new ETag and are cached before gzip, Brotli or zstd encoding.", + "saveTransform": "Apply transformation policy", + "vhTransform": "Transform", + "vhTransformEnable": "Enable transformations for this vhost", + "vhTransformExtensionsCustom": "Override global transform extensions", + "vhTransformHelp": "The global engine must also be enabled. Format selectors inherit the global policy unless explicitly enabled or disabled here.", + "dockerTransform": "Transform static responses", + "vhPassive5xxWarning": "Counting HTTP 5xx can quarantine the whole backend after repeated application errors from one endpoint.", + "vhPassiveSlowWarning": "Counting slow responses can quarantine the backend after repeated responses that are correct but exceed the slow-response threshold.", + "transform": "Transform", + "transformTitle": "Static response transformation.", + "transformDescription": "Runtime transformation effectiveness, savings, skips and cache reuse.", + "transformApplied": "Transformed responses", + "transformExamined": "Examined", + "transformCacheHits": "Transformed cache HITs", + "transformCacheHitsHelp": "Served without running the transformer again", + "transformSaved": "Bytes saved before compression", + "transformReduction": "Average reduction", + "transformReductionHelp": "For responses that changed representation", + "transformSkipped": "Skipped / fail-open", + "transformErrors": "Body / transport errors", + "transformAvgTime": "Avg time", + "transformByType": "Transformations by type", + "transformType": "Type", + "transformSkipReasons": "Skip / fail-open reasons", + "transformNoSkips": "No skipped transformations in this scope.", + "transformNoSkipsShort": "No skips", + "transformVhosts": "Vhost effectiveness", + "transformScopeHint": "Counters are in-memory runtime diagnostics. A transformed cache HIT does not execute the transformer again.", + "transformNoData": "No transform activity yet.", + "transformBadge": "transformed", + "cacheLiveChartTitle": "Live cache activity", + "cacheLiveChartHelp": "HIT / MISS / BYPASS per second for the selected scope", + "liveChartSamples": "Samples", + "transformLiveChartTitle": "Live transformation activity", + "transformLiveChartHelp": "Applied / cache HIT / skipped / errors per second for the selected scope", + "transformChartApplied": "Applied", + "transformChartCacheHits": "Cache HIT", + "transformChartSkipped": "Skipped", + "cacheInvalidationTitle": "Cache purge / ban", + "cacheInvalidationOperation": "Operation", + "cacheInvalidationSelector": "Selector", + "cacheInvalidationValue": "Value", + "cacheInvalidationHost": "Host (optional)", + "cacheBanTtl": "BAN TTL (seconds)", + "cacheInvalidationHelp": "The selected vhost limits the operation. Exact and prefix values include the query string. Regex and root-prefix / operations require an additional confirmation.", + "cacheRunInvalidation": "Run operation", + "cacheActiveBans": "Active BAN rules", + "cacheBanExpires": "Expires", + "cacheNoBans": "No active BAN rules.", + "cacheTarget": "Target", + "cacheKeyTags": "Cache key / tags", + "cachePurgeUrl": "Purge URL", + "cacheRemoveBan": "Remove BAN", + "cacheBanCreated": "BAN created", + "cacheConfirmPurgeUrl": "Purge this cached URL from RAM and cold storage?", + "cacheConfirmRemoveBan": "Remove this BAN rule?", + "cacheWideInvalidationConfirm": "This broad invalidation may affect many cached objects. Continue?", + "cacheCollapseWaiters": "waiting", + "cacheCollapsed": "collapsed", + "actions": "Actions", + "cacheCollapseTitle": "Request collapsing", + "cacheCollapseEnable": "Collapse concurrent fills for one cache key", + "cacheCollapseTimeout": "Follower wait timeout (ms)", + "cacheCollapseFallback": "Timeout fallback", + "cacheCollapseFallbackOrigin": "Origin", + "cacheCollapseFallbackStale": "Stale if available", + "cacheStaleErrorTitle": "Stale-if-error policy", + "cacheSieTimeout": "Timeout", + "cacheSieConnection": "Connection failure", + "cacheSieDns": "DNS failure", + "cacheSieTls": "TLS failure", + "cacheSieStatuses": "HTTP 5xx statuses eligible for stale fallback", + "cacheProactiveTitle": "Proactive refresh", + "cacheProactiveEnable": "Refresh sampled cached objects before expiry", + "cacheProactiveBefore": "Refresh before expiry (seconds)", + "cacheProactivePercent": "Sampled objects (%)", + "cacheProactiveInflight": "Maximum concurrent refreshes", + "cacheProactiveHelp": "Uses validator revalidation, deterministic sampling and a global concurrency cap to avoid refresh storms.", + "cacheInvalidationOpen": "Purge / Ban", + "cacheBanListOpen": "Active BANs", + "cacheBanRemoved": "BAN rule removed", + "tunnelsOpen": "Active tunnels", + "tunnelsTitle": "Active WebSocket / tunnels", + "tunnelsReload": "Reload", + "tunnelsEmpty": "No active tunnels.", + "tunnelId": "ID", + "tunnelClient": "Client", + "tunnelHost": "Host", + "tunnelUpstream": "Upstream", + "tunnelProtocol": "Protocol", + "tunnelDuration": "Duration", + "tunnelRx": "RX", + "tunnelTx": "TX", + "tunnelKill": "Kill", + "tunnelKilled": "Tunnel closed by administrator", + "tunnelKillConfirm": "Close this tunnel?", + "tunnelKillReason": "Close reason", + "tunnelKillReasonPlaceholder": "Administrative close", + "tunnelKillReasonDefault": "admin panel", + "listenerH2c": "h2c cleartext HTTP/2 (prior knowledge + HTTP/1.1 Upgrade)", + "vhWebsocketRfc8441": "RFC 8441 Extended CONNECT over HTTP/2", + "wsH1": "H1 Upgrade", + "wsH2Rfc8441": "H2 RFC 8441", + "wsH3Rfc9220": "H3 RFC 9220", + "wsEstablished": "Established", + "httpProtocolMix": "HTTP protocol mix", + "wsProtocolMix": "WebSocket protocol mix", + "vhWebsocketMode": "WebSocket transport", + "vhWebsocketDisabled": "Disabled", + "vhWebsocketH1": "HTTP/1.1 Upgrade", + "vhWebsocketH2Fallback": "HTTP/2 RFC 8441 + H1 fallback", + "vhWebsocketH3Fallback": "HTTP/3 RFC 9220 + H1 fallback", + "vhWebsocketH2H3Fallback": "HTTP/2 + HTTP/3 + H1 fallback", + "vhMaxConcurrentRequests": "Max concurrent requests", + "vhBackendQueueTimeout": "Capacity queue timeout (ms)", + "vhBackendCapacityHelp": "0 concurrent requests means unlimited. A queue timeout of 0 rejects immediately with HTTP 503 when the backend is at capacity.", + "vhLastFailure": "Last failure", + "vhLastRecovery": "Last recovery", + "certificateIssuerName": "Certificate issuer", + "certificateValidFrom": "Valid from", + "certificateDaysRemaining": "Days remaining", + "certificateExpired": "Certificate expired", + "certificateExpiresSoon": "Certificate expires soon", + "certificateRenewalStatus": "Renewal status", + "certificateRenewalError": "Last renewal error", + "vhActiveHealth": "Active health", + "vhPassiveFailures": "Passive failures", + "certificateAdminRestartRequired": "Administration TLS uses a startup-fixed listener; restart RustProxy to refresh that certificate.", + "cacheTabGeneral": "General", + "cacheTabResilience": "Resilience", + "cacheTabCold": "Cold cache", + "cacheTabDiagnostics": "Static & diagnostics", + "certificateApiTitle": "Certificate API", + "certificateApiHelp": "Create separate Key ID + Bearer Token credentials for certificate automation. New tokens are reload-only by default and are restricted to the listed certificate IDs. Enable deploy only for automation that must upload certificate material.", + "certificateApiKeyId": "Key ID", + "certificateApiToken": "Bearer token (optional custom value)", + "certificateApiTokenHelp": "Leave the field empty to generate a strong token. Each cleartext token is shown only once; RustProxy stores only its hash.", + "certificateApiRotate": "Generate / rotate token", + "certificateApiRevoke": "Revoke token", + "certificateApiOneTimeToken": "Copy this token now", + "certificateApiCurlHelp": "External automation sends the Key ID header and Bearer token over HTTPS (or loopback HTTP). Reload tokens must use the targeted /api/certificates/{id}/reload endpoint; a token cannot call the global reload endpoint.", + "certificateApiUnknown": "Loading certificate API status…", + "certificateApiConfigured": "Certificate API enabled", + "certificateApiDisabled": "Certificate API disabled", + "certificateApiLastUsed": "last used", + "certificateApiRotated": "Certificate API token created / rotated. Copy the token now.", + "certificateApiRevoked": "Certificate API token revoked.", + "certificateApiRevokeConfirm": "Revoke the external certificate API token? Certbot/API deployments using it will stop immediately.", + "certificate_api.invalid_token": "Invalid certificate API Key ID or Bearer Token.", + "certificate_api.https_required": "External certificate API tokens require HTTPS unless the request comes from loopback.", + "certificate_api.invalid_token_format": "Certificate API tokens must be 32–128 URL-safe characters.", + "certificate_api.invalid_key_id": "Invalid certificate API Key ID.", + "vhWebsocketLegacySupport": "Enable WebSocket legacy support", + "vhWebsocketLegacySupportHelp": "For older appliances and embedded servers. WebSocket handshakes use HTTP/1.1 upstream, Host and Origin are aligned to the configured backend authority, and legacy 101 response framing is tolerated. Ordinary HTTP traffic is unchanged.", + "vhWebsocketLegacyBadge": "WebSocket legacy", + "runtimeInformation": "Runtime information", + "runtimeInformationHelp": "Reported by the running RustProxy process and available only from the authenticated administrator status API.", + "runtimePlatform": "Platform", + "statisticsSource": "Statistics source", + "dataDirectory": "Data directory", + "databaseLocation": "Database", + "connectionActivity": "CONNECTION ACTIVITY", + "reloadCertificate": "Reload certificate", + "certificateApiComment": "Comment", + "certificateApiCertificateIds": "Allowed certificate IDs", + "certificateApiCertificateIdsHelp": "One or more certificate IDs separated by commas or new lines. The token is rejected for every other certificate ID.", + "certificateApiCertificateIdsRequired": "Enter at least one certificate ID for this token.", + "certificateApiAllowDeploy": "Also allow deploy", + "certificateApiAllowDeployHelp": "Leave disabled for Certbot/acme.sh renewal hooks. Reload-only is the recommended least-privilege mode.", + "certificateApiScopeReloadOnly": "reload only", + "certificateApiScopeDeployReload": "deploy + reload", + "certificateApiAllowedIds": "allowed IDs", + "certificateApiConfiguredCount": "Certificate API tokens: {count} / {max}", + "certificateApiNeverUsed": "never used", + "certificateApiRevokeAll": "Revoke all tokens", + "certificateApiRevokeOneConfirm": "Revoke Certificate API token {key}? Any automation using it will stop immediately.", + "certificateApiRevokeAllConfirm": "Revoke every Certificate API token? All external certificate automation using these tokens will stop immediately.", + "certificate_api.invalid_comment": "Certificate API comments must be at most 256 characters and contain no control characters.", + "certificate_api.limit_reached": "The Certificate API token limit has been reached.", + "certificate_api.not_found": "Certificate API token not found.", + "certificate_api.invalid_certificate_ids": "Certificate API tokens require 1-32 valid certificate IDs.", + "certificate_api.target_required": "Certificate API tokens cannot reload every certificate at once; use the targeted certificate reload endpoint.", + "certificate_api.certificate_forbidden": "This Certificate API token is not allowed to operate on that certificate ID.", + "certificate_api.deploy_forbidden": "This Certificate API token is reload-only. Rotate it with deploy permission only if upload/deploy automation is required.", + "networkInterfacesVisibleCount": "{visible} / {total} interfaces", + "hideMetric": "Hide", + "showMetric": "Show", + "hiddenInterfaces": "Hidden interfaces", + "networkNoVisibleInterfaces": "All detected interfaces are hidden in this browser.", + "cacheMemorySizingTitle": "Memory sizing", + "cacheMemorySizingHelp": "Presets use effective memory available to RustProxy (host RAM capped by cgroup when present). They tune HOT, transient RAM, object size, entry count and the demotion queue; the COLD disk budget is left unchanged.", + "cachePresetConservative": "Conservative", + "cachePresetRecommended": "Recommended", + "cachePresetPerformance": "Performance", + "cachePresetSelected": "Selected", + "cachePresetCustom": "Custom", + "cacheColdPresetTitle": "COLD policy preset", + "cacheColdPresetHelp": "The recommended policy enables idle/LRU/shutdown demotion, sizes the COLD queue from transient RAM and uses conservative GC timings. Disk path, disk budget and object limit are left unchanged.", + "cacheTransientLimit": "Transient RAM budget (MiB)", + "cacheEvictionLimit": "LRU removals per admission", + "cacheColdQueueLimit": "COLD queue RAM budget (MiB)", + "cacheMemoryNotDetected": "Memory not detected", + "cacheMemoryPresetUnavailable": "Automatic presets require host or cgroup memory telemetry.", + "cacheMemoryCgroup": "Effective cgroup RAM", + "cacheMemoryDetected": "Detected RAM", + "cacheQueueOverTransientWarning": "COLD queue budget cannot exceed the transient RAM budget.", + "cacheMemoryHighWarning": "Warning: HOT + transient cache budgets are a large share of effective RAM:", + "cachePresetTransientShort": "transient", + "cachePresetObjectShort": "max object", + "cachePresetQueueShort": "COLD queue", + "cacheLogicalShort": "logical", + "cacheLimitShort": "limit", + "cacheMemoryPressureShort": "memory pressure", + "cacheNukeLimitShort": "LRU limit", + "cacheMemoryPresetTooSmall": "Automatic cache presets are disabled below 256 MiB of effective RAM; configure the cache manually if this is intentional.", + "cacheMemoryRecommendedFor": "Recommended for detected memory:", + "cacheMemoryHeadroomWarning": "Warning: the additional cache budget is large compared with currently available host/cgroup memory:", + "cacheObjectOverTransientWarning": "Maximum object exceeds the transient RAM budget; objects near that size will pass through instead of being cached.", + "vhCacheGlobalDisabled": "Global cache is disabled. This vhost cannot enable cache until the global cache is enabled.", + "responseLatencyInfo": "Response time information", + "responseLatencyHint": "The value is the cumulative mean duration of completed HTTP exchanges since process start. The chart shows successive samples of that mean; it is not a percentile or the latency of one request.", + "web-server": "Web Server", + "web-serverTitle": "Serve local files.", + "web-serverDescription": "Create reusable Web Services and attach them to vhosts or Custom locations.", + "webServer": "Web Server", + "webServerIntro": "Create reusable local Web Services here, then attach them to a vhost or Custom location.", + "webServiceList": "Web Services", + "webServiceSearch": "Search Web Services", + "webServiceSearchPlaceholder": "Search name, ID or document root", + "webServiceNew": "New Web Service", + "webServiceEditor": "Web Service configuration", + "webServiceTabGeneral": "General", + "webServiceTabRouting": "Routing", + "webServiceTabMime": "MIME", + "webServiceTabCacheHeaders": "Cache & headers", + "webServiceTabDelivery": "Delivery", + "webServiceTabPhp": "PHP-FPM", + "webServiceTabSecurity": "Security", + "webServiceId": "Service ID", + "webServiceName": "Name", + "webServiceRoot": "Document root", + "webServiceIndex": "Index files", + "webServiceDirectoryListing": "Directory listing", + "webServiceMimeOverrides": "MIME overrides", + "webServiceMimeBuiltin": "Built-in mappings", + "webServiceMimeHelp": "Optional extension-to-Content-Type overrides, one per line. Use: ext = media/type.", + "webServiceMimeInvalid": "MIME overrides must use one unique 'ext = media/type' entry per line.", + "webServiceDefaultMime": "Default MIME type", + "webServiceCacheControl": "Cache-Control", + "webServiceCacheRules": "Cache-Control rules", + "webServiceCacheRulesHelp": "Specific rules are evaluated in order before the default policy. Match ext:css or mime:image/.", + "webServiceCacheRulesInvalid": "Cache-Control rules must use matcher = value, for example ext:css = public, max-age=3600.", + "webServiceResponseHeaders": "Response headers", + "webServiceResponseHeadersHelp": "Fixed headers for this Web Service. Vhost/location response headers run later and can override them.", + "webServiceResponseHeadersInvalid": "Response headers must use one unique Header-Name = value entry per line.", + "webServiceCacheHelp": "Eligible Web Server responses use RustProxy object cache when vhost caching is enabled. Private and no-store responses are not cached.", + "webServiceTryFiles": "try_files", + "webServiceTryFilesHelp": "One candidate per line. Supports $uri, $query_string and a final =404/other error status.", + "webServiceRewrites": "Rewrite rules", + "webServiceRewritesHelp": "One regex rule per line. Use pattern => /target for an internal rewrite or pattern => 301:/target for a redirect.", + "webServiceRewritesInvalid": "Rewrite rules must use pattern => /target or pattern => 301:/target (301/302/307/308).", + "webServiceCompression": "Dynamic compression", + "webServiceCompressionHelp": "Inherits the vhost/global policy by default. Individual codecs can be filtered here; globally disabled codecs remain unavailable and minimum size stays global.", + "webServicePrecompressed": "Serve precompressed sidecars", + "webServicePrecompressedBr": "Brotli (.br)", + "webServicePrecompressedGzip": "Gzip (.gz)", + "webServiceDownloadExtensions": "Download extensions", + "webServiceDownloadExtensionsHelp": "Files with these extensions receive Content-Disposition: attachment.", + "webServiceRangeHelp": "Static and precompressed representations keep validators, single and multipart byte-range requests, 206/416 and streaming delivery.", + "webServicePhpEnabled": "Enable PHP-FPM / FastCGI", + "webServicePhpEndpoint": "PHP-FPM endpoint", + "webServicePhpEndpointHelp": "Unix: unix:/run/php/php-fpm.sock. TCP: tcp:127.0.0.1:9000 or tcp:[::1]:9000. TCP is intentionally limited to loopback addresses.", + "webServicePhpExtensions": "PHP extensions", + "webServicePhpPassAuthorization": "Forward Authorization header", + "webServicePhpConnectTimeout": "Connect timeout (ms)", + "webServicePhpRequestTimeout": "Total FastCGI request timeout (ms)", + "webServicePhpConcurrency": "Max concurrent PHP requests", + "webServicePhpBodyLimit": "PHP request body limit (bytes, 0 = unlimited)", + "webServicePhpHeaderLimit": "Max FastCGI response header bytes", + "webServicePhpStderrLimit": "Max PHP-FPM stderr log bytes", + "webServicePhpCustomParams": "Custom FastCGI parameters", + "webServicePhpCustomParamsHelp": "One NAME = value per line, e.g. APP_ENV = production. Names: A-Z, digits, underscores; no HTTP_*, PHP_VALUE, PHP_ADMIN_VALUE or core CGI parameters.", + "webServicePhpCustomParamsInvalid": "Custom FastCGI parameters must use one unique NAME = value entry per line.", + "webServicePhpCustomDataTooLarge": "Custom FastCGI parameters and PHP INI overrides may use at most 128 KiB combined.", + "webServicePhpValues": "PHP INI overrides (PHP_VALUE)", + "webServicePhpValuesHelp": "One directive = value per line, e.g. memory_limit = 256M, upload_max_filesize = 64M, post_max_size = 64M; PHP_FPM decides which can be changed.", + "webServicePhpValuesInvalid": "PHP INI overrides must use one unique directive = value entry per line.", + "webServicePhpDocsButton": "FastCGI syntax and safety", + "webServicePhpDocsTitle": "PHP-FPM / FastCGI configuration", + "webServicePhpDocsIntro": "RustProxy connects directly to an existing PHP-FPM pool. Prefer a Unix socket on the same host. TCP is accepted only on loopback and is not a public/network FastCGI transport.", + "webServicePhpDocsEndpointTitle": "Endpoint syntax", + "webServicePhpDocsEndpoint": "Use unix: plus full socket path, or tcp: plus a literal loopback IP and port. TCP hostnames/public addresses are rejected. Use distinct FPM sockets for applications with different PHP INI restrictions.", + "webServicePhpDocsCustomTitle": "Custom FastCGI parameters", + "webServicePhpDocsCustom": "One NAME = value per line (NAME starts with a letter and contains A-Z, digits or underscore). Static values only. SCRIPT_FILENAME, HTTP_* and PHP_VALUE/PHP_ADMIN_VALUE are reserved.", + "webServicePhpDocsValuesTitle": "PHP INI overrides", + "webServicePhpDocsValues": "These lines go into PHP_VALUE. Use the separate PHP_ADMIN_VALUE field for trusted administrative settings; neither one isolates workers sharing the same PHP-FPM pool.", + "webServicePhpDocsLimitsTitle": "Limits and headers", + "webServicePhpDocsLimits": "Request timeout covers the FastCGI exchange. The body limit is enforced while streaming and returns 413 when exceeded; 0 disables the RustProxy-side body limit. Response-header and stderr limits bound memory/diagnostic data. Up to 64 custom FastCGI parameters and 64 PHP INI overrides are accepted, with at most 128 KiB of combined custom data. Authorization forwarding can be disabled for applications that do not need HTTP_AUTHORIZATION.", + "webServicePhpDocsSecurity": "Do not put secrets in custom parameters or PHP_VALUE unless you accept storing them in normal Web Service configuration. Use a dedicated non-root PHP-FPM pool identity and keep PHP-FPM off public networks.", + "webServicePhpHelp": "RustProxy speaks FastCGI directly to a long-running PHP-FPM pool. PHP is not spawned per request and PHP source is never served when PHP handling is selected.", + "webServicePhpSymlinkConflict": "PHP-FPM currently requires the symlink policy to be Deny.", + "webServiceSymlinkPolicy": "Symlink policy", + "webServiceSymlinkDeny": "Deny", + "webServiceSymlinkInsideRoot": "Allow only inside document root (Linux)", + "webServiceSafety": "Document root is a strict filesystem boundary. The default denies symlinks. Linux can optionally follow only targets that resolve inside the root; PHP-FPM currently requires the deny policy.", + "webServiceSelect": "Select Web Service", + "webService": "Web Service", + "webServiceUsedBy": "Used by", + "webServiceUnused": "Not attached", + "webServiceEmpty": "No Web Services yet.", + "webServiceSearchEmpty": "No Web Services match this search.", + "webServiceInUse": "Web Service is still used by", + "webServiceDelete": "Delete this Web Service?", + "webServiceIndexRequired": "Index files are optional.", + "webServiceDuplicate": "A Web Service with this ID already exists.", + "webServiceRequired": "Select a Web Service.", + "sourceType": "Source type", + "sourceReverseProxy": "Reverse Proxy", + "sourceWebServer": "Web Server", + "sourceHelp": "The vhost stores only the source choice. Web Server settings remain in the selected reusable Web Service.", + "locationWebServerAction": "Web Server", + "locationWebServerShort": "Web Server", + "docsReader": "Reader", + "docsApi": "API", + "docsReaderSearch": "Search documentation", + "docsReaderSearchPlaceholder": "Search title or path", + "docsReaderEmpty": "No documentation matches this search.", + "docsCategoryProject": "Project", + "docsCategoryGuides": "Functional guides", + "docsCategoryTechnical": "Technical", + "docsCategoryAudits": "Audits and history", + "docsCategoryHistorical": "Historical", + "webServiceIndexHelp": "Optional. Leave empty to skip index lookup; with directory listing enabled, the file list is shown directly.", + "webServiceNoIndex": "No index files", + "vhBackendRequired": "Reverse Proxy requires at least one backend. Add a backend or change the source to Web Server.", + "vhListenerRequired": "Select at least one listener for this vhost.", + "traffic": "Traffic", + "trafficTitle": "Traffic, per vhost.", + "trafficDescription": "Bounded request analytics: countries, User-Agents, client IPs, paths, routes and protocol outcomes.", + "trafficSinceStart": "Since process start", + "trafficResponseBytes": "Response bytes", + "trafficRequestBytes": "Request bytes", + "trafficMeanLatency": "Mean latency", + "trafficDroppedSamples": "Dropped analytics samples", + "trafficBoundedLabel": "BOUNDED / ASYNC", + "trafficDimensionsTitle": "Traffic dimensions", + "trafficApproxHelp": "Top lists are approximate and bounded so high-cardinality traffic cannot grow proxy memory without limit.", + "trafficTopCountries": "Top countries", + "trafficTopUserAgents": "Top User-Agents", + "trafficTopClientIps": "Top client IPs", + "trafficClientIp": "Client IP", + "trafficTopPaths": "Top paths", + "trafficTopRoutes": "Top routes", + "trafficMethods": "Methods", + "trafficStatusClasses": "Status classes", + "trafficCacheOutcomes": "Cache outcomes", + "trafficValue": "Value", + "trafficOther": "Other", + "trafficNoCountryData": "Country collection is disabled or no GeoIP data is available.", + "trafficNoUserAgentData": "No User-Agent data collected.", + "trafficNoData": "No traffic data yet.", + "trafficUserAgentEnabled": "User-Agent collection enabled", + "trafficUserAgentDisabled": "User-Agent collection disabled", + "trafficCountryEnabled": "Country collection enabled", + "trafficCountryDisabled": "Country collection disabled", + "trafficCountryNoDb": "Country collection enabled, GeoIP database unavailable", + "trafficUpdated": "Updated", + "loggingCaptureTitle": "Captured request dimensions", + "loggingCaptureUserAgent": "Capture User-Agent", + "loggingCaptureCountry": "Capture country from GeoIP", + "loggingCaptureCountryHelp": "Country lookup is optional and disabled by default. Enable it only when country logs/statistics are needed; country ACL/rate-limit rules still resolve GeoIP when required.", + "cacheAutoTrimEnabled": "Automatically release idle HOT cache memory", + "cacheAutoTrimAfter": "Idle age before HOT trim (seconds)", + "cacheAutoTrimTarget": "HOT trim target (%)", + "cacheAutoTrimHelp": "When HOT memory is above the target, only entries idle for the configured age are removed in bounded passes. Active objects are not purged.", + "webServiceTabHtaccess": ".htaccess", + "webServiceHtaccessEnabled": "Enable .htaccess compatibility", + "webServiceHtaccessFilename": "Configuration file", + "webServiceHtaccessRecursive": "Scan subdirectories", + "webServiceHtaccessStrict": "Strict unsupported-directive mode", + "webServiceHtaccessDocs": "Compatibility documentation", + "webServiceHtaccessHelp": "RustProxy compiles a bounded Apache-compatible subset when the Web Service is applied. Files are not parsed on every request; apply the Web Service again after editing .htaccess on disk.", + "webServiceHtaccessDocsTitle": ".htaccess compatibility", + "webServiceHtaccessDocsIntro": "This is a migration-oriented compatibility layer, not a complete Apache runtime. Supported directives are compiled into RustProxy Web Server rules.", + "webServiceHtaccessDocsSupportedTitle": "Supported subset", + "webServiceHtaccessDocsRewriteTitle": "Rewrite compatibility", + "webServiceHtaccessDocsRewrite": "Rewrite conditions support REQUEST_FILENAME file tests (-f, -d, -s), REQUEST_URI, REQUEST_METHOD, QUERY_STRING, HTTP_HOST, HTTPS, DOCUMENT_ROOT, THE_REQUEST and HTTP headers. Flags: L, END, R, R=301/302/307/308, QSA, NC, F, OR. PT/NE are accepted as no-ops. E= is accepted as a compatibility no-op because request HTTP headers already reach PHP-FPM.", + "webServiceHtaccessDocsPhpTitle": "PHP-FPM", + "webServiceHtaccessDocsPhp": ".htaccess may add PHP extensions with AddHandler, but the PHP-FPM endpoint, timeouts and concurrency remain controlled by the Web Service PHP-FPM tab. Apache SetHandler proxy socket syntax is intentionally not accepted.", + "webServiceHtaccessDocsReloadTitle": "Loading and limits", + "webServiceHtaccessDocsReload": "Files are read and compiled on Validate, save and apply. RustProxy does not parse .htaccess per request. Re-apply after changing a file. The scan is bounded to 128 files, depth 16, 128 KiB per file and 1 MiB total.", + "webServiceHtaccessDocsUnsupportedTitle": "Not Apache-complete", + "webServiceHtaccessDocsUnsupported": "ErrorDocument, SetEnvIf, Redirect/RedirectMatch and FallbackResource are not implemented in this compatibility layer. General Files/FilesMatch, unsupported authentication/authorization directives, non-PHP handlers and SetHandler are rejected even when Strict mode is off because silently ignoring them could weaken access control or expose source. .htaccess/.htpasswd files are always blocked from HTTP delivery. Other unsupported non-security directives are logged and ignored unless Strict mode is enabled.", + "webServiceHtaccessDocsSecurity": "Vhost/location ACL, authentication, error pages and response rules still remain authoritative. .htaccess cannot escape the document root, select an arbitrary FastCGI socket or override protected framing headers.", + "webServicePresetTitle": "Configuration preset", + "webServicePreset": "Preset", + "webServicePresetCustom": "Custom", + "webServicePresetStatic": "Static site", + "webServicePresetSpa": "SPA", + "webServicePresetPhpGeneric": "PHP generic", + "webServicePresetDownloads": "Downloads / file repository", + "webServicePresetAction": "Preset action", + "webServicePresetApply": "Apply preset", + "webServicePresetHelp": "Presets fill normal Web Service settings and remain fully editable. Document root, service ID/name and PHP-FPM endpoint are not replaced.", + "webServicePresetChoose": "Choose a preset before applying it.", + "webServicePresetApplied": "Preset applied. Review the generated settings before saving.", + "webServicePresetHintStatic": "Static site: HTML indexes, precompressed sidecars and cache rules for CSS/JS/images/fonts.", + "webServicePresetHintSpa": "SPA: index.html fallback via try_files, precompressed assets and long-lived static asset caching.", + "webServicePresetHintPhpGeneric": "Generic PHP: PHP-FPM enabled, index.php first and only existing files/directories are served directly.", + "webServicePresetHintWordpress": "WordPress: PHP-FPM, index.php and front-controller routing. Keep document root on the WordPress public directory.", + "webServicePresetHintDrupal": "Drupal: PHP-FPM, index.php and front-controller routing with static asset caching.", + "webServicePresetHintLaravel": "Laravel: PHP-FPM and front-controller routing. Point document root at the application public/ directory.", + "webServicePresetHintSymfony": "Symfony: PHP-FPM and front-controller routing. Point document root at the application public/ directory.", + "webServicePresetHintJoomla": "Joomla: PHP-FPM, index.php and front-controller routing with static asset caching.", + "webServicePresetHintPrestashop": "PrestaShop: PHP-FPM, index.php and front-controller routing with static asset caching.", + "webServicePresetHintDownloads": "Downloads: directory listing enabled, Range-friendly static delivery and common archive/document extensions forced as attachments.", + "webServiceHardeningTitle": "Hardening", + "webServiceHardeningAttacks": "Block common attack probes", + "webServiceHardeningHeaders": "Strip unnecessary fingerprint headers", + "webServiceHardeningSecurityHelp": "Hardening switches are shown beside the preset selector on General. They remain independent settings after a preset is applied.", + "trafficTabOverview": "Overview", + "trafficTabClients": "Clients", + "trafficTabRequests": "Requests", + "trafficTabDelivery": "Delivery", + "trafficVolume": "Traffic volume", + "vhMethodMode": "HTTP method policy", + "vhMethodList": "HTTP methods (one per line)", + "vhMethodHelp": "Allow only listed methods or block listed methods for this vhost. The policy stacks with location rules.", + "locationAclProfile": "ACL profile", + "locationMethodMode": "HTTP method policy", + "locationMethodList": "HTTP methods (one per line)", + "locationMethodAllow": "Allow only", + "locationMethodBlock": "Block listed", + "locationMethodHelp": "The location policy stacks with the vhost method policy. Example: GET, HEAD and POST.", + "locationCountryMode": "Country access policy", + "locationCountryList": "Country codes (one per line)", + "locationCountryHelp": "Location country policy stacks with vhost/profile policy. GeoIP is resolved only when required.", + "configHistory": "Revision history", + "configHistoryRetention": "SQLITE / LAST 512 REVISIONS", + "configHistoryHelp": "Select a marked day to inspect retained configuration revisions and their changes. History retention starts with 0.25.3.", + "configHistoryChanges": "CHANGES", + "configHistorySelectDay": "Select a day.", + "configHistoryRevision": "Revision", + "configHistoryRevisionShort": "rev", + "configHistoryBefore": "Before", + "configHistoryAfter": "After", + "configHistoryBaseline": "This is the oldest retained baseline; no earlier revision is available for comparison.", + "configHistoryNoChanges": "No configuration differences were found.", + "configHistoryDiffLimit": "Diff truncated after 200 changed paths.", + "configHistoryMissing": "(not present)", + "configHistoryEntries": "revisions", + "configHistorySummary": "{count} retained revisions", + "configHistoryEmpty": "No retained revisions.", + "configHistoryLoading": "Loading revision history...", + "configHistoryFailed": "Could not load revision history.", + "configHistoryRestoreAction": "Restore revision", + "configHistoryRestoreConfirm": "Restore revision {revision}? It will be validated and saved as a new revision after current revision {current}. The existing history will be preserved.", + "configHistoryRestored": "Revision {revision} restored as new revision {new_revision}.", + "about": "About", + "aboutTitle": "About RustProxy", + "aboutAuthor": "Author", + "aboutLicense": "License", + "aboutLicenseValue": "Open Source", + "aboutOpenSourceText": "RustProxy is an open-source project. Its source code is publicly available for inspection, development and community contribution.", + "aboutRepository": "Official source repository", + "aboutPackagesRepository": "Official package repository", + "aboutPythonRuntime": "Certificate Python runtime", + "runtimePython": "Python", + "runtimePip": "pip", + "runtimeCertbot": "Certbot", + "runtimeAcmeMode": "ACME mode", + "runtimeAvailable": "Available", + "runtimeUnavailable": "Unavailable", + "vhPoolRecycleRequests": "Recycle upstream pool after requests (0 = off)", + "vhPoolRecycleSeconds": "Maximum upstream pool age (seconds, 0 = off)", + "vhHttp1MaxBuffer": "HTTP/1 upstream max read buffer (bytes)", + "vhPoolRecycleHelp": "Recycling replaces only the Hyper client generation for each backend; active exchanges continue normally. Defaults bound long-lived pool/TLS buffer retention without reloading the vhost or cache. Request recycling accepts 0 or 1000..10000000; age accepts 0 or 60..604800.", + "backendSearch": "Search backends", + "backendSearchPlaceholder": "Search vhost, backend or state", + "backendSortBy": "Sort by", + "backendSortAscending": "Ascending sort", + "backendSortDescending": "Descending sort", + "backendColumns": "Columns", + "backendSearchEmpty": "No backends match the current search.", + "vhQuickEnable": "Enable", + "vhQuickDisable": "Disable", + "vhEnableHint": "Controls whether this vhost accepts traffic.", + "backendColumnsRequired": "At least one backend table column must remain visible.", + "logs": "Logs", + "storedLogs": "Stored logs", + "fileLogsSearchHelp": "Search syntax", + "fileLogsSearchHelpTitle": "Kibana-style field search", + "fileLogsSearchHelpText": "Use field:value clauses. Clauses are joined with AND; prefix a clause with ! to exclude it. Quotes keep spaces together.", + "fileLogsParsedFields": "Parsed fields", + "fileLogsRawJson": "Raw JSON", + "filelogs": "Logs", + "filelogsTitle": "Stored log explorer", + "filelogsDescription": "Search configured on-disk RustProxy logs without coupling to the Live Logs memory buffer.", + "fileLogsTitle": "Stored log explorer", + "fileLogsMode": "LOCAL / READ-ONLY / BOUNDED", + "fileLogsHelp": "Searches bounded RustProxy access/error logs and parses structured fields without exposing filesystem paths.", + "fileLogsSettingsHelp": "Browse configured access and error files with server-side filters. File logging can be off while Live Logs continues to use its memory buffer.", + "openLogs": "Open Logs", + "fileLogsSource": "Source", + "fileLogsAllSources": "All configured files", + "fileLogsNoSources": "No configured log files are available.", + "fileLogsEvent": "Event", + "fileLogsPageSize": "Page size", + "fileLogsRecord": "Request / reason", + "fileLogsEmpty": "No records match the current filters.", + "fileLogsDetails": "Record details", + "fileLogsSelectRecord": "Select a record to inspect its JSON.", + "fileLogsSearchPlaceholder": "req: GET AND !country: Poland", + "fileLogsTruncated": "scan limit reached", + "fileLogsSummary": "{from}–{to} of {total} matches · scanned {bytes} / {lines} lines", + "fileLogsMatchedBy": "Matched by", + "fileLogsSni": "SNI", + "fileLogsAuthority": "Authority / Host", + "fileLogsColumnsRequired": "At least one Stored Logs column must remain visible.", + "fileLogsSourcesRead": "Configured files", + "search": "Search", + "all": "All", + "clientIp": "Client IP", + "vhost": "Vhost", + "previous": "Previous", + "next": "Next", + "refresh": "Refresh", + "applyFilters": "Apply filters", + "appearance": "Appearance", + "appearanceEyebrow": "INTERFACE", + "appearanceHelp": "These preferences are saved for your administrator account and follow you between sessions.", + "languageAutomatic": "Automatic (browser)", + "interfaceScale": "Interface scale", + "interfaceScaleHelp": "Scale the entire administration interface, including dialogs and overlays, without changing browser zoom.", + "timeFormat": "Time format", + "timeFormat24": "24-hour", + "timeFormat12": "12-hour", + "chartStyle": "Chart style", + "chartStyleDefault": "Default", + "chartStyleSoft": "Soft", + "chartStyleTechnical": "Technical", + "chartStyleHighContrast": "High contrast", + "chartStylePreview": "Preview", + "draftDockTitle": "Drafts", + "draftUntitled": "New", + "draftRestore": "Restore draft", + "draftDiscard": "Discard draft", + "draftDiscardConfirm": "Discard this unsaved draft?", + "draftMinimized": "Draft moved to the dock.", + "chartStyleNeon": "Neon", + "chartStyleMonochrome": "Monochrome", + "chartStyleWarm": "Warm", + "chartStyleCool": "Cool", + "settingsSearch": "Find settings", + "settingsSearchPlaceholder": "Search settings, sections and options...", + "settingsSearchResults": "Matching sections", + "appearanceReset": "Reset appearance", + "appearanceResetDone": "Appearance preferences reset", + "appearanceInterfaceSection": "INTERFACE", + "appearanceInterfaceTitle": "Interface", + "appearanceChartsSection": "CHARTS", + "appearanceDataSection": "TABLES / LOGS", + "appearanceDashboardSection": "DASHBOARD", + "resetSection": "Reset section", + "appearanceSectionResetDone": "Section preferences reset", + "chartStylePalettes": "Chart style palettes", + "tableDensity": "Tables and logs density", + "densityNormal": "Normal", + "densityUltra": "Ultra compact", + "tableDensityHelp": "Changes row height and log spacing independently from interface scale.", + "dashboardLayout": "Dashboard layout", + "dashboardDensity": "Dashboard density", + "dashboardDensityHelp": "Controls Overview telemetry card size without changing the rest of the interface.", + "dashboardWidgets": "Overview cards", + "dashboardWidgetsHelp": "Show, hide and reorder the telemetry cards at the top of Overview.", + "dashboardWidgetVisible": "Visible", + "moveUp": "Move up", + "moveDown": "Move down", + "draftDockCollapse": "Collapse drafts", + "draftDockExpand": "Expand drafts", + "draftAgeNow": "now", + "draftAgeMinutes": "{count} min", + "draftAgeHours": "{count} h", + "draftAgeDays": "{count} d", + "buttonStyle": "Button style", + "buttonStyleNormal": "Normal", + "buttonStyleCompact": "Compact", + "buttonStyleHelp": "Normal uses the refreshed controls; Compact restores the smaller source-style buttons across all actions and dialog tabs.", + "entityEnableHint": "Controls whether this item is active.", + "setupCommandExample": "Standard service installation example:", + "certificateRuntimeTitle": "ACME runtime", + "certificateRuntimePreparing": "Preparing", + "certificateRuntimeManaged": "Managed Python", + "certificateRuntimeSystem": "System fallback", + "certificateRuntimeUnavailable": "Unavailable", + "certificateRuntimePath": "Managed environment", + "acme.runtime_checking": "Checking the local Python environment for certificate management.", + "acme.runtime_initializing": "Creating a private Python environment in the RustProxy data directory and installing Certbot modules with pip.", + "acme.runtime_ready": "The private Python/pip environment for certificate management is ready.", + "acme.runtime_fallback": "The private environment could not be used. RustProxy is using the distribution Python and Certbot available in PATH.", + "acme.runtime_python_missing": "Python is not available on this system. Automatic ACME generation and renewal are unavailable; PEM import and existing certificate files still work.", + "acme.runtime_pip_missing": "Python is available, but RustProxy could not create a local pip environment and no distribution Certbot is available. Install Python with venv/pip or Certbot packages. PEM import and existing files still work.", + "acme.runtime_setup_failed": "RustProxy could not create the local Python/pip environment and no distribution Certbot is available. Check network/package access or install Certbot from the distribution. PEM import and existing files still work.", + "acme.runtime_venv_failed": "Python and pip are available, but RustProxy could not create its private environment in the data directory. Check venv/virtualenv support and data-directory permissions. No system Certbot is available.", + "acme.runtime_venv_pip_failed": "The private Python environment was created, but pip/ensurepip could not be started inside it. Check the Python venv/ensurepip installation. No system Certbot is available.", + "acme.runtime_certbot_install_failed": "The private Python environment works, but pip could not install the Certbot packages. Check PyPI/proxy/DNS/TLS access. No system Certbot is available.", + "acme.runtime_certbot_validation_failed": "ACME packages were installed, but Certbot or one of the required plugins failed validation. Remove the acme-python directory and restart RustProxy, or check compatibility with the local Python.", + "acme.runtime_data_dir_missing": "The RustProxy data directory is unavailable, so a private Python environment cannot be created. PEM import and existing certificate files still work.", + "geoipUpdatesTitle": "GeoIP updates", + "geoipUpdatesHelp": "Download direct GeoIP country sources in the background and hot-reload them without restarting proxy traffic. Publisher SHA-256 verification is optional per file.", + "geoipUpdatesEnable": "Enable automatic GeoIP updates", + "geoipReleaseUrl": "Release files base URL", + "geoipChecksumUrl": "Checksum files base URL", + "geoipMaxFileMiB": "Maximum file size (MiB)", + "geoipIntervalHours": "Update interval (hours)", + "geoipStatusLoaded": "GeoIP database loaded", + "geoipStatusMissing": "GeoIP database is not loaded yet. The background worker will retry automatically.", + "geoipStatusDisabled": "Automatic GeoIP updates are disabled.", + "cacheViaEnable": "Add Via response header", + "cacheViaValue": "Via header value", + "cacheViaHelp": "Defaults to Via: RustProxy. Bypass and response-only diagnostic header changes apply immediately without purging cached objects.", + "geoipUpdateNow": "Download now", + "geoipUpdateNowHelp": "Uses the saved direct source URLs and optional checksum URLs. Manual download works even when automatic updates are disabled and hot-reloads the validated database.", + "geoipUpdateSaveFirst": "Save GeoIP settings before running a manual download.", + "geoipUpdateWorking": "Downloading and validating GeoIP data…", + "geoipUpdateUpdated": "GeoIP database downloaded and hot-reloaded.", + "geoipUpdateCurrent": "GeoIP database is already current and loaded.", + "geoipUpdateBusy": "Another GeoIP update is already running; try again shortly.", + "geoipFormat": "Database format", + "geoipFormatCsvPair": "CSV ranges (separate IPv4 + IPv6)", + "geoipFormatMmdb": "MaxMind DB (.mmdb, IPv4 + IPv6)", + "geoipIpv4Url": "IPv4 CSV direct URL", + "geoipIpv4ChecksumUrl": "IPv4 SHA-256 URL (optional)", + "geoipIpv6Url": "IPv6 CSV direct URL", + "geoipIpv6ChecksumUrl": "IPv6 SHA-256 URL (optional)", + "geoipMmdbUrl": "MaxMind DB direct URL", + "geoipMmdbChecksumUrl": "MaxMind DB SHA-256 URL (optional)", + "geoipSourceHelp": "CSV mode uses separate IPv4 and IPv6 range files. MMDB mode uses one native MaxMind DB file and supports standard country.iso_code, registered_country.iso_code, or country_code records. Leave a checksum URL empty to skip publisher-checksum verification; every download is still size-bounded and format-validated before atomic publish. For a local custom MMDB, place it at /geoip/country.mmdb, select MMDB and disable automatic updates.", + "certificateFileSource": "File location", + "certificateSourceManaged": "RustProxy certificate directory (/certs)", + "certificateSourceExternal": "External files (absolute paths)", + "certificateSourceManagedHelp": "Enter filenames only, for example app.pem and app.key. RustProxy loads them from /certs (normally /var/lib/rustproxy/certs for a packaged service).", + "certificateSourceExternalHelp": "Enter absolute server paths. Certbot example: /etc/letsencrypt/live/example.com/fullchain.pem and /etc/letsencrypt/live/example.com/privkey.pem. live/ symlinks are resolved on every reload. POSIX ACL read access is supported; RustProxy does not require an exact chmod. The private key must not be world-readable or group/world-writable, and the path must stay under root- or rustproxy-owned non-writable directories.", + "certificateExternalAclExample": "ACL example (grant traversal, not directory read):\nsetfacl -m u:rustproxy:--x /etc/ssl/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/\nsetfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem /etc/letsencrypt/archive/example.com/privkey*.pem\nAfter renewal, reapply read ACL to the current resolved files before API reload:\nsetfacl -m u:rustproxy:r-- \"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)\" \"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)\"", + "certificateExternalFiles": "External file paths", + "vhWwwRedirect": "Redirect www ↔ non-www", + "vhWwwRedirectTarget": "Choose the domain and redirect direction (308 destination)", + "vhWwwRedirectHelp": "Only the selected www/non-www pair redirects (308). Other names use the same backend without cross-domain redirects. TLS must cover both variants.", + "vhCacheForceEnabled": "Unsafe forced cache for selected URLs", + "vhCacheForceTargets": "Forced cache path/query prefixes (one per line)", + "vhCacheForceHelp": "Per-vhost only. Matching GET/HEAD targets may ignore query bypass, request Cookie/Authorization and client no-cache/no-store. Cookie/Authorization remain separate cache variants. Origin Set-Cookie, private and no-store responses are still never stored.", + "vhCacheForceConfirm": "Forced cache can serve stale or user-specific content if configured incorrectly. Enable it only for explicitly reviewed URL prefixes?", + "locationRedirectAction": "Redirect to URL", + "locationRedirectShort": "redirect", + "locationRedirectStatus": "Redirect status", + "locationRedirectTarget": "Target URL", + "locationRedirectPattern": "Optional rewrite regex for captures", + "locationRedirectQuery": "Preserve request query string", + "locationRedirectHelp": "Target must be an absolute http/https URL. Optional rewrite regex supports $1/${name} replacements in the path/query portion, for example ^/(.*)$ → https://storage.example/o/$1.", + "locationRedirect301": "301 Permanent", + "locationRedirect302": "302 Found", + "locationRedirect303": "303 See Other", + "locationRedirect307": "307 Temporary", + "locationRedirect308": "308 Permanent", + "cacheReason_cache_miss_forced": "Forced-cache target miss", + "vhWwwPanelTitle": "WWW address routing", + "vhWwwInactive": "Redirect is off. Choose a destination below and then enable the switch above. All hostnames currently serve the same vhost.", + "vhWwwOtherDomains": "Other domains (no redirect):", + "vhWwwNoHosts": "Add an exact hostname above to enable a WWW redirect. Wildcards cannot be the destination.", + "vhWwwSharedCount": "{count} names / one vhost", + "vhWwwEnable": "Enable 308 redirect", + "vhErrorFallbackTarget": "Main error fallback target", + "vhErrorFallbackStatuses": "Intercepted status codes", + "vhErrorFallbackHelp": "Target must be an enabled internal Custom Location. Supported statuses: 403, 404, 500, 502, 503, 504. The request is dispatched internally without an HTTP redirect.", + "locationInternal": "Internal named location", + "locationErrorFallbackTarget": "Error fallback target", + "locationErrorFallbackStatuses": "Intercepted status codes", + "locationErrorFallbackHelp": "Optional Nginx-style error fallback. Target an enabled internal location by ID. Supported statuses: 403, 404, 500, 502, 503, 504.", + "webServicePhpPathInfo": "Enable PHP PATH_INFO splitting", + "phpProfileSelect": "PHP-FPM configuration", + "phpProfileLocal": "Local configuration", + "phpProfileHelp": "Select a shared PHP-FPM profile or configure this Web Service directly. Shared edits affect all attached services.", + "phpProfileManage": "Manage PHP-FPM profiles", + "phpProfileToolbar": "PHP-FPM profiles", + "phpProfileTitle": "Shared PHP-FPM profiles", + "phpProfileDescription": "Expand a profile to edit it; save applies immediately to every attached Web Service.", + "phpProfileAdd": "Add profile", + "phpProfileNew": "New PHP-FPM profile", + "phpProfileId": "Profile ID", + "phpProfileName": "Profile name", + "phpProfileAdminValues": "PHP administrator INI overrides (PHP_ADMIN_VALUE)", + "phpProfileInUse": "Profile is used by", + "phpProfileDelete": "Delete this PHP-FPM profile?", + "phpProfileInvalid": "Invalid PHP-FPM profile fields", + "phpProfileDuplicate": "Duplicate PHP-FPM profile ID", + "phpProfileShared": "shared profile", + "backendHealthEnabled": "Monitor backend health (active and passive)", + "backendHealthPerBackend": "Individual backend health checks", + "backendHealthSelect": "Backend to configure", + "backendHealthSelectionHelp": "Choose a backend with monitoring enabled in Backends. Each backend keeps its own unsaved edits when you switch. Turn on Custom health check to override the shared settings above.", + "backendHealthNoEnabled": "No monitored backends. Enable “Monitor backend health” on at least one backend in the Backends tab.", + "backendHealthOverride": "Custom health check for this backend", + "backendHealthOverridesHelp": "When unchecked, inherit the vhost health policy. Disabling monitoring keeps the backend available for explicitly pinned locations, without active probes or passive ejection. Use a custom check for an individual backend.", + "cacheOnlyHits": "Expose cache diagnostics only on HIT (hide MISS and BYPASS)", + "phpHintEnabled": "Run configured PHP file extensions through an already-running PHP-FPM pool. RustProxy does not start PHP; non-PHP files remain static.", + "phpHintPathInfo": "Split /static.php/skins/file.css into SCRIPT_FILENAME=static.php and PATH_INFO=/skins/file.css. Useful for Roundcube; usually unnecessary for WordPress. Off by default.", + "phpHintPassAuthorization": "Pass the incoming Authorization header as HTTP_AUTHORIZATION to PHP (API/Bearer/Basic authentication). Disable if not needed.", + "phpHintExtensions": "Comma-separated extensions without leading dots: php, phtml. Only these extensions run as PHP.", + "phpHintConnectTimeout": "PHP-FPM socket connection timeout in milliseconds: 1000 = 1 second. Range: 50–60000.", + "phpHintRequestTimeout": "Timeout for the complete FastCGI request in milliseconds: 30000 = 30 seconds. Range: 100–3600000.", + "phpHintConcurrency": "Maximum concurrent PHP requests per service: e.g. 64. Exhaustion returns 503. Range: 1–4096.", + "phpHintBodyLimit": "Request-body limit in bytes: 67108864 = 64 MiB. 0 means no RustProxy-side limit. PHP post_max_size is separate.", + "phpHintHeaderLimit": "Maximum FastCGI response header length in bytes: 65536 = 64 KiB. Range: 4096–1048576.", + "phpHintStderrLimit": "Maximum PHP-FPM STDERR bytes logged per request: e.g. 4096. 0 disables stderr capture (max 65536).", + "phpHintAdminValues": "Trusted administrative INI settings, e.g. open_basedir = /var/www/webmail/:/tmp/. Use a dedicated PHP-FPM pool/socket to prevent cross-vhost INI leakage.", + "phpDocsScope": "These fields and examples apply to local PHP settings in a Web Service and to a reusable PHP-FPM profile. The chosen profile supplies PHP settings to all attached services; RustProxy connects to an already-running FPM pool.", + "phpDocsSwitchesTitle": "Enable PHP and handle PATH_INFO", + "phpDocsLimitsTitle": "Execution and size limits", + "phpDocsLimitsText": "PHP extensions: php, phtml (comma-separated). Connect timeout: 1000 ms = 1 second. Full request timeout: 30000 ms = 30 seconds. Maximum concurrent requests: 64 (exhaustion returns 503). Request body limit: 67108864 bytes = 64 MiB; 0 is unlimited in RustProxy. Response headers: 65536 bytes. STDERR capture: 4096 bytes; 0 disables it. Enter whole numbers; PHP INI limits are configured separately.", + "phpDocsValuesText": "Enter one directive = value per line in PHP INI overrides (PHP_VALUE), NOT in custom FastCGI parameters. Use php.ini syntax without [section] headers or PHP tags. Units such as M/G, booleans On/Off and timeouts in seconds are interpreted by PHP. Some PHP directives cannot be changed via PHP_VALUE. Never duplicate a key in PHP_ADMIN_VALUE.", + "phpDocsAdminTitle": "PHP_ADMIN_VALUE — privileged INI overrides", + "phpDocsAdminText": "Enter these in PHP administrator INI overrides (PHP_ADMIN_VALUE). On Unix, open_basedir is a colon-separated list of application-specific allowed paths, e.g. /var/www/webmail/:/tmp/. Prefer php_admin_value[...] defined in a dedicated PHP-FPM pool for sensitive directives. Do not apply Roundcube restrictions to WordPress workers.", + "phpDocsOtherTitle": "Validation, storage and troubleshooting", + "phpDocsOtherText": "INI names may contain letters, digits, dot, underscore and hyphen (max 64 bytes). Values must be nonempty (max 8192 bytes). At most 64 custom FastCGI parameters and 64 INI entries per map; combined data at most 128 KiB. Newlines/control characters inside values are rejected. All values are saved in standard configuration; avoid secrets. A syntactically valid option can still be rejected or ignored by PHP-FPM.", + "phpDocsPoolWarning": "Important: PHP_VALUE/PHP_ADMIN_VALUE do NOT isolate vhosts sharing PHP-FPM workers. WordPress and Roundcube must use distinct pool sockets if their INI policies differ. Restart old PHP-FPM workers after separating them. PATH_INFO is not a workaround for open_basedir.", + "vhostInterceptOriginErrors": "Replace application error bodies with RustProxy error pages", + "vhostInterceptOriginStatuses": "Intercept HTTP statuses (comma-separated)", + "vhostInterceptOriginErrorsHelp": "Example: 404, 500, 502, 503, 504. For selected codes, RustProxy keeps the HTTP error status and returns the vhost/global custom error page, or a built-in one. Application content/cookies are removed; the replacement is no-store. Off by default. Internal named-location fallbacks take precedence. Applies to reverse proxy and Web Server (including PHP).", + "vhostInterceptOriginInvalid": "Select one or more unique supported HTTP codes: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504.", + "systemInformation": "System information", + "systemInformationHelp": "Live, read-only information from the authenticated status API. Unavailable metrics are not estimated.", + "systemHostHeading": "Operating system and hardware", + "systemOsVersion": "Operating system", + "systemKernel": "Kernel", + "systemArchitecture": "Architecture", + "systemLogicalCpus": "Logical CPUs", + "systemCpuLoad": "CPU usage", + "systemProcessHeading": "RustProxy process", + "systemAppVersion": "RustProxy version", + "systemProcessUptime": "Process uptime", + "systemProcessMemory": "Process RAM (RSS)", + "systemProcessThreads": "Process threads", + "systemOpenFds": "Open file descriptors", + "systemCertificateHeading": "ACME / Python environment", + "systemCertificateHelp": "Versions are detected from the interpreter and tools actually selected for certificate management.", + "systemAcmeStatus": "ACME status", + "systemPathsHeading": "Data locations", + "refreshSection": "REFRESH", + "refreshTitle": "Data refresh", + "refreshHelp": "Intervals are saved per administrator. Turning off automatic refresh stops SSE and all polling; manual buttons still work.", + "refreshLiveEnabled": "Automatic refresh enabled", + "refreshOverview": "Live overview / SSE interval", + "refreshTraffic": "Traffic interval", + "refreshCache": "Cache interval", + "refreshSystemInfo": "System information interval", + "refreshManual": "Manual", + "refreshNow": "Refresh now", + "refreshViewLive": "Auto in this view", + "refreshManualHint": "Automatic system metrics are paused; use Refresh now.", + "refreshSystemHint": "System metrics refresh independently when Overview or the relevant Settings section is open.", + "startupLoading": "Loading RustProxy…", + "startupPreparing": "Connecting to the administration panel.", + "startupSlow": "The panel is taking longer than expected. Check your connection and try again.", + "startupRetry": "Reload panel", + "startupFailed": "Could not load RustProxy", + "panelCompressionTitle": "Panel content compression", + "panelCompressionHelp": "Compressed HTML, scripts, styles and language files. Browser negotiation: Zstd → Brotli → gzip at equal quality. Disable all for identity. Takes effect immediately; a reverse proxy may change Accept-Encoding or re-encode responses.", + "cacheHttpInvalidationCurlLabel": "How to send BAN and PURGE with curl", + "cacheHttpInvalidationCurlIntro": "Run curl from an allowlisted IP against a public vhost:", + "cacheHttpInvalidationCurlNote": "PURGE removes an exact URL; BAN invalidates a path prefix for 1 hour. No admin cookie is needed, but your IP must be allowed. A reverse proxy must forward these methods and trusted client IP information.", + "cacheHttpInvalidationTitle": "HTTP BAN / PURGE allowlist", + "cacheHttpInvalidationAddresses": "Permitted client IP addresses / CIDRs (one per line)", + "cacheHttpInvalidationHelp": "Empty = disabled. PURGE /path?query removes exact matches; BAN /path prevents use of cached URLs with that prefix (default TTL 1 hour, optional X-RustProxy-Ban-Ttl). BAN / requires X-RustProxy-Confirm-Wide: 1. Only the matched Host and vhost are affected. Behind a reverse proxy, configure trusted proxies and sanitize forwarding headers." + } } diff --git a/langs/pl_pl.json b/langs/pl_pl.json index c960635..6947326 100644 --- a/langs/pl_pl.json +++ b/langs/pl_pl.json @@ -1,2309 +1,2335 @@ { - "meta": { - "locale": "pl-PL", - "name": "Polish", - "native_name": "Polski", - "direction": "ltr" - }, - "translations": { - "overview": "Przegląd", - "mainMenu": "Menu", - "routing": "Starsze trasy", - "configuration": "Konfiguracja", - "manualConfig": "Konfiguracja ręczna", - "runtime": "ŚRODOWISKO", - "localOnly": "ADMINISTRACJA", - "logout": "Wyloguj", - "firstSlice": "NIEZALEŻNY RDZEŃ / JAWNY DOSTĘP", - "loginTitle": "Administracja", - "unlock": "Odblokuj administrację", - "tokenHelp": "Uruchom polecenie w drugim terminalu i wklej token poniżej.", - "token": "Lokalny token dostępu", - "connect": "Połącz z proxy", - "tokenNote": "Administracja domyślnie używa HTTP na loopback. Przed użyciem adresu spoza loopback włącz HTTPS panelu.", - "requests": "Żądania", - "activeExchanges": "Aktywne żądania", - "bodyLifetime": "Do zakończenia strumienia", - "bandwidth": "Przekazane dane", - "bothDirections": "W obu kierunkach", - "errors": "Błędy", - "errorDescription": "5xx / body / przerwane", - "liveTraffic": "RUCH NA ŻYWO", - "liveTransformed": "Transformowane", - "requestsPerSecond": "Żądania na sekundę", - "live": "NA ŻYWO", - "lastMinute": "Ostatnie próbki / reset po odświeżeniu", - "coreState": "STAN RDZENIA", - "configurationRevision": "Aktywna rewizja", - "backends": "Backendy", - "routes": "Trasy", - "connections": "Otwarte połączenia", - "uptime": "Czas działania", - "activity": "AKTYWNOŚĆ", - "accessLog": "Log dostępu", - "boundedLog": "Ostatnie 50 / bufor 4096", - "time": "Czas", - "request": "Żądanie", - "status": "Status", - "duration": "Czas obsługi", - "outcome": "Wynik", - "noTraffic": "Brak ruchu. Otwórz proxy, aby wysłać pierwsze żądanie.", - "reload": "Wczytaj konfigurację", - "addBackend": "Dodaj / edytuj backend", - "backendUrl": "Adres backendu (HTTP / HTTPS, IP / DNS)", - "allowPrivate": "Jawnie zezwól na prywatny IP / loopback", - "preserveHost": "Zachowaj oryginalny nagłówek Host", - "saveBackend": "Zapisz backend", - "clear": "Wyczyść", - "addRoute": "Dodaj / edytuj trasę", - "hostnames": "Hostname i aliasy, oddzielone przecinkami", - "pathPrefix": "Prefiks ścieżki (bez usuwania)", - "backend": "Backend", - "enabled": "Włączona", - "saveRoute": "Zapisz trasę", - "rawConfig": "Dokument konfiguracji", - "export": "Eksportuj JSON", - "configHelp": "Poprawny zapis jest stosowany atomowo. Błędne lub nieaktualne zmiany nie zastępują działającej konfiguracji.", - "apply": "Sprawdź, zapisz i zastosuj", - "noRestart": "Routing i polityki odpowiedzi działają od razu; zmiana portów wymaga restartu.", - "roadmapHelp": "Zmiany w wydaniach i planowane funkcje opisuje plik ROADMAP.md.", - "overviewTitle": "Ruch pod kontrolą.", - "overviewDescription": "Ruch, cache i pomiary systemu operacyjnego w jednym zwartym panelu.", - "routingTitle": "Połącz właściwe adresy.", - "routingDescription": "Dotychczasowy format backendów i tras pozostaje obsługiwany. Nowe konfiguracje mogą używać vhosta z własnym frontendem i pulą.", - "configurationTitle": "Jedno źródło prawdy.", - "configurationDescription": "Jedna konfiguracja: vhosty, listenery i wspolne polityki.", - "connected": "POŁĄCZONO Z LOKALNYM RDZENIEM", - "disconnected": "RDZEŃ NIEDOSTĘPNY", - "edit": "Edytuj", - "remove": "Usuń", - "privateAllowed": "ZGODA NA PRYWATNY IP", - "configured": "SKONFIGUROWANY / BEZ PROBE", - "disabled": "WYŁĄCZONA", - "emptyBackends": "Brak backendów. Dodaj jawny adres HTTP lub HTTPS.", - "emptyRoutes": "Brak tras. Nieznane hostname zwracają 404.", - "saving": "Zapisywanie…", - "unsavedChanges": "Niezapisane zmiany", - "saved": "Zastosowano konfigurację.", - "confirmDelete": "Usunąć ten element? Zmiana zadziała natychmiast.", - "confirmTitle": "Potwierdź operację", - "confirmationLabel": "POTWIERDZENIE", - "warningLabel": "OSTRZEŻENIE", - "continueAction": "Kontynuuj", - "cancelAction": "Anuluj", - "discardAction": "Odrzuć zmiany", - "errorContext": "{area}: {message}", - "stale": "Konfiguracja zmieniła się w innym miejscu. Wczytaj ją przed zapisem.", - "busy": "Trwa już zapis.", - "loginError": "Nie udało się zalogować.", - "fresh": "Wczytano konfigurację.", - "locked": "Wylogowano i unieważniono sesję.", - "backendUsed": "Backend jest używany przez trasę. Najpierw usuń lub zmień trasę.", - "renaming": "Nie można zmienić ID w formularzu. Użyj edytora JSON i zmień również powiązania.", - "complete": "zakończono", - "cancelled": "przerwano", - "body_error": "błąd body", - "proxy_or_upstream_error": "błąd proxy/backendu", - "security": "Bezpieczeństwo", - "sessionNote": "Lokalna administracja HTTP. Hasła używają Argon2id, a cookies sesji są HttpOnly. Nie udostępniaj panelu publicznie.", - "setupHelp": "Utwórz pierwsze konto administratora. Jednorazowy token odczytasz lokalnie w terminalu.", - "setupToken": "Jednorazowy token instalacyjny", - "username": "Nazwa użytkownika", - "password": "Hasło", - "confirmPassword": "Powtórz hasło", - "setupTitle": "Utwórz administratora", - "signIn": "Zaloguj się", - "createAndSignIn": "Utwórz konto i zaloguj", - "securityTitle": "Dostęp pod kontrolą.", - "securityDescription": "Jeden administrator. Odwoływalne sesje. Jawne polityki hostów i tras.", - "manualBackends": "RĘCZNE BACKENDY", - "routePolicies": "Kontrola dostępu i nagłówki", - "useRouteAcl": "Włącz ACL trasy", - "aclDefault": "Domyślna akcja", - "allow": "Zezwól", - "deny": "Zablokuj", - "aclRules": "Jedna reguła w wierszu: allow/deny oraz CIDR", - "aclHelp": "Podaj adres IPv4/IPv6 albo CIDR, np. 192.168.1.0/24 lub 10.0.0.5. Odmowa ma pierwszeństwo. Przy trybie krajów „tylko zezwolone” wystarczy dozwolony kraj LUB dozwolone IP; przy blokowaniu krajów blokowany kraj LUB deny IP odrzuca ruch. Uwierzytelnienie nadal obowiązuje.", - "routeHeaders": "Nagłówki request / response (JSON)", - "headersHelp": "Operacje: ustaw, dodaj, usuń. Nagłówki transportu, kodowania body i tożsamości klienta są chronione.", - "currentSession": "BIEŻĄCA SESJA", - "recoveryHelp": "Utrata dostępu? Uruchom lokalnie rust-proxy admin reset-password. Dotychczasowe sesje zostaną unieważnione.", - "revokeAll": "Wyloguj wszystkie sesje", - "changePassword": "Zmień hasło", - "currentPassword": "Obecne hasło", - "newPassword": "Nowe hasło (12-128 bajtów UTF-8)", - "savePassword": "Zmień i wyloguj", - "hostPolicies": "Polityki dostępu hostów", - "editHostPolicy": "Dodaj / zastąp politykę hosta", - "savePolicy": "Zastosuj politykę", - "auditRetention": "SQLITE / OSTATNIE 100 / LIMIT 10 000", - "audit": "Audyt administracyjny", - "reloadAudit": "Odśwież", - "actor": "Użytkownik", - "action": "Operacja", - "detail": "Szczegóły", - "noAudit": "Brak wpisów audytu.", - "noPolicies": "Brak ACL hostów. Dostęp mogą ograniczać reguły tras.", - "expires": "Sesja wygaśnie najpóźniej: ", - "passwordChanged": "Zmieniono hasło i unieważniono sesje. Zaloguj się ponownie.", - "passwordMismatch": "Hasła nie są takie same.", - "ruleSyntax": "Wpisz jedną regułę w wierszu, np. allow 127.0.0.1/32", - "confirmRevoke": "Wylogować wszystkie sesje, również bieżącą?", - "streamLive": "SSE na żywo", - "streamFallback": "Odpytywanie awaryjne", - "auth.required": "Sesja wygasla lub zostala odwolana. Zaloguj sie ponownie.", - "auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło.", - "auth.password_policy": "Haslo musi miec 12-128 bajtow UTF-8, bez znakow nowego wiersza.", - "auth.invalid_username": "Uzyj 3-64 liter, cyfr, kropek, myslnikow lub podkreslen.", - "auth.invalid_setup_token": "Niepoprawny token instalacyjny. Odczytaj go w lokalnym terminalu.", - "auth.already_initialized": "Administrator juz istnieje. Zaloguj sie.", - "auth.rate_limited": "Zbyt wiele prob. Ponow po 60 sekundach lub uzyj lokalnego resetu hasla.", - "auth.busy": "Trwa weryfikacja innych hasel. Ponow za chwile.", - "auth.csrf": "Niepoprawna ochrona zadania. Odswiez strone i ponow.", - "auth.invalid_host": "Użyj skonfigurowanego adresu administracji i dozwolonej nazwy hosta.", - "auth.invalid_origin": "Zablokowano dostep administracyjny z innego originu.", - "auth.credentials_changed": "Dane logowania zostaly zmienione. Zaloguj sie ponownie.", - "auth.unavailable": "Baza administracyjna jest chwilowo niedostepna.", - "events.too_many_viewers": "Osiagnieto limit odbiorcow SSE; pozostaje odpytywanie.", - "protocol": "Protokół upstream", - "protocolHelp": "HTTP/2 i auto używają authority backendu. Preserve Host jest dostępne tylko z HTTP/1.1. RFC 8441 może użyć jawnego upstreamu HTTP/2; pozostałe transporty WebSocket są mostkowane przez HTTP/1.1.", - "addressAllowlist": "Opcjonalna lista dozwolonych adresów (CIDR, po jednym w wierszu)", - "caPem": "Dodatkowy zaufany CA / certyfikat (PEM, opcjonalnie)", - "backendTlsHelp": "Szyfrowanie TLS pozostaje dostępne, ale weryfikacja certyfikatu i nazwy jest domyślnie wyłączona, dopóki nie włączysz jej jawnie. Prywatne adresy nadal wymagają zgody, także po rozwiązaniu DNS.", - "advancedTransport": "Transport i zaufanie", - "tls": "Certyfikaty", - "tlsTitle": "Szyfrowanie pod kontrolą.", - "tlsDescription": "Dokładne nazwy SNI. Prywatne pliki certyfikatów. Zmiany bez restartu.", - "certificateBindings": "Powiązania certyfikatów", - "addCertificate": "Dodaj certyfikat", - "certificateId": "ID certyfikatu", - "certificateFile": "Plik certyfikatu / ścieżka", - "privateKeyFile": "Plik klucza prywatnego / ścieżka", - "defaultCertificate": "Użyj jako certyfikatu domyślnego", - "saveCertificate": "Zastosuj certyfikat", - "reloadCertificates": "Wczytaj pliki certyfikatów", - "certificateHelp": "Możesz użyć plików zarządzanych przez RustProxy, pełnych ścieżek zewnętrznych, importu PEM albo ACME. Nazwy plików są ładowane z /certs. Ścieżki zewnętrzne są czytane bez kopiowania i można je przeładować po odnowieniu certyfikatu bez zmiany rewizji konfiguracji.", - "certificateHint": "Odnowienie zewnętrzne: pozostaw ścieżki fullchain.pem/privkey.pem w konfiguracji, odnów je przez Certbot/acme.sh, a następnie użyj Przeładuj certyfikat albo POST /api/certificates/{certificate_id}/reload.", - "emptyCertificates": "Brak jawnych powiązań SNI. Aktywny listener TLS używa wygenerowanego certyfikatu domyślnego.", - "certificateSafety": "Certyfikaty samopodpisane służą do testów. Certyfikat nie tworzy trasy. Zmiana dotyczy nowych połączeń TLS.", - "wsActive": "Tunele WebSocket", - "h2Requests": "Żądania HTTP/2", - "h2ActiveConnections": "Aktywne połączenia H2", - "tlsFailures": "Błędy handshake TLS", - "tlsListener": "Listener TLS", - "notEnabled": "Wyłączony", - "websocket_closed": "WebSocket zamknięty", - "defaultLabel": "DOMYŚLNY", - "language": "Język", - "settings": "Ustawienia", - "settingsTitle": "Ustawienia proxy.", - "settingsDescription": "Przelaczaj karty funkcji. Niezapisane zmiany pozostaja w formularzach.", - "cacheHitRatio": "Trafienia cache", - "processMemory": "Pamięć procesu", - "systemLoad": "OBCIĄŻENIE SYSTEMU", - "systemChart": "Ostatnie próbki obciążenia CPU", - "systemMemory": "RAM użyta / łącznie", - "systemSwap": "Swap systemu", - "systemUptime": "Czas pracy systemu", - "loadAverage": "Load 1 / 5 / 15 min", - "cacheOverview": "Cache RAM", - "cacheObjects": "Obiekty / pamięć", - "cacheEvictions": "Usunięte / wygasłe", - "processDiagnostics": "Proces", - "platform": "Platforma", - "processCpu": "CPU (100% = 1 rdzeń)", - "fileDescriptors": "Deskryptory / limit", - "processThreads": "Wątki systemowe", - "processVirtualMemory": "Pamięć wirtualna / limit", - "processStartTime": "Start procesu", - "containerMemory": "RAM cgroup / limit", - "networkDiagnostics": "Sieć i odpowiedzi", - "hostScope": "Zakres systemu", - "networkRate": "RX / TX na sekundę", - "meanLatency": "Średni czas obsługi", - "filterLogs": "Filtruj host lub ścieżkę", - "filterResult": "Filtruj wynik", - "allResults": "Wszystkie", - "routeCache": "Cache dla trasy", - "inheritCache": "Dziedzicz ustawienia globalne", - "bypassCache": "Zawsze pomijaj cache", - "listeners": "Listenery", - "httpConnections": "Połączenia HTTP", - "httpConnectionsHelp": "Cykl życia połączeń i limity transportu dla HTTP/2 oraz HTTP/3.", - "http2Tab": "HTTP/2", - "http3Tab": "HTTP/3", - "http2Settings": "Połączenia HTTP/2", - "http2SettingsHelp": "HTTP/2 włącza się osobno dla każdego listenera. Tutaj ustawisz bezczynność, maksymalny czas życia i graceful drain po GOAWAY.", - "http2IdleTimeout": "Idle timeout (sekundy)", - "http2MaxLifetime": "Maksymalny czas życia połączenia (sekundy, 0 = wyłączony)", - "http2DrainTimeout": "Drain timeout (sekundy)", - "http2DrainHelp": "Te limity dotyczą tego listenera. Idle timeout obejmuje tylko nieużywane połączenia, maksymalny lifetime jest bezwzględny, a drain ogranicza czas aktywnych strumieni po GOAWAY. Zmiany wymagają restartu.", - "http2Save": "Zapisz ustawienia HTTP/2", - "http2NoEnabledListeners": "Żaden listener nie ma włączonego HTTP/2. Najpierw włącz HTTP/2 lub h2c w Ustawienia > Listenery.", - "http2ListenersDirty": "Najpierw zapisz oczekujące zmiany w Listenerach, a następnie edytuj ustawienia HTTP/2.", - "restartLabel": "RESTART", - "listenersHelp": "Nazwane adresy IP i porty. Zmiany wymagają restartu. Panel administracyjny pozostaje osobno.", - "httpAddress": "Adres nasłuchu i port HTTP", - "httpsAddress": "Adres nasłuchu i port HTTPS", - "enableHttps": "Włącz nasłuch HTTPS", - "lowPortsHelp": "Porty uprzywilejowane mogą wymagać CAP_NET_BIND_SERVICE. Nie uruchamiaj całej aplikacji jako root.", - "saveListeners": "Zapisz ustawienia portów", - "inheritListeners": "Użyj ustawień startowych", - "responseSettings": "Odpowiedź do klienta", - "hotApply": "BEZ RESTARTU", - "serverHeader": "Nagłówek Server", - "serverPreserve": "Zachowaj wartość backendu", - "serverHide": "Ukryj całkowicie", - "serverCustom": "Nadpisz / dodaj własną wartość", - "serverValue": "Własna wartość Server", - "compression": "Kompresja strumieniowa", - "compressionMin": "Minimalny rozmiar odpowiedzi (bajty)", - "compressionHelp": "Negocjuje Accept-Encoding według q klienta, a przy równym q używa priorytetu zstd → br → gzip → deflate. Cookie, Authorization, Set-Cookie oraz odpowiedzi private i no-store mogą być kompresowane. Istniejący Content-Encoding, zakresy, SSE, upgrade i no-transform są przekazywane bez rekompresji.", - "saveResponse": "Zastosuj ustawienia odpowiedzi", - "cacheSettings": "Cache", - "enableCache": "Włącz cache", - "cacheSafety": "Cache zapisuje kompletne, bezpieczne odpowiedzi GET. Authorization, cookies oraz odpowiedzi private/no-store nadal omijają cache. Pojedynczy zakres bajtów może być obsłużony z pełnego obiektu cache; multipart i nieobsługiwane zakresy przechodzą do originu.", - "cacheMemory": "Limit alokowanej pamięci HOT (MiB)", - "cacheObject": "Limit obiektu (KiB)", - "cacheEntries": "Maksymalna liczba wpisów", - "cacheMaxTtl": "Maksymalny TTL (sekundy)", - "cacheDefaultTtl": "Zapasowy TTL odpowiedzi publicznych (0 = wył.)", - "bypassQuery": "Pomijaj adresy z parametrami query", - "cachePrefixes": "Pomijane prefiksy ścieżek, po jednym w wierszu", - "cacheAgeHelp": "Trafienia zawierają Age, chyba że jawna reguła nagłówka odpowiedzi go usunie lub zastąpi. Takie reguły Age działają tylko na odpowiedzi wysyłanej do klienta i nie zmieniają świeżości cache. Liczniki działają również przy ukrytych nagłówkach diagnostycznych. Tylko zmiany wpływające na cache unieważniają niezgodne obiekty; podczas zarządzanej aktualizacji hot cache jest przenoszony do cold storage, jeśli cold tier jest włączony.", - "saveCache": "Zastosuj ustawienia cache", - "purgeCache": "Wyczyść cały cache", - "customHeaders": "Globalne reguły nagłówków", - "globalHeadersHelp": "Request trafia do backendu, response do klienta. Reguły trasy są stosowane po globalnych. Polityka Server ma ostateczne pierwszeństwo.", - "responseHeaders": "Odpowiedź do klienta", - "requestHeaders": "Żądanie do backendu", - "addHeader": "Dodaj regułę nagłówka", - "saveHeaders": "Zastosuj reguły nagłówków", - "unavailable": "Brak danych", - "threads": "wątków", - "realSample": "Pomiar", - "noKnownLimit": "nieznany / bez limitu", - "unlimited": "bez limitu", - "restartPending": "Zapisano zmianę portów. Uruchom ponownie RustProxy, aby ją zastosować.", - "activeListeners": "Aktualnie aktywne", - "headerLimit": "Maksymalnie 32 reguły w każdym kierunku.", - "headerOperation": "Operacja", - "header_set": "Ustaw", - "header_append": "Dodaj", - "header_remove": "Usuń", - "headerName": "Nazwa nagłówka", - "headerValue": "Wartość nagłówka", - "confirmInherit": "Przywrócić ustawienia portów z parametrów startowych i środowiska po restarcie?", - "confirmPurge": "Usunąć wszystkie obiekty cache i anulować zapis trwających pobrań?", - "cachePurged": "Cache wyczyszczony; usunięte wpisy", - "logsDropped": "odrzucone", - "updateAvailable": "Nowa wersja", - "vhosts": "Vhosty", - "vhostsTitle": "Vhosty i backendy", - "vhostsDescription": "Zarządzaj domenami, frontendami, backendami i politykami hostów.", - "vhHealthTitle": "Backendy", - "vhManage": "Zarządzaj vhostami", - "vhHealthEmpty": "Brak danych diagnostycznych backendów.", - "vhVhost": "Vhost", - "vhActive": "Aktywne", - "vhRequests": "Żądania", - "vhErrors": "Błędy", - "vhLatency": "Opóźnienie (EWMA)", - "vhTraffic": "Transfer/s", - "vhClientGeneration": "Gen. klienta", - "vhGenerationRequests": "Żądania gen.", - "vhGenerationAge": "Wiek gen.", - "vhClientRotations": "Rotacje", - "vhIntro": "Każdy vhost ma własne adresy IP, ścieżki i backendy. Inne adresy go nie obsługują. Zmiana nasłuchu wymaga restartu.", - "vhList": "Hosty wirtualne", - "vhSearch": "Szukaj vhostów", - "vhSearchPlaceholder": "Szukaj domeny, vhosta lub backendu", - "vhSearchEmpty": "Brak vhostów pasujących do wyszukiwania.", - "vhNew": "Nowy vhost", - "vhMigrate": "Migracja grupy starszych tras", - "vhImport": "Wczytaj do edytora", - "vhMigrationHint": "Migracja jest atomowa. Trasy tych samych hostów i backendu są grupowane; niejednoznaczne przypisanie hostów jest odrzucane.", - "vhEditor": "Konfiguracja vhosta", - "vhReset": "Odrzuć zmiany", - "vhSafetyHint": "Prywatny IP nie zastępuje uwierzytelniania. Zachowaj firewall i ACL. Preload HSTS jest opcjonalny; cookie affinity nie jest sesją logowania.", - "vhId": "Identyfikator vhosta", - "vhHosts": "Nazwy hostów", - "vhEnabled": "Włączony", - "vhFrontends": "Frontendy / adresy nasłuchu", - "vhAddFrontend": "Dodaj frontend", - "vhAddress": "IP:port (IPv6: [adres]:port)", - "vhTls": "TLS włączony", - "vhHttp2": "Preferuj HTTP/2 przez TLS; zachowaj HTTP/1.1", - "vhBackendPool": "Backendy", - "vhAddBackend": "Dodaj backend", - "vhMemberId": "Identyfikator backendu", - "vhUrl": "Adres URL backendu", - "vhWeight": "Waga", - "vhBackup": "Tylko zapasowy", - "vhDrain": "Wstrzymaj nowe sesje", - "vhPrivate": "Zezwól na prywatny IP backendu", - "vhPreserveHost": "Zachowaj Host klienta (tylko HTTP/1.1)", - "vhProtocol": "Protokół backendu", - "vhProtocolHttp1": "HTTP/1.1", - "vhProtocolAuto": "TLS ALPN: HTTP/2 + HTTP/1.1", - "vhProtocolHttp2": "HTTP/2 (TLS: także HTTP/1.1; bez TLS: h2c)", - "vhAddressAllowlist": "Dozwolone IP/CIDR backendu (opcjonalne)", - "vhTlsServerName": "Nazwa serwera TLS / SNI (opcjonalna)", - "vhTlsVerify": "Weryfikuj certyfikat i nazwę hosta backendu", - "vhTlsVerifyHelp": "Przy pominięciu ustawienia weryfikacja jest wyłączona. Włącz ją, gdy RustProxy ma weryfikować łańcuch i tożsamość certyfikatu backendu. Jeśli walidacja nie przechodzi, ustaw nazwę serwera TLS na nazwę DNS obecną w certyfikacie i/lub dodaj PEM prywatnego CA. Nie używaj adresu IP jako SNI.", - "vhCa": "Dodatkowy zaufany CA (PEM, opcjonalny)", - "vhBalancing": "Balansowanie i ponowne użycie połączeń", - "vhAlgorithm": "Algorytm", - "vhRoundRobin": "Płynny ważony round-robin", - "vhLeastConnections": "Najmniej aktywnych wymian, z wagami", - "vhLeastTraffic": "Najmniejszy transfer (EWMA), z wagami", - "vhAdaptive": "Adaptacyjny: aktywność + opóźnienie + transfer", - "vhIpHash": "Skrót IP klienta", - "vhSlowStart": "Łagodny powrót (sekundy)", - "vhRetryMax": "Ponowienia failover", - "vhRetryConnect": "Ponawiaj błędy połączenia", - "vhRetryTimeouts": "Ponawiaj timeout nagłówków odpowiedzi", - "vhRetryStatuses": "Ponawiane statusy HTTP", - "vhRetryHelp": "Retry działa tylko dla bezbody GET, HEAD i OPTIONS, wybiera inny zdrowy backend i nigdy nie ponawia WebSocketów ani żądań zapisu.", - "vhKeepalive": "Używaj ponownie połączeń do backendu (keepalive)", - "vhIdleTimeout": "Limit bezczynności w puli (sekundy)", - "vhMaxIdle": "Maks. bezczynnych połączeń / backend", - "vhTcpKeepalive": "Interwał keepalive TCP / HTTP/2 (sekundy)", - "vhHealth": "Zdrowie backendów", - "vhHealthMode": "Rodzaj testu", - "vhOff": "Wyłączone", - "vhTcp": "Połączenie TCP", - "vhHttp": "Status HTTP + czas odpowiedzi", - "vhHealthPath": "Ścieżka testu HTTP", - "vhHealthHost": "Własny Host testu HTTP (opcjonalny)", - "vhInterval": "Interwał (ms)", - "vhTimeout": "Limit czasu (ms)", - "vhSlow": "Próg wolnej odpowiedzi (ms; 0 = wył.)", - "vhFall": "Niepowodzenia przed wyłączeniem", - "vhRise": "Sukcesy przed przywróceniem", - "vhStatusMin": "Minimalny status HTTP", - "vhStatusMax": "Maksymalny status HTTP", - "vhHealthNote": "Sondy HTTP używają HTTP/1.1 i mierzą czas do nagłówków, nie czas całego body. Gdy pole Host jest puste, RustProxy automatycznie używa pierwszej dokładnej domeny DNS vhosta; jeśli jej nie ma, używa hosta backendu. Dla usług tylko h2c użyj TCP. Nowe backendy czekają na udaną sondę. Active health jest niezależny od passive health.", - "vhPassiveHealth": "Pasywne zdrowie backendu", - "vhPassiveEnabled": "Pozwól, aby błędy zwykłych requestów oznaczały backend jako niedostępny", - "vhPassiveHelp": "Domyślnie wyłączone. Błędy nadal są logowane i zliczane, ale zwykły ruch użytkowników nie zmienia stanu backendu bez świadomego włączenia.", - "vhPassiveConnect": "Zliczaj błędy połączenia", - "vhPassiveTimeouts": "Zliczaj timeouty", - "vhPassiveIo": "Zliczaj błędy I/O body/odczytu/zapisu", - "vhPassive5xx": "Zliczaj odpowiedzi HTTP 5xx", - "vhPassiveSlow": "Zliczaj wolne odpowiedzi", - "vhPassiveFall": "Pasywne błędy przed wyłączeniem (puste = active fall)", - "vhPassiveRise": "Pasywne sukcesy przed przywróceniem (puste = active rise)", - "vhPassiveWarning": "Włączenie 5xx lub wolnych odpowiedzi może wyłączyć cały backend po serii błędów z pojedynczego endpointu.", - "vhSticky": "Sticky sessions", - "vhStickyMode": "Tryb przypisania", - "vhCookie": "Nieprzezroczyste cookie proxy", - "vhCookieName": "Nazwa cookie (pusta = domyślna per vhost)", - "vhCookieTtl": "Ważność cookie (sekundy)", - "vhStickyNote": "Sticky cookie omija współdzielony cache. Drain zachowuje istniejące sesje cookie. Przypisanie po IP używa zweryfikowanego adresu klienta zgodnie z polityką zaufanych proxy.", - "vhSecurity": "Bezpieczeństwo", - "vhHsts": "Włącz HSTS (tylko odpowiedzi TLS)", - "vhHstsAge": "HSTS max-age (sekundy)", - "vhHstsSub": "HSTS includeSubDomains", - "vhHstsPreload": "HSTS preload (świadome włączenie)", - "vhNosniff": "X-Content-Type-Options: nosniff", - "vhFrame": "X-Frame-Options", - "vhPreserve": "Zachowaj wartość backendu", - "vhDeny": "DENY", - "vhSameorigin": "SAMEORIGIN", - "vhReferrer": "Referrer-Policy (opcjonalne)", - "vhCsp": "Content-Security-Policy (opcjonalne)", - "vhPermissions": "Permissions-Policy (opcjonalne)", - "vhAdvanced": "Zaawansowane: ścieżki, ACL i własne nagłówki", - "vhPaths": "Sciezki (JSON; opcjonalne backend_id przypina sciezke do jednego backendu)", - "vhAcl": "ACL vhosta (JSON lub null)", - "vhHeaders": "Reguły nagłówków żądania / odpowiedzi (JSON)", - "vhEmpty": "Brak vhostów. Dotychczasowe trasy nadal działają.", - "vhUnsaved": "Odrzucić niezapisane zmiany vhosta?", - "vhDelete": "Usunąć vhost? Gniazda nasłuchu zostaną zamknięte po restarcie.", - "vhConflict": "Vhost o takim identyfikatorze już istnieje.", - "vhRowLimit": "Osiągnięto maksymalną liczbę wierszy.", - "vhMigrationAmbiguous": "Hosty pokrywają się z innymi trasami. Uporządkuj przypisanie w JSON przed migracją.", - "vhMigrationPending": "Migracja starszych tras zostanie zatwierdzona przy zapisie.", - "vhMigrationNone": "Wybierz starszą trasę", - "vhJsonInvalid": "Nieprawidłowy JSON lub typ danych w ścieżkach, ACL lub nagłówkach.", - "vhUp": "Działa", - "vhDown": "Niedostępny", - "vhChecking": "Sprawdzanie", - "vhDraining": "Wygaszanie", - "vhDisabled": "Wyłączony", - "vhHealthDisabled": "Testy wyłączone", - "vhHealthUnknown": "Nieznany", - "vhNewLabel": "NOWY", - "vhEditLabel": "EDYCJA", - "vhFrontendNote": "Użyj IP przypisanego do serwera. Wildcard przyjmuje wszystkie IP danej rodziny; routing nadal sprawdza lokalny adres docelowy. Certyfikaty ustaw w zakładce TLS.", - "close": "Zamknij", - "inherit": "Dziedzicz", - "liveLogs": "Logi na żywo", - "logVhost": "Filtruj vhost", - "logAllVhosts": "Wszystkie vhosty", - "logAllMethods": "Wszystkie metody", - "logPause": "Wstrzymaj", - "logPaused": "WSTRZYMANO", - "logResume": "Wznów", - "logPin": "Przypnij", - "logUndock": "Otwórz modalnie", - "logColumns": "Kolumny", - "logColumnsRequired": "Co najmniej jedna kolumna Live Logs musi pozostać widoczna.", - "logLoadHistory": "Zwiększ bufor", - "logHistoryLoaded": "Bufor live zwiększono do {count} zachowanych wpisów.", - "logHost": "Host", - "logBufferHint": "Widok na żywo zaczyna od ostatnich 256 zakończonych wymian. Zwiększenie bufora wczytuje zachowaną historię i dalej dopisuje logi na żywo do 4096 wpisów, aż do zamknięcia okna. Eksport obejmuje aktualnie przefiltrowane wiersze.", - "settingsSecurityHint": "Konta i ACL są globalne. SSL, HSTS, nagłówki i cache konfigurujesz także wewnątrz vhosta.", - "theme": "Motyw", - "themeDark": "Ciemny", - "themeLight": "Jasny", - "density": "Gęstość interfejsu", - "densityCompact": "Kompaktowy", - "densityDense": "Gęsty", - "densityComfortable": "Przestronny", - "globalPages": "Domyślne odpowiedzi", - "pagesHelp": "Strony dla adresu IP, nieznanych hostów i błędów. Pusty status zachowuje kod odpowiedzi. Strony statyczne nie odpytują backendu.", - "pageInherit": "Domyślna / dziedziczona", - "pageWelcome": "Strona gratulacyjna", - "pageEmpty": "Pusta odpowiedź", - "pageHtml": "Własny HTML", - "pageStatus": "Kod odpowiedzi", - "pageOriginalStatus": "Oryginalny status", - "pageHtmlSource": "Kod HTML", - "pageUploadHtml": "Opcjonalny plik HTML (maks. 32 KiB)", - "pageTooLarge": "HTML nie może przekraczać 32 KiB w UTF-8.", - "customErrorPages": "Własne strony błędów", - "pageStaticHost": "Host statyczny / przerwa techniczna", - "pageMissingPath": "Niepasująca ścieżka", - "pageDirectIp": "Wejście bezpośrednio po IP", - "pageUnknownHost": "Nieznany host", - "pagesSafetyHint": "HTML to treść publiczna. ACL nadal działa przed odpowiedzią. Jawne HTTP 200 dla błędu ukrywa kod przed klientami i monitoringiem. Cała konfiguracja ma limit rozmiaru.", - "vhDetailsTab": "Szczegóły", - "vhSsl": "Certyfikaty SSL", - "vhCertificate": "Wybrany certyfikat", - "vhCertificateDefault": "Globalne SNI / certyfikat domyślny", - "vhCertificateFile": "Certyfikat / pełny łańcuch (PEM)", - "vhPrivateKeyFile": "Klucz prywatny (PEM)", - "vhUploadCertificate": "Wgraj i zweryfikuj parę", - "vhSelectCertificateFiles": "Wybierz plik certyfikatu i klucza prywatnego.", - "vhCertificateTooLarge": "Każdy plik PEM ma limit 128 KiB.", - "vhCertificateNeedsHosts": "Najpierw wpisz domeny vhosta w Szczegółach.", - "vhCertificateStaged": "Para zweryfikowana i zapisana. Zapisz vhost, aby aktywować certyfikat.", - "vhCertificateUploadHint": "Upload sprawdza zgodność klucza i domen. Klucz trafia do prywatnego pliku, nie do API. Zapis vhosta aktywuje wybór. Upload nie zapisuje pozostałych zmian. Zmiany listenerów wymagają restartu.", - "vhCompression": "Kompresja", - "vhCompressionHelp": "Kompresja i każdy kodek domyślnie dziedziczą ustawienia. Na tym poziomie można ograniczyć globalnie włączone kodeki, ale nie można włączyć kodeku wyłączonego w Ustawienia → Odpowiedź. Minimalny rozmiar pozostaje globalny.", - "compressionEnabled": "Kompresja dynamiczna", - "compressionZstd": "Zstandard (zstd)", - "compressionBr": "Brotli (br)", - "compressionGzip": "gzip", - "compressionDeflate": "deflate", - "vhCache": "Cache", - "vhCacheTtl": "Maks. TTL (sekundy, puste = dziedzicz)", - "vhCacheDefault": "Domyślny TTL public (puste = dziedzicz)", - "vhCacheObject": "Limit obiektu (KiB, puste = dziedzicz)", - "cacheEnabled": "Włącz cache", - "cacheBypassQuery": "Pomijaj adresy z parametrami", - "cacheStatusHeader": "Dodaj X-Cache", - "cacheHitsHeader": "Dodaj X-Cache-Hits", - "cacheRfcHeader": "Dodaj Cache-Status", - "cacheBypassPrefixes": "Pomijane prefiksy ścieżek", - "vhCacheCustomPrefixes": "Nadpisz odziedziczone pomijane ścieżki", - "vhCacheHint": "Polityka hosta może włączyć cache niezależnie od ustawienia globalnego. Vhosty współdzielą limit RAM i wpisów. Sticky cookie, dane prywatne i autoryzacja nadal omijają cache.", - "vhPurgeCache": "Wyczyść cache hosta", - "vhPurgeConfirm": "Wyczyścić cache tylko tego vhosta? Trwające zapisy tego hosta także zostaną unieważnione.", - "vhPages": "Strony błędów", - "vhHeadersOrder": "Kolejność: reguły globalne, vhost, ścieżka, następnie security/Server. Nadal dostępny jest zaawansowany JSON.", - "vhNewHealthHint": "Zapisz vhost, aby rozpocząć obserwację backendów.", - "listenerAdd": "Dodaj listener", - "listenerMigrate": "Zamień domyślne porty na nazwane listenery", - "listenerScopeHint": "Wewnętrzny / publiczny to etykieta, nie firewall. Nowe listenery nie udostępniają starych tras bez jawnego wyboru.", - "listenerLimit": "Maksymalnie 64 nazwane listenery.", - "listenerEndpoint": "Listener", - "listenerName": "Nazwa wyświetlana", - "listenerScope": "Etykieta", - "listenerInternal": "Wewnętrzny", - "listenerPublic": "Publiczny", - "listenerLegacy": "Udostępnij stare trasy", - "listenerInUse": "Listener jest przypisany do vhostów", - "listenerDuplicate": "Identyfikatory listenerów muszą być unikalne.", - "listenerChooseHint": "Wybierz nazwane listenery skonfigurowane w Ustawienia > Listenery. Wyłączony listener nie daje dostępu.", - "listenerMissing": "Brakujący listener", - "http3Settings": "HTTP/3 / QUIC", - "http3SettingsHelp": "HTTP/3 włącza się w Ustawienia > Listenery. Tutaj ustawisz ogłaszanie usługi oraz limity połączeń i strumieni.", - "http3NoTlsListeners": "Brak listenerów TLS. Najpierw dodaj i zapisz listener TLS.", - "http3NoEnabledListeners": "Żaden listener TLS nie ma włączonego HTTP/3. Najpierw włącz HTTP/3 w Ustawienia > Listenery.", - "http3ListenersDirty": "Najpierw zapisz oczekujące zmiany w Listenerach, a następnie edytuj ustawienia HTTP/3.", - "http3Save": "Zapisz ustawienia HTTP/3", - "h2Configure": "Ustaw limity HTTP/2", - "h3Enable": "Włącz HTTP/3 przez QUIC (UDP)", - "h3Configure": "Ustaw limity HTTP/3", - "h3Limits": "Limity HTTP/3 i ogłaszanie usługi", - "h3_alt_svc_max_age": "Ważność Alt-Svc (s)", - "h3_max_concurrent_connections": "Połączenia na listener", - "h3_max_concurrent_bidistream": "Strumienie dwukierunkowe na połączenie", - "h3_max_concurrent_unistream": "Strumienie jednokierunkowe na połączenie", - "h3_max_idle_timeout": "Bezczynność QUIC (s; 0 = bez limitu)", - "h3Help": "HTTP/3 wymaga TLS. Udostępnij TCP i UDP na tym samym porcie; HTTP/1.1 i HTTP/2 pozostają. Zero wyłącza lokalny timeout QUIC. Zmiana wymaga restartu.", - "h3TuningHelp": "Te limity obowiązują tylko wtedy, gdy HTTP/3 jest włączone dla danego listenera TLS. Zmiana wymaga restartu.", - "h3Requests": "Żądania HTTP/3", - "certImport": "Importuj certyfikat", - "certImportSave": "Importuj i zapisz w bibliotece", - "certLibraryHint": "Wgraj certyfikat/fullchain i klucz prywatny PEM. Zapisz raz, następnie przypisz certyfikat do jednego lub wielu vhostów.", - "certCheckDomains": "Domeny do sprawdzenia (po przecinku)", - "certAcmeLater": "Wydane certyfikaty trafiaja do biblioteki i sa przypisywane do vhostow.", - "certAdvanced": "Istniejące pliki / zaawansowane", - "certLegacyNames": "Stare nazwy SNI (opcjonalne; zalecane przypisanie w vhoście)", - "certAssigned": "Przypisane vhosty", - "certUnassigned": "Nieprzypisany", - "certInUse": "Certyfikat jest używany. Zmień przypisania vhostów i certyfikat domyślny przed usunięciem.", - "certDuplicate": "Ten identyfikator certyfikatu istnieje. Do bezpiecznej rotacji użyj nowego.", - "certManage": "Zarządzaj biblioteką certyfikatów", - "certSelectHint": "Wybierz zaimportowany certyfikat. Import i rotacja są w zakładce Certyfikaty. Przypisania są sprawdzane względem nazw vhosta.", - "vhTinyauth": "Uwierzytelnianie i dostęp IP", - "vhTabAuth": "Uwierzytelnianie", - "taEnable": "Włącz ochronę Tinyauth", - "taUrl": "URL Tinyauth / punkt weryfikacji", - "taLogin": "Publiczny URL logowania", - "taRedirectForbidden": "Akceptuj przekierowanie Tinyauth dla 403 (ten sam origin)", - "taConnect": "Limit połączenia (s)", - "taTimeout": "Łączny limit weryfikacji (s)", - "taRequestHeaders": "Nagłówki klienta do Tinyauth", - "taResponseHeaders": "Zweryfikowane nagłówki tożsamości do aplikacji", - "taHelp": "Do adresu serwera dodawane jest /api/auth/nginx. Weryfikacja bez body działa przed cache i WebSocket. Chronione odpowiedzi mają private, no-store. Ustaw w Tinyauth zaufanie do rzeczywistego IP RustProxy.", - "vhResponsePolicy": "Identyfikacja odpowiedzi i diagnostyka", - "vhInherit": "Dziedzicz ustawienia globalne", - "diagnosticHeaders": "Diagnostyczne nagłówki odpowiedzi", - "diagInherit": "Dziedzicz nagłówki diagnostyczne", - "diagRequestId": "Udostępnij X-Request-ID", - "hideMockOrigin": "Ukryj X-Mock-Origin", - "diagHelp": "X-Request-ID jest opcjonalny. Pozostale naglowki odpowiedzi upstreamu kontroluj zwyklymi regulami naglowkow odpowiedzi.", - "forwardingSettings": "Tożsamość klienta / forwarding", - "forwardingHelp": "Domyślnie nagłówki forwarding od klienta są usuwane. Dodaj tylko kontrolowane proxy. Łańcuch jest sprawdzany od prawej do pierwszego niezaufanego adresu.", - "forwardingCanonical": "RustProxy generuje Forwarded, X-Forwarded-For, X-Real-IP, X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port i X-Forwarded-URI. Adres docelowy połączenia nigdy nie pochodzi z nagłówka.", - "trustedProxies": "Zaufane sieci proxy CIDR (po jednej w wierszu; /32 dla IPv4, /128 dla IPv6)", - "trustForwardedProto": "Ufaj X-Forwarded-Proto tylko od zaufanych proxy", - "listenerId": "ID listenera", - "cache": "Cache", - "cacheTitle": "Cache pod kontrolą.", - "cacheDescription": "Obiekty, skuteczność domen i dokładne przyczyny decyzji.", - "cacheRefresh": "Odśwież", - "cacheLive": "Na żywo (3 s)", - "cachePurgeScope": "Wyczyść wybrany zakres", - "cacheObjectCount": "Obiekty w pamięci", - "cacheSaved": "Dane odpowiedzi z RAM", - "cacheCounterScope": "Liczniki domen od zmiany konfiguracji", - "cacheDomains": "Domeny i skuteczność", - "cacheScopeHint": "Limity RAM, zapisy i usunięcia są globalne. Liczniki domen zerują się przy zmianie konfiguracji; czyszczenie obiektów ich nie zeruje.", - "cacheDomain": "Domena", - "cacheReasons": "Liczniki decyzji", - "cacheNoDomains": "Brak decyzji cache. Wyślij żądania do skonfigurowanego hosta.", - "cacheObjectList": "Zapisane obiekty", - "cachePrevious": "Poprzednie", - "cacheNext": "Następne", - "cacheObjectHint": "Najpierw ostatnio używane. Kliknij Target albo Cache key / tags, aby skopiować pełną wartość. Treści i nagłówki żądań nie są ujawniane.", - "cachePath": "Ścieżka", - "cacheBodySize": "Treść", - "cacheAge": "Wiek", - "cacheTtlRemaining": "Pozostały TTL", - "cacheNoObjects": "Brak świeżych obiektów w tym zakresie. Sprawdź powyższe liczniki decyzji.", - "cacheLegacy": "Nieprzypisane", - "cacheGlobalLimit": "Limit globalny", - "cacheFills": "Aktywne zapisy (globalnie)", - "cacheRejected": "Odrzucone zapisy (globalnie)", - "cacheUpdated": "Aktualizacja", - "acmeTitle": "Let's Encrypt / ACME", - "acmeHelp": "Wydaj certyfikat ACME, dodaj go do biblioteki i przypisz do vhosta. Automatyczne odnawianie rozpocznie się po imporcie.", - "acmeEmail": "Email kontaktowy ACME", - "acmeDomains": "Nazwy DNS, rozdzielone przecinkami (wildcard tylko z DNS-01)", - "acmeEnvironment": "Środowisko CA", - "acmeStaging": "Staging - niezaufane przez przeglądarkę", - "acmeProduction": "Produkcja", - "acmeDnsServer": "IP serwera aktualizacji DNS", - "acmeDnsPort": "Port aktualizacji DNS", - "acmeKeyName": "Nazwa klucza TSIG", - "acmeSecret": "Sekret TSIG (base64)", - "acmeAlgorithm": "Algorytm TSIG", - "acmePropagation": "Oczekiwanie na propagację DNS (sekundy)", - "acmeSignQuery": "Podpisuj zapytanie SOA", - "acmeTos": "Akceptuje umowe subskrybenta Let's Encrypt i zezwalam na walidacje domen.", - "acmeConfirmProduction": "Potwierdzam wydanie produkcyjne (obowiązują limity CA).", - "acmeIssue": "Zamów certyfikat", - "acmeIdle": "Brak zadania wydania w tym procesie.", - "acmeImport": "Dodaj wydany certyfikat do biblioteki", - "acmeRenewalHint": "Pierwsze wydanie/import pozostaje ręczne. Profile zarządzanych certyfikatów są następnie sprawdzane automatycznie podczas działania RustProxy; po restarcie zapisany stan odnowień jest odtwarzany z SQLite.", - "acmeReplace": "Zastąpić certyfikat w bibliotece? Przypisania vhostów zostaną zachowane i ponownie sprawdzone.", - "acmeImported": "Certyfikat zapisany. Przypisz go w zakładce SSL vhosta.", - "certFuture": "Automatyczne odnawianie jest dostępne dla obsługiwanych dostawców ACME; obserwacje odnowień są zapisywane w SQLite.", - "cacheReason_cache_hit": "Użyto świeżego obiektu", - "cacheReason_cache_miss": "Brak pasującego świeżego obiektu", - "cacheReason_cache_disabled": "Cache wyłączony", - "cacheReason_route_disabled": "Cache wyłączony na trasie", - "cacheReason_request_cookie": "Cookie aplikacji", - "cacheReason_request_authorization": "Nagłówek Authorization", - "cacheReason_request_cache_control": "Dyrektywa klienta / twarde odświeżenie", - "cacheReason_request_pragma": "Pragma klienta", - "cacheReason_request_method": "Metoda inna niż GET", - "cacheReason_request_body": "Niepusta treść żądania", - "cacheReason_request_range": "Żądanie zakresu", - "cacheReason_request_conditional": "Żądanie warunkowe", - "cacheReason_request_upgrade": "Zmiana protokołu", - "cacheReason_query_excluded": "Wykluczone query", - "cacheReason_path_excluded": "Wykluczona ścieżka", - "cacheReason_request_cache_control_invalid": "Błędne dyrektywy cache klienta", - "cacheReason_response_status": "Odpowiedź inna niż 200", - "cacheReason_response_set_cookie": "Set-Cookie z backendu", - "cacheReason_response_cache_control": "Prywatność / dyrektywa backendu", - "cacheReason_response_cache_control_invalid": "Błędne dyrektywy backendu", - "cacheReason_response_vary": "Niedozwolony wariant Vary", - "cacheReason_response_range": "Odpowiedź częściowa", - "cacheReason_response_trailers": "Trailers odpowiedzi", - "cacheReason_response_ttl_size_or_metadata": "Odrzucony TTL, rozmiar lub metadane", - "cacheReason_response_headers_policy": "Reguła nagłówków blokuje zapis", - "cacheReason_configuration_changed": "Zmieniona konfiguracja", - "cacheReason_not_proxy_response": "Strona lokalna / odrzucone żądanie", - "cacheReason_tinyauth": "Treść chroniona Tinyauth", - "cacheReason_sticky_cookie": "Tryb cookie sticky session", - "acme.working": "Trwa zamawianie certyfikatu.", - "acme.ready_to_import": "Certyfikat sprawdzony; gotowy do dodania do biblioteki.", - "acme.certbot_not_installed": "Brak Certbota w PATH usługi. Zainstaluj Certbota i certbot-dns-rfc2136.", - "acme.certbot_failed_check_private_log": "Błąd Certbota. Sprawdź prywatne logi ACME; dokładna ścieżka jest pokazana przy błędzie operacji.", - "acme.production_confirmation": "Wymagane potwierdzenie wydania produkcyjnego.", - "acme.consent_required": "Zaakceptuj umowę i potwierdź wybrane środowisko CA.", - "acme.provider_timeout": "Przekroczono czas operacji CA. Przed ponowieniem sprawdź logi Certbota wskazane przy błędzie operacji.", - "acme.invalid_key_secret": "Nieprawidłowy sekret TSIG: wymagany base64 kodujący 16-512 bajtów.", - "acme.job_busy": "Inne zadanie certyfikatu już trwa.", - "acme.cancelled": "Zadanie certyfikatu anulowane podczas zamykania.", - "acme.certificate_validation_failed": "Walidacja certyfikatu, klucza lub domen nie powiodła się. Nic nie aktywowano.", - "acme.invalid_request": "Nieprawidłowe żądanie ACME.", - "acme.unknown_field": "Żądanie zawiera nieobsługiwane pole.", - "acme.invalid_id": "Identyfikator certyfikatu: 1-64 litery, cyfry, myślnik lub podkreślenie.", - "acme.invalid_email": "Podaj prawidłowy adres e-mail kontaktu ACME.", - "acme.invalid_domains": "Podaj od 1 do 16 nazw DNS.", - "acme.invalid_domain": "Podaj prawidłową nazwę DNS (ASCII/punycode).", - "acme.dns_names_only": "Certyfikat wymaga nazw DNS, nie adresów IP.", - "acme.invalid_environment": "Wybierz staging albo produkcję.", - "acme.tos_required": "ACME: tos required.", - "acme.invalid_dns_server": "Podaj adres IPv4 lub IPv6 serwera aktualizacji DNS.", - "acme.invalid_limits": "Port musi mieścić się w 1-65535, a czas propagacji w 10-900 s.", - "acme.invalid_key_algorithm": "Dozwolone algorytmy TSIG: HMAC-SHA256, HMAC-SHA384 i HMAC-SHA512.", - "acme.invalid_sign_query": "Opcja podpisywania SOA musi mieć wartość true albo false.", - "acme.unsafe_directory": "Katalog certyfikatów musi należeć do usługi i mieć prawa 0700; bez dowiązań.", - "acme.unsafe_output_path": "Plik wyniku wykracza poza prywatne archiwum Certbota.", - "acme.invalid_pem_size": "Plik PEM jest za duży lub nie jest zwykłym plikiem.", - "acme.invalid_pem": "Certbot nie zwrócił prawidłowego pliku PEM.", - "acme.profile_limit": "Osiągnięto limit 128 profili ACME. Sprawdź nieużywane profile lokalnie.", - "acme.profile_busy": "Ten profil ACME jest już używany przez inny proces.", - "acme.staging_limit": "Osiągnięto limit 128 par ACME. Usuń lokalnie wyłącznie nieużywane pliki.", - "acme.request_too_large": "Żądanie ACME przekracza 16 KiB.", - "acme.local_io_or_json_error": "Błąd lokalnego pliku lub formatu JSON. Sprawdź katalog i uprawnienia.", - "acme.no_certificate_directory": "Brak katalogu certyfikatów w konfiguracji uruchomienia.", - "acme.python_not_available": "Proces usługi nie może uruchomić python3. Sprawdź PATH i uprawnienia.", - "acme.worker_io": "Nie udała się komunikacja z lokalnym procesem ACME.", - "acme.invalid_worker_response": "Proces ACME zwrócił nieprawidłowy wynik.", - "acme.worker_failed": "Proces ACME zakończył się błędem.", - "acme.validation_worker_failed": "Nie udało się zakończyć walidacji pary certyfikatu i klucza.", - "cacheResidentLabel": "Pamięć cache", - "certificateFuture": "Dostawcy ACME: HTTP-01, Cloudflare, OVH i RFC2136. Odnawianie jest ręczne.", - "prometheusTitle": "Prometheus", - "prometheusHelp": "Osobny endpoint metryk, niezależny od sesji administratora. Domyślnie wyłączony.", - "prometheusEnable": "Włącz endpoint Prometheusa", - "prometheusListen": "IP i port eksportera", - "prometheusPath": "Ścieżka metryk", - "prometheusCidrs": "Dozwolone adresy kolektora (CIDR, po jednym w wierszu)", - "prometheusTokenEnv": "Nazwa zmiennej środowiskowej z tokenem", - "prometheusTokenHelp": "Podaj nazwę zmiennej, nie sekret. Token musi mieć co najmniej 32 znaki. Zmiana adresu lub tokenu wymaga restartu.", - "prometheusCollectors": "Grupy metryk", - "prometheusPreview": "Podgląd metryk", - "prometheusPreviewFailed": "Nie udało się pobrać metryk.", - "prometheusActive": "Aktywny endpoint", - "prometheusDisabled": "Endpoint metryk jest wyłączony.", - "certificateProvider": "Dostawca certyfikatu", - "providerHttp01": "Let's Encrypt - walidacja domeny (HTTP-01)", - "providerCloudflare": "Let's Encrypt - Cloudflare API (DNS-01)", - "providerOvh": "Let's Encrypt - OVH API (DNS-01)", - "providerRfc2136": "Let's Encrypt - RFC2136 / TSIG (DNS-01)", - "providerImport": "Import certyfikatu i klucza prywatnego (PEM)", - "providerExisting": "Istniejące pliki na serwerze / ścieżki zewnętrzne", - "providerHint_http01": "RustProxy uruchamia Certbota w trybie HTTP-01. Nie podajesz tutaj ścieżek do certyfikatu. Każda domena musi wskazywać na ten serwer, a publiczny TCP/80 musi trafiać do listenera HTTP. Wydana para trafia do magazynu certyfikatów RustProxy. Wildcard wymaga DNS-01.", - "providerHint_cloudflare": "RustProxy uruchamia Certbota DNS-01 z pluginem Cloudflare. Nie podajesz tutaj ścieżek do certyfikatu. Użyj tokenu Zone:DNS:Edit ograniczonego do wymaganych stref; wydana para trafia do magazynu RustProxy i może być odnawiana automatycznie.", - "providerHint_ovh": "RustProxy uruchamia Certbota DNS-01 z pluginem OVH. Nie podajesz tutaj ścieżek do certyfikatu. Podaj poświadczenia ograniczone do wymaganych stref DNS; wydana para trafia do magazynu RustProxy i może być odnawiana automatycznie.", - "providerHint_rfc2136": "RustProxy uruchamia Certbota DNS-01 przez RFC2136/TSIG. Nie podajesz tutaj ścieżek do certyfikatu. Serwer DNS musi przyjmować podpisane aktualizacje rekordów walidacyjnych; wydana para trafia do magazynu certyfikatów RustProxy.", - "providerHint_import": "Wybierz z komputera certyfikat/fullchain i odpowiadający klucz prywatny PEM. RustProxy sprawdzi parę i skopiuje ją do /certs; lokalna ścieżka z przeglądarki nie jest zapisywana.", - "providerHint_existing": "Wskaż pliki, które już istnieją na serwerze RustProxy. Wybierz nazwy plików z /certs albo pełne ścieżki zewnętrzne, np. /etc/letsencrypt/live/example.com/fullchain.pem i privkey.pem. Pliki zewnętrzne nie są kopiowane do backupu RustProxy.", - "acmeCfToken": "Token API Cloudflare", - "acmeOvhEndpoint": "Region API OVH", - "acmeOvhApplicationKey": "OVH application key", - "acmeOvhApplicationSecret": "OVH application secret", - "acmeOvhConsumerKey": "OVH consumer key", - "acme.invalid_provider": "Nieobsługiwany dostawca certyfikatu.", - "acme.invalid_api_credential": "Brak klucza API lub niedozwolone znaki w kluczu.", - "acme.invalid_ovh_endpoint": "Wybierz OVH Europa lub Kanada.", - "acme.http01_no_wildcard": "HTTP-01 nie obsługuje wildcardów. Wybierz dostawcę DNS-01.", - "acme.http01_listener_required": "HTTP-01 wymaga aktywnego listenera HTTP bez TLS.", - "cacheScopeLegacy": "Nieprzypisany / poprzedni zakres", - "certificateLibrary": "Certyfikaty", - "certificateWorkspaceHelp": "Zarządzaj zainstalowanymi certyfikatami, automatycznym odnawianiem i generowaniem/importem w osobnych widokach.", - "certificateTabInstalled": "Zainstalowane", - "certificateTabRenewal": "Odnowienie", - "certificateTabGenerate": "Generowanie", - "certificateInstalledTitle": "Zainstalowane certyfikaty", - "certificateInstalledHelp": "Lista certyfikatów dostępnych dla vhostów, redirectów, hostów 404 i panelu administracyjnego.", - "certificateGenerateTitle": "Generowanie i import", - "certificateGenerateHelp": "Wybierz metodę: Let's Encrypt/ACME, import plików PEM albo istniejące pliki z prywatnego katalogu RustProxy.", - "certificateOperationStatus": "Ostatnia operacja ACME", - "certificateNoRenewalOperation": "Brak operacji odnowienia w tym procesie.", - "certificateStateIdle": "Bezczynne", - "certificateStateRunning": "W toku", - "certificateStateReady": "Gotowe", - "certificateStateComplete": "Zakończone", - "certificateStateFailed": "Błąd", - "certbotLogs": "Logi Certbota", - "systemDiagnostics": "System / proces", - "vhWebsockets": "Obsługa WebSocket", - "vhLocations": "Własne lokalizacje", - "locationPath": "Prefiks lokalizacji", - "locationPool": "Backendy vhosta", - "locationMissing": "Brak backendu", - "locationLimit": "Host obsługuje do 64 lokalizacji.", - "locationAccess": "Dostęp do lokalizacji", - "locationHeaders": "Nagłówki lokalizacji", - "locationAddBackend": "Dodaj osobny backend", - "locationAdd": "Dodaj lokalizację", - "locationHelp": "Wygrywa najdłuższy pasujący prefiks. Ścieżka i query pozostają bez zmian. Wybierz backend hosta lub dodaj osobny. Uwierzytelnianie i ACL hosta obowiązują zawsze.", - "aclUse": "Włącz listę dostępu IP", - "aclAllow": "Zezwól", - "aclNetwork": "Sieć IP (CIDR)", - "aclAction": "Akcja", - "aclAdd": "Dodaj regułę IP", - "authMode": "Uwierzytelnianie", - "authNone": "Brak", - "authTinyauth": "Tinyauth", - "authBasic": "HTTP Basic Auth", - "basicRealm": "Realm (nazwa logowania)", - "basicRequireTls": "Wymagaj HTTPS dla Basic Auth", - "basicForward": "Przekaż zweryfikowany Authorization do backendu", - "basicPassword": "Hasło (12-128 bajtów)", - "basicKeepPassword": "Puste pole zachowuje hasło", - "basicHashSaved": "Hasło ustawione", - "basicHashRequired": "Ustaw hasło dla każdego użytkownika.", - "basicAddUser": "Dodaj użytkownika", - "basicHelp": "Zapisywane są wyłącznie skróty Argon2id. Dane logowania nie trafiają do backendu, chyba że włączysz przekazywanie. Używaj go tylko z zaufanym backendem HTTPS lub w izolowanej sieci. Chronione hosty omijają współdzielony cache.", - "basic.too_many_users": "Maksymalnie 64 użytkowników na hosta.", - "basic.invalid_username": "Nieprawidłowa nazwa użytkownika.", - "basic.duplicate_username": "Powtórzona nazwa użytkownika.", - "basic.unsupported_hash": "Użyj skrótu Argon2id wygenerowanego przez RustProxy.", - "basic.invalid_hash": "Nieprawidłowy skrót hasła.", - "basic.invalid_realm": "Nieprawidłowa nazwa realm.", - "basic.users_required": "Basic Auth wymaga przynajmniej jednego użytkownika.", - "basic.busy": "Weryfikacja haseł jest zajęta. Spróbuj ponownie.", - "renewalTitle": "Automatyczne odnawianie", - "renewalHelp": "Certyfikaty ACME są sprawdzane co około 12 godzin, gdy RustProxy działa. Certbot określa termin odnowienia. Poprawne certyfikaty są wdrażane bez restartu listenerów. Zachowaj dane dostawcy i dostępność portu 80 dla HTTP-01.", - "renewalNone": "W bibliotece nie ma zarządzanych certyfikatów ACME.", - "renewalEnabled": "Automatyczne odnawianie włączone", - "renewalDisabled": "Automatyczne odnawianie wyłączone", - "renewalEnable": "Włącz odnawianie", - "renewalDisable": "Wyłącz odnawianie", - "renewalNow": "Sprawdź teraz", - "renewalDryRun": "Sprawdź odnowienie (dry run)", - "renewalLast": "Ostatnia kontrola", - "renewalNext": "Kolejna kontrola", - "renewalExpires": "Ważny do", - "acme.renewing": "Sprawdzanie odnowienia certyfikatu", - "acme.renewed": "Certyfikat odnowiony i wdrożony", - "acme.not_due": "Certyfikat nie wymaga jeszcze odnowienia", - "acme.renewal_test_ok": "Weryfikacja odnowienia poprawna; aktywny certyfikat bez zmian", - "acme.renewal_profile_missing": "Brak dostępu do zapisanego profilu odnowienia. Przywróć prywatny profil ACME.", - "acme.configuration_changed": "Konfiguracja certyfikatu zmieniła się podczas odnowienia. Nie wdrożono zmian.", - "acme.deployment_failed": "Odnowiony certyfikat nie przeszedł walidacji konfiguracji. Poprzedni nadal jest aktywny.", - "acme.not_managed": "Automatycznie można odnawiać tylko zarządzane certyfikaty ACME.", - "acme.certificate_not_found": "Certyfikat już nie istnieje.", - "acme.renewal_disabled": "Automatyczne odnawianie jest wyłączone.", - "acme.openssl_not_installed": "OpenSSL jest wymagany do odczytu dat ważności certyfikatu.", - "cacheReason_basic_auth": "Uwierzytelnianie HTTP Basic", - "locationPoolEligible": "Uwzględnij w domyślnej puli backendów", - "locationId": "Identyfikator lokalizacji", - "vhIpAccess": "Dostęp do vhosta według IP", - "apiDocs": "Dokumentacja", - "auth.invalid_request": "Nieprawidłowe żądanie. Sprawdź podane pola.", - "locationProxyHeaders": "Nagłówki żądania proxy (do backendu)", - "locationResponseHeaders": "Nagłówki odpowiedzi (do klienta)", - "locationHeadersHelp": "Ustaw nadpisuje nagłówek, dopisz dodaje wartość, usuń kasuje go. Reguły lokalizacji są stosowane po globalnych i vhosta. Wartości są dosłowne (bez zmiennych nginx). Nagłówkami transportu i zweryfikowanej tożsamości zarządza system.", - "headersAdvancedJson": "Zaawansowane reguły nagłówków (JSON)", - "headersInvalid": "Nieprawidłowa reguła nagłówka. Sprawdź nazwę, operację i wartość.", - "headersProtected": "Nie można tu nadpisać nagłówków transportu i tożsamości proxy. Użyj ustawień forwardingu, autentykacji lub odpowiedzi.", - "docsTitle": "Dokumentacja", - "docsSession": "Korzysta z sesji administratora. Operacje zapisu API wymagają ochrony CSRF.", - "docsSignIn": "Zaloguj się, aby otworzyć dokumentację", - "docsPanel": "Powrót do panelu", - "docsWrites": "Włącz operacje zapisu", - "docsWriteConfirm": "Ta operacja naprawdę zmieni RustProxy. Kontynuować?", - "docsExpired": "Sesja wygasła. Zaloguj się ponownie.", - "docsError": "Nie udało się wczytać dokumentacji.", - "docsAccount": "Zalogowano jako", - "docsReadOnly": "Domyślnie wykonywane są tylko odczyty. Strumienie SSE oraz logowanie i setup nie są tu uruchamiane.", - "docsLogout": "Wyloguj", - "authPrivateNote": "Dostęp tylko dla uprawnionych administratorów.", - "advanced": "Zaawansowane", - "docsLicenses": "Licencje Swagger UI", - "vhTrafficPolicy": "Polityka ruchu", - "vhHttp3Enabled": "Włącz HTTP/3 dla tego vhosta", - "vhCloudflareClientIp": "Używaj zweryfikowanego IP klienta z Cloudflare", - "vhTimingHeaders": "Udostępniaj diagnostykę Server-Timing", - "vhRateEnabled": "Włącz limit żądań per IP klienta", - "vhRateRequests": "Żądania na okno", - "vhRateWindow": "Okno (sekundy)", - "vhRateBurst": "Dopuszczalny burst", - "vhCountryMode": "Polityka dostępu krajów", - "vhCountryBlock": "Blokuj wskazane kraje", - "vhCountryAllow": "Zezwalaj tylko wskazanym krajom", - "vhCountryList": "Kraje (ISO 3166-1 alpha-2)", - "vhCountryPolicyHelp": "Reguły krajów korzystają z bazy GeoIP. W trybie „tylko zezwolone” dostęp daje wskazany kraj LUB jawnie dozwolone IP. W trybie blokowania ruch odrzuca wskazany kraj LUB jawnie zablokowane IP. Nieustalony kraj nie spełnia gałęzi kraju.", - "vhCountry": "Kraj", - "vhAddCountryRate": "Dodaj limit dla kraju", - "vhCountryRateLimit": "Dozwolone są maksymalnie 64 limity przypisane do krajów.", - "vhRateHelp": "Limit dla kraju nadpisuje podstawowy limit per IP. Przekroczenie daje 429, a zabroniony kraj 403.", - "vhHealthOffWarning": "Aktywne sondy backendu są wyłączone. Passive Backend Health jest konfigurowany osobno i pozostaje wyłączony, dopóki nie zostanie świadomie włączony.", - "vhFrontendSelectionOnly": "Wybierz listenery dla vhosta. Adresy IP, porty oraz dostępność H2/H3 konfiguruje się wyłącznie w Ustawienia > Listenery.", - "vhForceSsl": "Wymuś SSL (przekieruj HTTP do HTTPS)", - "vhForceSslHelp": "Żądania HTTP tego vhosta dostają przekierowanie 308 do HTTPS z zachowaniem ścieżki i parametrów. Wybierz co najmniej jeden listener zwykłego HTTP oraz co najmniej jeden listener TLS. Jeśli wybrano kilka portów TLS, używany jest port 443; w innym przypadku port docelowy TLS musi być jednoznaczny.", - "vhForceSslNeedsTls": "Wymuszenie SSL wymaga co najmniej jednego włączonego listenera TLS dla tego vhosta.", - "vhForceSslPortConflict": "Nie można jednoznacznie wybrać portu docelowego HTTPS: wybrane listenery TLS używają kilku niestandardowych portów. Wybierz jeden port TLS albo listener na porcie 443.", - "vhForwardClientIp": "Przekazuj IP klienta do backendu", - "vhForwardClientIpHelp": "Pozostaw włączone dla standardowego reverse proxy. Wyłącz, aby nie wysyłać do aplikacji nagłówków Forwarded, X-Forwarded-For i X-Real-IP; RustProxy, ACL, limity i Tinyauth nadal korzystają z prawdziwego IP klienta.", - "vhClientIpHidden": "IP klienta ukryte przed backendem", - "listenerMigrateInline": "Istniejący listener vhosta (zostanie zmigrowany)", - "vhDebugOriginPreset": "Zastosuj preset zalecany", - "vhDebugOriginTlsWarning": "Ten preset pozostawia wyłączoną walidację certyfikatu TLS backendu. Połączenie jest szyfrowane, ale CA i nazwa hosta nie są sprawdzane. Kontynuować?", - "vhOriginPresetHelp": "Domyślne ustawienia nowego backendu do debugowania: origin HTTPS po IP, domyślna pula LB ON, prywatny origin ON, HTTP/1.1, zachowanie Host klienta ON i weryfikacja TLS OFF. Włącz weryfikację TLS, gdy certyfikat originu da się poprawnie zweryfikować.", - "vhDebugOriginMismatch": "Ten origin HTTPS po IP nie odpowiada presetowi debugowania (pula ON, prywatny origin ON, HTTP/1.1, zachowanie Host ON, walidacja TLS OFF). Jeśli włączysz walidację, ustaw nazwę serwera TLS na DNS z certyfikatu i w razie potrzeby dodaj jego prywatne CA. Kontynuować z bieżącymi wartościami?", - "vhCacheStaticCookie": "Ignoruj Cookie żądania dla wybranych rozszerzeń statycznych", - "vhCacheStaticCustom": "Użyj własnej listy rozszerzeń statycznych", - "vhCacheStaticExtensions": "Rozszerzenia statyczne (po jednym w linii, bez kropki)", - "vhCacheStaticHelp": "Dziedzicz ustawienie globalne albo nadpisz je dla vhosta. Ignorowanie Cookie stosuj tylko do plików, których treść nie zależy od sesji; Set-Cookie, private/no-store i Vary: Cookie nadal blokują zapis do shared cache.", - "cacheStaticTitle": "Cache statycznych zasobów", - "cacheStaticCookie": "Ignoruj Cookie żądania dla statycznych plików", - "cacheStaticExtensions": "Globalne rozszerzenia statyczne (po jednym w linii, bez kropki)", - "cacheStaticHelp": "Włączaj tylko dla plików, których treść nie zależy od sesji. Cookie żądania nie wpływa wtedy na klucz cache dla podanych rozszerzeń; odpowiedzi z Set-Cookie, private/no-store lub Vary: Cookie nadal nie są zapisywane we współdzielonym cache.", - "vhHttp2Enabled": "Włącz HTTP/2 dla tego vhosta", - "vhFrontendProtocolsHelp": "H2 i H3 są przełącznikami per vhost. Opcja jest aktywna tylko wtedy, gdy co najmniej jeden wybrany listener ma dany protokół włączony w Ustawienia > Listenery.", - "administration": "Panel administracyjny", - "administrationActiveHttp": "Aktywny panel HTTP", - "administrationActiveHttps": "Aktywny panel HTTPS", - "administrationCertificate": "Certyfikat", - "administrationGeneratedCertificate": "Domyślny certyfikat wygenerowany przez aplikację", - "administrationHelp": "HTTPS pozwala wystawić panel administracyjny poza loopback. Zwykły HTTP na porcie HTTPS jest automatycznie przekierowywany. Zmiany listenera i certyfikatu działają po restarcie.", - "administrationHosts": "Dozwolone hosty DNS (opcjonalna ścisła lista)", - "administrationHttps": "Włącz HTTPS dla panelu administracyjnego", - "administrationListen": "Adres nasłuchu", - "cacheOverhead": "Narzut", - "cacheResidentAge": "X-Cache-Resident-Age", - "controlPlane": "PANEL STEROWANIA", - "logging": "Logowanie", - "loggingTitle": "Strukturalne logi dostępu", - "loggingTabOutputs": "Wyjścia", - "loggingTabFormat": "Format JSON", - "loggingTabErrors": "Log błędów", - "loggingDescription": "Zapisuj konfigurowalne logi dostępu JSON do pliku, Logstash lub Loki.", - "loggingHelp": "Podgląd logów na żywo pozostaje w pamięci. Zewnętrzne wyjścia używają ograniczonej kolejki w tle i nie blokują obsługi żądań.", - "loggingFile": "Plik JSON", - "loggingPath": "Ścieżka pliku", - "loggingAddress": "Adres TCP", - "loggingTokenEnv": "Zmienna środowiskowa z tokenem Bearer", - "loggingLabels": "Etykiety (obiekt JSON)", - "loggingFormat": "Konfigurowalny format JSON", - "loggingFormatHelp": "Mapuj nazwy pól wyjściowych na pola źródłowe. Puste mapowanie zapisuje pełny standardowy wpis.", - "loggingFields": "Mapowanie pól (obiekt JSON)", - "loggingStatic": "Pola stałe (obiekt JSON)", - "loggingJsonInvalid": "Pola konfiguracji logów muszą zawierać poprawne obiekty JSON.", - "loggingQueue": "Kolejka wyjść", - "loggingDropped": "odrzuconych", - "loggingSent": "wysłanych", - "loggingCapacity": "pojemność", - "errorLogTitle": "Plik błędów", - "errorLogRoutingHelp": "Ostrzeżenia, błędy i paniki wątków trafiają do tego pliku, gdy jest włączony. Gdy wyłączony, trafiają do konsoli/systemd. W razie błędu zapisu używana jest konsola.", - "errorLogPath": "Ścieżka logu błędów", - "errorLogFormat": "Format", - "tls12": "TLS 1.2", - "tls13": "TLS 1.3", - "tlsPolicy": "Polityka TLS", - "tlsPolicyHelp": "To globalna polityka TLS dziedziczona przez każdy host TLS w aplikacji. Ustawienia per-vhost mogą ją tylko zawężać. HTTP/3 wymaga TLS 1.3.", - "tlsCiphers": "Szyfry (jeden na linię; puste = domyślne rustls)", - "tlsAvailableCiphers": "Dostępne szyfry", - "tlsPolicyVersionRequired": "Włącz co najmniej jedną wersję TLS.", - "vhTlsOverride": "Nadpisz globalną politykę TLS", - "vhTlsOverrideHelp": "Użyj tylko, gdy ten vhost wymaga innych wersji TLS lub szyfrów. Pusta lista szyfrów zachowuje domyślne rustls.", - "vhTlsHttp3Conflict": "HTTP/3 korzysta z globalnej polityki TLS. Wyłącz HTTP/3 dla tego vhosta przed użyciem innego nadpisania TLS.", - "vhListeners": "Listenery", - "vhProtocols": "Protokoły", - "loggingMode": "JSON / ASYNC / OGRANICZONA KOLEJKA", - "loggingPushUrl": "Adres Push URL", - "cacheHotObjects": "Obiekty hot", - "cacheHotMemory": "Pamięć hot", - "cacheColdObjects": "Obiekty cold", - "cacheColdDisk": "Dysk cold", - "cacheTierMoves": "Przeniesienia między warstwami", - "cacheColdHits": "trafień cold", - "cacheTier": "Warstwa", - "cacheStorage": "Pamięć / dysk", - "cacheColdTitle": "Trwały cache cold", - "cacheColdEnable": "Włącz trwałą warstwę dyskową", - "cacheColdHelp": "Wybierz, które obiekty z hot RAM mają trafiać na trwały dysk. Trafienie cold jest odczytywane asynchronicznie i promowane z powrotem do RAM. Metadane są w JSON, a body w plikach .body.", - "cacheColdPath": "Ścieżka cold (wewnątrz katalogu danych)", - "cacheColdBytes": "Limit dysku cold (MiB)", - "cacheColdEntries": "Limit obiektów cold", - "cacheColdAfter": "Przenieś po sekundach bezczynności", - "cacheHot": "Hot", - "cacheCold": "Cold", - "cacheReason_request_no_cache": "no-cache klienta / twarde odświeżenie wymaga rewalidacji", - "cacheReason_request_max_age": "max-age klienta wymaga rewalidacji", - "cacheReason_request_min_fresh": "min-fresh klienta wymaga rewalidacji", - "cacheReason_request_pragma_no_cache": "Pragma no-cache klienta / starszy tryb twardego odświeżenia", - "cacheReason_cache_revalidated": "Obiekt z cache potwierdzony przez backend", - "cacheReason_cache_conditional_hit": "Świeży obiekt obsłużył żądanie warunkowe", - "cacheReason_request_no_store": "no-store klienta omija cache", - "cacheReason_request_precondition": "Warunek klienta wymaga backendu", - "loggingPreset": "Preset formatu", - "loggingPresetCustom": "Własny", - "loggingPresetStandard": "Standard RustProxy", - "loggingPresetNginx": "JSON w stylu Nginx", - "loggingPresetEcs": "Nazwy pól w stylu ECS", - "loggingPresetMinimal": "Minimalny", - "loggingBuilder": "Graficzny konfigurator pól", - "loggingBuilderOpen": "Konfiguruj pola", - "loggingAdvancedJson": "Zaawansowane mapowanie JSON", - "loggingBuilderHelp": "Wybierz pola źródłowe i opcjonalnie zmień ich nazwy w JSON. Zastosowanie edytora graficznego normalizuje każde pole źródłowe do maksymalnie jednego klucza wyjściowego; duplikaty aliasów ustawisz w trybie zaawansowanym.", - "loggingSourceField": "Pole źródłowe", - "loggingOutputField": "Klucz JSON na wyjściu", - "loggingPreview": "Podgląd", - "loggingOutputRequired": "Każde włączone pole logu musi mieć klucz JSON na wyjściu.", - "loggingOutputDuplicate": "Klucze JSON na wyjściu muszą być unikalne.", - "aclProfiles": "Profile ACL", - "acl-profiles": "Profile ACL", - "acl-profilesTitle": "Wielokrotnego użytku profile ACL", - "acl-profilesDescription": "Definiuj wspólne reguły dostępu IP i krajów, a następnie przypinaj je do wielu vhostów.", - "aclProfilesIntro": "Profile dostępu można przypinać do wielu vhostów. Indywidualne reguły ACL i krajów w vhoście nadal działają i są egzekwowane razem z profilem.", - "aclProfilesReusable": "WSPÓLNA POLITYKA", - "aclProfileNew": "Nowy profil", - "aclProfileEdit": "Edycja profilu", - "aclProfileEditor": "Konfiguracja profilu", - "aclProfileId": "ID profilu", - "aclProfileName": "Nazwa wyświetlana", - "aclProfileIpEnabled": "Włącz ACL IP", - "aclProfileRules": "Reguły IP — po jednej „allow|deny CIDR” w wierszu", - "aclProfileCombineHelp": "Wyjątki krajów i IP są łączone operatorem LUB w obrębie tego profilu. Jawne blokady IP i zablokowane kraje wygrywają; profil, vhost i lokalizacja nadal muszą przejść wszystkie swoje polityki.", - "aclProfilePolicyMode": "Wybierz bazową politykę", - "aclProfilePolicyModeHelp": "Do typowych przypadków użyj prostego trybu zezwoleń albo blokad. Tryb niestandardowy daje pełną kontrolę nad mieszanymi regułami.", - "aclProfileModeAllowExceptions": "Domyślnie blokuj", - "aclProfileModeAllowExceptionsHelp": "Zezwalaj wskazanym krajom LUB wskazanym adresom/sieciom IP.", - "aclProfileModeBlockExceptions": "Domyślnie zezwalaj", - "aclProfileModeBlockExceptionsHelp": "Blokuj wskazane kraje LUB wskazane adresy/sieci IP.", - "aclProfileModeCustom": "Niestandardowa", - "aclProfileModeCustomHelp": "Ustawiaj ręcznie akcję domyślną, tryb krajów i każdą regułę IP.", - "aclProfileCountryExceptions": "Wyjątki krajów", - "aclProfileUseCountries": "Uwzględniaj kraje", - "aclProfileAllowedCountries": "Dozwolone kraje (kody ISO)", - "aclProfileBlockedCountries": "Blokowane kraje (kody ISO)", - "aclProfileAllowedCountriesHelp": "Ruch z dowolnego wskazanego kraju będzie dozwolony nawet wtedy, gdy jego IP nie znajduje się na liście.", - "aclProfileBlockedCountriesHelp": "Ruch z dowolnego wskazanego kraju będzie blokowany nawet wtedy, gdy jego IP byłoby inaczej dozwolone.", - "aclProfileCustomCountryHelp": "W trybie niestandardowym zachowanie krajów ustawiasz poniżej. Zablokowany kraj zawsze odmawia dostępu.", - "aclProfileIpExceptions": "Wyjątki IP / CIDR", - "aclProfileAddIpException": "Dodaj wyjątek IP", - "aclProfileAllowedIpsHelp": "Dodaj adres IPv4/IPv6 albo CIDR. Te adresy są dozwolone dodatkowo, niezależnie od wybranych krajów.", - "aclProfileBlockedIpsHelp": "Dodaj adres IPv4/IPv6 albo CIDR. Te adresy są blokowane dodatkowo, niezależnie od wybranych krajów.", - "aclProfileCustomIpHelp": "Każda reguła IP może niezależnie zezwalać albo blokować. Zwykły adres IP bez maski jest akceptowany jako pojedynczy host.", - "aclProfileBulkExceptions": "Wklej wiele adresów IP / CIDR", - "aclProfilePreview": "Efekt reguły", - "aclProfilePreviewDefaultDeny": "DOMYŚLNIE: BLOKUJ", - "aclProfilePreviewDefaultAllow": "DOMYŚLNIE: ZEZWALAJ", - "aclProfilePreviewAllowedCountries": "ZEZWÓL KRAJ", - "aclProfilePreviewBlockedCountries": "BLOKUJ KRAJ", - "aclProfilePreviewAllowedIps": "ZEZWÓL IP", - "aclProfilePreviewBlockedIps": "BLOKUJ IP", - "aclProfilePreviewIpRules": "REGUŁY IP", - "aclProfilePreviewNoExceptions": "Brak skonfigurowanych wyjątków", - "aclProfilePreviewBlocksAll": "Ten profil blokuje teraz cały ruch. Dodaj kraj lub wyjątek IP, aby zezwolić na dostęp.", - "aclProfilePreviewAllowsAll": "Ten profil zezwala teraz na cały ruch. Dodaj kraj lub wyjątek IP, aby coś zablokować.", - "aclProfilePreviewAllowNote": "Dostęp jest przyznany, gdy pasuje dozwolony kraj LUB adres klienta pasuje do dozwolonego IP/CIDR.", - "aclProfilePreviewBlockNote": "Dostęp jest blokowany, gdy pasuje blokowany kraj LUB adres klienta pasuje do blokowanego IP/CIDR.", - "aclProfilePreviewCustomNote": "Tryb niestandardowy używa priorytetów backendu: jawne deny IP i blokowany kraj odmawiają dostępu; dozwolony kraj i jawne allow IP mogą spełnić tę samą politykę.", - "aclProfileOr": "LUB", - "aclProfileCountriesShortAllow": "Dozwolone kraje", - "aclProfileCountriesShortBlock": "Blokowane kraje", - "aclProfileIpsShortAllow": "Dozwolone IP", - "aclProfileIpsShortBlock": "Blokowane IP", - "aclProfileIpRulesShort": "reguł IP", - "aclProfileIpOff": "ACL IP wyłączony", - "aclProfileCountriesOff": "Filtrowanie krajów wyłączone", - "aclProfileAttached": "Przypięty do", - "aclProfileInUse": "Profil jest przypięty do vhostów", - "aclProfilesEmpty": "Brak profili ACL wielokrotnego użytku.", - "aclProfileCountryInvalid": "Kody krajów muszą mieć dwuliterowy format ISO.", - "aclProfileCountryRequired": "Dodaj co najmniej jeden kraj albo wyłącz wyjątki krajów.", - "aclProfileDuplicate": "Profil ACL o tym ID już istnieje.", - "vhAclProfile": "Profil ACL", - "vhAclProfileNone": "Bez wspólnego profilu", - "vhAclProfileHelp": "Opcjonalny profil wielokrotnego użytku. Indywidualne ACL i reguły krajów vhosta poniżej nadal są dodatkowo egzekwowane.", - "vhHostAdd": "Dodaj hostname", - "vhHostsHelp": "Dodaj wiele domen (np. domena1.local i domena2.local), zatwierdzając Enterem lub przecinkiem. Wszystkie korzystają z tego samego vhosta i backendu. Wildcard tylko w postaci *.domena.pl.", - "vhHostInvalid": "Nieprawidłowy hostname lub wzorzec wildcard.", - "vhHostRequired": "Dodaj co najmniej jeden hostname.", - "vhLifetimeChart": "Przebieg ruchu od otwarcia panelu", - "vhReqPerSecond": "żąd./s", - "vhPeak": "max", - "listenerAddresses": "Adresy nasłuchu", - "listenerAddressesInvalid": "Podaj 1–16 unikalnych adresów nasłuchu.", - "listenerAddressesExplicit": "Przy wielu adresach wybierz konkretne IP; 0.0.0.0 i :: nie są dozwolone.", - "ruleSyntaxLine": "Nieprawidłowa reguła ACL w wierszu", - "vhForceSslNeedsHttp": "Wymuszenie SSL wymaga co najmniej jednego wybranego listenera zwykłego HTTP (bez TLS). Listener nazwany „HTTP” może mimo nazwy mieć włączony TLS w Ustawienia > Listenery.", - "aclProfileRuleBuilder": "Reguły IP", - "aclProfileBulkRules": "Wklej / importuj reguły", - "aclProfileBulkHelp": "Wklej po jednej regule „allow|deny CIDR” w wierszu. Niewidoczne odstępy Unicode są normalizowane, a błąd wskazuje dokładny wiersz.", - "aclProfileImportRules": "Importuj reguły", - "aclProfileRulesImported": "Reguły ACL zaimportowano do edytora graficznego.", - "streamHosts": "Hosty stream", - "streamsHelp": "Surowe przekazywanie TCP/UDP na osobnych portach. Ruch stream omija vhosty HTTP, ACL, cache, nagłówki i kompresję.", - "streamAdd": "Dodaj host stream", - "saveStreams": "Zapisz hosty stream", - "streamRestartHelp": "Zmiana listenera stream wymaga restartu RustProxy.", - "streamRestartPending": "Zapisano zmiany streamów. Uruchom ponownie RustProxy, aby je zastosować.", - "streamActive": "Aktualnie aktywne streamy", - "streamNone": "Brak aktywnych hostów stream", - "streamEndpoint": "Endpoint stream", - "streamId": "ID streamu", - "streamName": "Nazwa wyświetlana", - "streamProtocol": "Protokół", - "streamListen": "Adres nasłuchu", - "streamUpstream": "Adres upstream", - "streamConnectTimeout": "Timeout połączenia TCP (sekundy)", - "streamIdleTimeout": "Timeout bezczynności/sesji (sekundy)", - "streamMaxConnections": "Maks. połączeń/sesji", - "streamTcp": "TCP", - "streamUdp": "UDP", - "streamLimit": "Obsługiwane jest maksymalnie 128 hostów stream.", - "streamDuplicate": "ID hostów stream muszą być unikalne.", - "streams": "Streamy", - "streamsTitle": "Proxy TCP / UDP", - "streamsDescription": "Zarządzaj surowymi listenerami Layer 4, upstreamami i statystykami ruchu na żywo.", - "streamsPageIntro": "Streamy TCP/UDP są niezależnymi proxy Layer 4. Każdy stream ma własny listener i upstream oraz omija warstwę vhostów HTTP, ACL i cache.", - "streamLayer4": "LAYER 4 / TCP + UDP", - "streamList": "Streamy", - "streamSearch": "Szukaj streamów", - "streamSearchPlaceholder": "ID, nazwa, listener lub upstream…", - "streamSearchEmpty": "Brak streamów pasujących do wyszukiwania.", - "streamEmpty": "Brak streamów TCP/UDP. Dodaj listener, aby rozpocząć proxowanie ruchu Layer 4.", - "streamNew": "Nowy stream", - "streamAddListener": "Dodaj listener TCP/UDP", - "streamEditor": "Konfiguracja streamu", - "streamEditLabel": "EDYCJA", - "streamNewLabel": "NOWY", - "streamDetailsTab": "Szczegóły", - "streamNetworkTab": "Listener i upstream", - "streamLimitsTab": "Limity", - "streamNetwork": "Listener i upstream", - "streamLimits": "Limity połączeń / sesji", - "streamDedicatedListenerHelp": "Listener streamu jest tworzony razem ze streamem i jest niezależny od listenerów HTTP/HTTPS.", - "streamLimitsHelp": "Limity dotyczą równoległych połączeń TCP lub sesji UDP per klient. Timeout łączenia TCP nie jest używany przez UDP.", - "streamListenAdd": "Dodaj adres nasłuchu", - "streamListenHelp": "Wpisz jeden adres IP:port i naciśnij Enter. Zmiana listenera zacznie działać po restarcie RustProxy.", - "streamListenInvalid": "Adres listenera musi być pojedynczą wartością IP:port bez spacji i przecinków.", - "streamListenRequired": "Dodaj adres nasłuchu.", - "streamDelete": "Usunąć ten stream? Aktywny listener pozostanie do restartu RustProxy.", - "streamPendingRestart": "Oczekuje na restart", - "streamRunning": "Działa", - "streamActiveSessions": "Aktywne połączenia / sesje", - "streamSessionsTotal": "Połączenia / sesje", - "streamErrors": "Błędy", - "streamRejected": "Odrzucone", - "streamTransferred": "Przesłano", - "streamTrafficChart": "Ruch na żywo", - "streamClientToUpstream": "klient → upstream", - "streamUpstreamToClient": "upstream → klient", - "streamNoLiveMetrics": "Statystyki pojawią się, gdy stream będzie aktywny po restarcie.", - "streamIdleShort": "Timeout bezczynności", - "listenerAddressAdd": "Dodaj adres nasłuchu", - "listenerAddressesHelp": "Naciśnij Enter lub przecinek, aby dodać adres. Wiele adresów korzysta z tej samej polityki listenera; wildcard 0.0.0.0/:: nie może być mieszany z konkretnymi adresami.", - "cacheColdOnIdle": "Przenoś stare/nieużywane obiekty do cold storage", - "cacheColdOnLru": "Przenoś obiekty wyrzucane z hot LRU do cold storage", - "cacheColdOnShutdown": "Przy łagodnym restarcie zrzucaj hot cache do cold storage", - "certificateCovers": "Domeny certyfikatu", - "certificateCoverageUnknown": "Brak danych o domenach (DNS SAN). Wczytaj ponownie pliki certyfikatów lub zaimportuj certyfikat ponownie.", - "certificateDetails": "Szczegóły certyfikatu", - "certificateFiles": "Pliki certyfikatu / klucza", - "redirects": "Przekierowania", - "redirectsTitle": "Przekieruj świadomie.", - "redirectsDescription": "Lokalne hosty przekierowań z jawnym dopasowaniem, celem, kodem oraz obsługą ścieżki i query.", - "redirectsPageIntro": "Hosty przekierowań odpowiadają lokalnie, bez otwierania połączenia do backendu. Reguły mogą dopasować protokół i ścieżkę, a następnie przekierować do URL, innego hosta lub ścieżki.", - "redirectLayer": "PRZEKIEROWANIA HTTP", - "redirectList": "Hosty przekierowań", - "redirectSearch": "Szukaj przekierowań", - "redirectSearchPlaceholder": "ID, domena, cel…", - "redirectNew": "Nowe przekierowanie", - "redirectEmpty": "Brak skonfigurowanych hostów przekierowań.", - "redirectSearchEmpty": "Brak przekierowań pasujących do wyszukiwania.", - "redirectEditor": "Konfiguracja przekierowania", - "redirectId": "ID przekierowania", - "redirectName": "Nazwa", - "redirectHosts": "Domeny źródłowe", - "redirectFrontend": "FRONTEND", - "redirectListeners": "Listenery", - "redirectBehavior": "ZACHOWANIE", - "redirectRules": "Reguły", - "redirectAddRule": "Dodaj regułę", - "redirectRulesHelp": "Wygrywa najdłuższa pasująca ścieżka. 307/308 zachowują metodę żądania; 301/302/303 mogą ją zmienić po stronie klienta.", - "redirectSafetyHint": "Host przekierowania nie łączy się z backendem. Globalne ACL hostów nadal obowiązują. Unikaj reguł kierujących bezwarunkowo do samych siebie.", - "redirectRule": "Reguła", - "redirectRuleId": "ID reguły", - "redirectSourceScheme": "Protokół wejściowy", - "redirectSchemeAny": "HTTP lub HTTPS", - "redirectSchemeHttp": "HTTP", - "redirectSchemeHttps": "HTTPS", - "redirectSchemePreserve": "Zachowaj protokół wejściowy", - "redirectMatchType": "Dopasowanie ścieżki", - "redirectMatchPrefix": "Prefiks", - "redirectMatchExact": "Dokładnie", - "redirectSourcePath": "Ścieżka źródłowa", - "redirectTargetType": "Typ celu", - "redirectTargetUrl": "Pełny URL", - "redirectTargetHost": "Host / aplikacja", - "redirectTargetPath": "Ścieżka na tym samym hoście", - "redirectTarget": "Cel", - "redirectTargetScheme": "Protokół celu", - "redirectPathMode": "Obsługa ścieżki", - "redirectPathAppend": "Dopisz pełną ścieżkę źródłową", - "redirectPathStrip": "Usuń dopasowany prefiks i dopisz resztę", - "redirectPathReplace": "Użyj tylko skonfigurowanej ścieżki celu", - "redirectQueryMode": "Obsługa query", - "redirectQueryPreserve": "Zachowaj query źródłowe", - "redirectQueryDrop": "Usuń query źródłowe", - "redirectStatus": "Status HTTP", - "redirect301": "301 Moved Permanently", - "redirect302": "302 Found", - "redirect303": "303 See Other", - "redirect307": "307 Temporary Redirect (zachowaj metodę)", - "redirect308": "308 Permanent Redirect (zachowaj metodę)", - "redirectCacheSeconds": "max-age cache przeglądarki/proxy (sekundy; 0 = no-store)", - "redirectHostsRequired": "Dodaj co najmniej jedną domenę źródłową.", - "redirectListenersRequired": "Wybierz co najmniej jeden listener.", - "redirectRulesRequired": "Dodaj co najmniej jedną regułę przekierowania.", - "redirectDuplicate": "Przekierowanie o tym ID już istnieje.", - "redirectUnsaved": "Odrzucić niezapisane zmiany przekierowania?", - "redirectNewLabel": "NOWE", - "administrationSessionDays": "Czas ważności sesji administratora (dni)", - "administrationSessionDaysHelp": "Sesje przeglądarkowe mają bezwzględny czas ważności 7–30 dni. Zmiana działa od razu; zmiana hasła i „wyloguj wszystkie sesje” nadal natychmiast unieważniają sesje.", - "vhLocalListenerRisk": "Zezwól na lokalny listener RustProxy jako backend (ryzyko)", - "vhLocalListenerRiskHelp": "Ta opcja pojawia się, gdy URL backendu bezpośrednio wskazuje skonfigurowany lub aktywny listener administracyjny RustProxy. localhost, 127.0.0.1 i ::1 są traktowane jako równoważne adresy loopback na tym samym porcie panelu. Jej włączenie może wystawić panel lub utworzyć pętlę proxy; adres prywatny/loopback nadal wymaga opcji „Zezwól na prywatny IP backendu”.", - "vhLocalListenerRiskConfirm": "Ta opcja może kierować żądania do lokalnego listenera RustProxy, w tym portu administracyjnego, oraz utworzyć pętlę lub wystawić control plane. Akceptujesz to ryzyko?", - "cacheResidentHistory": "Historia zajętości RAM cache", - "networkHistory": "Historia RX / TX", - "responseLatencyHistory": "Historia czasu odpowiedzi", - "sourceIp": "IP źródłowe", - "cacheColdOnAge": "Przenoś obiekty do cold po maksymalnym czasie w hot RAM", - "cacheColdMaxHot": "Maksymalny czas w hot RAM (sekundy)", - "cacheColdPolicyHelp": "Czas bezczynności określa, jak długo obiekt nie był używany. Wiek hot określa ciągły czas przebywania w RAM nawet przy dalszych odczytach. Presja LRU i zrzut przy łagodnym zamknięciu są niezależnymi regułami.", - "documentation": "Dokumentacja", - "locationPathPattern": "Ścieżka / wzorzec regex", - "locationMatchType": "Sposób dopasowania", - "locationMatchPrefix": "Prefiks", - "locationMatchPriority": "Priorytetowy prefiks (^~)", - "locationMatchExact": "Dokładna ścieżka (=)", - "locationMatchRegex": "Regex (~)", - "locationMatchRegexI": "Regex bez rozróżniania wielkości liter (~*)", - "locationMatch_prefix": "prefiks", - "locationMatch_prefix_priority": "prefiks priorytetowy", - "locationMatch_exact": "dokładne", - "locationMatch_regex": "regex", - "locationMatch_regex_insensitive": "regex i", - "locationAction": "Akcja", - "locationProxyAction": "Proxy do backendu", - "locationReturnAction": "Własna odpowiedź (return)", - "locationProxyShort": "proxy", - "locationReturnShort": "odpowiedź lokalna", - "locationHandling": "Obsługa żądania", - "locationRewriteMode": "Przepisanie ścieżki do backendu", - "locationRewritePreserve": "Zachowaj oryginalną ścieżkę", - "locationRewriteStrip": "Usuń dopasowany prefiks", - "locationRewriteReplace": "Zastąp dopasowany prefiks", - "locationRewriteRegex": "Zamiana regex", - "locationRewriteValue": "Ścieżka / wyrażenie zastępujące", - "locationRewriteHelp": "Parametry query są zachowywane. Przepisywanie prefiksu działa na znormalizowanej ścieżce routingu. Zamiana regex obsługuje grupy Rust regex, np. $1. Wynik jest walidowany przed wysłaniem do backendu.", - "locationResponseStatus": "Kod odpowiedzi", - "locationResponseType": "Content-Type", - "locationResponseBody": "Treść odpowiedzi", - "headersVisualBuilder": "Graficzny konfigurator", - "locationDocsTitle": "Dokumentacja Custom locations", - "headersDocsTitle": "Dokumentacja reguł nagłówków", - "locationDocsIntro": "Custom locations są oceniane wewnątrz wybranego vhosta po autentykacji i ACL hosta. Nie mogą omijać zabezpieczeń vhosta.", - "locationDocsPrefix": "Prefiks: dopasowuje pełny prefiks segmentów ścieżki. Wygrywa najdłuższy prefiks. Priorytetowy prefiks działa jak nginx ^~ i blokuje pierwszeństwo regexów.", - "locationDocsExact": "Dokładne: pasuje wyłącznie do dokładnej znormalizowanej ścieżki, jak nginx location = /path.", - "locationDocsRegex": "Regex: po obsłudze exact/prioritetowego prefiksu wygrywa pierwszy pasujący regex. Dostępny jest tryb czuły i nieczuły na wielkość liter. Używana jest składnia Rust regex; look-around i backreference w samym wzorcu są celowo niedostępne.", - "locationDocsReturn": "Własna odpowiedź: RustProxy odpowiada lokalnie wybranym kodem HTTP, Content-Type i body bez otwierania połączenia do backendu. Reguły nagłówków odpowiedzi lokalizacji nadal obowiązują.", - "locationDocsRewrite": "Przepisanie ścieżki backendu: zachowanie oryginału, usunięcie/zamiana prefiksu albo zamiana z grupami regex. Oryginalny query string jest zachowany.", - "locationDocsAccess": "ACL IP lokalizacji, nadpisanie cache i reguły nagłówków są łączone z polityką vhosta. Lokalizacja nie może wyłączyć autentykacji ani ACL hosta.", - "locationDocsLimits": "Do 64 lokalizacji na vhost. Regexy i body odpowiedzi mają limity; nagłówki transportu, zweryfikowanej tożsamości i protokołu WebSocket pozostają zarządzane przez system.", - "headersDocsIntro": "Reguły nagłówków są stosowane kolejno: globalne → vhost → pasująca lokalizacja. Do zwykłej konfiguracji użyj graficznego konfiguratora; JSON pozostaje trybem zaawansowanym.", - "headersDocsSet": "Ustaw: zastępuje wszystkie istniejące wartości nagłówka skonfigurowaną wartością dosłowną.", - "headersDocsAppend": "Dopisz: dodaje kolejną wartość bez usuwania istniejących.", - "headersDocsRemove": "Usuń: kasuje nagłówek przed wysłaniem do backendu albo klienta.", - "headersDocsProtected": "Chronionych nagłówków transportu/tożsamości (Host, Connection, Content-Length, Forwarded/X-Forwarded-*, nagłówki dowodu WebSocket itd.) nie można tu nadpisywać.", - "headersDocsOrder": "Wartości są dosłowne; zmienne nginx nie są interpretowane. Reguły request działają przed nałożeniem kanonicznych danych forwardingu, a response przed końcową polityką bezpieczeństwa/diagnostyki RustProxy.", - "cacheColdPolicyTitle": "Polityka hot → cold", - "headerRule": "Reguła nagłówka", - "headerRulesEmpty": "Brak reguł. Dodaj regułę poniżej.", - "headerRuleHelp": "Wybierz Ustaw, Dodaj lub Usuń. Nagłówki transportowe, tożsamości forwardingu i protokołu WebSocket pozostają chronione.", - "locationPreviewTitle": "Podgląd efektywnego działania", - "locationPreviewMatch": "Dopasowanie", - "locationPreviewHandling": "Obsługa", - "locationPreviewRewrite": "Ścieżka do backendu", - "locationPreviewCache": "Cache", - "locationPreviewAccess": "Dostęp", - "locationPreviewHeaders": "Reguły nagłówków", - "locationPreviewDisabled": "Ta lokalizacja jest wyłączona i nie będzie dopasowywać żądań.", - "locationPreviewBackendSkipped": "backend nie jest wywoływany", - "locationPreviewVhostAcl": "Tylko autentykacja i ACL vhosta", - "locationPreviewCustomAcl": "Polityka vhosta + ACL lokalizacji", - "locationPreviewRequestRules": "request", - "locationPreviewResponseRules": "response", - "locationPreviewQueryPreserved": "query string jest zachowywany", - "locationDocsMatchHeading": "1. Dopasowanie i kolejność", - "locationDocsActionHeading": "2. Proxy, rewrite i odpowiedź lokalna", - "locationDocsPolicyHeading": "3. Dostęp, cache i nagłówki", - "locationDocsExamplesHeading": "Popularne przykłady", - "locationDocsExampleExact": "Endpoint health: Dokładna ścieżka (=) /health + Własna odpowiedź 200 odpowiada lokalnie bez łączenia z backendem.", - "locationDocsExampleStatic": "Pliki statyczne: Priorytetowy prefiks (^~) /static/ blokuje przejęcie przez regex; dla tej lokalizacji można osobno włączyć cache.", - "locationDocsExampleRegex": "Wersjonowane API: Regex (~) ^/api/v[0-9]+/ może wybrać backend i używać grup zamiany regex, np. $1.", - "cacheOutcomeHistory": "Aktywność HIT / MISS / BYPASS", - "cacheHotMemoryHistory": "Zajętość RAM HOT / limit", - "cacheTransientMemoryHistory": "Zajętość RAM przejściowego / limit", - "cacheColdStorageHistory": "Zajętość COLD / limit", - "processCpuHistory": "Historia CPU procesu", - "processRssHistory": "Historia RSS procesu", - "cacheMemoryBreakdown": "Cache hot / pamięć przejściowa", - "cacheTransientBreakdown": "Fill / kolejka cold / I/O cold", - "cacheColdStorageUsage": "Zajętość COLD / limit", - "cacheColdStorageObjects": "obiektów", - "cacheTransientMemory": "Pamięć przejściowa cache", - "cacheTransientShort": "RAZEM", - "cacheFillShort": "fill", - "cacheQueueShort": "kolejka cold", - "cacheIoShort": "I/O cold", - "processRssTotal": "RSS łącznie", - "memoryCacheHot": "RAM cache HOT / limit", - "memoryCacheTransient": "RAM przejściowy cache / limit", - "memoryTransform": "RAM zarezerwowany przez transformacje / budżet", - "memoryTransformInflight": "Aktywne transformacje / limit", - "memoryNonCacheRss": "RSS procesu poza cache", - "transformMemoryUsage": "Rezerwacja RAM transformacji", - "transformConcurrencyUsage": "Aktywne sloty transformacji", - "transformBudgetShort": "budżet", - "processRssAnon": "RSS anonimowy", - "processRssFile": "RSS plików / współdzielony", - "processDataSwap": "Obszar danych / swap", - "memoryAccountingHint": "RSS pokazuje rzeczywisty ślad pamięci procesu. HOT i pamięć przejściowa cache są księgowane przez RustProxy, a pamięć transformacji jest konserwatywną rezerwacją aktywnej pracy. RSS poza cache to RSS pomniejszony o księgowany cache i obejmuje m.in. transformacje, połączenia, TLS, logowanie, Tokio/runtime, narzut alokatora i inne bufory, więc nie wszystkie wiersze należy sumować.", - "memoryAccountingInfo": "Informacje o rozliczaniu pamięci", - "logTransport": "Transport", - "logWebSocket": "WebSocket", - "logUpstreamError": "Błąd upstreamu", - "listenerDefaultPortsHint": "Nowy vhost automatycznie wybiera standardowy listener HTTP :80, jeśli jest dostępny.", - "vhBackendDiagnostics": "Diagnostyka backendu", - "vhBackendHeader": "Udostępniaj wybrany backend (X-RustProxy-Backend)", - "vhBackendTimingHeader": "Udostępniaj czas odpowiedzi backendu (X-RustProxy-Backend-Time)", - "vhBackendDiagnosticsHelp": "Opcjonalne nagłówki odpowiedzi. Czas backendu oznacza czas od wysłania żądania do otrzymania nagłówków odpowiedzi; odpowiedzi obsłużone bez kontaktu z originem nie raportują backendu.", - "authModeHelp": "Wybierz jedną metodę uwierzytelniania. Lista dostępu IP poniżej działa niezależnie i może być używana razem z dowolną metodą.", - "taAdvanced": "Zaawansowane ustawienia Tinyauth", - "notfound": "Hosty 404", - "notfoundTitle": "Hosty 404 pod kontrolą.", - "notfoundDescription": "Rezerwuj domeny, które mają lokalnie zwracać kod, zaślepkę albo własny HTML.", - "notfoundPageIntro": "Hosty 404 rezerwują wybrane domeny i odpowiadają lokalnie bez łączenia z backendem. Mogą używać tych samych listenerów i certyfikatów co vhosty.", - "notfoundLayer": "ODPOWIEDŹ LOKALNA", - "notfoundList": "Hosty 404", - "notfoundSearch": "Szukaj hostów 404", - "notfoundSearchPlaceholder": "ID, nazwa lub domena…", - "notfoundNew": "Nowy host 404", - "notfoundEditor": "Konfiguracja hosta 404", - "notfoundId": "ID hosta", - "notfoundName": "Nazwa wyświetlana", - "notfoundFrontend": "FRONTEND", - "notfoundBehavior": "ODPOWIEDŹ", - "notfoundResponse": "Odpowiedź lokalna", - "notfoundMode": "Typ odpowiedzi", - "notfoundModeWelcome": "Zaślepka RustProxy", - "notfoundModeEmpty": "Tylko status / pusta", - "notfoundModeHtml": "Własny HTML", - "notfoundStatus": "Status HTTP", - "notfoundHtml": "Własny HTML", - "notfoundResponseHelp": "Host nigdy nie otwiera połączenia do backendu. Wbudowana zaślepka ma no-store i celowo nie dodaje opcjonalnych nagłówków diagnostycznych ani Server RustProxy.", - "notfoundHosts": "Domeny", - "notfoundNewLabel": "NOWY", - "notfoundHostsRequired": "Dodaj co najmniej jedną domenę.", - "notfoundListenersRequired": "Wybierz co najmniej jeden listener.", - "notfoundHtmlRequired": "Własny HTML nie może być pusty.", - "notfound204Empty": "Odpowiedzi HTTP 204, 205 i 304 muszą używać trybu Pusta.", - "notfoundDuplicate": "Host 404 o takim ID już istnieje.", - "notfoundUnsaved": "Odrzucić niezapisane zmiany hosta 404?", - "notfoundEmpty": "Brak skonfigurowanych hostów 404.", - "notfoundSearchEmpty": "Brak hostów 404 pasujących do wyszukiwania.", - "prometheusTrustedNoToken": "Zezwól zaufanym CIDR na odczyt bez tokenu", - "prometheusTrustedNoTokenHelp": "Klient nadal musi pasować do listy dozwolonych CIDR. Włączaj tylko w prywatnej, zaufanej sieci monitoringu; nagłówki przekazywane nie są używane do tej decyzji.", - "docker-vhosts": "Docker vhosty", - "docker-vhostsTitle": "Usługi Dockera pod nazwami.", - "docker-vhostsDescription": "Wykrywaj wystawione porty Dockera i mapuj uslugi na nazwy w domenie agenta, wlasne domeny oraz aliasy bez zapisywania obserwowanego stanu do SQLite.", - "dockerVhosts": "Docker vhosty", - "dockerVhostsIntro": "Wystawione porty TCP wykryte przez skonfigurowane agenty Dockera. Dla każdej usługi ustaw krótką nazwę w domenie agenta albo dowolny FQDN, dodaj aliasy, wybierz IP hosta Dockera oraz sprawdzaj i konfiguruj stan backendu na żywo.", - "dockerDiscovery": "WYKRYWANIE DOCKERA", - "dockerRefresh": "Odśwież agentów", - "dockerAgentsSettings": "Ustawienia agentów Dockera", - "dockerAgents": "Agenty Dockera", - "dockerDesiredState": "STAN ŻĄDANY / SQLITE", - "dockerAgentsHelp": "Skonfiguruj osobnego agenta dla każdego hosta Dockera. Domyślnie agent używa HTTPS ze stałym certyfikatem self-signed; wklej jego fingerprint SHA-256, aby przypiąć certyfikat. Każdy agent ma własną domenę bazową i opcjonalne domyślne IP backendu.", - "dockerAgentAdd": "Dodaj agenta Dockera", - "dockerAgentsSave": "Zapisz i połącz agentów", - "dockerAgent": "AGENT DOCKERA", - "dockerAgentNew": "Nowy agent", - "dockerAgentId": "ID agenta", - "dockerAgentName": "Nazwa wyświetlana", - "dockerAgentEndpoint": "Adres agenta", - "dockerAgentToken": "Token Bearer", - "dockerBaseDomain": "Domena bazowa", - "dockerTargetOverride": "Nadpisanie IP docelowego", - "dockerTargetAuto": "automatyczne", - "dockerListeners": "Listenery", - "dockerListenersRequired": "Wybierz co najmniej jeden listener dla agenta Docker.", - "dockerNoAgents": "Brak skonfigurowanych agentów Dockera.", - "dockerNoAgentsConfigured": "Najpierw skonfiguruj agenta Dockera w Ustawieniach.", - "dockerPublish": "Wystaw", - "dockerPreserveHost": "Zachowaj Host", - "dockerServiceMissing": "Brak w ostatnim odczycie", - "dockerConnected": "POŁĄCZONO", - "dockerError": "BŁĄD", - "dockerWaiting": "OCZEKIWANIE", - "dockerAgentDisabled": "Ten agent Dockera jest wyłączony.", - "dockerNoObservation": "Brak odczytu. Odśwież agentów.", - "dockerHost": "Host Dockera", - "dockerDetectedIp": "Wykryte IP", - "dockerEffectiveIp": "IP routingu", - "dockerNoPublishedPorts": "Agent nie zgłosił wystawionych portów TCP.", - "dockerAssignmentsSave": "Zapisz Docker vhosty", - "dockerDefaultTarget": "Domyslne IP backendu", - "dockerDetectedIps": "Wykryte adresy IP hosta", - "dockerHostname": "Nazwa hosta", - "dockerHostnameHelp": "Krotka nazwa uzyje {domain}; pelny FQDN uzyje wlasnej domeny.", - "dockerAliases": "Aliasy", - "dockerAliasAdd": "Dodaj alias", - "dockerAliasesHelp": "Dodawaj aliasy klawiszem Enter lub przecinkiem. Krótkie nazwy użyją domeny bazowej agenta, a pełne FQDN pozostaną bez zmian.", - "dockerBackendIp": "IP hosta backendu", - "dockerBackendIpAuto": "Auto / domyslne agenta", - "dockerBackendScheme": "Protokol backendu", - "dockerBindingIp": "binding Dockera", - "dockerAgentDefaultIp": "domyslne agenta", - "dockerAgentEndpointHelp": "Dla domyślnej konfiguracji agenta użyj https://. Zwykły HTTP pozostaje tylko dla zgodności i jest wyraźnie oznaczony jako niezabezpieczony.", - "dockerTlsFingerprint": "SHA-256 certyfikatu TLS", - "dockerTlsFingerprintHelp": "Dla domyślnego certyfikatu self-signed wklej fingerprint wypisany przez install.sh albo rustproxy-docker-agent --print-fingerprint. Zostaw puste wyłącznie dla certyfikatu zaufanego przez publiczne CA.", - "dockerTlsPinned": "TLS PRZYPIĘTY", - "dockerTlsPublicCa": "TLS / PUBLICZNE CA", - "dockerTlsPlain": "ZWYKŁY HTTP", - "dockerTlsPlainHelp": "Token agenta i dane inventory nie są chronione przez TLS na tym endpointcie.", - "dockerTlsPublicCaHelp": "Łańcuch certyfikatu i nazwa hosta są weryfikowane względem publicznych CA.", - "dockerAgentConnectionFailed": "Nie udało się połączyć z agentem", - "dockerAgentVersion": "Wersja agenta", - "dockerLastChecked": "Ostatnie sprawdzenie", - "dockerNeverChecked": "nigdy", - "dockerJustNow": "przed chwilą", - "dockerSecondsAgo": "{seconds} s temu", - "dockerMinutesAgo": "{minutes} min temu", - "dockerBackendMissing": "BRAK USŁUGI", - "dockerBackendNotActive": "NIEWYSTAWIONY", - "dockerBackendChecking": "SPRAWDZANIE", - "dockerBackendHealthOff": "HEALTH WYŁ.", - "dockerBackendUp": "UP", - "dockerBackendDown": "DOWN", - "dockerBackendDraining": "WYGASZANIE", - "dockerBackendNoMetrics": "Metryki runtime pojawią się po aktywacji dynamicznego backendu.", - "dockerHealthCheck": "Badanie backendu", - "dockerHealthSummary": "Aktywne testy TCP/HTTP; pasywne zdrowie jest konfigurowane osobno", - "dockerPassiveHealth": "Pasywne zdrowie backendu", - "dockerPassiveHealthSummary": "Domyślnie wyłączone; wymaga świadomego użycia zwykłego ruchu do decyzji health", - "dockerHealthMode": "Tryb", - "dockerHealthHttp": "HTTP", - "dockerHealthTcp": "TCP", - "dockerHealthOff": "Wyłączone", - "dockerHealthPath": "Ścieżka HTTP", - "dockerHealthHost": "Nagłówek Host health-checka", - "dockerHealthHostAuto": "host backendu / automatycznie", - "dockerHealthInterval": "Interwał (ms)", - "dockerHealthTimeout": "Timeout (ms)", - "dockerHealthSlow": "Próg wolnej odpowiedzi (ms)", - "dockerHealthFall": "Błędy do oznaczenia DOWN", - "dockerHealthRise": "Sukcesy do oznaczenia UP", - "dockerHealthStatusMin": "Min. status HTTP", - "dockerHealthStatusMax": "Maks. status HTTP", - "dockerHealthLatency": "Opóźnienie", - "dockerHealthResult": "Ostatni probe", - "dockerHealthRequests": "Żądania", - "dockerHealthErrors": "Błędy", - "dockerHealthActive": "Aktywne", - "loggingPerVhost": "Osobny plik per vhost", - "loggingDirectory": "Katalog logów per-vhost", - "loggingPerVhostHelp": "Po włączeniu każdy vhost zapisuje do własnego pliku JSONL. Ruch niedopasowany trafia do _unmatched.jsonl.", - "listenerStartupLocked": "Tego adresu ustawionego przy starcie procesu/systemd nie można zmienić ani usunąć. Pozostałe adresy i opcje listenera nadal można edytować.", - "administrationStartupLocked": "Ten adres jest ustawiony przy starcie procesu/systemd i nie można go tutaj zmienić.", - "notfoundStatusInvalid": "Wybierz obsługiwany status błędu HTTP.", - "tlsEffectiveCiphers": "Aktualnie używane szyfry", - "certificateManaged": "Zarządzany przez ACME", - "certManagedReadOnly": "Certyfikaty zarządzane przez ACME są odnawiane automatycznie i nie można ich edytować jak własnych certyfikatów z plików.", - "vhWebsocketTransport": "Transport WebSocket", - "vhWebsocketHttp1": "HTTP/1.1 Upgrade (zalecane)", - "vhWebsocketRfc9220": "RFC 9220 Extended CONNECT przez HTTP/3", - "vhWebsocketTransportHelp": "Włącz WebSocket osobnym przełącznikiem, a następnie wybierz transport. Tryby H2/H3 używają Extended CONNECT, a HTTP/1.1 Upgrade pozostaje fallbackiem.", - "dockerLiveView": "Widok live", - "dockerPollInterval": "Interwał odpytywania agenta (s)", - "dockerPollIntervalHelp": "Jak często RustProxy pobiera inventory Dockera z tego agenta. Widok live czyta tylko stan z pamięci RustProxy.", - "dockerAgentPollEvery": "Interwał agenta", - "dockerSearch": "Szukaj Docker vhostów", - "dockerSearchPlaceholder": "Szukaj usługi, hosta, aliasu lub agenta...", - "dockerShowHidden": "Pokaż ukryte", - "dockerHideHidden": "Ukryj ukryte", - "dockerHideVhost": "Ukryj z widoku", - "dockerUnhideVhost": "Pokaż w widoku", - "dockerCertificate": "Certyfikat frontendu", - "dockerCertificateHelp": "Certyfikat przypisany do wszystkich generowanych vhostów tego agenta. Domyślna opcja używa standardowego wyboru TLS/SNI.", - "dockerDefaultAcl": "Domyślny profil ACL", - "dockerDefaultAclNone": "Bez domyślnego profilu ACL", - "dockerDefaultAclHelp": "Dziedziczony przez generowane Docker vhosty, które nie mają wybranego własnego profilu ACL per aplikacja.", - "dockerApplyService": "Zastosuj zmiany", - "dockerAccessControl": "Kontrola dostępu", - "dockerAclSummary": "Opcjonalny profil ACL i reguły IP per aplikacja", - "dockerAclProfile": "Profil ACL", - "dockerAclProfileNone": "Bez profilu ACL", - "dockerAclProfileInherit": "Domyślny agenta", - "dockerAclInline": "Użyj dodatkowego ACL per aplikacja", - "dockerAclDefault": "Domyślna akcja ACL", - "dockerAclRules": "Reguły IP/CIDR", - "dockerAclHelp": "Jedna reguła w linii, np. allow 10.0.0.0/8. Profil i ACL per aplikacja są egzekwowane łącznie.", - "dockerProxyTimeouts": "Timeouty proxy", - "dockerProxyTimeoutsHelp": "Jawne limity per aplikacja: domyślnie połączenie 10 s, nagłówki odpowiedzi 120 s, odczyt 60 s i wysyłanie 60 s.", - "dockerHostnameRequired": "Przed wystawieniem wymagany jest hostname.", - "dockerForceSsl": "Przekieruj HTTP do HTTPS", - "dockerForceSslUnavailable": "Wymaga aktywnego listenera zwykłego HTTP oraz jednego jednoznacznego listenera TLS dla tego agenta.", - "dockerWebsockets": "WebSockety", - "dockerCompression": "Kompresja", - "dockerCompressionHelp": "Włącz lub wyłącz kompresję dla tego vhosta Docker. Dostępne kodeki i ich priorytet wynikają z globalnych ustawień Response.", - "dockerCache": "Cache", - "vhProxyTimeouts": "Timeouty proxy", - "vhProxyConnectTimeout": "Proxy connect timeout (s)", - "vhProxyHeaderTimeout": "Proxy response header timeout (s)", - "vhProxyReadTimeout": "Proxy read timeout (s)", - "vhProxySendTimeout": "Proxy send timeout (s)", - "vhProxyTimeoutsHelp": "Domyślnie: połączenie 10 s, nagłówki odpowiedzi 120 s, odczyt 60 s i wysyłanie 60 s. Read/send to timeout bezczynności podczas streamowania body.", - "notfoundModeBuiltin": "Zaślepka aplikacyjna dla statusu", - "pageBuiltin": "Wbudowana strona statusu", - "vhOpenBrowser": "Otwórz", - "securityPasswordHint": "Wymaga obecnego hasła i wyloguje wszystkie sesje.", - "securityAccessControl": "KONTROLA DOSTĘPU", - "securityPoliciesHint": "Lista polityk pozostaje widoczna; edytor otwieraj tylko przy dodawaniu lub zmianie reguł.", - "securityPolicyEditorHint": "Zakres hostów, domyślna akcja i reguły CIDR.", - "securityAuditOpen": "Audyt administracyjny", - "securityAuditHint": "Ostatnie operacje administracyjne są pobierane dopiero po otwarciu tego okna.", - "dockerConfigureService": "Konfiguruj vhost", - "dockerAliasesShort": "aliasów", - "dockerAgentDetails": "Szczegóły agenta", - "dockerServices": "Usługi", - "dockerOpenApp": "Otwórz aplikację", - "networkInterfacesHistory": "Ruch według interfejsów", - "networkInterfaceRate": "RX / TX", - "networkNoInterfaces": "Brak dostępnych interfejsów sieciowych innych niż loopback.", - "networkInterfacesCount": "{count} interfejsów", - "logExpandBuffer": "Zwiększ bufor", - "logExpandedBuffer": "Bufor 4096", - "logBufferExpanded": "Bufor live zwiększono do {count} zachowanych wpisów.", - "vhIdRenameHelp": "Zmiana ID vhosta tworzy nową tożsamość metryk/logów/cache i resetuje bieżącą historię health/traffic. Hostname’y i skonfigurowane ID backendów pozostają bez zmian.", - "vhIdRenameConfirm": "Zmienić ID vhosta z {old} na {new}? Bieżąca historia health/traffic oraz tożsamość metryk/cache tego vhosta zostaną rozpoczęte od nowa.", - "cacheDomainFilter": "Pokaż", - "cacheDomainFilterAll": "Wszystkie domeny", - "cacheDomainFilterEnabled": "Cache włączony", - "cacheDomainFilterDisabled": "Cache wyłączony", - "cacheNoDomainsFiltered": "Brak domen pasujących do tego filtra.", - "cacheColdCleanup": "Wyczyść cold cache", - "cacheColdCleanupHelp": "Operacja działa asynchronicznie i korzysta z tego samego mechanizmu eviction co automatyczny GC cold cache.", - "cacheColdUnusedSeconds": "Nieużywane co najmniej przez (sekundy, opcjonalnie)", - "cacheColdOldestCount": "Usuń maksymalnie najstarszych obiektów", - "cacheColdHitsZero": "Tylko obiekty z hits = 0", - "cacheColdCleanupStart": "Uruchom czyszczenie", - "cacheColdCleanupRunning": "Trwa czyszczenie cold cache…", - "cacheColdCleanupDone": "Usunięto {count} obiektów i zwolniono {bytes}.", - "cacheColdCleanupFailed": "Czyszczenie cold cache nie powiodło się.", - "cacheColdCleanupInvalidJob": "Czyszczenie cold cache nie zwróciło identyfikatora zadania.", - "cacheColdGcInterval": "Interwał GC cold cache (sekundy)", - "cacheColdGcMaxDelete": "Maksymalna liczba usunięć na przebieg GC", - "transformSettings": "Transformacja odpowiedzi statycznych", - "transformEnable": "Włącz globalnie silnik transformacji", - "transformHelp": "Transformacja odbywa się po dekodowaniu upstreamu, a przed zapisem do cache i kompresją dla klienta. Każdy vhost musi zostać włączony osobno.", - "transformJavascript": "Minifikacja JavaScript", - "transformCss": "Minifikacja CSS", - "transformHtml": "Minifikacja HTML", - "transformSvg": "Optymalizacja SVG", - "transformJson": "Kompaktowanie JSON", - "transformExtensions": "Dozwolone rozszerzenia plików, po jednym w linii", - "transformMaxBody": "Maksymalny rozmiar transformowanej odpowiedzi (KiB)", - "transformResourcePresetTitle": "Preset zasobów", - "transformResourcePresetHelp": "Presety ustawiają limit pamięci tymczasowej, maksymalny rozmiar transformowanej odpowiedzi i globalny limit równoległych transformacji. Typy treści i lista rozszerzeń pozostają bez zmian.", - "transformPresetConservative": "Oszczędny RAM", - "transformPresetRecommended": "Rekomendowany", - "transformPresetPerformance": "Wydajność", - "transformPresetUber": "Uber wydajność", - "transformMemoryBudget": "Budżet pamięci tymczasowej transformacji (MiB)", - "transformMaxInflight": "Maksymalna liczba równoległych transformacji", - "transformMaxInflightShort": "równolegle", - "transformResourceCurrent": "Aktualne limity", - "transformBudgetTooSmallWarning": "Budżet pamięci jest mniejszy niż pesymistyczny koszt buforów dla jednej maksymalnej skompresowanej odpowiedzi. Takie odpowiedzi mogą zostać pominięte z powodem memory_pressure.", - "transformBudgetHighWarning": "Limit pamięci transformacji przekracza 50% efektywnej pamięci RAM hosta/cgroup:", - "transformSafety": "Cache-Control: no-transform jest zawsze respektowane. Przetransformowana reprezentacja otrzymuje nowy ETag i jest zapisywana do cache przed gzip, Brotli lub zstd.", - "saveTransform": "Zastosuj ustawienia transformacji", - "vhTransform": "Transformacja", - "vhTransformEnable": "Włącz transformacje dla tego vhosta", - "vhTransformExtensionsCustom": "Nadpisz globalne rozszerzenia transformacji", - "vhTransformHelp": "Globalny silnik również musi być włączony. Ustawienia formatów dziedziczą politykę globalną, chyba że zostaną jawnie nadpisane.", - "dockerTransform": "Transformuj odpowiedzi statyczne", - "vhPassive5xxWarning": "Zliczanie HTTP 5xx może wyłączyć cały backend po serii błędów aplikacyjnych z pojedynczego endpointu.", - "vhPassiveSlowWarning": "Zliczanie wolnych odpowiedzi może wyłączyć backend po serii poprawnych odpowiedzi przekraczających próg slow-response.", - "transform": "Transformacja", - "transformTitle": "Transformacja odpowiedzi statycznych.", - "transformDescription": "Skuteczność transformacji, oszczędność, pominięcia i ponowne użycie z cache.", - "transformApplied": "Przetransformowane odpowiedzi", - "transformExamined": "Sprawdzone", - "transformCacheHits": "HIT-y transformowanych obiektów", - "transformCacheHitsHelp": "Obsłużone bez ponownego uruchamiania transformacji", - "transformSaved": "Bajty oszczędzone przed kompresją", - "transformReduction": "Średnia redukcja", - "transformReductionHelp": "Dla odpowiedzi, których reprezentacja faktycznie się zmieniła", - "transformSkipped": "Pominięte / fail-open", - "transformErrors": "Błędy body / transportu", - "transformAvgTime": "Średni czas", - "transformByType": "Transformacje według typu", - "transformType": "Typ", - "transformSkipReasons": "Powody pominięcia / fail-open", - "transformNoSkips": "Brak pominiętych transformacji w tym zakresie.", - "transformNoSkipsShort": "Brak pominięć", - "transformVhosts": "Skuteczność per vhost", - "transformScopeHint": "Liczniki są diagnostyką runtime w pamięci. HIT transformowanego obiektu z cache nie uruchamia transformera ponownie.", - "transformNoData": "Brak aktywności transformacji.", - "transformBadge": "transformowany", - "cacheLiveChartTitle": "Aktywność cache na żywo", - "cacheLiveChartHelp": "HIT / MISS / BYPASS na sekundę dla wybranego zakresu", - "liveChartSamples": "Próbki", - "transformLiveChartTitle": "Aktywność transformacji na żywo", - "transformLiveChartHelp": "Zastosowane / HIT cache / pominięte / błędy na sekundę dla wybranego zakresu", - "transformChartApplied": "Zastosowane", - "transformChartCacheHits": "HIT cache", - "transformChartSkipped": "Pominięte", - "cacheInvalidationTitle": "Purge / BAN cache", - "cacheInvalidationOperation": "Operacja", - "cacheInvalidationSelector": "Selektor", - "cacheInvalidationValue": "Wartość", - "cacheInvalidationHost": "Host (opcjonalnie)", - "cacheBanTtl": "TTL BAN (sekundy)", - "cacheInvalidationHelp": "Wybrany vhost ogranicza zakres operacji. Exact i prefix uwzględniają query string. Operacje regex i prefix / wymagają dodatkowego potwierdzenia.", - "cacheRunInvalidation": "Wykonaj operację", - "cacheActiveBans": "Aktywne reguły BAN", - "cacheBanExpires": "Wygasa", - "cacheNoBans": "Brak aktywnych reguł BAN.", - "cacheTarget": "Target", - "cacheKeyTags": "Klucz cache / tagi", - "cachePurgeUrl": "Purge URL", - "cacheRemoveBan": "Usuń BAN", - "cacheBanCreated": "Utworzono BAN", - "cacheConfirmPurgeUrl": "Usunąć ten URL z cache RAM i cold storage?", - "cacheConfirmRemoveBan": "Usunąć tę regułę BAN?", - "cacheWideInvalidationConfirm": "To szeroka operacja unieważniania i może objąć wiele obiektów cache. Kontynuować?", - "cacheCollapseWaiters": "oczekuje", - "cacheCollapsed": "scalone", - "actions": "Akcje", - "cacheCollapseTitle": "Scalanie żądań", - "cacheCollapseEnable": "Scalaj równoczesne fille dla jednego klucza cache", - "cacheCollapseTimeout": "Limit oczekiwania followera (ms)", - "cacheCollapseFallback": "Fallback po timeout", - "cacheCollapseFallbackOrigin": "Origin", - "cacheCollapseFallbackStale": "Stale, jeśli dostępny", - "cacheStaleErrorTitle": "Polityka stale-if-error", - "cacheSieTimeout": "Timeout", - "cacheSieConnection": "Błąd połączenia", - "cacheSieDns": "Błąd DNS", - "cacheSieTls": "Błąd TLS", - "cacheSieStatuses": "Statusy HTTP 5xx dopuszczające stale fallback", - "cacheProactiveTitle": "Proaktywne odświeżanie", - "cacheProactiveEnable": "Odświeżaj próbkę obiektów cache przed wygaśnięciem", - "cacheProactiveBefore": "Odśwież przed wygaśnięciem (sekundy)", - "cacheProactivePercent": "Próbkowane obiekty (%)", - "cacheProactiveInflight": "Maksymalna liczba równoległych odświeżeń", - "cacheProactiveHelp": "Używa rewalidacji validatorów, deterministycznego próbkowania i globalnego limitu współbieżności, aby zapobiegać lawinie odświeżeń.", - "cacheInvalidationOpen": "Purge / Ban", - "cacheBanListOpen": "Aktywne BAN-y", - "cacheBanRemoved": "Reguła BAN została usunięta", - "tunnelsOpen": "Aktywne tunele", - "tunnelsTitle": "Aktywne WebSockety / tunele", - "tunnelsReload": "Odśwież", - "tunnelsEmpty": "Brak aktywnych tuneli.", - "tunnelId": "ID", - "tunnelClient": "Klient", - "tunnelHost": "Host", - "tunnelUpstream": "Upstream", - "tunnelProtocol": "Protokół", - "tunnelDuration": "Czas", - "tunnelRx": "RX", - "tunnelTx": "TX", - "tunnelKill": "Zamknij", - "tunnelKilled": "Tunel zamknięty przez administratora", - "tunnelKillConfirm": "Zamknąć ten tunel?", - "tunnelKillReason": "Powód zamknięcia", - "tunnelKillReasonPlaceholder": "Zamknięcie administracyjne", - "tunnelKillReasonDefault": "panel administracyjny", - "listenerH2c": "h2c HTTP/2 bez TLS (prior knowledge + Upgrade z HTTP/1.1)", - "vhWebsocketRfc8441": "RFC 8441 Extended CONNECT przez HTTP/2", - "wsH1": "H1 Upgrade", - "wsH2Rfc8441": "H2 RFC 8441", - "wsH3Rfc9220": "H3 RFC 9220", - "wsEstablished": "Utworzone", - "httpProtocolMix": "Miks protokołów HTTP", - "wsProtocolMix": "Miks protokołów WebSocket", - "vhWebsocketMode": "Transport WebSocket", - "vhWebsocketDisabled": "Wyłączony", - "vhWebsocketH1": "HTTP/1.1 Upgrade", - "vhWebsocketH2Fallback": "HTTP/2 RFC 8441 + fallback H1", - "vhWebsocketH3Fallback": "HTTP/3 RFC 9220 + fallback H1", - "vhWebsocketH2H3Fallback": "HTTP/2 + HTTP/3 + fallback H1", - "vhMaxConcurrentRequests": "Maks. równoległych żądań", - "vhBackendQueueTimeout": "Timeout kolejki pojemności (ms)", - "vhBackendCapacityHelp": "0 równoległych żądań oznacza brak limitu. Timeout kolejki równy 0 natychmiast zwraca HTTP 503 po osiągnięciu limitu backendu.", - "vhLastFailure": "Ostatni błąd", - "vhLastRecovery": "Ostatni powrót", - "certificateIssuerName": "Wystawca certyfikatu", - "certificateValidFrom": "Ważny od", - "certificateDaysRemaining": "Pozostało dni", - "certificateExpired": "Certyfikat wygasł", - "certificateExpiresSoon": "Certyfikat wkrótce wygaśnie", - "certificateRenewalStatus": "Status odnawiania", - "certificateRenewalError": "Ostatni błąd odnawiania", - "vhActiveHealth": "Active health", - "vhPassiveFailures": "Błędy passive health", - "certificateAdminRestartRequired": "TLS panelu administracyjnego używa konfiguracji ładowanej przy starcie; uruchom ponownie RustProxy, aby odświeżyć ten certyfikat.", - "cacheTabGeneral": "Ogólne", - "cacheTabResilience": "Odporność", - "cacheTabCold": "Cold cache", - "cacheTabDiagnostics": "Statyczne i diagnostyka", - "certificateApiTitle": "API certyfikatów", - "certificateApiHelp": "Twórz osobne poświadczenia Key ID + Bearer Token dla automatyzacji certyfikatów. Nowe tokeny są domyślnie tylko do reload i są ograniczone do wskazanych ID certyfikatów. Włącz deploy tylko dla automatyzacji, która musi wysyłać materiał certyfikatu.", - "certificateApiKeyId": "Key ID", - "certificateApiToken": "Bearer token (opcjonalna własna wartość)", - "certificateApiTokenHelp": "Pozostaw pole puste, aby wygenerować silny token. Jawna wartość każdego tokenu jest pokazywana tylko raz; RustProxy zapisuje wyłącznie jego hash.", - "certificateApiRotate": "Generuj / obróć token", - "certificateApiRevoke": "Unieważnij token", - "certificateApiOneTimeToken": "Skopiuj ten token teraz", - "certificateApiCurlHelp": "Automatyzacja zewnętrzna wysyła Key ID i Bearer Token przez HTTPS (lub HTTP po loopback). Token reload musi używać /api/certificates/{id}/reload; token API nie może wywołać globalnego reload wszystkich certyfikatów.", - "certificateApiUnknown": "Ładowanie statusu API certyfikatów…", - "certificateApiConfigured": "API certyfikatów włączone", - "certificateApiDisabled": "API certyfikatów wyłączone", - "certificateApiLastUsed": "ostatnio użyty", - "certificateApiRotated": "Token API certyfikatów został utworzony / zmieniony. Skopiuj go teraz.", - "certificateApiRevoked": "Token API certyfikatów został unieważniony.", - "certificateApiRevokeConfirm": "Unieważnić token zewnętrznego API certyfikatów? Certbot i integracje API używające go natychmiast przestaną działać.", - "certificate_api.invalid_token": "Nieprawidłowy Key ID lub Bearer Token API certyfikatów.", - "certificate_api.https_required": "Zewnętrzny token API certyfikatów wymaga HTTPS, chyba że żądanie pochodzi z loopback.", - "certificate_api.invalid_token_format": "Token API certyfikatów musi mieć 32–128 bezpiecznych znaków URL.", - "certificate_api.invalid_key_id": "Nieprawidłowy Key ID API certyfikatów.", - "vhWebsocketLegacySupport": "Włącz zgodność ze starym WebSocket", - "vhWebsocketLegacySupportHelp": "Dla starszych urządzeń i serwerów embedded. Handshake WebSocket do upstreamu używa HTTP/1.1, Host i Origin są dopasowane do skonfigurowanego adresu backendu, a stary format odpowiedzi 101 jest tolerowany. Zwykły ruch HTTP pozostaje bez zmian.", - "vhWebsocketLegacyBadge": "WebSocket legacy", - "runtimeInformation": "Informacje środowiska", - "runtimeInformationHelp": "Wartości raportuje uruchomiony proces RustProxy. Są dostępne tylko w uwierzytelnionym API administratora.", - "runtimePlatform": "Platforma", - "statisticsSource": "Źródło statystyk", - "dataDirectory": "Katalog danych", - "databaseLocation": "Baza danych", - "connectionActivity": "AKTYWNOŚĆ POŁĄCZEŃ", - "reloadCertificate": "Przeładuj certyfikat", - "certificateApiComment": "Komentarz", - "certificateApiCertificateIds": "Dozwolone ID certyfikatów", - "certificateApiCertificateIdsHelp": "Podaj jedno lub więcej ID certyfikatów oddzielonych przecinkiem lub nową linią. Dla każdego innego ID token zostanie odrzucony.", - "certificateApiCertificateIdsRequired": "Podaj co najmniej jedno ID certyfikatu dla tego tokenu.", - "certificateApiAllowDeploy": "Zezwól także na deploy", - "certificateApiAllowDeployHelp": "Dla hooka odnowienia Certbot/acme.sh pozostaw wyłączone. Tryb tylko reload jest zalecanym wariantem least-privilege.", - "certificateApiScopeReloadOnly": "tylko reload", - "certificateApiScopeDeployReload": "deploy + reload", - "certificateApiAllowedIds": "dozwolone ID", - "certificateApiConfiguredCount": "Tokeny API certyfikatów: {count} / {max}", - "certificateApiNeverUsed": "nigdy nie użyty", - "certificateApiRevokeAll": "Unieważnij wszystkie tokeny", - "certificateApiRevokeOneConfirm": "Unieważnić token API certyfikatów {key}? Automatyzacja używająca tego tokenu natychmiast przestanie działać.", - "certificateApiRevokeAllConfirm": "Unieważnić wszystkie tokeny API certyfikatów? Cała automatyzacja zewnętrzna korzystająca z tych tokenów natychmiast przestanie działać.", - "certificate_api.invalid_comment": "Komentarz tokenu API certyfikatów może mieć maksymalnie 256 znaków i nie może zawierać znaków sterujących.", - "certificate_api.limit_reached": "Osiągnięto limit tokenów API certyfikatów.", - "certificate_api.not_found": "Nie znaleziono tokenu API certyfikatów.", - "certificate_api.invalid_certificate_ids": "Token API certyfikatów wymaga 1-32 poprawnych ID certyfikatów.", - "certificate_api.target_required": "Token API certyfikatów nie może przeładować wszystkich certyfikatów naraz; użyj endpointu reload dla konkretnego ID.", - "certificate_api.certificate_forbidden": "Ten token API certyfikatów nie ma dostępu do wskazanego ID certyfikatu.", - "certificate_api.deploy_forbidden": "Ten token API certyfikatów ma tylko uprawnienie reload. Włącz deploy przy rotacji wyłącznie, jeśli automatyzacja musi wysyłać certyfikat.", - "networkInterfacesVisibleCount": "{visible} / {total} interfejsów", - "hideMetric": "Ukryj", - "showMetric": "Pokaż", - "hiddenInterfaces": "Ukryte interfejsy", - "networkNoVisibleInterfaces": "Wszystkie wykryte interfejsy są ukryte w tej przeglądarce.", - "cacheMemorySizingTitle": "Dobór pamięci", - "cacheMemorySizingHelp": "Presety korzystają z efektywnej pamięci dostępnej dla RustProxy (RAM hosta ograniczony limitem cgroup, jeśli występuje). Ustawiają HOT, pamięć przejściową, rozmiar obiektu, liczbę wpisów i kolejkę demotion; limit dysku COLD pozostaje bez zmian.", - "cachePresetConservative": "Oszczędny", - "cachePresetRecommended": "Zalecany", - "cachePresetPerformance": "Wydajnościowy", - "cachePresetSelected": "Wybrany", - "cachePresetCustom": "Własny", - "cacheColdPresetTitle": "Preset polityki COLD", - "cacheColdPresetHelp": "Preset zalecany włącza przenoszenie po bezczynności/LRU/zamknięciu, dobiera kolejkę COLD do pamięci przejściowej i ustawia zachowawcze czasy GC. Ścieżka, limit dysku i limit obiektów pozostają bez zmian.", - "cacheTransientLimit": "Limit pamięci przejściowej (MiB)", - "cacheEvictionLimit": "Maks. usunięć LRU na przyjęcie obiektu", - "cacheColdQueueLimit": "Limit RAM kolejki COLD (MiB)", - "cacheMemoryNotDetected": "Nie wykryto pamięci", - "cacheMemoryPresetUnavailable": "Automatyczne presety wymagają telemetrii RAM hosta lub limitu cgroup.", - "cacheMemoryCgroup": "Efektywny RAM cgroup", - "cacheMemoryDetected": "Wykryty RAM", - "cacheQueueOverTransientWarning": "Limit kolejki COLD nie może przekraczać limitu pamięci przejściowej.", - "cacheMemoryHighWarning": "Ostrzeżenie: budżety HOT + pamięć przejściowa zajmują dużą część efektywnego RAM:", - "cachePresetTransientShort": "przejściowa", - "cachePresetObjectShort": "maks. obiekt", - "cachePresetQueueShort": "kolejka COLD", - "cacheLogicalShort": "logiczne", - "cacheLimitShort": "limit", - "cacheMemoryPressureShort": "presja pamięci", - "cacheNukeLimitShort": "limit LRU", - "cacheMemoryPresetTooSmall": "Automatyczne presety cache są wyłączone poniżej 256 MiB efektywnego RAM; jeśli to celowe, skonfiguruj cache ręcznie.", - "cacheMemoryRecommendedFor": "Zalecane dla wykrytej pamięci:", - "cacheMemoryHeadroomWarning": "Ostrzeżenie: dodatkowy budżet cache jest duży względem aktualnie dostępnej pamięci hosta/cgroup:", - "cacheObjectOverTransientWarning": "Maksymalny rozmiar obiektu przekracza limit pamięci przejściowej; obiekty zbliżone do tego rozmiaru przejdą bez zapisania w cache.", - "vhCacheGlobalDisabled": "Globalny cache jest wyłączony. Ten vhost nie może włączyć cache, dopóki cache globalny pozostaje wyłączony.", - "responseLatencyInfo": "Informacje o czasie odpowiedzi", - "responseLatencyHint": "Wartość to skumulowana średnia długość zakończonych wymian HTTP od startu procesu. Wykres pokazuje kolejne próbki tej średniej; nie jest to percentyl ani czas pojedynczego żądania.", - "web-server": "Serwer WWW", - "web-serverTitle": "Serwuj lokalne pliki.", - "web-serverDescription": "Twórz wielokrotnego użytku usługi WWW i podpinaj je do vhostów lub lokalizacji.", - "webServer": "Serwer WWW", - "webServerIntro": "Tutaj utwórz lokalne usługi WWW, a następnie podepnij je do vhosta lub lokalizacji.", - "webServiceList": "Usługi WWW", - "webServiceSearch": "Szukaj usług WWW", - "webServiceSearchPlaceholder": "Szukaj nazwy, ID lub document root", - "webServiceNew": "Nowa usługa WWW", - "webServiceEditor": "Konfiguracja usługi WWW", - "webServiceTabGeneral": "Ogólne", - "webServiceTabRouting": "Routing", - "webServiceTabMime": "MIME", - "webServiceTabCacheHeaders": "Cache i nagłówki", - "webServiceTabDelivery": "Serwowanie", - "webServiceTabPhp": "PHP-FPM", - "webServiceTabSecurity": "Bezpieczeństwo", - "webServiceId": "ID usługi", - "webServiceName": "Nazwa", - "webServiceRoot": "Document root", - "webServiceIndex": "Pliki index", - "webServiceDirectoryListing": "Listowanie katalogu", - "webServiceMimeOverrides": "Nadpisania MIME", - "webServiceMimeBuiltin": "Wbudowane mapowania", - "webServiceMimeHelp": "Opcjonalne nadpisania rozszerzenie → Content-Type, po jednym na linię. Format: ext = media/type.", - "webServiceMimeInvalid": "Nadpisania MIME muszą mieć unikalny format 'ext = media/type', po jednym wpisie na linię.", - "webServiceDefaultMime": "Domyślny typ MIME", - "webServiceCacheControl": "Cache-Control", - "webServiceCacheRules": "Reguły Cache-Control", - "webServiceCacheRulesHelp": "Bardziej szczegółowe reguły są sprawdzane przed polityką domyślną. Użyj ext:css albo mime:image/.", - "webServiceCacheRulesInvalid": "Reguły Cache-Control muszą mieć format dopasowanie = wartość, np. ext:css = public, max-age=3600.", - "webServiceResponseHeaders": "Nagłówki odpowiedzi", - "webServiceResponseHeadersHelp": "Stałe nagłówki tej usługi WWW. Reguły vhosta/lokalizacji są nakładane później i mogą je nadpisać.", - "webServiceResponseHeadersInvalid": "Nagłówki odpowiedzi muszą mieć unikalny format Nazwa-Nagłówka = wartość, po jednym wpisie na linię.", - "webServiceCacheHelp": "Gdy cache vhosta jest włączony, odpowiedzi serwera WWW spełniające warunki są buforowane również w RustProxy. Odpowiedzi prywatne i no-store nie są buforowane.", - "webServiceTryFiles": "try_files", - "webServiceTryFilesHelp": "Jeden kandydat na linię. Obsługuje $uri, $query_string oraz końcowy status błędu typu =404.", - "webServiceRewrites": "Reguły rewrite", - "webServiceRewritesHelp": "Jedna reguła regex na linię. Użyj wzorzec => /cel dla rewrite wewnętrznego albo wzorzec => 301:/cel dla przekierowania.", - "webServiceRewritesInvalid": "Reguły rewrite muszą mieć format wzorzec => /cel albo wzorzec => 301:/cel (301/302/307/308).", - "webServiceCompression": "Kompresja dynamiczna", - "webServiceCompressionHelp": "Domyślnie dziedziczy politykę vhosta/globalną. Tutaj można ograniczyć poszczególne kodeki; kodeki wyłączone globalnie pozostają niedostępne, a minimalny rozmiar pozostaje globalny.", - "webServicePrecompressed": "Serwuj gotowe pliki skompresowane", - "webServicePrecompressedBr": "Brotli (.br)", - "webServicePrecompressedGzip": "Gzip (.gz)", - "webServiceDownloadExtensions": "Rozszerzenia do pobierania", - "webServiceDownloadExtensionsHelp": "Pliki z tymi rozszerzeniami otrzymają Content-Disposition: attachment.", - "webServiceRangeHelp": "Statyczne i prekompresowane reprezentacje zachowują walidatory, pojedyncze i wieloczęściowe zakresy bajtów, odpowiedzi 206/416 oraz streaming.", - "webServicePhpEnabled": "Włącz PHP-FPM / FastCGI", - "webServicePhpEndpoint": "Endpoint PHP-FPM", - "webServicePhpEndpointHelp": "Unix: unix:/run/php/php-fpm.sock. TCP: tcp:127.0.0.1:9000 albo tcp:[::1]:9000. TCP jest celowo ograniczone do adresów loopback.", - "webServicePhpExtensions": "Rozszerzenia PHP", - "webServicePhpPassAuthorization": "Przekazuj nagłówek Authorization", - "webServicePhpConnectTimeout": "Timeout połączenia (ms)", - "webServicePhpRequestTimeout": "Łączny timeout żądania FastCGI (ms)", - "webServicePhpConcurrency": "Maks. równoległych żądań PHP", - "webServicePhpBodyLimit": "Limit body żądania PHP (bajty, 0 = bez limitu)", - "webServicePhpHeaderLimit": "Maks. bajtów nagłówków odpowiedzi FastCGI", - "webServicePhpStderrLimit": "Maks. bajtów stderr PHP-FPM w logu", - "webServicePhpCustomParams": "Własne parametry FastCGI", - "webServicePhpCustomParamsHelp": "Jedna linia NAME = wartość, np. APP_ENV = production. Nazwy: A-Z, cyfry i podkreślenia; bez HTTP_*, PHP_VALUE, PHP_ADMIN_VALUE i podstawowych parametrów CGI.", - "webServicePhpCustomParamsInvalid": "Własne parametry FastCGI muszą mieć unikalny format NAZWA = wartość, po jednym wpisie na linię.", - "webServicePhpCustomDataTooLarge": "Własne parametry FastCGI i nadpisania PHP INI mogą łącznie zajmować maksymalnie 128 KiB.", - "webServicePhpValues": "Nadpisania PHP INI (PHP_VALUE)", - "webServicePhpValuesHelp": "Jedna dyrektywa = wartość w wierszu, np. memory_limit = 256M, upload_max_filesize = 64M, post_max_size = 64M. O możliwości zmiany decyduje PHP-FPM.", - "webServicePhpValuesInvalid": "Nadpisania PHP INI muszą mieć unikalny format dyrektywa = wartość, po jednym wpisie na linię.", - "webServicePhpDocsButton": "Składnia i bezpieczeństwo FastCGI", - "webServicePhpDocsTitle": "Konfiguracja PHP-FPM / FastCGI", - "webServicePhpDocsIntro": "RustProxy łączy się bezpośrednio z istniejącą pulą PHP-FPM. Na tym samym hoście preferuj socket Unix. TCP jest akceptowane wyłącznie na loopback i nie jest publicznym/sieciowym transportem FastCGI.", - "webServicePhpDocsEndpointTitle": "Składnia endpointu", - "webServicePhpDocsEndpoint": "Użyj unix: z pełną ścieżką gniazda albo tcp: z adresem IP loopback i portem. Nazwy DNS i publiczne adresy TCP są zabronione. Oddziel pule PHP-FPM aplikacji z różnymi ograniczeniami INI.", - "webServicePhpDocsCustomTitle": "Własne parametry FastCGI", - "webServicePhpDocsCustom": "Jedna linia NAME = wartość (nazwa zaczyna się literą i zawiera A-Z, cyfry lub podkreślenie). Wartości są statyczne. SCRIPT_FILENAME, HTTP_* i PHP_VALUE/PHP_ADMIN_VALUE są zastrzeżone.", - "webServicePhpDocsValuesTitle": "Nadpisania PHP INI", - "webServicePhpDocsValues": "Każda linia trafia do PHP_VALUE. Zaufane ustawienia administracyjne wpisuj osobno w PHP_ADMIN_VALUE; żadne z tych pól nie izoluje procesów korzystających z tej samej puli PHP-FPM.", - "webServicePhpDocsLimitsTitle": "Limity i nagłówki", - "webServicePhpDocsLimits": "Timeout żądania obejmuje wymianę FastCGI. Limit body jest egzekwowany podczas streamingu i po przekroczeniu zwraca 413; 0 wyłącza limit body po stronie RustProxy. Limity nagłówków odpowiedzi i stderr ograniczają pamięć/dane diagnostyczne. Dozwolone są maks. 64 własne parametry FastCGI i 64 nadpisania PHP INI, łącznie do 128 KiB danych własnych. Przekazywanie Authorization można wyłączyć, gdy aplikacja nie potrzebuje HTTP_AUTHORIZATION.", - "webServicePhpDocsSecurity": "Nie umieszczaj sekretów we własnych parametrach ani PHP_VALUE, jeśli nie akceptujesz ich przechowywania w zwykłej konfiguracji Web Service. Używaj osobnej, nieuprzywilejowanej tożsamości puli PHP-FPM i nie wystawiaj PHP-FPM do publicznej sieci.", - "webServicePhpHelp": "RustProxy komunikuje się bezpośrednio przez FastCGI z długowieczną pulą PHP-FPM. PHP nie jest uruchamiane osobno dla każdego żądania, a kod źródłowy PHP nie jest serwowany, gdy obsługa PHP jest wybrana.", - "webServicePhpSymlinkConflict": "PHP-FPM wymaga obecnie polityki symlinków Deny.", - "webServiceSymlinkPolicy": "Polityka symlinków", - "webServiceSymlinkDeny": "Zabroń", - "webServiceSymlinkInsideRoot": "Zezwól tylko wewnątrz document root (Linux)", - "webServiceSafety": "Document root jest ścisłą granicą dostępu do filesystemu. Domyślnie symlinki są blokowane. Linux może opcjonalnie podążać wyłącznie do celów pozostających w document root; PHP-FPM wymaga obecnie polityki blokującej symlinki.", - "webServiceSelect": "Wybierz usługę WWW", - "webService": "Usługa WWW", - "webServiceUsedBy": "Używana przez", - "webServiceUnused": "Niepodpięta", - "webServiceEmpty": "Brak usług WWW.", - "webServiceSearchEmpty": "Brak usług WWW pasujących do wyszukiwania.", - "webServiceInUse": "Usługa WWW jest nadal używana przez", - "webServiceDelete": "Usunąć tę usługę WWW?", - "webServiceIndexRequired": "Pliki index są opcjonalne.", - "webServiceDuplicate": "Usługa WWW o tym ID już istnieje.", - "webServiceRequired": "Wybierz usługę WWW.", - "sourceType": "Typ źródła", - "sourceReverseProxy": "Reverse Proxy", - "sourceWebServer": "Serwer WWW", - "sourceHelp": "Vhost przechowuje tylko wybór źródła. Ustawienia Serwera WWW pozostają w wybranej, wielokrotnego użytku usłudze WWW.", - "locationWebServerAction": "Serwer WWW", - "locationWebServerShort": "Serwer WWW", - "docsReader": "Reader", - "docsApi": "API", - "docsReaderSearch": "Szukaj w dokumentacji", - "docsReaderSearchPlaceholder": "Szukaj tytułu lub ścieżki", - "docsReaderEmpty": "Brak dokumentów pasujących do wyszukiwania.", - "docsCategoryProject": "Projekt", - "docsCategoryGuides": "Dokumentacja funkcjonalna", - "docsCategoryTechnical": "Techniczna", - "docsCategoryAudits": "Audyty i historia", - "docsCategoryHistorical": "Historyczne", - "webServiceIndexHelp": "Opcjonalne. Pozostaw puste, aby pominąć wyszukiwanie indexu; przy włączonym listingu od razu zostanie pokazana lista plików.", - "webServiceNoIndex": "Brak plików index", - "vhBackendRequired": "Reverse Proxy wymaga co najmniej jednego backendu. Dodaj backend albo zmień źródło na Serwer WWW.", - "vhListenerRequired": "Wybierz co najmniej jeden listener dla tego vhosta.", - "traffic": "Ruch", - "trafficTitle": "Ruch per vhost.", - "trafficDescription": "Ograniczone statystyki żądań: kraje, User-Agenty, adresy IP klientów, ścieżki, trasy i wyniki obsługi.", - "trafficSinceStart": "Od startu procesu", - "trafficResponseBytes": "Bajty odpowiedzi", - "trafficRequestBytes": "Bajty żądań", - "trafficMeanLatency": "Średnie opóźnienie", - "trafficDroppedSamples": "Odrzucone próbki statystyk", - "trafficBoundedLabel": "OGRANICZONE / ASYNCHRONICZNE", - "trafficDimensionsTitle": "Wymiary ruchu", - "trafficApproxHelp": "Listy TOP są przybliżone i mają stały limit, aby ruch o dużej liczbie unikalnych wartości nie zwiększał pamięci proxy bez ograniczeń.", - "trafficTopCountries": "Top kraje", - "trafficTopUserAgents": "Top User-Agenty", - "trafficTopClientIps": "Top adresy IP klientów", - "trafficClientIp": "IP klienta", - "trafficTopPaths": "Top ścieżki", - "trafficTopRoutes": "Top trasy", - "trafficMethods": "Metody", - "trafficStatusClasses": "Klasy statusów", - "trafficCacheOutcomes": "Wyniki cache", - "trafficValue": "Wartość", - "trafficOther": "Pozostałe", - "trafficNoCountryData": "Zbieranie kraju jest wyłączone albo baza GeoIP jest niedostępna.", - "trafficNoUserAgentData": "Brak zebranych danych User-Agent.", - "trafficNoData": "Brak danych o ruchu.", - "trafficUserAgentEnabled": "Zbieranie User-Agent włączone", - "trafficUserAgentDisabled": "Zbieranie User-Agent wyłączone", - "trafficCountryEnabled": "Zbieranie kraju włączone", - "trafficCountryDisabled": "Zbieranie kraju wyłączone", - "trafficCountryNoDb": "Zbieranie kraju włączone, baza GeoIP niedostępna", - "trafficUpdated": "Odświeżono", - "loggingCaptureTitle": "Zbierane wymiary żądania", - "loggingCaptureUserAgent": "Zbieraj User-Agent", - "loggingCaptureCountry": "Zbieraj kraj z GeoIP", - "loggingCaptureCountryHelp": "Lookup kraju jest opcjonalny i domyślnie wyłączony. Włącz go tylko, gdy potrzebujesz kraju w logach/statystykach; reguły ACL/rate-limit per kraj nadal użyją GeoIP, gdy jest to wymagane.", - "cacheAutoTrimEnabled": "Automatycznie zwalniaj nieużywaną pamięć cache HOT", - "cacheAutoTrimAfter": "Czas bezczynności przed trim HOT (sekundy)", - "cacheAutoTrimTarget": "Docelowe użycie HOT po trim (%)", - "cacheAutoTrimHelp": "Gdy pamięć HOT przekracza próg, w ograniczonych partiach usuwane są wyłącznie obiekty nieużywane przez ustawiony czas. Aktywny working set nie jest czyszczony.", - "webServiceTabHtaccess": ".htaccess", - "webServiceHtaccessEnabled": "Włącz zgodność z .htaccess", - "webServiceHtaccessFilename": "Plik konfiguracyjny", - "webServiceHtaccessRecursive": "Skanuj podkatalogi", - "webServiceHtaccessStrict": "Tryb ścisły dla niewspieranych dyrektyw", - "webServiceHtaccessDocs": "Dokumentacja zgodności", - "webServiceHtaccessHelp": "RustProxy kompiluje ograniczony podzbiór zgodny z Apache podczas zastosowania Web Service. Pliki nie są parsowane przy każdym żądaniu; po zmianie .htaccess na dysku zastosuj Web Service ponownie.", - "webServiceHtaccessDocsTitle": "Zgodność .htaccess", - "webServiceHtaccessDocsIntro": "To warstwa zgodności przeznaczona do migracji, a nie pełne środowisko Apache. Wspierane dyrektywy są kompilowane do reguł Web Servera RustProxy.", - "webServiceHtaccessDocsSupportedTitle": "Wspierany podzbiór", - "webServiceHtaccessDocsRewriteTitle": "Zgodność rewrite", - "webServiceHtaccessDocsRewrite": "Warunki rewrite obsługują testy REQUEST_FILENAME (-f, -d, -s), REQUEST_URI, REQUEST_METHOD, QUERY_STRING, HTTP_HOST, HTTPS, DOCUMENT_ROOT, THE_REQUEST i nagłówki HTTP. Flagi: L, END, R, R=301/302/307/308, QSA, NC, F, OR. PT/NE są akceptowane jako no-op. E= jest akceptowane jako no-op zgodności, ponieważ nagłówki HTTP żądania i tak trafiają do PHP-FPM.", - "webServiceHtaccessDocsPhpTitle": "PHP-FPM", - "webServiceHtaccessDocsPhp": ".htaccess może dodawać rozszerzenia PHP przez AddHandler, ale endpoint PHP-FPM, timeouty i limit współbieżności nadal są kontrolowane w zakładce PHP-FPM Web Service. Składnia Apache SetHandler wskazująca socket proxy celowo nie jest akceptowana.", - "webServiceHtaccessDocsReloadTitle": "Ładowanie i limity", - "webServiceHtaccessDocsReload": "Pliki są odczytywane i kompilowane podczas „Validate, save and apply”. RustProxy nie parsuje .htaccess przy każdym żądaniu. Po zmianie pliku zastosuj konfigurację ponownie. Skan ma limity: 128 plików, głębokość 16, 128 KiB na plik i 1 MiB łącznie.", - "webServiceHtaccessDocsUnsupportedTitle": "To nie jest pełny Apache", - "webServiceHtaccessDocsUnsupported": "ErrorDocument, SetEnvIf, Redirect/RedirectMatch i FallbackResource nie są zaimplementowane w tej warstwie. Ogólne Files/FilesMatch, niewspierane dyrektywy uwierzytelniania/autoryzacji, handlery inne niż PHP i SetHandler są odrzucane nawet przy wyłączonym trybie ścisłym, ponieważ ich ciche pominięcie mogłoby osłabić kontrolę dostępu lub ujawnić kod źródłowy. Pliki .htaccess/.htpasswd są zawsze blokowane przed wysłaniem przez HTTP. Pozostałe niewspierane dyrektywy niesecurity są logowane i ignorowane, chyba że włączono tryb ścisły.", - "webServiceHtaccessDocsSecurity": "ACL vhost/location, uwierzytelnianie, strony błędów i reguły odpowiedzi nadal mają nadrzędną rolę. .htaccess nie może wyjść poza document root, wybrać dowolnego socketu FastCGI ani nadpisać chronionych nagłówków transportowych.", - "webServicePresetTitle": "Preset konfiguracji", - "webServicePreset": "Preset", - "webServicePresetCustom": "Własny", - "webServicePresetStatic": "Strona statyczna", - "webServicePresetSpa": "SPA", - "webServicePresetPhpGeneric": "PHP ogólny", - "webServicePresetDownloads": "Pliki / repozytorium pobrań", - "webServicePresetAction": "Akcja presetu", - "webServicePresetApply": "Zastosuj preset", - "webServicePresetHelp": "Preset wypełnia zwykłe ustawienia Web Service i wszystko nadal można edytować. Document root, ID/nazwa usługi i endpoint PHP-FPM nie są zmieniane.", - "webServicePresetChoose": "Wybierz preset przed jego zastosowaniem.", - "webServicePresetApplied": "Preset zastosowany. Sprawdź wygenerowane ustawienia przed zapisem.", - "webServicePresetHintStatic": "Strona statyczna: indeksy HTML, prekompresowane pliki i reguły cache dla CSS/JS/obrazów/fontów.", - "webServicePresetHintSpa": "SPA: fallback do index.html przez try_files, prekompresowane assety i długi cache zasobów statycznych.", - "webServicePresetHintPhpGeneric": "PHP ogólny: PHP-FPM włączony, index.php pierwszy, bez front-controllera dla brakujących plików.", - "webServicePresetHintWordpress": "WordPress: PHP-FPM, index.php i routing front-controller. Document root ustaw na publiczny katalog WordPressa.", - "webServicePresetHintDrupal": "Drupal: PHP-FPM, index.php i routing front-controller z cache zasobów statycznych.", - "webServicePresetHintLaravel": "Laravel: PHP-FPM i routing front-controller. Document root ustaw na katalog public/ aplikacji.", - "webServicePresetHintSymfony": "Symfony: PHP-FPM i routing front-controller. Document root ustaw na katalog public/ aplikacji.", - "webServicePresetHintJoomla": "Joomla: PHP-FPM, index.php i routing front-controller z cache zasobów statycznych.", - "webServicePresetHintPrestashop": "PrestaShop: PHP-FPM, index.php i routing front-controller z cache zasobów statycznych.", - "webServicePresetHintDownloads": "Pobrania: directory listing włączony, obsługa Range i typowe archiwa/dokumenty wymuszane jako załączniki.", - "webServiceHardeningTitle": "Utwardzenie", - "webServiceHardeningAttacks": "Blokuj typowe próby ataków", - "webServiceHardeningHeaders": "Usuwaj zbędne nagłówki ujawniające technologię", - "webServiceHardeningSecurityHelp": "Przełączniki utwardzenia są widoczne obok wyboru presetu w zakładce Ogólne. Po zastosowaniu presetu pozostają niezależnymi ustawieniami.", - "trafficTabOverview": "Przegląd", - "trafficTabClients": "Klienci", - "trafficTabRequests": "Żądania", - "trafficTabDelivery": "Obsługa odpowiedzi", - "trafficVolume": "Wolumen ruchu", - "vhMethodMode": "Polityka metod HTTP", - "vhMethodList": "Metody HTTP (jedna na linię)", - "vhMethodHelp": "Zezwalaj tylko na podane metody albo blokuj podane metody dla tego vhosta. Reguła łączy się z polityką lokalizacji.", - "locationAclProfile": "Profil ACL", - "locationMethodMode": "Polityka metod HTTP", - "locationMethodList": "Metody HTTP (jedna na linię)", - "locationMethodAllow": "Zezwalaj tylko", - "locationMethodBlock": "Blokuj wskazane", - "locationMethodHelp": "Polityka lokalizacji łączy się z polityką vhosta. Przykład: GET, HEAD i POST.", - "locationCountryMode": "Polityka dostępu krajów", - "locationCountryList": "Kody krajów (jeden na linię)", - "locationCountryHelp": "Polityka krajów lokalizacji łączy się z polityką vhosta/profilu. GeoIP jest odczytywany tylko, gdy jest potrzebny.", - "configHistory": "Historia rewizji", - "configHistoryRetention": "SQLITE / OSTATNIE 512 REWIZJI", - "configHistoryHelp": "Wybierz oznaczony dzień, aby zobaczyć zachowane rewizje konfiguracji i ich zmiany. Historia jest zapisywana od wersji 0.25.3.", - "configHistoryChanges": "ZMIANY", - "configHistorySelectDay": "Wybierz dzień.", - "configHistoryRevision": "Rewizja", - "configHistoryRevisionShort": "rew.", - "configHistoryBefore": "Przed", - "configHistoryAfter": "Po", - "configHistoryBaseline": "To najstarsza zachowana baza; wcześniejsza rewizja nie jest dostępna do porównania.", - "configHistoryNoChanges": "Nie znaleziono różnic w konfiguracji.", - "configHistoryDiffLimit": "Podgląd różnic ograniczono do 200 zmienionych ścieżek.", - "configHistoryMissing": "(brak)", - "configHistoryEntries": "rewizji", - "configHistorySummary": "Zachowane rewizje: {count}", - "configHistoryEmpty": "Brak zachowanych rewizji.", - "configHistoryLoading": "Wczytywanie historii rewizji...", - "configHistoryFailed": "Nie udało się wczytać historii rewizji.", - "configHistoryRestoreAction": "Przywróć rewizję", - "configHistoryRestoreConfirm": "Przywrócić rewizję {revision}? Zostanie zwalidowana i zapisana jako nowa rewizja po bieżącej rewizji {current}. Dotychczasowa historia zostanie zachowana.", - "configHistoryRestored": "Rewizja {revision} została przywrócona jako nowa rewizja {new_revision}.", - "about": "O projekcie", - "aboutTitle": "O RustProxy", - "aboutAuthor": "Autor", - "aboutLicense": "Licencja", - "aboutLicenseValue": "Open Source", - "aboutOpenSourceText": "RustProxy jest projektem open source. Kod źródłowy jest publicznie dostępny do wglądu, rozwoju i współtworzenia.", - "aboutRepository": "Oficjalne repozytorium źródłowe", - "aboutPackagesRepository": "Oficjalne repozytorium pakietów", - "aboutPythonRuntime": "Środowisko Python certyfikatów", - "runtimePython": "Python", - "runtimePip": "pip", - "runtimeCertbot": "Certbot", - "runtimeAcmeMode": "Tryb ACME", - "runtimeAvailable": "Dostępny", - "runtimeUnavailable": "Niedostępny", - "vhPoolRecycleRequests": "Odśwież pulę upstream po liczbie żądań (0 = wył.)", - "vhPoolRecycleSeconds": "Maks. wiek puli upstream (sekundy, 0 = wył.)", - "vhHttp1MaxBuffer": "Maks. bufor odczytu HTTP/1 upstream (bajty)", - "vhPoolRecycleHelp": "Odświeżanie podmienia tylko generację klienta Hyper danego backendu; aktywne wymiany kończą się normalnie. Domyślne limity ograniczają retencję pamięci puli/TLS bez przeładowania vhosta ani cache. Limit żądań: 0 lub 1000..10000000; limit czasu: 0 lub 60..604800.", - "backendSearch": "Szukaj backendów", - "backendSearchPlaceholder": "Szukaj vhosta, backendu lub stanu", - "backendSortBy": "Sortuj według", - "backendSortAscending": "Sortowanie rosnące", - "backendSortDescending": "Sortowanie malejące", - "backendColumns": "Kolumny", - "backendSearchEmpty": "Brak backendów pasujących do wyszukiwania.", - "vhQuickEnable": "Włącz", - "vhQuickDisable": "Wyłącz", - "vhEnableHint": "Określa, czy ten vhost przyjmuje ruch.", - "backendColumnsRequired": "Co najmniej jedna kolumna tabeli backendów musi pozostać widoczna.", - "logs": "Logs", - "storedLogs": "Zapisane logi", - "fileLogsSearchHelp": "Składnia wyszukiwania", - "fileLogsSearchHelpTitle": "Wyszukiwanie po polach jak w Kibanie", - "fileLogsSearchHelpText": "Używaj warunków pole:wartość. Warunki łączy AND; znak ! przed warunkiem wyklucza wynik. Cudzysłowy zachowują spacje.", - "fileLogsParsedFields": "Sparsowane pola", - "fileLogsRawJson": "Surowy JSON", - "filelogs": "Logs", - "filelogsTitle": "Przeglądarka zapisanych logów", - "filelogsDescription": "Przeszukuj skonfigurowane logi RustProxy na dysku niezależnie od bufora Live Logs.", - "fileLogsTitle": "Przeglądarka zapisanych logów", - "fileLogsMode": "LOKALNIE / TYLKO ODCZYT / LIMITY", - "fileLogsHelp": "Przeszukuje logi access/error RustProxy w ustalonych limitach i parsuje pola bez ujawniania ścieżek systemu plików.", - "fileLogsSettingsHelp": "Przeglądaj skonfigurowane pliki access/error z filtrami po stronie serwera. Logowanie do plików może być wyłączone, a Live Logs nadal korzysta z bufora pamięci.", - "openLogs": "Otwórz Logs", - "fileLogsSource": "Źródło", - "fileLogsAllSources": "Wszystkie skonfigurowane pliki", - "fileLogsNoSources": "Brak dostępnych skonfigurowanych plików logów.", - "fileLogsEvent": "Zdarzenie", - "fileLogsPageSize": "Rozmiar strony", - "fileLogsRecord": "Żądanie / przyczyna", - "fileLogsEmpty": "Brak rekordów pasujących do filtrów.", - "fileLogsDetails": "Szczegóły rekordu", - "fileLogsSelectRecord": "Wybierz rekord, aby zobaczyć jego JSON.", - "fileLogsSearchPlaceholder": "req: GET AND !country: Poland", - "fileLogsTruncated": "osiągnięto limit skanowania", - "fileLogsSummary": "{from}–{to} z {total} wyników · przeskanowano {bytes} / {lines} linii", - "fileLogsMatchedBy": "Dopasowanie przez", - "fileLogsSni": "SNI", - "fileLogsAuthority": "Authority / Host", - "fileLogsColumnsRequired": "Co najmniej jedna kolumna zapisanych logów musi pozostać widoczna.", - "fileLogsSourcesRead": "Skonfigurowane pliki", - "search": "Szukaj", - "all": "Wszystkie", - "clientIp": "IP klienta", - "vhost": "Vhost", - "previous": "Poprzednia", - "next": "Następna", - "refresh": "Odśwież", - "applyFilters": "Zastosuj filtry", - "appearance": "Wygląd", - "appearanceEyebrow": "INTERFEJS", - "appearanceHelp": "Te preferencje są zapisywane dla konta administratora i obowiązują również w kolejnych sesjach.", - "languageAutomatic": "Automatycznie (przeglądarka)", - "interfaceScale": "Skala interfejsu", - "interfaceScaleHelp": "Skaluj cały interfejs administracyjny, łącznie z modalami i nakładkami, bez zmiany powiększenia przeglądarki.", - "timeFormat": "Format czasu", - "timeFormat24": "24-godzinny", - "timeFormat12": "12-godzinny", - "chartStyle": "Styl wykresów", - "chartStyleDefault": "Domyślny", - "chartStyleSoft": "Łagodny", - "chartStyleTechnical": "Techniczny", - "chartStyleHighContrast": "Wysoki kontrast", - "chartStylePreview": "Podgląd", - "draftDockTitle": "Szkice", - "draftUntitled": "Nowy", - "draftRestore": "Otwórz szkic", - "draftDiscard": "Usuń szkic", - "draftDiscardConfirm": "Usunąć ten niezapisany szkic?", - "draftMinimized": "Szkic przeniesiono do docka.", - "chartStyleNeon": "Neonowy", - "chartStyleMonochrome": "Monochromatyczny", - "chartStyleWarm": "Ciepły", - "chartStyleCool": "Chłodny", - "settingsSearch": "Znajdź ustawienia", - "settingsSearchPlaceholder": "Szukaj ustawień, sekcji i opcji...", - "settingsSearchResults": "Pasujące sekcje", - "appearanceReset": "Przywróć wygląd", - "appearanceResetDone": "Przywrócono domyślne ustawienia wyglądu", - "appearanceInterfaceSection": "INTERFEJS", - "appearanceInterfaceTitle": "Interfejs", - "appearanceChartsSection": "WYKRESY", - "appearanceDataSection": "TABELE / LOGI", - "appearanceDashboardSection": "DASHBOARD", - "resetSection": "Resetuj sekcję", - "appearanceSectionResetDone": "Ustawienia sekcji zostały zresetowane", - "chartStylePalettes": "Palety stylów wykresów", - "tableDensity": "Gęstość tabel i logów", - "densityNormal": "Normalna", - "densityUltra": "Ultra kompaktowa", - "tableDensityHelp": "Zmienia wysokość wierszy i odstępy w logach niezależnie od globalnej skali interfejsu.", - "dashboardLayout": "Układ dashboardu", - "dashboardDensity": "Gęstość dashboardu", - "dashboardDensityHelp": "Steruje rozmiarem kafelków telemetrii w Overview bez zmiany reszty interfejsu.", - "dashboardWidgets": "Kafelki Overview", - "dashboardWidgetsHelp": "Pokazuj, ukrywaj i zmieniaj kolejność kafelków telemetrii na górze Overview.", - "dashboardWidgetVisible": "Widoczny", - "moveUp": "Przenieś wyżej", - "moveDown": "Przenieś niżej", - "draftDockCollapse": "Zwiń szkice", - "draftDockExpand": "Rozwiń szkice", - "draftAgeNow": "teraz", - "draftAgeMinutes": "{count} min", - "draftAgeHours": "{count} godz.", - "draftAgeDays": "{count} d", - "buttonStyle": "Styl przycisków", - "buttonStyleNormal": "Normalny", - "buttonStyleCompact": "Kompaktowy", - "buttonStyleHelp": "Normalny używa odświeżonych przycisków; Kompaktowy przywraca mniejsze przyciski ze źródłowego interfejsu we wszystkich akcjach i zakładkach modali.", - "entityEnableHint": "Określa, czy ten element jest aktywny.", - "setupCommandExample": "Przykład dla standardowej instalacji usługi:", - "certificateRuntimeTitle": "Środowisko ACME", - "certificateRuntimePreparing": "Przygotowanie", - "certificateRuntimeManaged": "Python aplikacji", - "certificateRuntimeSystem": "Fallback systemowy", - "certificateRuntimeUnavailable": "Niedostępne", - "certificateRuntimePath": "Środowisko aplikacji", - "acme.runtime_checking": "Sprawdzanie lokalnego środowiska Python do zarządzania certyfikatami.", - "acme.runtime_initializing": "Tworzenie prywatnego środowiska Python w katalogu danych RustProxy i instalowanie modułów Certbota przez pip.", - "acme.runtime_ready": "Prywatne środowisko Python/pip do zarządzania certyfikatami jest gotowe.", - "acme.runtime_fallback": "Nie można użyć prywatnego środowiska. RustProxy korzysta z systemowego Pythona i Certbota dostępnego w PATH.", - "acme.runtime_python_missing": "Python nie jest dostępny w tym systemie. Automatyczne generowanie i odnawianie ACME są niedostępne; import PEM i istniejące pliki certyfikatów nadal działają.", - "acme.runtime_pip_missing": "Python jest dostępny, ale RustProxy nie może utworzyć lokalnego środowiska pip, a systemowy Certbot nie jest dostępny. Zainstaluj Python z venv/pip albo pakiety Certbota. Import PEM i istniejące pliki nadal działają.", - "acme.runtime_setup_failed": "RustProxy nie może utworzyć lokalnego środowiska Python/pip, a systemowy Certbot nie jest dostępny. Sprawdź dostęp do sieci/pakietów albo zainstaluj Certbota z dystrybucji. Import PEM i istniejące pliki nadal działają.", - "acme.runtime_venv_failed": "Python i pip są dostępne, ale RustProxy nie może utworzyć prywatnego środowiska w katalogu danych. Sprawdź obsługę venv/virtualenv oraz uprawnienia do katalogu danych. Systemowy Certbot nie jest dostępny.", - "acme.runtime_venv_pip_failed": "Prywatne środowisko Python zostało utworzone, ale nie udało się uruchomić w nim pip/ensurepip. Sprawdź instalację Python venv/ensurepip. Systemowy Certbot nie jest dostępny.", - "acme.runtime_certbot_install_failed": "Prywatne środowisko Python działa, ale pip nie może zainstalować pakietów Certbota. Sprawdź dostęp do PyPI/proxy/DNS/TLS. Systemowy Certbot nie jest dostępny.", - "acme.runtime_certbot_validation_failed": "Pakiety ACME zostały zainstalowane, ale Certbot lub jedna z wymaganych wtyczek nie przechodzi walidacji. Usuń katalog acme-python i uruchom RustProxy ponownie albo sprawdź zgodność lokalnego Pythona.", - "acme.runtime_data_dir_missing": "Katalog danych RustProxy jest niedostępny, więc nie można utworzyć prywatnego środowiska Python. Import PEM i istniejące pliki certyfikatów nadal działają.", - "geoipUpdatesTitle": "Aktualizacje GeoIP", - "geoipUpdatesHelp": "Pobiera w tle bezpośrednie źródła baz krajów GeoIP i przeładowuje je bez restartu ruchu proxy. Weryfikacja SHA-256 wydawcy jest opcjonalna osobno dla każdego pliku.", - "geoipUpdatesEnable": "Włącz automatyczne aktualizacje GeoIP", - "geoipReleaseUrl": "Bazowy URL plików wydań", - "geoipChecksumUrl": "Bazowy URL plików sum kontrolnych", - "geoipMaxFileMiB": "Maksymalny rozmiar pliku (MiB)", - "geoipIntervalHours": "Interwał aktualizacji (godziny)", - "geoipStatusLoaded": "Baza GeoIP załadowana", - "geoipStatusMissing": "Baza GeoIP nie jest jeszcze załadowana. Worker w tle automatycznie ponowi próbę.", - "geoipStatusDisabled": "Automatyczne aktualizacje GeoIP są wyłączone.", - "cacheViaEnable": "Dodawaj nagłówek odpowiedzi Via", - "cacheViaValue": "Wartość nagłówka Via", - "cacheViaHelp": "Domyślnie Via: RustProxy. Zmiany bypassów i nagłówków diagnostycznych odpowiedzi działają od razu bez czyszczenia zapisanych obiektów cache.", - "geoipUpdateNow": "Pobierz teraz", - "geoipUpdateNowHelp": "Używa zapisanych bezpośrednich URL-i źródeł i opcjonalnych URL-i sum kontrolnych. Ręczne pobranie działa także przy wyłączonych aktualizacjach automatycznych i przeładowuje zweryfikowaną bazę bez restartu.", - "geoipUpdateSaveFirst": "Najpierw zapisz ustawienia GeoIP, a potem uruchom ręczne pobranie.", - "geoipUpdateWorking": "Pobieranie i walidacja danych GeoIP…", - "geoipUpdateUpdated": "Baza GeoIP została pobrana i przeładowana bez restartu.", - "geoipUpdateCurrent": "Baza GeoIP jest już aktualna i załadowana.", - "geoipUpdateBusy": "Inna aktualizacja GeoIP już trwa; spróbuj ponownie za chwilę.", - "geoipFormat": "Format bazy", - "geoipFormatCsvPair": "Zakresy CSV (osobno IPv4 + IPv6)", - "geoipFormatMmdb": "MaxMind DB (.mmdb, IPv4 + IPv6)", - "geoipIpv4Url": "Bezpośredni URL CSV IPv4", - "geoipIpv4ChecksumUrl": "URL SHA-256 IPv4 (opcjonalny)", - "geoipIpv6Url": "Bezpośredni URL CSV IPv6", - "geoipIpv6ChecksumUrl": "URL SHA-256 IPv6 (opcjonalny)", - "geoipMmdbUrl": "Bezpośredni URL MaxMind DB", - "geoipMmdbChecksumUrl": "URL SHA-256 MaxMind DB (opcjonalny)", - "geoipSourceHelp": "Tryb CSV używa osobnych plików zakresów IPv4 i IPv6. Tryb MMDB używa jednego natywnego pliku MaxMind DB i obsługuje standardowe pola country.iso_code, registered_country.iso_code albo country_code. Puste pole URL sumy wyłącza weryfikację sumy wydawcy; każdy plik nadal ma limit rozmiaru i jest walidowany przed atomową publikacją. Dla lokalnej własnej bazy MMDB umieść plik w /geoip/country.mmdb, wybierz MMDB i wyłącz automatyczne aktualizacje.", - "certificateFileSource": "Położenie plików", - "certificateSourceManaged": "Katalog certyfikatów RustProxy (/certs)", - "certificateSourceExternal": "Pliki zewnętrzne (pełne ścieżki)", - "certificateSourceManagedHelp": "Podaj tylko nazwy plików, np. app.pem i app.key. RustProxy ładuje je z /certs (przy instalacji pakietowej zwykle /var/lib/rustproxy/certs).", - "certificateSourceExternalHelp": "Podaj pełne ścieżki na serwerze. Przykład Certbota: /etc/letsencrypt/live/example.com/fullchain.pem oraz /etc/letsencrypt/live/example.com/privkey.pem. Symlinki live/ są rozwiązywane przy każdym reloadzie. Odczyt przez POSIX ACL jest obsługiwany i RustProxy nie wymaga konkretnego chmod. Klucz prywatny nie może być czytelny dla innych ani zapisywalny przez grupę/innych, a katalogi ścieżki muszą być własnością root lub rustproxy i nie mogą być zapisywalne przez grupę/innych.", - "certificateExternalAclExample": "Przykład ACL (prawo przejścia bez listowania katalogu):\nsetfacl -m u:rustproxy:--x /etc/ssl/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/\nsetfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem /etc/letsencrypt/archive/example.com/privkey*.pem\nPo odnowieniu nadaj odczyt bieżącym plikom docelowym przed reloadem API:\nsetfacl -m u:rustproxy:r-- \"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)\" \"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)\"", - "certificateExternalFiles": "Zewnętrzne ścieżki plików", - "vhWwwRedirect": "Przekieruj www ↔ bez www", - "vhWwwRedirectTarget": "Wybierz domenę i kierunek przekierowania (308)", - "vhWwwRedirectHelp": "Tylko wybrana para www/bez www jest przekierowywana (308). Pozostałe domeny działają na tym samym backendzie bez przekierowań między nimi. Certyfikat TLS musi obejmować obie wersje.", - "vhCacheForceEnabled": "Ryzykowne wymuszenie cache dla wskazanych URL", - "vhCacheForceTargets": "Prefiksy ścieżki/query dla wymuszonego cache (po jednym w linii)", - "vhCacheForceHelp": "Tylko per-vhost. Pasujące GET/HEAD mogą ominąć bypass query, Cookie/Authorization żądania i klienckie no-cache/no-store. Cookie/Authorization nadal tworzą osobne warianty. Odpowiedzi origin z Set-Cookie, private lub no-store nadal nigdy nie są zapisywane.", - "vhCacheForceConfirm": "Błędne wymuszenie cache może podać nieaktualną lub prywatną treść. Włączyć je tylko dla jawnie sprawdzonych prefiksów URL?", - "locationRedirectAction": "Przekieruj na URL", - "locationRedirectShort": "redirect", - "locationRedirectStatus": "Kod przekierowania", - "locationRedirectTarget": "Adres docelowego / URL", - "locationRedirectPattern": "Opcjonalny regex rewrite dla capture", - "locationRedirectQuery": "Zachowaj query string żądania", - "locationRedirectHelp": "Cel musi być pełnym URL http/https. Opcjonalny regex rewrite obsługuje $1/${name} w ścieżce/query, np. ^/(.*)$ → https://storage.example/o/$1.", - "locationRedirect301": "301 Stałe", - "locationRedirect302": "302 Found", - "locationRedirect303": "303 See Other", - "locationRedirect307": "307 Tymczasowe", - "locationRedirect308": "308 Stałe", - "cacheReason_cache_miss_forced": "MISS dla wymuszonego cache", - "vhWwwPanelTitle": "Przekierowanie adresu www", - "vhWwwInactive": "Przekierowanie wyłączone. Wybierz adres docelowy poniżej i włącz przełącznik powyżej. Wszystkie domeny obsługują ten sam vhost.", - "vhWwwOtherDomains": "Pozostałe domeny (bez przekierowania):", - "vhWwwNoHosts": "Dodaj powyżej konkretną domenę. Wildcard nie może być adresem docelowym.", - "vhWwwSharedCount": "{count} nazw / jeden vhost", - "vhWwwEnable": "Włącz przekierowanie 308", - "vhErrorFallbackTarget": "Główny cel fallbacku błędów", - "vhErrorFallbackStatuses": "Przechwytywane kody statusu", - "vhErrorFallbackHelp": "Cel musi być włączoną wewnętrzną Custom Location. Obsługiwane statusy: 403, 404, 500, 502, 503, 504. Request jest przekazywany wewnętrznie, bez redirectu HTTP.", - "locationInternal": "Wewnętrzna nazwana lokalizacja", - "locationErrorFallbackTarget": "Cel fallbacku błędów", - "locationErrorFallbackStatuses": "Przechwytywane kody statusu", - "locationErrorFallbackHelp": "Opcjonalny fallback błędów w stylu Nginx. Wskaż ID włączonej lokalizacji wewnętrznej. Obsługiwane statusy: 403, 404, 500, 502, 503, 504.", - "webServicePhpPathInfo": "Włącz rozdzielanie PHP PATH_INFO", - "phpProfileSelect": "Konfiguracja PHP-FPM", - "phpProfileLocal": "Konfiguracja lokalna", - "phpProfileHelp": "Wybierz wspólny profil PHP-FPM lub skonfiguruj usługę samodzielnie. Zmiana profilu dotyczy wszystkich przypiętych usług.", - "phpProfileManage": "Zarządzaj profilami PHP-FPM", - "phpProfileToolbar": "Profile PHP-FPM", - "phpProfileTitle": "Wspólne profile PHP-FPM", - "phpProfileDescription": "Rozwiń profil, aby go zmienić. Zapis od razu aktualizuje wszystkie przypięte serwery WWW.", - "phpProfileAdd": "Dodaj profil", - "phpProfileNew": "Nowy profil PHP-FPM", - "phpProfileId": "ID profilu", - "phpProfileName": "Nazwa profilu", - "phpProfileAdminValues": "Administracyjne ustawienia PHP (PHP_ADMIN_VALUE)", - "phpProfileInUse": "Profil jest używany przez", - "phpProfileDelete": "Usunąć profil PHP-FPM?", - "phpProfileInvalid": "Nieprawidłowe pola profilu PHP-FPM", - "phpProfileDuplicate": "ID profilu PHP-FPM już istnieje", - "phpProfileShared": "profil wspólny", - "backendHealthEnabled": "Monitoruj stan backendu (aktywnie i pasywnie)", - "backendHealthPerBackend": "Indywidualne testy stanu backendów", - "backendHealthSelect": "Wybierz backend do konfiguracji", - "backendHealthSelectionHelp": "Wybierz backend z włączonym monitorowaniem w zakładce Backendy. Zmiana wyboru nie usuwa niezapisanych ustawień. Włącz osobny health check, aby nadpisać ustawienia wspólne powyżej.", - "backendHealthNoEnabled": "Brak monitorowanych backendów. Włącz monitorowanie stanu przynajmniej jednego backendu w zakładce Backendy.", - "backendHealthOverride": "Osobny health check dla tego backendu", - "backendHealthOverridesHelp": "Bez nadpisania backend dziedziczy ustawienia vhosta. Wylaczenie monitorowania nie blokuje lokalizacji przypisanych do backendu, ale wylacza probe aktywna i pasywne wykluczanie.", - "cacheOnlyHits": "Pokazuj naglowki diagnostyki cache tylko dla HIT (ukryj MISS i BYPASS)", - "phpHintEnabled": "Uruchamia pliki o wskazanych rozszerzeniach przez już działającą pulę PHP-FPM. RustProxy nie uruchamia PHP; inne pliki nadal są statyczne.", - "phpHintPathInfo": "Rozdziela /static.php/skins/file.css na SCRIPT_FILENAME=static.php i PATH_INFO=/skins/file.css. Potrzebne np. w Roundcube; zwykle zbędne w WordPressie. Domyślnie wyłączone.", - "phpHintPassAuthorization": "Przekazuje nagłówek Authorization do PHP jako HTTP_AUTHORIZATION (np. API/Bearer/Basic). Wyłącz, gdy aplikacja go nie używa.", - "phpHintExtensions": "Rozszerzenia oddzielone przecinkami, bez kropek: php, phtml. Tylko te rozszerzenia uruchamiają PHP.", - "phpHintConnectTimeout": "Limit łączenia z gniazdem PHP-FPM w milisekundach: 1000 = 1 sekunda. Zakres: 50–60000.", - "phpHintRequestTimeout": "Limit całego żądania FastCGI w milisekundach: 30000 = 30 sekund. Zakres: 100–3600000.", - "phpHintConcurrency": "Maksymalna liczba równoległych żądań PHP na usługę, np. 64. Przekroczenie zwraca 503. Zakres: 1–4096.", - "phpHintBodyLimit": "Limit treści żądania w bajtach: 67108864 = 64 MiB. 0 = bez limitu po stronie RustProxy. PHP post_max_size jest osobny.", - "phpHintHeaderLimit": "Maksymalny rozmiar nagłówków odpowiedzi FastCGI w bajtach: 65536 = 64 KiB. Zakres: 4096–1048576.", - "phpHintStderrLimit": "Maksymalna liczba bajtów STDERR PHP-FPM w logu na żądanie, np. 4096. 0 wyłącza przechwytywanie (maks. 65536).", - "phpHintAdminValues": "Administracyjne ustawienia INI, np. open_basedir = /var/www/webmail/:/tmp/. Aby zapobiec przeciekaniu ustawień między vhostami, stosuj osobne pule i gniazda PHP-FPM.", - "phpDocsScope": "Te same pola i przykłady obowiązują w lokalnych ustawieniach PHP usługi WWW i we współdzielonym profilu PHP-FPM. Wybrany profil dostarcza ustawienia wszystkim przypiętym usługom; RustProxy łączy się z uruchomioną pulą.", - "phpDocsSwitchesTitle": "Włączanie PHP i obsługa PATH_INFO", - "phpDocsLimitsTitle": "Limity wykonania i rozmiarów", - "phpDocsLimitsText": "Rozszerzenia PHP: php, phtml (oddzielone przecinkami). Timeout połączenia: 1000 ms = 1 s. Całe żądanie: 30000 ms = 30 s. Maksymalnie 64 równoległe żądania (po przekroczeniu 503). Limit żądania: 67108864 bajtów = 64 MiB; 0 oznacza brak limitu w RustProxy. Nagłówki odpowiedzi: 65536 bajtów. Log STDERR: 4096 bajtów; 0 wyłącza. Wprowadź liczby całkowite; limity PHP INI to osobne ustawienia.", - "phpDocsValuesText": "Wpisuj po jednej dyrektywie = wartość w wierszu w polu PHP INI overrides (PHP_VALUE), a NIE w parametrach FastCGI. Składnia php.ini bez sekcji [sekcja] i bez tagów PHP. Jednostki M/G, wartości On/Off i czasy w sekundach interpretuje PHP. Nie każdą dyrektywę wolno zmienić przez PHP_VALUE. Nie powielaj jej w PHP_ADMIN_VALUE.", - "phpDocsAdminTitle": "PHP_ADMIN_VALUE — administracyjne dyrektywy INI", - "phpDocsAdminText": "Wpisuj je w pole Administracyjne ustawienia PHP (PHP_ADMIN_VALUE). W systemie Unix open_basedir jest listą dozwolonych ścieżek oddzielonych dwukropkiem, np. /var/www/webmail/:/tmp/. Ważne ograniczenia lepiej ustawiać jako php_admin_value[...] w osobnej puli PHP-FPM. Nie stosuj ograniczeń Roundcube dla procesów WordPressa.", - "phpDocsOtherTitle": "Walidacja, przechowywanie i diagnostyka", - "phpDocsOtherText": "Nazwa dyrektywy INI może zawierać litery, cyfry, kropkę, podkreślenie i myślnik (maks. 64 bajty). Wartość nie może być pusta (maks. 8192 bajtów). Maksymalnie 64 parametry FastCGI i 64 wpisy INI w każdej mapie; łącznie maks. 128 KiB. Znaki sterujące i wielowierszowe wartości są odrzucane. Dane trafiają do zwykłej konfiguracji — nie zapisuj tam sekretów. PHP-FPM może odrzucić poprawną składniowo opcję.", - "phpDocsPoolWarning": "Ważne: PHP_VALUE/PHP_ADMIN_VALUE NIE izolują vhostów współdzielących procesy PHP-FPM. WordPress i Roundcube wymagają oddzielnych gniazd, jeśli mają różne ograniczenia INI. Po rozdzieleniu uruchom ponownie procesy PHP-FPM. PATH_INFO nie rozwiązuje problemu open_basedir.", - "vhostInterceptOriginErrors": "Zastępuj treść błędów aplikacji stronami błędów RustProxy", - "vhostInterceptOriginStatuses": "Przechwytywane kody HTTP (oddziel przecinkami)", - "vhostInterceptOriginErrorsHelp": "Przykład: 404, 500, 502, 503, 504. Dla wybranych kodów RustProxy zachowa kod błędu, ale wyświetli stronę skonfigurowaną dla vhosta/globalnie albo wbudowaną. Treść i ciasteczka aplikacji zostaną usunięte, a odpowiedź otrzyma no-store. Domyślnie wyłączone. Wewnętrzne lokalizacje błędów mają pierwszeństwo. Działa dla Reverse Proxy i Web Server (także PHP).", - "vhostInterceptOriginInvalid": "Podaj co najmniej jeden unikalny obsługiwany kod HTTP: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504.", - "systemInformation": "Informacje o systemie", - "systemInformationHelp": "Bieżące dane tylko do odczytu z uwierzytelnionego API statusu. Brakujących wartości nie szacujemy.", - "systemHostHeading": "System operacyjny i sprzęt", - "systemOsVersion": "System operacyjny", - "systemKernel": "Jądro systemu", - "systemArchitecture": "Architektura", - "systemLogicalCpus": "Procesory logiczne", - "systemCpuLoad": "Użycie CPU", - "systemProcessHeading": "Proces RustProxy", - "systemAppVersion": "Wersja RustProxy", - "systemProcessUptime": "Czas pracy procesu", - "systemProcessMemory": "Pamięć procesu (RSS)", - "systemProcessThreads": "Wątki procesu", - "systemOpenFds": "Otwarte deskryptory plików", - "systemCertificateHeading": "Środowisko ACME / Python", - "systemCertificateHelp": "Wersje są odczytywane z interpretera i narzędzi rzeczywiście używanych do obsługi certyfikatów.", - "systemAcmeStatus": "Stan ACME", - "systemPathsHeading": "Ścieżki danych" - } + "meta": { + "locale": "pl-PL", + "name": "Polish", + "native_name": "Polski", + "direction": "ltr" + }, + "translations": { + "overview": "Przegląd", + "mainMenu": "Menu", + "routing": "Starsze trasy", + "configuration": "Konfiguracja", + "manualConfig": "Konfiguracja ręczna", + "runtime": "ŚRODOWISKO", + "localOnly": "ADMINISTRACJA", + "logout": "Wyloguj", + "firstSlice": "NIEZALEŻNY RDZEŃ / JAWNY DOSTĘP", + "loginTitle": "Administracja", + "unlock": "Odblokuj administrację", + "tokenHelp": "Uruchom polecenie w drugim terminalu i wklej token poniżej.", + "token": "Lokalny token dostępu", + "connect": "Połącz z proxy", + "tokenNote": "Administracja domyślnie używa HTTP na loopback. Przed użyciem adresu spoza loopback włącz HTTPS panelu.", + "requests": "Żądania", + "activeExchanges": "Aktywne żądania", + "bodyLifetime": "Do zakończenia strumienia", + "bandwidth": "Przekazane dane", + "bothDirections": "W obu kierunkach", + "errors": "Błędy", + "errorDescription": "5xx / body / przerwane", + "liveTraffic": "RUCH NA ŻYWO", + "liveTransformed": "Transformowane", + "requestsPerSecond": "Żądania na sekundę", + "live": "NA ŻYWO", + "lastMinute": "Ostatnie próbki / reset po odświeżeniu", + "coreState": "STAN RDZENIA", + "configurationRevision": "Aktywna rewizja", + "backends": "Backendy", + "routes": "Trasy", + "connections": "Otwarte połączenia", + "uptime": "Czas działania", + "activity": "AKTYWNOŚĆ", + "accessLog": "Log dostępu", + "boundedLog": "Ostatnie 50 / bufor 4096", + "time": "Czas", + "request": "Żądanie", + "status": "Status", + "duration": "Czas obsługi", + "outcome": "Wynik", + "noTraffic": "Brak ruchu. Otwórz proxy, aby wysłać pierwsze żądanie.", + "reload": "Wczytaj konfigurację", + "addBackend": "Dodaj / edytuj backend", + "backendUrl": "Adres backendu (HTTP / HTTPS, IP / DNS)", + "allowPrivate": "Jawnie zezwól na prywatny IP / loopback", + "preserveHost": "Zachowaj oryginalny nagłówek Host", + "saveBackend": "Zapisz backend", + "clear": "Wyczyść", + "addRoute": "Dodaj / edytuj trasę", + "hostnames": "Hostname i aliasy, oddzielone przecinkami", + "pathPrefix": "Prefiks ścieżki (bez usuwania)", + "backend": "Backend", + "enabled": "Włączona", + "saveRoute": "Zapisz trasę", + "rawConfig": "Dokument konfiguracji", + "export": "Eksportuj JSON", + "configHelp": "Poprawny zapis jest stosowany atomowo. Błędne lub nieaktualne zmiany nie zastępują działającej konfiguracji.", + "apply": "Sprawdź, zapisz i zastosuj", + "noRestart": "Routing i polityki odpowiedzi działają od razu; zmiana portów wymaga restartu.", + "roadmapHelp": "Zmiany w wydaniach i planowane funkcje opisuje plik ROADMAP.md.", + "overviewTitle": "Ruch pod kontrolą.", + "overviewDescription": "Ruch, cache i pomiary systemu operacyjnego w jednym zwartym panelu.", + "routingTitle": "Połącz właściwe adresy.", + "routingDescription": "Dotychczasowy format backendów i tras pozostaje obsługiwany. Nowe konfiguracje mogą używać vhosta z własnym frontendem i pulą.", + "configurationTitle": "Jedno źródło prawdy.", + "configurationDescription": "Jedna konfiguracja: vhosty, listenery i wspolne polityki.", + "connected": "POŁĄCZONO Z LOKALNYM RDZENIEM", + "disconnected": "RDZEŃ NIEDOSTĘPNY", + "edit": "Edytuj", + "remove": "Usuń", + "privateAllowed": "ZGODA NA PRYWATNY IP", + "configured": "SKONFIGUROWANY / BEZ PROBE", + "disabled": "WYŁĄCZONA", + "emptyBackends": "Brak backendów. Dodaj jawny adres HTTP lub HTTPS.", + "emptyRoutes": "Brak tras. Nieznane hostname zwracają 404.", + "saving": "Zapisywanie…", + "unsavedChanges": "Niezapisane zmiany", + "saved": "Zastosowano konfigurację.", + "confirmDelete": "Usunąć ten element? Zmiana zadziała natychmiast.", + "confirmTitle": "Potwierdź operację", + "confirmationLabel": "POTWIERDZENIE", + "warningLabel": "OSTRZEŻENIE", + "continueAction": "Kontynuuj", + "cancelAction": "Anuluj", + "discardAction": "Odrzuć zmiany", + "errorContext": "{area}: {message}", + "stale": "Konfiguracja zmieniła się w innym miejscu. Wczytaj ją przed zapisem.", + "busy": "Trwa już zapis.", + "loginError": "Nie udało się zalogować.", + "fresh": "Wczytano konfigurację.", + "locked": "Wylogowano i unieważniono sesję.", + "backendUsed": "Backend jest używany przez trasę. Najpierw usuń lub zmień trasę.", + "renaming": "Nie można zmienić ID w formularzu. Użyj edytora JSON i zmień również powiązania.", + "complete": "zakończono", + "cancelled": "przerwano", + "body_error": "błąd body", + "proxy_or_upstream_error": "błąd proxy/backendu", + "security": "Bezpieczeństwo", + "sessionNote": "Lokalna administracja HTTP. Hasła używają Argon2id, a cookies sesji są HttpOnly. Nie udostępniaj panelu publicznie.", + "setupHelp": "Utwórz pierwsze konto administratora. Jednorazowy token odczytasz lokalnie w terminalu.", + "setupToken": "Jednorazowy token instalacyjny", + "username": "Nazwa użytkownika", + "password": "Hasło", + "confirmPassword": "Powtórz hasło", + "setupTitle": "Utwórz administratora", + "signIn": "Zaloguj się", + "createAndSignIn": "Utwórz konto i zaloguj", + "securityTitle": "Dostęp pod kontrolą.", + "securityDescription": "Jeden administrator. Odwoływalne sesje. Jawne polityki hostów i tras.", + "manualBackends": "RĘCZNE BACKENDY", + "routePolicies": "Kontrola dostępu i nagłówki", + "useRouteAcl": "Włącz ACL trasy", + "aclDefault": "Domyślna akcja", + "allow": "Zezwól", + "deny": "Zablokuj", + "aclRules": "Jedna reguła w wierszu: allow/deny oraz CIDR", + "aclHelp": "Podaj adres IPv4/IPv6 albo CIDR, np. 192.168.1.0/24 lub 10.0.0.5. Odmowa ma pierwszeństwo. Przy trybie krajów „tylko zezwolone” wystarczy dozwolony kraj LUB dozwolone IP; przy blokowaniu krajów blokowany kraj LUB deny IP odrzuca ruch. Uwierzytelnienie nadal obowiązuje.", + "routeHeaders": "Nagłówki request / response (JSON)", + "headersHelp": "Operacje: ustaw, dodaj, usuń. Nagłówki transportu, kodowania body i tożsamości klienta są chronione.", + "currentSession": "BIEŻĄCA SESJA", + "recoveryHelp": "Utrata dostępu? Uruchom lokalnie rust-proxy admin reset-password. Dotychczasowe sesje zostaną unieważnione.", + "revokeAll": "Wyloguj wszystkie sesje", + "changePassword": "Zmień hasło", + "currentPassword": "Obecne hasło", + "newPassword": "Nowe hasło (12-128 bajtów UTF-8)", + "savePassword": "Zmień i wyloguj", + "hostPolicies": "Polityki dostępu hostów", + "editHostPolicy": "Dodaj / zastąp politykę hosta", + "savePolicy": "Zastosuj politykę", + "auditRetention": "SQLITE / OSTATNIE 100 / LIMIT 10 000", + "audit": "Audyt administracyjny", + "reloadAudit": "Odśwież", + "actor": "Użytkownik", + "action": "Operacja", + "detail": "Szczegóły", + "noAudit": "Brak wpisów audytu.", + "noPolicies": "Brak ACL hostów. Dostęp mogą ograniczać reguły tras.", + "expires": "Sesja wygaśnie najpóźniej: ", + "passwordChanged": "Zmieniono hasło i unieważniono sesje. Zaloguj się ponownie.", + "passwordMismatch": "Hasła nie są takie same.", + "ruleSyntax": "Wpisz jedną regułę w wierszu, np. allow 127.0.0.1/32", + "confirmRevoke": "Wylogować wszystkie sesje, również bieżącą?", + "streamLive": "SSE na żywo", + "streamFallback": "Odpytywanie awaryjne", + "auth.required": "Sesja wygasla lub zostala odwolana. Zaloguj sie ponownie.", + "auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło.", + "auth.password_policy": "Haslo musi miec 12-128 bajtow UTF-8, bez znakow nowego wiersza.", + "auth.invalid_username": "Uzyj 3-64 liter, cyfr, kropek, myslnikow lub podkreslen.", + "auth.invalid_setup_token": "Niepoprawny token instalacyjny. Odczytaj go w lokalnym terminalu.", + "auth.already_initialized": "Administrator juz istnieje. Zaloguj sie.", + "auth.rate_limited": "Zbyt wiele prob. Ponow po 60 sekundach lub uzyj lokalnego resetu hasla.", + "auth.busy": "Trwa weryfikacja innych hasel. Ponow za chwile.", + "auth.csrf": "Niepoprawna ochrona zadania. Odswiez strone i ponow.", + "auth.invalid_host": "Użyj skonfigurowanego adresu administracji i dozwolonej nazwy hosta.", + "auth.invalid_origin": "Zablokowano dostep administracyjny z innego originu.", + "auth.credentials_changed": "Dane logowania zostaly zmienione. Zaloguj sie ponownie.", + "auth.unavailable": "Baza administracyjna jest chwilowo niedostepna.", + "events.too_many_viewers": "Osiagnieto limit odbiorcow SSE; pozostaje odpytywanie.", + "protocol": "Protokół upstream", + "protocolHelp": "HTTP/2 i auto używają authority backendu. Preserve Host jest dostępne tylko z HTTP/1.1. RFC 8441 może użyć jawnego upstreamu HTTP/2; pozostałe transporty WebSocket są mostkowane przez HTTP/1.1.", + "addressAllowlist": "Opcjonalna lista dozwolonych adresów (CIDR, po jednym w wierszu)", + "caPem": "Dodatkowy zaufany CA / certyfikat (PEM, opcjonalnie)", + "backendTlsHelp": "Szyfrowanie TLS pozostaje dostępne, ale weryfikacja certyfikatu i nazwy jest domyślnie wyłączona, dopóki nie włączysz jej jawnie. Prywatne adresy nadal wymagają zgody, także po rozwiązaniu DNS.", + "advancedTransport": "Transport i zaufanie", + "tls": "Certyfikaty", + "tlsTitle": "Szyfrowanie pod kontrolą.", + "tlsDescription": "Dokładne nazwy SNI. Prywatne pliki certyfikatów. Zmiany bez restartu.", + "certificateBindings": "Powiązania certyfikatów", + "addCertificate": "Dodaj certyfikat", + "certificateId": "ID certyfikatu", + "certificateFile": "Plik certyfikatu / ścieżka", + "privateKeyFile": "Plik klucza prywatnego / ścieżka", + "defaultCertificate": "Użyj jako certyfikatu domyślnego", + "saveCertificate": "Zastosuj certyfikat", + "reloadCertificates": "Wczytaj pliki certyfikatów", + "certificateHelp": "Możesz użyć plików zarządzanych przez RustProxy, pełnych ścieżek zewnętrznych, importu PEM albo ACME. Nazwy plików są ładowane z /certs. Ścieżki zewnętrzne są czytane bez kopiowania i można je przeładować po odnowieniu certyfikatu bez zmiany rewizji konfiguracji.", + "certificateHint": "Odnowienie zewnętrzne: pozostaw ścieżki fullchain.pem/privkey.pem w konfiguracji, odnów je przez Certbot/acme.sh, a następnie użyj Przeładuj certyfikat albo POST /api/certificates/{certificate_id}/reload.", + "emptyCertificates": "Brak jawnych powiązań SNI. Aktywny listener TLS używa wygenerowanego certyfikatu domyślnego.", + "certificateSafety": "Certyfikaty samopodpisane służą do testów. Certyfikat nie tworzy trasy. Zmiana dotyczy nowych połączeń TLS.", + "wsActive": "Tunele WebSocket", + "h2Requests": "Żądania HTTP/2", + "h2ActiveConnections": "Aktywne połączenia H2", + "tlsFailures": "Błędy handshake TLS", + "tlsListener": "Listener TLS", + "notEnabled": "Wyłączony", + "websocket_closed": "WebSocket zamknięty", + "defaultLabel": "DOMYŚLNY", + "language": "Język", + "settings": "Ustawienia", + "settingsTitle": "Ustawienia proxy.", + "settingsDescription": "Przelaczaj karty funkcji. Niezapisane zmiany pozostaja w formularzach.", + "cacheHitRatio": "Trafienia cache", + "processMemory": "Pamięć procesu", + "systemLoad": "OBCIĄŻENIE SYSTEMU", + "systemChart": "Ostatnie próbki obciążenia CPU", + "systemMemory": "RAM użyta / łącznie", + "systemSwap": "Swap systemu", + "systemUptime": "Czas pracy systemu", + "loadAverage": "Load 1 / 5 / 15 min", + "cacheOverview": "Cache RAM", + "cacheObjects": "Obiekty / pamięć", + "cacheEvictions": "Usunięte / wygasłe", + "processDiagnostics": "Proces", + "platform": "Platforma", + "processCpu": "CPU (100% = 1 rdzeń)", + "fileDescriptors": "Deskryptory / limit", + "processThreads": "Wątki systemowe", + "processVirtualMemory": "Pamięć wirtualna / limit", + "processStartTime": "Start procesu", + "containerMemory": "RAM cgroup / limit", + "networkDiagnostics": "Sieć i odpowiedzi", + "hostScope": "Zakres systemu", + "networkRate": "RX / TX na sekundę", + "meanLatency": "Średni czas obsługi", + "filterLogs": "Filtruj host lub ścieżkę", + "filterResult": "Filtruj wynik", + "allResults": "Wszystkie", + "routeCache": "Cache dla trasy", + "inheritCache": "Dziedzicz ustawienia globalne", + "bypassCache": "Zawsze pomijaj cache", + "listeners": "Listenery", + "httpConnections": "Połączenia HTTP", + "httpConnectionsHelp": "Cykl życia połączeń i limity transportu dla HTTP/2 oraz HTTP/3.", + "http2Tab": "HTTP/2", + "http3Tab": "HTTP/3", + "http2Settings": "Połączenia HTTP/2", + "http2SettingsHelp": "HTTP/2 włącza się osobno dla każdego listenera. Tutaj ustawisz bezczynność, maksymalny czas życia i graceful drain po GOAWAY.", + "http2IdleTimeout": "Idle timeout (sekundy)", + "http2MaxLifetime": "Maksymalny czas życia połączenia (sekundy, 0 = wyłączony)", + "http2DrainTimeout": "Drain timeout (sekundy)", + "http2DrainHelp": "Te limity dotyczą tego listenera. Idle timeout obejmuje tylko nieużywane połączenia, maksymalny lifetime jest bezwzględny, a drain ogranicza czas aktywnych strumieni po GOAWAY. Zmiany wymagają restartu.", + "http2Save": "Zapisz ustawienia HTTP/2", + "http2NoEnabledListeners": "Żaden listener nie ma włączonego HTTP/2. Najpierw włącz HTTP/2 lub h2c w Ustawienia > Listenery.", + "http2ListenersDirty": "Najpierw zapisz oczekujące zmiany w Listenerach, a następnie edytuj ustawienia HTTP/2.", + "restartLabel": "RESTART", + "listenersHelp": "Nazwane adresy IP i porty. Zmiany wymagają restartu. Panel administracyjny pozostaje osobno.", + "httpAddress": "Adres nasłuchu i port HTTP", + "httpsAddress": "Adres nasłuchu i port HTTPS", + "enableHttps": "Włącz nasłuch HTTPS", + "lowPortsHelp": "Porty uprzywilejowane mogą wymagać CAP_NET_BIND_SERVICE. Nie uruchamiaj całej aplikacji jako root.", + "saveListeners": "Zapisz ustawienia portów", + "inheritListeners": "Użyj ustawień startowych", + "responseSettings": "Odpowiedź do klienta", + "hotApply": "BEZ RESTARTU", + "serverHeader": "Nagłówek Server", + "serverPreserve": "Zachowaj wartość backendu", + "serverHide": "Ukryj całkowicie", + "serverCustom": "Nadpisz / dodaj własną wartość", + "serverValue": "Własna wartość Server", + "compression": "Kompresja strumieniowa", + "compressionMin": "Minimalny rozmiar odpowiedzi (bajty)", + "compressionHelp": "Negocjuje Accept-Encoding według q klienta, a przy równym q używa priorytetu zstd → br → gzip → deflate. Cookie, Authorization, Set-Cookie oraz odpowiedzi private i no-store mogą być kompresowane. Istniejący Content-Encoding, zakresy, SSE, upgrade i no-transform są przekazywane bez rekompresji.", + "saveResponse": "Zastosuj ustawienia odpowiedzi", + "cacheSettings": "Cache", + "enableCache": "Włącz cache", + "cacheSafety": "Cache zapisuje kompletne, bezpieczne odpowiedzi GET. Authorization, cookies oraz odpowiedzi private/no-store nadal omijają cache. Pojedynczy zakres bajtów może być obsłużony z pełnego obiektu cache; multipart i nieobsługiwane zakresy przechodzą do originu.", + "cacheMemory": "Limit alokowanej pamięci HOT (MiB)", + "cacheObject": "Limit obiektu (KiB)", + "cacheEntries": "Maksymalna liczba wpisów", + "cacheMaxTtl": "Maksymalny TTL (sekundy)", + "cacheDefaultTtl": "Zapasowy TTL odpowiedzi publicznych (0 = wył.)", + "bypassQuery": "Pomijaj adresy z parametrami query", + "cachePrefixes": "Pomijane prefiksy ścieżek, po jednym w wierszu", + "cacheAgeHelp": "Trafienia zawierają Age, chyba że jawna reguła nagłówka odpowiedzi go usunie lub zastąpi. Takie reguły Age działają tylko na odpowiedzi wysyłanej do klienta i nie zmieniają świeżości cache. Liczniki działają również przy ukrytych nagłówkach diagnostycznych. Tylko zmiany wpływające na cache unieważniają niezgodne obiekty; podczas zarządzanej aktualizacji hot cache jest przenoszony do cold storage, jeśli cold tier jest włączony.", + "saveCache": "Zastosuj ustawienia cache", + "purgeCache": "Wyczyść cały cache", + "customHeaders": "Globalne reguły nagłówków", + "globalHeadersHelp": "Request trafia do backendu, response do klienta. Reguły trasy są stosowane po globalnych. Polityka Server ma ostateczne pierwszeństwo.", + "responseHeaders": "Odpowiedź do klienta", + "requestHeaders": "Żądanie do backendu", + "addHeader": "Dodaj regułę nagłówka", + "saveHeaders": "Zastosuj reguły nagłówków", + "unavailable": "Brak danych", + "threads": "wątków", + "realSample": "Pomiar", + "noKnownLimit": "nieznany / bez limitu", + "unlimited": "bez limitu", + "restartPending": "Zapisano zmianę portów. Uruchom ponownie RustProxy, aby ją zastosować.", + "activeListeners": "Aktualnie aktywne", + "headerLimit": "Maksymalnie 32 reguły w każdym kierunku.", + "headerOperation": "Operacja", + "header_set": "Ustaw", + "header_append": "Dodaj", + "header_remove": "Usuń", + "headerName": "Nazwa nagłówka", + "headerValue": "Wartość nagłówka", + "confirmInherit": "Przywrócić ustawienia portów z parametrów startowych i środowiska po restarcie?", + "confirmPurge": "Usunąć wszystkie obiekty cache i anulować zapis trwających pobrań?", + "cachePurged": "Cache wyczyszczony; usunięte wpisy", + "logsDropped": "odrzucone", + "updateAvailable": "Nowa wersja", + "vhosts": "Vhosty", + "vhostsTitle": "Vhosty i backendy", + "vhostsDescription": "Zarządzaj domenami, frontendami, backendami i politykami hostów.", + "vhHealthTitle": "Backendy", + "vhManage": "Zarządzaj vhostami", + "vhHealthEmpty": "Brak danych diagnostycznych backendów.", + "vhVhost": "Vhost", + "vhActive": "Aktywne", + "vhRequests": "Żądania", + "vhErrors": "Błędy", + "vhLatency": "Opóźnienie (EWMA)", + "vhTraffic": "Transfer/s", + "vhClientGeneration": "Gen. klienta", + "vhGenerationRequests": "Żądania gen.", + "vhGenerationAge": "Wiek gen.", + "vhClientRotations": "Rotacje", + "vhIntro": "Każdy vhost ma własne adresy IP, ścieżki i backendy. Inne adresy go nie obsługują. Zmiana nasłuchu wymaga restartu.", + "vhList": "Hosty wirtualne", + "vhSearch": "Szukaj vhostów", + "vhSearchPlaceholder": "Szukaj domeny, vhosta lub backendu", + "vhSearchEmpty": "Brak vhostów pasujących do wyszukiwania.", + "vhNew": "Nowy vhost", + "vhMigrate": "Migracja grupy starszych tras", + "vhImport": "Wczytaj do edytora", + "vhMigrationHint": "Migracja jest atomowa. Trasy tych samych hostów i backendu są grupowane; niejednoznaczne przypisanie hostów jest odrzucane.", + "vhEditor": "Konfiguracja vhosta", + "vhReset": "Odrzuć zmiany", + "vhSafetyHint": "Prywatny IP nie zastępuje uwierzytelniania. Zachowaj firewall i ACL. Preload HSTS jest opcjonalny; cookie affinity nie jest sesją logowania.", + "vhId": "Identyfikator vhosta", + "vhHosts": "Nazwy hostów", + "vhEnabled": "Włączony", + "vhFrontends": "Frontendy / adresy nasłuchu", + "vhAddFrontend": "Dodaj frontend", + "vhAddress": "IP:port (IPv6: [adres]:port)", + "vhTls": "TLS włączony", + "vhHttp2": "Preferuj HTTP/2 przez TLS; zachowaj HTTP/1.1", + "vhBackendPool": "Backendy", + "vhAddBackend": "Dodaj backend", + "vhMemberId": "Identyfikator backendu", + "vhUrl": "Adres URL backendu", + "vhWeight": "Waga", + "vhBackup": "Tylko zapasowy", + "vhDrain": "Wstrzymaj nowe sesje", + "vhPrivate": "Zezwól na prywatny IP backendu", + "vhPreserveHost": "Zachowaj Host klienta (tylko HTTP/1.1)", + "vhProtocol": "Protokół backendu", + "vhProtocolHttp1": "HTTP/1.1", + "vhProtocolAuto": "TLS ALPN: HTTP/2 + HTTP/1.1", + "vhProtocolHttp2": "HTTP/2 (TLS: także HTTP/1.1; bez TLS: h2c)", + "vhAddressAllowlist": "Dozwolone IP/CIDR backendu (opcjonalne)", + "vhTlsServerName": "Nazwa serwera TLS / SNI (opcjonalna)", + "vhTlsVerify": "Weryfikuj certyfikat i nazwę hosta backendu", + "vhTlsVerifyHelp": "Przy pominięciu ustawienia weryfikacja jest wyłączona. Włącz ją, gdy RustProxy ma weryfikować łańcuch i tożsamość certyfikatu backendu. Jeśli walidacja nie przechodzi, ustaw nazwę serwera TLS na nazwę DNS obecną w certyfikacie i/lub dodaj PEM prywatnego CA. Nie używaj adresu IP jako SNI.", + "vhCa": "Dodatkowy zaufany CA (PEM, opcjonalny)", + "vhBalancing": "Balansowanie i ponowne użycie połączeń", + "vhAlgorithm": "Algorytm", + "vhRoundRobin": "Płynny ważony round-robin", + "vhLeastConnections": "Najmniej aktywnych wymian, z wagami", + "vhLeastTraffic": "Najmniejszy transfer (EWMA), z wagami", + "vhAdaptive": "Adaptacyjny: aktywność + opóźnienie + transfer", + "vhIpHash": "Skrót IP klienta", + "vhSlowStart": "Łagodny powrót (sekundy)", + "vhRetryMax": "Ponowienia failover", + "vhRetryConnect": "Ponawiaj błędy połączenia", + "vhRetryTimeouts": "Ponawiaj timeout nagłówków odpowiedzi", + "vhRetryStatuses": "Ponawiane statusy HTTP", + "vhRetryHelp": "Retry działa tylko dla bezbody GET, HEAD i OPTIONS, wybiera inny zdrowy backend i nigdy nie ponawia WebSocketów ani żądań zapisu.", + "vhKeepalive": "Używaj ponownie połączeń do backendu (keepalive)", + "vhIdleTimeout": "Limit bezczynności w puli (sekundy)", + "vhMaxIdle": "Maks. bezczynnych połączeń / backend", + "vhTcpKeepalive": "Interwał keepalive TCP / HTTP/2 (sekundy)", + "vhHealth": "Zdrowie backendów", + "vhHealthMode": "Rodzaj testu", + "vhOff": "Wyłączone", + "vhTcp": "Połączenie TCP", + "vhHttp": "Status HTTP + czas odpowiedzi", + "vhHealthPath": "Ścieżka testu HTTP", + "vhHealthHost": "Własny Host testu HTTP (opcjonalny)", + "vhInterval": "Interwał (ms)", + "vhTimeout": "Limit czasu (ms)", + "vhSlow": "Próg wolnej odpowiedzi (ms; 0 = wył.)", + "vhFall": "Niepowodzenia przed wyłączeniem", + "vhRise": "Sukcesy przed przywróceniem", + "vhStatusMin": "Minimalny status HTTP", + "vhStatusMax": "Maksymalny status HTTP", + "vhHealthNote": "Sondy HTTP używają HTTP/1.1 i mierzą czas do nagłówków, nie czas całego body. Gdy pole Host jest puste, RustProxy automatycznie używa pierwszej dokładnej domeny DNS vhosta; jeśli jej nie ma, używa hosta backendu. Dla usług tylko h2c użyj TCP. Nowe backendy czekają na udaną sondę. Active health jest niezależny od passive health.", + "vhPassiveHealth": "Pasywne zdrowie backendu", + "vhPassiveEnabled": "Pozwól, aby błędy zwykłych requestów oznaczały backend jako niedostępny", + "vhPassiveHelp": "Domyślnie wyłączone. Błędy nadal są logowane i zliczane, ale zwykły ruch użytkowników nie zmienia stanu backendu bez świadomego włączenia.", + "vhPassiveConnect": "Zliczaj błędy połączenia", + "vhPassiveTimeouts": "Zliczaj timeouty", + "vhPassiveIo": "Zliczaj błędy I/O body/odczytu/zapisu", + "vhPassive5xx": "Zliczaj odpowiedzi HTTP 5xx", + "vhPassiveSlow": "Zliczaj wolne odpowiedzi", + "vhPassiveFall": "Pasywne błędy przed wyłączeniem (puste = active fall)", + "vhPassiveRise": "Pasywne sukcesy przed przywróceniem (puste = active rise)", + "vhPassiveWarning": "Włączenie 5xx lub wolnych odpowiedzi może wyłączyć cały backend po serii błędów z pojedynczego endpointu.", + "vhSticky": "Sticky sessions", + "vhStickyMode": "Tryb przypisania", + "vhCookie": "Nieprzezroczyste cookie proxy", + "vhCookieName": "Nazwa cookie (pusta = domyślna per vhost)", + "vhCookieTtl": "Ważność cookie (sekundy)", + "vhStickyNote": "Sticky cookie omija współdzielony cache. Drain zachowuje istniejące sesje cookie. Przypisanie po IP używa zweryfikowanego adresu klienta zgodnie z polityką zaufanych proxy.", + "vhSecurity": "Bezpieczeństwo", + "vhHsts": "Włącz HSTS (tylko odpowiedzi TLS)", + "vhHstsAge": "HSTS max-age (sekundy)", + "vhHstsSub": "HSTS includeSubDomains", + "vhHstsPreload": "HSTS preload (świadome włączenie)", + "vhNosniff": "X-Content-Type-Options: nosniff", + "vhFrame": "X-Frame-Options", + "vhPreserve": "Zachowaj wartość backendu", + "vhDeny": "DENY", + "vhSameorigin": "SAMEORIGIN", + "vhReferrer": "Referrer-Policy (opcjonalne)", + "vhCsp": "Content-Security-Policy (opcjonalne)", + "vhPermissions": "Permissions-Policy (opcjonalne)", + "vhAdvanced": "Zaawansowane: ścieżki, ACL i własne nagłówki", + "vhPaths": "Sciezki (JSON; opcjonalne backend_id przypina sciezke do jednego backendu)", + "vhAcl": "ACL vhosta (JSON lub null)", + "vhHeaders": "Reguły nagłówków żądania / odpowiedzi (JSON)", + "vhEmpty": "Brak vhostów. Dotychczasowe trasy nadal działają.", + "vhUnsaved": "Odrzucić niezapisane zmiany vhosta?", + "vhDelete": "Usunąć vhost? Gniazda nasłuchu zostaną zamknięte po restarcie.", + "vhConflict": "Vhost o takim identyfikatorze już istnieje.", + "vhRowLimit": "Osiągnięto maksymalną liczbę wierszy.", + "vhMigrationAmbiguous": "Hosty pokrywają się z innymi trasami. Uporządkuj przypisanie w JSON przed migracją.", + "vhMigrationPending": "Migracja starszych tras zostanie zatwierdzona przy zapisie.", + "vhMigrationNone": "Wybierz starszą trasę", + "vhJsonInvalid": "Nieprawidłowy JSON lub typ danych w ścieżkach, ACL lub nagłówkach.", + "vhUp": "Działa", + "vhDown": "Niedostępny", + "vhChecking": "Sprawdzanie", + "vhDraining": "Wygaszanie", + "vhDisabled": "Wyłączony", + "vhHealthDisabled": "Testy wyłączone", + "vhHealthUnknown": "Nieznany", + "vhNewLabel": "NOWY", + "vhEditLabel": "EDYCJA", + "vhFrontendNote": "Użyj IP przypisanego do serwera. Wildcard przyjmuje wszystkie IP danej rodziny; routing nadal sprawdza lokalny adres docelowy. Certyfikaty ustaw w zakładce TLS.", + "close": "Zamknij", + "inherit": "Dziedzicz", + "liveLogs": "Logi na żywo", + "logVhost": "Filtruj vhost", + "logAllVhosts": "Wszystkie vhosty", + "logAllMethods": "Wszystkie metody", + "logPause": "Wstrzymaj", + "logPaused": "WSTRZYMANO", + "logResume": "Wznów", + "logPin": "Przypnij", + "logUndock": "Otwórz modalnie", + "logColumns": "Kolumny", + "logColumnsRequired": "Co najmniej jedna kolumna Live Logs musi pozostać widoczna.", + "logLoadHistory": "Zwiększ bufor", + "logHistoryLoaded": "Bufor live zwiększono do {count} zachowanych wpisów.", + "logHost": "Host", + "logBufferHint": "Widok na żywo zaczyna od ostatnich 256 zakończonych wymian. Zwiększenie bufora wczytuje zachowaną historię i dalej dopisuje logi na żywo do 4096 wpisów, aż do zamknięcia okna. Eksport obejmuje aktualnie przefiltrowane wiersze.", + "settingsSecurityHint": "Konta i ACL są globalne. SSL, HSTS, nagłówki i cache konfigurujesz także wewnątrz vhosta.", + "theme": "Motyw", + "themeDark": "Ciemny", + "themeLight": "Jasny", + "density": "Gęstość interfejsu", + "densityCompact": "Kompaktowy", + "densityDense": "Gęsty", + "densityComfortable": "Przestronny", + "globalPages": "Domyślne odpowiedzi", + "pagesHelp": "Strony dla adresu IP, nieznanych hostów i błędów. Pusty status zachowuje kod odpowiedzi. Strony statyczne nie odpytują backendu.", + "pageInherit": "Domyślna / dziedziczona", + "pageWelcome": "Strona gratulacyjna", + "pageEmpty": "Pusta odpowiedź", + "pageHtml": "Własny HTML", + "pageStatus": "Kod odpowiedzi", + "pageOriginalStatus": "Oryginalny status", + "pageHtmlSource": "Kod HTML", + "pageUploadHtml": "Opcjonalny plik HTML (maks. 32 KiB)", + "pageTooLarge": "HTML nie może przekraczać 32 KiB w UTF-8.", + "customErrorPages": "Własne strony błędów", + "pageStaticHost": "Host statyczny / przerwa techniczna", + "pageMissingPath": "Niepasująca ścieżka", + "pageDirectIp": "Wejście bezpośrednio po IP", + "pageUnknownHost": "Nieznany host", + "pagesSafetyHint": "HTML to treść publiczna. ACL nadal działa przed odpowiedzią. Jawne HTTP 200 dla błędu ukrywa kod przed klientami i monitoringiem. Cała konfiguracja ma limit rozmiaru.", + "vhDetailsTab": "Szczegóły", + "vhSsl": "Certyfikaty SSL", + "vhCertificate": "Wybrany certyfikat", + "vhCertificateDefault": "Globalne SNI / certyfikat domyślny", + "vhCertificateFile": "Certyfikat / pełny łańcuch (PEM)", + "vhPrivateKeyFile": "Klucz prywatny (PEM)", + "vhUploadCertificate": "Wgraj i zweryfikuj parę", + "vhSelectCertificateFiles": "Wybierz plik certyfikatu i klucza prywatnego.", + "vhCertificateTooLarge": "Każdy plik PEM ma limit 128 KiB.", + "vhCertificateNeedsHosts": "Najpierw wpisz domeny vhosta w Szczegółach.", + "vhCertificateStaged": "Para zweryfikowana i zapisana. Zapisz vhost, aby aktywować certyfikat.", + "vhCertificateUploadHint": "Upload sprawdza zgodność klucza i domen. Klucz trafia do prywatnego pliku, nie do API. Zapis vhosta aktywuje wybór. Upload nie zapisuje pozostałych zmian. Zmiany listenerów wymagają restartu.", + "vhCompression": "Kompresja", + "vhCompressionHelp": "Kompresja i każdy kodek domyślnie dziedziczą ustawienia. Na tym poziomie można ograniczyć globalnie włączone kodeki, ale nie można włączyć kodeku wyłączonego w Ustawienia → Odpowiedź. Minimalny rozmiar pozostaje globalny.", + "compressionEnabled": "Kompresja dynamiczna", + "compressionZstd": "Zstandard (zstd)", + "compressionBr": "Brotli (br)", + "compressionGzip": "gzip", + "compressionDeflate": "deflate", + "vhCache": "Cache", + "vhCacheTtl": "Maks. TTL (sekundy, puste = dziedzicz)", + "vhCacheDefault": "Domyślny TTL public (puste = dziedzicz)", + "vhCacheObject": "Limit obiektu (KiB, puste = dziedzicz)", + "cacheEnabled": "Włącz cache", + "cacheBypassQuery": "Pomijaj adresy z parametrami", + "cacheStatusHeader": "Dodaj X-Cache", + "cacheHitsHeader": "Dodaj X-Cache-Hits", + "cacheRfcHeader": "Dodaj Cache-Status", + "cacheBypassPrefixes": "Pomijane prefiksy ścieżek", + "vhCacheCustomPrefixes": "Nadpisz odziedziczone pomijane ścieżki", + "vhCacheHint": "Polityka hosta może włączyć cache niezależnie od ustawienia globalnego. Vhosty współdzielą limit RAM i wpisów. Sticky cookie, dane prywatne i autoryzacja nadal omijają cache.", + "vhPurgeCache": "Wyczyść cache hosta", + "vhPurgeConfirm": "Wyczyścić cache tylko tego vhosta? Trwające zapisy tego hosta także zostaną unieważnione.", + "vhPages": "Strony błędów", + "vhHeadersOrder": "Kolejność: reguły globalne, vhost, ścieżka, następnie security/Server. Nadal dostępny jest zaawansowany JSON.", + "vhNewHealthHint": "Zapisz vhost, aby rozpocząć obserwację backendów.", + "listenerAdd": "Dodaj listener", + "listenerMigrate": "Zamień domyślne porty na nazwane listenery", + "listenerScopeHint": "Wewnętrzny / publiczny to etykieta, nie firewall. Nowe listenery nie udostępniają starych tras bez jawnego wyboru.", + "listenerLimit": "Maksymalnie 64 nazwane listenery.", + "listenerEndpoint": "Listener", + "listenerName": "Nazwa wyświetlana", + "listenerScope": "Etykieta", + "listenerInternal": "Wewnętrzny", + "listenerPublic": "Publiczny", + "listenerLegacy": "Udostępnij stare trasy", + "listenerInUse": "Listener jest przypisany do vhostów", + "listenerDuplicate": "Identyfikatory listenerów muszą być unikalne.", + "listenerChooseHint": "Wybierz nazwane listenery skonfigurowane w Ustawienia > Listenery. Wyłączony listener nie daje dostępu.", + "listenerMissing": "Brakujący listener", + "http3Settings": "HTTP/3 / QUIC", + "http3SettingsHelp": "HTTP/3 włącza się w Ustawienia > Listenery. Tutaj ustawisz ogłaszanie usługi oraz limity połączeń i strumieni.", + "http3NoTlsListeners": "Brak listenerów TLS. Najpierw dodaj i zapisz listener TLS.", + "http3NoEnabledListeners": "Żaden listener TLS nie ma włączonego HTTP/3. Najpierw włącz HTTP/3 w Ustawienia > Listenery.", + "http3ListenersDirty": "Najpierw zapisz oczekujące zmiany w Listenerach, a następnie edytuj ustawienia HTTP/3.", + "http3Save": "Zapisz ustawienia HTTP/3", + "h2Configure": "Ustaw limity HTTP/2", + "h3Enable": "Włącz HTTP/3 przez QUIC (UDP)", + "h3Configure": "Ustaw limity HTTP/3", + "h3Limits": "Limity HTTP/3 i ogłaszanie usługi", + "h3_alt_svc_max_age": "Ważność Alt-Svc (s)", + "h3_max_concurrent_connections": "Połączenia na listener", + "h3_max_concurrent_bidistream": "Strumienie dwukierunkowe na połączenie", + "h3_max_concurrent_unistream": "Strumienie jednokierunkowe na połączenie", + "h3_max_idle_timeout": "Bezczynność QUIC (s; 0 = bez limitu)", + "h3Help": "HTTP/3 wymaga TLS. Udostępnij TCP i UDP na tym samym porcie; HTTP/1.1 i HTTP/2 pozostają. Zero wyłącza lokalny timeout QUIC. Zmiana wymaga restartu.", + "h3TuningHelp": "Te limity obowiązują tylko wtedy, gdy HTTP/3 jest włączone dla danego listenera TLS. Zmiana wymaga restartu.", + "h3Requests": "Żądania HTTP/3", + "certImport": "Importuj certyfikat", + "certImportSave": "Importuj i zapisz w bibliotece", + "certLibraryHint": "Wgraj certyfikat/fullchain i klucz prywatny PEM. Zapisz raz, następnie przypisz certyfikat do jednego lub wielu vhostów.", + "certCheckDomains": "Domeny do sprawdzenia (po przecinku)", + "certAcmeLater": "Wydane certyfikaty trafiaja do biblioteki i sa przypisywane do vhostow.", + "certAdvanced": "Istniejące pliki / zaawansowane", + "certLegacyNames": "Stare nazwy SNI (opcjonalne; zalecane przypisanie w vhoście)", + "certAssigned": "Przypisane vhosty", + "certUnassigned": "Nieprzypisany", + "certInUse": "Certyfikat jest używany. Zmień przypisania vhostów i certyfikat domyślny przed usunięciem.", + "certDuplicate": "Ten identyfikator certyfikatu istnieje. Do bezpiecznej rotacji użyj nowego.", + "certManage": "Zarządzaj biblioteką certyfikatów", + "certSelectHint": "Wybierz zaimportowany certyfikat. Import i rotacja są w zakładce Certyfikaty. Przypisania są sprawdzane względem nazw vhosta.", + "vhTinyauth": "Uwierzytelnianie i dostęp IP", + "vhTabAuth": "Uwierzytelnianie", + "taEnable": "Włącz ochronę Tinyauth", + "taUrl": "URL Tinyauth / punkt weryfikacji", + "taLogin": "Publiczny URL logowania", + "taRedirectForbidden": "Akceptuj przekierowanie Tinyauth dla 403 (ten sam origin)", + "taConnect": "Limit połączenia (s)", + "taTimeout": "Łączny limit weryfikacji (s)", + "taRequestHeaders": "Nagłówki klienta do Tinyauth", + "taResponseHeaders": "Zweryfikowane nagłówki tożsamości do aplikacji", + "taHelp": "Do adresu serwera dodawane jest /api/auth/nginx. Weryfikacja bez body działa przed cache i WebSocket. Chronione odpowiedzi mają private, no-store. Ustaw w Tinyauth zaufanie do rzeczywistego IP RustProxy.", + "vhResponsePolicy": "Identyfikacja odpowiedzi i diagnostyka", + "vhInherit": "Dziedzicz ustawienia globalne", + "diagnosticHeaders": "Diagnostyczne nagłówki odpowiedzi", + "diagInherit": "Dziedzicz nagłówki diagnostyczne", + "diagRequestId": "Udostępnij X-Request-ID", + "hideMockOrigin": "Ukryj X-Mock-Origin", + "diagHelp": "X-Request-ID jest opcjonalny. Pozostale naglowki odpowiedzi upstreamu kontroluj zwyklymi regulami naglowkow odpowiedzi.", + "forwardingSettings": "Tożsamość klienta / forwarding", + "forwardingHelp": "Domyślnie nagłówki forwarding od klienta są usuwane. Dodaj tylko kontrolowane proxy. Łańcuch jest sprawdzany od prawej do pierwszego niezaufanego adresu.", + "forwardingCanonical": "RustProxy generuje Forwarded, X-Forwarded-For, X-Real-IP, X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port i X-Forwarded-URI. Adres docelowy połączenia nigdy nie pochodzi z nagłówka.", + "trustedProxies": "Zaufane sieci proxy CIDR (po jednej w wierszu; /32 dla IPv4, /128 dla IPv6)", + "trustForwardedProto": "Ufaj X-Forwarded-Proto tylko od zaufanych proxy", + "listenerId": "ID listenera", + "cache": "Cache", + "cacheTitle": "Cache pod kontrolą.", + "cacheDescription": "Obiekty, skuteczność domen i dokładne przyczyny decyzji.", + "cacheRefresh": "Odśwież", + "cacheLive": "Na żywo (3 s)", + "cachePurgeScope": "Wyczyść wybrany zakres", + "cacheObjectCount": "Obiekty w pamięci", + "cacheSaved": "Dane odpowiedzi z RAM", + "cacheCounterScope": "Liczniki domen od zmiany konfiguracji", + "cacheDomains": "Domeny i skuteczność", + "cacheScopeHint": "Limity RAM, zapisy i usunięcia są globalne. Liczniki domen zerują się przy zmianie konfiguracji; czyszczenie obiektów ich nie zeruje.", + "cacheDomain": "Domena", + "cacheReasons": "Liczniki decyzji", + "cacheNoDomains": "Brak decyzji cache. Wyślij żądania do skonfigurowanego hosta.", + "cacheObjectList": "Zapisane obiekty", + "cachePrevious": "Poprzednie", + "cacheNext": "Następne", + "cacheObjectHint": "Najpierw ostatnio używane. Kliknij Target albo Cache key / tags, aby skopiować pełną wartość. Treści i nagłówki żądań nie są ujawniane.", + "cachePath": "Ścieżka", + "cacheBodySize": "Treść", + "cacheAge": "Wiek", + "cacheTtlRemaining": "Pozostały TTL", + "cacheNoObjects": "Brak świeżych obiektów w tym zakresie. Sprawdź powyższe liczniki decyzji.", + "cacheLegacy": "Nieprzypisane", + "cacheGlobalLimit": "Limit globalny", + "cacheFills": "Aktywne zapisy (globalnie)", + "cacheRejected": "Odrzucone zapisy (globalnie)", + "cacheUpdated": "Aktualizacja", + "acmeTitle": "Let's Encrypt / ACME", + "acmeHelp": "Wydaj certyfikat ACME, dodaj go do biblioteki i przypisz do vhosta. Automatyczne odnawianie rozpocznie się po imporcie.", + "acmeEmail": "Email kontaktowy ACME", + "acmeDomains": "Nazwy DNS, rozdzielone przecinkami (wildcard tylko z DNS-01)", + "acmeEnvironment": "Środowisko CA", + "acmeStaging": "Staging - niezaufane przez przeglądarkę", + "acmeProduction": "Produkcja", + "acmeDnsServer": "IP serwera aktualizacji DNS", + "acmeDnsPort": "Port aktualizacji DNS", + "acmeKeyName": "Nazwa klucza TSIG", + "acmeSecret": "Sekret TSIG (base64)", + "acmeAlgorithm": "Algorytm TSIG", + "acmePropagation": "Oczekiwanie na propagację DNS (sekundy)", + "acmeSignQuery": "Podpisuj zapytanie SOA", + "acmeTos": "Akceptuje umowe subskrybenta Let's Encrypt i zezwalam na walidacje domen.", + "acmeConfirmProduction": "Potwierdzam wydanie produkcyjne (obowiązują limity CA).", + "acmeIssue": "Zamów certyfikat", + "acmeIdle": "Brak zadania wydania w tym procesie.", + "acmeImport": "Dodaj wydany certyfikat do biblioteki", + "acmeRenewalHint": "Pierwsze wydanie/import pozostaje ręczne. Profile zarządzanych certyfikatów są następnie sprawdzane automatycznie podczas działania RustProxy; po restarcie zapisany stan odnowień jest odtwarzany z SQLite.", + "acmeReplace": "Zastąpić certyfikat w bibliotece? Przypisania vhostów zostaną zachowane i ponownie sprawdzone.", + "acmeImported": "Certyfikat zapisany. Przypisz go w zakładce SSL vhosta.", + "certFuture": "Automatyczne odnawianie jest dostępne dla obsługiwanych dostawców ACME; obserwacje odnowień są zapisywane w SQLite.", + "cacheReason_cache_hit": "Użyto świeżego obiektu", + "cacheReason_cache_miss": "Brak pasującego świeżego obiektu", + "cacheReason_cache_disabled": "Cache wyłączony", + "cacheReason_route_disabled": "Cache wyłączony na trasie", + "cacheReason_request_cookie": "Cookie aplikacji", + "cacheReason_request_authorization": "Nagłówek Authorization", + "cacheReason_request_cache_control": "Dyrektywa klienta / twarde odświeżenie", + "cacheReason_request_pragma": "Pragma klienta", + "cacheReason_request_method": "Metoda inna niż GET", + "cacheReason_request_body": "Niepusta treść żądania", + "cacheReason_request_range": "Żądanie zakresu", + "cacheReason_request_conditional": "Żądanie warunkowe", + "cacheReason_request_upgrade": "Zmiana protokołu", + "cacheReason_query_excluded": "Wykluczone query", + "cacheReason_path_excluded": "Wykluczona ścieżka", + "cacheReason_request_cache_control_invalid": "Błędne dyrektywy cache klienta", + "cacheReason_response_status": "Odpowiedź inna niż 200", + "cacheReason_response_set_cookie": "Set-Cookie z backendu", + "cacheReason_response_cache_control": "Prywatność / dyrektywa backendu", + "cacheReason_response_cache_control_invalid": "Błędne dyrektywy backendu", + "cacheReason_response_vary": "Niedozwolony wariant Vary", + "cacheReason_response_range": "Odpowiedź częściowa", + "cacheReason_response_trailers": "Trailers odpowiedzi", + "cacheReason_response_ttl_size_or_metadata": "Odrzucony TTL, rozmiar lub metadane", + "cacheReason_response_headers_policy": "Reguła nagłówków blokuje zapis", + "cacheReason_configuration_changed": "Zmieniona konfiguracja", + "cacheReason_not_proxy_response": "Strona lokalna / odrzucone żądanie", + "cacheReason_tinyauth": "Treść chroniona Tinyauth", + "cacheReason_sticky_cookie": "Tryb cookie sticky session", + "acme.working": "Trwa zamawianie certyfikatu.", + "acme.ready_to_import": "Certyfikat sprawdzony; gotowy do dodania do biblioteki.", + "acme.certbot_not_installed": "Brak Certbota w PATH usługi. Zainstaluj Certbota i certbot-dns-rfc2136.", + "acme.certbot_failed_check_private_log": "Błąd Certbota. Sprawdź prywatne logi ACME; dokładna ścieżka jest pokazana przy błędzie operacji.", + "acme.production_confirmation": "Wymagane potwierdzenie wydania produkcyjnego.", + "acme.consent_required": "Zaakceptuj umowę i potwierdź wybrane środowisko CA.", + "acme.provider_timeout": "Przekroczono czas operacji CA. Przed ponowieniem sprawdź logi Certbota wskazane przy błędzie operacji.", + "acme.invalid_key_secret": "Nieprawidłowy sekret TSIG: wymagany base64 kodujący 16-512 bajtów.", + "acme.job_busy": "Inne zadanie certyfikatu już trwa.", + "acme.cancelled": "Zadanie certyfikatu anulowane podczas zamykania.", + "acme.certificate_validation_failed": "Walidacja certyfikatu, klucza lub domen nie powiodła się. Nic nie aktywowano.", + "acme.invalid_request": "Nieprawidłowe żądanie ACME.", + "acme.unknown_field": "Żądanie zawiera nieobsługiwane pole.", + "acme.invalid_id": "Identyfikator certyfikatu: 1-64 litery, cyfry, myślnik lub podkreślenie.", + "acme.invalid_email": "Podaj prawidłowy adres e-mail kontaktu ACME.", + "acme.invalid_domains": "Podaj od 1 do 16 nazw DNS.", + "acme.invalid_domain": "Podaj prawidłową nazwę DNS (ASCII/punycode).", + "acme.dns_names_only": "Certyfikat wymaga nazw DNS, nie adresów IP.", + "acme.invalid_environment": "Wybierz staging albo produkcję.", + "acme.tos_required": "ACME: tos required.", + "acme.invalid_dns_server": "Podaj adres IPv4 lub IPv6 serwera aktualizacji DNS.", + "acme.invalid_limits": "Port musi mieścić się w 1-65535, a czas propagacji w 10-900 s.", + "acme.invalid_key_algorithm": "Dozwolone algorytmy TSIG: HMAC-SHA256, HMAC-SHA384 i HMAC-SHA512.", + "acme.invalid_sign_query": "Opcja podpisywania SOA musi mieć wartość true albo false.", + "acme.unsafe_directory": "Katalog certyfikatów musi należeć do usługi i mieć prawa 0700; bez dowiązań.", + "acme.unsafe_output_path": "Plik wyniku wykracza poza prywatne archiwum Certbota.", + "acme.invalid_pem_size": "Plik PEM jest za duży lub nie jest zwykłym plikiem.", + "acme.invalid_pem": "Certbot nie zwrócił prawidłowego pliku PEM.", + "acme.profile_limit": "Osiągnięto limit 128 profili ACME. Sprawdź nieużywane profile lokalnie.", + "acme.profile_busy": "Ten profil ACME jest już używany przez inny proces.", + "acme.staging_limit": "Osiągnięto limit 128 par ACME. Usuń lokalnie wyłącznie nieużywane pliki.", + "acme.request_too_large": "Żądanie ACME przekracza 16 KiB.", + "acme.local_io_or_json_error": "Błąd lokalnego pliku lub formatu JSON. Sprawdź katalog i uprawnienia.", + "acme.no_certificate_directory": "Brak katalogu certyfikatów w konfiguracji uruchomienia.", + "acme.python_not_available": "Proces usługi nie może uruchomić python3. Sprawdź PATH i uprawnienia.", + "acme.worker_io": "Nie udała się komunikacja z lokalnym procesem ACME.", + "acme.invalid_worker_response": "Proces ACME zwrócił nieprawidłowy wynik.", + "acme.worker_failed": "Proces ACME zakończył się błędem.", + "acme.validation_worker_failed": "Nie udało się zakończyć walidacji pary certyfikatu i klucza.", + "cacheResidentLabel": "Pamięć cache", + "certificateFuture": "Dostawcy ACME: HTTP-01, Cloudflare, OVH i RFC2136. Odnawianie jest ręczne.", + "prometheusTitle": "Prometheus", + "prometheusHelp": "Osobny endpoint metryk, niezależny od sesji administratora. Domyślnie wyłączony.", + "prometheusEnable": "Włącz endpoint Prometheusa", + "prometheusListen": "IP i port eksportera", + "prometheusPath": "Ścieżka metryk", + "prometheusCidrs": "Dozwolone adresy kolektora (CIDR, po jednym w wierszu)", + "prometheusTokenEnv": "Nazwa zmiennej środowiskowej z tokenem", + "prometheusTokenHelp": "Podaj nazwę zmiennej, nie sekret. Token musi mieć co najmniej 32 znaki. Zmiana adresu lub tokenu wymaga restartu.", + "prometheusCollectors": "Grupy metryk", + "prometheusPreview": "Podgląd metryk", + "prometheusPreviewFailed": "Nie udało się pobrać metryk.", + "prometheusActive": "Aktywny endpoint", + "prometheusDisabled": "Endpoint metryk jest wyłączony.", + "certificateProvider": "Dostawca certyfikatu", + "providerHttp01": "Let's Encrypt - walidacja domeny (HTTP-01)", + "providerCloudflare": "Let's Encrypt - Cloudflare API (DNS-01)", + "providerOvh": "Let's Encrypt - OVH API (DNS-01)", + "providerRfc2136": "Let's Encrypt - RFC2136 / TSIG (DNS-01)", + "providerImport": "Import certyfikatu i klucza prywatnego (PEM)", + "providerExisting": "Istniejące pliki na serwerze / ścieżki zewnętrzne", + "providerHint_http01": "RustProxy uruchamia Certbota w trybie HTTP-01. Nie podajesz tutaj ścieżek do certyfikatu. Każda domena musi wskazywać na ten serwer, a publiczny TCP/80 musi trafiać do listenera HTTP. Wydana para trafia do magazynu certyfikatów RustProxy. Wildcard wymaga DNS-01.", + "providerHint_cloudflare": "RustProxy uruchamia Certbota DNS-01 z pluginem Cloudflare. Nie podajesz tutaj ścieżek do certyfikatu. Użyj tokenu Zone:DNS:Edit ograniczonego do wymaganych stref; wydana para trafia do magazynu RustProxy i może być odnawiana automatycznie.", + "providerHint_ovh": "RustProxy uruchamia Certbota DNS-01 z pluginem OVH. Nie podajesz tutaj ścieżek do certyfikatu. Podaj poświadczenia ograniczone do wymaganych stref DNS; wydana para trafia do magazynu RustProxy i może być odnawiana automatycznie.", + "providerHint_rfc2136": "RustProxy uruchamia Certbota DNS-01 przez RFC2136/TSIG. Nie podajesz tutaj ścieżek do certyfikatu. Serwer DNS musi przyjmować podpisane aktualizacje rekordów walidacyjnych; wydana para trafia do magazynu certyfikatów RustProxy.", + "providerHint_import": "Wybierz z komputera certyfikat/fullchain i odpowiadający klucz prywatny PEM. RustProxy sprawdzi parę i skopiuje ją do /certs; lokalna ścieżka z przeglądarki nie jest zapisywana.", + "providerHint_existing": "Wskaż pliki, które już istnieją na serwerze RustProxy. Wybierz nazwy plików z /certs albo pełne ścieżki zewnętrzne, np. /etc/letsencrypt/live/example.com/fullchain.pem i privkey.pem. Pliki zewnętrzne nie są kopiowane do backupu RustProxy.", + "acmeCfToken": "Token API Cloudflare", + "acmeOvhEndpoint": "Region API OVH", + "acmeOvhApplicationKey": "OVH application key", + "acmeOvhApplicationSecret": "OVH application secret", + "acmeOvhConsumerKey": "OVH consumer key", + "acme.invalid_provider": "Nieobsługiwany dostawca certyfikatu.", + "acme.invalid_api_credential": "Brak klucza API lub niedozwolone znaki w kluczu.", + "acme.invalid_ovh_endpoint": "Wybierz OVH Europa lub Kanada.", + "acme.http01_no_wildcard": "HTTP-01 nie obsługuje wildcardów. Wybierz dostawcę DNS-01.", + "acme.http01_listener_required": "HTTP-01 wymaga aktywnego listenera HTTP bez TLS.", + "cacheScopeLegacy": "Nieprzypisany / poprzedni zakres", + "certificateLibrary": "Certyfikaty", + "certificateWorkspaceHelp": "Zarządzaj zainstalowanymi certyfikatami, automatycznym odnawianiem i generowaniem/importem w osobnych widokach.", + "certificateTabInstalled": "Zainstalowane", + "certificateTabRenewal": "Odnowienie", + "certificateTabGenerate": "Generowanie", + "certificateInstalledTitle": "Zainstalowane certyfikaty", + "certificateInstalledHelp": "Lista certyfikatów dostępnych dla vhostów, redirectów, hostów 404 i panelu administracyjnego.", + "certificateGenerateTitle": "Generowanie i import", + "certificateGenerateHelp": "Wybierz metodę: Let's Encrypt/ACME, import plików PEM albo istniejące pliki z prywatnego katalogu RustProxy.", + "certificateOperationStatus": "Ostatnia operacja ACME", + "certificateNoRenewalOperation": "Brak operacji odnowienia w tym procesie.", + "certificateStateIdle": "Bezczynne", + "certificateStateRunning": "W toku", + "certificateStateReady": "Gotowe", + "certificateStateComplete": "Zakończone", + "certificateStateFailed": "Błąd", + "certbotLogs": "Logi Certbota", + "systemDiagnostics": "System / proces", + "vhWebsockets": "Obsługa WebSocket", + "vhLocations": "Własne lokalizacje", + "locationPath": "Prefiks lokalizacji", + "locationPool": "Backendy vhosta", + "locationMissing": "Brak backendu", + "locationLimit": "Host obsługuje do 64 lokalizacji.", + "locationAccess": "Dostęp do lokalizacji", + "locationHeaders": "Nagłówki lokalizacji", + "locationAddBackend": "Dodaj osobny backend", + "locationAdd": "Dodaj lokalizację", + "locationHelp": "Wygrywa najdłuższy pasujący prefiks. Ścieżka i query pozostają bez zmian. Wybierz backend hosta lub dodaj osobny. Uwierzytelnianie i ACL hosta obowiązują zawsze.", + "aclUse": "Włącz listę dostępu IP", + "aclAllow": "Zezwól", + "aclNetwork": "Sieć IP (CIDR)", + "aclAction": "Akcja", + "aclAdd": "Dodaj regułę IP", + "authMode": "Uwierzytelnianie", + "authNone": "Brak", + "authTinyauth": "Tinyauth", + "authBasic": "HTTP Basic Auth", + "basicRealm": "Realm (nazwa logowania)", + "basicRequireTls": "Wymagaj HTTPS dla Basic Auth", + "basicForward": "Przekaż zweryfikowany Authorization do backendu", + "basicPassword": "Hasło (12-128 bajtów)", + "basicKeepPassword": "Puste pole zachowuje hasło", + "basicHashSaved": "Hasło ustawione", + "basicHashRequired": "Ustaw hasło dla każdego użytkownika.", + "basicAddUser": "Dodaj użytkownika", + "basicHelp": "Zapisywane są wyłącznie skróty Argon2id. Dane logowania nie trafiają do backendu, chyba że włączysz przekazywanie. Używaj go tylko z zaufanym backendem HTTPS lub w izolowanej sieci. Chronione hosty omijają współdzielony cache.", + "basic.too_many_users": "Maksymalnie 64 użytkowników na hosta.", + "basic.invalid_username": "Nieprawidłowa nazwa użytkownika.", + "basic.duplicate_username": "Powtórzona nazwa użytkownika.", + "basic.unsupported_hash": "Użyj skrótu Argon2id wygenerowanego przez RustProxy.", + "basic.invalid_hash": "Nieprawidłowy skrót hasła.", + "basic.invalid_realm": "Nieprawidłowa nazwa realm.", + "basic.users_required": "Basic Auth wymaga przynajmniej jednego użytkownika.", + "basic.busy": "Weryfikacja haseł jest zajęta. Spróbuj ponownie.", + "renewalTitle": "Automatyczne odnawianie", + "renewalHelp": "Certyfikaty ACME są sprawdzane co około 12 godzin, gdy RustProxy działa. Certbot określa termin odnowienia. Poprawne certyfikaty są wdrażane bez restartu listenerów. Zachowaj dane dostawcy i dostępność portu 80 dla HTTP-01.", + "renewalNone": "W bibliotece nie ma zarządzanych certyfikatów ACME.", + "renewalEnabled": "Automatyczne odnawianie włączone", + "renewalDisabled": "Automatyczne odnawianie wyłączone", + "renewalEnable": "Włącz odnawianie", + "renewalDisable": "Wyłącz odnawianie", + "renewalNow": "Sprawdź teraz", + "renewalDryRun": "Sprawdź odnowienie (dry run)", + "renewalLast": "Ostatnia kontrola", + "renewalNext": "Kolejna kontrola", + "renewalExpires": "Ważny do", + "acme.renewing": "Sprawdzanie odnowienia certyfikatu", + "acme.renewed": "Certyfikat odnowiony i wdrożony", + "acme.not_due": "Certyfikat nie wymaga jeszcze odnowienia", + "acme.renewal_test_ok": "Weryfikacja odnowienia poprawna; aktywny certyfikat bez zmian", + "acme.renewal_profile_missing": "Brak dostępu do zapisanego profilu odnowienia. Przywróć prywatny profil ACME.", + "acme.configuration_changed": "Konfiguracja certyfikatu zmieniła się podczas odnowienia. Nie wdrożono zmian.", + "acme.deployment_failed": "Odnowiony certyfikat nie przeszedł walidacji konfiguracji. Poprzedni nadal jest aktywny.", + "acme.not_managed": "Automatycznie można odnawiać tylko zarządzane certyfikaty ACME.", + "acme.certificate_not_found": "Certyfikat już nie istnieje.", + "acme.renewal_disabled": "Automatyczne odnawianie jest wyłączone.", + "acme.openssl_not_installed": "OpenSSL jest wymagany do odczytu dat ważności certyfikatu.", + "cacheReason_basic_auth": "Uwierzytelnianie HTTP Basic", + "locationPoolEligible": "Uwzględnij w domyślnej puli backendów", + "locationId": "Identyfikator lokalizacji", + "vhIpAccess": "Dostęp do vhosta według IP", + "apiDocs": "Dokumentacja", + "auth.invalid_request": "Nieprawidłowe żądanie. Sprawdź podane pola.", + "locationProxyHeaders": "Nagłówki żądania proxy (do backendu)", + "locationResponseHeaders": "Nagłówki odpowiedzi (do klienta)", + "locationHeadersHelp": "Ustaw nadpisuje nagłówek, dopisz dodaje wartość, usuń kasuje go. Reguły lokalizacji są stosowane po globalnych i vhosta. Wartości są dosłowne (bez zmiennych nginx). Nagłówkami transportu i zweryfikowanej tożsamości zarządza system.", + "headersAdvancedJson": "Zaawansowane reguły nagłówków (JSON)", + "headersInvalid": "Nieprawidłowa reguła nagłówka. Sprawdź nazwę, operację i wartość.", + "headersProtected": "Nie można tu nadpisać nagłówków transportu i tożsamości proxy. Użyj ustawień forwardingu, autentykacji lub odpowiedzi.", + "docsTitle": "Dokumentacja", + "docsSession": "Korzysta z sesji administratora. Operacje zapisu API wymagają ochrony CSRF.", + "docsSignIn": "Zaloguj się, aby otworzyć dokumentację", + "docsPanel": "Powrót do panelu", + "docsWrites": "Włącz operacje zapisu", + "docsWriteConfirm": "Ta operacja naprawdę zmieni RustProxy. Kontynuować?", + "docsExpired": "Sesja wygasła. Zaloguj się ponownie.", + "docsError": "Nie udało się wczytać dokumentacji.", + "docsAccount": "Zalogowano jako", + "docsReadOnly": "Domyślnie wykonywane są tylko odczyty. Strumienie SSE oraz logowanie i setup nie są tu uruchamiane.", + "docsLogout": "Wyloguj", + "authPrivateNote": "Dostęp tylko dla uprawnionych administratorów.", + "advanced": "Zaawansowane", + "docsLicenses": "Licencje Swagger UI", + "vhTrafficPolicy": "Polityka ruchu", + "vhHttp3Enabled": "Włącz HTTP/3 dla tego vhosta", + "vhCloudflareClientIp": "Używaj zweryfikowanego IP klienta z Cloudflare", + "vhTimingHeaders": "Udostępniaj diagnostykę Server-Timing", + "vhRateEnabled": "Włącz limit żądań per IP klienta", + "vhRateRequests": "Żądania na okno", + "vhRateWindow": "Okno (sekundy)", + "vhRateBurst": "Dopuszczalny burst", + "vhCountryMode": "Polityka dostępu krajów", + "vhCountryBlock": "Blokuj wskazane kraje", + "vhCountryAllow": "Zezwalaj tylko wskazanym krajom", + "vhCountryList": "Kraje (ISO 3166-1 alpha-2)", + "vhCountryPolicyHelp": "Reguły krajów korzystają z bazy GeoIP. W trybie „tylko zezwolone” dostęp daje wskazany kraj LUB jawnie dozwolone IP. W trybie blokowania ruch odrzuca wskazany kraj LUB jawnie zablokowane IP. Nieustalony kraj nie spełnia gałęzi kraju.", + "vhCountry": "Kraj", + "vhAddCountryRate": "Dodaj limit dla kraju", + "vhCountryRateLimit": "Dozwolone są maksymalnie 64 limity przypisane do krajów.", + "vhRateHelp": "Limit dla kraju nadpisuje podstawowy limit per IP. Przekroczenie daje 429, a zabroniony kraj 403.", + "vhHealthOffWarning": "Aktywne sondy backendu są wyłączone. Passive Backend Health jest konfigurowany osobno i pozostaje wyłączony, dopóki nie zostanie świadomie włączony.", + "vhFrontendSelectionOnly": "Wybierz listenery dla vhosta. Adresy IP, porty oraz dostępność H2/H3 konfiguruje się wyłącznie w Ustawienia > Listenery.", + "vhForceSsl": "Wymuś SSL (przekieruj HTTP do HTTPS)", + "vhForceSslHelp": "Żądania HTTP tego vhosta dostają przekierowanie 308 do HTTPS z zachowaniem ścieżki i parametrów. Wybierz co najmniej jeden listener zwykłego HTTP oraz co najmniej jeden listener TLS. Jeśli wybrano kilka portów TLS, używany jest port 443; w innym przypadku port docelowy TLS musi być jednoznaczny.", + "vhForceSslNeedsTls": "Wymuszenie SSL wymaga co najmniej jednego włączonego listenera TLS dla tego vhosta.", + "vhForceSslPortConflict": "Nie można jednoznacznie wybrać portu docelowego HTTPS: wybrane listenery TLS używają kilku niestandardowych portów. Wybierz jeden port TLS albo listener na porcie 443.", + "vhForwardClientIp": "Przekazuj IP klienta do backendu", + "vhForwardClientIpHelp": "Pozostaw włączone dla standardowego reverse proxy. Wyłącz, aby nie wysyłać do aplikacji nagłówków Forwarded, X-Forwarded-For i X-Real-IP; RustProxy, ACL, limity i Tinyauth nadal korzystają z prawdziwego IP klienta.", + "vhClientIpHidden": "IP klienta ukryte przed backendem", + "listenerMigrateInline": "Istniejący listener vhosta (zostanie zmigrowany)", + "vhDebugOriginPreset": "Zastosuj preset zalecany", + "vhDebugOriginTlsWarning": "Ten preset pozostawia wyłączoną walidację certyfikatu TLS backendu. Połączenie jest szyfrowane, ale CA i nazwa hosta nie są sprawdzane. Kontynuować?", + "vhOriginPresetHelp": "Domyślne ustawienia nowego backendu do debugowania: origin HTTPS po IP, domyślna pula LB ON, prywatny origin ON, HTTP/1.1, zachowanie Host klienta ON i weryfikacja TLS OFF. Włącz weryfikację TLS, gdy certyfikat originu da się poprawnie zweryfikować.", + "vhDebugOriginMismatch": "Ten origin HTTPS po IP nie odpowiada presetowi debugowania (pula ON, prywatny origin ON, HTTP/1.1, zachowanie Host ON, walidacja TLS OFF). Jeśli włączysz walidację, ustaw nazwę serwera TLS na DNS z certyfikatu i w razie potrzeby dodaj jego prywatne CA. Kontynuować z bieżącymi wartościami?", + "vhCacheStaticCookie": "Ignoruj Cookie żądania dla wybranych rozszerzeń statycznych", + "vhCacheStaticCustom": "Użyj własnej listy rozszerzeń statycznych", + "vhCacheStaticExtensions": "Rozszerzenia statyczne (po jednym w linii, bez kropki)", + "vhCacheStaticHelp": "Dziedzicz ustawienie globalne albo nadpisz je dla vhosta. Ignorowanie Cookie stosuj tylko do plików, których treść nie zależy od sesji; Set-Cookie, private/no-store i Vary: Cookie nadal blokują zapis do shared cache.", + "cacheStaticTitle": "Cache statycznych zasobów", + "cacheStaticCookie": "Ignoruj Cookie żądania dla statycznych plików", + "cacheStaticExtensions": "Globalne rozszerzenia statyczne (po jednym w linii, bez kropki)", + "cacheStaticHelp": "Włączaj tylko dla plików, których treść nie zależy od sesji. Cookie żądania nie wpływa wtedy na klucz cache dla podanych rozszerzeń; odpowiedzi z Set-Cookie, private/no-store lub Vary: Cookie nadal nie są zapisywane we współdzielonym cache.", + "vhHttp2Enabled": "Włącz HTTP/2 dla tego vhosta", + "vhFrontendProtocolsHelp": "H2 i H3 są przełącznikami per vhost. Opcja jest aktywna tylko wtedy, gdy co najmniej jeden wybrany listener ma dany protokół włączony w Ustawienia > Listenery.", + "administration": "Panel administracyjny", + "administrationActiveHttp": "Aktywny panel HTTP", + "administrationActiveHttps": "Aktywny panel HTTPS", + "administrationCertificate": "Certyfikat", + "administrationGeneratedCertificate": "Domyślny certyfikat wygenerowany przez aplikację", + "administrationHelp": "HTTPS pozwala wystawić panel administracyjny poza loopback. Zwykły HTTP na porcie HTTPS jest automatycznie przekierowywany. Zmiany listenera i certyfikatu działają po restarcie.", + "administrationHosts": "Dozwolone hosty DNS (opcjonalna ścisła lista)", + "administrationHttps": "Włącz HTTPS dla panelu administracyjnego", + "administrationListen": "Adres nasłuchu", + "cacheOverhead": "Narzut", + "cacheResidentAge": "X-Cache-Resident-Age", + "controlPlane": "PANEL STEROWANIA", + "logging": "Logowanie", + "loggingTitle": "Strukturalne logi dostępu", + "loggingTabOutputs": "Wyjścia", + "loggingTabFormat": "Format JSON", + "loggingTabErrors": "Log błędów", + "loggingDescription": "Zapisuj konfigurowalne logi dostępu JSON do pliku, Logstash lub Loki.", + "loggingHelp": "Podgląd logów na żywo pozostaje w pamięci. Zewnętrzne wyjścia używają ograniczonej kolejki w tle i nie blokują obsługi żądań.", + "loggingFile": "Plik JSON", + "loggingPath": "Ścieżka pliku", + "loggingAddress": "Adres TCP", + "loggingTokenEnv": "Zmienna środowiskowa z tokenem Bearer", + "loggingLabels": "Etykiety (obiekt JSON)", + "loggingFormat": "Konfigurowalny format JSON", + "loggingFormatHelp": "Mapuj nazwy pól wyjściowych na pola źródłowe. Puste mapowanie zapisuje pełny standardowy wpis.", + "loggingFields": "Mapowanie pól (obiekt JSON)", + "loggingStatic": "Pola stałe (obiekt JSON)", + "loggingJsonInvalid": "Pola konfiguracji logów muszą zawierać poprawne obiekty JSON.", + "loggingQueue": "Kolejka wyjść", + "loggingDropped": "odrzuconych", + "loggingSent": "wysłanych", + "loggingCapacity": "pojemność", + "errorLogTitle": "Plik błędów", + "errorLogRoutingHelp": "Ostrzeżenia, błędy i paniki wątków trafiają do tego pliku, gdy jest włączony. Gdy wyłączony, trafiają do konsoli/systemd. W razie błędu zapisu używana jest konsola.", + "errorLogPath": "Ścieżka logu błędów", + "errorLogFormat": "Format", + "tls12": "TLS 1.2", + "tls13": "TLS 1.3", + "tlsPolicy": "Polityka TLS", + "tlsPolicyHelp": "To globalna polityka TLS dziedziczona przez każdy host TLS w aplikacji. Ustawienia per-vhost mogą ją tylko zawężać. HTTP/3 wymaga TLS 1.3.", + "tlsCiphers": "Szyfry (jeden na linię; puste = domyślne rustls)", + "tlsAvailableCiphers": "Dostępne szyfry", + "tlsPolicyVersionRequired": "Włącz co najmniej jedną wersję TLS.", + "vhTlsOverride": "Nadpisz globalną politykę TLS", + "vhTlsOverrideHelp": "Użyj tylko, gdy ten vhost wymaga innych wersji TLS lub szyfrów. Pusta lista szyfrów zachowuje domyślne rustls.", + "vhTlsHttp3Conflict": "HTTP/3 korzysta z globalnej polityki TLS. Wyłącz HTTP/3 dla tego vhosta przed użyciem innego nadpisania TLS.", + "vhListeners": "Listenery", + "vhProtocols": "Protokoły", + "loggingMode": "JSON / ASYNC / OGRANICZONA KOLEJKA", + "loggingPushUrl": "Adres Push URL", + "cacheHotObjects": "Obiekty hot", + "cacheHotMemory": "Pamięć hot", + "cacheColdObjects": "Obiekty cold", + "cacheColdDisk": "Dysk cold", + "cacheTierMoves": "Przeniesienia między warstwami", + "cacheColdHits": "trafień cold", + "cacheTier": "Warstwa", + "cacheStorage": "Pamięć / dysk", + "cacheColdTitle": "Trwały cache cold", + "cacheColdEnable": "Włącz trwałą warstwę dyskową", + "cacheColdHelp": "Wybierz, które obiekty z hot RAM mają trafiać na trwały dysk. Trafienie cold jest odczytywane asynchronicznie i promowane z powrotem do RAM. Metadane są w JSON, a body w plikach .body.", + "cacheColdPath": "Ścieżka cold (wewnątrz katalogu danych)", + "cacheColdBytes": "Limit dysku cold (MiB)", + "cacheColdEntries": "Limit obiektów cold", + "cacheColdAfter": "Przenieś po sekundach bezczynności", + "cacheHot": "Hot", + "cacheCold": "Cold", + "cacheReason_request_no_cache": "no-cache klienta / twarde odświeżenie wymaga rewalidacji", + "cacheReason_request_max_age": "max-age klienta wymaga rewalidacji", + "cacheReason_request_min_fresh": "min-fresh klienta wymaga rewalidacji", + "cacheReason_request_pragma_no_cache": "Pragma no-cache klienta / starszy tryb twardego odświeżenia", + "cacheReason_cache_revalidated": "Obiekt z cache potwierdzony przez backend", + "cacheReason_cache_conditional_hit": "Świeży obiekt obsłużył żądanie warunkowe", + "cacheReason_request_no_store": "no-store klienta omija cache", + "cacheReason_request_precondition": "Warunek klienta wymaga backendu", + "loggingPreset": "Preset formatu", + "loggingPresetCustom": "Własny", + "loggingPresetStandard": "Standard RustProxy", + "loggingPresetNginx": "JSON w stylu Nginx", + "loggingPresetEcs": "Nazwy pól w stylu ECS", + "loggingPresetMinimal": "Minimalny", + "loggingBuilder": "Graficzny konfigurator pól", + "loggingBuilderOpen": "Konfiguruj pola", + "loggingAdvancedJson": "Zaawansowane mapowanie JSON", + "loggingBuilderHelp": "Wybierz pola źródłowe i opcjonalnie zmień ich nazwy w JSON. Zastosowanie edytora graficznego normalizuje każde pole źródłowe do maksymalnie jednego klucza wyjściowego; duplikaty aliasów ustawisz w trybie zaawansowanym.", + "loggingSourceField": "Pole źródłowe", + "loggingOutputField": "Klucz JSON na wyjściu", + "loggingPreview": "Podgląd", + "loggingOutputRequired": "Każde włączone pole logu musi mieć klucz JSON na wyjściu.", + "loggingOutputDuplicate": "Klucze JSON na wyjściu muszą być unikalne.", + "aclProfiles": "Profile ACL", + "acl-profiles": "Profile ACL", + "acl-profilesTitle": "Wielokrotnego użytku profile ACL", + "acl-profilesDescription": "Definiuj wspólne reguły dostępu IP i krajów, a następnie przypinaj je do wielu vhostów.", + "aclProfilesIntro": "Profile dostępu można przypinać do wielu vhostów. Indywidualne reguły ACL i krajów w vhoście nadal działają i są egzekwowane razem z profilem.", + "aclProfilesReusable": "WSPÓLNA POLITYKA", + "aclProfileNew": "Nowy profil", + "aclProfileEdit": "Edycja profilu", + "aclProfileEditor": "Konfiguracja profilu", + "aclProfileId": "ID profilu", + "aclProfileName": "Nazwa wyświetlana", + "aclProfileIpEnabled": "Włącz ACL IP", + "aclProfileRules": "Reguły IP — po jednej „allow|deny CIDR” w wierszu", + "aclProfileCombineHelp": "Wyjątki krajów i IP są łączone operatorem LUB w obrębie tego profilu. Jawne blokady IP i zablokowane kraje wygrywają; profil, vhost i lokalizacja nadal muszą przejść wszystkie swoje polityki.", + "aclProfilePolicyMode": "Wybierz bazową politykę", + "aclProfilePolicyModeHelp": "Do typowych przypadków użyj prostego trybu zezwoleń albo blokad. Tryb niestandardowy daje pełną kontrolę nad mieszanymi regułami.", + "aclProfileModeAllowExceptions": "Domyślnie blokuj", + "aclProfileModeAllowExceptionsHelp": "Zezwalaj wskazanym krajom LUB wskazanym adresom/sieciom IP.", + "aclProfileModeBlockExceptions": "Domyślnie zezwalaj", + "aclProfileModeBlockExceptionsHelp": "Blokuj wskazane kraje LUB wskazane adresy/sieci IP.", + "aclProfileModeCustom": "Niestandardowa", + "aclProfileModeCustomHelp": "Ustawiaj ręcznie akcję domyślną, tryb krajów i każdą regułę IP.", + "aclProfileCountryExceptions": "Wyjątki krajów", + "aclProfileUseCountries": "Uwzględniaj kraje", + "aclProfileAllowedCountries": "Dozwolone kraje (kody ISO)", + "aclProfileBlockedCountries": "Blokowane kraje (kody ISO)", + "aclProfileAllowedCountriesHelp": "Ruch z dowolnego wskazanego kraju będzie dozwolony nawet wtedy, gdy jego IP nie znajduje się na liście.", + "aclProfileBlockedCountriesHelp": "Ruch z dowolnego wskazanego kraju będzie blokowany nawet wtedy, gdy jego IP byłoby inaczej dozwolone.", + "aclProfileCustomCountryHelp": "W trybie niestandardowym zachowanie krajów ustawiasz poniżej. Zablokowany kraj zawsze odmawia dostępu.", + "aclProfileIpExceptions": "Wyjątki IP / CIDR", + "aclProfileAddIpException": "Dodaj wyjątek IP", + "aclProfileAllowedIpsHelp": "Dodaj adres IPv4/IPv6 albo CIDR. Te adresy są dozwolone dodatkowo, niezależnie od wybranych krajów.", + "aclProfileBlockedIpsHelp": "Dodaj adres IPv4/IPv6 albo CIDR. Te adresy są blokowane dodatkowo, niezależnie od wybranych krajów.", + "aclProfileCustomIpHelp": "Każda reguła IP może niezależnie zezwalać albo blokować. Zwykły adres IP bez maski jest akceptowany jako pojedynczy host.", + "aclProfileBulkExceptions": "Wklej wiele adresów IP / CIDR", + "aclProfilePreview": "Efekt reguły", + "aclProfilePreviewDefaultDeny": "DOMYŚLNIE: BLOKUJ", + "aclProfilePreviewDefaultAllow": "DOMYŚLNIE: ZEZWALAJ", + "aclProfilePreviewAllowedCountries": "ZEZWÓL KRAJ", + "aclProfilePreviewBlockedCountries": "BLOKUJ KRAJ", + "aclProfilePreviewAllowedIps": "ZEZWÓL IP", + "aclProfilePreviewBlockedIps": "BLOKUJ IP", + "aclProfilePreviewIpRules": "REGUŁY IP", + "aclProfilePreviewNoExceptions": "Brak skonfigurowanych wyjątków", + "aclProfilePreviewBlocksAll": "Ten profil blokuje teraz cały ruch. Dodaj kraj lub wyjątek IP, aby zezwolić na dostęp.", + "aclProfilePreviewAllowsAll": "Ten profil zezwala teraz na cały ruch. Dodaj kraj lub wyjątek IP, aby coś zablokować.", + "aclProfilePreviewAllowNote": "Dostęp jest przyznany, gdy pasuje dozwolony kraj LUB adres klienta pasuje do dozwolonego IP/CIDR.", + "aclProfilePreviewBlockNote": "Dostęp jest blokowany, gdy pasuje blokowany kraj LUB adres klienta pasuje do blokowanego IP/CIDR.", + "aclProfilePreviewCustomNote": "Tryb niestandardowy używa priorytetów backendu: jawne deny IP i blokowany kraj odmawiają dostępu; dozwolony kraj i jawne allow IP mogą spełnić tę samą politykę.", + "aclProfileOr": "LUB", + "aclProfileCountriesShortAllow": "Dozwolone kraje", + "aclProfileCountriesShortBlock": "Blokowane kraje", + "aclProfileIpsShortAllow": "Dozwolone IP", + "aclProfileIpsShortBlock": "Blokowane IP", + "aclProfileIpRulesShort": "reguł IP", + "aclProfileIpOff": "ACL IP wyłączony", + "aclProfileCountriesOff": "Filtrowanie krajów wyłączone", + "aclProfileAttached": "Przypięty do", + "aclProfileInUse": "Profil jest przypięty do vhostów", + "aclProfilesEmpty": "Brak profili ACL wielokrotnego użytku.", + "aclProfileCountryInvalid": "Kody krajów muszą mieć dwuliterowy format ISO.", + "aclProfileCountryRequired": "Dodaj co najmniej jeden kraj albo wyłącz wyjątki krajów.", + "aclProfileDuplicate": "Profil ACL o tym ID już istnieje.", + "vhAclProfile": "Profil ACL", + "vhAclProfileNone": "Bez wspólnego profilu", + "vhAclProfileHelp": "Opcjonalny profil wielokrotnego użytku. Indywidualne ACL i reguły krajów vhosta poniżej nadal są dodatkowo egzekwowane.", + "vhHostAdd": "Dodaj hostname", + "vhHostsHelp": "Dodaj wiele domen (np. domena1.local i domena2.local), zatwierdzając Enterem lub przecinkiem. Wszystkie korzystają z tego samego vhosta i backendu. Wildcard tylko w postaci *.domena.pl.", + "vhHostInvalid": "Nieprawidłowy hostname lub wzorzec wildcard.", + "vhHostRequired": "Dodaj co najmniej jeden hostname.", + "vhLifetimeChart": "Przebieg ruchu od otwarcia panelu", + "vhReqPerSecond": "żąd./s", + "vhPeak": "max", + "listenerAddresses": "Adresy nasłuchu", + "listenerAddressesInvalid": "Podaj 1–16 unikalnych adresów nasłuchu.", + "listenerAddressesExplicit": "Przy wielu adresach wybierz konkretne IP; 0.0.0.0 i :: nie są dozwolone.", + "ruleSyntaxLine": "Nieprawidłowa reguła ACL w wierszu", + "vhForceSslNeedsHttp": "Wymuszenie SSL wymaga co najmniej jednego wybranego listenera zwykłego HTTP (bez TLS). Listener nazwany „HTTP” może mimo nazwy mieć włączony TLS w Ustawienia > Listenery.", + "aclProfileRuleBuilder": "Reguły IP", + "aclProfileBulkRules": "Wklej / importuj reguły", + "aclProfileBulkHelp": "Wklej po jednej regule „allow|deny CIDR” w wierszu. Niewidoczne odstępy Unicode są normalizowane, a błąd wskazuje dokładny wiersz.", + "aclProfileImportRules": "Importuj reguły", + "aclProfileRulesImported": "Reguły ACL zaimportowano do edytora graficznego.", + "streamHosts": "Hosty stream", + "streamsHelp": "Surowe przekazywanie TCP/UDP na osobnych portach. Ruch stream omija vhosty HTTP, ACL, cache, nagłówki i kompresję.", + "streamAdd": "Dodaj host stream", + "saveStreams": "Zapisz hosty stream", + "streamRestartHelp": "Zmiana listenera stream wymaga restartu RustProxy.", + "streamRestartPending": "Zapisano zmiany streamów. Uruchom ponownie RustProxy, aby je zastosować.", + "streamActive": "Aktualnie aktywne streamy", + "streamNone": "Brak aktywnych hostów stream", + "streamEndpoint": "Endpoint stream", + "streamId": "ID streamu", + "streamName": "Nazwa wyświetlana", + "streamProtocol": "Protokół", + "streamListen": "Adres nasłuchu", + "streamUpstream": "Adres upstream", + "streamConnectTimeout": "Timeout połączenia TCP (sekundy)", + "streamIdleTimeout": "Timeout bezczynności/sesji (sekundy)", + "streamMaxConnections": "Maks. połączeń/sesji", + "streamTcp": "TCP", + "streamUdp": "UDP", + "streamLimit": "Obsługiwane jest maksymalnie 128 hostów stream.", + "streamDuplicate": "ID hostów stream muszą być unikalne.", + "streams": "Streamy", + "streamsTitle": "Proxy TCP / UDP", + "streamsDescription": "Zarządzaj surowymi listenerami Layer 4, upstreamami i statystykami ruchu na żywo.", + "streamsPageIntro": "Streamy TCP/UDP są niezależnymi proxy Layer 4. Każdy stream ma własny listener i upstream oraz omija warstwę vhostów HTTP, ACL i cache.", + "streamLayer4": "LAYER 4 / TCP + UDP", + "streamList": "Streamy", + "streamSearch": "Szukaj streamów", + "streamSearchPlaceholder": "ID, nazwa, listener lub upstream…", + "streamSearchEmpty": "Brak streamów pasujących do wyszukiwania.", + "streamEmpty": "Brak streamów TCP/UDP. Dodaj listener, aby rozpocząć proxowanie ruchu Layer 4.", + "streamNew": "Nowy stream", + "streamAddListener": "Dodaj listener TCP/UDP", + "streamEditor": "Konfiguracja streamu", + "streamEditLabel": "EDYCJA", + "streamNewLabel": "NOWY", + "streamDetailsTab": "Szczegóły", + "streamNetworkTab": "Listener i upstream", + "streamLimitsTab": "Limity", + "streamNetwork": "Listener i upstream", + "streamLimits": "Limity połączeń / sesji", + "streamDedicatedListenerHelp": "Listener streamu jest tworzony razem ze streamem i jest niezależny od listenerów HTTP/HTTPS.", + "streamLimitsHelp": "Limity dotyczą równoległych połączeń TCP lub sesji UDP per klient. Timeout łączenia TCP nie jest używany przez UDP.", + "streamListenAdd": "Dodaj adres nasłuchu", + "streamListenHelp": "Wpisz jeden adres IP:port i naciśnij Enter. Zmiana listenera zacznie działać po restarcie RustProxy.", + "streamListenInvalid": "Adres listenera musi być pojedynczą wartością IP:port bez spacji i przecinków.", + "streamListenRequired": "Dodaj adres nasłuchu.", + "streamDelete": "Usunąć ten stream? Aktywny listener pozostanie do restartu RustProxy.", + "streamPendingRestart": "Oczekuje na restart", + "streamRunning": "Działa", + "streamActiveSessions": "Aktywne połączenia / sesje", + "streamSessionsTotal": "Połączenia / sesje", + "streamErrors": "Błędy", + "streamRejected": "Odrzucone", + "streamTransferred": "Przesłano", + "streamTrafficChart": "Ruch na żywo", + "streamClientToUpstream": "klient → upstream", + "streamUpstreamToClient": "upstream → klient", + "streamNoLiveMetrics": "Statystyki pojawią się, gdy stream będzie aktywny po restarcie.", + "streamIdleShort": "Timeout bezczynności", + "listenerAddressAdd": "Dodaj adres nasłuchu", + "listenerAddressesHelp": "Naciśnij Enter lub przecinek, aby dodać adres. Wiele adresów korzysta z tej samej polityki listenera; wildcard 0.0.0.0/:: nie może być mieszany z konkretnymi adresami.", + "cacheColdOnIdle": "Przenoś stare/nieużywane obiekty do cold storage", + "cacheColdOnLru": "Przenoś obiekty wyrzucane z hot LRU do cold storage", + "cacheColdOnShutdown": "Przy łagodnym restarcie zrzucaj hot cache do cold storage", + "certificateCovers": "Domeny certyfikatu", + "certificateCoverageUnknown": "Brak danych o domenach (DNS SAN). Wczytaj ponownie pliki certyfikatów lub zaimportuj certyfikat ponownie.", + "certificateDetails": "Szczegóły certyfikatu", + "certificateFiles": "Pliki certyfikatu / klucza", + "redirects": "Przekierowania", + "redirectsTitle": "Przekieruj świadomie.", + "redirectsDescription": "Lokalne hosty przekierowań z jawnym dopasowaniem, celem, kodem oraz obsługą ścieżki i query.", + "redirectsPageIntro": "Hosty przekierowań odpowiadają lokalnie, bez otwierania połączenia do backendu. Reguły mogą dopasować protokół i ścieżkę, a następnie przekierować do URL, innego hosta lub ścieżki.", + "redirectLayer": "PRZEKIEROWANIA HTTP", + "redirectList": "Hosty przekierowań", + "redirectSearch": "Szukaj przekierowań", + "redirectSearchPlaceholder": "ID, domena, cel…", + "redirectNew": "Nowe przekierowanie", + "redirectEmpty": "Brak skonfigurowanych hostów przekierowań.", + "redirectSearchEmpty": "Brak przekierowań pasujących do wyszukiwania.", + "redirectEditor": "Konfiguracja przekierowania", + "redirectId": "ID przekierowania", + "redirectName": "Nazwa", + "redirectHosts": "Domeny źródłowe", + "redirectFrontend": "FRONTEND", + "redirectListeners": "Listenery", + "redirectBehavior": "ZACHOWANIE", + "redirectRules": "Reguły", + "redirectAddRule": "Dodaj regułę", + "redirectRulesHelp": "Wygrywa najdłuższa pasująca ścieżka. 307/308 zachowują metodę żądania; 301/302/303 mogą ją zmienić po stronie klienta.", + "redirectSafetyHint": "Host przekierowania nie łączy się z backendem. Globalne ACL hostów nadal obowiązują. Unikaj reguł kierujących bezwarunkowo do samych siebie.", + "redirectRule": "Reguła", + "redirectRuleId": "ID reguły", + "redirectSourceScheme": "Protokół wejściowy", + "redirectSchemeAny": "HTTP lub HTTPS", + "redirectSchemeHttp": "HTTP", + "redirectSchemeHttps": "HTTPS", + "redirectSchemePreserve": "Zachowaj protokół wejściowy", + "redirectMatchType": "Dopasowanie ścieżki", + "redirectMatchPrefix": "Prefiks", + "redirectMatchExact": "Dokładnie", + "redirectSourcePath": "Ścieżka źródłowa", + "redirectTargetType": "Typ celu", + "redirectTargetUrl": "Pełny URL", + "redirectTargetHost": "Host / aplikacja", + "redirectTargetPath": "Ścieżka na tym samym hoście", + "redirectTarget": "Cel", + "redirectTargetScheme": "Protokół celu", + "redirectPathMode": "Obsługa ścieżki", + "redirectPathAppend": "Dopisz pełną ścieżkę źródłową", + "redirectPathStrip": "Usuń dopasowany prefiks i dopisz resztę", + "redirectPathReplace": "Użyj tylko skonfigurowanej ścieżki celu", + "redirectQueryMode": "Obsługa query", + "redirectQueryPreserve": "Zachowaj query źródłowe", + "redirectQueryDrop": "Usuń query źródłowe", + "redirectStatus": "Status HTTP", + "redirect301": "301 Moved Permanently", + "redirect302": "302 Found", + "redirect303": "303 See Other", + "redirect307": "307 Temporary Redirect (zachowaj metodę)", + "redirect308": "308 Permanent Redirect (zachowaj metodę)", + "redirectCacheSeconds": "max-age cache przeglądarki/proxy (sekundy; 0 = no-store)", + "redirectHostsRequired": "Dodaj co najmniej jedną domenę źródłową.", + "redirectListenersRequired": "Wybierz co najmniej jeden listener.", + "redirectRulesRequired": "Dodaj co najmniej jedną regułę przekierowania.", + "redirectDuplicate": "Przekierowanie o tym ID już istnieje.", + "redirectUnsaved": "Odrzucić niezapisane zmiany przekierowania?", + "redirectNewLabel": "NOWE", + "administrationSessionDays": "Czas ważności sesji administratora (dni)", + "administrationSessionDaysHelp": "Sesje przeglądarkowe mają bezwzględny czas ważności 7–30 dni. Zmiana działa od razu; zmiana hasła i „wyloguj wszystkie sesje” nadal natychmiast unieważniają sesje.", + "vhLocalListenerRisk": "Zezwól na lokalny listener RustProxy jako backend (ryzyko)", + "vhLocalListenerRiskHelp": "Ta opcja pojawia się, gdy URL backendu bezpośrednio wskazuje skonfigurowany lub aktywny listener administracyjny RustProxy. localhost, 127.0.0.1 i ::1 są traktowane jako równoważne adresy loopback na tym samym porcie panelu. Jej włączenie może wystawić panel lub utworzyć pętlę proxy; adres prywatny/loopback nadal wymaga opcji „Zezwól na prywatny IP backendu”.", + "vhLocalListenerRiskConfirm": "Ta opcja może kierować żądania do lokalnego listenera RustProxy, w tym portu administracyjnego, oraz utworzyć pętlę lub wystawić control plane. Akceptujesz to ryzyko?", + "cacheResidentHistory": "Historia zajętości RAM cache", + "networkHistory": "Historia RX / TX", + "responseLatencyHistory": "Historia czasu odpowiedzi", + "sourceIp": "IP źródłowe", + "cacheColdOnAge": "Przenoś obiekty do cold po maksymalnym czasie w hot RAM", + "cacheColdMaxHot": "Maksymalny czas w hot RAM (sekundy)", + "cacheColdPolicyHelp": "Czas bezczynności określa, jak długo obiekt nie był używany. Wiek hot określa ciągły czas przebywania w RAM nawet przy dalszych odczytach. Presja LRU i zrzut przy łagodnym zamknięciu są niezależnymi regułami.", + "documentation": "Dokumentacja", + "locationPathPattern": "Ścieżka / wzorzec regex", + "locationMatchType": "Sposób dopasowania", + "locationMatchPrefix": "Prefiks", + "locationMatchPriority": "Priorytetowy prefiks (^~)", + "locationMatchExact": "Dokładna ścieżka (=)", + "locationMatchRegex": "Regex (~)", + "locationMatchRegexI": "Regex bez rozróżniania wielkości liter (~*)", + "locationMatch_prefix": "prefiks", + "locationMatch_prefix_priority": "prefiks priorytetowy", + "locationMatch_exact": "dokładne", + "locationMatch_regex": "regex", + "locationMatch_regex_insensitive": "regex i", + "locationAction": "Akcja", + "locationProxyAction": "Proxy do backendu", + "locationReturnAction": "Własna odpowiedź (return)", + "locationProxyShort": "proxy", + "locationReturnShort": "odpowiedź lokalna", + "locationHandling": "Obsługa żądania", + "locationRewriteMode": "Przepisanie ścieżki do backendu", + "locationRewritePreserve": "Zachowaj oryginalną ścieżkę", + "locationRewriteStrip": "Usuń dopasowany prefiks", + "locationRewriteReplace": "Zastąp dopasowany prefiks", + "locationRewriteRegex": "Zamiana regex", + "locationRewriteValue": "Ścieżka / wyrażenie zastępujące", + "locationRewriteHelp": "Parametry query są zachowywane. Przepisywanie prefiksu działa na znormalizowanej ścieżce routingu. Zamiana regex obsługuje grupy Rust regex, np. $1. Wynik jest walidowany przed wysłaniem do backendu.", + "locationResponseStatus": "Kod odpowiedzi", + "locationResponseType": "Content-Type", + "locationResponseBody": "Treść odpowiedzi", + "headersVisualBuilder": "Graficzny konfigurator", + "locationDocsTitle": "Dokumentacja Custom locations", + "headersDocsTitle": "Dokumentacja reguł nagłówków", + "locationDocsIntro": "Custom locations są oceniane wewnątrz wybranego vhosta po autentykacji i ACL hosta. Nie mogą omijać zabezpieczeń vhosta.", + "locationDocsPrefix": "Prefiks: dopasowuje pełny prefiks segmentów ścieżki. Wygrywa najdłuższy prefiks. Priorytetowy prefiks działa jak nginx ^~ i blokuje pierwszeństwo regexów.", + "locationDocsExact": "Dokładne: pasuje wyłącznie do dokładnej znormalizowanej ścieżki, jak nginx location = /path.", + "locationDocsRegex": "Regex: po obsłudze exact/prioritetowego prefiksu wygrywa pierwszy pasujący regex. Dostępny jest tryb czuły i nieczuły na wielkość liter. Używana jest składnia Rust regex; look-around i backreference w samym wzorcu są celowo niedostępne.", + "locationDocsReturn": "Własna odpowiedź: RustProxy odpowiada lokalnie wybranym kodem HTTP, Content-Type i body bez otwierania połączenia do backendu. Reguły nagłówków odpowiedzi lokalizacji nadal obowiązują.", + "locationDocsRewrite": "Przepisanie ścieżki backendu: zachowanie oryginału, usunięcie/zamiana prefiksu albo zamiana z grupami regex. Oryginalny query string jest zachowany.", + "locationDocsAccess": "ACL IP lokalizacji, nadpisanie cache i reguły nagłówków są łączone z polityką vhosta. Lokalizacja nie może wyłączyć autentykacji ani ACL hosta.", + "locationDocsLimits": "Do 64 lokalizacji na vhost. Regexy i body odpowiedzi mają limity; nagłówki transportu, zweryfikowanej tożsamości i protokołu WebSocket pozostają zarządzane przez system.", + "headersDocsIntro": "Reguły nagłówków są stosowane kolejno: globalne → vhost → pasująca lokalizacja. Do zwykłej konfiguracji użyj graficznego konfiguratora; JSON pozostaje trybem zaawansowanym.", + "headersDocsSet": "Ustaw: zastępuje wszystkie istniejące wartości nagłówka skonfigurowaną wartością dosłowną.", + "headersDocsAppend": "Dopisz: dodaje kolejną wartość bez usuwania istniejących.", + "headersDocsRemove": "Usuń: kasuje nagłówek przed wysłaniem do backendu albo klienta.", + "headersDocsProtected": "Chronionych nagłówków transportu/tożsamości (Host, Connection, Content-Length, Forwarded/X-Forwarded-*, nagłówki dowodu WebSocket itd.) nie można tu nadpisywać.", + "headersDocsOrder": "Wartości są dosłowne; zmienne nginx nie są interpretowane. Reguły request działają przed nałożeniem kanonicznych danych forwardingu, a response przed końcową polityką bezpieczeństwa/diagnostyki RustProxy.", + "cacheColdPolicyTitle": "Polityka hot → cold", + "headerRule": "Reguła nagłówka", + "headerRulesEmpty": "Brak reguł. Dodaj regułę poniżej.", + "headerRuleHelp": "Wybierz Ustaw, Dodaj lub Usuń. Nagłówki transportowe, tożsamości forwardingu i protokołu WebSocket pozostają chronione.", + "locationPreviewTitle": "Podgląd efektywnego działania", + "locationPreviewMatch": "Dopasowanie", + "locationPreviewHandling": "Obsługa", + "locationPreviewRewrite": "Ścieżka do backendu", + "locationPreviewCache": "Cache", + "locationPreviewAccess": "Dostęp", + "locationPreviewHeaders": "Reguły nagłówków", + "locationPreviewDisabled": "Ta lokalizacja jest wyłączona i nie będzie dopasowywać żądań.", + "locationPreviewBackendSkipped": "backend nie jest wywoływany", + "locationPreviewVhostAcl": "Tylko autentykacja i ACL vhosta", + "locationPreviewCustomAcl": "Polityka vhosta + ACL lokalizacji", + "locationPreviewRequestRules": "request", + "locationPreviewResponseRules": "response", + "locationPreviewQueryPreserved": "query string jest zachowywany", + "locationDocsMatchHeading": "1. Dopasowanie i kolejność", + "locationDocsActionHeading": "2. Proxy, rewrite i odpowiedź lokalna", + "locationDocsPolicyHeading": "3. Dostęp, cache i nagłówki", + "locationDocsExamplesHeading": "Popularne przykłady", + "locationDocsExampleExact": "Endpoint health: Dokładna ścieżka (=) /health + Własna odpowiedź 200 odpowiada lokalnie bez łączenia z backendem.", + "locationDocsExampleStatic": "Pliki statyczne: Priorytetowy prefiks (^~) /static/ blokuje przejęcie przez regex; dla tej lokalizacji można osobno włączyć cache.", + "locationDocsExampleRegex": "Wersjonowane API: Regex (~) ^/api/v[0-9]+/ może wybrać backend i używać grup zamiany regex, np. $1.", + "cacheOutcomeHistory": "Aktywność HIT / MISS / BYPASS", + "cacheHotMemoryHistory": "Zajętość RAM HOT / limit", + "cacheTransientMemoryHistory": "Zajętość RAM przejściowego / limit", + "cacheColdStorageHistory": "Zajętość COLD / limit", + "processCpuHistory": "Historia CPU procesu", + "processRssHistory": "Historia RSS procesu", + "cacheMemoryBreakdown": "Cache hot / pamięć przejściowa", + "cacheTransientBreakdown": "Fill / kolejka cold / I/O cold", + "cacheColdStorageUsage": "Zajętość COLD / limit", + "cacheColdStorageObjects": "obiektów", + "cacheTransientMemory": "Pamięć przejściowa cache", + "cacheTransientShort": "RAZEM", + "cacheFillShort": "fill", + "cacheQueueShort": "kolejka cold", + "cacheIoShort": "I/O cold", + "processRssTotal": "RSS łącznie", + "memoryCacheHot": "RAM cache HOT / limit", + "memoryCacheTransient": "RAM przejściowy cache / limit", + "memoryTransform": "RAM zarezerwowany przez transformacje / budżet", + "memoryTransformInflight": "Aktywne transformacje / limit", + "memoryNonCacheRss": "RSS procesu poza cache", + "transformMemoryUsage": "Rezerwacja RAM transformacji", + "transformConcurrencyUsage": "Aktywne sloty transformacji", + "transformBudgetShort": "budżet", + "processRssAnon": "RSS anonimowy", + "processRssFile": "RSS plików / współdzielony", + "processDataSwap": "Obszar danych / swap", + "memoryAccountingHint": "RSS pokazuje rzeczywisty ślad pamięci procesu. HOT i pamięć przejściowa cache są księgowane przez RustProxy, a pamięć transformacji jest konserwatywną rezerwacją aktywnej pracy. RSS poza cache to RSS pomniejszony o księgowany cache i obejmuje m.in. transformacje, połączenia, TLS, logowanie, Tokio/runtime, narzut alokatora i inne bufory, więc nie wszystkie wiersze należy sumować.", + "memoryAccountingInfo": "Informacje o rozliczaniu pamięci", + "logTransport": "Transport", + "logWebSocket": "WebSocket", + "logUpstreamError": "Błąd upstreamu", + "listenerDefaultPortsHint": "Nowy vhost automatycznie wybiera standardowy listener HTTP :80, jeśli jest dostępny.", + "vhBackendDiagnostics": "Diagnostyka backendu", + "vhBackendHeader": "Udostępniaj wybrany backend (X-RustProxy-Backend)", + "vhBackendTimingHeader": "Udostępniaj czas odpowiedzi backendu (X-RustProxy-Backend-Time)", + "vhBackendDiagnosticsHelp": "Opcjonalne nagłówki odpowiedzi. Czas backendu oznacza czas od wysłania żądania do otrzymania nagłówków odpowiedzi; odpowiedzi obsłużone bez kontaktu z originem nie raportują backendu.", + "authModeHelp": "Wybierz jedną metodę uwierzytelniania. Lista dostępu IP poniżej działa niezależnie i może być używana razem z dowolną metodą.", + "taAdvanced": "Zaawansowane ustawienia Tinyauth", + "notfound": "Hosty 404", + "notfoundTitle": "Hosty 404 pod kontrolą.", + "notfoundDescription": "Rezerwuj domeny, które mają lokalnie zwracać kod, zaślepkę albo własny HTML.", + "notfoundPageIntro": "Hosty 404 rezerwują wybrane domeny i odpowiadają lokalnie bez łączenia z backendem. Mogą używać tych samych listenerów i certyfikatów co vhosty.", + "notfoundLayer": "ODPOWIEDŹ LOKALNA", + "notfoundList": "Hosty 404", + "notfoundSearch": "Szukaj hostów 404", + "notfoundSearchPlaceholder": "ID, nazwa lub domena…", + "notfoundNew": "Nowy host 404", + "notfoundEditor": "Konfiguracja hosta 404", + "notfoundId": "ID hosta", + "notfoundName": "Nazwa wyświetlana", + "notfoundFrontend": "FRONTEND", + "notfoundBehavior": "ODPOWIEDŹ", + "notfoundResponse": "Odpowiedź lokalna", + "notfoundMode": "Typ odpowiedzi", + "notfoundModeWelcome": "Zaślepka RustProxy", + "notfoundModeEmpty": "Tylko status / pusta", + "notfoundModeHtml": "Własny HTML", + "notfoundStatus": "Status HTTP", + "notfoundHtml": "Własny HTML", + "notfoundResponseHelp": "Host nigdy nie otwiera połączenia do backendu. Wbudowana zaślepka ma no-store i celowo nie dodaje opcjonalnych nagłówków diagnostycznych ani Server RustProxy.", + "notfoundHosts": "Domeny", + "notfoundNewLabel": "NOWY", + "notfoundHostsRequired": "Dodaj co najmniej jedną domenę.", + "notfoundListenersRequired": "Wybierz co najmniej jeden listener.", + "notfoundHtmlRequired": "Własny HTML nie może być pusty.", + "notfound204Empty": "Odpowiedzi HTTP 204, 205 i 304 muszą używać trybu Pusta.", + "notfoundDuplicate": "Host 404 o takim ID już istnieje.", + "notfoundUnsaved": "Odrzucić niezapisane zmiany hosta 404?", + "notfoundEmpty": "Brak skonfigurowanych hostów 404.", + "notfoundSearchEmpty": "Brak hostów 404 pasujących do wyszukiwania.", + "prometheusTrustedNoToken": "Zezwól zaufanym CIDR na odczyt bez tokenu", + "prometheusTrustedNoTokenHelp": "Klient nadal musi pasować do listy dozwolonych CIDR. Włączaj tylko w prywatnej, zaufanej sieci monitoringu; nagłówki przekazywane nie są używane do tej decyzji.", + "docker-vhosts": "Docker vhosty", + "docker-vhostsTitle": "Usługi Dockera pod nazwami.", + "docker-vhostsDescription": "Wykrywaj wystawione porty Dockera i mapuj uslugi na nazwy w domenie agenta, wlasne domeny oraz aliasy bez zapisywania obserwowanego stanu do SQLite.", + "dockerVhosts": "Docker vhosty", + "dockerVhostsIntro": "Wystawione porty TCP wykryte przez skonfigurowane agenty Dockera. Dla każdej usługi ustaw krótką nazwę w domenie agenta albo dowolny FQDN, dodaj aliasy, wybierz IP hosta Dockera oraz sprawdzaj i konfiguruj stan backendu na żywo.", + "dockerDiscovery": "WYKRYWANIE DOCKERA", + "dockerRefresh": "Odśwież agentów", + "dockerAgentsSettings": "Ustawienia agentów Dockera", + "dockerAgents": "Agenty Dockera", + "dockerDesiredState": "STAN ŻĄDANY / SQLITE", + "dockerAgentsHelp": "Skonfiguruj osobnego agenta dla każdego hosta Dockera. Domyślnie agent używa HTTPS ze stałym certyfikatem self-signed; wklej jego fingerprint SHA-256, aby przypiąć certyfikat. Każdy agent ma własną domenę bazową i opcjonalne domyślne IP backendu.", + "dockerAgentAdd": "Dodaj agenta Dockera", + "dockerAgentsSave": "Zapisz i połącz agentów", + "dockerAgent": "AGENT DOCKERA", + "dockerAgentNew": "Nowy agent", + "dockerAgentId": "ID agenta", + "dockerAgentName": "Nazwa wyświetlana", + "dockerAgentEndpoint": "Adres agenta", + "dockerAgentToken": "Token Bearer", + "dockerBaseDomain": "Domena bazowa", + "dockerTargetOverride": "Nadpisanie IP docelowego", + "dockerTargetAuto": "automatyczne", + "dockerListeners": "Listenery", + "dockerListenersRequired": "Wybierz co najmniej jeden listener dla agenta Docker.", + "dockerNoAgents": "Brak skonfigurowanych agentów Dockera.", + "dockerNoAgentsConfigured": "Najpierw skonfiguruj agenta Dockera w Ustawieniach.", + "dockerPublish": "Wystaw", + "dockerPreserveHost": "Zachowaj Host", + "dockerServiceMissing": "Brak w ostatnim odczycie", + "dockerConnected": "POŁĄCZONO", + "dockerError": "BŁĄD", + "dockerWaiting": "OCZEKIWANIE", + "dockerAgentDisabled": "Ten agent Dockera jest wyłączony.", + "dockerNoObservation": "Brak odczytu. Odśwież agentów.", + "dockerHost": "Host Dockera", + "dockerDetectedIp": "Wykryte IP", + "dockerEffectiveIp": "IP routingu", + "dockerNoPublishedPorts": "Agent nie zgłosił wystawionych portów TCP.", + "dockerAssignmentsSave": "Zapisz Docker vhosty", + "dockerDefaultTarget": "Domyslne IP backendu", + "dockerDetectedIps": "Wykryte adresy IP hosta", + "dockerHostname": "Nazwa hosta", + "dockerHostnameHelp": "Krotka nazwa uzyje {domain}; pelny FQDN uzyje wlasnej domeny.", + "dockerAliases": "Aliasy", + "dockerAliasAdd": "Dodaj alias", + "dockerAliasesHelp": "Dodawaj aliasy klawiszem Enter lub przecinkiem. Krótkie nazwy użyją domeny bazowej agenta, a pełne FQDN pozostaną bez zmian.", + "dockerBackendIp": "IP hosta backendu", + "dockerBackendIpAuto": "Auto / domyslne agenta", + "dockerBackendScheme": "Protokol backendu", + "dockerBindingIp": "binding Dockera", + "dockerAgentDefaultIp": "domyslne agenta", + "dockerAgentEndpointHelp": "Dla domyślnej konfiguracji agenta użyj https://. Zwykły HTTP pozostaje tylko dla zgodności i jest wyraźnie oznaczony jako niezabezpieczony.", + "dockerTlsFingerprint": "SHA-256 certyfikatu TLS", + "dockerTlsFingerprintHelp": "Dla domyślnego certyfikatu self-signed wklej fingerprint wypisany przez install.sh albo rustproxy-docker-agent --print-fingerprint. Zostaw puste wyłącznie dla certyfikatu zaufanego przez publiczne CA.", + "dockerTlsPinned": "TLS PRZYPIĘTY", + "dockerTlsPublicCa": "TLS / PUBLICZNE CA", + "dockerTlsPlain": "ZWYKŁY HTTP", + "dockerTlsPlainHelp": "Token agenta i dane inventory nie są chronione przez TLS na tym endpointcie.", + "dockerTlsPublicCaHelp": "Łańcuch certyfikatu i nazwa hosta są weryfikowane względem publicznych CA.", + "dockerAgentConnectionFailed": "Nie udało się połączyć z agentem", + "dockerAgentVersion": "Wersja agenta", + "dockerLastChecked": "Ostatnie sprawdzenie", + "dockerNeverChecked": "nigdy", + "dockerJustNow": "przed chwilą", + "dockerSecondsAgo": "{seconds} s temu", + "dockerMinutesAgo": "{minutes} min temu", + "dockerBackendMissing": "BRAK USŁUGI", + "dockerBackendNotActive": "NIEWYSTAWIONY", + "dockerBackendChecking": "SPRAWDZANIE", + "dockerBackendHealthOff": "HEALTH WYŁ.", + "dockerBackendUp": "UP", + "dockerBackendDown": "DOWN", + "dockerBackendDraining": "WYGASZANIE", + "dockerBackendNoMetrics": "Metryki runtime pojawią się po aktywacji dynamicznego backendu.", + "dockerHealthCheck": "Badanie backendu", + "dockerHealthSummary": "Aktywne testy TCP/HTTP; pasywne zdrowie jest konfigurowane osobno", + "dockerPassiveHealth": "Pasywne zdrowie backendu", + "dockerPassiveHealthSummary": "Domyślnie wyłączone; wymaga świadomego użycia zwykłego ruchu do decyzji health", + "dockerHealthMode": "Tryb", + "dockerHealthHttp": "HTTP", + "dockerHealthTcp": "TCP", + "dockerHealthOff": "Wyłączone", + "dockerHealthPath": "Ścieżka HTTP", + "dockerHealthHost": "Nagłówek Host health-checka", + "dockerHealthHostAuto": "host backendu / automatycznie", + "dockerHealthInterval": "Interwał (ms)", + "dockerHealthTimeout": "Timeout (ms)", + "dockerHealthSlow": "Próg wolnej odpowiedzi (ms)", + "dockerHealthFall": "Błędy do oznaczenia DOWN", + "dockerHealthRise": "Sukcesy do oznaczenia UP", + "dockerHealthStatusMin": "Min. status HTTP", + "dockerHealthStatusMax": "Maks. status HTTP", + "dockerHealthLatency": "Opóźnienie", + "dockerHealthResult": "Ostatni probe", + "dockerHealthRequests": "Żądania", + "dockerHealthErrors": "Błędy", + "dockerHealthActive": "Aktywne", + "loggingPerVhost": "Osobny plik per vhost", + "loggingDirectory": "Katalog logów per-vhost", + "loggingPerVhostHelp": "Po włączeniu każdy vhost zapisuje do własnego pliku JSONL. Ruch niedopasowany trafia do _unmatched.jsonl.", + "listenerStartupLocked": "Tego adresu ustawionego przy starcie procesu/systemd nie można zmienić ani usunąć. Pozostałe adresy i opcje listenera nadal można edytować.", + "administrationStartupLocked": "Ten adres jest ustawiony przy starcie procesu/systemd i nie można go tutaj zmienić.", + "notfoundStatusInvalid": "Wybierz obsługiwany status błędu HTTP.", + "tlsEffectiveCiphers": "Aktualnie używane szyfry", + "certificateManaged": "Zarządzany przez ACME", + "certManagedReadOnly": "Certyfikaty zarządzane przez ACME są odnawiane automatycznie i nie można ich edytować jak własnych certyfikatów z plików.", + "vhWebsocketTransport": "Transport WebSocket", + "vhWebsocketHttp1": "HTTP/1.1 Upgrade (zalecane)", + "vhWebsocketRfc9220": "RFC 9220 Extended CONNECT przez HTTP/3", + "vhWebsocketTransportHelp": "Włącz WebSocket osobnym przełącznikiem, a następnie wybierz transport. Tryby H2/H3 używają Extended CONNECT, a HTTP/1.1 Upgrade pozostaje fallbackiem.", + "dockerLiveView": "Widok live", + "dockerPollInterval": "Interwał odpytywania agenta (s)", + "dockerPollIntervalHelp": "Jak często RustProxy pobiera inventory Dockera z tego agenta. Widok live czyta tylko stan z pamięci RustProxy.", + "dockerAgentPollEvery": "Interwał agenta", + "dockerSearch": "Szukaj Docker vhostów", + "dockerSearchPlaceholder": "Szukaj usługi, hosta, aliasu lub agenta...", + "dockerShowHidden": "Pokaż ukryte", + "dockerHideHidden": "Ukryj ukryte", + "dockerHideVhost": "Ukryj z widoku", + "dockerUnhideVhost": "Pokaż w widoku", + "dockerCertificate": "Certyfikat frontendu", + "dockerCertificateHelp": "Certyfikat przypisany do wszystkich generowanych vhostów tego agenta. Domyślna opcja używa standardowego wyboru TLS/SNI.", + "dockerDefaultAcl": "Domyślny profil ACL", + "dockerDefaultAclNone": "Bez domyślnego profilu ACL", + "dockerDefaultAclHelp": "Dziedziczony przez generowane Docker vhosty, które nie mają wybranego własnego profilu ACL per aplikacja.", + "dockerApplyService": "Zastosuj zmiany", + "dockerAccessControl": "Kontrola dostępu", + "dockerAclSummary": "Opcjonalny profil ACL i reguły IP per aplikacja", + "dockerAclProfile": "Profil ACL", + "dockerAclProfileNone": "Bez profilu ACL", + "dockerAclProfileInherit": "Domyślny agenta", + "dockerAclInline": "Użyj dodatkowego ACL per aplikacja", + "dockerAclDefault": "Domyślna akcja ACL", + "dockerAclRules": "Reguły IP/CIDR", + "dockerAclHelp": "Jedna reguła w linii, np. allow 10.0.0.0/8. Profil i ACL per aplikacja są egzekwowane łącznie.", + "dockerProxyTimeouts": "Timeouty proxy", + "dockerProxyTimeoutsHelp": "Jawne limity per aplikacja: domyślnie połączenie 10 s, nagłówki odpowiedzi 120 s, odczyt 60 s i wysyłanie 60 s.", + "dockerHostnameRequired": "Przed wystawieniem wymagany jest hostname.", + "dockerForceSsl": "Przekieruj HTTP do HTTPS", + "dockerForceSslUnavailable": "Wymaga aktywnego listenera zwykłego HTTP oraz jednego jednoznacznego listenera TLS dla tego agenta.", + "dockerWebsockets": "WebSockety", + "dockerCompression": "Kompresja", + "dockerCompressionHelp": "Włącz lub wyłącz kompresję dla tego vhosta Docker. Dostępne kodeki i ich priorytet wynikają z globalnych ustawień Response.", + "dockerCache": "Cache", + "vhProxyTimeouts": "Timeouty proxy", + "vhProxyConnectTimeout": "Proxy connect timeout (s)", + "vhProxyHeaderTimeout": "Proxy response header timeout (s)", + "vhProxyReadTimeout": "Proxy read timeout (s)", + "vhProxySendTimeout": "Proxy send timeout (s)", + "vhProxyTimeoutsHelp": "Domyślnie: połączenie 10 s, nagłówki odpowiedzi 120 s, odczyt 60 s i wysyłanie 60 s. Read/send to timeout bezczynności podczas streamowania body.", + "notfoundModeBuiltin": "Zaślepka aplikacyjna dla statusu", + "pageBuiltin": "Wbudowana strona statusu", + "vhOpenBrowser": "Otwórz", + "securityPasswordHint": "Wymaga obecnego hasła i wyloguje wszystkie sesje.", + "securityAccessControl": "KONTROLA DOSTĘPU", + "securityPoliciesHint": "Lista polityk pozostaje widoczna; edytor otwieraj tylko przy dodawaniu lub zmianie reguł.", + "securityPolicyEditorHint": "Zakres hostów, domyślna akcja i reguły CIDR.", + "securityAuditOpen": "Audyt administracyjny", + "securityAuditHint": "Ostatnie operacje administracyjne są pobierane dopiero po otwarciu tego okna.", + "dockerConfigureService": "Konfiguruj vhost", + "dockerAliasesShort": "aliasów", + "dockerAgentDetails": "Szczegóły agenta", + "dockerServices": "Usługi", + "dockerOpenApp": "Otwórz aplikację", + "networkInterfacesHistory": "Ruch według interfejsów", + "networkInterfaceRate": "RX / TX", + "networkNoInterfaces": "Brak dostępnych interfejsów sieciowych innych niż loopback.", + "networkInterfacesCount": "{count} interfejsów", + "logExpandBuffer": "Zwiększ bufor", + "logExpandedBuffer": "Bufor 4096", + "logBufferExpanded": "Bufor live zwiększono do {count} zachowanych wpisów.", + "vhIdRenameHelp": "Zmiana ID vhosta tworzy nową tożsamość metryk/logów/cache i resetuje bieżącą historię health/traffic. Hostname’y i skonfigurowane ID backendów pozostają bez zmian.", + "vhIdRenameConfirm": "Zmienić ID vhosta z {old} na {new}? Bieżąca historia health/traffic oraz tożsamość metryk/cache tego vhosta zostaną rozpoczęte od nowa.", + "cacheDomainFilter": "Pokaż", + "cacheDomainFilterAll": "Wszystkie domeny", + "cacheDomainFilterEnabled": "Cache włączony", + "cacheDomainFilterDisabled": "Cache wyłączony", + "cacheNoDomainsFiltered": "Brak domen pasujących do tego filtra.", + "cacheColdCleanup": "Wyczyść cold cache", + "cacheColdCleanupHelp": "Operacja działa asynchronicznie i korzysta z tego samego mechanizmu eviction co automatyczny GC cold cache.", + "cacheColdUnusedSeconds": "Nieużywane co najmniej przez (sekundy, opcjonalnie)", + "cacheColdOldestCount": "Usuń maksymalnie najstarszych obiektów", + "cacheColdHitsZero": "Tylko obiekty z hits = 0", + "cacheColdCleanupStart": "Uruchom czyszczenie", + "cacheColdCleanupRunning": "Trwa czyszczenie cold cache…", + "cacheColdCleanupDone": "Usunięto {count} obiektów i zwolniono {bytes}.", + "cacheColdCleanupFailed": "Czyszczenie cold cache nie powiodło się.", + "cacheColdCleanupInvalidJob": "Czyszczenie cold cache nie zwróciło identyfikatora zadania.", + "cacheColdGcInterval": "Interwał GC cold cache (sekundy)", + "cacheColdGcMaxDelete": "Maksymalna liczba usunięć na przebieg GC", + "transformSettings": "Transformacja odpowiedzi statycznych", + "transformEnable": "Włącz globalnie silnik transformacji", + "transformHelp": "Transformacja odbywa się po dekodowaniu upstreamu, a przed zapisem do cache i kompresją dla klienta. Każdy vhost musi zostać włączony osobno.", + "transformJavascript": "Minifikacja JavaScript", + "transformCss": "Minifikacja CSS", + "transformHtml": "Minifikacja HTML", + "transformSvg": "Optymalizacja SVG", + "transformJson": "Kompaktowanie JSON", + "transformExtensions": "Dozwolone rozszerzenia plików, po jednym w linii", + "transformMaxBody": "Maksymalny rozmiar transformowanej odpowiedzi (KiB)", + "transformResourcePresetTitle": "Preset zasobów", + "transformResourcePresetHelp": "Presety ustawiają limit pamięci tymczasowej, maksymalny rozmiar transformowanej odpowiedzi i globalny limit równoległych transformacji. Typy treści i lista rozszerzeń pozostają bez zmian.", + "transformPresetConservative": "Oszczędny RAM", + "transformPresetRecommended": "Rekomendowany", + "transformPresetPerformance": "Wydajność", + "transformPresetUber": "Uber wydajność", + "transformMemoryBudget": "Budżet pamięci tymczasowej transformacji (MiB)", + "transformMaxInflight": "Maksymalna liczba równoległych transformacji", + "transformMaxInflightShort": "równolegle", + "transformResourceCurrent": "Aktualne limity", + "transformBudgetTooSmallWarning": "Budżet pamięci jest mniejszy niż pesymistyczny koszt buforów dla jednej maksymalnej skompresowanej odpowiedzi. Takie odpowiedzi mogą zostać pominięte z powodem memory_pressure.", + "transformBudgetHighWarning": "Limit pamięci transformacji przekracza 50% efektywnej pamięci RAM hosta/cgroup:", + "transformSafety": "Cache-Control: no-transform jest zawsze respektowane. Przetransformowana reprezentacja otrzymuje nowy ETag i jest zapisywana do cache przed gzip, Brotli lub zstd.", + "saveTransform": "Zastosuj ustawienia transformacji", + "vhTransform": "Transformacja", + "vhTransformEnable": "Włącz transformacje dla tego vhosta", + "vhTransformExtensionsCustom": "Nadpisz globalne rozszerzenia transformacji", + "vhTransformHelp": "Globalny silnik również musi być włączony. Ustawienia formatów dziedziczą politykę globalną, chyba że zostaną jawnie nadpisane.", + "dockerTransform": "Transformuj odpowiedzi statyczne", + "vhPassive5xxWarning": "Zliczanie HTTP 5xx może wyłączyć cały backend po serii błędów aplikacyjnych z pojedynczego endpointu.", + "vhPassiveSlowWarning": "Zliczanie wolnych odpowiedzi może wyłączyć backend po serii poprawnych odpowiedzi przekraczających próg slow-response.", + "transform": "Transformacja", + "transformTitle": "Transformacja odpowiedzi statycznych.", + "transformDescription": "Skuteczność transformacji, oszczędność, pominięcia i ponowne użycie z cache.", + "transformApplied": "Przetransformowane odpowiedzi", + "transformExamined": "Sprawdzone", + "transformCacheHits": "HIT-y transformowanych obiektów", + "transformCacheHitsHelp": "Obsłużone bez ponownego uruchamiania transformacji", + "transformSaved": "Bajty oszczędzone przed kompresją", + "transformReduction": "Średnia redukcja", + "transformReductionHelp": "Dla odpowiedzi, których reprezentacja faktycznie się zmieniła", + "transformSkipped": "Pominięte / fail-open", + "transformErrors": "Błędy body / transportu", + "transformAvgTime": "Średni czas", + "transformByType": "Transformacje według typu", + "transformType": "Typ", + "transformSkipReasons": "Powody pominięcia / fail-open", + "transformNoSkips": "Brak pominiętych transformacji w tym zakresie.", + "transformNoSkipsShort": "Brak pominięć", + "transformVhosts": "Skuteczność per vhost", + "transformScopeHint": "Liczniki są diagnostyką runtime w pamięci. HIT transformowanego obiektu z cache nie uruchamia transformera ponownie.", + "transformNoData": "Brak aktywności transformacji.", + "transformBadge": "transformowany", + "cacheLiveChartTitle": "Aktywność cache na żywo", + "cacheLiveChartHelp": "HIT / MISS / BYPASS na sekundę dla wybranego zakresu", + "liveChartSamples": "Próbki", + "transformLiveChartTitle": "Aktywność transformacji na żywo", + "transformLiveChartHelp": "Zastosowane / HIT cache / pominięte / błędy na sekundę dla wybranego zakresu", + "transformChartApplied": "Zastosowane", + "transformChartCacheHits": "HIT cache", + "transformChartSkipped": "Pominięte", + "cacheInvalidationTitle": "Purge / BAN cache", + "cacheInvalidationOperation": "Operacja", + "cacheInvalidationSelector": "Selektor", + "cacheInvalidationValue": "Wartość", + "cacheInvalidationHost": "Host (opcjonalnie)", + "cacheBanTtl": "TTL BAN (sekundy)", + "cacheInvalidationHelp": "Wybrany vhost ogranicza zakres operacji. Exact i prefix uwzględniają query string. Operacje regex i prefix / wymagają dodatkowego potwierdzenia.", + "cacheRunInvalidation": "Wykonaj operację", + "cacheActiveBans": "Aktywne reguły BAN", + "cacheBanExpires": "Wygasa", + "cacheNoBans": "Brak aktywnych reguł BAN.", + "cacheTarget": "Target", + "cacheKeyTags": "Klucz cache / tagi", + "cachePurgeUrl": "Purge URL", + "cacheRemoveBan": "Usuń BAN", + "cacheBanCreated": "Utworzono BAN", + "cacheConfirmPurgeUrl": "Usunąć ten URL z cache RAM i cold storage?", + "cacheConfirmRemoveBan": "Usunąć tę regułę BAN?", + "cacheWideInvalidationConfirm": "To szeroka operacja unieważniania i może objąć wiele obiektów cache. Kontynuować?", + "cacheCollapseWaiters": "oczekuje", + "cacheCollapsed": "scalone", + "actions": "Akcje", + "cacheCollapseTitle": "Scalanie żądań", + "cacheCollapseEnable": "Scalaj równoczesne fille dla jednego klucza cache", + "cacheCollapseTimeout": "Limit oczekiwania followera (ms)", + "cacheCollapseFallback": "Fallback po timeout", + "cacheCollapseFallbackOrigin": "Origin", + "cacheCollapseFallbackStale": "Stale, jeśli dostępny", + "cacheStaleErrorTitle": "Polityka stale-if-error", + "cacheSieTimeout": "Timeout", + "cacheSieConnection": "Błąd połączenia", + "cacheSieDns": "Błąd DNS", + "cacheSieTls": "Błąd TLS", + "cacheSieStatuses": "Statusy HTTP 5xx dopuszczające stale fallback", + "cacheProactiveTitle": "Proaktywne odświeżanie", + "cacheProactiveEnable": "Odświeżaj próbkę obiektów cache przed wygaśnięciem", + "cacheProactiveBefore": "Odśwież przed wygaśnięciem (sekundy)", + "cacheProactivePercent": "Próbkowane obiekty (%)", + "cacheProactiveInflight": "Maksymalna liczba równoległych odświeżeń", + "cacheProactiveHelp": "Używa rewalidacji validatorów, deterministycznego próbkowania i globalnego limitu współbieżności, aby zapobiegać lawinie odświeżeń.", + "cacheInvalidationOpen": "Purge / Ban", + "cacheBanListOpen": "Aktywne BAN-y", + "cacheBanRemoved": "Reguła BAN została usunięta", + "tunnelsOpen": "Aktywne tunele", + "tunnelsTitle": "Aktywne WebSockety / tunele", + "tunnelsReload": "Odśwież", + "tunnelsEmpty": "Brak aktywnych tuneli.", + "tunnelId": "ID", + "tunnelClient": "Klient", + "tunnelHost": "Host", + "tunnelUpstream": "Upstream", + "tunnelProtocol": "Protokół", + "tunnelDuration": "Czas", + "tunnelRx": "RX", + "tunnelTx": "TX", + "tunnelKill": "Zamknij", + "tunnelKilled": "Tunel zamknięty przez administratora", + "tunnelKillConfirm": "Zamknąć ten tunel?", + "tunnelKillReason": "Powód zamknięcia", + "tunnelKillReasonPlaceholder": "Zamknięcie administracyjne", + "tunnelKillReasonDefault": "panel administracyjny", + "listenerH2c": "h2c HTTP/2 bez TLS (prior knowledge + Upgrade z HTTP/1.1)", + "vhWebsocketRfc8441": "RFC 8441 Extended CONNECT przez HTTP/2", + "wsH1": "H1 Upgrade", + "wsH2Rfc8441": "H2 RFC 8441", + "wsH3Rfc9220": "H3 RFC 9220", + "wsEstablished": "Utworzone", + "httpProtocolMix": "Miks protokołów HTTP", + "wsProtocolMix": "Miks protokołów WebSocket", + "vhWebsocketMode": "Transport WebSocket", + "vhWebsocketDisabled": "Wyłączony", + "vhWebsocketH1": "HTTP/1.1 Upgrade", + "vhWebsocketH2Fallback": "HTTP/2 RFC 8441 + fallback H1", + "vhWebsocketH3Fallback": "HTTP/3 RFC 9220 + fallback H1", + "vhWebsocketH2H3Fallback": "HTTP/2 + HTTP/3 + fallback H1", + "vhMaxConcurrentRequests": "Maks. równoległych żądań", + "vhBackendQueueTimeout": "Timeout kolejki pojemności (ms)", + "vhBackendCapacityHelp": "0 równoległych żądań oznacza brak limitu. Timeout kolejki równy 0 natychmiast zwraca HTTP 503 po osiągnięciu limitu backendu.", + "vhLastFailure": "Ostatni błąd", + "vhLastRecovery": "Ostatni powrót", + "certificateIssuerName": "Wystawca certyfikatu", + "certificateValidFrom": "Ważny od", + "certificateDaysRemaining": "Pozostało dni", + "certificateExpired": "Certyfikat wygasł", + "certificateExpiresSoon": "Certyfikat wkrótce wygaśnie", + "certificateRenewalStatus": "Status odnawiania", + "certificateRenewalError": "Ostatni błąd odnawiania", + "vhActiveHealth": "Active health", + "vhPassiveFailures": "Błędy passive health", + "certificateAdminRestartRequired": "TLS panelu administracyjnego używa konfiguracji ładowanej przy starcie; uruchom ponownie RustProxy, aby odświeżyć ten certyfikat.", + "cacheTabGeneral": "Ogólne", + "cacheTabResilience": "Odporność", + "cacheTabCold": "Cold cache", + "cacheTabDiagnostics": "Statyczne i diagnostyka", + "certificateApiTitle": "API certyfikatów", + "certificateApiHelp": "Twórz osobne poświadczenia Key ID + Bearer Token dla automatyzacji certyfikatów. Nowe tokeny są domyślnie tylko do reload i są ograniczone do wskazanych ID certyfikatów. Włącz deploy tylko dla automatyzacji, która musi wysyłać materiał certyfikatu.", + "certificateApiKeyId": "Key ID", + "certificateApiToken": "Bearer token (opcjonalna własna wartość)", + "certificateApiTokenHelp": "Pozostaw pole puste, aby wygenerować silny token. Jawna wartość każdego tokenu jest pokazywana tylko raz; RustProxy zapisuje wyłącznie jego hash.", + "certificateApiRotate": "Generuj / obróć token", + "certificateApiRevoke": "Unieważnij token", + "certificateApiOneTimeToken": "Skopiuj ten token teraz", + "certificateApiCurlHelp": "Automatyzacja zewnętrzna wysyła Key ID i Bearer Token przez HTTPS (lub HTTP po loopback). Token reload musi używać /api/certificates/{id}/reload; token API nie może wywołać globalnego reload wszystkich certyfikatów.", + "certificateApiUnknown": "Ładowanie statusu API certyfikatów…", + "certificateApiConfigured": "API certyfikatów włączone", + "certificateApiDisabled": "API certyfikatów wyłączone", + "certificateApiLastUsed": "ostatnio użyty", + "certificateApiRotated": "Token API certyfikatów został utworzony / zmieniony. Skopiuj go teraz.", + "certificateApiRevoked": "Token API certyfikatów został unieważniony.", + "certificateApiRevokeConfirm": "Unieważnić token zewnętrznego API certyfikatów? Certbot i integracje API używające go natychmiast przestaną działać.", + "certificate_api.invalid_token": "Nieprawidłowy Key ID lub Bearer Token API certyfikatów.", + "certificate_api.https_required": "Zewnętrzny token API certyfikatów wymaga HTTPS, chyba że żądanie pochodzi z loopback.", + "certificate_api.invalid_token_format": "Token API certyfikatów musi mieć 32–128 bezpiecznych znaków URL.", + "certificate_api.invalid_key_id": "Nieprawidłowy Key ID API certyfikatów.", + "vhWebsocketLegacySupport": "Włącz zgodność ze starym WebSocket", + "vhWebsocketLegacySupportHelp": "Dla starszych urządzeń i serwerów embedded. Handshake WebSocket do upstreamu używa HTTP/1.1, Host i Origin są dopasowane do skonfigurowanego adresu backendu, a stary format odpowiedzi 101 jest tolerowany. Zwykły ruch HTTP pozostaje bez zmian.", + "vhWebsocketLegacyBadge": "WebSocket legacy", + "runtimeInformation": "Informacje środowiska", + "runtimeInformationHelp": "Wartości raportuje uruchomiony proces RustProxy. Są dostępne tylko w uwierzytelnionym API administratora.", + "runtimePlatform": "Platforma", + "statisticsSource": "Źródło statystyk", + "dataDirectory": "Katalog danych", + "databaseLocation": "Baza danych", + "connectionActivity": "AKTYWNOŚĆ POŁĄCZEŃ", + "reloadCertificate": "Przeładuj certyfikat", + "certificateApiComment": "Komentarz", + "certificateApiCertificateIds": "Dozwolone ID certyfikatów", + "certificateApiCertificateIdsHelp": "Podaj jedno lub więcej ID certyfikatów oddzielonych przecinkiem lub nową linią. Dla każdego innego ID token zostanie odrzucony.", + "certificateApiCertificateIdsRequired": "Podaj co najmniej jedno ID certyfikatu dla tego tokenu.", + "certificateApiAllowDeploy": "Zezwól także na deploy", + "certificateApiAllowDeployHelp": "Dla hooka odnowienia Certbot/acme.sh pozostaw wyłączone. Tryb tylko reload jest zalecanym wariantem least-privilege.", + "certificateApiScopeReloadOnly": "tylko reload", + "certificateApiScopeDeployReload": "deploy + reload", + "certificateApiAllowedIds": "dozwolone ID", + "certificateApiConfiguredCount": "Tokeny API certyfikatów: {count} / {max}", + "certificateApiNeverUsed": "nigdy nie użyty", + "certificateApiRevokeAll": "Unieważnij wszystkie tokeny", + "certificateApiRevokeOneConfirm": "Unieważnić token API certyfikatów {key}? Automatyzacja używająca tego tokenu natychmiast przestanie działać.", + "certificateApiRevokeAllConfirm": "Unieważnić wszystkie tokeny API certyfikatów? Cała automatyzacja zewnętrzna korzystająca z tych tokenów natychmiast przestanie działać.", + "certificate_api.invalid_comment": "Komentarz tokenu API certyfikatów może mieć maksymalnie 256 znaków i nie może zawierać znaków sterujących.", + "certificate_api.limit_reached": "Osiągnięto limit tokenów API certyfikatów.", + "certificate_api.not_found": "Nie znaleziono tokenu API certyfikatów.", + "certificate_api.invalid_certificate_ids": "Token API certyfikatów wymaga 1-32 poprawnych ID certyfikatów.", + "certificate_api.target_required": "Token API certyfikatów nie może przeładować wszystkich certyfikatów naraz; użyj endpointu reload dla konkretnego ID.", + "certificate_api.certificate_forbidden": "Ten token API certyfikatów nie ma dostępu do wskazanego ID certyfikatu.", + "certificate_api.deploy_forbidden": "Ten token API certyfikatów ma tylko uprawnienie reload. Włącz deploy przy rotacji wyłącznie, jeśli automatyzacja musi wysyłać certyfikat.", + "networkInterfacesVisibleCount": "{visible} / {total} interfejsów", + "hideMetric": "Ukryj", + "showMetric": "Pokaż", + "hiddenInterfaces": "Ukryte interfejsy", + "networkNoVisibleInterfaces": "Wszystkie wykryte interfejsy są ukryte w tej przeglądarce.", + "cacheMemorySizingTitle": "Dobór pamięci", + "cacheMemorySizingHelp": "Presety korzystają z efektywnej pamięci dostępnej dla RustProxy (RAM hosta ograniczony limitem cgroup, jeśli występuje). Ustawiają HOT, pamięć przejściową, rozmiar obiektu, liczbę wpisów i kolejkę demotion; limit dysku COLD pozostaje bez zmian.", + "cachePresetConservative": "Oszczędny", + "cachePresetRecommended": "Zalecany", + "cachePresetPerformance": "Wydajnościowy", + "cachePresetSelected": "Wybrany", + "cachePresetCustom": "Własny", + "cacheColdPresetTitle": "Preset polityki COLD", + "cacheColdPresetHelp": "Preset zalecany włącza przenoszenie po bezczynności/LRU/zamknięciu, dobiera kolejkę COLD do pamięci przejściowej i ustawia zachowawcze czasy GC. Ścieżka, limit dysku i limit obiektów pozostają bez zmian.", + "cacheTransientLimit": "Limit pamięci przejściowej (MiB)", + "cacheEvictionLimit": "Maks. usunięć LRU na przyjęcie obiektu", + "cacheColdQueueLimit": "Limit RAM kolejki COLD (MiB)", + "cacheMemoryNotDetected": "Nie wykryto pamięci", + "cacheMemoryPresetUnavailable": "Automatyczne presety wymagają telemetrii RAM hosta lub limitu cgroup.", + "cacheMemoryCgroup": "Efektywny RAM cgroup", + "cacheMemoryDetected": "Wykryty RAM", + "cacheQueueOverTransientWarning": "Limit kolejki COLD nie może przekraczać limitu pamięci przejściowej.", + "cacheMemoryHighWarning": "Ostrzeżenie: budżety HOT + pamięć przejściowa zajmują dużą część efektywnego RAM:", + "cachePresetTransientShort": "przejściowa", + "cachePresetObjectShort": "maks. obiekt", + "cachePresetQueueShort": "kolejka COLD", + "cacheLogicalShort": "logiczne", + "cacheLimitShort": "limit", + "cacheMemoryPressureShort": "presja pamięci", + "cacheNukeLimitShort": "limit LRU", + "cacheMemoryPresetTooSmall": "Automatyczne presety cache są wyłączone poniżej 256 MiB efektywnego RAM; jeśli to celowe, skonfiguruj cache ręcznie.", + "cacheMemoryRecommendedFor": "Zalecane dla wykrytej pamięci:", + "cacheMemoryHeadroomWarning": "Ostrzeżenie: dodatkowy budżet cache jest duży względem aktualnie dostępnej pamięci hosta/cgroup:", + "cacheObjectOverTransientWarning": "Maksymalny rozmiar obiektu przekracza limit pamięci przejściowej; obiekty zbliżone do tego rozmiaru przejdą bez zapisania w cache.", + "vhCacheGlobalDisabled": "Globalny cache jest wyłączony. Ten vhost nie może włączyć cache, dopóki cache globalny pozostaje wyłączony.", + "responseLatencyInfo": "Informacje o czasie odpowiedzi", + "responseLatencyHint": "Wartość to skumulowana średnia długość zakończonych wymian HTTP od startu procesu. Wykres pokazuje kolejne próbki tej średniej; nie jest to percentyl ani czas pojedynczego żądania.", + "web-server": "Serwer WWW", + "web-serverTitle": "Serwuj lokalne pliki.", + "web-serverDescription": "Twórz wielokrotnego użytku usługi WWW i podpinaj je do vhostów lub lokalizacji.", + "webServer": "Serwer WWW", + "webServerIntro": "Tutaj utwórz lokalne usługi WWW, a następnie podepnij je do vhosta lub lokalizacji.", + "webServiceList": "Usługi WWW", + "webServiceSearch": "Szukaj usług WWW", + "webServiceSearchPlaceholder": "Szukaj nazwy, ID lub document root", + "webServiceNew": "Nowa usługa WWW", + "webServiceEditor": "Konfiguracja usługi WWW", + "webServiceTabGeneral": "Ogólne", + "webServiceTabRouting": "Routing", + "webServiceTabMime": "MIME", + "webServiceTabCacheHeaders": "Cache i nagłówki", + "webServiceTabDelivery": "Serwowanie", + "webServiceTabPhp": "PHP-FPM", + "webServiceTabSecurity": "Bezpieczeństwo", + "webServiceId": "ID usługi", + "webServiceName": "Nazwa", + "webServiceRoot": "Document root", + "webServiceIndex": "Pliki index", + "webServiceDirectoryListing": "Listowanie katalogu", + "webServiceMimeOverrides": "Nadpisania MIME", + "webServiceMimeBuiltin": "Wbudowane mapowania", + "webServiceMimeHelp": "Opcjonalne nadpisania rozszerzenie → Content-Type, po jednym na linię. Format: ext = media/type.", + "webServiceMimeInvalid": "Nadpisania MIME muszą mieć unikalny format 'ext = media/type', po jednym wpisie na linię.", + "webServiceDefaultMime": "Domyślny typ MIME", + "webServiceCacheControl": "Cache-Control", + "webServiceCacheRules": "Reguły Cache-Control", + "webServiceCacheRulesHelp": "Bardziej szczegółowe reguły są sprawdzane przed polityką domyślną. Użyj ext:css albo mime:image/.", + "webServiceCacheRulesInvalid": "Reguły Cache-Control muszą mieć format dopasowanie = wartość, np. ext:css = public, max-age=3600.", + "webServiceResponseHeaders": "Nagłówki odpowiedzi", + "webServiceResponseHeadersHelp": "Stałe nagłówki tej usługi WWW. Reguły vhosta/lokalizacji są nakładane później i mogą je nadpisać.", + "webServiceResponseHeadersInvalid": "Nagłówki odpowiedzi muszą mieć unikalny format Nazwa-Nagłówka = wartość, po jednym wpisie na linię.", + "webServiceCacheHelp": "Gdy cache vhosta jest włączony, odpowiedzi serwera WWW spełniające warunki są buforowane również w RustProxy. Odpowiedzi prywatne i no-store nie są buforowane.", + "webServiceTryFiles": "try_files", + "webServiceTryFilesHelp": "Jeden kandydat na linię. Obsługuje $uri, $query_string oraz końcowy status błędu typu =404.", + "webServiceRewrites": "Reguły rewrite", + "webServiceRewritesHelp": "Jedna reguła regex na linię. Użyj wzorzec => /cel dla rewrite wewnętrznego albo wzorzec => 301:/cel dla przekierowania.", + "webServiceRewritesInvalid": "Reguły rewrite muszą mieć format wzorzec => /cel albo wzorzec => 301:/cel (301/302/307/308).", + "webServiceCompression": "Kompresja dynamiczna", + "webServiceCompressionHelp": "Domyślnie dziedziczy politykę vhosta/globalną. Tutaj można ograniczyć poszczególne kodeki; kodeki wyłączone globalnie pozostają niedostępne, a minimalny rozmiar pozostaje globalny.", + "webServicePrecompressed": "Serwuj gotowe pliki skompresowane", + "webServicePrecompressedBr": "Brotli (.br)", + "webServicePrecompressedGzip": "Gzip (.gz)", + "webServiceDownloadExtensions": "Rozszerzenia do pobierania", + "webServiceDownloadExtensionsHelp": "Pliki z tymi rozszerzeniami otrzymają Content-Disposition: attachment.", + "webServiceRangeHelp": "Statyczne i prekompresowane reprezentacje zachowują walidatory, pojedyncze i wieloczęściowe zakresy bajtów, odpowiedzi 206/416 oraz streaming.", + "webServicePhpEnabled": "Włącz PHP-FPM / FastCGI", + "webServicePhpEndpoint": "Endpoint PHP-FPM", + "webServicePhpEndpointHelp": "Unix: unix:/run/php/php-fpm.sock. TCP: tcp:127.0.0.1:9000 albo tcp:[::1]:9000. TCP jest celowo ograniczone do adresów loopback.", + "webServicePhpExtensions": "Rozszerzenia PHP", + "webServicePhpPassAuthorization": "Przekazuj nagłówek Authorization", + "webServicePhpConnectTimeout": "Timeout połączenia (ms)", + "webServicePhpRequestTimeout": "Łączny timeout żądania FastCGI (ms)", + "webServicePhpConcurrency": "Maks. równoległych żądań PHP", + "webServicePhpBodyLimit": "Limit body żądania PHP (bajty, 0 = bez limitu)", + "webServicePhpHeaderLimit": "Maks. bajtów nagłówków odpowiedzi FastCGI", + "webServicePhpStderrLimit": "Maks. bajtów stderr PHP-FPM w logu", + "webServicePhpCustomParams": "Własne parametry FastCGI", + "webServicePhpCustomParamsHelp": "Jedna linia NAME = wartość, np. APP_ENV = production. Nazwy: A-Z, cyfry i podkreślenia; bez HTTP_*, PHP_VALUE, PHP_ADMIN_VALUE i podstawowych parametrów CGI.", + "webServicePhpCustomParamsInvalid": "Własne parametry FastCGI muszą mieć unikalny format NAZWA = wartość, po jednym wpisie na linię.", + "webServicePhpCustomDataTooLarge": "Własne parametry FastCGI i nadpisania PHP INI mogą łącznie zajmować maksymalnie 128 KiB.", + "webServicePhpValues": "Nadpisania PHP INI (PHP_VALUE)", + "webServicePhpValuesHelp": "Jedna dyrektywa = wartość w wierszu, np. memory_limit = 256M, upload_max_filesize = 64M, post_max_size = 64M. O możliwości zmiany decyduje PHP-FPM.", + "webServicePhpValuesInvalid": "Nadpisania PHP INI muszą mieć unikalny format dyrektywa = wartość, po jednym wpisie na linię.", + "webServicePhpDocsButton": "Składnia i bezpieczeństwo FastCGI", + "webServicePhpDocsTitle": "Konfiguracja PHP-FPM / FastCGI", + "webServicePhpDocsIntro": "RustProxy łączy się bezpośrednio z istniejącą pulą PHP-FPM. Na tym samym hoście preferuj socket Unix. TCP jest akceptowane wyłącznie na loopback i nie jest publicznym/sieciowym transportem FastCGI.", + "webServicePhpDocsEndpointTitle": "Składnia endpointu", + "webServicePhpDocsEndpoint": "Użyj unix: z pełną ścieżką gniazda albo tcp: z adresem IP loopback i portem. Nazwy DNS i publiczne adresy TCP są zabronione. Oddziel pule PHP-FPM aplikacji z różnymi ograniczeniami INI.", + "webServicePhpDocsCustomTitle": "Własne parametry FastCGI", + "webServicePhpDocsCustom": "Jedna linia NAME = wartość (nazwa zaczyna się literą i zawiera A-Z, cyfry lub podkreślenie). Wartości są statyczne. SCRIPT_FILENAME, HTTP_* i PHP_VALUE/PHP_ADMIN_VALUE są zastrzeżone.", + "webServicePhpDocsValuesTitle": "Nadpisania PHP INI", + "webServicePhpDocsValues": "Każda linia trafia do PHP_VALUE. Zaufane ustawienia administracyjne wpisuj osobno w PHP_ADMIN_VALUE; żadne z tych pól nie izoluje procesów korzystających z tej samej puli PHP-FPM.", + "webServicePhpDocsLimitsTitle": "Limity i nagłówki", + "webServicePhpDocsLimits": "Timeout żądania obejmuje wymianę FastCGI. Limit body jest egzekwowany podczas streamingu i po przekroczeniu zwraca 413; 0 wyłącza limit body po stronie RustProxy. Limity nagłówków odpowiedzi i stderr ograniczają pamięć/dane diagnostyczne. Dozwolone są maks. 64 własne parametry FastCGI i 64 nadpisania PHP INI, łącznie do 128 KiB danych własnych. Przekazywanie Authorization można wyłączyć, gdy aplikacja nie potrzebuje HTTP_AUTHORIZATION.", + "webServicePhpDocsSecurity": "Nie umieszczaj sekretów we własnych parametrach ani PHP_VALUE, jeśli nie akceptujesz ich przechowywania w zwykłej konfiguracji Web Service. Używaj osobnej, nieuprzywilejowanej tożsamości puli PHP-FPM i nie wystawiaj PHP-FPM do publicznej sieci.", + "webServicePhpHelp": "RustProxy komunikuje się bezpośrednio przez FastCGI z długowieczną pulą PHP-FPM. PHP nie jest uruchamiane osobno dla każdego żądania, a kod źródłowy PHP nie jest serwowany, gdy obsługa PHP jest wybrana.", + "webServicePhpSymlinkConflict": "PHP-FPM wymaga obecnie polityki symlinków Deny.", + "webServiceSymlinkPolicy": "Polityka symlinków", + "webServiceSymlinkDeny": "Zabroń", + "webServiceSymlinkInsideRoot": "Zezwól tylko wewnątrz document root (Linux)", + "webServiceSafety": "Document root jest ścisłą granicą dostępu do filesystemu. Domyślnie symlinki są blokowane. Linux może opcjonalnie podążać wyłącznie do celów pozostających w document root; PHP-FPM wymaga obecnie polityki blokującej symlinki.", + "webServiceSelect": "Wybierz usługę WWW", + "webService": "Usługa WWW", + "webServiceUsedBy": "Używana przez", + "webServiceUnused": "Niepodpięta", + "webServiceEmpty": "Brak usług WWW.", + "webServiceSearchEmpty": "Brak usług WWW pasujących do wyszukiwania.", + "webServiceInUse": "Usługa WWW jest nadal używana przez", + "webServiceDelete": "Usunąć tę usługę WWW?", + "webServiceIndexRequired": "Pliki index są opcjonalne.", + "webServiceDuplicate": "Usługa WWW o tym ID już istnieje.", + "webServiceRequired": "Wybierz usługę WWW.", + "sourceType": "Typ źródła", + "sourceReverseProxy": "Reverse Proxy", + "sourceWebServer": "Serwer WWW", + "sourceHelp": "Vhost przechowuje tylko wybór źródła. Ustawienia Serwera WWW pozostają w wybranej, wielokrotnego użytku usłudze WWW.", + "locationWebServerAction": "Serwer WWW", + "locationWebServerShort": "Serwer WWW", + "docsReader": "Reader", + "docsApi": "API", + "docsReaderSearch": "Szukaj w dokumentacji", + "docsReaderSearchPlaceholder": "Szukaj tytułu lub ścieżki", + "docsReaderEmpty": "Brak dokumentów pasujących do wyszukiwania.", + "docsCategoryProject": "Projekt", + "docsCategoryGuides": "Dokumentacja funkcjonalna", + "docsCategoryTechnical": "Techniczna", + "docsCategoryAudits": "Audyty i historia", + "docsCategoryHistorical": "Historyczne", + "webServiceIndexHelp": "Opcjonalne. Pozostaw puste, aby pominąć wyszukiwanie indexu; przy włączonym listingu od razu zostanie pokazana lista plików.", + "webServiceNoIndex": "Brak plików index", + "vhBackendRequired": "Reverse Proxy wymaga co najmniej jednego backendu. Dodaj backend albo zmień źródło na Serwer WWW.", + "vhListenerRequired": "Wybierz co najmniej jeden listener dla tego vhosta.", + "traffic": "Ruch", + "trafficTitle": "Ruch per vhost.", + "trafficDescription": "Ograniczone statystyki żądań: kraje, User-Agenty, adresy IP klientów, ścieżki, trasy i wyniki obsługi.", + "trafficSinceStart": "Od startu procesu", + "trafficResponseBytes": "Bajty odpowiedzi", + "trafficRequestBytes": "Bajty żądań", + "trafficMeanLatency": "Średnie opóźnienie", + "trafficDroppedSamples": "Odrzucone próbki statystyk", + "trafficBoundedLabel": "OGRANICZONE / ASYNCHRONICZNE", + "trafficDimensionsTitle": "Wymiary ruchu", + "trafficApproxHelp": "Listy TOP są przybliżone i mają stały limit, aby ruch o dużej liczbie unikalnych wartości nie zwiększał pamięci proxy bez ograniczeń.", + "trafficTopCountries": "Top kraje", + "trafficTopUserAgents": "Top User-Agenty", + "trafficTopClientIps": "Top adresy IP klientów", + "trafficClientIp": "IP klienta", + "trafficTopPaths": "Top ścieżki", + "trafficTopRoutes": "Top trasy", + "trafficMethods": "Metody", + "trafficStatusClasses": "Klasy statusów", + "trafficCacheOutcomes": "Wyniki cache", + "trafficValue": "Wartość", + "trafficOther": "Pozostałe", + "trafficNoCountryData": "Zbieranie kraju jest wyłączone albo baza GeoIP jest niedostępna.", + "trafficNoUserAgentData": "Brak zebranych danych User-Agent.", + "trafficNoData": "Brak danych o ruchu.", + "trafficUserAgentEnabled": "Zbieranie User-Agent włączone", + "trafficUserAgentDisabled": "Zbieranie User-Agent wyłączone", + "trafficCountryEnabled": "Zbieranie kraju włączone", + "trafficCountryDisabled": "Zbieranie kraju wyłączone", + "trafficCountryNoDb": "Zbieranie kraju włączone, baza GeoIP niedostępna", + "trafficUpdated": "Odświeżono", + "loggingCaptureTitle": "Zbierane wymiary żądania", + "loggingCaptureUserAgent": "Zbieraj User-Agent", + "loggingCaptureCountry": "Zbieraj kraj z GeoIP", + "loggingCaptureCountryHelp": "Lookup kraju jest opcjonalny i domyślnie wyłączony. Włącz go tylko, gdy potrzebujesz kraju w logach/statystykach; reguły ACL/rate-limit per kraj nadal użyją GeoIP, gdy jest to wymagane.", + "cacheAutoTrimEnabled": "Automatycznie zwalniaj nieużywaną pamięć cache HOT", + "cacheAutoTrimAfter": "Czas bezczynności przed trim HOT (sekundy)", + "cacheAutoTrimTarget": "Docelowe użycie HOT po trim (%)", + "cacheAutoTrimHelp": "Gdy pamięć HOT przekracza próg, w ograniczonych partiach usuwane są wyłącznie obiekty nieużywane przez ustawiony czas. Aktywny working set nie jest czyszczony.", + "webServiceTabHtaccess": ".htaccess", + "webServiceHtaccessEnabled": "Włącz zgodność z .htaccess", + "webServiceHtaccessFilename": "Plik konfiguracyjny", + "webServiceHtaccessRecursive": "Skanuj podkatalogi", + "webServiceHtaccessStrict": "Tryb ścisły dla niewspieranych dyrektyw", + "webServiceHtaccessDocs": "Dokumentacja zgodności", + "webServiceHtaccessHelp": "RustProxy kompiluje ograniczony podzbiór zgodny z Apache podczas zastosowania Web Service. Pliki nie są parsowane przy każdym żądaniu; po zmianie .htaccess na dysku zastosuj Web Service ponownie.", + "webServiceHtaccessDocsTitle": "Zgodność .htaccess", + "webServiceHtaccessDocsIntro": "To warstwa zgodności przeznaczona do migracji, a nie pełne środowisko Apache. Wspierane dyrektywy są kompilowane do reguł Web Servera RustProxy.", + "webServiceHtaccessDocsSupportedTitle": "Wspierany podzbiór", + "webServiceHtaccessDocsRewriteTitle": "Zgodność rewrite", + "webServiceHtaccessDocsRewrite": "Warunki rewrite obsługują testy REQUEST_FILENAME (-f, -d, -s), REQUEST_URI, REQUEST_METHOD, QUERY_STRING, HTTP_HOST, HTTPS, DOCUMENT_ROOT, THE_REQUEST i nagłówki HTTP. Flagi: L, END, R, R=301/302/307/308, QSA, NC, F, OR. PT/NE są akceptowane jako no-op. E= jest akceptowane jako no-op zgodności, ponieważ nagłówki HTTP żądania i tak trafiają do PHP-FPM.", + "webServiceHtaccessDocsPhpTitle": "PHP-FPM", + "webServiceHtaccessDocsPhp": ".htaccess może dodawać rozszerzenia PHP przez AddHandler, ale endpoint PHP-FPM, timeouty i limit współbieżności nadal są kontrolowane w zakładce PHP-FPM Web Service. Składnia Apache SetHandler wskazująca socket proxy celowo nie jest akceptowana.", + "webServiceHtaccessDocsReloadTitle": "Ładowanie i limity", + "webServiceHtaccessDocsReload": "Pliki są odczytywane i kompilowane podczas „Validate, save and apply”. RustProxy nie parsuje .htaccess przy każdym żądaniu. Po zmianie pliku zastosuj konfigurację ponownie. Skan ma limity: 128 plików, głębokość 16, 128 KiB na plik i 1 MiB łącznie.", + "webServiceHtaccessDocsUnsupportedTitle": "To nie jest pełny Apache", + "webServiceHtaccessDocsUnsupported": "ErrorDocument, SetEnvIf, Redirect/RedirectMatch i FallbackResource nie są zaimplementowane w tej warstwie. Ogólne Files/FilesMatch, niewspierane dyrektywy uwierzytelniania/autoryzacji, handlery inne niż PHP i SetHandler są odrzucane nawet przy wyłączonym trybie ścisłym, ponieważ ich ciche pominięcie mogłoby osłabić kontrolę dostępu lub ujawnić kod źródłowy. Pliki .htaccess/.htpasswd są zawsze blokowane przed wysłaniem przez HTTP. Pozostałe niewspierane dyrektywy niesecurity są logowane i ignorowane, chyba że włączono tryb ścisły.", + "webServiceHtaccessDocsSecurity": "ACL vhost/location, uwierzytelnianie, strony błędów i reguły odpowiedzi nadal mają nadrzędną rolę. .htaccess nie może wyjść poza document root, wybrać dowolnego socketu FastCGI ani nadpisać chronionych nagłówków transportowych.", + "webServicePresetTitle": "Preset konfiguracji", + "webServicePreset": "Preset", + "webServicePresetCustom": "Własny", + "webServicePresetStatic": "Strona statyczna", + "webServicePresetSpa": "SPA", + "webServicePresetPhpGeneric": "PHP ogólny", + "webServicePresetDownloads": "Pliki / repozytorium pobrań", + "webServicePresetAction": "Akcja presetu", + "webServicePresetApply": "Zastosuj preset", + "webServicePresetHelp": "Preset wypełnia zwykłe ustawienia Web Service i wszystko nadal można edytować. Document root, ID/nazwa usługi i endpoint PHP-FPM nie są zmieniane.", + "webServicePresetChoose": "Wybierz preset przed jego zastosowaniem.", + "webServicePresetApplied": "Preset zastosowany. Sprawdź wygenerowane ustawienia przed zapisem.", + "webServicePresetHintStatic": "Strona statyczna: indeksy HTML, prekompresowane pliki i reguły cache dla CSS/JS/obrazów/fontów.", + "webServicePresetHintSpa": "SPA: fallback do index.html przez try_files, prekompresowane assety i długi cache zasobów statycznych.", + "webServicePresetHintPhpGeneric": "PHP ogólny: PHP-FPM włączony, index.php pierwszy, bez front-controllera dla brakujących plików.", + "webServicePresetHintWordpress": "WordPress: PHP-FPM, index.php i routing front-controller. Document root ustaw na publiczny katalog WordPressa.", + "webServicePresetHintDrupal": "Drupal: PHP-FPM, index.php i routing front-controller z cache zasobów statycznych.", + "webServicePresetHintLaravel": "Laravel: PHP-FPM i routing front-controller. Document root ustaw na katalog public/ aplikacji.", + "webServicePresetHintSymfony": "Symfony: PHP-FPM i routing front-controller. Document root ustaw na katalog public/ aplikacji.", + "webServicePresetHintJoomla": "Joomla: PHP-FPM, index.php i routing front-controller z cache zasobów statycznych.", + "webServicePresetHintPrestashop": "PrestaShop: PHP-FPM, index.php i routing front-controller z cache zasobów statycznych.", + "webServicePresetHintDownloads": "Pobrania: directory listing włączony, obsługa Range i typowe archiwa/dokumenty wymuszane jako załączniki.", + "webServiceHardeningTitle": "Utwardzenie", + "webServiceHardeningAttacks": "Blokuj typowe próby ataków", + "webServiceHardeningHeaders": "Usuwaj zbędne nagłówki ujawniające technologię", + "webServiceHardeningSecurityHelp": "Przełączniki utwardzenia są widoczne obok wyboru presetu w zakładce Ogólne. Po zastosowaniu presetu pozostają niezależnymi ustawieniami.", + "trafficTabOverview": "Przegląd", + "trafficTabClients": "Klienci", + "trafficTabRequests": "Żądania", + "trafficTabDelivery": "Obsługa odpowiedzi", + "trafficVolume": "Wolumen ruchu", + "vhMethodMode": "Polityka metod HTTP", + "vhMethodList": "Metody HTTP (jedna na linię)", + "vhMethodHelp": "Zezwalaj tylko na podane metody albo blokuj podane metody dla tego vhosta. Reguła łączy się z polityką lokalizacji.", + "locationAclProfile": "Profil ACL", + "locationMethodMode": "Polityka metod HTTP", + "locationMethodList": "Metody HTTP (jedna na linię)", + "locationMethodAllow": "Zezwalaj tylko", + "locationMethodBlock": "Blokuj wskazane", + "locationMethodHelp": "Polityka lokalizacji łączy się z polityką vhosta. Przykład: GET, HEAD i POST.", + "locationCountryMode": "Polityka dostępu krajów", + "locationCountryList": "Kody krajów (jeden na linię)", + "locationCountryHelp": "Polityka krajów lokalizacji łączy się z polityką vhosta/profilu. GeoIP jest odczytywany tylko, gdy jest potrzebny.", + "configHistory": "Historia rewizji", + "configHistoryRetention": "SQLITE / OSTATNIE 512 REWIZJI", + "configHistoryHelp": "Wybierz oznaczony dzień, aby zobaczyć zachowane rewizje konfiguracji i ich zmiany. Historia jest zapisywana od wersji 0.25.3.", + "configHistoryChanges": "ZMIANY", + "configHistorySelectDay": "Wybierz dzień.", + "configHistoryRevision": "Rewizja", + "configHistoryRevisionShort": "rew.", + "configHistoryBefore": "Przed", + "configHistoryAfter": "Po", + "configHistoryBaseline": "To najstarsza zachowana baza; wcześniejsza rewizja nie jest dostępna do porównania.", + "configHistoryNoChanges": "Nie znaleziono różnic w konfiguracji.", + "configHistoryDiffLimit": "Podgląd różnic ograniczono do 200 zmienionych ścieżek.", + "configHistoryMissing": "(brak)", + "configHistoryEntries": "rewizji", + "configHistorySummary": "Zachowane rewizje: {count}", + "configHistoryEmpty": "Brak zachowanych rewizji.", + "configHistoryLoading": "Wczytywanie historii rewizji...", + "configHistoryFailed": "Nie udało się wczytać historii rewizji.", + "configHistoryRestoreAction": "Przywróć rewizję", + "configHistoryRestoreConfirm": "Przywrócić rewizję {revision}? Zostanie zwalidowana i zapisana jako nowa rewizja po bieżącej rewizji {current}. Dotychczasowa historia zostanie zachowana.", + "configHistoryRestored": "Rewizja {revision} została przywrócona jako nowa rewizja {new_revision}.", + "about": "O projekcie", + "aboutTitle": "O RustProxy", + "aboutAuthor": "Autor", + "aboutLicense": "Licencja", + "aboutLicenseValue": "Open Source", + "aboutOpenSourceText": "RustProxy jest projektem open source. Kod źródłowy jest publicznie dostępny do wglądu, rozwoju i współtworzenia.", + "aboutRepository": "Oficjalne repozytorium źródłowe", + "aboutPackagesRepository": "Oficjalne repozytorium pakietów", + "aboutPythonRuntime": "Środowisko Python certyfikatów", + "runtimePython": "Python", + "runtimePip": "pip", + "runtimeCertbot": "Certbot", + "runtimeAcmeMode": "Tryb ACME", + "runtimeAvailable": "Dostępny", + "runtimeUnavailable": "Niedostępny", + "vhPoolRecycleRequests": "Odśwież pulę upstream po liczbie żądań (0 = wył.)", + "vhPoolRecycleSeconds": "Maks. wiek puli upstream (sekundy, 0 = wył.)", + "vhHttp1MaxBuffer": "Maks. bufor odczytu HTTP/1 upstream (bajty)", + "vhPoolRecycleHelp": "Odświeżanie podmienia tylko generację klienta Hyper danego backendu; aktywne wymiany kończą się normalnie. Domyślne limity ograniczają retencję pamięci puli/TLS bez przeładowania vhosta ani cache. Limit żądań: 0 lub 1000..10000000; limit czasu: 0 lub 60..604800.", + "backendSearch": "Szukaj backendów", + "backendSearchPlaceholder": "Szukaj vhosta, backendu lub stanu", + "backendSortBy": "Sortuj według", + "backendSortAscending": "Sortowanie rosnące", + "backendSortDescending": "Sortowanie malejące", + "backendColumns": "Kolumny", + "backendSearchEmpty": "Brak backendów pasujących do wyszukiwania.", + "vhQuickEnable": "Włącz", + "vhQuickDisable": "Wyłącz", + "vhEnableHint": "Określa, czy ten vhost przyjmuje ruch.", + "backendColumnsRequired": "Co najmniej jedna kolumna tabeli backendów musi pozostać widoczna.", + "logs": "Logs", + "storedLogs": "Zapisane logi", + "fileLogsSearchHelp": "Składnia wyszukiwania", + "fileLogsSearchHelpTitle": "Wyszukiwanie po polach jak w Kibanie", + "fileLogsSearchHelpText": "Używaj warunków pole:wartość. Warunki łączy AND; znak ! przed warunkiem wyklucza wynik. Cudzysłowy zachowują spacje.", + "fileLogsParsedFields": "Sparsowane pola", + "fileLogsRawJson": "Surowy JSON", + "filelogs": "Logs", + "filelogsTitle": "Przeglądarka zapisanych logów", + "filelogsDescription": "Przeszukuj skonfigurowane logi RustProxy na dysku niezależnie od bufora Live Logs.", + "fileLogsTitle": "Przeglądarka zapisanych logów", + "fileLogsMode": "LOKALNIE / TYLKO ODCZYT / LIMITY", + "fileLogsHelp": "Przeszukuje logi access/error RustProxy w ustalonych limitach i parsuje pola bez ujawniania ścieżek systemu plików.", + "fileLogsSettingsHelp": "Przeglądaj skonfigurowane pliki access/error z filtrami po stronie serwera. Logowanie do plików może być wyłączone, a Live Logs nadal korzysta z bufora pamięci.", + "openLogs": "Otwórz Logs", + "fileLogsSource": "Źródło", + "fileLogsAllSources": "Wszystkie skonfigurowane pliki", + "fileLogsNoSources": "Brak dostępnych skonfigurowanych plików logów.", + "fileLogsEvent": "Zdarzenie", + "fileLogsPageSize": "Rozmiar strony", + "fileLogsRecord": "Żądanie / przyczyna", + "fileLogsEmpty": "Brak rekordów pasujących do filtrów.", + "fileLogsDetails": "Szczegóły rekordu", + "fileLogsSelectRecord": "Wybierz rekord, aby zobaczyć jego JSON.", + "fileLogsSearchPlaceholder": "req: GET AND !country: Poland", + "fileLogsTruncated": "osiągnięto limit skanowania", + "fileLogsSummary": "{from}–{to} z {total} wyników · przeskanowano {bytes} / {lines} linii", + "fileLogsMatchedBy": "Dopasowanie przez", + "fileLogsSni": "SNI", + "fileLogsAuthority": "Authority / Host", + "fileLogsColumnsRequired": "Co najmniej jedna kolumna zapisanych logów musi pozostać widoczna.", + "fileLogsSourcesRead": "Skonfigurowane pliki", + "search": "Szukaj", + "all": "Wszystkie", + "clientIp": "IP klienta", + "vhost": "Vhost", + "previous": "Poprzednia", + "next": "Następna", + "refresh": "Odśwież", + "applyFilters": "Zastosuj filtry", + "appearance": "Wygląd", + "appearanceEyebrow": "INTERFEJS", + "appearanceHelp": "Te preferencje są zapisywane dla konta administratora i obowiązują również w kolejnych sesjach.", + "languageAutomatic": "Automatycznie (przeglądarka)", + "interfaceScale": "Skala interfejsu", + "interfaceScaleHelp": "Skaluj cały interfejs administracyjny, łącznie z modalami i nakładkami, bez zmiany powiększenia przeglądarki.", + "timeFormat": "Format czasu", + "timeFormat24": "24-godzinny", + "timeFormat12": "12-godzinny", + "chartStyle": "Styl wykresów", + "chartStyleDefault": "Domyślny", + "chartStyleSoft": "Łagodny", + "chartStyleTechnical": "Techniczny", + "chartStyleHighContrast": "Wysoki kontrast", + "chartStylePreview": "Podgląd", + "draftDockTitle": "Szkice", + "draftUntitled": "Nowy", + "draftRestore": "Otwórz szkic", + "draftDiscard": "Usuń szkic", + "draftDiscardConfirm": "Usunąć ten niezapisany szkic?", + "draftMinimized": "Szkic przeniesiono do docka.", + "chartStyleNeon": "Neonowy", + "chartStyleMonochrome": "Monochromatyczny", + "chartStyleWarm": "Ciepły", + "chartStyleCool": "Chłodny", + "settingsSearch": "Znajdź ustawienia", + "settingsSearchPlaceholder": "Szukaj ustawień, sekcji i opcji...", + "settingsSearchResults": "Pasujące sekcje", + "appearanceReset": "Przywróć wygląd", + "appearanceResetDone": "Przywrócono domyślne ustawienia wyglądu", + "appearanceInterfaceSection": "INTERFEJS", + "appearanceInterfaceTitle": "Interfejs", + "appearanceChartsSection": "WYKRESY", + "appearanceDataSection": "TABELE / LOGI", + "appearanceDashboardSection": "DASHBOARD", + "resetSection": "Resetuj sekcję", + "appearanceSectionResetDone": "Ustawienia sekcji zostały zresetowane", + "chartStylePalettes": "Palety stylów wykresów", + "tableDensity": "Gęstość tabel i logów", + "densityNormal": "Normalna", + "densityUltra": "Ultra kompaktowa", + "tableDensityHelp": "Zmienia wysokość wierszy i odstępy w logach niezależnie od globalnej skali interfejsu.", + "dashboardLayout": "Układ dashboardu", + "dashboardDensity": "Gęstość dashboardu", + "dashboardDensityHelp": "Steruje rozmiarem kafelków telemetrii w Overview bez zmiany reszty interfejsu.", + "dashboardWidgets": "Kafelki Overview", + "dashboardWidgetsHelp": "Pokazuj, ukrywaj i zmieniaj kolejność kafelków telemetrii na górze Overview.", + "dashboardWidgetVisible": "Widoczny", + "moveUp": "Przenieś wyżej", + "moveDown": "Przenieś niżej", + "draftDockCollapse": "Zwiń szkice", + "draftDockExpand": "Rozwiń szkice", + "draftAgeNow": "teraz", + "draftAgeMinutes": "{count} min", + "draftAgeHours": "{count} godz.", + "draftAgeDays": "{count} d", + "buttonStyle": "Styl przycisków", + "buttonStyleNormal": "Normalny", + "buttonStyleCompact": "Kompaktowy", + "buttonStyleHelp": "Normalny używa odświeżonych przycisków; Kompaktowy przywraca mniejsze przyciski ze źródłowego interfejsu we wszystkich akcjach i zakładkach modali.", + "entityEnableHint": "Określa, czy ten element jest aktywny.", + "setupCommandExample": "Przykład dla standardowej instalacji usługi:", + "certificateRuntimeTitle": "Środowisko ACME", + "certificateRuntimePreparing": "Przygotowanie", + "certificateRuntimeManaged": "Python aplikacji", + "certificateRuntimeSystem": "Fallback systemowy", + "certificateRuntimeUnavailable": "Niedostępne", + "certificateRuntimePath": "Środowisko aplikacji", + "acme.runtime_checking": "Sprawdzanie lokalnego środowiska Python do zarządzania certyfikatami.", + "acme.runtime_initializing": "Tworzenie prywatnego środowiska Python w katalogu danych RustProxy i instalowanie modułów Certbota przez pip.", + "acme.runtime_ready": "Prywatne środowisko Python/pip do zarządzania certyfikatami jest gotowe.", + "acme.runtime_fallback": "Nie można użyć prywatnego środowiska. RustProxy korzysta z systemowego Pythona i Certbota dostępnego w PATH.", + "acme.runtime_python_missing": "Python nie jest dostępny w tym systemie. Automatyczne generowanie i odnawianie ACME są niedostępne; import PEM i istniejące pliki certyfikatów nadal działają.", + "acme.runtime_pip_missing": "Python jest dostępny, ale RustProxy nie może utworzyć lokalnego środowiska pip, a systemowy Certbot nie jest dostępny. Zainstaluj Python z venv/pip albo pakiety Certbota. Import PEM i istniejące pliki nadal działają.", + "acme.runtime_setup_failed": "RustProxy nie może utworzyć lokalnego środowiska Python/pip, a systemowy Certbot nie jest dostępny. Sprawdź dostęp do sieci/pakietów albo zainstaluj Certbota z dystrybucji. Import PEM i istniejące pliki nadal działają.", + "acme.runtime_venv_failed": "Python i pip są dostępne, ale RustProxy nie może utworzyć prywatnego środowiska w katalogu danych. Sprawdź obsługę venv/virtualenv oraz uprawnienia do katalogu danych. Systemowy Certbot nie jest dostępny.", + "acme.runtime_venv_pip_failed": "Prywatne środowisko Python zostało utworzone, ale nie udało się uruchomić w nim pip/ensurepip. Sprawdź instalację Python venv/ensurepip. Systemowy Certbot nie jest dostępny.", + "acme.runtime_certbot_install_failed": "Prywatne środowisko Python działa, ale pip nie może zainstalować pakietów Certbota. Sprawdź dostęp do PyPI/proxy/DNS/TLS. Systemowy Certbot nie jest dostępny.", + "acme.runtime_certbot_validation_failed": "Pakiety ACME zostały zainstalowane, ale Certbot lub jedna z wymaganych wtyczek nie przechodzi walidacji. Usuń katalog acme-python i uruchom RustProxy ponownie albo sprawdź zgodność lokalnego Pythona.", + "acme.runtime_data_dir_missing": "Katalog danych RustProxy jest niedostępny, więc nie można utworzyć prywatnego środowiska Python. Import PEM i istniejące pliki certyfikatów nadal działają.", + "geoipUpdatesTitle": "Aktualizacje GeoIP", + "geoipUpdatesHelp": "Pobiera w tle bezpośrednie źródła baz krajów GeoIP i przeładowuje je bez restartu ruchu proxy. Weryfikacja SHA-256 wydawcy jest opcjonalna osobno dla każdego pliku.", + "geoipUpdatesEnable": "Włącz automatyczne aktualizacje GeoIP", + "geoipReleaseUrl": "Bazowy URL plików wydań", + "geoipChecksumUrl": "Bazowy URL plików sum kontrolnych", + "geoipMaxFileMiB": "Maksymalny rozmiar pliku (MiB)", + "geoipIntervalHours": "Interwał aktualizacji (godziny)", + "geoipStatusLoaded": "Baza GeoIP załadowana", + "geoipStatusMissing": "Baza GeoIP nie jest jeszcze załadowana. Worker w tle automatycznie ponowi próbę.", + "geoipStatusDisabled": "Automatyczne aktualizacje GeoIP są wyłączone.", + "cacheViaEnable": "Dodawaj nagłówek odpowiedzi Via", + "cacheViaValue": "Wartość nagłówka Via", + "cacheViaHelp": "Domyślnie Via: RustProxy. Zmiany bypassów i nagłówków diagnostycznych odpowiedzi działają od razu bez czyszczenia zapisanych obiektów cache.", + "geoipUpdateNow": "Pobierz teraz", + "geoipUpdateNowHelp": "Używa zapisanych bezpośrednich URL-i źródeł i opcjonalnych URL-i sum kontrolnych. Ręczne pobranie działa także przy wyłączonych aktualizacjach automatycznych i przeładowuje zweryfikowaną bazę bez restartu.", + "geoipUpdateSaveFirst": "Najpierw zapisz ustawienia GeoIP, a potem uruchom ręczne pobranie.", + "geoipUpdateWorking": "Pobieranie i walidacja danych GeoIP…", + "geoipUpdateUpdated": "Baza GeoIP została pobrana i przeładowana bez restartu.", + "geoipUpdateCurrent": "Baza GeoIP jest już aktualna i załadowana.", + "geoipUpdateBusy": "Inna aktualizacja GeoIP już trwa; spróbuj ponownie za chwilę.", + "geoipFormat": "Format bazy", + "geoipFormatCsvPair": "Zakresy CSV (osobno IPv4 + IPv6)", + "geoipFormatMmdb": "MaxMind DB (.mmdb, IPv4 + IPv6)", + "geoipIpv4Url": "Bezpośredni URL CSV IPv4", + "geoipIpv4ChecksumUrl": "URL SHA-256 IPv4 (opcjonalny)", + "geoipIpv6Url": "Bezpośredni URL CSV IPv6", + "geoipIpv6ChecksumUrl": "URL SHA-256 IPv6 (opcjonalny)", + "geoipMmdbUrl": "Bezpośredni URL MaxMind DB", + "geoipMmdbChecksumUrl": "URL SHA-256 MaxMind DB (opcjonalny)", + "geoipSourceHelp": "Tryb CSV używa osobnych plików zakresów IPv4 i IPv6. Tryb MMDB używa jednego natywnego pliku MaxMind DB i obsługuje standardowe pola country.iso_code, registered_country.iso_code albo country_code. Puste pole URL sumy wyłącza weryfikację sumy wydawcy; każdy plik nadal ma limit rozmiaru i jest walidowany przed atomową publikacją. Dla lokalnej własnej bazy MMDB umieść plik w /geoip/country.mmdb, wybierz MMDB i wyłącz automatyczne aktualizacje.", + "certificateFileSource": "Położenie plików", + "certificateSourceManaged": "Katalog certyfikatów RustProxy (/certs)", + "certificateSourceExternal": "Pliki zewnętrzne (pełne ścieżki)", + "certificateSourceManagedHelp": "Podaj tylko nazwy plików, np. app.pem i app.key. RustProxy ładuje je z /certs (przy instalacji pakietowej zwykle /var/lib/rustproxy/certs).", + "certificateSourceExternalHelp": "Podaj pełne ścieżki na serwerze. Przykład Certbota: /etc/letsencrypt/live/example.com/fullchain.pem oraz /etc/letsencrypt/live/example.com/privkey.pem. Symlinki live/ są rozwiązywane przy każdym reloadzie. Odczyt przez POSIX ACL jest obsługiwany i RustProxy nie wymaga konkretnego chmod. Klucz prywatny nie może być czytelny dla innych ani zapisywalny przez grupę/innych, a katalogi ścieżki muszą być własnością root lub rustproxy i nie mogą być zapisywalne przez grupę/innych.", + "certificateExternalAclExample": "Przykład ACL (prawo przejścia bez listowania katalogu):\nsetfacl -m u:rustproxy:--x /etc/ssl/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/\nsetfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/\nsetfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem /etc/letsencrypt/archive/example.com/privkey*.pem\nPo odnowieniu nadaj odczyt bieżącym plikom docelowym przed reloadem API:\nsetfacl -m u:rustproxy:r-- \"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)\" \"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)\"", + "certificateExternalFiles": "Zewnętrzne ścieżki plików", + "vhWwwRedirect": "Przekieruj www ↔ bez www", + "vhWwwRedirectTarget": "Wybierz domenę i kierunek przekierowania (308)", + "vhWwwRedirectHelp": "Tylko wybrana para www/bez www jest przekierowywana (308). Pozostałe domeny działają na tym samym backendzie bez przekierowań między nimi. Certyfikat TLS musi obejmować obie wersje.", + "vhCacheForceEnabled": "Ryzykowne wymuszenie cache dla wskazanych URL", + "vhCacheForceTargets": "Prefiksy ścieżki/query dla wymuszonego cache (po jednym w linii)", + "vhCacheForceHelp": "Tylko per-vhost. Pasujące GET/HEAD mogą ominąć bypass query, Cookie/Authorization żądania i klienckie no-cache/no-store. Cookie/Authorization nadal tworzą osobne warianty. Odpowiedzi origin z Set-Cookie, private lub no-store nadal nigdy nie są zapisywane.", + "vhCacheForceConfirm": "Błędne wymuszenie cache może podać nieaktualną lub prywatną treść. Włączyć je tylko dla jawnie sprawdzonych prefiksów URL?", + "locationRedirectAction": "Przekieruj na URL", + "locationRedirectShort": "redirect", + "locationRedirectStatus": "Kod przekierowania", + "locationRedirectTarget": "Adres docelowego / URL", + "locationRedirectPattern": "Opcjonalny regex rewrite dla capture", + "locationRedirectQuery": "Zachowaj query string żądania", + "locationRedirectHelp": "Cel musi być pełnym URL http/https. Opcjonalny regex rewrite obsługuje $1/${name} w ścieżce/query, np. ^/(.*)$ → https://storage.example/o/$1.", + "locationRedirect301": "301 Stałe", + "locationRedirect302": "302 Found", + "locationRedirect303": "303 See Other", + "locationRedirect307": "307 Tymczasowe", + "locationRedirect308": "308 Stałe", + "cacheReason_cache_miss_forced": "MISS dla wymuszonego cache", + "vhWwwPanelTitle": "Przekierowanie adresu www", + "vhWwwInactive": "Przekierowanie wyłączone. Wybierz adres docelowy poniżej i włącz przełącznik powyżej. Wszystkie domeny obsługują ten sam vhost.", + "vhWwwOtherDomains": "Pozostałe domeny (bez przekierowania):", + "vhWwwNoHosts": "Dodaj powyżej konkretną domenę. Wildcard nie może być adresem docelowym.", + "vhWwwSharedCount": "{count} nazw / jeden vhost", + "vhWwwEnable": "Włącz przekierowanie 308", + "vhErrorFallbackTarget": "Główny cel fallbacku błędów", + "vhErrorFallbackStatuses": "Przechwytywane kody statusu", + "vhErrorFallbackHelp": "Cel musi być włączoną wewnętrzną Custom Location. Obsługiwane statusy: 403, 404, 500, 502, 503, 504. Request jest przekazywany wewnętrznie, bez redirectu HTTP.", + "locationInternal": "Wewnętrzna nazwana lokalizacja", + "locationErrorFallbackTarget": "Cel fallbacku błędów", + "locationErrorFallbackStatuses": "Przechwytywane kody statusu", + "locationErrorFallbackHelp": "Opcjonalny fallback błędów w stylu Nginx. Wskaż ID włączonej lokalizacji wewnętrznej. Obsługiwane statusy: 403, 404, 500, 502, 503, 504.", + "webServicePhpPathInfo": "Włącz rozdzielanie PHP PATH_INFO", + "phpProfileSelect": "Konfiguracja PHP-FPM", + "phpProfileLocal": "Konfiguracja lokalna", + "phpProfileHelp": "Wybierz wspólny profil PHP-FPM lub skonfiguruj usługę samodzielnie. Zmiana profilu dotyczy wszystkich przypiętych usług.", + "phpProfileManage": "Zarządzaj profilami PHP-FPM", + "phpProfileToolbar": "Profile PHP-FPM", + "phpProfileTitle": "Wspólne profile PHP-FPM", + "phpProfileDescription": "Rozwiń profil, aby go zmienić. Zapis od razu aktualizuje wszystkie przypięte serwery WWW.", + "phpProfileAdd": "Dodaj profil", + "phpProfileNew": "Nowy profil PHP-FPM", + "phpProfileId": "ID profilu", + "phpProfileName": "Nazwa profilu", + "phpProfileAdminValues": "Administracyjne ustawienia PHP (PHP_ADMIN_VALUE)", + "phpProfileInUse": "Profil jest używany przez", + "phpProfileDelete": "Usunąć profil PHP-FPM?", + "phpProfileInvalid": "Nieprawidłowe pola profilu PHP-FPM", + "phpProfileDuplicate": "ID profilu PHP-FPM już istnieje", + "phpProfileShared": "profil wspólny", + "backendHealthEnabled": "Monitoruj stan backendu (aktywnie i pasywnie)", + "backendHealthPerBackend": "Indywidualne testy stanu backendów", + "backendHealthSelect": "Wybierz backend do konfiguracji", + "backendHealthSelectionHelp": "Wybierz backend z włączonym monitorowaniem w zakładce Backendy. Zmiana wyboru nie usuwa niezapisanych ustawień. Włącz osobny health check, aby nadpisać ustawienia wspólne powyżej.", + "backendHealthNoEnabled": "Brak monitorowanych backendów. Włącz monitorowanie stanu przynajmniej jednego backendu w zakładce Backendy.", + "backendHealthOverride": "Osobny health check dla tego backendu", + "backendHealthOverridesHelp": "Bez nadpisania backend dziedziczy ustawienia vhosta. Wylaczenie monitorowania nie blokuje lokalizacji przypisanych do backendu, ale wylacza probe aktywna i pasywne wykluczanie.", + "cacheOnlyHits": "Pokazuj naglowki diagnostyki cache tylko dla HIT (ukryj MISS i BYPASS)", + "phpHintEnabled": "Uruchamia pliki o wskazanych rozszerzeniach przez już działającą pulę PHP-FPM. RustProxy nie uruchamia PHP; inne pliki nadal są statyczne.", + "phpHintPathInfo": "Rozdziela /static.php/skins/file.css na SCRIPT_FILENAME=static.php i PATH_INFO=/skins/file.css. Potrzebne np. w Roundcube; zwykle zbędne w WordPressie. Domyślnie wyłączone.", + "phpHintPassAuthorization": "Przekazuje nagłówek Authorization do PHP jako HTTP_AUTHORIZATION (np. API/Bearer/Basic). Wyłącz, gdy aplikacja go nie używa.", + "phpHintExtensions": "Rozszerzenia oddzielone przecinkami, bez kropek: php, phtml. Tylko te rozszerzenia uruchamiają PHP.", + "phpHintConnectTimeout": "Limit łączenia z gniazdem PHP-FPM w milisekundach: 1000 = 1 sekunda. Zakres: 50–60000.", + "phpHintRequestTimeout": "Limit całego żądania FastCGI w milisekundach: 30000 = 30 sekund. Zakres: 100–3600000.", + "phpHintConcurrency": "Maksymalna liczba równoległych żądań PHP na usługę, np. 64. Przekroczenie zwraca 503. Zakres: 1–4096.", + "phpHintBodyLimit": "Limit treści żądania w bajtach: 67108864 = 64 MiB. 0 = bez limitu po stronie RustProxy. PHP post_max_size jest osobny.", + "phpHintHeaderLimit": "Maksymalny rozmiar nagłówków odpowiedzi FastCGI w bajtach: 65536 = 64 KiB. Zakres: 4096–1048576.", + "phpHintStderrLimit": "Maksymalna liczba bajtów STDERR PHP-FPM w logu na żądanie, np. 4096. 0 wyłącza przechwytywanie (maks. 65536).", + "phpHintAdminValues": "Administracyjne ustawienia INI, np. open_basedir = /var/www/webmail/:/tmp/. Aby zapobiec przeciekaniu ustawień między vhostami, stosuj osobne pule i gniazda PHP-FPM.", + "phpDocsScope": "Te same pola i przykłady obowiązują w lokalnych ustawieniach PHP usługi WWW i we współdzielonym profilu PHP-FPM. Wybrany profil dostarcza ustawienia wszystkim przypiętym usługom; RustProxy łączy się z uruchomioną pulą.", + "phpDocsSwitchesTitle": "Włączanie PHP i obsługa PATH_INFO", + "phpDocsLimitsTitle": "Limity wykonania i rozmiarów", + "phpDocsLimitsText": "Rozszerzenia PHP: php, phtml (oddzielone przecinkami). Timeout połączenia: 1000 ms = 1 s. Całe żądanie: 30000 ms = 30 s. Maksymalnie 64 równoległe żądania (po przekroczeniu 503). Limit żądania: 67108864 bajtów = 64 MiB; 0 oznacza brak limitu w RustProxy. Nagłówki odpowiedzi: 65536 bajtów. Log STDERR: 4096 bajtów; 0 wyłącza. Wprowadź liczby całkowite; limity PHP INI to osobne ustawienia.", + "phpDocsValuesText": "Wpisuj po jednej dyrektywie = wartość w wierszu w polu PHP INI overrides (PHP_VALUE), a NIE w parametrach FastCGI. Składnia php.ini bez sekcji [sekcja] i bez tagów PHP. Jednostki M/G, wartości On/Off i czasy w sekundach interpretuje PHP. Nie każdą dyrektywę wolno zmienić przez PHP_VALUE. Nie powielaj jej w PHP_ADMIN_VALUE.", + "phpDocsAdminTitle": "PHP_ADMIN_VALUE — administracyjne dyrektywy INI", + "phpDocsAdminText": "Wpisuj je w pole Administracyjne ustawienia PHP (PHP_ADMIN_VALUE). W systemie Unix open_basedir jest listą dozwolonych ścieżek oddzielonych dwukropkiem, np. /var/www/webmail/:/tmp/. Ważne ograniczenia lepiej ustawiać jako php_admin_value[...] w osobnej puli PHP-FPM. Nie stosuj ograniczeń Roundcube dla procesów WordPressa.", + "phpDocsOtherTitle": "Walidacja, przechowywanie i diagnostyka", + "phpDocsOtherText": "Nazwa dyrektywy INI może zawierać litery, cyfry, kropkę, podkreślenie i myślnik (maks. 64 bajty). Wartość nie może być pusta (maks. 8192 bajtów). Maksymalnie 64 parametry FastCGI i 64 wpisy INI w każdej mapie; łącznie maks. 128 KiB. Znaki sterujące i wielowierszowe wartości są odrzucane. Dane trafiają do zwykłej konfiguracji — nie zapisuj tam sekretów. PHP-FPM może odrzucić poprawną składniowo opcję.", + "phpDocsPoolWarning": "Ważne: PHP_VALUE/PHP_ADMIN_VALUE NIE izolują vhostów współdzielących procesy PHP-FPM. WordPress i Roundcube wymagają oddzielnych gniazd, jeśli mają różne ograniczenia INI. Po rozdzieleniu uruchom ponownie procesy PHP-FPM. PATH_INFO nie rozwiązuje problemu open_basedir.", + "vhostInterceptOriginErrors": "Zastępuj treść błędów aplikacji stronami błędów RustProxy", + "vhostInterceptOriginStatuses": "Przechwytywane kody HTTP (oddziel przecinkami)", + "vhostInterceptOriginErrorsHelp": "Przykład: 404, 500, 502, 503, 504. Dla wybranych kodów RustProxy zachowa kod błędu, ale wyświetli stronę skonfigurowaną dla vhosta/globalnie albo wbudowaną. Treść i ciasteczka aplikacji zostaną usunięte, a odpowiedź otrzyma no-store. Domyślnie wyłączone. Wewnętrzne lokalizacje błędów mają pierwszeństwo. Działa dla Reverse Proxy i Web Server (także PHP).", + "vhostInterceptOriginInvalid": "Podaj co najmniej jeden unikalny obsługiwany kod HTTP: 400, 401, 403, 404, 408, 413, 429, 500, 502, 503, 504.", + "systemInformation": "Informacje o systemie", + "systemInformationHelp": "Bieżące dane tylko do odczytu z uwierzytelnionego API statusu. Brakujących wartości nie szacujemy.", + "systemHostHeading": "System operacyjny i sprzęt", + "systemOsVersion": "System operacyjny", + "systemKernel": "Jądro systemu", + "systemArchitecture": "Architektura", + "systemLogicalCpus": "Procesory logiczne", + "systemCpuLoad": "Użycie CPU", + "systemProcessHeading": "Proces RustProxy", + "systemAppVersion": "Wersja RustProxy", + "systemProcessUptime": "Czas pracy procesu", + "systemProcessMemory": "Pamięć procesu (RSS)", + "systemProcessThreads": "Wątki procesu", + "systemOpenFds": "Otwarte deskryptory plików", + "systemCertificateHeading": "Środowisko ACME / Python", + "systemCertificateHelp": "Wersje są odczytywane z interpretera i narzędzi rzeczywiście używanych do obsługi certyfikatów.", + "systemAcmeStatus": "Stan ACME", + "systemPathsHeading": "Ścieżki danych", + "refreshSection": "ODŚWIEŻANIE", + "refreshTitle": "Odświeżanie danych", + "refreshHelp": "Interwały są zapisywane osobno dla administratora. Wyłączenie automatycznego odświeżania zatrzymuje SSE i polling; ręczne przyciski działają nadal.", + "refreshLiveEnabled": "Automatyczne odświeżanie włączone", + "refreshOverview": "Interwał statusu na żywo / SSE", + "refreshTraffic": "Interwał Traffic", + "refreshCache": "Interwał Cache", + "refreshSystemInfo": "Interwał informacji systemowych", + "refreshManual": "Ręcznie", + "refreshNow": "Odśwież teraz", + "refreshViewLive": "Automatycznie w tym widoku", + "refreshManualHint": "Automatyczne metryki systemowe są wstrzymane; użyj przycisku Odśwież teraz.", + "refreshSystemHint": "Metryki systemowe odświeżają się niezależnie, gdy otwarto Overview lub odpowiednią sekcję Settings.", + "startupLoading": "Uruchamianie RustProxy…", + "startupPreparing": "Łączenie z panelem administracyjnym.", + "startupSlow": "Wczytywanie trwa dłużej niż zwykle. Sprawdź połączenie i spróbuj ponownie.", + "startupRetry": "Wczytaj panel ponownie", + "startupFailed": "Nie udało się uruchomić RustProxy", + "panelCompressionTitle": "Kompresja zasobów panelu", + "panelCompressionHelp": "Obejmuje HTML, skrypty, style i języki. Negocjacja z przeglądarką: Zstd → Brotli → gzip przy równej jakości. Odznacz wszystkie, by wyłączyć. Działa od razu; reverse proxy może zmieniać Accept-Encoding lub kodowanie odpowiedzi.", + "cacheHttpInvalidationCurlLabel": "Jak wywołać BAN i PURGE poleceniem curl", + "cacheHttpInvalidationCurlIntro": "Wyślij polecenie curl z dozwolonego IP na adres publicznego vhosta:", + "cacheHttpInvalidationCurlNote": "PURGE usuwa dokładny URL; BAN unieważnia prefiks ścieżki na 1 godzinę. Nie potrzeba ciasteczka administratora, lecz IP musi być na liście. Reverse proxy musi przekazywać te metody oraz poprawnie ustalone IP klienta.", + "cacheHttpInvalidationTitle": "Lista dozwolonych adresów HTTP BAN / PURGE", + "cacheHttpInvalidationAddresses": "Dozwolone adresy IP klientów / CIDR (jeden na wiersz)", + "cacheHttpInvalidationHelp": "Pusta lista = wyłączone. PURGE /ścieżka?zapytanie usuwa dokładne trafienia; BAN /ścieżka blokuje buforowane adresy URL z tym prefiksem (domyślnie 1 godzina, opcjonalnie X-RustProxy-Ban-Ttl). BAN / wymaga X-RustProxy-Confirm-Wide: 1. Operacje dotyczą wyłącznie dopasowanego hosta i vhosta. Za reverse proxy skonfiguruj zaufane proxy i usuwanie fałszywych nagłówków przekazywania." + } } diff --git a/src/admin/documentation/documents.rs b/src/admin/documentation/documents.rs index ddb370f..f36e87f 100644 --- a/src/admin/documentation/documents.rs +++ b/src/admin/documentation/documents.rs @@ -233,6 +233,7 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[ "/docs/SECURITY_AUDIT.md" )), ), + reader_doc!("docs/SECURITY_AUDIT_0.31.0.md", "audits"), ( "docs/SETTINGS.md", "guides", @@ -243,6 +244,7 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[ "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/STREAMS.md")), ), + reader_doc!("docs/IMPLEMENTATION_0.31.0.md", "technical"), ( "docs/TEST_REPORT.md", "guides", diff --git a/src/admin/logging_api.rs b/src/admin/logging_api.rs index 17ad657..52d04b6 100644 --- a/src/admin/logging_api.rs +++ b/src/admin/logging_api.rs @@ -294,7 +294,21 @@ async fn audit(State(state): State>) -> Result, ApiError> { async fn metrics(State(state): State>) -> Response { crate::prometheus::response(&state) } -async fn events(State(state): State>, headers: HeaderMap) -> Result { +#[derive(Default, Deserialize)] +#[serde(default, deny_unknown_fields)] +struct EventsQuery { + interval: Option, +} + +async fn events( + State(state): State>, + headers: HeaderMap, + Query(query): Query, +) -> Result { + let interval = query.interval.unwrap_or(2); + if ![1, 2, 5, 10].contains(&interval) { + return Err(ApiError(StatusCode::UNPROCESSABLE_ENTITY, "invalid SSE interval".into())); + } let secret = auth::cookie_secret(&headers) .ok_or_else(|| ApiError(StatusCode::UNAUTHORIZED, "auth.required".into()))?; let permit = state @@ -310,10 +324,10 @@ async fn events(State(state): State>, headers: HeaderMap) -> Result>, headers: HeaderMap) -> Result return None, } let payload = - json!({"status":status_payload(&state), "logs":logs_payload(&state)}).to_string(); + json!({"status":live_status_payload(&state), "logs":logs_payload(&state)}).to_string(); let event = Event::default().event("snapshot").data(payload); Some(( Ok::(event), - (state, secret, permit, true), + (state, secret, permit, true, interval), )) }, ); diff --git a/src/admin/openapi.json b/src/admin/openapi.json index b0b683b..d9fd565 100644 --- a/src/admin/openapi.json +++ b/src/admin/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "RustProxy Administration API", - "version": "0.30.1", + "version": "0.31.0", "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust." }, "servers": [ @@ -772,6 +772,25 @@ "maximum": 30, "default": 30, "description": "Absolute browser administrator-session lifetime in days. Applies immediately and does not require restart." + }, + "panel_compression": { + "type": "object", + "description": "Negotiated compression for authenticated panel HTML and embedded CSS/JS/translation assets; takes effect without restart. At equal client q: zstd > br > gzip. Disable all for identity. Independent from public proxy response compression.", + "properties": { + "zstd": { + "type": "boolean", + "default": true + }, + "br": { + "type": "boolean", + "default": true + }, + "gzip": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -1859,6 +1878,16 @@ "type": "boolean", "default": false, "description": "Suppress RustProxy cache diagnostic headers on MISS/BYPASS. Cache HIT diagnostics remain governed by expose_* flags. The independently configured Via header is unaffected." + }, + "http_invalidation_allowed_cidrs": { + "type": "array", + "description": "Optional IP/CIDR allowlist for HTTP BAN and PURGE on matched public vhosts. Empty disables all HTTP invalidation. Max 32 entries; /0 networks forbidden. Trusted reverse proxies must provide an authenticated forwarding chain.", + "maxItems": 32, + "items": { + "type": "string", + "maxLength": 64 + }, + "default": [] } }, "additionalProperties": false @@ -3527,6 +3556,60 @@ "type": "boolean", "default": false, "description": "Account-scoped sort direction for the Streams list." + }, + "refresh": { + "type": "object", + "additionalProperties": false, + "description": "Account-scoped polling preferences; omitted fields use defaults. Zero disables only that source automatically.", + "properties": { + "live_enabled": { + "type": "boolean", + "default": true + }, + "overview_seconds": { + "type": "integer", + "enum": [ + 1, + 2, + 5, + 10 + ], + "default": 2 + }, + "traffic_seconds": { + "type": "integer", + "enum": [ + 0, + 3, + 5, + 10, + 30 + ], + "default": 5 + }, + "cache_seconds": { + "type": "integer", + "enum": [ + 0, + 3, + 5, + 10, + 30 + ], + "default": 5 + }, + "system_info_seconds": { + "type": "integer", + "enum": [ + 0, + 15, + 30, + 60, + 300 + ], + "default": 60 + } + } } }, "required": [ @@ -5777,6 +5860,27 @@ "$ref": "#/components/schemas/WebService/properties/php" } } + }, + "SystemInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "system": { + "type": "object", + "description": "Cached system and process metrics", + "additionalProperties": true + }, + "runtime": { + "type": "object", + "description": "Cached platform and ACME runtime metadata", + "additionalProperties": true + }, + "memory_accounting": { + "type": "object", + "description": "Cached process/cache and transform memory accounting", + "additionalProperties": true + } + } } } }, @@ -6691,7 +6795,22 @@ } } } - } + }, + "parameters": [ + { + "name": "view", + "in": "query", + "required": false, + "description": "Omit for the backward-compatible full status; use live for the compact dynamic-only fallback.", + "schema": { + "type": "string", + "enum": [ + "live" + ] + } + } + ], + "description": "Without view, returns the historical full status including runtime and system diagnostics. view=live returns compact live-only telemetry; the browser fetches system diagnostics separately." } }, "/api/backends": { @@ -8228,7 +8347,25 @@ } } }, - "description": "Bounded snapshot every approximately 2 seconds. Session/revocation rechecked before every snapshot. Not executed in Swagger because it is an unbounded stream." + "description": "SSE snapshot events contain compact volatile status and bounded logs. Session validity is checked before each event. Native reconnect and keepalive apply. Each viewer selects 1/2/5/10 second pacing; invalid values return 422.", + "parameters": [ + { + "name": "interval", + "in": "query", + "required": false, + "description": "Per-client snapshot interval (seconds), never a global SSE rate change.", + "schema": { + "type": "integer", + "enum": [ + 1, + 2, + 5, + 10 + ], + "default": 2 + } + } + ] } }, "/metrics": { @@ -10727,6 +10864,33 @@ } } } + }, + "/api/system-info": { + "get": { + "summary": "Cached system and runtime information for the administration panel", + "tags": [ + "Monitoring" + ], + "operationId": "get_api_system_info", + "responses": { + "200": { + "description": "Read-only cached system info; no full configuration or secrets", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemInfo" + } + } + } + }, + "401": { + "description": "Authentication required" + }, + "403": { + "description": "Invalid origin" + } + } + } } } } diff --git a/src/admin/operations.rs b/src/admin/operations.rs index 2d44b79..7b8b240 100644 --- a/src/admin/operations.rs +++ b/src/admin/operations.rs @@ -79,6 +79,8 @@ async fn cache_purge( )); } } + let permit = state.cache_invalidation_slots.clone().try_acquire_owned() + .map_err(|_| ApiError(StatusCode::TOO_MANY_REQUESTS, "cache maintenance busy".into()))?; let detail = format!( "scope={}; in-flight fills invalidated", scope.vhost.as_deref().unwrap_or("all") @@ -88,10 +90,15 @@ async fn cache_purge( .audit(&session.username, "cache.purge", &detail) .await .map_err(auth::internal)?; - let purged = match &scope.vhost { - Some(id) => state.cache.purge_scope(id), - None => state.cache.purge(None), - }; + let cache = state.cache.clone(); + let vhost = scope.vhost.clone(); + let purged = tokio::task::spawn_blocking(move || { + let _permit = permit; + match &vhost { + Some(id) => cache.purge_scope(id), + None => cache.purge(None), + } + }).await.map_err(auth::internal)?; Ok(Json( json!({"purged_entries":purged, "vhost":scope.vhost, "metrics":state.cache.view()}), )) @@ -119,6 +126,8 @@ async fn cache_invalidate( )); } } + let permit = state.cache_invalidation_slots.clone().try_acquire_owned() + .map_err(|_| ApiError(StatusCode::TOO_MANY_REQUESTS, "cache maintenance busy".into()))?; let detail = format!( "operation={:?}; selector={:?}; vhost={}; host={}; value={:?}", request.operation, @@ -132,10 +141,12 @@ async fn cache_invalidate( .audit(&session.username, "cache.invalidate", &detail) .await .map_err(auth::internal)?; - let result = state - .cache - .invalidate(request) - .map_err(|error| ApiError(StatusCode::UNPROCESSABLE_ENTITY, error.to_string()))?; + let cache = state.cache.clone(); + let result = tokio::task::spawn_blocking(move || { + let _permit = permit; + cache.invalidate(request) + }).await.map_err(auth::internal)? + .map_err(|error| ApiError(StatusCode::UNPROCESSABLE_ENTITY, error.to_string()))?; Ok(Json(json!({ "result": result, "metrics": state.cache.view(), @@ -152,7 +163,9 @@ async fn cache_ban_delete( Extension(session): Extension, Path(id): Path, ) -> Result, ApiError> { - if !state.cache.remove_ban(id) { + // This is a destructive operation: fail closed if the audit store is + // unavailable, rather than removing a ban and only then trying to log it. + if !state.cache.bans().iter().any(|rule| rule.id == id) { return Err(ApiError( StatusCode::NOT_FOUND, "unknown cache BAN rule".into(), @@ -167,6 +180,9 @@ async fn cache_ban_delete( ) .await .map_err(auth::internal)?; + if !state.cache.remove_ban(id) { + return Err(ApiError(StatusCode::NOT_FOUND, "unknown cache BAN rule".into())); + } Ok(Json(json!({"removed": id, "bans": state.cache.bans()}))) } diff --git a/src/admin/panel_assets.rs b/src/admin/panel_assets.rs new file mode 100644 index 0000000..0f20ab6 --- /dev/null +++ b/src/admin/panel_assets.rs @@ -0,0 +1,57 @@ +// Fixed private admin assets, shared by all admins. The existing route/access +// allowlist decides which paths are reachable. Do not expand routes dynamically. +use super::panel_content::PanelContent; +use crate::runtime::App; +use axum::{extract::{Request, State}, response::{IntoResponse, Response}}; +use http::StatusCode; +use std::{collections::HashMap, sync::{Arc, LazyLock, OnceLock}}; + +struct Asset { + mime: &'static str, + raw: &'static str, + content: OnceLock, +} + +const JS: &str = "text/javascript; charset=utf-8"; +const JSON: &str = "application/json; charset=utf-8"; +const CSS: &str = "text/css; charset=utf-8"; +const SVG: &str = "image/svg+xml"; + +static ASSETS: LazyLock> = LazyLock::new(|| { + let entries: [(&'static str, &'static str, &'static str); 22] = [ + ("/assets/login.js", JS, include_str!("web/login.js")), + ("/assets/login-langs.json", JSON, include_str!(concat!(env!("OUT_DIR"), "/login-langs.json"))), + ("/assets/langs.json", JSON, include_str!(concat!(env!("OUT_DIR"), "/langs.json"))), + ("/assets/app.js", JS, super::admin_app_javascript()), + ("/assets/management.js", JS, include_str!("web/management.js")), + ("/assets/drafts.js", JS, include_str!("web/drafts.js")), + ("/assets/integrations.js", JS, include_str!("web/integrations.js")), + ("/assets/traffic-panel.js", JS, include_str!("web/traffic-panel.js")), + ("/assets/cache-panel.js", JS, include_str!("web/cache-panel.js")), + ("/assets/transform-panel.js", JS, include_str!("web/transform-panel.js")), + ("/assets/file-logs.js", JS, include_str!("web/file-logs.js")), + ("/assets/settings.js", JS, include_str!("web/settings.js")), + ("/assets/acme.js", JS, include_str!("web/acme.js")), + ("/assets/vhosts.js", JS, super::admin_vhosts_javascript()), + ("/assets/web-server-profiles.js", JS, include_str!("web/web-server-profiles.js")), + ("/assets/web-server.js", JS, include_str!("web/web-server.js")), + ("/assets/docker.js", JS, include_str!("web/docker.js")), + ("/assets/streams.js", JS, include_str!("web/streams.js")), + ("/assets/redirects.js", JS, include_str!("web/redirects.js")), + ("/assets/host404.js", JS, include_str!("web/host404.js")), + ("/assets/style.css", CSS, include_str!("web/style.css")), + ("/favicon.svg", SVG, include_str!("web/favicon.svg")), + ]; + entries.into_iter().map(|(path, mime, raw)| (path, Asset { mime, raw, content: OnceLock::new() })) + .collect() +}); + +pub(super) async fn serve(State(state): State>, request: Request) -> Response { + let Some(asset) = ASSETS.get(request.uri().path()) else { + return StatusCode::NOT_FOUND.into_response(); + }; + let snapshot = state.config.load(); + let codecs = &snapshot.envelope.config.administration.panel_compression; + asset.content.get_or_init(|| PanelContent::new(asset.raw)) + .response(request.headers(), codecs, asset.mime) +} diff --git a/src/admin/panel_content.rs b/src/admin/panel_content.rs new file mode 100644 index 0000000..ab0a4b4 --- /dev/null +++ b/src/admin/panel_content.rs @@ -0,0 +1,56 @@ +// Shared immutable precompressed responses for private panel HTML and assets. +// Each embedded document is encoded once on first request, never per visitor. +use crate::settings::PanelCompression; +use axum::{body::Body, response::Response}; +use bytes::Bytes; +use flate2::{write::GzEncoder, Compression}; +use http::{header, HeaderMap, HeaderValue}; +use std::io::Write; + +pub(super) struct PanelContent { + plain: Bytes, + gzip: Bytes, + brotli: Bytes, + zstd: Bytes, +} + +impl PanelContent { + pub(super) fn new(raw: &'static str) -> Self { + let mut gzip = GzEncoder::new(Vec::new(), Compression::default()); + gzip.write_all(raw.as_bytes()).expect("compress panel gzip"); + let gzip = Bytes::from(gzip.finish().expect("finish panel gzip")); + let mut br = Vec::new(); + { + let mut writer = brotli::CompressorWriter::new(&mut br, 4096, 5, 22); + writer.write_all(raw.as_bytes()).expect("compress panel Brotli"); + } + let zstd = zstd::stream::encode_all(raw.as_bytes(), 3).expect("compress panel zstd"); + Self { plain: Bytes::from_static(raw.as_bytes()), gzip, brotli: Bytes::from(br), zstd: Bytes::from(zstd) } + } + + pub(super) fn response(&self, headers: &HeaderMap, codecs: &PanelCompression, mime: &'static str) -> Response { + let settings = crate::compression::Settings { + gzip: codecs.gzip, br: codecs.br, zstd: codecs.zstd, + deflate: false, min_bytes: 1024, + }; + let encoding = if crate::compression::no_transform(headers) { + None + } else { + crate::compression::preferred_encoding(headers, &settings) + }; + let (body, name) = match encoding.map(|codec| codec.as_str()) { + Some("zstd") => (self.zstd.clone(), Some("zstd")), + Some("br") => (self.brotli.clone(), Some("br")), + Some("gzip") => (self.gzip.clone(), Some("gzip")), + _ => (self.plain.clone(), None), + }; + let mut response = Response::new(Body::from(body.clone())); + response.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static(mime)); + response.headers_mut().insert(header::CONTENT_LENGTH, HeaderValue::from_str(&body.len().to_string()).expect("valid length")); + if let Some(name) = name { + response.headers_mut().insert(header::CONTENT_ENCODING, HeaderValue::from_static(name)); + } + response.headers_mut().insert(header::VARY, HeaderValue::from_static("Accept-Encoding")); + response + } +} diff --git a/src/admin/preferences.rs b/src/admin/preferences.rs index 7dc7a0f..0f047ed 100644 --- a/src/admin/preferences.rs +++ b/src/admin/preferences.rs @@ -1,94 +1,4 @@ -const LIVE_LOG_COLUMNS: &[&str] = &[ - "time", - "request", - "host", - "source_ip", - "vhost", - "backend", - "status", - "duration", - "cache", - "outcome", - "transport", - "websocket", - "upstream_error", -]; -const FILE_LOG_COLUMN_MAX: usize = 64; -const BACKEND_TABLE_COLUMNS: &[&str] = &[ - "vhost", - "backend", - "status", - "active", - "requests", - "errors", - "latency", - "traffic", - "generation", - "generationRequests", - "generationAge", - "rotations", - "lastFailure", - "lastRecovery", -]; -const VHOST_LIST_SORT_COLUMNS: &[&str] = &[ - "id", - "hostname", - "status", - "listeners", - "certificate", - "backends", -]; -const DOCKER_VHOST_SORT_COLUMNS: &[&str] = &[ - "service", - "hostname", - "status", - "backend", - "scheme", - "published", -]; -const REDIRECT_LIST_SORT_COLUMNS: &[&str] = &[ - "id", - "name", - "hostname", - "status", - "target", - "rules", - "listeners", - "certificate", -]; -const NOTFOUND_LIST_SORT_COLUMNS: &[&str] = &[ - "id", - "name", - "hostname", - "status", - "mode", - "listeners", - "certificate", -]; -const STREAM_LIST_SORT_COLUMNS: &[&str] = &[ - "name", - "id", - "protocol", - "listen", - "upstream", - "status", - "maxConnections", - "idleTimeout", -]; -const DASHBOARD_WIDGETS: &[&str] = &[ - "requests", - "active", - "bandwidth", - "errors", - "cache", - "memory", - "websocket", - "http2", - "http3", - "tls", - "http_protocol", - "websocket_protocol", -]; +include!("preferences_columns.rs"); fn default_admin_theme() -> String { "dark".to_owned() @@ -159,9 +69,13 @@ fn default_stream_list_sort() -> String { "name".to_owned() } +include!("refresh_preferences.rs"); #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] struct AdminPreferences { + #[serde(default)] + refresh: RefreshPreferences, + #[serde(default = "default_live_log_columns")] live_log_columns: Vec, #[serde(default = "default_admin_theme")] theme: String, @@ -216,6 +130,7 @@ struct AdminPreferences { impl Default for AdminPreferences { fn default() -> Self { Self { + refresh: RefreshPreferences::default(), live_log_columns: LIVE_LOG_COLUMNS .iter() .map(|value| (*value).to_owned()) @@ -249,6 +164,7 @@ impl Default for AdminPreferences { } fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiError> { + validate_refresh_preferences(&preferences.refresh)?; if preferences.live_log_columns.is_empty() || preferences.live_log_columns.len() > LIVE_LOG_COLUMNS.len() { @@ -490,3 +406,4 @@ async fn update_preferences( } Ok(Json(preferences)) } + diff --git a/src/admin/preferences_columns.rs b/src/admin/preferences_columns.rs new file mode 100644 index 0000000..e4e7a95 --- /dev/null +++ b/src/admin/preferences_columns.rs @@ -0,0 +1,92 @@ +const LIVE_LOG_COLUMNS: &[&str] = &[ + "time", + "request", + "host", + "source_ip", + "vhost", + "backend", + "status", + "duration", + "cache", + "outcome", + "transport", + "websocket", + "upstream_error", +]; +const FILE_LOG_COLUMN_MAX: usize = 64; +const BACKEND_TABLE_COLUMNS: &[&str] = &[ + "vhost", + "backend", + "status", + "active", + "requests", + "errors", + "latency", + "traffic", + "generation", + "generationRequests", + "generationAge", + "rotations", + "lastFailure", + "lastRecovery", +]; +const VHOST_LIST_SORT_COLUMNS: &[&str] = &[ + "id", + "hostname", + "status", + "listeners", + "certificate", + "backends", +]; +const DOCKER_VHOST_SORT_COLUMNS: &[&str] = &[ + "service", + "hostname", + "status", + "backend", + "scheme", + "published", +]; +const REDIRECT_LIST_SORT_COLUMNS: &[&str] = &[ + "id", + "name", + "hostname", + "status", + "target", + "rules", + "listeners", + "certificate", +]; +const NOTFOUND_LIST_SORT_COLUMNS: &[&str] = &[ + "id", + "name", + "hostname", + "status", + "mode", + "listeners", + "certificate", +]; +const STREAM_LIST_SORT_COLUMNS: &[&str] = &[ + "name", + "id", + "protocol", + "listen", + "upstream", + "status", + "maxConnections", + "idleTimeout", +]; +const DASHBOARD_WIDGETS: &[&str] = &[ + "requests", + "active", + "bandwidth", + "errors", + "cache", + "memory", + "websocket", + "http2", + "http3", + "tls", + "http_protocol", + "websocket_protocol", +]; + diff --git a/src/admin/public.rs b/src/admin/public.rs index 35911a0..5cf37be 100644 --- a/src/admin/public.rs +++ b/src/admin/public.rs @@ -1,21 +1,35 @@ //! Deliberately small anonymous surface. Operational templates/assets are private. -use crate::{auth::Session, runtime::ApiError}; -use axum::{extract::rejection::JsonRejection, response::Html, Extension}; -use http::StatusCode; -use std::sync::OnceLock; +use crate::{auth::Session, runtime::{ApiError, App}, settings::PanelCompression}; +use axum::{extract::{rejection::JsonRejection, State}, response::{Html, IntoResponse, Response}, Extension}; +use http::{HeaderMap, StatusCode}; +use std::sync::{Arc, OnceLock}; -static ADMIN_INDEX: OnceLock = OnceLock::new(); +use super::panel_content::PanelContent; -pub(super) async fn index(session: Option>) -> Html<&'static str> { - if session.is_some() { - Html(ADMIN_INDEX.get_or_init(|| { - include_str!("web/index.html") - .replace("__RUSTPROXY_VERSION__", env!("CARGO_PKG_VERSION")) - }).as_str()) - } else { - Html(include_str!("web/login.html")) - } +static ADMIN_INDEX: OnceLock = OnceLock::new(); + +fn panel_response(headers: &HeaderMap, compression: &PanelCompression) -> Response { + let content = ADMIN_INDEX.get_or_init(|| { + let html = include_str!("web/index.html") + .replace("__RUSTPROXY_VERSION__", env!("CARGO_PKG_VERSION")); + // Exactly one static panel document shared between all administrator accounts. + PanelContent::new(Box::leak(html.into_boxed_str())) + }); + content.response(headers, compression, "text/html; charset=utf-8") } + +pub(super) async fn index( + State(state): State>, + session: Option>, + headers: HeaderMap, +) -> Response { + if session.is_none() { + return Html(include_str!("web/login.html")).into_response(); + } + let snapshot = state.config.load(); + panel_response(&headers, &snapshot.envelope.config.administration.panel_compression) +} + pub(super) fn invalid_json(error: JsonRejection) -> ApiError { // Keep useful protocol statuses without returning serde's input excerpts. let status = match error.status() { @@ -25,3 +39,6 @@ pub(super) fn invalid_json(error: JsonRejection) -> ApiError { }; ApiError(status, "auth.invalid_request".into()) } + +#[cfg(test)] +mod tests; diff --git a/src/admin/public/tests.rs b/src/admin/public/tests.rs new file mode 100644 index 0000000..86ec0d3 --- /dev/null +++ b/src/admin/public/tests.rs @@ -0,0 +1,57 @@ +use super::*; +use flate2::read::GzDecoder; +use http_body_util::BodyExt; +use std::io::Read; + +fn headers(value: &str) -> HeaderMap { + let mut h = HeaderMap::new(); + h.insert(header::ACCEPT_ENCODING, value.parse().unwrap()); + h +} +#[tokio::test] +async fn panel_chooses_zstd_brotli_gzip_by_quality_and_support() { + let all = PanelCompression::default(); + let plain = panel_response(&headers("identity"), &all); + assert!(!plain.headers().contains_key(header::CONTENT_ENCODING)); + let source = plain.into_body().collect().await.unwrap().to_bytes(); + assert!(std::str::from_utf8(&source).unwrap().contains("id=\"app-startup\"")); + assert!(std::str::from_utf8(&source).unwrap().contains(env!("CARGO_PKG_VERSION"))); + for (accept, selected) in [ + ("gzip, br, zstd", "zstd"), + ("gzip, br", "br"), + ("gzip", "gzip"), + ("zstd;q=0.1, br;q=0.9, gzip;q=0.2", "br"), + ("zstd;q=0, br;q=0, gzip;q=1", "gzip"), + ("*;q=0.5", "zstd"), + ] { + let response = panel_response(&headers(accept), &all); + assert_eq!(response.headers()[header::CONTENT_ENCODING], selected, "{accept}"); + assert_eq!(response.headers()[header::VARY], "Accept-Encoding"); + let size: usize = response.headers()[header::CONTENT_LENGTH].to_str().unwrap().parse().unwrap(); + let encoded = response.into_body().collect().await.unwrap().to_bytes(); + assert_eq!(size, encoded.len()); + let decoded = match selected { + "gzip" => { let mut bytes = Vec::new(); GzDecoder::new(encoded.as_ref()).read_to_end(&mut bytes).unwrap(); bytes }, + "br" => { let mut bytes = Vec::new(); brotli::Decompressor::new(encoded.as_ref(), 4096).read_to_end(&mut bytes).unwrap(); bytes }, + _ => zstd::stream::decode_all(encoded.as_ref()).unwrap(), + }; + assert_eq!(decoded.as_slice(), source.as_ref()); + } + let disabled = PanelCompression { gzip: false, br: false, zstd: false }; + let response = panel_response(&headers("zstd, br, gzip"), &disabled); + assert!(!response.headers().contains_key(header::CONTENT_ENCODING)); + assert_eq!(response.into_body().collect().await.unwrap().to_bytes(), source); + let only_gzip = PanelCompression { gzip: true, br: false, zstd: false }; + assert_eq!(panel_response(&headers("zstd, br, gzip"), &only_gzip).headers()[header::CONTENT_ENCODING], "gzip"); + let mut no_transform = headers("zstd, br, gzip"); + no_transform.insert("cache-control", "no-transform".parse().unwrap()); + assert!(!panel_response(&no_transform, &all).headers().contains_key(header::CONTENT_ENCODING)); +} + +#[test] +fn administration_codec_defaults_keep_older_configuration_compatible() { + let admin: crate::settings::Administration = serde_json::from_str("{\"session_days\":30}").unwrap(); + assert_eq!(admin.panel_compression, PanelCompression::default()); + let saved = serde_json::to_value(&admin).unwrap(); + assert_eq!(saved["panel_compression"]["zstd"], true); +} diff --git a/src/admin/refresh_preferences.rs b/src/admin/refresh_preferences.rs new file mode 100644 index 0000000..5bb8a03 --- /dev/null +++ b/src/admin/refresh_preferences.rs @@ -0,0 +1,41 @@ +// Stored inside the existing per-account administration preferences document. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(default, deny_unknown_fields)] +struct RefreshPreferences { + live_enabled: bool, + overview_seconds: u16, + traffic_seconds: u16, + cache_seconds: u16, + system_info_seconds: u16, +} + +impl Default for RefreshPreferences { + fn default() -> Self { + Self { + live_enabled: true, + overview_seconds: 2, + traffic_seconds: 5, + cache_seconds: 5, + system_info_seconds: 60, + } + } +} + +fn validate_refresh_preferences(refresh: &RefreshPreferences) -> Result<(), ApiError> { + if ![1, 2, 5, 10].contains(&refresh.overview_seconds) + || ![0, 3, 5, 10, 30].contains(&refresh.traffic_seconds) + || ![0, 3, 5, 10, 30].contains(&refresh.cache_seconds) + || ![0, 15, 30, 60, 300].contains(&refresh.system_info_seconds) + { + return Err(ApiError( + StatusCode::UNPROCESSABLE_ENTITY, + "invalid administration refresh interval".into(), + )); + } + Ok(()) +} + +fn default_live_log_columns() -> Vec { + LIVE_LOG_COLUMNS.iter().map(|column| (*column).to_owned()).collect() +} + diff --git a/src/admin/router.rs b/src/admin/router.rs index 206b99d..170b0b5 100644 --- a/src/admin/router.rs +++ b/src/admin/router.rs @@ -1,5 +1,7 @@ mod documentation; mod public; +mod panel_content; +mod panel_assets; use crate::{ auth::{self, Session}, config::Update, @@ -33,6 +35,7 @@ fn admin_app_javascript() -> &'static str { static ASSET: std::sync::LazyLock = std::sync::LazyLock::new(|| { [ include_str!("web/app/core.js"), + include_str!("web/app/refresh.js"), include_str!("web/app/preferences.js"), include_str!("web/app/config-history.js"), include_str!("web/app/runtime.js"), @@ -87,24 +90,8 @@ pub fn router(state: Arc) -> Router { .route("/logging", get(public::index)) .route("/logs", get(public::index)) .route("/settings", get(public::index)) - .route( - "/assets/login.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/login.js"), - ) - }), - ) - .route( - "/assets/login-langs.json", - get(|| async { - ( - [("content-type", "application/json; charset=utf-8")], - include_str!(concat!(env!("OUT_DIR"), "/login-langs.json")), - ) - }), - ) + .route("/assets/login.js", get(panel_assets::serve)) + .route("/assets/login-langs.json", get(panel_assets::serve)) .route("/docs", get(documentation::index)) .route("/docs/", get(documentation::index)) .route("/docs/openapi.json", get(documentation::schema)) @@ -123,183 +110,29 @@ pub fn router(state: Arc) -> Router { "/docs/licenses.txt", get(|| async { documentation::asset("licenses.txt") }), ) - .route( - "/assets/langs.json", - get(|| async { - ( - [("content-type", "application/json; charset=utf-8")], - include_str!(concat!(env!("OUT_DIR"), "/langs.json")), - ) - }), - ) - .route( - "/assets/app.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - admin_app_javascript(), - ) - }), - ) - .route( - "/assets/management.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/management.js"), - ) - }), - ) - .route( - "/assets/drafts.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/drafts.js"), - ) - }), - ) - .route( - "/assets/integrations.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/integrations.js"), - ) - }), - ) - .route( - "/assets/traffic-panel.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/traffic-panel.js"), - ) - }), - ) - .route( - "/assets/cache-panel.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/cache-panel.js"), - ) - }), - ) - .route( - "/assets/transform-panel.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/transform-panel.js"), - ) - }), - ) - .route( - "/assets/file-logs.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/file-logs.js"), - ) - }), - ) - .route( - "/assets/settings.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/settings.js"), - ) - }), - ) - .route( - "/assets/acme.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/acme.js"), - ) - }), - ) - .route( - "/assets/vhosts.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - admin_vhosts_javascript(), - ) - }), - ) - .route("/assets/web-server-profiles.js", get(|| async { - ([ ("content-type", "text/javascript; charset=utf-8") ], - include_str!("web/web-server-profiles.js")) - })) - .route( - "/assets/web-server.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/web-server.js"), - ) - }), - ) - .route( - "/assets/docker.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/docker.js"), - ) - }), - ) - .route( - "/assets/streams.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/streams.js"), - ) - }), - ) - .route( - "/assets/redirects.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/redirects.js"), - ) - }), - ) - .route( - "/assets/host404.js", - get(|| async { - ( - [("content-type", "text/javascript; charset=utf-8")], - include_str!("web/host404.js"), - ) - }), - ) - .route( - "/assets/style.css", - get(|| async { - ( - [("content-type", "text/css; charset=utf-8")], - include_str!("web/style.css"), - ) - }), - ) - .route( - "/favicon.svg", - get(|| async { - ( - [("content-type", "image/svg+xml")], - include_str!("web/favicon.svg"), - ) - }), - ) + .route("/assets/langs.json", get(panel_assets::serve)) + .route("/assets/app.js", get(panel_assets::serve)) + .route("/assets/management.js", get(panel_assets::serve)) + .route("/assets/drafts.js", get(panel_assets::serve)) + .route("/assets/integrations.js", get(panel_assets::serve)) + .route("/assets/traffic-panel.js", get(panel_assets::serve)) + .route("/assets/cache-panel.js", get(panel_assets::serve)) + .route("/assets/transform-panel.js", get(panel_assets::serve)) + .route("/assets/file-logs.js", get(panel_assets::serve)) + .route("/assets/settings.js", get(panel_assets::serve)) + .route("/assets/acme.js", get(panel_assets::serve)) + .route("/assets/vhosts.js", get(panel_assets::serve)) + .route("/assets/web-server-profiles.js", get(panel_assets::serve)) + .route("/assets/web-server.js", get(panel_assets::serve)) + .route("/assets/docker.js", get(panel_assets::serve)) + .route("/assets/streams.js", get(panel_assets::serve)) + .route("/assets/redirects.js", get(panel_assets::serve)) + .route("/assets/host404.js", get(panel_assets::serve)) + .route("/assets/style.css", get(panel_assets::serve)) + .route("/favicon.svg", get(panel_assets::serve)) .route("/healthz", get(|| async { Json(json!({"status":"ok"})) })) .route("/api/status", get(status)) + .route("/api/system-info", get(system_info)) .route("/api/backends", get(backend_status)) .route("/api/docker", get(docker_status)) .route("/api/docker/refresh", post(docker_refresh)) @@ -481,7 +314,6 @@ enum CertificateApiOperation { ReloadAll, ReloadOne(String), } - fn certificate_api_operation(method: &Method, path: &str) -> Option { if method != Method::POST { return None; diff --git a/src/admin/status.rs b/src/admin/status.rs index adecb34..97d3516 100644 --- a/src/admin/status.rs +++ b/src/admin/status.rs @@ -1,32 +1,4 @@ -// Reuse sysinfo's native OS APIs on Linux, BSD, macOS and Windows. The -// diagnostics endpoint must never spawn system commands or parse platform files. -// Host identification is cached because it changes only after a reboot. -fn valid_host_text(value: String) -> Option { - let value = value.trim(); - (!value.is_empty() && value.len() <= 128 && !value.chars().any(char::is_control)) - .then(|| value.to_owned()) -} - -fn os_release() -> Option<&'static str> { - static VALUE: std::sync::OnceLock> = std::sync::OnceLock::new(); - VALUE.get_or_init(|| { - sysinfo::System::long_os_version() - .or_else(|| { - let name = sysinfo::System::name()?; - Some(match sysinfo::System::os_version() { - Some(version) if !version.is_empty() => format!("{name} {version}"), - _ => name, - }) - }) - .and_then(valid_host_text) - }).as_deref() -} - -fn kernel_release() -> Option<&'static str> { - static VALUE: std::sync::OnceLock> = std::sync::OnceLock::new(); - VALUE.get_or_init(|| sysinfo::System::kernel_version().and_then(valid_host_text)) - .as_deref() -} +include!("status_system.rs"); fn status_payload(state: &App) -> Value { let snapshot = state.config.load_full(); @@ -187,38 +159,10 @@ fn status_payload(state: &App) -> Value { payload["cache"] = json!(&cache_view); payload["transform_resources"] = json!(&transform_resources); payload["geoip"] = json!(state.geoip.load().view()); - let system = state.diagnostics.view(); - let cache_ram_bytes = - (cache_view.hot_allocated_bytes as u64).saturating_add(cache_view.transient_bytes); - let non_cache_rss_bytes = system - .process_rss_bytes - .map(|rss| rss.saturating_sub(cache_ram_bytes)); - payload["memory_accounting"] = json!({ - "process_rss_bytes": system.process_rss_bytes, - "cache_hot_bytes": cache_view.hot_allocated_bytes, - "cache_hot_limit_bytes": cache_view.hot_limit_bytes, - "cache_transient_bytes": cache_view.transient_bytes, - "cache_transient_limit_bytes": cache_view.transient_limit_bytes, - "cache_total_bytes": cache_ram_bytes, - "transform_reserved_bytes": transform_resources.memory_reserved_bytes, - "transform_budget_bytes": transform_resources.memory_budget_bytes, - "transform_inflight": transform_resources.inflight, - "transform_max_inflight": transform_resources.max_inflight, - "non_cache_rss_bytes": non_cache_rss_bytes, - "non_cache_rss_includes_transform": true, - "method": "rss_minus_accounted_cache; transform reservation is an overlapping estimate" - }); - payload["runtime"] = json!({ - "platform": system.platform, - "operating_system": os_release(), - "kernel_version": kernel_release(), - "architecture": std::env::consts::ARCH, - "statistics_source": system.source, - "data_directory": state.policy.data_dir.as_ref().map(|path| path.display().to_string()), - "database_path": state.policy.data_dir.as_ref().map(|path| path.join("config.sqlite3").display().to_string()), - "certificate_runtime": state.acme.runtime_view() - }); - payload["system"] = json!(system); + let system_info = system_info_payload(state); + for field in ["system", "runtime", "memory_accounting"] { + payload[field] = system_info[field].clone(); + } payload["backend_pools"] = json!(snapshot.pools.iter().map(|p| p.view()).collect::>()); payload["vhost_metrics"] = json!(snapshot .vhost_metrics @@ -281,9 +225,9 @@ fn status_payload(state: &App) -> Value { }); payload } -async fn status(State(state): State>) -> Json { - Json(status_payload(&state)) -} +// SSE and emergency polling use only volatile telemetry. Never serialize the +// whole configuration, OS diagnostics or certificate metadata on each tick. +include!("status_live.rs"); async fn backend_status(State(state): State>) -> Json { let snapshot = state.config.load_full(); Json( diff --git a/src/admin/status_live.rs b/src/admin/status_live.rs new file mode 100644 index 0000000..9bd0507 --- /dev/null +++ b/src/admin/status_live.rs @@ -0,0 +1,43 @@ +fn live_status_payload(state: &App) -> Value { + let snapshot = state.config.load_full(); + let stream_metrics = state.active_streams.iter().filter_map(|stream| { + state.stream_metrics.get(&stream.id).map(|metrics| (stream.id.clone(), metrics.view())) + }).collect::>(); + json!({ + "version": env!("CARGO_PKG_VERSION"), + "uptime_seconds": state.started.elapsed().as_secs(), + "revision": snapshot.envelope.revision, + "listeners": { + "active_endpoints": state.active_listeners, + "desired_endpoints": snapshot.listeners, + "restart_required": snapshot.listeners != state.active_listeners + }, + "metrics": state.metrics.view(), + "cache": state.cache.view(), + "transform_metrics": state.transform_metrics.total(), + "logs_dropped": state.logs.dropped.load(Ordering::Relaxed), + "backends": snapshot.envelope.config.vhosts.iter().map(|v| v.backends.len()).sum::(), + "backend_pools": snapshot.pools.iter().map(|p| p.view()).collect::>(), + "vhost_metrics": snapshot.vhost_metrics.iter().map(|(id, metrics)| (id.clone(), metrics.view())).collect::>(), + "streams": {"metrics": stream_metrics}, + "logging": {"sinks": state.logs.stats(), "error_file": crate::logging::error_log_stats()} + }) +} + +#[derive(Default, Deserialize)] +#[serde(default, deny_unknown_fields)] +struct StatusQuery { + view: Option, +} + +async fn status(State(state): State>, Query(query): Query) -> Result, ApiError> { + match query.view.as_deref() { + None => Ok(Json(status_payload(&state))), + Some("live") => Ok(Json(live_status_payload(&state))), + _ => Err(ApiError(StatusCode::BAD_REQUEST, "invalid status view".into())), + } +} + +async fn system_info(State(state): State>) -> Json { + Json(system_info_payload(&state)) +} diff --git a/src/admin/status_system.rs b/src/admin/status_system.rs new file mode 100644 index 0000000..f904411 --- /dev/null +++ b/src/admin/status_system.rs @@ -0,0 +1,70 @@ +// Reuse sysinfo's native OS APIs on Linux, BSD, macOS and Windows. The +// diagnostics endpoint must never spawn system commands or parse platform files. +// Host identification is cached because it changes only after a reboot. +fn valid_host_text(value: String) -> Option { + let value = value.trim(); + (!value.is_empty() && value.len() <= 128 && !value.chars().any(char::is_control)) + .then(|| value.to_owned()) +} + +fn os_release() -> Option<&'static str> { + static VALUE: std::sync::OnceLock> = std::sync::OnceLock::new(); + VALUE.get_or_init(|| { + sysinfo::System::long_os_version() + .or_else(|| { + let name = sysinfo::System::name()?; + Some(match sysinfo::System::os_version() { + Some(version) if !version.is_empty() => format!("{name} {version}"), + _ => name, + }) + }) + .and_then(valid_host_text) + }).as_deref() +} + +fn kernel_release() -> Option<&'static str> { + static VALUE: std::sync::OnceLock> = std::sync::OnceLock::new(); + VALUE.get_or_init(|| sysinfo::System::kernel_version().and_then(valid_host_text)) + .as_deref() +} + +// Cached diagnostics and ACME metadata are requested separately from high-rate SSE. +// Preserve the legacy full /api/status shape for existing API clients. +fn system_info_payload(state: &App) -> Value { + let cache_view = state.cache.view(); + let transform_resources = crate::transform::resource_usage(&state.config.load().envelope.config.transform); + let mut payload = json!({}); + let system = state.diagnostics.view(); + let cache_ram_bytes = + (cache_view.hot_allocated_bytes as u64).saturating_add(cache_view.transient_bytes); + let non_cache_rss_bytes = system + .process_rss_bytes + .map(|rss| rss.saturating_sub(cache_ram_bytes)); + payload["memory_accounting"] = json!({ + "process_rss_bytes": system.process_rss_bytes, + "cache_hot_bytes": cache_view.hot_allocated_bytes, + "cache_hot_limit_bytes": cache_view.hot_limit_bytes, + "cache_transient_bytes": cache_view.transient_bytes, + "cache_transient_limit_bytes": cache_view.transient_limit_bytes, + "cache_total_bytes": cache_ram_bytes, + "transform_reserved_bytes": transform_resources.memory_reserved_bytes, + "transform_budget_bytes": transform_resources.memory_budget_bytes, + "transform_inflight": transform_resources.inflight, + "transform_max_inflight": transform_resources.max_inflight, + "non_cache_rss_bytes": non_cache_rss_bytes, + "non_cache_rss_includes_transform": true, + "method": "rss_minus_accounted_cache; transform reservation is an overlapping estimate" + }); + payload["runtime"] = json!({ + "platform": system.platform, + "operating_system": os_release(), + "kernel_version": kernel_release(), + "architecture": std::env::consts::ARCH, + "statistics_source": system.source, + "data_directory": state.policy.data_dir.as_ref().map(|path| path.display().to_string()), + "database_path": state.policy.data_dir.as_ref().map(|path| path.join("config.sqlite3").display().to_string()), + "certificate_runtime": state.acme.runtime_view() + }); + payload["system"] = json!(system); + payload +} diff --git a/src/admin/tests.rs b/src/admin/tests.rs index 802b567..93a3ca6 100644 --- a/src/admin/tests.rs +++ b/src/admin/tests.rs @@ -7,4 +7,6 @@ mod tests { include!("tests/part_06.rs"); include!("tests/part_07.rs"); include!("tests/part_08.rs"); + include!("tests/part_09.rs"); + include!("tests/part_10.rs"); } diff --git a/src/admin/tests/part_04.rs b/src/admin/tests/part_04.rs index c527074..fe5428c 100644 --- a/src/admin/tests/part_04.rs +++ b/src/admin/tests/part_04.rs @@ -16,6 +16,10 @@ assert_eq!(response.status(), StatusCode::OK); let initial = json_body(response).await; assert_eq!(initial["live_log_columns"].as_array().unwrap().len(), LIVE_LOG_COLUMNS.len()); + assert_eq!(initial["refresh"], json!({ + "live_enabled":true, "overview_seconds":2, "traffic_seconds":5, + "cache_seconds":5, "system_info_seconds":60 + })); assert_eq!(initial["theme"], "dark"); assert_eq!(initial["language"], "auto"); assert_eq!(initial["ui_scale"], 100); @@ -50,6 +54,7 @@ assert_eq!(response.status(), StatusCode::OK); let legacy = json_body(response).await; assert_eq!(legacy["live_log_columns"], json!(["time", "status"])); + assert_eq!(legacy["refresh"], initial["refresh"]); assert_eq!(legacy["theme"], "dark"); assert_eq!(legacy["language"], "auto"); assert_eq!(legacy["ui_scale"], 100); @@ -65,6 +70,7 @@ assert_eq!(legacy["backend_table_descending"], false); let selected = json!({ + "refresh":{"live_enabled":false,"overview_seconds":10,"traffic_seconds":0,"cache_seconds":3,"system_info_seconds":300}, "live_log_columns":["time","request","status","upstream_error"], "theme":"light", "language":"pl_pl", @@ -103,6 +109,20 @@ serde_json::from_str(&store.admin_preferences("owner").await.unwrap().unwrap()) .unwrap(); assert_eq!(saved, selected); + // Preferences must also survive a subsequent authenticated read. + let persisted = router.clone().oneshot(request("/api/preferences") + .header("cookie", &cookie).body(Body::empty()).unwrap()).await.unwrap(); + assert_eq!(json_body(persisted).await, selected); + // Wrong allowlist value is rejected even if the rest is valid. + let mut invalid = selected.clone(); + invalid["refresh"]["traffic_seconds"] = json!(4); + let rejected = router.clone().oneshot(request("/api/preferences") + .method("PUT").header("cookie", &cookie) + .header("origin", "http://127.0.0.1:9090") + .header("x-rustproxy-request", "1").header("x-csrf-token", &csrf) + .header("content-type", "application/json") + .body(Body::from(invalid.to_string())).unwrap()).await.unwrap(); + assert_eq!(rejected.status(), StatusCode::UNPROCESSABLE_ENTITY); let response = router .clone() @@ -327,6 +347,7 @@ let (router, store, _dir, _, _) = fixture().await; for path in [ "/api/status", + "/api/system-info", "/api/config", "/api/config/history", "/api/backends", @@ -446,3 +467,7 @@ } store.close().await; } + + + + diff --git a/src/admin/tests/part_09.rs b/src/admin/tests/part_09.rs new file mode 100644 index 0000000..3b82186 --- /dev/null +++ b/src/admin/tests/part_09.rs @@ -0,0 +1,69 @@ + #[tokio::test] + async fn system_info_and_compact_live_status_reuse_existing_admin_session() { + let (router, store, _dir, secret, _) = fixture().await; + let cookie = format!("{}={secret}", auth::COOKIE_NAME); + let info = router.clone().oneshot(request("/api/system-info") + .header("cookie", &cookie).body(Body::empty()).unwrap()).await.unwrap(); + assert_eq!(info.status(), StatusCode::OK); + let info = json_body(info).await; + assert!(info.get("system").is_some()); + assert!(info.get("runtime").is_some()); + assert!(info.get("memory_accounting").is_some()); + assert!(info.get("config").is_none()); + let live = router.clone().oneshot(request("/api/status?view=live") + .header("cookie", &cookie).body(Body::empty()).unwrap()).await.unwrap(); + assert_eq!(live.status(), StatusCode::OK); + let live = json_body(live).await; + assert!(live.get("metrics").is_some()); + assert!(live.get("backend_pools").is_some()); + assert!(live["listeners"].get("active_endpoints").is_some()); + assert!(live.get("system").is_none()); + assert!(live.get("memory_accounting").is_none()); + assert!(live.get("administration").is_none()); + let complete = router.clone().oneshot(request("/api/status") + .header("cookie", &cookie).body(Body::empty()).unwrap()).await.unwrap(); + assert_eq!(complete.status(), StatusCode::OK); + assert!(json_body(complete).await.get("system").is_some()); + let invalid = router.clone().oneshot(request("/api/status?view=unknown") + .header("cookie", &cookie).body(Body::empty()).unwrap()).await.unwrap(); + assert_eq!(invalid.status(), StatusCode::BAD_REQUEST); + let bad_sse = router.oneshot(request("/api/events?interval=4") + .header("cookie", &cookie).body(Body::empty()).unwrap()).await.unwrap(); + assert_eq!(bad_sse.status(), StatusCode::UNPROCESSABLE_ENTITY); + store.close().await; + } + + + #[test] + fn defaults_and_partial_legacy_refresh_documents() { + let previous = serde_json::to_value(AdminPreferences::default()).unwrap(); + let mut legacy = previous.as_object().unwrap().clone(); + legacy.remove("refresh"); + let loaded: AdminPreferences = serde_json::from_value(Value::Object(legacy)).unwrap(); + assert_eq!(loaded.refresh, RefreshPreferences::default()); + let partial: RefreshPreferences = serde_json::from_str(r#"{"traffic_seconds":0}"#).unwrap(); + assert_eq!(partial.traffic_seconds, 0); + assert_eq!(partial.system_info_seconds, 60); + assert!(validate_admin_preferences(&loaded).is_ok()); + } + + #[test] + fn refresh_values_are_allowlisted() { + let mut value = RefreshPreferences::default(); + for seconds in [0, 4, 11, 65000] { + value.overview_seconds = seconds; + assert!(validate_refresh_preferences(&value).is_err()); + } + value.overview_seconds = 1; + for seconds in [1, 2, 4, 31] { + value.traffic_seconds = seconds; + assert!(validate_refresh_preferences(&value).is_err()); + } + value.traffic_seconds = 0; + value.cache_seconds = 0; + value.system_info_seconds = 0; + assert!(validate_refresh_preferences(&value).is_ok()); + value.system_info_seconds = 16; + assert!(validate_refresh_preferences(&value).is_err()); + assert!(serde_json::from_str::(r#"{"unexpected":1}"#).is_err()); + } diff --git a/src/admin/tests/part_10.rs b/src/admin/tests/part_10.rs new file mode 100644 index 0000000..77fea4b --- /dev/null +++ b/src/admin/tests/part_10.rs @@ -0,0 +1,71 @@ + #[tokio::test] + async fn cache_system_events_and_static_private_assets_reject_anonymous_requests() { + let (router, store, _dir, secret, csrf) = fixture().await; + for path in [ + "/api/status", "/api/status?view=live", "/api/system-info", "/api/config", + "/api/preferences", "/api/cache", "/api/cache/bans", "/api/audit", + "/api/logs", "/api/traffic", "/api/events", "/api/live-logs", + "/api/file-logs", "/api/certificate-api", "/docs/openapi.json", + "/assets/app.js", "/assets/cache-panel.js", "/assets/langs.json", + ] { + let response = router.clone().oneshot(request(path).body(Body::empty()).unwrap()) + .await.unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED, "anonymous GET {path}"); + assert_eq!(response.headers().get("x-frame-options").unwrap(), "DENY"); + assert!(response.headers().get("content-security-policy").is_some()); + } + let cookie = format!("{}={secret}", auth::COOKIE_NAME); + for (method, path) in [ + ("POST", "/api/cache/purge"), + ("POST", "/api/cache/invalidate"), + ("DELETE", "/api/cache/bans/1"), + ("PUT", "/api/config/cache"), + ("PUT", "/api/preferences"), + ("POST", "/api/auth/logout-all"), + ] { + let no_session = request(path).method(method).body(Body::empty()).unwrap(); + let response = router.clone().oneshot(no_session).await.unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED, "anonymous {method} {path}"); + let without_csrf = request(path).method(method) + .header("cookie", &cookie) + .header("origin", "http://127.0.0.1:9090") + .header("x-rustproxy-request", "1") + .body(Body::empty()).unwrap(); + let response = router.clone().oneshot(without_csrf).await.unwrap(); + assert_eq!(response.status(), StatusCode::FORBIDDEN, "CSRF {method} {path}"); + } + // The valid session and CSRF continue to work for a benign mutation, + // proving the failed requests above did not revoke the cookie. + let response = router.oneshot(request("/api/auth/logout") + .method("POST") + .header("cookie", &cookie) + .header("origin", "http://127.0.0.1:9090") + .header("x-rustproxy-request", "1") + .header("x-csrf-token", &csrf) + .body(Body::empty()).unwrap()).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + store.close().await; + } + + #[tokio::test] + async fn admin_origin_host_and_fetch_metadata_are_checked_even_with_valid_cookie() { + let (router, store, _dir, secret, _csrf) = fixture().await; + let cookie = format!("{}={secret}", auth::COOKIE_NAME); + let evil_origin = request("/api/status").header("cookie", &cookie) + .header("origin", "https://another.example") + .body(Body::empty()).unwrap(); + let response = router.clone().oneshot(evil_origin).await.unwrap(); + assert_eq!(response.status(), StatusCode::FORBIDDEN); + + let mut repeated_host = request("/api/status").header("cookie", &cookie) + .body(Body::empty()).unwrap(); + repeated_host.headers_mut().append("host", "other.example".parse().unwrap()); + let response = router.clone().oneshot(repeated_host).await.unwrap(); + assert_eq!(response.status(), StatusCode::FORBIDDEN); + + let cross_site = request("/api/status").header("cookie", &cookie) + .header("sec-fetch-site", "cross-site").body(Body::empty()).unwrap(); + let response = router.clone().oneshot(cross_site).await.unwrap(); + assert_eq!(response.status(), StatusCode::FORBIDDEN); + store.close().await; + } diff --git a/src/admin/web/acme.js b/src/admin/web/acme.js index ad4578e..27b2daf 100644 --- a/src/admin/web/acme.js +++ b/src/admin/web/acme.js @@ -155,15 +155,21 @@ if (!list.children.length) list.append(el("p", t("renewalNone"), "empty")); } - async function refresh() { - if (!authenticated || currentPanel !== "tls" || loading) return; loading = true; const epoch = authEpoch; + async function loadAcme({ signal, isCurrent }) { + if (!authenticated || currentPanel !== "tls" || loading) return; + loading = true; + const epoch = authEpoch; try { - const data = await api("/api/acme"); - if (authenticated && epoch === authEpoch) { renderRuntime(data.runtime); render(data.job); renderRenewals(data.renewals, data.log_directories); } + const data = await api("/api/acme", { signal }); + if (isCurrent() && epoch === authEpoch) { renderRuntime(data.runtime); render(data.job); renderRenewals(data.renewals, data.log_directories); } } catch (error) { - if (epoch === authEpoch) { toast(error.message, true); if (error.status === 401) lock(); } + if (error?.name !== "AbortError" && epoch === authEpoch && isCurrent()) { + toast(error.message, true); + if (error.status === 401) lock(); + } } finally { loading = false; } } + function refresh() { return rustProxyRefresh.manual("acme"); } $("acme-environment").addEventListener("change", () => { $("acme-confirm-production").required = $("acme-environment").value === "production"; }); $("acme-form").addEventListener("submit", async event => { event.preventDefault(); if (!authenticated || pending || job?.status === "running" || !runtimeAvailable) return; @@ -197,7 +203,9 @@ else next.tls.certificates.push(cert); if (await saveSection("tls", next.tls || {})) { toast(t("acmeImported")); $("acme-import").hidden = true; selectTab("installed"); } }); - setInterval(() => { if (!document.hidden) void refresh(); }, 3000); + rustProxyRefresh.register("acme", { + interval: () => 3, active: () => currentPanel === "tls", run: loadAcme + }); window.rustProxyAcme = { refresh, select: selectTab, diff --git a/src/admin/web/app/core.js b/src/admin/web/app/core.js index d023a5b..932bc4f 100644 --- a/src/admin/web/app/core.js +++ b/src/admin/web/app/core.js @@ -23,8 +23,8 @@ function clearInvalidOriginRecovery() { const PANEL_PATHS = { overview: "/overview", vhosts: "/vhosts", "web-server": "/web-server", "docker-vhosts": "/docker-vhosts", redirects: "/redirects", notfound: "/404-hosts", streams: "/streams", "acl-profiles": "/acl-profiles", tls: "/tls", traffic: "/traffic", cache: "/cache", transform: "/transform", filelogs: "/logs", settings: "/settings" }; function panelFromLocation() { if (typeof location === "undefined") return "overview"; if (location.pathname === "/configuration" || location.pathname === "/logging") return "settings"; const found = Object.entries(PANEL_PATHS).find(([, path]) => path === location.pathname); return found?.[0] || "overview"; } let envelope = null, busy = false, connected = false, currentPanel = panelFromLocation(), previous = null; -let samples = [], protocolSamples = { h1: [], h2: [], h3: [] }, websocketProtocolSamples = { h1: [], h2: [], h3: [] }, transformSamples = [], polling = false, toastTimer, editingBackend = null, editingRoute = null; -let liveEvents = null, liveConnected = false, authEpoch = 0, pendingAuthDestination = null; +let samples = [], protocolSamples = { h1: [], h2: [], h3: [] }, websocketProtocolSamples = { h1: [], h2: [], h3: [] }, transformSamples = [], toastTimer, editingBackend = null, editingRoute = null; +let liveEvents = null, liveConnected = false, liveFallback = false, liveInterval = null, authEpoch = 0, pendingAuthDestination = null; let lastApiLatencyMs = null; let adminAuthChannel = null; try { adminAuthChannel = new BroadcastChannel("rustproxy-admin-session"); adminAuthChannel.addEventListener("message", event => { if (event.data === "locked" && authenticated) lock(false); }); } catch { } // Language content is discovered by build.rs from langs/*.json, never compiled @@ -152,14 +152,16 @@ function setPanel(name, navigate = true) { if (name === "security") { setPanel("settings", navigate); window.rustProxySettings?.select("security"); return; } if (!Object.hasOwn(PANEL_PATHS, name)) name = "overview"; currentPanel = name; + $("overview-refresh").hidden = name !== "overview"; document.querySelectorAll(".nav").forEach(e => e.classList.toggle("active", e.dataset.panel === name)); document.querySelectorAll(".panel").forEach(e => e.hidden = e.id !== `panel-${name}`); $("page-label").textContent = t(name); $("page-title").textContent = t(`${name}Title`); $("page-description").textContent = t(`${name}Description`); if (navigate && typeof location !== "undefined" && ["http:", "https:"].includes(location.protocol) && location.pathname !== PANEL_PATHS[name]) history.pushState({ panel: name }, "", PANEL_PATHS[name]); if (name === "settings") window.rustProxySettings?.syncLocation?.(); + window.rustProxyRefresh?.setPanel(name); if (name === "tls") window.rustProxyAcme?.syncLocation?.(); if (name === "filelogs") void window.rustProxyFileLogs?.refresh(); - if (name === "traffic") void window.rustProxyTrafficPanel?.refresh(); if (name === "cache") void window.rustProxyCachePanel?.refresh(); if (name === "transform") void window.rustProxyTransformPanel?.refresh(); if (name === "docker-vhosts") void window.rustProxyDocker?.refresh(false); if (name === "tls") { void window.rustProxyAcme?.refresh(); void refreshCertificateInventory().then(() => { if (authenticated && currentPanel === "tls") renderCertificates(); }); } + if (name === "tls") void refreshCertificateInventory().then(() => { if (authenticated && currentPanel === "tls") renderCertificates(); }); } function bytes(value) { if (value < 1024) return `${value} B`; for (const unit of ["KiB", "MiB", "GiB", "TiB"]) { value /= 1024; if (value < 1024 || unit === "TiB") return `${value.toFixed(value < 10 ? 1 : 0)} ${unit}`; } } function uptime(value) { @@ -192,8 +194,14 @@ function enhanceEnabledControl(input, hintKey = "entityEnableHint") { } window.rustProxyEnableControl = enhanceEnabledControl; function action(label, fn) { const b = el("button", label, "quiet"); b.type = "button"; b.addEventListener("click", fn); return b; } -function renderStatus(s) { - const firstStatus = latestStatus === null; latestStatus = s; window.rustProxySettings?.render(); if (firstStatus) renderSettings(); +function renderStatus(s, systemSample = false) { + const firstStatus = latestStatus === null; + latestStatus = { ...(latestStatus || {}), ...s, + streams: s.streams ? { ...(latestStatus?.streams || {}), ...s.streams } : latestStatus?.streams, + listeners: s.listeners ? { ...(latestStatus?.listeners || {}), ...s.listeners } : latestStatus?.listeners, + logging: s.logging ? { ...(latestStatus?.logging || {}), ...s.logging } : latestStatus?.logging + }; + s = latestStatus; window.rustProxySettings?.render(); if (firstStatus) renderSettings(); connected = true; $("connection-label").textContent = t("connected"); $("signal").classList.remove("status-error"); const m = s.metrics; $("metric-ws").textContent = m.websocket_active || 0; $("metric-ws-h1").textContent = m.websocket_h1_active || 0; @@ -250,7 +258,7 @@ function renderStatus(s) { $("ws-protocol-h2").setAttribute("points", sparklinePoints(websocketProtocolSamples.h2, 300, 60, 5)); $("ws-protocol-h3").setAttribute("points", sparklinePoints(websocketProtocolSamples.h3, 300, 60, 5)); $("chart-peak").textContent = `max ${Math.max(0, ...samples).toFixed(1)} req/s`; - renderFooter(s); renderDiagnostics(s); window.rustProxyVhosts?.renderStatus(s); window.rustProxyStreams?.renderStatus(s); + renderFooter(s); renderDiagnostics(s, true, systemSample); window.rustProxyVhosts?.renderStatus(s); window.rustProxyStreams?.renderStatus(s); } function renderFooter(s) { const apiLatency = lastApiLatencyMs == null ? "API -" : `API ${lastApiLatencyMs.toFixed(lastApiLatencyMs < 10 ? 1 : 0)} ms`; @@ -273,9 +281,40 @@ function renderFooter(s) { } let lastLogs = null; function renderLogs(data) { lastLogs = data; window.rustProxyLogs?.render(data); } -async function refresh(force = false) { if (!authenticated || polling || (!force && liveConnected)) return; polling = true; const epoch = authEpoch; try { const [status, logs] = await Promise.all([api("/api/status"), api("/api/logs")]); if (!authenticated || epoch !== authEpoch) return; renderStatus(status); renderLogs(logs); } catch (e) { connected = false; $("connection-label").textContent = t("disconnected"); if (e.status === 401) lock(); } finally { polling = false; } } -function stopLive() { if (liveEvents) liveEvents.close(); liveEvents = null; liveConnected = false; } -function startLive() { stopLive(); if (!authenticated || document.hidden || typeof EventSource === "undefined") return; const epoch = authEpoch; const stream = new EventSource("/api/events"); liveEvents = stream; stream.addEventListener("snapshot", event => { if (!authenticated || epoch !== authEpoch) return; try { const payload = JSON.parse(event.data); liveConnected = true; renderStatus(payload.status); renderLogs(payload.logs); } catch { liveConnected = false; } }); stream.onerror = () => { if (epoch !== authEpoch) return; liveConnected = false; void refresh(); }; } +async function refresh(force = false) { + if (!authenticated) return; + if (force) return Promise.all([rustProxyRefresh.manual("status-full"), rustProxyRefresh.manual("logs-fallback")]); + return rustProxyRefresh.manual("status-fallback"); +} +function renderLiveStatus(value) { renderStatus(value); } +function stopLive() { + if (liveEvents) { liveEvents.close(); liveEvents = null; } + liveConnected = false; liveFallback = false; liveInterval = null; +} +function startLive() { + if (!authenticated || document.hidden || typeof EventSource === "undefined") return; + const seconds = window.rustProxyRefresh?.current().overview_seconds || 2; + if (liveEvents && liveInterval === seconds) return; + stopLive(); + liveInterval = seconds; + const epoch = authEpoch, stream = new EventSource(`/api/events?interval=${seconds}`); + liveEvents = stream; + stream.addEventListener("snapshot", event => { + if (!authenticated || epoch !== authEpoch || stream !== liveEvents) return; + try { + const payload = JSON.parse(event.data); + if (!payload.status || !payload.logs) throw new Error("Invalid SSE payload"); + liveConnected = true; liveFallback = false; + renderLiveStatus(payload.status); renderLogs(payload.logs); + window.rustProxyRefresh?.sync(); + } catch (error) { liveConnected = false; liveFallback = true; window.rustProxyRefresh?.sync(); } + }); + stream.onerror = () => { + if (epoch !== authEpoch || stream !== liveEvents) return; + liveConnected = false; liveFallback = true; + window.rustProxyRefresh?.sync(); + }; +} async function refreshCertificateInventory() { if (!authenticated) return; const epoch = authEpoch; @@ -322,6 +361,7 @@ function authReturnDestination() { return tab ? `/settings?tab=${encodeURIComponent(tab)}` : "/settings"; } function lock(notify = true) { + window.rustProxyRefresh?.stop(); const destination = pendingAuthDestination || authReturnDestination(), navigating = typeof location !== "undefined" && (location.protocol === "http:" || location.protocol === "https:"); pendingAuthDestination = destination; if (notify) adminAuthChannel?.postMessage("locked"); $("api-docs").hidden = true; $("logs-menu").hidden = true; resetAdminPreferences(); window.rustProxyCertificates?.reset(); window.rustProxySettings?.reset(); window.rustProxyTrafficPanel?.reset(); window.rustProxyCachePanel?.reset(); window.rustProxyTransformPanel?.reset(); window.rustProxyAcme?.reset(); window.rustProxyIntegrations?.reset(); window.rustProxyLogs?.reset(); window.rustProxyFileLogs?.reset(); window.rustProxyDrafts?.reset(); $("logs-open").hidden = true; $("file-logs-open").hidden = true; window.rustProxyAclProfiles?.reset(); window.rustProxyVhosts?.reset(); window.rustProxyWebServer?.reset(); window.rustProxyDocker?.reset(); window.rustProxyRedirects?.reset(); window.rustProxyNotFound?.reset(); window.rustProxyStreams?.reset(); authEpoch++; stopLive(); csrf = ""; authenticated = false; sessionInfo = null; envelope = null; certificateInventory = new Map(); previous = null; samples = []; protocolSamples = { h1: [], h2: [], h3: [] }; transformSamples = []; systemSamples = []; connectionOpenSamples = []; connectionExchangeSamples = []; networkInterfaceSamples.clear(); lastLogs = null; latestStatus = null; clearSettingsDirty(); connected = false; busy = false; $("workspace").hidden = true; $("login").hidden = navigating; $("logout").hidden = true; $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("password-form").reset(); $("config-editor").value = ""; $("logs").replaceChildren(); $("audit-rows").replaceChildren(); authMessage(""); for (const id of ["certificate-list", "host-policy-list"]) { $(id)?.replaceChildren(); } @@ -334,16 +374,52 @@ function lock(notify = true) { if (navigating) { document.documentElement.classList.add("app-loading"); location.replace(destination); } } -async function activate(info) { pendingAuthDestination = null; authMessage(""); $("api-docs").hidden = false; $("logs-menu").hidden = false; $("logs-open").hidden = false; $("file-logs-open").hidden = false; authEpoch++; sessionInfo = info; csrf = info.csrf_token; authenticated = true; const preferences = await loadAdminPreferences(); window.rustProxyLogs?.applyPreferences(preferences); window.rustProxyFileLogs?.applyPreferences(preferences); window.rustProxyVhosts?.applyPreferences(preferences); window.rustProxyDocker?.applyPreferences(preferences); window.rustProxyRedirects?.applyPreferences(preferences); window.rustProxyNotFound?.applyPreferences(preferences); window.rustProxyStreams?.applyPreferences(preferences); await loadConfig(); $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("login").hidden = true; $("workspace").hidden = false; $("logout").hidden = false; renderSession(); await refresh(true); startLive(); document.documentElement.classList.remove("app-loading"); } +async function activate(info) { + pendingAuthDestination = null; authMessage(""); + for (const id of ["api-docs", "logs-menu", "logs-open", "file-logs-open"]) $(id).hidden = false; + const epoch = ++authEpoch; + sessionInfo = info; csrf = info.csrf_token; authenticated = true; + for (const id of ["password", "password-confirm", "setup-token"]) $(id).value = ""; + $("login").hidden = true; $("workspace").hidden = false; $("logout").hidden = false; + renderSession(); + // Paint a usable shell immediately. Preferences, configuration and initial + // telemetry are network requests and must not block the first content. + document.documentElement.classList.remove("app-loading"); + document.documentElement.classList.remove("app-startup-failed"); + rustProxyRefresh.start(); + const preferences = await loadAdminPreferences(); + if (!authenticated || epoch !== authEpoch) return; + window.rustProxyLogs?.applyPreferences(preferences); + window.rustProxyFileLogs?.applyPreferences(preferences); + window.rustProxyVhosts?.applyPreferences(preferences); + window.rustProxyDocker?.applyPreferences(preferences); + window.rustProxyRedirects?.applyPreferences(preferences); + window.rustProxyNotFound?.applyPreferences(preferences); + window.rustProxyStreams?.applyPreferences(preferences); + // The live SSE stream is already running. Configuration can load behind the + // visible shell; it does not belong in the live refresh cycle. + try { await loadConfig(); } + catch (error) { + if (authenticated && epoch === authEpoch) toast(error.message, true); + return; + } + if (!authenticated || epoch !== authEpoch) return; + // A single full snapshot seeds non-live diagnostics; subsequent updates + // continue over SSE rather than polling full status on a timer. + void refresh(true).catch(error => { + if (authenticated && epoch === authEpoch && error?.status !== 401) toast(error.message, true); + }); +} async function loadAudit() { if (!authenticated) return; const epoch = authEpoch; try { const data = await api("/api/audit"); if (!authenticated || epoch !== authEpoch) return; const rows = data.entries.map(item => { const tr = el("tr"); for (const value of [formatDateTime(item.time * 1000), item.actor, item.action, item.detail]) { const td = el("td", value); td.title = value; tr.append(td); } return tr; }); $("audit-rows").replaceChildren(...rows); $("empty-audit").hidden = rows.length > 0; } catch (e) { toast(e.message, true); } } -let tunnelRefreshTimer = null; function tunnelMessage(value = "", error = false) { const node = $("tunnels-status"); if (!node) return; node.textContent = value; node.classList.toggle("error", !!error); } -async function loadTunnels() { - if (!authenticated) return; +let tunnelsLoading = false; +async function loadTunnels(context = null) { + if (!authenticated || tunnelsLoading) return; + tunnelsLoading = true; const epoch = authEpoch; try { - const data = await api("/api/tunnels"); - if (!authenticated || epoch !== authEpoch) return; + const data = await api("/api/tunnels", context?.signal ? { signal: context.signal } : {}); + if (!authenticated || epoch !== authEpoch || !$("tunnels-dialog")?.open || (context?.isCurrent && !context.isCurrent())) return; const rows = (data.tunnels || []).map(item => { const tr = el("tr"); for (const value of [item.id, item.client, item.host, item.upstream, item.protocol, `${fmt((Number(item.duration_ms) || 0) / 1000, 1)} s`, fmtBytes(item.rx_bytes), fmtBytes(item.tx_bytes)]) { @@ -365,10 +441,11 @@ async function loadTunnels() { actions.append(kill); tr.append(actions); return tr; }); $("tunnels-rows").replaceChildren(...rows); $("tunnels-empty").hidden = rows.length > 0; - } catch (error) { if (authenticated && epoch === authEpoch) tunnelMessage(error.message, true); } + } catch (error) { if (error?.name !== "AbortError" && authenticated && epoch === authEpoch && $("tunnels-dialog")?.open) tunnelMessage(error.message, true); } + finally { tunnelsLoading = false; } } -function stopTunnelRefresh() { if (tunnelRefreshTimer) clearInterval(tunnelRefreshTimer); tunnelRefreshTimer = null; } -function startTunnelRefresh() { stopTunnelRefresh(); tunnelRefreshTimer = setInterval(() => { if ($("tunnels-dialog")?.open) void loadTunnels(); }, 2000); } +function stopTunnelRefresh() { window.rustProxyRefresh?.sync(); } +function startTunnelRefresh() { window.rustProxyRefresh?.sync(); } $("login-form").addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; authMessage(""); try { await withFormSubmitState(form, event.submitter, async () => { const username = $("username").value.trim(); const password = $("password").value; if (setupRequired) { if (password !== $("password-confirm").value) throw fieldError("password-confirm", t("passwordMismatch")); await api("/api/auth/setup", { method: "POST", body: JSON.stringify({ token: $("setup-token").value.trim(), username, password }) }); setupRequired = false; syncAuthForm(); } const info = await api("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password }) }); await activate(info); clearTimeout(toastTimer); hideToast(); }); } catch (e) { if (e.code === "auth.already_initialized") { setupRequired = false; syncAuthForm(); } $("password").value = ""; $("password-confirm").value = ""; authMessage(e.message); } }); $("logout").addEventListener("click", async () => { try { await api("/api/auth/logout", { method: "POST" }); lock(); toast(t("locked")); } catch (e) { if (e.status === 401) lock(); toast(e.message, true); } }); $("about-open").addEventListener("click", () => { const dialog = $("about-dialog"); if (!dialog.open) dialog.showModal(); }); @@ -390,9 +467,9 @@ $("revoke-sessions").addEventListener("click", async () => { if (!(await appConf $("security-audit-open").addEventListener("click", () => { const dialog = $("audit-dialog"); if (!dialog.open) dialog.showModal(); void loadAudit(); }); $("audit-dialog-close").addEventListener("click", () => $("audit-dialog").close()); $("reload-audit").addEventListener("click", () => void loadAudit()); -$("tunnels-open").addEventListener("click", () => { const dialog = $("tunnels-dialog"); tunnelMessage(); if (!dialog.open) dialog.showModal(); void loadTunnels(); startTunnelRefresh(); }); -$("tunnels-close").addEventListener("click", () => { stopTunnelRefresh(); $("tunnels-dialog").close(); }); -$("tunnels-reload").addEventListener("click", () => void loadTunnels()); +$("tunnels-open").addEventListener("click", () => { const dialog = $("tunnels-dialog"); tunnelMessage(); if (!dialog.open) dialog.showModal(); void rustProxyRefresh.manual("tunnels"); startTunnelRefresh(); }); +$("tunnels-close").addEventListener("click", () => { $("tunnels-dialog").close(); stopTunnelRefresh(); }); +$("tunnels-reload").addEventListener("click", () => void rustProxyRefresh.manual("tunnels")); $("tunnels-dialog").addEventListener("close", stopTunnelRefresh); $("config-editor").addEventListener("input", () => setSettingsDirty("config-editor")); $("save-json").addEventListener("click", async event => { const editor = $("config-editor"), page = editor.closest("[data-settings-page]"); if (!envelope || busy) return; try { await withFormSubmitState(page, event.currentTarget, async () => { let config; try { config = JSON.parse(editor.value); } catch { throw fieldError(editor, t("vhJsonInvalid")); } if (await save(config)) setSettingsDirty("config-editor", false); }); } catch (e) { toast(e.message, true); } }); @@ -404,18 +481,19 @@ async function revalidateSession() { clearInvalidOriginRecovery(); if (epoch !== authEpoch) return; if (!authenticated) { await activate(info); return; } - sessionInfo = info; csrf = info.csrf_token; renderSession(); await refresh(true); startLive(); + sessionInfo = info; csrf = info.csrf_token; renderSession(); window.rustProxyRefresh?.sync(); } catch (error) { if (recoverInvalidOrigin(error)) return; if (error.status !== 401) toast(error.message, true); } } window.addEventListener("pageshow", event => { if (event.persisted) void revalidateSession(); }); -document.addEventListener("visibilitychange", () => { if (document.hidden) stopLive(); else if (authenticated) { void revalidateSession(); } }); -setInterval(() => { if (!document.hidden) void refresh(); }, 5000); function showStartupFailure(error) { - // index.html is served only after the server has accepted the browser session. - // A module/configuration error must therefore never masquerade as a logout. + // Never expose a blank page or an incomplete workspace after startup fails. + // The authenticated shell keeps its own retry link; 401 still redirects to login. $("login").hidden = true; - $("workspace").hidden = false; + $("workspace").hidden = true; $("logout").hidden = !authenticated; - document.documentElement.classList.remove("app-loading"); - toast(error?.message || String(error), true); + document.documentElement.classList.add("app-loading", "app-startup-failed"); + $("app-startup-title").textContent = languagePacks.size ? t("startupFailed") : "Could not load RustProxy"; + const detail = $("app-startup-detail"); + detail.textContent = error?.message || String(error); + detail.hidden = false; } diff --git a/src/admin/web/app/preferences.js b/src/admin/web/app/preferences.js index 535a6bb..59f613b 100644 --- a/src/admin/web/app/preferences.js +++ b/src/admin/web/app/preferences.js @@ -9,7 +9,7 @@ const ADMIN_APPEARANCE_DEFAULTS = Object.freeze({ button_style: "normal", table_density: "normal", dashboard_density: "compact", dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [], }); const CHART_STYLE_KEYS = Object.freeze({ default: "chartStyleDefault", soft: "chartStyleSoft", technical: "chartStyleTechnical", high_contrast: "chartStyleHighContrast", neon: "chartStyleNeon", monochrome: "chartStyleMonochrome", warm: "chartStyleWarm", cool: "chartStyleCool" }); -let adminPreferences = null, adminPreferencesLoading = null, adminPreferencesWrite = Promise.resolve(); +let adminPreferences = null, adminPreferencesLoading = null, adminPreferencesWrite = Promise.resolve(), adminPreferencesUnavailable = false; function normalizeAdminTheme(value) { return ADMIN_THEMES.has(value) ? value : "dark"; } function normalizeAdminLanguage(value) { return value === "auto" || /^[A-Za-z0-9_-]{1,64}$/.test(value || "") ? value : "auto"; } @@ -122,7 +122,7 @@ function refreshChartStylePalette(active = adminPreferences?.chart_style || "def }); target.replaceChildren(...buttons); } -function refreshAppearanceUi() { refreshChartStylePalette(); renderDashboardWidgetSettings(); window.rustProxySettings?.refreshSearch?.(); } +function refreshAppearanceUi() { refreshChartStylePalette(); renderDashboardWidgetSettings(); renderRefreshPreferences(adminPreferences?.refresh); window.rustProxySettings?.refreshSearch?.(); } function formatDateTime(value) { const date = value instanceof Date ? value : new Date(value); return date.toLocaleString(locale(), { hour12: (adminPreferences?.time_format || "24h") === "12h" }); } function formatTime(value) { const date = value instanceof Date ? value : new Date(value); return date.toLocaleTimeString(locale(), { hour12: (adminPreferences?.time_format || "24h") === "12h" }); } function applyAdminPreferences(value, cache = true) { @@ -130,11 +130,14 @@ function applyAdminPreferences(value, cache = true) { const dashboard = applyDashboardLayout(value.dashboard_widgets, value.dashboard_hidden_widgets); const normalized = { ...value, + refresh: normalizeRefreshPreferences(value.refresh), theme: applyAdminTheme(value.theme, cache), language: applyAdminLanguage(value.language, cache, false), ui_scale: applyAdminUiScale(value.ui_scale, cache), time_format: applyAdminTimeFormat(value.time_format), chart_style: applyAdminChartStyle(value.chart_style), button_style: applyAdminButtonStyle(value.button_style), table_density: applyAdminTableDensity(value.table_density), dashboard_density: applyAdminDashboardDensity(value.dashboard_density), dashboard_widgets: dashboard.order, dashboard_hidden_widgets: dashboard.hidden, }; adminPreferences = normalized; + window.rustProxyRefresh?.update(normalized.refresh); + renderRefreshPreferences(normalized.refresh); window.rustProxyVhosts?.applyPreferences(normalized); window.rustProxyDocker?.applyPreferences(normalized); window.rustProxyRedirects?.applyPreferences(normalized); @@ -152,9 +155,18 @@ async function loadAdminPreferences(force = false) { if (adminPreferencesLoading) return adminPreferencesLoading; const epoch = authEpoch; adminPreferencesLoading = (async () => { - const value = await api("/api/preferences"); - if (!authenticated || epoch !== authEpoch) return adminPreferences; - return applyAdminPreferences(value); + try { + const value = await api("/api/preferences"); + if (!authenticated || epoch !== authEpoch) return adminPreferences; + adminPreferencesUnavailable = false; + return applyAdminPreferences(value); + } catch (error) { + if (error.status === 401 || !authenticated || epoch !== authEpoch) throw error; + // Preferences are optional for startup. Keep UI and live telemetry usable. + console.warn("Preferences unavailable; using defaults", error); + adminPreferencesUnavailable = true; + return applyAdminPreferences({ ...ADMIN_APPEARANCE_DEFAULTS, refresh: REFRESH_DEFAULTS }); + } })(); try { return await adminPreferencesLoading; } finally { adminPreferencesLoading = null; } } @@ -162,9 +174,11 @@ function updateAdminPreferences(patch) { const epoch = authEpoch; const task = adminPreferencesWrite.then(async () => { if (!authenticated || epoch !== authEpoch) throw new Error(t("auth.required")); - const current = await loadAdminPreferences(); + const current = await loadAdminPreferences(adminPreferencesUnavailable); + if (adminPreferencesUnavailable) throw new Error("Cannot persist preferences while the preference service is unavailable"); if (!current || !authenticated || epoch !== authEpoch) throw new Error(t("auth.required")); const next = { ...current, ...patch }; + next.refresh = normalizeRefreshPreferences(next.refresh); next.theme = normalizeAdminTheme(next.theme); next.language = normalizeAdminLanguage(next.language); next.ui_scale = normalizeAdminUiScale(next.ui_scale); next.time_format = normalizeAdminTimeFormat(next.time_format); next.chart_style = normalizeAdminChartStyle(next.chart_style); next.button_style = normalizeAdminButtonStyle(next.button_style); next.table_density = normalizeAdminDensity(next.table_density); next.dashboard_density = normalizeAdminDensity(next.dashboard_density, "compact"); next.dashboard_widgets = normalizeDashboardWidgets(next.dashboard_widgets); next.dashboard_hidden_widgets = normalizeHiddenDashboardWidgets(next.dashboard_hidden_widgets); @@ -174,7 +188,44 @@ function updateAdminPreferences(patch) { }); adminPreferencesWrite = task.catch(() => undefined); return task; } -function resetAdminPreferences() { adminPreferences = null; adminPreferencesLoading = null; adminPreferencesWrite = Promise.resolve(); } +function resetAdminPreferences() { adminPreferences = null; adminPreferencesLoading = null; adminPreferencesUnavailable = false; adminPreferencesWrite = Promise.resolve(); window.rustProxyRefresh?.update(REFRESH_DEFAULTS); } +function renderRefreshPreferences(value = REFRESH_DEFAULTS) { + const refresh = normalizeRefreshPreferences(value); + const switcher = $("refresh-live-enabled"); if (switcher) switcher.checked = refresh.live_enabled; + for (const field of Object.keys(REFRESH_CHOICES)) { + const input = $(`refresh-${field.replaceAll("_", "-")}`); + if (input) input.value = String(refresh[field]); + } + const manual = refresh.system_info_seconds === 0 || !refresh.live_enabled; + const hint = $("system-info-refresh-note"); + if (hint) hint.textContent = t(manual ? "refreshManualHint" : "refreshSystemHint"); +} +for (const control of document.querySelectorAll("[data-refresh-preference]")) { + control.addEventListener("change", () => { + const key = control.dataset.refreshPreference; + const previous = normalizeRefreshPreferences(adminPreferences?.refresh); + const value = key === "live_enabled" ? control.checked : Number(control.value); + if (key !== "live_enabled" && !REFRESH_CHOICES[key]?.includes(value)) { + renderRefreshPreferences(previous); return; + } + const next = { ...previous, [key]: value }; + if (adminPreferences) adminPreferences.refresh = next; + window.rustProxyRefresh?.update(next); renderRefreshPreferences(next); + void saveAppearancePreference({ refresh: next }, control, () => { + if (adminPreferences) adminPreferences.refresh = previous; + window.rustProxyRefresh?.update(previous); renderRefreshPreferences(previous); + }); + }); +} +$("refresh-reset")?.addEventListener("click", () => { + const previous = normalizeRefreshPreferences(adminPreferences?.refresh), next = { ...REFRESH_DEFAULTS }; + if (adminPreferences) adminPreferences.refresh = next; + window.rustProxyRefresh?.update(next); renderRefreshPreferences(next); + void saveAppearancePreference({ refresh: next }, $("refresh-reset"), () => { + if (adminPreferences) adminPreferences.refresh = previous; + window.rustProxyRefresh?.update(previous); renderRefreshPreferences(previous); + }); +}); window.rustProxyPreferences = { load: loadAdminPreferences, update: updateAdminPreferences, current: () => adminPreferences, applyTheme: applyAdminTheme, refreshAppearanceUi }; async function saveAppearancePreference(patch, control, rollback) { diff --git a/src/admin/web/app/refresh.js b/src/admin/web/app/refresh.js new file mode 100644 index 0000000..2433c75 --- /dev/null +++ b/src/admin/web/app/refresh.js @@ -0,0 +1,161 @@ +// Single scheduler for all authenticated dashboard polling. SSE is controlled here +// but remains one native EventSource with its own browser reconnect behavior. +const REFRESH_DEFAULTS = Object.freeze({ + live_enabled: true, overview_seconds: 2, traffic_seconds: 5, + cache_seconds: 5, system_info_seconds: 60 +}); +const REFRESH_CHOICES = Object.freeze({ + overview_seconds: [1, 2, 5, 10], traffic_seconds: [0, 3, 5, 10, 30], + cache_seconds: [0, 3, 5, 10, 30], system_info_seconds: [0, 15, 30, 60, 300] +}); +function normalizeRefreshPreferences(input = {}) { + const value = { ...REFRESH_DEFAULTS }; + if (typeof input.live_enabled === "boolean") value.live_enabled = input.live_enabled; + for (const [key, allowed] of Object.entries(REFRESH_CHOICES)) { + if (allowed.includes(input[key])) value[key] = input[key]; + } + return value; +} + +const rustProxyRefresh = (() => { + const tasks = new Map(); + let ready = false, options = { ...REFRESH_DEFAULTS }, panel = currentPanel; + let settingsTab = "listeners", watchingVisibility = false; + const visible = () => !document.hidden; + function allowed(task) { + return ready && authenticated && visible() && options.live_enabled && + task.interval() > 0 && (!task.active || task.active()); + } + function clear(task, abort = false) { + if (task.timer !== null) clearTimeout(task.timer); + task.timer = null; + if (abort) { + ++task.generation; + task.controller?.abort(); + } + } + function schedule(task, delay) { + if (task.timer !== null || task.inFlight || !allowed(task)) return; + task.timer = setTimeout(() => { task.timer = null; void execute(task, false); }, delay); + } + function execute(task, manual) { + if (!authenticated || (!manual && !allowed(task))) return Promise.resolve(); + if (task.inFlight) { + if (manual) task.queued = true; + return task.inFlight; + } + clear(task); + const controller = new AbortController(), generation = task.generation, epoch = authEpoch; + const panelAtStart = panel, tabAtStart = settingsTab; + task.controller = controller; + // A manual request remains valid with Live switched off, but not in a stale view. + const isCurrent = () => authenticated && epoch === authEpoch && !controller.signal.aborted && + generation === task.generation && (manual ? panel === panelAtStart && settingsTab === tabAtStart : allowed(task)); + const promise = Promise.resolve().then(() => task.run({ signal: controller.signal, isCurrent })); + task.inFlight = promise.catch(error => { + if (error?.name !== "AbortError" && isCurrent()) { + // A failure is isolated to this source; other timers continue. + if (task.onError) task.onError(error); + else console.warn(`Refresh ${task.id} failed:`, error); + } + }).finally(() => { + task.inFlight = null; + if (task.controller === controller) task.controller = null; + if (task.queued && authenticated) { + task.queued = false; + void execute(task, true); + } else { + task.queued = false; + schedule(task, task.interval() * 1000); + } + }); + return task.inFlight; + } + function register(id, spec) { + if (tasks.has(id)) throw new Error(`Duplicate refresh task: ${id}`); + if (typeof spec.run !== "function" || typeof spec.interval !== "function") throw new Error(`Invalid refresh task: ${id}`); + const task = { ...spec, id, timer: null, inFlight: null, controller: null, generation: 0, queued: false, enabled: false, seconds: null }; + tasks.set(id, task); + sync(); + return () => { clear(task, true); tasks.delete(id); }; + } + function sync() { + for (const task of tasks.values()) { + const shouldRun = allowed(task), seconds = task.interval(); + if (!shouldRun) { + if (task.enabled) clear(task, true); + task.enabled = false; + } else if (!task.enabled || task.seconds !== seconds) { + clear(task); + task.enabled = true; + schedule(task, 0); + } + task.seconds = seconds; + } + if (ready && authenticated && visible() && options.live_enabled) startLive(); + else stopLive(); + } + function onVisibility() { sync(); if (visible() && ready && authenticated) void revalidateSession(); } + function start() { + ready = true; panel = currentPanel; + settingsTab = window.rustProxySettings?.current?.() || "listeners"; + if (!watchingVisibility) { document.addEventListener("visibilitychange", onVisibility); watchingVisibility = true; } + sync(); + } + function stop() { + ready = false; + for (const task of tasks.values()) { + clear(task, true); task.queued = false; task.enabled = false; + } + if (watchingVisibility) { document.removeEventListener("visibilitychange", onVisibility); watchingVisibility = false; } + stopLive(); + } + function update(value) { options = normalizeRefreshPreferences(value); sync(); } + function setPanel(value) { + panel = value; settingsTab = window.rustProxySettings?.current?.() || settingsTab; + for (const task of tasks.values()) if (task.active && !task.active()) task.queued = false; + sync(); + } + function setSettingsTab(value) { + settingsTab = value; + for (const task of tasks.values()) if (task.active && !task.active()) task.queued = false; + sync(); + } + function manual(id) { + const task = tasks.get(id); + if (!task) return Promise.reject(new Error(`Unknown refresh task: ${id}`)); + return execute(task, true); + } + register("status-full", { interval: () => 0, run: async ({ signal, isCurrent }) => { + const value = await api("/api/status", { signal }); + if (isCurrent()) renderStatus(value, true); + }}); + register("status-fallback", { interval: () => options.overview_seconds, active: () => !liveConnected && (liveFallback || typeof EventSource === "undefined"), run: async ({ signal, isCurrent }) => { + const value = await api("/api/status?view=live", { signal }); + if (isCurrent()) renderLiveStatus(value); + }, onError: error => { if (error?.status !== 401) $("connection-label").textContent = t("disconnected"); }}); + register("logs-fallback", { interval: () => options.overview_seconds, active: () => !liveConnected && (liveFallback || typeof EventSource === "undefined"), run: async ({ signal, isCurrent }) => { + const value = await api("/api/logs", { signal }); + if (isCurrent()) renderLogs(value); + }}); + register("system-info", { interval: () => options.system_info_seconds, + active: () => panel === "overview" || (panel === "settings" && ["system-info", "cache"].includes(settingsTab)), + run: async ({ signal, isCurrent }) => { + const value = await api("/api/system-info", { signal }); + if (!isCurrent()) return; + latestStatus = { ...(latestStatus || {}), ...value }; + if (latestStatus.metrics) renderDiagnostics(latestStatus, false, true); + window.rustProxySettings?.render(); + } + }); + register("tunnels", { interval: () => 2, active: () => !!$("tunnels-dialog")?.open, + run: async context => { await loadTunnels(context); } }); + return { register, manual, start, stop, sync, update, setPanel, setSettingsTab, + current: () => ({ ...options }), diagnostics: () => [...tasks.values()].map(t => ({ id: t.id, enabled: t.enabled, inFlight: !!t.inFlight, scheduled: t.timer !== null })) }; +})(); +window.rustProxyRefresh = rustProxyRefresh; + +$("overview-refresh")?.addEventListener("click", () => { + void Promise.all([rustProxyRefresh.manual("status-full"), rustProxyRefresh.manual("logs-fallback"), rustProxyRefresh.manual("system-info")]); +}); +$("system-info-refresh")?.addEventListener("click", () => void rustProxyRefresh.manual("system-info")); diff --git a/src/admin/web/app/runtime.js b/src/admin/web/app/runtime.js index cfceab2..34787a1 100644 --- a/src/admin/web/app/runtime.js +++ b/src/admin/web/app/runtime.js @@ -242,7 +242,7 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) { target.append(el("p", t("networkNoVisibleInterfaces"), "empty network-interface-empty")); } } -function renderDiagnostics(status, appendSample = true) { +function renderDiagnostics(status, appendSample = true, systemSample = appendSample) { const d = status.system || {}, c = status.cache || {}, m = status.metrics || {}; $("metric-h3").textContent = fmt(m.http3_requests); $("metric-cache-ratio").textContent = c.hit_ratio == null ? "-" : `${fmt(c.hit_ratio * 100, 1)}%`; @@ -257,7 +257,7 @@ function renderDiagnostics(status, appendSample = true) { const sourceSupported = !!d.source && d.source !== "unsupported"; $("system-source").hidden = !sourceSupported; if (sourceSupported) $("system-source").textContent = t("realSample"); - if (appendSample && d.cpu_percent != null) { systemSamples.push(d.cpu_percent); systemSamples = systemSamples.slice(-30); } + if (systemSample && d.cpu_percent != null) { systemSamples.push(d.cpu_percent); systemSamples = systemSamples.slice(-30); } $("system-sparkline").setAttribute("points", systemSamples.map((n, i) => `${i * 300 / Math.max(1, systemSamples.length - 1)},${95 - Math.max(0, Math.min(100, n)) * .9}`).join(" ")); const systemMemory = knownPair(d.memory_used_bytes, d.memory_total_bytes, value => fmtBytes(value)); setOptionalMetric("system-memory", systemMemory != null, systemMemory); @@ -294,6 +294,8 @@ function renderDiagnostics(status, appendSample = true) { cacheHitSamples = cacheHitSamples.slice(-60); cacheMissSamples = cacheMissSamples.slice(-60); cacheBypassSamples = cacheBypassSamples.slice(-60); lastCacheCounters = currentCounters; } + } + if (systemSample) { if (d.process_cpu_percent != null) { processCpuSamples.push(Number(d.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-60); } if (d.process_rss_bytes != null) { processRssSamples.push(Number(d.process_rss_bytes)); processRssSamples = processRssSamples.slice(-60); } } diff --git a/src/admin/web/app/settings.js b/src/admin/web/app/settings.js index 07973d9..9a05719 100644 --- a/src/admin/web/app/settings.js +++ b/src/admin/web/app/settings.js @@ -28,6 +28,7 @@ function renderSettings() { $("cache-sie-timeout").checked = c.stale_if_error_timeout !== false; $("cache-sie-connection").checked = c.stale_if_error_connection !== false; $("cache-sie-dns").checked = c.stale_if_error_dns !== false; $("cache-sie-tls").checked = c.stale_if_error_tls !== false; $("cache-sie-statuses").value = (c.stale_if_error_statuses || [500, 502, 503, 504]).join(", "); $("cache-proactive-enabled").checked = c.proactive_refresh_enabled === true; $("cache-proactive-before").value = c.proactive_refresh_before_expiry_seconds ?? 15; $("cache-proactive-percent").value = c.proactive_refresh_percent ?? 10; $("cache-proactive-inflight").value = c.proactive_refresh_max_inflight ?? 4; $("cache-query").checked = !!c.bypass_query; $("cache-prefixes").value = (c.bypass_prefixes || []).join("\n"); + $("cache-http-invalidation-cidrs").value = (c.http_invalidation_allowed_cidrs || []).join("\n"); $("cache-static-cookie").checked = c.static_ignore_cookies !== false; $("cache-static-extensions").value = (c.static_extensions || []).join("\n"); $("cache-status-header").checked = !!c.expose_status; $("cache-hits-header").checked = !!c.expose_hits; $("cache-rfc-header").checked = !!c.expose_cache_status; $("cache-resident-age-header").checked = !!c.expose_resident_age; $("cache-only-hits").checked = !!c.expose_only_hits; const viaHeader = c.via_header === undefined ? "RustProxy" : c.via_header; @@ -285,7 +286,7 @@ bindSettingsForm("response-form", next => { next.compression = { min_bytes: Number($("compress-min").value) }; for (const name of ["gzip", "br", "zstd", "deflate"]) next.compression[name] = $(`compress-${name}`).checked; }, "response", next => ({ server_header: next.server_header, diagnostic_headers: next.diagnostic_headers, compression: next.compression })); bindSettingsForm("cache-form", next => { - next.cache = { ...(next.cache || {}), enabled: $("cache-enabled").checked, memory_preset: selectedCacheMemoryPreset, max_bytes: Math.round(Number($("cache-memory").value) * 1048576), transient_max_bytes: Math.round(Number($("cache-transient-memory").value) * 1048576), max_object_bytes: Number($("cache-object").value) * 1024, max_entries: Number($("cache-entries").value), hot_eviction_max_objects: Number($("cache-eviction-limit").value), hot_auto_trim_enabled: $("cache-auto-trim-enabled").checked, hot_auto_trim_after_seconds: Number($("cache-auto-trim-after").value), hot_auto_trim_target_percent: Number($("cache-auto-trim-target").value), cold_enabled: $("cache-cold-enabled").checked, cold_preset: selectedCacheColdPreset, cold_path: $("cache-cold-path").value.trim(), cold_max_bytes: Math.round(Number($("cache-cold-bytes").value) * 1048576), cold_max_entries: Number($("cache-cold-entries").value), cold_queue_max_bytes: Math.round(Number($("cache-cold-queue-bytes").value) * 1048576), cold_gc_interval_seconds: Number($("cache-cold-gc-interval").value), cold_gc_max_delete: Number($("cache-cold-gc-max-delete").value), cold_after_seconds: Number($("cache-cold-after").value), cold_max_hot_seconds: Number($("cache-cold-max-hot").value), cold_on_idle: $("cache-cold-on-idle").checked, cold_on_age: $("cache-cold-on-age").checked, cold_on_lru: $("cache-cold-on-lru").checked, cold_on_shutdown: $("cache-cold-on-shutdown").checked, max_ttl_seconds: Number($("cache-ttl").value), default_ttl_seconds: Number($("cache-default-ttl").value), collapse_enabled: $("cache-collapse-enabled").checked, collapse_wait_timeout_ms: Number($("cache-collapse-timeout").value), collapse_timeout_fallback: $("cache-collapse-fallback").value, stale_if_error_timeout: $("cache-sie-timeout").checked, stale_if_error_connection: $("cache-sie-connection").checked, stale_if_error_dns: $("cache-sie-dns").checked, stale_if_error_tls: $("cache-sie-tls").checked, stale_if_error_statuses: $("cache-sie-statuses").value.split(/[\s,]+/).filter(Boolean).map(Number), proactive_refresh_enabled: $("cache-proactive-enabled").checked, proactive_refresh_before_expiry_seconds: Number($("cache-proactive-before").value), proactive_refresh_percent: Number($("cache-proactive-percent").value), proactive_refresh_max_inflight: Number($("cache-proactive-inflight").value), bypass_query: $("cache-query").checked, bypass_prefixes: $("cache-prefixes").value.split(/\r?\n/).map(v => v.trim()).filter(Boolean), static_ignore_cookies: $("cache-static-cookie").checked, static_extensions: $("cache-static-extensions").value.split(/[\n,]+/).map(v => v.trim().replace(/^\./, "").toLowerCase()).filter(Boolean), expose_status: $("cache-status-header").checked, expose_hits: $("cache-hits-header").checked, expose_cache_status: $("cache-rfc-header").checked, expose_resident_age: $("cache-resident-age-header").checked, expose_only_hits: $("cache-only-hits").checked, via_header: $("cache-via-enabled").checked ? $("cache-via-value").value.trim() : null }; + next.cache = { ...(next.cache || {}), enabled: $("cache-enabled").checked, memory_preset: selectedCacheMemoryPreset, max_bytes: Math.round(Number($("cache-memory").value) * 1048576), transient_max_bytes: Math.round(Number($("cache-transient-memory").value) * 1048576), max_object_bytes: Number($("cache-object").value) * 1024, max_entries: Number($("cache-entries").value), hot_eviction_max_objects: Number($("cache-eviction-limit").value), hot_auto_trim_enabled: $("cache-auto-trim-enabled").checked, hot_auto_trim_after_seconds: Number($("cache-auto-trim-after").value), hot_auto_trim_target_percent: Number($("cache-auto-trim-target").value), cold_enabled: $("cache-cold-enabled").checked, cold_preset: selectedCacheColdPreset, cold_path: $("cache-cold-path").value.trim(), cold_max_bytes: Math.round(Number($("cache-cold-bytes").value) * 1048576), cold_max_entries: Number($("cache-cold-entries").value), cold_queue_max_bytes: Math.round(Number($("cache-cold-queue-bytes").value) * 1048576), cold_gc_interval_seconds: Number($("cache-cold-gc-interval").value), cold_gc_max_delete: Number($("cache-cold-gc-max-delete").value), cold_after_seconds: Number($("cache-cold-after").value), cold_max_hot_seconds: Number($("cache-cold-max-hot").value), cold_on_idle: $("cache-cold-on-idle").checked, cold_on_age: $("cache-cold-on-age").checked, cold_on_lru: $("cache-cold-on-lru").checked, cold_on_shutdown: $("cache-cold-on-shutdown").checked, max_ttl_seconds: Number($("cache-ttl").value), default_ttl_seconds: Number($("cache-default-ttl").value), collapse_enabled: $("cache-collapse-enabled").checked, collapse_wait_timeout_ms: Number($("cache-collapse-timeout").value), collapse_timeout_fallback: $("cache-collapse-fallback").value, stale_if_error_timeout: $("cache-sie-timeout").checked, stale_if_error_connection: $("cache-sie-connection").checked, stale_if_error_dns: $("cache-sie-dns").checked, stale_if_error_tls: $("cache-sie-tls").checked, stale_if_error_statuses: $("cache-sie-statuses").value.split(/[\s,]+/).filter(Boolean).map(Number), proactive_refresh_enabled: $("cache-proactive-enabled").checked, proactive_refresh_before_expiry_seconds: Number($("cache-proactive-before").value), proactive_refresh_percent: Number($("cache-proactive-percent").value), proactive_refresh_max_inflight: Number($("cache-proactive-inflight").value), bypass_query: $("cache-query").checked, bypass_prefixes: $("cache-prefixes").value.split(/\r?\n/).map(v => v.trim()).filter(Boolean), http_invalidation_allowed_cidrs: $("cache-http-invalidation-cidrs").value.split(/[\n,]+/).map(v => v.trim()).filter(Boolean), static_ignore_cookies: $("cache-static-cookie").checked, static_extensions: $("cache-static-extensions").value.split(/[\n,]+/).map(v => v.trim().replace(/^\./, "").toLowerCase()).filter(Boolean), expose_status: $("cache-status-header").checked, expose_hits: $("cache-hits-header").checked, expose_cache_status: $("cache-rfc-header").checked, expose_resident_age: $("cache-resident-age-header").checked, expose_only_hits: $("cache-only-hits").checked, via_header: $("cache-via-enabled").checked ? $("cache-via-value").value.trim() : null }; }, "cache"); bindSettingsForm("transform-form", next => { next.transform = { diff --git a/src/admin/web/cache-panel.js b/src/admin/web/cache-panel.js index 4432f89..42d2e58 100644 --- a/src/admin/web/cache-panel.js +++ b/src/admin/web/cache-panel.js @@ -217,13 +217,24 @@ generation++; await refresh(); } catch (error) { operationMessage("cache-bans-status", error.message, true); } } - async function refresh() { + async function loadCache({ signal, isCurrent }) { if (!authenticated || !envelope || currentPanel !== "cache" || loading) return; - loading = true; const epoch = authEpoch, token = generation; const query = new URLSearchParams({ offset: String(offset), limit: "50" }); if (scope() !== "*") query.set("vhost", scope()); - try { const data = await api(`/api/cache?${query}`); if (epoch !== authEpoch || token !== generation || !authenticated) return; render(data); } - catch (error) { if (epoch === authEpoch) { $("cache-panel-state").textContent = error.message; if (error.status === 401) lock(); } } - finally { loading = false; if (token !== generation && authenticated && currentPanel === "cache") void refresh(); } + loading = true; + const epoch = authEpoch, token = generation; + const query = new URLSearchParams({ offset: String(offset), limit: "50" }); + if (scope() !== "*") query.set("vhost", scope()); + try { + const data = await api(`/api/cache?${query}`, { signal }); + if (isCurrent() && token === generation) render(data); + } catch (error) { + if (error?.name !== "AbortError" && epoch === authEpoch && isCurrent()) { + $("cache-panel-state").textContent = error.message; + if (error.status === 401) lock(); + } + } finally { loading = false; } } + function refresh() { return rustProxyRefresh.manual("cache"); } + initializeLiveChartLimit(); $("cache-live-samples")?.addEventListener("change", event => setLiveChartLimit(event.currentTarget.value)); $("cache-scope").addEventListener("change", () => { offset = 0; resetLiveChart(); generation++; void refresh(); }); @@ -319,7 +330,12 @@ offset = 0; generation++; await refresh(); } catch (error) { if (epoch === authEpoch) operationMessage("cache-toolbar-operation-status", error.message, true); } finally { button.disabled = false; } }); - setInterval(() => { if (!document.hidden && $("cache-live").checked) void refresh(); }, 3000); + rustProxyRefresh.register("cache", { + interval: () => rustProxyRefresh.current().cache_seconds, + active: () => currentPanel === "cache" && !!$("cache-live")?.checked, + run: loadCache + }); + $("cache-live")?.addEventListener("change", () => rustProxyRefresh.sync()); window.rustProxyCachePanel = { refresh, configure, reason, reset() { generation++; offset = 0; resetLiveChart(); closeInvalidation(); closeBans(); diff --git a/src/admin/web/docker.js b/src/admin/web/docker.js index 5a4ef56..e99ff84 100644 --- a/src/admin/web/docker.js +++ b/src/admin/web/docker.js @@ -363,14 +363,39 @@ } function syncLiveSwitch() { const control = $("docker-live-refresh"); if (control) control.checked = liveEnabled(); } function canLiveRender() { const target = $("docker-discovery-list"); return !target?.querySelector(".docker-service-row.is-dirty, .docker-service-editor[open], .docker-agent-details[open]") && !document.activeElement?.closest?.(".docker-service-row"); } - async function refresh(force = false) { - if (!authenticated) return; const button = $("docker-refresh"); if (button && force) button.disabled = true; - try { const data = await api(force ? "/api/docker/refresh" : "/api/docker", force ? { method: "POST", body: "{}" } : {}); observations = data.observations || []; pools = data.pools || []; poolRoutes = data.pool_routes || []; poolIndex = new Map(pools.map(pool => [pool.id, pool])); renderSettings(); if (force || canLiveRender()) renderDiscovery(); } - catch (error) { if (force) toast(error.message, true); } finally { if (button) button.disabled = false; } + let dockerLoading = null, dockerForcePending = false; + async function refresh(force = false, context = null) { + if (!authenticated) return; + if (dockerLoading) { + if (force) dockerForcePending = true; + return dockerLoading; + } + dockerLoading = loadDocker(force, context); + try { return await dockerLoading; } + finally { + dockerLoading = null; + if (dockerForcePending && authenticated) { dockerForcePending = false; void refresh(true); } + else dockerForcePending = false; + } + } + async function loadDocker(force = false, context = null) { + const button = $("docker-refresh"), epoch = authEpoch; + if (button && force) button.disabled = true; + const options = force ? { method: "POST", body: "{}" } : {}; + if (context?.signal) options.signal = context.signal; + try { + const data = await api(force ? "/api/docker/refresh" : "/api/docker", options); + if (!authenticated || epoch !== authEpoch || (context?.isCurrent && !context.isCurrent())) return; + observations = data.observations || []; pools = data.pools || []; poolRoutes = data.pool_routes || []; + poolIndex = new Map(pools.map(pool => [pool.id, pool])); + renderSettings(); if (force || canLiveRender()) renderDiscovery(); + } catch (error) { + if (force && error?.name !== "AbortError" && authenticated && epoch === authEpoch) toast(error.message, true); + } finally { if (button) button.disabled = false; } } $("docker-refresh")?.addEventListener("click", () => void refresh(true)); - $("docker-live-refresh")?.addEventListener("change", event => { localStorage.setItem(LIVE_KEY, event.target.checked ? "1" : "0"); if (event.target.checked) void refresh(false); }); + $("docker-live-refresh")?.addEventListener("change", event => { localStorage.setItem(LIVE_KEY, event.target.checked ? "1" : "0"); rustProxyRefresh.sync(); }); $("docker-settings-open")?.addEventListener("click", () => { setPanel("settings"); window.rustProxySettings?.select("docker-agents"); }); $("docker-search")?.addEventListener("input", applyViewFilters); $("docker-vhost-sort")?.addEventListener("change", event => { listPrefs.sort = event.target.value; void saveListPrefs(); applyViewFilters(); }); @@ -379,6 +404,10 @@ $("docker-agent-add")?.addEventListener("click", () => { const target = $("docker-agent-settings-list"); target.querySelector(".empty")?.remove(); target.append(agentSettingsRow()); setSettingsDirty("docker-agents-form"); }); $("docker-agents-form")?.addEventListener("input", () => setSettingsDirty("docker-agents-form")); $("docker-agents-form")?.addEventListener("change", () => setSettingsDirty("docker-agents-form")); $("docker-agents-form")?.addEventListener("submit", async event => { event.preventDefault(); const form = event.currentTarget; if (!envelope || busy) return; try { await withFormSubmitState(form, event.submitter, async () => { if (await saveSection("docker-agents", collectSettings())) { setSettingsDirty("docker-agents-form", false); await refresh(true); } }); } catch (error) { toast(error.message, true); } }); - setInterval(() => { if (authenticated && liveEnabled() && typeof currentPanel !== "undefined" && currentPanel === "docker-vhosts" && canLiveRender()) void refresh(false); }, 5000); + rustProxyRefresh.register("docker", { + interval: () => 5, + active: () => currentPanel === "docker-vhosts" && liveEnabled() && canLiveRender(), + run: async context => refresh(false, context) + }); window.rustProxyDocker = { render() { syncLiveSwitch(); renderSettings(); renderDiscovery(); }, refresh, applyPreferences, reset() { observations = []; pools = []; poolRoutes = []; poolIndex = new Map(); showHidden = false; listPrefs = { sort: "service", descending: false }; hiddenVhosts = loadHidden(); if ($("docker-search")) $("docker-search").value = ""; if ($("docker-vhost-sort")) $("docker-vhost-sort").value = listPrefs.sort; syncSortDirection(); $("docker-discovery-list")?.replaceChildren(); $("docker-agent-settings-list")?.replaceChildren(); syncLiveSwitch(); applyViewFilters(); } }; })(); diff --git a/src/admin/web/drafts.js b/src/admin/web/drafts.js index 95befd5..18db6c1 100644 --- a/src/admin/web/drafts.js +++ b/src/admin/web/drafts.js @@ -61,6 +61,7 @@ const remove = el("button", "×", "draft-dock-remove"); remove.type = "button"; remove.setAttribute("aria-label", t("draftDiscard")); remove.addEventListener("click", () => void discard(draft.id)); row.append(open, remove); list.append(row); } + window.rustProxyRefresh?.sync(); } toggle.addEventListener("click", () => { collapsed = !collapsed; render(); }); function register(type, api) { editors.set(type, api); render(); } @@ -92,7 +93,10 @@ } const takeChildren = node => { const children = [...node.childNodes]; node.replaceChildren(); return children; }; const restoreChildren = (node, children) => node.replaceChildren(...(children || [])); - if (typeof setInterval === "function") setInterval(() => { if (drafts.length && !collapsed) render(); }, 60000); + window.rustProxyRefresh?.register("drafts", { + interval: () => 60, active: () => drafts.length > 0 && !collapsed, + run: async () => render() + }); window.rustProxyDrafts = { register, dock: dockEditor, prepare, reset, refresh: render, captureControls, restoreControls, takeChildren, restoreChildren }; })(); diff --git a/src/admin/web/index.html b/src/admin/web/index.html index b7376e2..f366e0d 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -28,6 +28,21 @@ + +
+
+ + +

Loading RustProxy…

+

Connecting to the administration panel.

+
+

The panel is taking longer than expected. Check your connection and try again.

+ + Reload panel +
+ +
+