Release 3.8.2.11: idle rev-cache, port-safe Connect, single-instance tray UX.
Faster unchanged polls; orphan-core cleanup on listen ports; clearer busy-port errors; cores only from binDir; incremental server list; CSP without Google Fonts; single-instance activates existing window; close-to-tray without kill-others or balloon. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+75
-50
@@ -262,69 +262,94 @@
|
||||
});
|
||||
}
|
||||
|
||||
function pingLabel(pr) {
|
||||
if (pr && pr.ok) {
|
||||
return {
|
||||
cls: "ms " + msClass(pr.ms, true),
|
||||
text: (pr.soft ? "~" : "") + pr.ms + " ms",
|
||||
title: pr.soft ? "soft-up (UDP без ответа) — не выбирается как «Лучший»" : ""
|
||||
};
|
||||
}
|
||||
if (pr && pr.error) {
|
||||
return { cls: "ms bad", text: "—", title: pr.error };
|
||||
}
|
||||
return { cls: "ms", text: "…", title: "" };
|
||||
}
|
||||
|
||||
function ensureServerRow(p) {
|
||||
let row = serverList.querySelector('.server-row[data-name="' + CSS.escape(p.name) + '"]');
|
||||
if (row) return row;
|
||||
row = document.createElement("button");
|
||||
row.type = "button";
|
||||
row.className = "server-row";
|
||||
row.dataset.name = p.name;
|
||||
const left = document.createElement("div");
|
||||
left.className = "left";
|
||||
const nameEl = document.createElement("div");
|
||||
nameEl.className = "name";
|
||||
const sub = document.createElement("div");
|
||||
sub.className = "sub";
|
||||
left.appendChild(nameEl);
|
||||
left.appendChild(sub);
|
||||
const right = document.createElement("div");
|
||||
right.className = "right";
|
||||
const proto = document.createElement("span");
|
||||
proto.className = "proto";
|
||||
const ms = document.createElement("span");
|
||||
ms.className = "ms";
|
||||
right.appendChild(proto);
|
||||
right.appendChild(ms);
|
||||
row.appendChild(left);
|
||||
row.appendChild(right);
|
||||
row.addEventListener("click", () => onServerClick(p.name));
|
||||
row.addEventListener("dblclick", () => onServerConnect(p.name));
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderServerList(activeName) {
|
||||
const active = activeName || profile.value;
|
||||
serverList.innerHTML = "";
|
||||
const all = orderedProfiles();
|
||||
const list = filteredProfiles();
|
||||
if (!all.length) {
|
||||
const keep = new Set();
|
||||
|
||||
if (!all.length || !list.length) {
|
||||
serverList.innerHTML = "";
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "server-empty";
|
||||
empty.textContent = "Нет серверов — добавьте профиль или подписку";
|
||||
serverList.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
if (!list.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "server-empty";
|
||||
empty.textContent = "Ничего не найдено";
|
||||
empty.textContent = !all.length
|
||||
? "Нет серверов — добавьте профиль или подписку"
|
||||
: "Ничего не найдено";
|
||||
serverList.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop empty-state nodes from prior renders.
|
||||
serverList.querySelectorAll(".server-empty").forEach((el) => el.remove());
|
||||
|
||||
list.forEach((p) => {
|
||||
const row = document.createElement("button");
|
||||
row.type = "button";
|
||||
keep.add(p.name);
|
||||
const row = ensureServerRow(p);
|
||||
row.className = "server-row" + (p.name === active ? " active" : "");
|
||||
row.dataset.name = p.name;
|
||||
|
||||
const left = document.createElement("div");
|
||||
left.className = "left";
|
||||
const nameEl = document.createElement("div");
|
||||
nameEl.className = "name";
|
||||
nameEl.textContent = p.name;
|
||||
const sub = document.createElement("div");
|
||||
sub.className = "sub";
|
||||
sub.textContent = p.host || "нет хоста";
|
||||
left.appendChild(nameEl);
|
||||
left.appendChild(sub);
|
||||
|
||||
const right = document.createElement("div");
|
||||
right.className = "right";
|
||||
const proto = document.createElement("span");
|
||||
proto.className = "proto";
|
||||
proto.textContent = (p.protocol || "?").toString();
|
||||
const ms = document.createElement("span");
|
||||
const pr = pingMap[p.name];
|
||||
if (pr && pr.ok) {
|
||||
ms.className = "ms " + msClass(pr.ms, true);
|
||||
ms.textContent = (pr.soft ? "~" : "") + pr.ms + " ms";
|
||||
if (pr.soft) ms.title = "soft-up (UDP без ответа) — не выбирается как «Лучший»";
|
||||
} else if (pr && pr.error) {
|
||||
ms.className = "ms bad";
|
||||
ms.textContent = "—";
|
||||
ms.title = pr.error;
|
||||
} else {
|
||||
ms.className = "ms";
|
||||
ms.textContent = "…";
|
||||
const nameEl = row.querySelector(".name");
|
||||
const sub = row.querySelector(".sub");
|
||||
const proto = row.querySelector(".proto");
|
||||
const ms = row.querySelector(".ms");
|
||||
if (nameEl && nameEl.textContent !== p.name) nameEl.textContent = p.name;
|
||||
const hostText = p.host || "нет хоста";
|
||||
if (sub && sub.textContent !== hostText) sub.textContent = hostText;
|
||||
const protoText = (p.protocol || "?").toString();
|
||||
if (proto && proto.textContent !== protoText) proto.textContent = protoText;
|
||||
const pl = pingLabel(pingMap[p.name]);
|
||||
if (ms) {
|
||||
if (ms.className !== pl.cls) ms.className = pl.cls;
|
||||
if (ms.textContent !== pl.text) ms.textContent = pl.text;
|
||||
if ((ms.title || "") !== (pl.title || "")) ms.title = pl.title || "";
|
||||
}
|
||||
right.appendChild(proto);
|
||||
right.appendChild(ms);
|
||||
serverList.appendChild(row); // re-order if needed
|
||||
});
|
||||
|
||||
row.appendChild(left);
|
||||
row.appendChild(right);
|
||||
row.addEventListener("click", () => onServerClick(p.name));
|
||||
row.addEventListener("dblclick", () => onServerConnect(p.name));
|
||||
serverList.appendChild(row);
|
||||
serverList.querySelectorAll(".server-row").forEach((row) => {
|
||||
if (!keep.has(row.dataset.name)) row.remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user