This commit is contained in:
Mateusz Gruszczyński committed 2026-10-07 13:40:21 +02:00
1 parent 3a2eb4fe79
commit e0194fbaff
63 files changed
+1553 -379

No files matched your search

+15 -2
View File
@@ -2,8 +2,13 @@
// Layer-4 stream management mirrors the vhost workflow while keeping TCP/UDP
// listeners separate from the HTTP policy stack.
(() => {
let editing = null, revision = null, dirty = false, initialized = false;
const sortColumns = ["name", "id", "protocol", "listen", "upstream", "status", "maxConnections", "idleTimeout"];
let editing = null, revision = null, dirty = false, initialized = false, listPrefs = { sort: "name", descending: false };
const chartHistory = new Map();
function normalizeListPrefs(preferences) { return { sort: sortColumns.includes(preferences?.stream_list_sort) ? preferences.stream_list_sort : "name", descending: preferences?.stream_list_descending === true }; }
function syncSortDirection() { const control = $("stream-sort-direction"); if (!control) return; control.textContent = listPrefs.descending ? "↓" : "↑"; control.setAttribute("aria-label", t(listPrefs.descending ? "backendSortDescending" : "backendSortAscending")); }
function applyPreferences(preferences) { listPrefs = normalizeListPrefs(preferences); if ($("stream-sort")) $("stream-sort").value = listPrefs.sort; syncSortDirection(); render(); }
async function saveListPrefs() { if (!authenticated) return; try { listPrefs = normalizeListPrefs(await window.rustProxyPreferences.update({ stream_list_sort: listPrefs.sort, stream_list_descending: listPrefs.descending })); if ($("stream-sort")) $("stream-sort").value = listPrefs.sort; syncSortDirection(); } catch (error) { if (error.status !== 401) toast(error.message, true); } }
const dialog = $("stream-dialog"), form = $("stream-form"), container = $("stream-fields");
const text = (tag, key, cls) => { const node = el(tag, t(key), cls); node.dataset.i18n = key; return node; };
const button = (key, callback, cls = "quiet") => { const node = text("button", key, cls); node.type = "button"; node.addEventListener("click", callback); return node; };
@@ -102,6 +107,12 @@
for (const id of chartHistory.keys()) if (!configuredIds.has(id)) chartHistory.delete(id);
const query = ($("stream-search")?.value || "").trim().toLowerCase();
const streams = configured.filter(stream => !query || [stream.id, stream.name, stream.protocol, stream.listen, stream.upstream].filter(Boolean).join(" ").toLowerCase().includes(query));
const active = new Map((latestStatus?.streams?.active || []).map(item => [item.id, item]));
const sortValue = stream => {
const live = active.get(stream.id), same = live && JSON.stringify(live) === JSON.stringify(stream);
return ({ name: String(stream.name || stream.id || "").toLowerCase(), id: String(stream.id || "").toLowerCase(), protocol: String(stream.protocol || "tcp"), listen: String(stream.listen || "").toLowerCase(), upstream: String(stream.upstream || "").toLowerCase(), status: stream.enabled === false ? 2 : same ? 0 : 1, maxConnections: Number(stream.max_connections || 0), idleTimeout: Number(stream.idle_timeout_seconds || 0) })[listPrefs.sort];
};
streams.sort((left, right) => { const a = sortValue(left), b = sortValue(right); const result = typeof a === "number" && typeof b === "number" ? a - b : String(a).localeCompare(String(b), locale(), { numeric: true, sensitivity: "base" }); return listPrefs.descending ? -result : result; });
const cards = streams.map(stream => {
const box = el("article", undefined, "vhost-card stream-card"); box.dataset.streamCard = stream.id;
const head = el("div", undefined, "vhost-card-head"), identity = el("div", undefined, "vhost-card-identity"), title = el("div", undefined, "vhost-title-line");
@@ -178,10 +189,12 @@
$("stream-new").addEventListener("click", () => { window.rustProxyDrafts.prepare(); load(); });
$("stream-listener-add").addEventListener("click", () => { setPanel("settings"); window.rustProxySettings?.select("listeners"); });
$("stream-search").addEventListener("input", render);
$("stream-sort")?.addEventListener("change", event => { listPrefs.sort = event.target.value; void saveListPrefs(); render(); });
$("stream-sort-direction")?.addEventListener("click", () => { listPrefs.descending = !listPrefs.descending; syncSortDirection(); void saveListPrefs(); render(); });
$("stream-close").addEventListener("click", closeEditor);
$("stream-reset").addEventListener("click", async () => { if (await allowDiscard()) load(editing ? structuredClone((envelope?.config.streams || []).find(item => item.id === editing) || defaults()) : defaults()); });
dialog.addEventListener("cancel", event => { event.preventDefault(); closeEditor(); });
window.rustProxyDrafts.register("stream", { labelKey: "streams", isOpen: () => dialog.open, isDirty: () => dirty, suspend: suspendDraft, restore: restoreDraft, close: () => { dirty = false; if (dialog.open) dialog.close(); } });
window.rustProxyStreams = { render, renderStatus, reset() { dialog.close(); editing = null; revision = null; dirty = false; initialized = false; chartHistory.clear(); container.replaceChildren(); $("stream-search").value = ""; $("stream-list").replaceChildren(); } };
window.rustProxyStreams = { render, renderStatus, applyPreferences, reset() { dialog.close(); editing = null; revision = null; dirty = false; initialized = false; listPrefs = { sort: "name", descending: false }; chartHistory.clear(); container.replaceChildren(); $("stream-search").value = ""; $("stream-list").replaceChildren(); if ($("stream-sort")) $("stream-sort").value = listPrefs.sort; syncSortDirection(); } };
if (envelope) render(); if (latestStatus) renderStatus(latestStatus);
})();