Fix operator names in WebUI HTML sinks

This commit is contained in:
Herbert Wolverson committed 2026-06-14 14:34:03 -05:00
1 parent c976364ea2
commit 8f11fa4ee9
16 files changed
+248 -86

No files matched your search

@@ -1,6 +1,6 @@
// Obtain URL parameters
import {DirectChannel} from "./pubsub/direct_channels";
import {clearDiv, formatLastSeen, simpleRow, simpleRowHtml, theading} from "./helpers/builders";
import {clearDiv, formatLastSeen, simpleRow, simpleRowTrustedHtml, theading} from "./helpers/builders";
import {
formatRetransmitFraction,
formatRtt,
@@ -1914,14 +1914,14 @@ function renderTopAsnTab() {
row.appendChild(truncatedTrafficCell(rowData.asn_name, "lqos-circuit-traffic-asn-cell"));
row.appendChild(truncatedTrafficCell(rowData.asn_country, "lqos-circuit-traffic-country-cell"));
row.appendChild(simpleRowHtml(formatThroughput(rowData.down_bps, plan.down)));
row.appendChild(simpleRowHtml(formatThroughput(rowData.up_bps, plan.up)));
row.appendChild(simpleRowHtml(formatRttNanos(rowData.rtt_down_nanos)));
row.appendChild(simpleRowHtml(formatRttNanos(rowData.rtt_up_nanos)));
row.appendChild(simpleRowHtml(formatQooScore(rowData.qoo_down)));
row.appendChild(simpleRowHtml(formatQooScore(rowData.qoo_up)));
row.appendChild(simpleRowHtml(rowData.retransmit_down_pct > 0 ? formatRetransmitFraction(rowData.retransmit_down_pct) : "-"));
row.appendChild(simpleRowHtml(rowData.retransmit_up_pct > 0 ? formatRetransmitFraction(rowData.retransmit_up_pct) : "-"));
row.appendChild(simpleRowTrustedHtml(formatThroughput(rowData.down_bps, plan.down)));
row.appendChild(simpleRowTrustedHtml(formatThroughput(rowData.up_bps, plan.up)));
row.appendChild(simpleRowTrustedHtml(formatRttNanos(rowData.rtt_down_nanos)));
row.appendChild(simpleRowTrustedHtml(formatRttNanos(rowData.rtt_up_nanos)));
row.appendChild(simpleRowTrustedHtml(formatQooScore(rowData.qoo_down)));
row.appendChild(simpleRowTrustedHtml(formatQooScore(rowData.qoo_up)));
row.appendChild(simpleRowTrustedHtml(rowData.retransmit_down_pct > 0 ? formatRetransmitFraction(rowData.retransmit_down_pct) : "-"));
row.appendChild(simpleRowTrustedHtml(rowData.retransmit_up_pct > 0 ? formatRetransmitFraction(rowData.retransmit_up_pct) : "-"));
row.appendChild(simpleRow(scaleNumber(rowData.flow_count)));
tbody.appendChild(row);
@@ -2003,18 +2003,18 @@ function renderTrafficTab() {
row.style.opacity = toNumber(rowData.opacity, 1);
row.appendChild(truncatedTrafficCell(rowData.protocol_name, "lqos-circuit-traffic-protocol-cell"));
row.appendChild(simpleRowHtml(formatThroughput(rowData.down_bps, plan.down)));
row.appendChild(simpleRowHtml(formatThroughput(rowData.up_bps, plan.up)));
row.appendChild(simpleRowTrustedHtml(formatThroughput(rowData.down_bps, plan.down)));
row.appendChild(simpleRowTrustedHtml(formatThroughput(rowData.up_bps, plan.up)));
row.appendChild(simpleRow(scaleNumber(rowData.bytes_sent_down)));
row.appendChild(simpleRow(scaleNumber(rowData.bytes_sent_up)));
row.appendChild(simpleRow(scaleNumber(rowData.packets_sent_down)));
row.appendChild(simpleRow(scaleNumber(rowData.packets_sent_up)));
row.appendChild(simpleRowHtml(rowData.retransmit_down_pct > 0 ? formatRetransmitFraction(rowData.retransmit_down_pct) : "-"));
row.appendChild(simpleRowHtml(rowData.retransmit_up_pct > 0 ? formatRetransmitFraction(rowData.retransmit_up_pct) : "-"));
row.appendChild(simpleRowHtml(formatRttNanos(rowData.rtt_down_nanos)));
row.appendChild(simpleRowHtml(formatRttNanos(rowData.rtt_up_nanos)));
row.appendChild(simpleRowHtml(formatQooScore(rowData.qoo_down)));
row.appendChild(simpleRowHtml(formatQooScore(rowData.qoo_up)));
row.appendChild(simpleRowTrustedHtml(rowData.retransmit_down_pct > 0 ? formatRetransmitFraction(rowData.retransmit_down_pct) : "-"));
row.appendChild(simpleRowTrustedHtml(rowData.retransmit_up_pct > 0 ? formatRetransmitFraction(rowData.retransmit_up_pct) : "-"));
row.appendChild(simpleRowTrustedHtml(formatRttNanos(rowData.rtt_down_nanos)));
row.appendChild(simpleRowTrustedHtml(formatRttNanos(rowData.rtt_up_nanos)));
row.appendChild(simpleRowTrustedHtml(formatQooScore(rowData.qoo_down)));
row.appendChild(simpleRowTrustedHtml(formatQooScore(rowData.qoo_up)));
row.appendChild(truncatedTrafficCell(rowData.asn_name, "lqos-circuit-traffic-asn-cell"));
row.appendChild(truncatedTrafficCell(rowData.asn_country, "lqos-circuit-traffic-country-cell"));
row.appendChild(simpleRow(rowData.remote_ip));
@@ -2161,7 +2161,10 @@ function initialDevices(circuits) {
let name = document.createElement("h5");
name.classList.add("redactable");
name.innerHTML = "<i class='fa fa-computer'></i> " + circuit.device_name;
const deviceIcon = document.createElement("i");
deviceIcon.classList.add("fa", "fa-computer");
name.appendChild(deviceIcon);
name.appendChild(document.createTextNode(` ${circuit.device_name ?? ""}`));
d.appendChild(name);
let infoTableWrap = document.createElement("div");
@@ -2181,7 +2184,7 @@ function initialDevices(circuits) {
td.classList.add("table-value-cell");
td.classList.add("redactable");
td.colSpan = 2;
td.innerHTML = circuit.mac;
td.textContent = circuit.mac ?? "";
tr.appendChild(td);
tbody.appendChild(tr);
@@ -2194,7 +2197,7 @@ function initialDevices(circuits) {
td = document.createElement("td");
td.classList.add("table-value-cell");
td.colSpan = 2;
td.innerHTML = circuit.comment;
td.textContent = circuit.comment ?? "";
tr2.appendChild(td);
tbody.appendChild(tr2);
@@ -97,17 +97,22 @@ function loadUsers() {
const tbody = $('<tbody>');
users.forEach(user => {
const actions = $('<td>');
const editButton = $('<button type="button" class="btn btn-sm btn-primary edit-user">')
.data('username', user.username)
.append($('<i class="fa fa-edit">'))
.append(document.createTextNode(' Edit'));
const deleteButton = $('<button type="button" class="btn btn-sm btn-danger delete-user">')
.data('username', user.username)
.append($('<i class="fa fa-trash">'))
.append(document.createTextNode(' Delete'));
actions.append(editButton, ' ', deleteButton);
const row = $('<tr>')
.append(`<td>${user.username}</td>`)
.append(`<td>${user.role}</td>`)
.append(`<td>
<button class="btn btn-sm btn-primary edit-user" data-username="${user.username}">
<i class="fa fa-edit"></i> Edit
</button>
<button class="btn btn-sm btn-danger delete-user" data-username="${user.username}">
<i class="fa fa-trash"></i> Delete
</button>
</td>`);
.append($('<td>').text(user.username))
.append($('<td>').text(user.role))
.append(actions);
tbody.append(row);
});
@@ -1,5 +1,5 @@
import {BaseDashlet} from "../lq_js_common/dashboard/base_dashlet";
import {clearDashDiv, simpleRowHtml, theading} from "../helpers/builders";
import {clearDashDiv, simpleRowTrustedHtml, theading} from "../helpers/builders";
import {formatRtt, formatPercent} from "../helpers/scaling";
import {redactCell} from "../helpers/redact";
import {TimedCache} from "../lq_js_common/helpers/timed_cache";
@@ -87,9 +87,9 @@ export class CircuitCapacityDash extends BaseDashlet {
linkCol.appendChild(link);
row.appendChild(linkCol);
row.appendChild(simpleRowHtml(formatPercent(c.capacity[0]*100)));
row.appendChild(simpleRowHtml(formatPercent(c.capacity[1]*100)));
row.appendChild(simpleRowHtml(formatRtt(c.rtt)));
row.appendChild(simpleRowTrustedHtml(formatPercent(c.capacity[0]*100)));
row.appendChild(simpleRowTrustedHtml(formatPercent(c.capacity[1]*100)));
row.appendChild(simpleRowTrustedHtml(formatRtt(c.rtt)));
tbody.appendChild(row);
count++;
@@ -1,5 +1,5 @@
import {BaseDashlet} from "../lq_js_common/dashboard/base_dashlet";
import {clearDiv, simpleRow, simpleRowHtml, theading} from "../helpers/builders";
import {clearDiv, simpleRow, simpleRowTrustedHtml, theading} from "../helpers/builders";
import {get_ws_client} from "../pubsub/ws";
const wsClient = get_ws_client();
@@ -74,7 +74,7 @@ export class LtsShaperStatus extends BaseDashlet {
} else if (row.last_seen_seconds_ago > 120) {
color = "orange";
}
tr.appendChild(simpleRowHtml(`<span style="color: ${color}">■</span>`));
tr.appendChild(simpleRowTrustedHtml(`<span style="color: ${color}">■</span>`));
tr.appendChild(simpleRow(row.name));
tr.appendChild(simpleRow(row.last_seen_seconds_ago + "s"));
tbody.appendChild(tr);
@@ -1,4 +1,11 @@
import {clearDashDiv, simpleRow, simpleRowHtml, theading, TopNTableFromMsgData} from "../helpers/builders";
import {
clearDashDiv,
simpleLinkRow,
simpleRow,
simpleRowTrustedHtml,
theading,
TopNTableFromMsgData,
} from "../helpers/builders";
import {TimedCache} from "../lq_js_common/helpers/timed_cache";
import {periodNameToSeconds} from "../helpers/time_periods";
import {formatRetransmit, formatRtt} from "../helpers/scaling";
@@ -94,15 +101,19 @@ export class Top10Downloaders extends DashletBaseInsight {
//console.log(row);
let tr = document.createElement("tr");
tr.classList.add("small");
tr.appendChild(simpleRowHtml("<a href='circuit.html?circuit=" + row.circuit_hash + "' class='redactable'>" + row.circuit_name + "</a>"));
tr.appendChild(simpleLinkRow(
`circuit.html?circuit=${encodeURIComponent(row.circuit_hash ?? "")}`,
row.circuit_name,
true,
));
tr.appendChild(simpleRow(scaleNumber(toNumber(row.bytes_down, 0) * 1000000, 0)));
if (row.rtt !== null) {
tr.appendChild(simpleRowHtml(formatRtt(row.rtt)));
tr.appendChild(simpleRowTrustedHtml(formatRtt(row.rtt)));
} else {
tr.appendChild(simpleRow("-"));
}
if (row.rxmit !== null) {
tr.appendChild(simpleRowHtml(formatRetransmit(row.rxmit)));
tr.appendChild(simpleRowTrustedHtml(formatRetransmit(row.rxmit)));
} else {
tr.appendChild(simpleRow("-"));
}
@@ -1,4 +1,4 @@
import {clearDashDiv, simpleRow, simpleRowHtml, theading} from "../helpers/builders";
import {clearDashDiv, simpleRow, simpleRowTrustedHtml, theading} from "../helpers/builders";
import {scaleNumber, toNumber} from "../lq_js_common/helpers/scaling";
import {formatRtt} from "../helpers/scaling";
import {TrimToFit} from "../lq_js_common/helpers/text_utils";
@@ -202,27 +202,27 @@ export class Top10FlowsBytes extends DashletBaseInsight {
tr.classList.add("small");
tr.appendChild(simpleRow(row.circuit_name || "", true));
tr.appendChild(simpleRow(row.protocol));
tr.appendChild(simpleRowHtml(scaleNumber(row.bytes_down)));
tr.appendChild(simpleRowHtml(scaleNumber(row.bytes_up)));
tr.appendChild(simpleRowTrustedHtml(scaleNumber(row.bytes_down)));
tr.appendChild(simpleRowTrustedHtml(scaleNumber(row.bytes_up)));
if (row.rtt_down === null) {
tr.appendChild(simpleRowHtml("-"));
tr.appendChild(simpleRowTrustedHtml("-"));
} else {
tr.appendChild(simpleRowHtml(formatRtt(row.rtt_down)));
tr.appendChild(simpleRowTrustedHtml(formatRtt(row.rtt_down)));
}
if (row.rtt_up === null) {
tr.appendChild(simpleRowHtml("-"));
tr.appendChild(simpleRowTrustedHtml("-"));
} else {
tr.appendChild(simpleRowHtml(formatRtt(row.rtt_up)));
tr.appendChild(simpleRowTrustedHtml(formatRtt(row.rtt_up)));
}
if (row.rxmit_down === null) {
tr.appendChild(simpleRowHtml("-"));
tr.appendChild(simpleRowTrustedHtml("-"));
} else {
tr.appendChild(simpleRowHtml(formatRetransmit(row.rxmit_down)));
tr.appendChild(simpleRowTrustedHtml(formatRetransmit(row.rxmit_down)));
}
if (row.rxmit_up === null) {
tr.appendChild(simpleRowHtml("-"));
tr.appendChild(simpleRowTrustedHtml("-"));
} else {
tr.appendChild(simpleRowHtml(formatRetransmit(row.rxmit_up)));
tr.appendChild(simpleRowTrustedHtml(formatRetransmit(row.rxmit_up)));
}
const asnCell = document.createElement("td");
asnCell.classList.add("lqos-asn-cell");
@@ -1,4 +1,4 @@
import {clearDiv, simpleRowHtml, theading} from "../helpers/builders";
import {clearDiv, simpleRowTrustedHtml, theading} from "../helpers/builders";
import {formatThroughput, formatRetransmit, formatCakeStat} from "../helpers/scaling";
import {toNumber} from "../lq_js_common/helpers/scaling";
import {DashletBaseInsight} from "./insight_dashlet_base";
@@ -89,8 +89,8 @@ export class TopTreeSummary extends DashletBaseInsight {
const tpDown = toNumber(r[1].current_throughput[0], 0) * 8;
const tpUp = toNumber(r[1].current_throughput[1], 0) * 8;
const maxThroughput = effectiveMax(r[1]);
row.appendChild(simpleRowHtml(formatThroughput(tpDown, maxThroughput[0])));
row.appendChild(simpleRowHtml(formatThroughput(tpUp, maxThroughput[1])));
row.appendChild(simpleRowTrustedHtml(formatThroughput(tpDown, maxThroughput[0])));
row.appendChild(simpleRowTrustedHtml(formatThroughput(tpUp, maxThroughput[1])));
const tcpPacketsDown = retransmitPacketsForNode(r[1], 0);
const tcpPacketsUp = retransmitPacketsForNode(r[1], 1);
@@ -98,19 +98,19 @@ export class TopTreeSummary extends DashletBaseInsight {
const retransmitsUp = toNumber(r[1].current_retransmits[1], 0);
if (tcpPacketsDown > 0) {
row.appendChild(simpleRowHtml(formatRetransmit(retransmitsDown / tcpPacketsDown)))
row.appendChild(simpleRowTrustedHtml(formatRetransmit(retransmitsDown / tcpPacketsDown)))
} else {
row.appendChild(simpleRowHtml(""));
row.appendChild(simpleRowTrustedHtml(""));
}
if (tcpPacketsUp > 0) {
row.appendChild(simpleRowHtml(formatRetransmit(retransmitsUp / tcpPacketsUp)))
row.appendChild(simpleRowTrustedHtml(formatRetransmit(retransmitsUp / tcpPacketsUp)))
} else {
row.appendChild(simpleRowHtml(""));
row.appendChild(simpleRowTrustedHtml(""));
}
row.appendChild(simpleRowHtml(formatCakeStat(r[1].current_marks[0])))
row.appendChild(simpleRowHtml(formatCakeStat(r[1].current_marks[1])))
row.appendChild(simpleRowHtml(formatCakeStat(r[1].current_drops[0])))
row.appendChild(simpleRowHtml(formatCakeStat(r[1].current_drops[1])))
row.appendChild(simpleRowTrustedHtml(formatCakeStat(r[1].current_marks[0])))
row.appendChild(simpleRowTrustedHtml(formatCakeStat(r[1].current_marks[1])))
row.appendChild(simpleRowTrustedHtml(formatCakeStat(r[1].current_drops[0])))
row.appendChild(simpleRowTrustedHtml(formatCakeStat(r[1].current_drops[1])))
target.appendChild(row);
});
}
@@ -1,4 +1,4 @@
import {clearDashDiv, simpleRowHtml, theading} from "../helpers/builders";
import {clearDashDiv, simpleRowTrustedHtml, theading} from "../helpers/builders";
import {formatRtt, formatPercent} from "../helpers/scaling";
import {DashletBaseInsight} from "./insight_dashlet_base";
@@ -74,9 +74,9 @@ export class TreeCapacityDash extends DashletBaseInsight {
linkCol.appendChild(link);
row.appendChild(linkCol);
row.appendChild(simpleRowHtml(formatPercent(down*100)));
row.appendChild(simpleRowHtml(formatPercent(up*100)));
row.appendChild(simpleRowHtml(formatRtt(node.rtt)));
row.appendChild(simpleRowTrustedHtml(formatPercent(down*100)));
row.appendChild(simpleRowTrustedHtml(formatPercent(up*100)));
row.appendChild(simpleRowTrustedHtml(formatRtt(node.rtt)));
tbody.appendChild(row);
});
@@ -1,4 +1,11 @@
import {clearDashDiv, simpleRow, simpleRowHtml, theading, TopNTableFromMsgData} from "../helpers/builders";
import {
clearDashDiv,
simpleLinkRow,
simpleRow,
simpleRowTrustedHtml,
theading,
TopNTableFromMsgData,
} from "../helpers/builders";
import {TimedCache} from "../lq_js_common/helpers/timed_cache";
import {periodNameToSeconds} from "../helpers/time_periods";
import {formatRetransmit, formatRtt} from "../helpers/scaling";
@@ -94,15 +101,19 @@ export class Worst10Downloaders extends DashletBaseInsight {
//console.log(row);
let tr = document.createElement("tr");
tr.classList.add("small");
tr.appendChild(simpleRowHtml("<a href='circuit.html?circuit=" + row.circuit_hash + "' class='redactable'>" + row.circuit_name + "</a>"));
tr.appendChild(simpleLinkRow(
`circuit.html?circuit=${encodeURIComponent(row.circuit_hash ?? "")}`,
row.circuit_name,
true,
));
tr.appendChild(simpleRow(scaleNumber(toNumber(row.bytes_down, 0) * 1000000, 0)));
if (row.rtt !== null) {
tr.appendChild(simpleRowHtml(formatRtt(row.rtt)));
tr.appendChild(simpleRowTrustedHtml(formatRtt(row.rtt)));
} else {
tr.appendChild(simpleRow("-"));
}
if (row.rxmit !== null) {
tr.appendChild(simpleRowHtml(formatRetransmit(row.rxmit)));
tr.appendChild(simpleRowTrustedHtml(formatRetransmit(row.rxmit)));
} else {
tr.appendChild(simpleRow("-"));
}
@@ -1,4 +1,11 @@
import {clearDashDiv, simpleRow, simpleRowHtml, theading, TopNTableFromMsgData} from "../helpers/builders";
import {
clearDashDiv,
simpleLinkRow,
simpleRow,
simpleRowTrustedHtml,
theading,
TopNTableFromMsgData,
} from "../helpers/builders";
import {TimedCache} from "../lq_js_common/helpers/timed_cache";
import {periodNameToSeconds} from "../helpers/time_periods";
import {formatRetransmit, formatRtt, retransmitFractionFromSample} from "../helpers/scaling";
@@ -98,15 +105,19 @@ export class Worst10Retransmits extends DashletBaseInsight {
//console.log(row);
let tr = document.createElement("tr");
tr.classList.add("small");
tr.appendChild(simpleRowHtml("<a href='circuit.html?circuit=" + row.circuit_hash + "' class='redactable'>" + row.circuit_name + "</a>"));
tr.appendChild(simpleLinkRow(
`circuit.html?circuit=${encodeURIComponent(row.circuit_hash ?? "")}`,
row.circuit_name,
true,
));
tr.appendChild(simpleRow(scaleNumber(toNumber(row.bytes_down, 0) * 1000000, 0)));
if (row.rtt !== null) {
tr.appendChild(simpleRowHtml(formatRtt(row.rtt)));
tr.appendChild(simpleRowTrustedHtml(formatRtt(row.rtt)));
} else {
tr.appendChild(simpleRow("-"));
}
if (row.rxmit !== null) {
tr.appendChild(simpleRowHtml(formatRetransmit(row.rxmit)));
tr.appendChild(simpleRowTrustedHtml(formatRetransmit(row.rxmit)));
} else {
tr.appendChild(simpleRow("-"));
}
@@ -7,6 +7,7 @@ import {
import {scaleNanos} from "../lq_js_common/helpers/scaling";
import {redactCell} from "./redact";
import {disposeTooltipsWithin, enableTooltipsWithin} from "../lq_js_common/helpers/tooltips";
export {safeRelativeHref, simpleLinkRow} from "./safe_dom.mjs";
export function heading5Icon(icon, text) {
let h5 = document.createElement("h5");
@@ -42,7 +43,7 @@ export function simpleRow(text, redact=false) {
return td;
}
export function simpleRowHtml(text) {
export function simpleRowTrustedHtml(text) {
let td = document.createElement("td");
td.innerHTML = text;
return td;
@@ -0,0 +1,75 @@
import assert from "node:assert/strict";
import test from "node:test";
import {safeRelativeHref, simpleLinkRow} from "./safe_dom.mjs";
class FakeClassList {
constructor() {
this.values = [];
}
add(...classes) {
this.values.push(...classes);
}
contains(className) {
return this.values.includes(className);
}
}
class FakeElement {
constructor(tagName) {
this.tagName = tagName.toUpperCase();
this.children = [];
this.classList = new FakeClassList();
this._textContent = "";
this._href = "";
}
appendChild(child) {
this.children.push(child);
}
set href(value) {
this._href = String(value);
}
get href() {
return this._href;
}
set textContent(value) {
this._textContent = String(value);
}
get textContent() {
return this._textContent;
}
}
test("safeRelativeHref rejects scriptable URL protocols", () => {
assert.equal(safeRelativeHref("javascript:alert(1)"), "#");
assert.equal(safeRelativeHref(" data:text/html,boom"), "#");
assert.equal(safeRelativeHref("https://example.invalid/circuit"), "#");
assert.equal(safeRelativeHref("//example.invalid/circuit"), "#");
assert.equal(safeRelativeHref("circuit.html?circuit=123"), "circuit.html?circuit=123");
});
test("simpleLinkRow renders operator text as text content", () => {
globalThis.document = {
createElement: (tagName) => new FakeElement(tagName),
};
const row = simpleLinkRow(
"javascript:alert(1)",
"<img src=x onerror=alert(1)>",
true,
);
const link = row.children[0];
assert.equal(row.tagName, "TD");
assert.equal(link.tagName, "A");
assert.equal(link.href, "#");
assert.equal(link.textContent, "<img src=x onerror=alert(1)>");
assert.equal(link.classList.contains("redactable"), true);
});
@@ -0,0 +1,20 @@
export function safeRelativeHref(href) {
const value = String(href ?? "");
const trimmed = value.trimStart();
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || trimmed.startsWith("//")) {
return "#";
}
return value;
}
export function simpleLinkRow(href, text, redact=false) {
let td = document.createElement("td");
let link = document.createElement("a");
link.href = safeRelativeHref(href);
link.textContent = text ?? "";
if (redact) {
link.classList.add("redactable");
}
td.appendChild(link);
return td;
}
@@ -1,4 +1,4 @@
import {clearDiv} from "./helpers/builders";
import {clearDiv, safeRelativeHref} from "./helpers/builders";
import {initRedact} from "./helpers/redact";
import {initDayNightMode} from "./helpers/dark_mode";
import {initColorBlind} from "./helpers/colorblind";
@@ -794,11 +794,23 @@ function renderSearchResults(data) {
let c = document.createElement("td");
if (item.Circuit !== undefined) {
c.innerHTML = "<a class='nav-link redactable' href='/circuit.html?id=" + encodeURI(item.Circuit.id) + "'><i class='fa fa-user'></i> " + item.Circuit.name + "</a>";
c.appendChild(searchResultLink(
`/circuit.html?id=${encodeURIComponent(item.Circuit.id ?? "")}`,
["fa", "fa-user"],
item.Circuit.name,
));
} else if (item.Device !== undefined) {
c.innerHTML = "<a class='nav-link redactable' href='/circuit.html?id=" + encodeURI(item.Device.circuit_id) + "'><i class='fa fa-computer'></i> " + item.Device.name + "</a>";
c.appendChild(searchResultLink(
`/circuit.html?id=${encodeURIComponent(item.Device.circuit_id ?? "")}`,
["fa", "fa-computer"],
item.Device.name,
));
} else if (item.Site !== undefined) {
c.innerHTML = "<a class='nav-link redactable' href='/tree.html?parent=" + item.Site.idx + "'><i class='fa fa-building'></i> " + item.Site.name + "</a>";
c.appendChild(searchResultLink(
`/tree.html?parent=${encodeURIComponent(item.Site.idx ?? "")}`,
["fa", "fa-building"],
item.Site.name,
));
} else {
console.log(item);
c.innerText = item;
@@ -814,6 +826,18 @@ function renderSearchResults(data) {
searchResults.appendChild(wrap);
}
function searchResultLink(href, iconClasses, label) {
const link = document.createElement("a");
link.classList.add("nav-link", "redactable");
link.href = safeRelativeHref(href);
const icon = document.createElement("i");
icon.classList.add(...iconClasses);
link.appendChild(icon);
link.appendChild(document.createTextNode(` ${label ?? ""}`));
return link;
}
function doSearch(search) {
if (search.length > 2) {
lastSearchTerm = search;
@@ -1,4 +1,4 @@
import {clearDiv, clientTableHeader, formatLastSeen, simpleRow, simpleRowHtml, theading} from "./helpers/builders";
import {clearDiv, clientTableHeader, formatLastSeen, simpleRow, simpleRowTrustedHtml, theading} from "./helpers/builders";
import {
formatCakeStat, formatCakeStatPercent,
formatRetransmit, formatRetransmitFraction, retransmitFractionFromSample,
@@ -1870,22 +1870,22 @@ function renderAttachedCircuitsRows(rows) {
tr.appendChild(ipCell);
tr.appendChild(simpleRow(formatLastSeen(toNumber(circuit.last_seen_nanos, 0))));
tr.appendChild(simpleRowHtml(formatThroughput(toNumber(circuit.bytes_per_second?.down, 0) * 8, toNumber(circuit.plan_mbps?.down, 0))));
tr.appendChild(simpleRowHtml(formatThroughput(toNumber(circuit.bytes_per_second?.up, 0) * 8, toNumber(circuit.plan_mbps?.up, 0))));
tr.appendChild(simpleRowTrustedHtml(formatThroughput(toNumber(circuit.bytes_per_second?.down, 0) * 8, toNumber(circuit.plan_mbps?.down, 0))));
tr.appendChild(simpleRowTrustedHtml(formatThroughput(toNumber(circuit.bytes_per_second?.up, 0) * 8, toNumber(circuit.plan_mbps?.up, 0))));
if (toNumber(circuit.rtt_current_p50_nanos?.down, 0) > 0) {
tr.appendChild(simpleRowHtml(formatRtt(toNumber(circuit.rtt_current_p50_nanos?.down, 0) / 1_000_000)));
tr.appendChild(simpleRowTrustedHtml(formatRtt(toNumber(circuit.rtt_current_p50_nanos?.down, 0) / 1_000_000)));
} else {
tr.appendChild(simpleRow("-"));
}
if (toNumber(circuit.rtt_current_p50_nanos?.up, 0) > 0) {
tr.appendChild(simpleRowHtml(formatRtt(toNumber(circuit.rtt_current_p50_nanos?.up, 0) / 1_000_000)));
tr.appendChild(simpleRowTrustedHtml(formatRtt(toNumber(circuit.rtt_current_p50_nanos?.up, 0) / 1_000_000)));
} else {
tr.appendChild(simpleRow("-"));
}
tr.appendChild(simpleRowHtml(formatRetransmit(retransmitFractionFromSample(circuit.tcp_retransmit_sample?.down))));
tr.appendChild(simpleRowHtml(formatRetransmit(retransmitFractionFromSample(circuit.tcp_retransmit_sample?.up))));
tr.appendChild(simpleRowTrustedHtml(formatRetransmit(retransmitFractionFromSample(circuit.tcp_retransmit_sample?.down))));
tr.appendChild(simpleRowTrustedHtml(formatRetransmit(retransmitFractionFromSample(circuit.tcp_retransmit_sample?.up))));
tbody.appendChild(tr);
});
@@ -10,6 +10,7 @@ fi
echo "[test-node-manager] Running frontend contract tests..."
node --test \
"${SCRIPT_DIR}/src/helpers/builders.test.mjs" \
"${SCRIPT_DIR}/src/config/shaped_device_wire.test.mjs" \
"${SCRIPT_DIR}/src/config/config_interface_mtu.test.mjs" \
"${SCRIPT_DIR}/src/config/ssl_redirect.test.mjs" \