Enhancement: fallback for missing si network stats (#6367)

This commit is contained in:
shamoon
2026-02-27 10:39:02 -08:00
committed by GitHub
parent 1645c1b8a1
commit d529f81cb4
3 changed files with 97 additions and 3 deletions

View File

@@ -90,17 +90,74 @@ describe("pages/api/widgets/resources", () => {
});
it("returns 404 when requested network interface does not exist", async () => {
si.networkStats.mockResolvedValueOnce([{ iface: "en0" }]);
si.networkStats.mockResolvedValueOnce([{ iface: "en0" }]).mockResolvedValueOnce([
{
iface: "missing",
operstate: "unknown",
rx_bytes: 0,
rx_dropped: 0,
rx_errors: 0,
tx_bytes: 0,
tx_dropped: 0,
tx_errors: 0,
rx_sec: null,
tx_sec: null,
ms: 0,
},
]);
const req = { query: { type: "network", interfaceName: "missing" } };
const res = createMockRes();
await handler(req, res);
expect(si.networkStats).toHaveBeenNthCalledWith(1, "*");
expect(si.networkStats).toHaveBeenNthCalledWith(2, "missing");
expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ error: "Interface not found" });
});
it("falls back to direct named interface query when wildcard enumeration misses it", async () => {
si.networkStats.mockResolvedValueOnce([{ iface: "eth0", rx_bytes: 1 }]).mockResolvedValueOnce([
{
iface: "eno1",
operstate: "up",
rx_bytes: 1000,
rx_dropped: 0,
rx_errors: 0,
tx_bytes: 500,
tx_dropped: 0,
tx_errors: 0,
rx_sec: null,
tx_sec: null,
ms: 0,
},
]);
const req = { query: { type: "network", interfaceName: "eno1" } };
const res = createMockRes();
await handler(req, res);
expect(si.networkStats).toHaveBeenNthCalledWith(1, "*");
expect(si.networkStats).toHaveBeenNthCalledWith(2, "eno1");
expect(res.statusCode).toBe(200);
expect(res.body.interface).toBe("eno1");
expect(res.body.network).toEqual({
iface: "eno1",
operstate: "up",
rx_bytes: 1000,
rx_dropped: 0,
rx_errors: 0,
tx_bytes: 500,
tx_dropped: 0,
tx_errors: 0,
rx_sec: null,
tx_sec: null,
ms: 0,
});
});
it("returns default interface network stats", async () => {
si.networkStats.mockResolvedValueOnce([{ iface: "en0", rx_bytes: 1 }]);
si.networkInterfaceDefault.mockResolvedValueOnce("en0");

View File

@@ -4,6 +4,21 @@ import createLogger from "utils/logger";
const logger = createLogger("resources");
function isMissingNetworkStat(networkData, interfaceName) {
return (
networkData.operstate === "unknown" &&
networkData.rx_bytes === 0 &&
networkData.rx_dropped === 0 &&
networkData.rx_errors === 0 &&
networkData.tx_bytes === 0 &&
networkData.tx_dropped === 0 &&
networkData.tx_errors === 0 &&
networkData.rx_sec === null &&
networkData.tx_sec === null &&
networkData.ms === 0
);
}
export default async function handler(req, res) {
const { type, target, interfaceName = "default" } = req.query;
@@ -64,6 +79,17 @@ export default async function handler(req, res) {
logger.debug("networkData:", JSON.stringify(networkData));
if (interfaceName && interfaceName !== "default") {
networkData = networkData.filter((network) => network.iface === interfaceName).at(0);
if (!networkData) {
// Fallback for e.g. docker where networkStats("*") may not return stats for host interfaces
const directNetworkData = await si.networkStats(interfaceName);
logger.debug("directNetworkData:", JSON.stringify(directNetworkData));
networkData = Array.isArray(directNetworkData) ? directNetworkData.at(0) : null;
// si returns unknown + zeroes when interface truly does not exist
if (!networkData || isMissingNetworkStat(networkData, interfaceName)) {
networkData = null;
}
}
if (!networkData) {
return res.status(404).json({
error: "Interface not found",