mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 20:05:13 +08:00
⛄ Sync 2026-04-23 20:32:35
This commit is contained in:
1 parent
17738c6f79
commit
fe51d2b304
206 files changed
+725671
No files matched your search
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"user": "nobody",
|
||||
"access": {
|
||||
"system": {
|
||||
"methods": [ "board", "info" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"admin": {
|
||||
"title": "Administration",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"recurse": true
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
|
||||
"login": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/status": {
|
||||
"title": "Status",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"preferred": "overview",
|
||||
"recurse": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/system": {
|
||||
"title": "System",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"preferred": "system",
|
||||
"recurse": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services": {
|
||||
"title": "Services",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"recurse": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/nas": {
|
||||
"title": "NAS",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"recurse": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/vpn": {
|
||||
"title": "VPN",
|
||||
"order": 50,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"recurse": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/control": {
|
||||
"title": "Control",
|
||||
"order": 51,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"recurse": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network": {
|
||||
"title": "Network",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "firstchild",
|
||||
"recurse": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/translations/*": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.index",
|
||||
"function": "action_translations"
|
||||
},
|
||||
"auth": {}
|
||||
},
|
||||
|
||||
"admin/ubus/*": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.index",
|
||||
"function": "action_ubus"
|
||||
},
|
||||
"auth": {}
|
||||
},
|
||||
|
||||
"admin/logout": {
|
||||
"title": "Log out",
|
||||
"order": 999,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.index",
|
||||
"function": "action_logout"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-base" ]
|
||||
},
|
||||
"firstchild_ineligible": true
|
||||
},
|
||||
|
||||
"admin/uci": {
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/uci/revert": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.uci",
|
||||
"function": "action_revert",
|
||||
"post": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/uci/apply_rollback": {
|
||||
"cors": true,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.uci",
|
||||
"function": "action_apply_rollback",
|
||||
"post": true
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ]
|
||||
}
|
||||
},
|
||||
|
||||
"admin/uci/apply_unchecked": {
|
||||
"cors": true,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.uci",
|
||||
"function": "action_apply_unchecked",
|
||||
"post": true
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ]
|
||||
}
|
||||
},
|
||||
|
||||
"admin/uci/confirm": {
|
||||
"cors": true,
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.uci",
|
||||
"function": "action_confirm"
|
||||
},
|
||||
"auth": {}
|
||||
},
|
||||
|
||||
"admin/menu": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.admin.index",
|
||||
"function": "action_menu"
|
||||
},
|
||||
"auth": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"unauthenticated": {
|
||||
"description": "Allow system feature probing",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci": [ "getFeatures" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"luci-base": {
|
||||
"description": "Grant access to basic LuCI procedures",
|
||||
"read": {
|
||||
"file": {
|
||||
"/": [ "list" ],
|
||||
"/*": [ "list" ]
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "list" ],
|
||||
"uci": [ "changes", "get" ]
|
||||
},
|
||||
"uci": [ "system", "luci" ]
|
||||
},
|
||||
"write": {
|
||||
"cgi-io": [ "upload" ],
|
||||
"file": {
|
||||
"/etc/luci-uploads/*": [ "write" ]
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "remove" ],
|
||||
"uci": [ "add", "apply", "confirm", "delete", "order", "rename", "set" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"luci-base-network-status": {
|
||||
"description": "Grant access to network status information",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci-rpc": [ "getBoardJSON", "getHostHints", "getNetworkDevices", "getWirelessDevices" ],
|
||||
"network": [ "get_proto_handlers" ],
|
||||
"network.device": [ "status" ],
|
||||
"network.interface": [ "dump" ]
|
||||
},
|
||||
"uci": [ "luci", "network", "wireless" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
// Licensed to the public under the Apache License 2.0.
|
||||
|
||||
'use strict';
|
||||
|
||||
import { stdin, access, dirname, basename, open, popen, glob, lsdir, readfile, readlink, realpath, error } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
|
||||
import { init_list, init_index, init_enabled, init_action, conntrack_list, process_list } from 'luci.sys';
|
||||
import { revision, branch } from 'luci.version';
|
||||
import { statvfs, uname } from 'luci.core';
|
||||
import { connect } from 'ubus';
|
||||
|
||||
import timezones from 'luci.zoneinfo';
|
||||
|
||||
|
||||
function shellquote(s) {
|
||||
return `'${replace(s, "'", "'\\''")}'`;
|
||||
}
|
||||
|
||||
function callPackageVersionCheck(pkg) {
|
||||
let version = "";
|
||||
|
||||
if ( access('/bin/opkg') ) {
|
||||
// <= v24.10
|
||||
let fd = popen('opkg list-installed ' + shellquote(pkg) + ' 2>/dev/null');
|
||||
if (fd) {
|
||||
const re = regexp('^' + pkg + ' - (.+)$', 's');
|
||||
const m = match(fd.read('all'), re);
|
||||
version = m?.[1];
|
||||
|
||||
fd.close();
|
||||
}
|
||||
}
|
||||
else if ( access('/usr/bin/apk') ) {
|
||||
// > v24.10
|
||||
let fd = popen('apk list -I ' + shellquote(pkg) + ' 2>/dev/null');
|
||||
if (fd) {
|
||||
const re = regexp('^' + pkg + '-(.+)$', 's');
|
||||
const m = match(fd.read('all'), re);
|
||||
version = m?.[1];
|
||||
|
||||
fd.close();
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
const methods = {
|
||||
getVersion: {
|
||||
call: function(request) {
|
||||
return { revision, branch };
|
||||
}
|
||||
},
|
||||
|
||||
getInitList: {
|
||||
args: { name: 'name' },
|
||||
call: function(request) {
|
||||
let scripts = {};
|
||||
|
||||
for (let name in filter(init_list(), i => !request.args.name || i == request.args.name)) {
|
||||
let idx = init_index(name);
|
||||
|
||||
scripts[name] = {
|
||||
index: idx?.[0],
|
||||
stop: idx?.[1],
|
||||
enabled: init_enabled(name)
|
||||
};
|
||||
}
|
||||
|
||||
return length(scripts) ? scripts : { error: 'No such init script' };
|
||||
}
|
||||
},
|
||||
|
||||
setInitAction: {
|
||||
args: { name: 'name', action: 'action' },
|
||||
call: function(request) {
|
||||
switch (request.args.action) {
|
||||
case 'enable':
|
||||
case 'enabled':
|
||||
case 'disable':
|
||||
case 'start':
|
||||
case 'stop':
|
||||
case 'restart':
|
||||
case 'reload':
|
||||
const rc = init_action(request.args.name, request.args.action);
|
||||
|
||||
if (rc === false)
|
||||
return { error: 'No such init script' };
|
||||
|
||||
return { result: rc == 0 };
|
||||
|
||||
default:
|
||||
return { error: 'Invalid action' };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getUnixtime: {
|
||||
call: function(request) {
|
||||
// time() ; Returns the current UNIX epoch.
|
||||
return { result: time() };
|
||||
}
|
||||
},
|
||||
|
||||
setLocaltime: {
|
||||
args: { localtime: 0 },
|
||||
call: function(request) {
|
||||
let t = localtime(request.args.localtime);
|
||||
|
||||
if (t) {
|
||||
system(sprintf('date -s "%04d-%02d-%02d %02d:%02d:%02d" >/dev/null', t.year, t.mon, t.mday, t.hour, t.min, t.sec));
|
||||
system('/etc/init.d/sysfixtime restart >/dev/null');
|
||||
}
|
||||
|
||||
return { result: request.args.localtime };
|
||||
}
|
||||
},
|
||||
|
||||
getTimezones: {
|
||||
call: function(request) {
|
||||
let tz = trim(readfile('/etc/TZ'));
|
||||
let zn = cursor()?.get?.('system', '@system[0]', 'zonename');
|
||||
let result = {};
|
||||
|
||||
for (let zone, tzstring in timezones) {
|
||||
result[zone] = { tzstring };
|
||||
|
||||
if (zn == zone)
|
||||
result[zone].active = true;
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
getLEDs: {
|
||||
call: function() {
|
||||
let result = {};
|
||||
|
||||
for (let led in lsdir('/sys/class/leds')) {
|
||||
let s;
|
||||
|
||||
result[led] = { triggers: [] };
|
||||
|
||||
s = trim(readfile(`/sys/class/leds/${led}/trigger`));
|
||||
for (let trigger in split(s, ' ')) {
|
||||
push(result[led].triggers, trim(trigger, '[]'));
|
||||
|
||||
if (trigger != result[led].triggers[-1])
|
||||
result[led].active_trigger = result[led].triggers[-1];
|
||||
}
|
||||
|
||||
s = readfile(`/sys/class/leds/${led}/brightness`);
|
||||
result[led].brightness = +s;
|
||||
|
||||
s = readfile(`/sys/class/leds/${led}/max_brightness`);
|
||||
result[led].max_brightness = +s;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
getUSBDevices: {
|
||||
call: function() {
|
||||
let result = { devices: [], ports: [] };
|
||||
|
||||
for (let path in glob('/sys/bus/usb/devices/[0-9]*/manufacturer')) {
|
||||
let id = basename(dirname(path));
|
||||
|
||||
push(result.devices, {
|
||||
id,
|
||||
vid: trim(readfile(`/sys/bus/usb/devices/${id}/idVendor`)),
|
||||
pid: trim(readfile(`/sys/bus/usb/devices/${id}/idProduct`)),
|
||||
vendor: trim(readfile(path)),
|
||||
product: trim(readfile(`/sys/bus/usb/devices/${id}/product`)),
|
||||
speed: +readfile(`/sys/bus/usb/devices/${id}/speed`)
|
||||
});
|
||||
}
|
||||
|
||||
for (let path in glob('/sys/bus/usb/devices/*/*-port[0-9]*')) {
|
||||
let port = basename(path);
|
||||
let link = readlink(`${path}/device`);
|
||||
|
||||
push(result.ports, {
|
||||
port,
|
||||
device: basename(link)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
getConntrackHelpers: {
|
||||
call: function() {
|
||||
const uci = cursor();
|
||||
let helpers = [];
|
||||
let package;
|
||||
|
||||
if (uci.load('/usr/share/firewall4/helpers'))
|
||||
package = 'helpers';
|
||||
else if (uci.load('/usr/share/fw3/helpers.conf'))
|
||||
package = 'helpers.conf';
|
||||
|
||||
if (package) {
|
||||
uci.foreach(package, 'helper', (s) => {
|
||||
push(helpers, {
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
module: s.module,
|
||||
family: s.family,
|
||||
proto: s.proto,
|
||||
port: s.port
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return { result: helpers };
|
||||
}
|
||||
},
|
||||
|
||||
getFeatures: {
|
||||
call: function() {
|
||||
let kernel_version = popen('echo -n `uname -r`').read('all');
|
||||
let result = {
|
||||
firewall: access('/sbin/fw3') == true,
|
||||
firewall4: access('/sbin/fw4') == true,
|
||||
opkg: access('/bin/opkg') == true,
|
||||
bonding: access('/sys/module/bonding'),
|
||||
mii_tool: access('/usr/sbin/mii-tool'),
|
||||
offloading: access('/sys/module/xt_FLOWOFFLOAD/refcnt') == true || access('/sys/module/nft_flow_offload/refcnt') == true,
|
||||
fullcone: access(`/lib/modules/${kernel_version}/xt_FULLCONENAT.ko`) == true || access(`/lib/modules/${kernel_version}/nft_fullcone.ko`) == true,
|
||||
br2684ctl: access('/usr/sbin/br2684ctl') == true,
|
||||
swconfig: access('/sbin/swconfig') == true,
|
||||
zram: access('/sys/class/zram-control') == true,
|
||||
sysntpd: readlink('/usr/sbin/ntpd') != null,
|
||||
ipv6: access('/proc/net/ipv6_route') == true,
|
||||
dropbear: access('/usr/sbin/dropbear') == true,
|
||||
cabundle: access('/etc/ssl/certs/ca-certificates.crt') == true,
|
||||
relayd: access('/usr/sbin/relayd') == true,
|
||||
apk: access('/usr/bin/apk') == true,
|
||||
wifi: access('/sbin/wifi') == true,
|
||||
ufpd: access('/usr/sbin/ufpd') == true,
|
||||
vrf: access('/sys/module/vrf/refcnt') == true, // vrf.ko is loaded
|
||||
netifd_vrf: false,
|
||||
mptcp: access('/proc/sys/net/mptcp/enabled'),
|
||||
};
|
||||
|
||||
const wifi_features = [
|
||||
'11ac',
|
||||
'11ax',
|
||||
'11be',
|
||||
'11r',
|
||||
'acs',
|
||||
'eap',
|
||||
'mesh',
|
||||
'ocv',
|
||||
'owe',
|
||||
'sae',
|
||||
'suiteb192',
|
||||
'wep',
|
||||
'wps',
|
||||
];
|
||||
|
||||
if (access('/usr/sbin/hostapd')) {
|
||||
result.hostapd = { cli: access('/usr/sbin/hostapd_cli') == true };
|
||||
|
||||
for (let feature in wifi_features)
|
||||
result.hostapd[feature] = system(`/usr/sbin/hostapd -v${feature} >/dev/null 2>/dev/null`) == 0;
|
||||
}
|
||||
|
||||
if (access('/usr/sbin/wpa_supplicant')) {
|
||||
result.wpasupplicant = { cli: access('/usr/sbin/wpa_cli') == true };
|
||||
|
||||
for (let feature in wifi_features)
|
||||
result.wpasupplicant[feature] = system(`/usr/sbin/wpa_supplicant -v${feature} >/dev/null 2>/dev/null`) == 0;
|
||||
}
|
||||
|
||||
let fd = popen('dnsmasq --version 2>/dev/null');
|
||||
|
||||
if (fd) {
|
||||
const m = match(fd.read('all'), /^Compile time options: (.+)$/s);
|
||||
|
||||
for (let opt in split(m?.[1], ' ')) {
|
||||
let f = replace(opt, 'no-', '', 1);
|
||||
|
||||
result.dnsmasq ??= {};
|
||||
result.dnsmasq[lc(f)] = (f == opt);
|
||||
}
|
||||
|
||||
fd.close();
|
||||
}
|
||||
|
||||
result.odhcpd = false;
|
||||
fd = popen('odhcpd -h 2>/dev/null');
|
||||
|
||||
if (fd) {
|
||||
const output = fd.read('all');
|
||||
|
||||
if (output) {
|
||||
result.odhcpd = {};
|
||||
const m = match(output, /^Features: (.+)$/s);
|
||||
|
||||
for (let opt in split(m?.[1], ' ')) {
|
||||
let f = replace(opt, 'no-', '', 1);
|
||||
result.odhcpd[lc(f)] = (f == opt);
|
||||
}
|
||||
}
|
||||
|
||||
fd.close();
|
||||
}
|
||||
|
||||
// This check can be removed after v25 release
|
||||
result.netifd_vrf = match(callPackageVersionCheck('netifd'), /^20[0-9][0-9]/s)?.[0] >= 2025;
|
||||
|
||||
fd = popen('ipset --help 2>/dev/null');
|
||||
|
||||
if (fd) {
|
||||
for (let line = fd.read('line'), flag = false; length(line); line = fd.read('line')) {
|
||||
if (line == 'Supported set types:\n') {
|
||||
flag = true;
|
||||
}
|
||||
else if (flag) {
|
||||
const m = match(line, /^ +([\w:,]+)\t+([0-9]+)\t/);
|
||||
|
||||
if (m) {
|
||||
result.ipset ??= {};
|
||||
result.ipset[m[1]] ??= +m[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fd.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
getSwconfigFeatures: {
|
||||
args: { switch: 'switch0' },
|
||||
call: function(request) {
|
||||
// Parse some common switch properties from swconfig help output.
|
||||
const swc = popen(`swconfig dev ${shellquote(request.args.switch)} help 2>/dev/null`);
|
||||
|
||||
if (swc) {
|
||||
let is_port_attr = false;
|
||||
let is_vlan_attr = false;
|
||||
let result = {};
|
||||
|
||||
for (let line = swc.read('line'); length(line); line = swc.read('line')) {
|
||||
if (match(line, /^\s+--vlan/)) {
|
||||
is_vlan_attr = true;
|
||||
}
|
||||
else if (match(line, /^\s+--port/)) {
|
||||
is_vlan_attr = false;
|
||||
is_port_attr = true;
|
||||
}
|
||||
else if (match(line, /cpu @/)) {
|
||||
result.switch_title = match(line, /^switch[0-9]+: \w+\((.+)\)/)?.[1];
|
||||
result.num_vlans = match(line, /vlans: ([0-9]+)/)?.[1] ?? 16;
|
||||
result.min_vid = 1;
|
||||
}
|
||||
else if (match(line, /: (pvid|tag|vid)/)) {
|
||||
if (is_vlan_attr)
|
||||
result.vid_option = match(line, /: (\w+)/)?.[1];
|
||||
}
|
||||
else if (match(line, /: enable_vlan4k/)) {
|
||||
result.vlan4k_option = 'enable_vlan4k';
|
||||
}
|
||||
else if (match(line, /: enable_vlan/)) {
|
||||
result.vlan_option = 'enable_vlan';
|
||||
}
|
||||
else if (match(line, /: enable_learning/)) {
|
||||
result.learning_option = 'enable_learning';
|
||||
}
|
||||
else if (match(line, /: enable_mirror_rx/)) {
|
||||
result.mirror_option = 'enable_mirror_rx';
|
||||
}
|
||||
else if (match(line, /: max_length/)) {
|
||||
result.jumbo_option = 'max_length';
|
||||
}
|
||||
}
|
||||
|
||||
swc.close();
|
||||
|
||||
if (!length(result))
|
||||
return { error: 'No such switch' };
|
||||
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return { error: error() };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getSwconfigPortState: {
|
||||
args: { switch: 'switch0' },
|
||||
call: function(request) {
|
||||
const swc = popen(`swconfig dev ${shellquote(request.args.switch)} show 2>/dev/null`);
|
||||
|
||||
if (swc) {
|
||||
let ports = [], port;
|
||||
|
||||
for (let line = swc.read('line'); length(line); line = swc.read('line')) {
|
||||
if (match(line, /^VLAN [0-9]+:/) && length(ports))
|
||||
break;
|
||||
|
||||
let pnum = match(line, /^Port ([0-9]+):/)?.[1];
|
||||
|
||||
if (pnum) {
|
||||
port = {
|
||||
port: +pnum,
|
||||
duplex: false,
|
||||
speed: 0,
|
||||
link: false,
|
||||
auto: false,
|
||||
rxflow: false,
|
||||
txflow: false,
|
||||
rx_bytes: 0,
|
||||
tx_bytes: 0,
|
||||
rx_packets: 0,
|
||||
tx_packets: 0
|
||||
};
|
||||
|
||||
push(ports, port);
|
||||
}
|
||||
|
||||
if (port) {
|
||||
let m;
|
||||
|
||||
if (match(line, /full[ -]duplex/))
|
||||
port.duplex = true;
|
||||
|
||||
if ((m = match(line, / speed:([0-9]+)/)) != null)
|
||||
port.speed = +m[1];
|
||||
|
||||
if ((m = match(line, /([0-9]+) Mbps/)) != null && !port.speed)
|
||||
port.speed = +m[1];
|
||||
|
||||
if ((m = match(line, /link: ([0-9]+)/)) != null && !port.speed)
|
||||
port.speed = +m[1];
|
||||
|
||||
if (match(line, /(link|status): ?up/))
|
||||
port.link = true;
|
||||
|
||||
if (match(line, /auto-negotiate|link:.*auto/))
|
||||
port.auto = true;
|
||||
|
||||
if (match(line, /link:.*rxflow/))
|
||||
port.rxflow = true;
|
||||
|
||||
if (match(line, /link:.*txflow/))
|
||||
port.txflow = true;
|
||||
|
||||
if ((m = match(line, /^\s*RxGoodByte\s*:\s*([0-9]+)/)) != null)
|
||||
port.rx_bytes = +m[1];
|
||||
|
||||
if ((m = match(line, /^\s*TxByte\s*:\s*([0-9]+)/)) != null)
|
||||
port.tx_bytes = +m[1];
|
||||
|
||||
if ((m = match(line, /^\s*Rx(UniCast|Multi|Broad)\s*:\s*([0-9]+)/)) != null)
|
||||
port.rx_packets += +m[2];
|
||||
|
||||
if ((m = match(line, /^\s*Tx(UniCast|Multi|Broad)\s*:\s*([0-9]+)/)) != null)
|
||||
port.tx_packets += +m[2];
|
||||
}
|
||||
}
|
||||
|
||||
swc.close();
|
||||
|
||||
if (!length(ports))
|
||||
return { error: 'No such switch' };
|
||||
|
||||
return { result: ports };
|
||||
}
|
||||
else {
|
||||
return { error: error() };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setPassword: {
|
||||
args: { username: 'root', password: 'password' },
|
||||
call: function(request) {
|
||||
const u = shellquote(request.args.username);
|
||||
const p = shellquote(request.args.password);
|
||||
|
||||
return {
|
||||
result: system(`(echo ${p}; sleep 1; echo ${p}) | /bin/busybox passwd ${u} >/dev/null 2>&1`) == 0
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
getBlockDevices: {
|
||||
call: function() {
|
||||
const block = popen('/sbin/block info 2>/dev/null');
|
||||
|
||||
if (block) {
|
||||
let result = {};
|
||||
|
||||
for (let line = block.read('line'); length(line); line = block.read('line')) {
|
||||
let dev = match(line, /^\/dev\/([^:]+):/)?.[1];
|
||||
|
||||
if (dev) {
|
||||
let e = result[dev] = {
|
||||
dev: `/dev/${dev}`,
|
||||
size: +readfile(`/sys/class/block/${dev}/size`) * 512
|
||||
};
|
||||
|
||||
for (let m in match(line, / (\w+)="([^"]+)"/g))
|
||||
e[lc(m[1])] = m[2];
|
||||
}
|
||||
}
|
||||
|
||||
block.close();
|
||||
|
||||
const swaps = open('/proc/swaps', 'r');
|
||||
|
||||
if (swaps) {
|
||||
for (let line = swaps.read('line'); length(line); line = swaps.read('line')) {
|
||||
let m = match(line, /^(\/\S+)\s+\S+\s+(\d+)/);
|
||||
|
||||
if (m) {
|
||||
let dev = replace(m[1], /\\(\d\d\d)/g, (_, n) => chr(int(n, 8)));
|
||||
|
||||
result[`swap:${m[1]}`] = {
|
||||
dev,
|
||||
type: 'swap',
|
||||
size: +m[2] * 1024
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
swaps.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return { error: 'Unable to execute block utility' };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setBlockDetect: {
|
||||
call: function() {
|
||||
return { result: system('/sbin/block detect > /etc/config/fstab') == 0 };
|
||||
}
|
||||
},
|
||||
|
||||
getMountPoints: {
|
||||
call: function() {
|
||||
const fd = open('/proc/mounts', 'r');
|
||||
|
||||
if (fd) {
|
||||
let result = [];
|
||||
|
||||
for (let line = fd.read('line'); length(line); line = fd.read('line')) {
|
||||
const m = split(line, ' ');
|
||||
const device = replace(m[0], /\\([0-9][0-9][0-9])/g, (m, n) => chr(int(n, 8)));
|
||||
const mount = replace(m[1], /\\([0-9][0-9][0-9])/g, (m, n) => chr(int(n, 8)));
|
||||
const stat = statvfs(mount);
|
||||
|
||||
if (stat?.blocks > 0) {
|
||||
push(result, {
|
||||
device, mount,
|
||||
size: stat.bsize * stat.blocks,
|
||||
avail: stat.bsize * stat.bavail,
|
||||
free: stat.bsize * stat.bfree
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fd.close();
|
||||
|
||||
return { result };
|
||||
}
|
||||
else {
|
||||
return { error: error() };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getRealtimeStats: {
|
||||
args: { mode: 'interface', device: 'eth0' },
|
||||
call: function(request) {
|
||||
let flags;
|
||||
|
||||
if (request.args.mode == 'interface')
|
||||
flags = `-i ${shellquote(request.args.device)}`;
|
||||
else if (request.args.mode == 'wireless')
|
||||
flags = `-r ${shellquote(request.args.device)}`;
|
||||
else if (request.args.mode == 'conntrack')
|
||||
flags = '-c';
|
||||
else if (request.args.mode == 'load')
|
||||
flags = '-l';
|
||||
else
|
||||
return { error: 'Invalid mode' };
|
||||
|
||||
const fd = popen(`luci-bwc ${flags}`, 'r');
|
||||
|
||||
if (fd) {
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = { result: json(`[${fd.read('all')}]`) };
|
||||
}
|
||||
catch (err) {
|
||||
result = { error: err };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return { error: error() };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getConntrackList: {
|
||||
call: function() {
|
||||
return { result: conntrack_list() };
|
||||
}
|
||||
},
|
||||
|
||||
getProcessList: {
|
||||
call: function() {
|
||||
return { result: process_list() };
|
||||
}
|
||||
},
|
||||
|
||||
getBuiltinEthernetPorts: {
|
||||
call: function() {
|
||||
const ubus = connect();
|
||||
const sysboard = ubus.call('system', 'board');
|
||||
let ports = [];
|
||||
|
||||
if (
|
||||
(type(sysboard) !== 'object') ||
|
||||
match(sysboard.release.target, /^(armsr|bcm27xx|mvebu|rockchip|sifiveu|sunxi|x86)/)
|
||||
) {
|
||||
const stat = ubus.call('network.device', 'status') || {};
|
||||
map(keys(stat), (ifname) => {
|
||||
if (
|
||||
ifname &&
|
||||
stat[ifname]?.devtype in ['ethernet', 'dsa'] && (
|
||||
length(stat[ifname]['link-advertising']) ||
|
||||
match(ifname, /^eth\d+$/)
|
||||
)
|
||||
)
|
||||
push(ports, { role: 'unknown', device: ifname });
|
||||
});
|
||||
} else {
|
||||
const fd = open('/etc/board.json', 'r');
|
||||
const board = fd ? json(fd) : {};
|
||||
for (let k in [ 'lan', 'wan' ]) {
|
||||
if (!board?.network?.[k])
|
||||
continue;
|
||||
|
||||
if (type(board.network[k].ports) == 'array') {
|
||||
for (let ifname in board.network[k].ports)
|
||||
push(ports, { role: k, device: ifname });
|
||||
} else if (type(board.network[k].device) == 'string') {
|
||||
push(ports, { role: k, device: board.network[k].device });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { result: ports };
|
||||
}
|
||||
},
|
||||
|
||||
getOdhcp6cStats: {
|
||||
call: function() {
|
||||
const ubus = connect();
|
||||
const devs = ubus.call('network.device', 'status');
|
||||
|
||||
if (!devs)
|
||||
return false;
|
||||
|
||||
const dev_stats = {};
|
||||
|
||||
for (let dev in devs) {
|
||||
const m = ubus.call(`odhcp6c.${dev}`, 'get_statistics');
|
||||
|
||||
// not all interfaces are exposed unless odhcp6c runs on it
|
||||
if (!m)
|
||||
continue;
|
||||
|
||||
push(dev_stats, dev);
|
||||
dev_stats[dev] = m;
|
||||
}
|
||||
|
||||
return { result: dev_stats };
|
||||
}
|
||||
},
|
||||
|
||||
getCPUBench: {
|
||||
call: function() {
|
||||
return { cpubench: readfile('/etc/bench.log') || '' };
|
||||
}
|
||||
},
|
||||
|
||||
getCPUInfo: {
|
||||
call: function() {
|
||||
if (!access('/sbin/cpuinfo'))
|
||||
return {};
|
||||
|
||||
const fd = popen('/sbin/cpuinfo');
|
||||
if (fd) {
|
||||
let cpuinfo = fd.read('all');
|
||||
if (!cpuinfo)
|
||||
cpuinfo = '?';
|
||||
fd.close();
|
||||
|
||||
return { cpuinfo: cpuinfo };
|
||||
} else {
|
||||
return { cpuinfo: error() };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getCPUUsage: {
|
||||
call: function() {
|
||||
if (access('/sbin/usage')) {
|
||||
const fd = popen('/sbin/usage');
|
||||
if (fd) {
|
||||
let cpuusage = trim(fd.read('all'));
|
||||
fd.close();
|
||||
|
||||
if (length(cpuusage))
|
||||
return { cpuusage };
|
||||
}
|
||||
}
|
||||
|
||||
const fd = popen('top -n1 | awk \'/^CPU/ {printf("%d%", 100 - $8)}\'');
|
||||
if (!fd)
|
||||
return { cpuusage: error() };
|
||||
|
||||
let cpuusage = trim(fd.read('all')) || '?';
|
||||
fd.close();
|
||||
|
||||
return { cpuusage };
|
||||
}
|
||||
},
|
||||
|
||||
getTempInfo: {
|
||||
call: function() {
|
||||
if (!access('/sbin/tempinfo'))
|
||||
return {};
|
||||
|
||||
const fd = popen('/sbin/tempinfo');
|
||||
if (fd) {
|
||||
let tempinfo = fd.read('all');
|
||||
if (!tempinfo)
|
||||
tempinfo = '?';
|
||||
fd.close();
|
||||
|
||||
return { tempinfo: tempinfo };
|
||||
} else {
|
||||
return { tempinfo: error() };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getOnlineUsers: {
|
||||
call: function() {
|
||||
const fd = open('/proc/net/arp', 'r');
|
||||
if (fd) {
|
||||
let onlineusers = 0;
|
||||
|
||||
for (let line = fd.read('line'); length(line); line = fd.read('line'))
|
||||
if (match(trim(line), /^.*(0x2).*(br-lan)$/))
|
||||
onlineusers++;
|
||||
|
||||
fd.close();
|
||||
|
||||
return { onlineusers: onlineusers };
|
||||
} else {
|
||||
return { onlineusers: error() };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { luci: methods };
|
||||
Reference in new issue
Block a user