netzbeere-web/scripts/js/interfaces.js

505 lines
15 KiB
JavaScript

/* Pi-hole: A black hole for Internet advertisements
* (c) 2024 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
/* global utils: false, i18n */
"use strict";
i18n.waitForTranslations(() => {
$.ajax({
url: document.body.dataset.apiurl + "/network/gateway",
data: { detailed: true },
}).done(data => {
const intl = new Intl.NumberFormat();
const gateway = data.gateway;
// Get all objects in gateway that has family == "inet"
const inet = gateway.find(obj => obj.family === "inet");
// Get first object in gateway that has family == "inet6"
const inet6 = gateway.find(obj => obj.family === "inet6");
// Create a set of the gateways when they are found
const gateways = new Set();
if (inet !== undefined) {
gateways.add(inet.gateway);
}
if (inet6 !== undefined) {
gateways.add(inet6.gateway);
}
const interfaces = {};
const masterInterfaces = {};
// For each interface in data.interface, create a new object and push it to json
for (const iface of data.interfaces) {
const carrierColor = iface.carrier ? "text-green" : "text-red";
let stateText = iface.state.toUpperCase();
if (stateText === "UNKNOWN" && iface.flags !== undefined && iface.flags.length > 0) {
if (iface.flags.includes("pointopoint")) {
// WireGuards, etc. -> the typo is intentional
stateText = "P2P";
} else if (iface.flags.includes("loopback")) {
// Loopback interfaces
stateText = "LOOPBACK";
}
}
const status = `<span class="${carrierColor}">${stateText}</span>`;
let master = null;
if (iface.master !== undefined) {
// Find interface.master in data.interfaces
master = data.interfaces.find(obj => obj.index === iface.master).name;
}
// Show an icon for indenting slave interfaces
const indentIcon =
master === null ? "" : "<span class='child-interface-icon'>&nbsp;&rdca;</span> ";
const obj = {
text: indentIcon + iface.name + " - " + status,
class: gateways.has(iface.name) ? "text-bold" : null,
icon: master === null ? "fa fa-network-wired fa-fw" : "",
nodes: [],
};
if (master !== null) {
obj.nodes.push({
text:
i18n.t("interfaces.master_interface") +
": <code>" +
utils.escapeHtml(master) +
"</code>",
icon: "fa fa-network-wired fa-fw",
});
if (master in masterInterfaces) {
masterInterfaces[master].push(iface.name);
} else {
masterInterfaces[master] = [iface.name];
}
}
if (iface.speed) {
obj.nodes.push({
text: i18n.tf("interfaces.speed", intl.format(iface.speed)),
icon: "fa fa-tachometer-alt fa-fw",
});
}
if (iface.type !== undefined) {
obj.nodes.push({
text: i18n.t("shared.type") + ": " + utils.escapeHtml(iface.type),
icon: "fa fa-network-wired fa-fw",
});
}
if (iface.flags !== undefined && iface.flags.length > 0) {
obj.nodes.push({
text: i18n.tf("interfaces.flags", utils.escapeHtml(iface.flags.join(", "))),
icon: "fa fa-flag fa-fw",
});
}
if (iface.address !== undefined) {
let extra = "";
if (iface.perm_address !== undefined && iface.perm_address !== iface.address) {
extra =
" (" +
i18n.t("interfaces.permanent") +
": <code>" +
utils.escapeHtml(iface.perm_address) +
"</code>)";
}
obj.nodes.push({
text:
i18n.t("interfaces.hardware_address") +
": <code>" +
utils.escapeHtml(iface.address) +
"</code>" +
extra,
icon: "fa fa-map-marker-alt fa-fw",
});
}
if (iface.addresses !== undefined) {
const addrs = {
text: i18n.tf(
iface.addresses.length === 1
? "interfaces.num_connected_to"
: "interfaces.nums_connected_to",
iface.addresses.length
),
icon: "fa fa-map-marker-alt fa-fw",
nodes: [],
};
for (const addr of iface.addresses) {
let extraaddr = "";
if (addr.prefixlen !== undefined) {
extraaddr += " / <code>" + addr.prefixlen + "</code>";
}
if (addr.address_type !== undefined) {
let familyextra = "";
if (addr.family === "inet") {
familyextra = "IPv4 ";
} else if (addr.family === "inet6") {
familyextra = "IPv6 ";
}
extraaddr += " (" + familyextra + utils.escapeHtml(addr.address_type) + ")";
}
let family = "";
if (addr.family !== undefined) {
family = addr.family + "</code> <code>";
}
const jaddr = {
text:
i18n.t("shared.address") +
": <code>" +
family +
utils.escapeHtml(addr.address) +
"</code>" +
extraaddr,
icon: "fa fa-map-marker-alt fa-fw",
nodes: [],
};
if (addr.local !== undefined) {
jaddr.nodes.push({
text:
i18n.t("interfaces.local") + ": <code>" + utils.escapeHtml(addr.local) + "</code>",
icon: "fa fa-map-marker-alt fa-fw",
});
}
if (addr.broadcast !== undefined) {
jaddr.nodes.push({
text: i18n.tf(
"interfaces.broadcast",
"<code>" + utils.escapeHtml(addr.broadcast) + "</code>"
),
icon: "fa fa-map-marker-alt fa-fw",
});
}
if (addr.scope !== undefined) {
jaddr.nodes.push({
text: i18n.tf("interfaces.scope", utils.escapeHtml(addr.scope)),
icon: "fa fa-map-marker-alt fa-fw",
});
}
if (addr.flags !== undefined && addr.flags.length > 0) {
jaddr.nodes.push({
text: i18n.tf("interfaces.flags", utils.escapeHtml(addr.flags.join(", "))),
icon: "fa fa-map-marker-alt fa-fw",
});
}
if (addr.prefered !== undefined) {
const pref =
addr.prefered === 4_294_967_295
? i18n.t("shared.forever")
: intl.format(addr.prefered) + " s";
jaddr.nodes.push({
text: i18n.tf("interfaces.preferred_lifetime", pref),
icon: "fa fa-clock fa-fw",
});
}
if (addr.valid !== undefined) {
const valid =
addr.valid === 4_294_967_295
? i18n.t("shared.forever")
: intl.format(addr.valid) + " s";
jaddr.nodes.push({
text: i18n.tf("interfaces.valid_lifetime", valid),
icon: "fa fa-clock fa-fw",
});
}
if (addr.cstamp !== undefined) {
jaddr.nodes.push({
text: i18n.tf("interfaces.created", new Date(addr.cstamp * 1000).toLocaleString()),
icon: "fa fa-clock fa-fw",
});
}
if (addr.tstamp !== undefined) {
jaddr.nodes.push({
text: i18n.tf(
"interfaces.last_updated",
new Date(addr.tstamp * 1000).toLocaleString()
),
icon: "fa fa-clock fa-fw",
});
}
addrs.nodes.push(jaddr);
}
obj.nodes.push(addrs);
}
if (iface.stats !== undefined) {
const stats = {
text: i18n.t("interfaces.statistics"),
icon: "fa fa-chart-line fa-fw",
expanded: false,
nodes: [],
};
if (iface.stats.rx_bytes !== undefined) {
stats.nodes.push({
text: i18n.tf(
"interfaces.rx_bytes",
intl.format(iface.stats.rx_bytes.value),
iface.stats.rx_bytes.unit
),
icon: "fa fa-download fa-fw",
});
}
if (iface.stats.tx_bytes !== undefined) {
stats.nodes.push({
text: i18n.tf(
"interfaces.tx_bytes",
intl.format(iface.stats.tx_bytes.value),
iface.stats.tx_bytes.unit
),
icon: "fa fa-upload fa-fw",
});
}
if (iface.stats.rx_packets !== undefined) {
stats.nodes.push({
text: i18n.tf("interfaces.rx_packets", intl.format(iface.stats.rx_packets)),
icon: "fa fa-download fa-fw",
});
}
if (iface.stats.rx_errors !== undefined) {
stats.nodes.push({
text: i18n.tf(
"interfaces.rx_errors",
intl.format(iface.stats.rx_errors),
((iface.stats.rx_errors / iface.stats.rx_packets) * 100).toFixed(1)
),
icon: "fa fa-download fa-fw",
});
}
if (iface.stats.rx_dropped !== undefined) {
stats.nodes.push({
text: i18n.tf(
"interfaces.rx_dropped",
intl.format(iface.stats.rx_dropped),
((iface.stats.rx_dropped / iface.stats.rx_packets) * 100).toFixed(1)
),
icon: "fa fa-download fa-fw",
});
}
if (iface.stats.tx_packets !== undefined) {
stats.nodes.push({
text: i18n.tf("interfaces.tx_packets", intl.format(iface.stats.tx_packets)),
icon: "fa fa-upload fa-fw",
});
}
if (iface.stats.tx_errors !== undefined) {
stats.nodes.push({
text: i18n.tf(
"interfaces.tx_errors",
intl.format(iface.stats.tx_errors),
((iface.stats.tx_errors / iface.stats.tx_packets) * 100).toFixed(1)
),
icon: "fa fa-upload fa-fw",
});
}
if (iface.stats.tx_dropped !== undefined) {
stats.nodes.push({
text: i18n.tf(
"interfaces.tx_dropped",
intl.format(iface.stats.tx_dropped),
((iface.stats.tx_dropped / iface.stats.tx_packets) * 100).toFixed(1)
),
icon: "fa fa-upload fa-fw",
});
}
if (iface.stats.multicast !== undefined) {
stats.nodes.push({
text: i18n.tf("interfaces.multicast", intl.format(iface.stats.multicast)),
icon: "fa fa-broadcast-tower fa-fw",
});
}
if (iface.stats.collisions !== undefined) {
stats.nodes.push({
text: i18n.tf("interfaces.collisions", intl.format(iface.stats.collisions)),
icon: "fa fa-exchange-alt fa-fw",
});
}
obj.nodes.push(stats);
}
const furtherDetails = {
text: i18n.t("interfaces.further_details"),
icon: "fa fa-info-circle fa-fw",
expanded: false,
nodes: [],
};
furtherDetails.nodes.push(
{
text:
i18n.t("interfaces.carrier") +
": " +
(iface.carrier
? `<span class='text-green'>${i18n.t("interfaces.connected")}</span>`
: `<span class='text-red'>${i18n.t("interfaces.disconnected")}</span>`),
icon: "fa fa-link fa-fw",
},
{
text: i18n.tf("interfaces.state", utils.escapeHtml(iface.state.toUpperCase())),
icon: "fa fa-server fa-fw",
}
);
if (iface.parent_dev_name !== undefined) {
let extra = "";
if (iface.parent_dev_bus_name !== undefined) {
extra = " @ " + utils.escapeHtml(iface.parent_dev_bus_name);
}
furtherDetails.nodes.push({
text: i18n.tf(
"interfaces.parent_devices",
"<code>" + utils.escapeHtml(iface.parent_dev_name) + extra + "</code>"
),
icon: "fa fa-network-wired fa-fw",
});
}
if (iface.carrier_changes !== undefined) {
furtherDetails.nodes.push({
text: i18n.tf("interfaces.carrier_changes", intl.format(iface.carrier_changes)),
icon: "fa fa-exchange-alt fa-fw",
});
}
if (iface.broadcast) {
furtherDetails.nodes.push({
text: i18n.tf(
"interfaces.broadcast",
"<code>" + utils.escapeHtml(iface.broadcast) + "</code>"
),
icon: "fa fa-broadcast-tower fa-fw",
});
}
if (iface.mtu) {
let extra = "";
if (iface.min_mtu !== undefined && iface.max_mtu !== undefined) {
extra +=
" (min: " +
intl.format(iface.min_mtu) +
" bytes, max: " +
intl.format(iface.max_mtu) +
" bytes)";
}
furtherDetails.nodes.push({
text: i18n.tf("interfaces.mtu", intl.format(iface.mtu), extra),
icon: "fa fa-arrows-alt-h fa-fw",
});
}
if (iface.txqlen) {
furtherDetails.nodes.push({
text: i18n.tf("interfaces.tx_queue_length", intl.format(iface.txqlen)),
icon: "fa fa-file-upload fa-fw",
});
}
if (iface.promiscuity !== undefined) {
furtherDetails.nodes.push({
text: i18n.tf(
"interfaces.promiscuity_mode",
i18n.t(iface.promiscuity ? "shared.yes" : "shared.no")
),
icon: "fa fa-eye fa-fw",
});
}
if (iface.qdisc !== undefined) {
furtherDetails.nodes.push({
text: i18n.tf("interfaces.scheduler", utils.escapeHtml(iface.qdisc)),
icon: "fa fa-network-wired fa-fw",
});
}
if (furtherDetails.nodes.length > 0) {
obj.nodes.push(furtherDetails);
}
interfaces[iface.name] = obj;
}
// Sort interfaces based on masterInterfaces. If an item is found in
// masterInterfaces, it should be placed after the master interface
const ifaces = Object.keys(interfaces);
const interfaceList = Object.keys(masterInterfaces);
// Add slave interfaces next to master interfaces
for (const master of interfaceList) {
if (master in masterInterfaces) {
for (const slave of masterInterfaces[master]) {
ifaces.splice(ifaces.indexOf(slave), 1);
interfaceList.splice(interfaceList.indexOf(master) + 1, 0, slave);
}
}
}
// Add interfaces that are not slaves at the top of the list (in reverse order)
for (const iface of ifaces.reverse()) {
if (!interfaceList.includes(iface)) {
interfaceList.unshift(iface);
}
}
// Build the tree view
const json = [];
for (const iface of interfaceList) {
json.push(interfaces[iface]);
}
$("#tree").bstreeview({
data: json,
expandIcon: "fa fa-angle-down fa-fw",
collapseIcon: "fa fa-angle-right fa-fw",
parentsMarginLeft: "0",
indent: 2.5,
});
$("#spinner").hide();
// Expand gateway interfaces by default
for (const gw of gateways) {
const div = $("#tree").find("div:contains('" + gw + "')");
div.removeClass("collapsed");
div.next("div").collapse("show");
// Change expand icon to collapse icon
div.find("i:first").removeClass("fa-angle-right").addClass("fa-angle-down");
}
});
});