|
@@ -41,6 +41,9 @@
|
|
|
<option value="">None (flat)</option>
|
|
<option value="">None (flat)</option>
|
|
|
</select>
|
|
</select>
|
|
|
</label>
|
|
</label>
|
|
|
|
|
+ <label>TP:
|
|
|
|
|
+ <select id="tp"><option value="">All</option></select>
|
|
|
|
|
+ </label>
|
|
|
<details id="cols"><summary>Columns ▾</summary><div id="colbox"></div></details>
|
|
<details id="cols"><summary>Columns ▾</summary><div id="colbox"></div></details>
|
|
|
<label>Rows/page:
|
|
<label>Rows/page:
|
|
|
<select id="size"><option>25</option><option selected>50</option><option>100</option><option value="0">All</option></select>
|
|
<select id="size"><option>25</option><option selected>50</option><option>100</option><option value="0">All</option></select>
|
|
@@ -78,6 +81,15 @@ function cell(r, k) {
|
|
|
// collapsed groups) lives in the URL — survives any reload and is shareable
|
|
// collapsed groups) lives in the URL — survives any reload and is shareable
|
|
|
const P = new URLSearchParams(location.search);
|
|
const P = new URLSearchParams(location.search);
|
|
|
const size0 = +(P.get("size") ?? 50);
|
|
const size0 = +(P.get("size") ?? 50);
|
|
|
|
|
+// distinct TP values from the data, for the exact-TP filter
|
|
|
|
|
+const TPVALS = [...new Set(DATA.map(r => r.tp))].sort((a, b) => a - b);
|
|
|
|
|
+const tpParam = P.get("tp");
|
|
|
|
|
+for (const v of TPVALS) {
|
|
|
|
|
+ const o = document.createElement("option");
|
|
|
|
|
+ o.value = String(v);
|
|
|
|
|
+ o.textContent = String(v);
|
|
|
|
|
+ document.getElementById("tp").appendChild(o);
|
|
|
|
|
+}
|
|
|
const S = {
|
|
const S = {
|
|
|
q: P.get("q") ?? "",
|
|
q: P.get("q") ?? "",
|
|
|
group: P.get("group") ?? "mq",
|
|
group: P.get("group") ?? "mq",
|
|
@@ -88,10 +100,12 @@ const S = {
|
|
|
hidden: new Set((P.get("hidden") ?? "title,image,port").split(",").filter(Boolean)),
|
|
hidden: new Set((P.get("hidden") ?? "title,image,port").split(",").filter(Boolean)),
|
|
|
closed: new Set((P.get("closed") ?? "").split("|").map(decodeURIComponent).filter(Boolean)),
|
|
closed: new Set((P.get("closed") ?? "").split("|").map(decodeURIComponent).filter(Boolean)),
|
|
|
openRow: P.get("open") ?? null,
|
|
openRow: P.get("open") ?? null,
|
|
|
|
|
+ tp: tpParam != null && TPVALS.includes(+tpParam) ? tpParam : "",
|
|
|
};
|
|
};
|
|
|
document.getElementById("q").value = S.q;
|
|
document.getElementById("q").value = S.q;
|
|
|
document.getElementById("group").value = S.group;
|
|
document.getElementById("group").value = S.group;
|
|
|
document.getElementById("size").value = String(S.size);
|
|
document.getElementById("size").value = String(S.size);
|
|
|
|
|
+document.getElementById("tp").value = S.tp;
|
|
|
function syncUrl() {
|
|
function syncUrl() {
|
|
|
const p = new URLSearchParams();
|
|
const p = new URLSearchParams();
|
|
|
if (S.q) p.set("q", S.q);
|
|
if (S.q) p.set("q", S.q);
|
|
@@ -105,6 +119,7 @@ function syncUrl() {
|
|
|
const closed = [...S.closed].map(encodeURIComponent);
|
|
const closed = [...S.closed].map(encodeURIComponent);
|
|
|
if (closed.length) p.set("closed", closed.join("|"));
|
|
if (closed.length) p.set("closed", closed.join("|"));
|
|
|
if (S.openRow) p.set("open", S.openRow);
|
|
if (S.openRow) p.set("open", S.openRow);
|
|
|
|
|
+ if (S.tp) p.set("tp", S.tp);
|
|
|
const qs = p.toString();
|
|
const qs = p.toString();
|
|
|
history.replaceState(null, "", qs ? "?" + qs : location.pathname);
|
|
history.replaceState(null, "", qs ? "?" + qs : location.pathname);
|
|
|
}
|
|
}
|
|
@@ -113,9 +128,10 @@ const esc = s => String(s).replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":
|
|
|
|
|
|
|
|
function filtered() {
|
|
function filtered() {
|
|
|
const q = S.q.toLowerCase();
|
|
const q = S.q.toLowerCase();
|
|
|
- return DATA.filter(r => !q || (r.name + " " + r.title + " " + r.model + " " + r.quant + " " +
|
|
|
|
|
|
|
+ return DATA.filter(r => (!S.tp || String(r.tp) === S.tp) &&
|
|
|
|
|
+ (!q || (r.name + " " + r.title + " " + r.model + " " + r.quant + " " +
|
|
|
r.engine + " " + r.image + " " + r.deploy + " " +
|
|
r.engine + " " + r.image + " " + r.deploy + " " +
|
|
|
- r.reports.map(e => e.date + " " + e.label).join(" ")).toLowerCase().includes(q));
|
|
|
|
|
|
|
+ r.reports.map(e => e.date + " " + e.label).join(" ")).toLowerCase().includes(q)));
|
|
|
}
|
|
}
|
|
|
function sorted(rows) {
|
|
function sorted(rows) {
|
|
|
const gk = { mq: r => r.model + "\0" + r.quant, model: r => r.model,
|
|
const gk = { mq: r => r.model + "\0" + r.quant, model: r => r.model,
|
|
@@ -214,6 +230,7 @@ document.querySelector("tbody").addEventListener("click", e => {
|
|
|
});
|
|
});
|
|
|
document.getElementById("q").addEventListener("input", e => { S.q = e.target.value; S.page = 0; render(); });
|
|
document.getElementById("q").addEventListener("input", e => { S.q = e.target.value; S.page = 0; render(); });
|
|
|
document.getElementById("group").addEventListener("change", e => { S.group = e.target.value; S.closed.clear(); S.page = 0; render(); });
|
|
document.getElementById("group").addEventListener("change", e => { S.group = e.target.value; S.closed.clear(); S.page = 0; render(); });
|
|
|
|
|
+document.getElementById("tp").addEventListener("change", e => { S.tp = e.target.value; S.page = 0; render(); });
|
|
|
document.getElementById("size").addEventListener("change", () => { S.page = 0; render(); });
|
|
document.getElementById("size").addEventListener("change", () => { S.page = 0; render(); });
|
|
|
document.getElementById("prev").addEventListener("click", () => { if (S.page > 0) { S.page--; render(); } });
|
|
document.getElementById("prev").addEventListener("click", () => { if (S.page > 0) { S.page--; render(); } });
|
|
|
document.getElementById("next").addEventListener("click", () => { S.page++; render(); });
|
|
document.getElementById("next").addEventListener("click", () => { S.page++; render(); });
|