Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue #353 and use swconfig to get port flow #355

Open
wants to merge 2 commits into
base: openwrt-21.02
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 158 additions & 130 deletions package/emortal/autocore/files/generic/21_ethinfo.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,54 @@
'use strict';
'require baseclass';
'require fs';
'require rpc';
'require network';

var callSwconfigFeatures = rpc.declare({
object: 'luci',
method: 'getSwconfigFeatures',
params: ['switch'],
expect: { '': {} }
});

var callSwconfigPortState = rpc.declare({
let callSwconfigPortState = rpc.declare({
object: 'luci',
method: 'getSwconfigPortState',
params: ['switch'],
expect: { result: [] }
});

var callLuciBoardJSON = rpc.declare({
let callLuciBoardJSON = rpc.declare({
object: 'luci-rpc',
method: 'getBoardJSON',
expect: { '': {} }
});

var callLuciNetworkDevices = rpc.declare({
let callLuciNetworkDevices = rpc.declare({
object: 'luci-rpc',
method: 'getNetworkDevices',
expect: { '': {} }
});

var isDSA = false;

const ethStyle = {
box: 'max-width: 100px;',
head: `
border-radius: 7px 7px 0 0;
text-align: center;
font-weight: bold;`,
body: `
border: 1px solid lightgrey;
border-radius: 0 0 7px 7px;
display: flex; flex-direction: column;
align-items: center; justify-content: center;`,
icon: 'margin: 5px; width: 40px;',
speed: 'font-size: 0.8rem; font-weight: bold;',
traffic: `
border-top: 1px solid lightgrey;
font-size: 0.8rem;`
};
function getSwitchPortFlow() {
const portFlow = [];
fs.exec('/sbin/swconfig', ['dev', 'switch0', 'show']).then((res) => {
const lines = res.stdout.trim().split(/\n/);
let portNum;
for (let line of lines) {
let match = line.match(/^Port\s+(\d+):$/);
if (match != null) {
portNum = Number(match[1]);
portFlow[portNum] = {};
continue;
}
match = line.match(/^TxByte\s*:\s*(\d+)$/);
if (match != null) {
portFlow[portNum].rxflow = Number(match[1]);
continue;
}
match = line.match(/^RxByte\s*:\s*(\d+)$/);
if (match != null) {
portFlow[portNum].txflow = Number(match[1]);
continue;
}
}
});
return portFlow;
}

function formatSpeed(speed) {
if (speed <= 0) return '-';
Expand All @@ -57,123 +58,150 @@ function formatSpeed(speed) {
}

function getPortColor(carrier, duplex) {
if (!carrier) return 'background-color: whitesmoke;';
if (duplex === 'full' || duplex === true)
return 'background-color: greenyellow;';
return 'background-color: darkorange';
if (!carrier) return 'Gainsboro;';
if (duplex === 'full' || duplex === true) return 'greenyellow;';
return 'darkorange';
}

function getPortIcon(carrier) {
return L.resource(`icons/port_${carrier ? 'up' : 'down'}.png`);
}

function portDom(link, duplex, label, speed, tx_bytes, rx_bytes) {
const portIcon = getPortIcon(link);
const portColor = getPortColor(link, duplex);

return E('div', { style: ethStyle.box }, [
E('div', { style: ethStyle.head + portColor }, label),
E('div', { style: ethStyle.body }, [
E('img', { style: ethStyle.icon, src: portIcon }),
E('div', { style: ethStyle.speed }, formatSpeed(speed)),
E('div', { style: ethStyle.traffic }, [
'\u25b2\u202f%1024.1mB'.format(tx_bytes),
E('br'),
'\u25bc\u202f%1024.1mB'.format(rx_bytes)
])
])
]);
}

return baseclass.extend({
title: _('Ethernet Information'),

load: function () {
return network.getSwitchTopologies().then(function (topologies) {
if (Object.keys(topologies).length === 0) {
isDSA = true;
return Promise.all([
L.resolveDefault(callLuciBoardJSON(), {}),
L.resolveDefault(callLuciNetworkDevices(), {})
]);
}

callSwconfigPortState('switch0').then((ports) => {
topologies.switch0.portstate = ports;
function getPorts(board, netdevs, switches, portflow) {
const ports = [];

if (Object.keys(switches).length === 0) {
const network = board.network;
const ifnames = [network?.wan?.device].concat(network?.lan?.ports);
for (const ifname of ifnames) {
if (ifname in netdevs === false) continue;
const dev = netdevs[ifname];
ports.push({
ifname: dev.name,
carrier: dev.link.carrier,
duplex: dev.link.duplex,
speed: dev.link.speed,
txflow: dev.stats.tx_bytes,
rxflow: dev.stats.rx_bytes
});
return Promise.all([
topologies,
L.resolveDefault(callLuciBoardJSON(), {}),
L.resolveDefault(callLuciNetworkDevices(), {})
]);
});
},
}
return ports;
}

render_gsw: function (data) {
const topologies = data[0];
const board = data[1];
const netdevs = data[2];

let stats;
let foundWAN = false;
const ethPorts = [];
const switch0 = topologies.switch0;
for (const port of switch0.ports) {
const label = port.label.toUpperCase();
const { link, duplex, speed } = switch0.portstate[port.num];
const txrx = { tx_bytes: 0, rx_bytes: 0 };

if (label.startsWith('WAN')) {
foundWAN = true;
stats = netdevs[board.network.wan.device].stats;
const { tx_bytes, rx_bytes } = stats;
ethPorts.unshift(
portDom(link, duplex, 'WAN', speed, tx_bytes, rx_bytes)
);
} else if (label.startsWith('LAN')) {
stats = netdevs['br-lan'].stats;
const { tx_bytes, rx_bytes } = link ? stats : txrx;
ethPorts.push(portDom(link, duplex, label, speed, tx_bytes, rx_bytes));
let wanInSwitch;
const switch0 = switches['switch0'];
const lan = netdevs['br-lan'];
const wan = netdevs[board.network.wan.device];
for (const port of switch0.ports) {
const label = port.label.toUpperCase();
const portstate = switch0.portstate[port.num];
portstate.ifname = label;
portstate.carrier = portstate.link;
if (portflow[port.num]) {
portstate.txflow = portflow[port.num].txflow;
portstate.rxflow = portflow[port.num].rxflow;
}
if (label.startsWith('WAN')) {
wanInSwitch = true;
if (!portstate.rxflow && wan) {
portstate.txflow = wan.stats.tx_bytes;
portstate.rxflow = wan.stats.rx_bytes;
}
ports.unshift(portstate);
} else if (label.startsWith('LAN')) {
if (!portstate.rxflow && lan) {
portstate.txflow = lan.stats.tx_bytes;
portstate.rxflow = lan.stats.rx_bytes;
}
ports.push(portstate);
}
}
if (wanInSwitch) return ports;

if (wan) {
ports.unshift({
ifname: 'WAN',
carrier: wan.link.carrier,
duplex: wan.link.duplex,
speed: wan.link.speed,
txflow: wan.stats.tx_bytes,
rxflow: wan.stats.rx_bytes
});
}
return ports;
}

if (foundWAN) return ethPorts;

const wan = netdevs[board.network.wan.device];
const { speed, duplex, carrier } = wan.link;
const { tx_bytes, rx_bytes } = wan.stats;
ethPorts.unshift(
portDom(carrier, duplex, 'WAN', speed, tx_bytes, rx_bytes)
function renderPorts(data) {
const css = {
grids: `
display: grid; grid-gap: 5px 10px;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
margin-bottom: 1em;
`,
head: `
color: Black;
text-align: center;
font-weight: bold;
border-radius: 7px 7px 0 0;
`,
body: `
border: 1px solid lightgrey;
border-radius: 0 0 7px 7px;
display: flex; flex-direction: column;
align-items: center; justify-content: center;`,
icon: 'margin: 5px; width: 32px;',
speed: 'font-size: 0.8rem; font-weight: bold;',
flow: `
border-top: 1px solid lightgrey;
font-size: 0.8rem;`
};

const ports = [];
getPorts(...data).forEach((port) => {
const { carrier, duplex } = port;
const ifname = port.ifname.replace(' ', '');
const color = `background-color: ${getPortColor(carrier, duplex)};`;
ports.push(
E('div', {}, [
E('div', { style: css.head + color }, ifname),
E('div', { style: css.body }, [
E('img', { style: css.icon, src: getPortIcon(carrier) }),
E('div', { style: css.speed }, formatSpeed(port.speed)),
E('div', { style: css.flow }, [
'\u25b2\u202f%1024.1mB'.format(carrier ? port.txflow : 0),
E('br'),
'\u25bc\u202f%1024.1mB'.format(carrier ? port.rxflow : 0)
])
])
])
);
return ethPorts;
},
});

render_dsa: function (data) {
const board = data[0];
const netdevs = data[1];

const ethPorts = [];
const wan = board.network.wan.device;
let devices = `${wan},lan0,lan1,lan2,lan3,lan4,lan5,lan6`;
devices = devices.split(',');
for (const device of devices) {
if (device in netdevs === false) continue;
const dev = netdevs[device];
const label = dev.name;
const { speed, duplex, carrier } = dev.link;
const { tx_bytes, rx_bytes } = dev.stats;
ethPorts.push(portDom(carrier, duplex, label, speed, tx_bytes, rx_bytes));
}
return E('div', { style: css.grids }, ports);
}

return ethPorts;
return baseclass.extend({
title: _('Ethernet Information'),

load: function () {
return Promise.all([
L.resolveDefault(callLuciBoardJSON(), {}),
L.resolveDefault(callLuciNetworkDevices(), {}),
network.getSwitchTopologies().then((topologies) => {
if (Object.keys(topologies).length === 0) return {};
callSwconfigPortState('switch0').then((portstate) => {
topologies['switch0'].portstate = portstate;
});
return topologies;
}),
network.getSwitchTopologies().then((topologies) => {
if (Object.keys(topologies).length === 0) return [];
return getSwitchPortFlow();
})
]);
},

render: function (data) {
const ethPorts = isDSA ? this.render_dsa(data) : this.render_gsw(data);
const gridStyle = `
display: grid; grid-gap: 5px 5px;
grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
margin-bottom: 1em`;
return E('div', { style: gridStyle }, ethPorts);
return renderPorts(data);
}
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"luci-mod-status-autocore": {
"description": "Grant access to autocore",
"read": {
"ubus": {
"luci": [ "getCPUInfo", "getETHInfo", "getTempInfo" ]
}
}
}
"luci-mod-status-autocore": {
"description": "Grant access to autocore",
"read": {
"ubus": {
"luci": ["getCPUInfo", "getETHInfo", "getTempInfo"]
},
"file": {
"/sbin/swconfig dev switch0 show": ["exec"]
}
}
}
}