This commit is contained in:
Mateusz Gruszczyński committed 2026-11-08 21:26:30 +01:00
1 parent 213332d0b5
commit 1ac94213e0
13 files changed
+68 -28

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# RustProxy source and documentation maintenance
Current source version: **0.36.6**. The Cargo package and binary are named `rust-proxy`.
Current source version: **0.36.7**. The Cargo package and binary are named `rust-proxy`.
Before editing, read [README.md](README.md), the relevant [documentation guide](docs/README.md) and the actual code and tests. Historical release experiments are not current specifications. Use the bundled `src/admin/openapi.json` for API routes and payloads; the source implementation is authoritative for runtime behavior.
+8
View File
@@ -2,6 +2,14 @@
Release history focused on **features, settings, default behavior, compatibility and fixes**. Changes to internal code structure and preliminary performance experiments are intentionally omitted. Historical descriptions reflect their respective releases; consult the current documentation for the settings available in the latest release.
## 0.36.7 — Docker vhosts and cache panel polish
- **Docker vhosts:** the collapsible sections (Configure vhost, Protocols, Access control, timeouts, health checks) now align arrow, title and hint to the left with a common title column. Traffic statistics are expanded by default for every enabled app (a section you collapse stays collapsed). A new **Hide disabled** button hides all unpublished vhosts from the view at once; **Show hidden** brings them back.
- **404 hosts:** cards use the same host chips → response layout as redirects, with listeners and certificate on a quiet meta line; redirect and 404 cards are separated by the same gap as vhost cards.
- **Cache panel:** the "transformed" badge stays visible next to long object URLs (the URL is shortened instead). Decision counters show a thin share bar under each counter (relative to the largest) and a compact "+N" toggle; the pager no longer shows doubled arrows; the folded Decision counters ("+N") keep their open/closed state across automatic refreshes.
- **Dashboard cache outcomes:** the legend under the bar shows only the request counts; percentages are shown once, inside the bar and in the headline (tooltips give both).
- **Build:** `SHA256SUMS` uses repository-relative paths again.
## 0.36.6 — redirect priority, copy and SNI documentation
- **Deploy script:** `update.sh` / `install.sh` retry the GeoIP download (transient 502/503/504, timeouts) up to four times and, if it still fails, warn and continue with the installed GeoIP data instead of aborting the update with `HTTP Error 504`.
Generated
+1 -1
View File
@@ -1943,7 +1943,7 @@ dependencies = [
[[package]]
name = "rust-proxy"
version = "0.36.6"
version = "0.36.7"
dependencies = [
"anyhow",
"arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rust-proxy"
version = "0.36.6"
version = "0.36.7"
edition = "2021"
rust-version = "1.99.1"
description = "RustProxy - independent reverse proxy with local administration"
+12 -12
View File
@@ -1,10 +1,10 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
e76e688874828e3743d03693b1d2872b9633b4db09909673786caf30f7cfe489 .gitignore
9533bda057fd758323d04b2928ab6c13ee82ed8ed5c9aaa67a0bc293a75ff4d1 AGENTS.md
c7ac863203142f93119fd9a29297a06e768e43d64f2c9f13d6dd16fbce4e5aca CHANGELOG.md
240f1748925cf041e3bdbb85ff380c64f3a1641943aed23bd762352d625f81aa Cargo.lock
168a2e8054b405d0e8baa52e7a80f4f02598f4d4f9446e807ea0a3987522190e Cargo.toml
357a4c6e4ac6aca6baf93f43013fa8a1701e68289b7d0d47d1728d03734846eb AGENTS.md
c43200ec7df77449be7074265cce582e9af7c10af70dca843e9a13285fa0b317 CHANGELOG.md
881fafd324cb38c3c016cd853e65b9870819d2ac6ce932a6dd6b6755701444d4 Cargo.lock
90664b6534166fb8bfb8d2b61885e3397d5df78f584e5a334ff81c522833c555 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 LICENSE
82d1dee2fc68158f0ea73259151461e64c8b18d807cd1fecfb1ed99dc30cf5f4 README.md
@@ -88,7 +88,7 @@ de6550ab8f48d556d752c252212c38ba65491fd224f972c10fbe552f91ed7520 integrations/o
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example
11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore
28880995ead34f2f22b43b57525109c5fe915206919509333182d6e514a531cb integrations/routeros-oci-build/README.md
5025cf1086254f3e664cd50be2737b437286cbf311c79d87578c3d1d6c4a4f64 integrations/routeros-oci-build/README.md
3f092a9ab80221eddd67be4d1a776acbc6e04c1eb6011b588280c28e83b2582f integrations/routeros-oci-build/build-routeros-oci.sh
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
@@ -165,7 +165,7 @@ d58a2ed83319176de4e66790ed292447c9497ae031464ec55cfe27174acb639a src/admin/file
77b8c05645969834cc8a58e249a7b60a57d6c47ad1c5cd92ffa5e4a9b5a8e73c src/admin/file_logs_query.rs
743bb7436f77e04015328a0e7e7d425e43ef8aa8d61467bde157e462c1dcdc2e src/admin/logging_api.rs
243f4a9c3fa99781e19c3952354d2632c0ce65e1dafac729567cbe3c177f94cb src/admin/mod.rs
3a2f3fd6d373f4a5f6085312ec9a0de3a404ad7e219b90bb2e13069106f6f34a src/admin/openapi.json
96101a80516c9cd668feb90a2cdd766363b4335b37888a926bb322e0cf3a160a src/admin/openapi.json
f7a356109786635dd58cba590c71c796dbf36b47f25845190c75ee5c6a9fcb86 src/admin/operations.rs
257d93a29d0bda96462bcf695aba8d0ec0f9412c379e066ec46ae8ee034e3a53 src/admin/panel_assets.rs
f5127855c2994d052678865df36d703b74f4a0afc633daf7718b10da18d26e37 src/admin/panel_content.rs
@@ -203,20 +203,20 @@ bfd9fafb3112197e8f875dbf87a9a66c81aef0fe83687601a84a66f5e13f69bc src/admin/web/
e540119edc555cefbe5147d97e06527adf49c984ba7a4dd51dc67603d3a46041 src/admin/web/app/preferences.js
9e2b12d34ab1fb29716645720577ad49b16c03b46c482a03695e8f645d170e79 src/admin/web/app/rate-sampling.js
3b8e566d81a5b94ef21c73e7805ae128454f085554de9358040de266bcbdb0e0 src/admin/web/app/refresh.js
f2c347cdd592ca5702d8b398d6ea9e9f75fa9639d70c5cbf3836334ca5b2b0d2 src/admin/web/app/runtime.js
e5f8a0124d57df71638df86b2262d5c210fe7947736712dda7f97e6ca36048f6 src/admin/web/app/runtime.js
31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js
c7a18238ac426de4d1cf84a395ee4dc66410d60a57a3822695dc10adc668d956 src/admin/web/app/settings.js
b7ead49224a4ec67181d3f24f9d9634bd6d76756f826a4d5d993300637d1d8f3 src/admin/web/app/stream-dashboard.js
7de070cc8cdde45d7e0932259299e341e25b32c9ffbd87e4fcce934d0f090de4 src/admin/web/app/web-overview.js
0625f3bfdd8f67d375fbb7204fbed3108fb5091cc12c4dcc2b60b26ad97223da src/admin/web/cache-panel.js
f87af546830f01411dcf93749b4f75ca0f0bb3745a3390eff73ee14fc768cef0 src/admin/web/cache-panel.js
9f323507649910561518b63cf6632e81b3ecc4b09501ae53b635705f34ea6587 src/admin/web/certificate-api.js
4810137aa4217a86770617d442cf5dc6bec0d9f19e20bb2155e932a9a111cedf src/admin/web/css/00-tokens.css
466a24e234e1ec15f17e7d1994e838ca1ee0f52f03a4ca8fb6d9903bf2afde55 src/admin/web/css/10-base.css
8a1996d37f138790c12a704deedde9de74f733e7dcbb6be816fd68c281e8e0a9 src/admin/web/css/20-shell.css
b01f645124c7dd5b2ddbeba2aa9fbb706bb281579ff5487f17b2174263808042 src/admin/web/css/30-overview.css
d4b20ada2d91ddbc1613c642194df19e601948b34c4483fd04930c3e6db02394 src/admin/web/css/30-overview.css
ca93e41d1c6001d82f4891cacdd712e896a0355d6db9f76cf62dd2f2225a8674 src/admin/web/css/40-editors.css
87df062d7478176641364c07de396ca988a37b1a8bcb892b53cc578fe8471850 src/admin/web/css/50-settings.css
733d10b6713006f53247fa6ea1cef31be75f90f989ed5811f8646b3a813667a4 src/admin/web/css/60-monitoring.css
9775d86312c03b9c722ce713f2dc4a99985157d2f6b98277f9c0dabdcf53de2c src/admin/web/css/60-monitoring.css
483664859787a2c54b51e73891cb760cfa15ff5e2e4132c96989eaa8109cf3a4 src/admin/web/css/70-responsive.css
8009afdb04d5170687816730cc930a22fead1fe0248835ca7e8de81d6907395d src/admin/web/css/README.md
8241bd9489dfed082863948829a44a49bf0a8e9cd63c55809655109e393e0918 src/admin/web/css/manifest.json
@@ -251,7 +251,7 @@ ac9f667273918e18406df06cb2613c923f50a88897693a03a18f500e22dc6ec2 src/admin/web/
c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js
44b44790341067304e15974afcbbc7cc99686e88c2e43f62979d285eca6f726e src/admin/web/management.js
1f3d10e389c9c20e6eb019509fa6e82b50c6b43bbf988c1210a4d42bb73985d0 src/admin/web/partials/overview-charts.html
284d6f9dbe1cf00fe7c33226a4c5f72132edef5300498ad963fabb7c258b8233 src/admin/web/partials/overview-metrics.html
c8d6ab08b1fde881b1ca40222b282be039e2c4fba1b79f59454221cf893a9bde src/admin/web/partials/overview-metrics.html
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
6f33915d33d3a8bbfaeec424651a63eda3d23b0f296ae028a59ea46463ba9fdf src/admin/web/redirects.js
76a01f590d49649dcb1f554c221290919b2a8438e6001737f3c096fa6daec74d src/admin/web/settings.js
@@ -561,7 +561,7 @@ f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures
102b83171a6429b6375d8001d6c5294702385cd58ee55727cb657b1f3566e877 tests/panel_icons_browser.py
52e61c389a60d147184ee76925cac76e454f8f2e3cb91039bb06e17787ab900f tests/panel_icons_contract.js
01af3008be27fabb486c141c67099af104f2725cc56f4c656aea9741a3c150cd tests/panel_interface_contract.py
200802ca30f82d8da50240573251dd976de500034608ad4384ddd95631ec904d tests/panel_refresh_contract.js
f9ac7a0117056cce2bcf1a20404db9977a9b1194c3d7df0eaef48b7e7f82b9f0 tests/panel_refresh_contract.js
e24a0f84f1bfc46964df865ac387d651f7b384d781cbc8f3a9a5b0b60f3d69ca tests/vhost_auth_selection.js
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 third_party/swagger-ui-5.33.1/LICENSE
0d20d1adef18aee3f40dd258172155521ce702ac445cb5f7b7d60ed32dad2fb2 third_party/swagger-ui-5.33.1/NOTICE
+2 -2
View File
@@ -77,10 +77,10 @@ ENV_FILE=integrations/routeros-oci-build/.env \
bash integrations/routeros-oci-build/build-routeros-oci.sh push
```
For example, with RustProxy version `0.36.6` and the defaults this publishes:
For example, with RustProxy version `0.36.7` and the defaults this publishes:
```text
zot.example.net/network/rustproxy:0.36.6
zot.example.net/network/rustproxy:0.36.7
zot.example.net/network/rustproxy:latest
zot.example.net/network/rustproxy:routeros
```
+1 -1
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
"version": "0.36.6",
"version": "0.36.7",
"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. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only."
},
"servers": [
+2 -1
View File
@@ -408,7 +408,7 @@ function cacheOutcomeMix(totals) {
return { counts, percentages: counts.map(value => value / requests * 100), requests };
}
// Percent labels live inside the bar segments; a label that does not fit is hidden
// (the exact count and percentage stay in the segment and legend tooltips).
// (the legend carries the request counts; tooltips give both).
function fitCacheMixLabels() {
const bar = $("cache-mix-bar");
if (!bar) return;
@@ -430,6 +430,7 @@ function renderCacheMix(mix, totalRequests) {
segment.style.width = `${pct}%`;
segment.textContent = mix && pct > 0 ? `${fmt(pct, pct < 10 ? 1 : 0)}%` : "";
const detail = mix ? `${name.toUpperCase()}: ${fmt(mix.counts[i])} (${fmt(pct, 1)}%)` : "";
$(`cache-mix-${name}-count`).textContent = mix ? fmt(mix.counts[i]) : "-";
segment.title = detail; $(`cache-mix-${name}-key`).title = detail;
});
fitCacheMixLabels();
+11 -4
View File
@@ -69,7 +69,9 @@
const reason = value => { const key = `cacheReason_${value}`, text = t(key); return text === key ? value : text; };
// Decision counters: label + count + share bar; top three visible, the rest folded into a details toggle.
function reasonsCell(reasons) {
// Folded counters keep their open/closed state across auto-refresh re-renders.
const openReasonRows = new Set();
function reasonsCell(reasons, rowKey = "") {
const td = el("td", undefined, "cache-reasons"), entries = Object.entries(reasons || {}).sort((a, b) => b[1] - a[1]);
if (!entries.length) { td.append(el("span", "-", "cache-reason-none")); return td; }
const max = entries[0][1] || 1, line = ([key, count]) => {
@@ -78,7 +80,7 @@
item.append(el("span", reason(key), "cache-reason-name"), el("b", number(count), "cache-reason-count"), track); item.title = key; return item;
};
entries.slice(0, 3).forEach(entry => td.append(line(entry)));
if (entries.length > 3) { const more = el("details", undefined, "cache-reason-more"); more.append(el("summary", `+${entries.length - 3}`)); entries.slice(3).forEach(entry => more.append(line(entry))); td.append(more); }
if (entries.length > 3) { const more = el("details", undefined, "cache-reason-more"); more.open = openReasonRows.has(rowKey); more.addEventListener("toggle", () => { if (more.open) openReasonRows.add(rowKey); else openReasonRows.delete(rowKey); }); more.append(el("summary", `+${entries.length - 3}`)); entries.slice(3).forEach(entry => more.append(line(entry))); td.append(more); }
return td;
}
function resetLiveChart(selected = scope()) {
@@ -182,7 +184,7 @@
[[row.hits, "cache-outcome-hit"], [row.misses, "cache-outcome-miss"], [row.bypasses, "cache-outcome-bypass"]].forEach(([count, cls], index) => { if (index) outcomes.append(el("span", "/", "cache-outcome-sep")); outcomes.append(el("span", number(count), cls)); });
const ratioPercent = Math.max(0, Math.min(100, (row.hit_ratio || 0) * 100)), ratio = el("td", undefined, "cache-ratio-cell"), meter = el("span", undefined, "cache-ratio-meter"), fill = el("i"); fill.style.width = `${ratioPercent}%`; meter.append(fill); ratio.append(el("span", `${fmt(ratioPercent, 1)}%`, "cache-ratio-value"), meter);
tr.append(outcomes, ratio);
tr.append(reasonsCell(row.reasons)); return tr;
tr.append(reasonsCell(row.reasons, `${row.vhost || ""}\u0000${row.domain || ""}`)); return tr;
}));
const domainEmpty = $("cache-domains-empty");
domainEmpty.textContent = groups.length ? t("cacheNoDomainsFiltered") : t("cacheNoDomains");
@@ -191,7 +193,12 @@
const tr = el("tr"), resident = Number(row.resident_bytes) || 0, disk = Number(row.storage_bytes) || 0, body = Number(row.body_bytes) || 0, storage = resident || disk;
tr.append(el("td", row.vhost || t("cacheLegacy")), el("td", row.domain));
const targetValue = row.target || row.path || "-", target = makeCopyCell(targetValue);
if (row.transformed) target.append(el("span", t("transformBadge"), "cache-transform-badge"));
if (row.transformed) {
// Keep the badge outside the ellipsised text so a long URL never clips it.
const wrap = el("div", undefined, "cache-target-wrap");
wrap.append(el("span", targetValue, "cache-target-text"), el("span", t("transformBadge"), "cache-transform-badge"));
target.replaceChildren(wrap);
}
tr.append(target);
const keyTags = `${row.cache_key || "-"}${row.tags?.length ? ` · ${row.tags.join(", ")}` : ""}`;
tr.append(makeCopyCell(keyTags));
+6
View File
@@ -2246,6 +2246,12 @@ html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .over
background: var(--chart-secondary);
}
#panel-overview .cache-mix-legend b {
margin-left: 1px;
color: var(--text);
font: 500 9px var(--mono);
}
#panel-overview .system-card .system-uptime-meta,
#panel-overview .connection-card .connection-backends-meta {
white-space: nowrap;
+18
View File
@@ -1184,3 +1184,21 @@ html[data-table-density=ultra] .file-log-inline-detail pre {
.transform-reason-list .cache-reason-line { margin: 0; }
.cache-ratio-meter, .cache-ratio-meter>i { border-radius: var(--rp-progress-radius, 0); }
/* Decision counters (0.36.7): name + count on one line, a thin share bar below (relative to the largest counter) */
.cache-reasons .cache-reason-line { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; align-items: baseline; margin: 0 0 6px; white-space: nowrap; }
.cache-reasons .cache-reason-name { overflow: hidden; text-overflow: ellipsis; }
.cache-reasons .cache-reason-track { grid-column: 1 / -1; display: block; height: 2px; margin-top: 3px; background: var(--line); border-radius: var(--rp-progress-radius, 0); overflow: hidden; }
.cache-reasons .cache-reason-track>i { display: block; height: 100%; background: var(--muted); opacity: .7; border-radius: var(--rp-progress-radius, 0); }
.cache-reasons .cache-reason-more { cursor: default; }
.cache-reasons .cache-reason-more>summary { cursor: pointer; display: inline-flex; align-items: center; gap: 5px; margin: 0 0 6px; padding: 1px 6px; border: 1px solid var(--line); border-radius: var(--rp-radius-control, 0); font: 10px/1.5 var(--mono); color: var(--muted); list-style: none; user-select: none; }
.cache-reasons .cache-reason-more>summary::-webkit-details-marker { display: none; }
.cache-reasons .cache-reason-more>summary::before { content: "›"; display: inline-block; font-size: 12px; line-height: 1; transition: transform .15s ease; }
.cache-reasons .cache-reason-more[open]>summary::before { transform: rotate(90deg); }
.cache-reasons .cache-reason-more>summary:hover { color: var(--text); border-color: var(--muted); }
/* Transformed badge (0.36.7): the URL ellipsises, the badge always stays visible */
.cache-target-wrap { display: flex; align-items: center; gap: 6px; min-width: 0; }
.cache-target-text { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cache-target-wrap .cache-transform-badge { flex: 0 0 auto; margin-left: 0; }
+4 -4
View File
@@ -46,10 +46,10 @@
<span class="cache-mix-other" id="cache-mix-other" style="width:0%"></span>
</div>
<small class="cache-mix-legend">
<span class="cache-mix-hit-key" id="cache-mix-hit-key"><i></i>HIT</span>
<span class="cache-mix-miss-key" id="cache-mix-miss-key"><i></i>MISS</span>
<span class="cache-mix-bypass-key" id="cache-mix-bypass-key"><i></i>BYPASS</span>
<span class="cache-mix-other-key" id="cache-mix-other-key"><i></i><span data-i18n="cacheMixOther">OTHER</span></span>
<span class="cache-mix-hit-key" id="cache-mix-hit-key"><i></i>HIT <b id="cache-mix-hit-count">-</b></span>
<span class="cache-mix-miss-key" id="cache-mix-miss-key"><i></i>MISS <b id="cache-mix-miss-count">-</b></span>
<span class="cache-mix-bypass-key" id="cache-mix-bypass-key"><i></i>BYPASS <b id="cache-mix-bypass-count">-</b></span>
<span class="cache-mix-other-key" id="cache-mix-other-key"><i></i><span data-i18n="cacheMixOther">OTHER</span> <b id="cache-mix-other-count">-</b></span>
</small>
</article>
<article class="stat" data-dashboard-widget="memory"><span data-i18n="processMemory">Process
+1 -1
View File
@@ -51,5 +51,5 @@ const events = openapi.paths["/api/events"].get;
const logsFlag = (events.parameters || []).find(parameter => parameter.name === "logs");
assert.equal(logsFlag?.schema?.type, "boolean");
assert.equal(logsFlag?.schema?.default, true);
assert.equal(openapi.info.version, "0.36.6");
assert.equal(openapi.info.version, "0.36.7");
console.log("Panel refresh/API contract checks: OK");