update 2026-09-24 16:56:26

This commit is contained in:
action committed 2026-09-24 16:56:26 +08:00
1 parent b31a5c3878
commit e4cee331e9
6885 files changed
+3240523

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 gSpotx2f
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
#
# Copyright (C) 2026 gSpot (https://github.com/gSpotx2f/luci-app-interfaces-statistics)
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-interfaces-statistics
PKG_VERSION:=0.4.2
PKG_RELEASE:=1
LUCI_TITLE:=Network interfaces statistics
LUCI_PKGARCH:=all
PKG_LICENSE:=MIT
#include ../../luci.mk
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
+36
View File
@@ -0,0 +1,36 @@
# luci-app-interfaces-statistics
Network interfaces statistics for LuCI (OpenWrt webUI).
OpenWrt >= 21.02.
## Installation notes
**OpenWrt >= 25.12:**
wget --no-check-certificate -O /tmp/luci-app-interfaces-statistics-0.4.2-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-app-interfaces-statistics-0.4.2-r1.apk
apk --allow-untrusted add /tmp/luci-app-interfaces-statistics-0.4.2-r1.apk
rm /tmp/luci-app-interfaces-statistics-0.4.2-r1.apk
service rpcd restart
i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-interfaces-statistics-ru-0.4.2-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-i18n-interfaces-statistics-ru-0.4.2-r1.apk
apk --allow-untrusted add /tmp/luci-i18n-interfaces-statistics-ru-0.4.2-r1.apk
rm /tmp/luci-i18n-interfaces-statistics-ru-0.4.2-r1.apk
**OpenWrt <= 24.10:**
wget --no-check-certificate -O /tmp/luci-app-interfaces-statistics_0.4.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-app-interfaces-statistics_0.4.2-r1_all.ipk
opkg install /tmp/luci-app-interfaces-statistics_0.4.2-r1_all.ipk
rm /tmp/luci-app-interfaces-statistics_0.4.2-r1_all.ipk
service rpcd restart
i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-interfaces-statistics-ru_0.4.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-i18n-interfaces-statistics-ru_0.4.2-r1_all.ipk
opkg install /tmp/luci-i18n-interfaces-statistics-ru_0.4.2-r1_all.ipk
rm /tmp/luci-i18n-interfaces-statistics-ru_0.4.2-r1_all.ipk
## Screenshots:
![](https://github.com/gSpotx2f/luci-app-interfaces-statistics/blob/master/screenshots/01.jpg)
@@ -0,0 +1,384 @@
'use strict';
'require poll';
'require rpc';
'require ui';
'require view';
const labelStyleUp = 'background-color:#388438; color:#fff';
const labelStyleDown = 'background-color:#767676; color:#fff';
return view.extend({
ifacesArray : [],
callNetDevice: rpc.declare({
object: 'network.device',
method: 'status',
expect: { '': {} }
}),
formatBytesString(bytes) {
let units = (bytes > 10**12) ?
`${Number((bytes / 1000 / 1000 / 1000 / 1000).toFixed(2))} ${_('Tb')}` :
(bytes > 10**9) ?
`${Number((bytes / 1000 / 1000 / 1000).toFixed(2))} ${_('Gb')}` :
(bytes > 10**6) ?
`${Number((bytes / 1000 / 1000).toFixed(2))} ${_('Mb')}` :
`${Number((bytes / 1000).toFixed(2))} ${_('Kb')}`;
return `${bytes} (${units})`;
},
setIfacesData(ifacesData) {
for(let iface of this.ifacesArray) {
let ifaceState = null;
let ifacesStatisticsObject = null;
if(ifacesData[iface] !== undefined) {
ifaceState = ifacesData[iface].up;
ifacesStatisticsObject = ifacesData[iface].statistics;
};
let state = document.querySelector('[data-ifstat="%s_state"]'.format(iface));
state.textContent = (ifaceState) ? _('Interface is up') : _('Interface is down');
state.style = (ifaceState) ? labelStyleUp : labelStyleDown;
if(!ifacesStatisticsObject) continue;
for(let [k, v] of Object.entries(ifacesStatisticsObject)) {
let elem = document.querySelector('[data-ifstat="%s_%s"]'.format(iface, k));
if(elem !== null) {
if(k == 'rx_bytes' || k == 'tx_bytes') {
elem.innerHTML = this.formatBytesString(v);
} else {
elem.textContent = v;
}
};
};
};
},
update() {
return this.callNetDevice().then(ifacesData => {
this.setIfacesData(ifacesData);
}).catch(e => ui.addNotification(null, E('p', {}, e.message)));
},
load() {
return this.callNetDevice().catch(
e => ui.addNotification(null, E('p', {}, e.message)));
},
render(ifacesData) {
let ifacesNode = E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' },
E('div', { 'class': 'cbi-value' },
E('em', {}, _('No interfaces detected'))
)
)
);
if(ifacesData) {
ifacesNode = E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' },
E('div', { 'class': 'cbi-value' }, [
E('div', { 'style': 'width:100%; text-align:right !important' },
E('button', {
'class': 'btn',
'click': () => window.location.reload(),
}, _('Refresh interfaces'))
)
])
)
);
let tabsContainer = E('div', { 'class': 'cbi-section-node cbi-section-node-tabbed' });
ifacesNode.append(tabsContainer);
let tab = 0;
for(let iface in ifacesData) {
let ifaceName = iface;
let ifacesStatisticsObject = ifacesData[iface].statistics;
let ifaceState = ifacesData[iface].up;
let ifaceMac = ifacesData[iface].macaddr;
if(ifaceMac == "00:00:00:00:00:00") {
ifaceMac = null;
};
this.ifacesArray.push(iface);
this.ifacesArray.sort();
let ifaceTab = E('div', {
'data-tab' : tab,
'data-tab-title': ifaceName,
});
tabsContainer.append(ifaceTab);
let ifaceTable = E('table', { 'class': 'table' });
if(ifaceMac) {
ifaceTable.append(
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'style': 'width:33%' },
_('MAC Address') + ':'),
E('td', { 'class': 'td left' }, ifaceMac),
]),
);
};
ifaceTable.append(
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'style': 'width:33%' },
_('State') + ':'),
E('td', { 'class': 'td left' },
E('span', {
'data-ifstat': iface + '_state',
'class': 'label',
'style': (ifaceState) ? labelStyleUp : labelStyleDown,
},
(ifaceState) ?
_('Interface is up') : _('Interface is down')
)
),
]),
);
let statTable = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th left', 'style': 'width:33%' },
_('Parameter')),
E('th', { 'class': 'th left', 'style': 'width:33%' },
_('Receive')),
E('th', { 'class': 'th left' }, _('Transmit')),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('bytes')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_bytes',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_bytes
),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_bytes',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_bytes
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('packets')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_packets',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_packets
),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_packets',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_packets
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('errors')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_errors',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_errors
),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_errors',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_errors
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('dropped')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_dropped',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_dropped
),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_dropped',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_dropped
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('compressed')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_compressed',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_compressed
),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_compressed',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_compressed
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('fifo errors')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_fifo_errors',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_fifo_errors
),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_fifo_errors',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_fifo_errors
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('multicast')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_multicast',
'data-title' : _('Receive') },
ifacesStatisticsObject.multicast
),
E('td', { 'class': 'td left', 'data-title': _('Transmit') },
'&#160;'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('crc errors')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_crc_errors',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_crc_errors
),
E('td', { 'class': 'td left', 'data-title': _('Transmit') },
'&#160;'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('frame errors')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_frame_errors',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_frame_errors
),
E('td', { 'class': 'td left', 'data-title': _('Transmit') },
'&#160;'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('length errors')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_length_errors',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_length_errors
),
E('td', { 'class': 'td left', 'data-title': _('Transmit') },
'&#160;'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('missed errors')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_missed_errors',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_missed_errors
),
E('td', { 'class': 'td left', 'data-title': _('Transmit') },
'&#160;'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('over errors')),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_rx_over_errors',
'data-title' : _('Receive') },
ifacesStatisticsObject.rx_over_errors
),
E('td', { 'class': 'td left', 'data-title': _('Transmit') },
'&#160;'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('collisions')),
E('td', { 'class': 'td left', 'data-title': _('Receive') },
'&#160;'),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_collisions',
'data-title' : _('Transmit') },
ifacesStatisticsObject.collisions
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('carrier errors')),
E('td', { 'class': 'td left', 'data-title': _('Receive') },
'&#160;'),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_carrier_errors',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_carrier_errors
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('heartbeat errors')),
E('td', { 'class': 'td left', 'data-title': _('Receive') },
'&#160;'),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_heartbeat_errors',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_heartbeat_errors
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('aborted errors')),
E('td', { 'class': 'td left', 'data-title': _('Receive') },
'&#160;'),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_aborted_errors',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_aborted_errors
),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': _('Parameter') },
_('window errors')),
E('td', { 'class': 'td left', 'data-title': _('Receive') },
'&#160;'),
E('td', { 'class': 'td left',
'data-ifstat': ifaceName + '_tx_window_errors',
'data-title' : _('Transmit') },
ifacesStatisticsObject.tx_window_errors
),
]),
]);
ifaceTab.append(E([
E('div', { 'class': 'cbi-value' }, ifaceTable),
E('div', { 'class': 'cbi-value' }, statTable),
]));
tab++;
};
ui.tabs.initTabGroup(tabsContainer.children);
poll.add(L.bind(this.update, this));
};
return E([
E('h2', { 'class': 'fade-in' }, _('Interfaces Statistics')),
E('div', { 'class': 'cbi-section-descr fade-in' }),
ifacesNode,
]);
},
handleSaveApply: null,
handleSave : null,
handleReset : null,
});
@@ -0,0 +1,106 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: ru\n"
msgid "Gb"
msgstr "Гб"
msgid "Interface is down"
msgstr "Интерфейс выключен"
msgid "Interface is up"
msgstr "Интерфейс включен"
msgid "Interfaces Statistics"
msgstr "Статистика интерфейсов"
msgid "Kb"
msgstr "Кб"
msgid "MAC Address"
msgstr "MAC адрес"
msgid "Mb"
msgstr "Мб"
msgid "No interfaces detected"
msgstr "Интерфейсы не найдены"
msgid "Parameter"
msgstr "Параметр"
msgid "Receive"
msgstr "Получено"
msgid "Refresh interfaces"
msgstr "Обновить интерфейсы"
msgid "State"
msgstr "Состояние"
msgid "Tb"
msgstr "Тб"
msgid "Transmit"
msgstr "Передано"
msgid "aborted errors"
msgstr ""
msgid "bytes"
msgstr ""
msgid "carrier errors"
msgstr ""
msgid "collisions"
msgstr ""
msgid "compressed"
msgstr ""
msgid "crc errors"
msgstr ""
msgid "dropped"
msgstr ""
msgid "errors"
msgstr ""
msgid "fifo errors"
msgstr ""
msgid "frame errors"
msgstr ""
msgid "heartbeat errors"
msgstr ""
msgid "length errors"
msgstr ""
msgid "missed errors"
msgstr ""
msgid "multicast"
msgstr ""
msgid "over errors"
msgstr ""
msgid "packets"
msgstr ""
msgid "window errors"
msgstr ""
@@ -0,0 +1,95 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Gb"
msgstr ""
msgid "Interface is down"
msgstr ""
msgid "Interface is up"
msgstr ""
msgid "Interfaces Statistics"
msgstr ""
msgid "Kb"
msgstr ""
msgid "MAC Address"
msgstr ""
msgid "Mb"
msgstr ""
msgid "No interfaces detected"
msgstr ""
msgid "Parameter"
msgstr ""
msgid "Receive"
msgstr ""
msgid "Refresh interfaces"
msgstr ""
msgid "State"
msgstr ""
msgid "Tb"
msgstr ""
msgid "Transmit"
msgstr ""
msgid "aborted errors"
msgstr ""
msgid "bytes"
msgstr ""
msgid "carrier errors"
msgstr ""
msgid "collisions"
msgstr ""
msgid "compressed"
msgstr ""
msgid "crc errors"
msgstr ""
msgid "dropped"
msgstr ""
msgid "errors"
msgstr ""
msgid "fifo errors"
msgstr ""
msgid "frame errors"
msgstr ""
msgid "heartbeat errors"
msgstr ""
msgid "length errors"
msgstr ""
msgid "missed errors"
msgstr ""
msgid "multicast"
msgstr ""
msgid "over errors"
msgstr ""
msgid "packets"
msgstr ""
msgid "window errors"
msgstr ""
@@ -0,0 +1,85 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.2\n"
"Last-Translator: 孤爺仔 <admin@guyezi.com>\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: zh_CN\n"
msgid "Collecting data..."
msgstr "正在收集数据..."
msgid "Frequency"
msgstr "频率"
msgid "Ghz"
msgstr "Ghz"
msgid "Interface is down"
msgstr "接口已关闭"
msgid "Interface is up"
msgstr "接口已打开"
msgid "Interfaces Statistics"
msgstr "接口统计"
msgid "MAC Address"
msgstr "MAC地址"
msgid "No interfaces detected"
msgstr "未检测到接口"
msgid "Parameter"
msgstr "参数"
msgid "Receive"
msgstr "接收"
msgid "Refresh interfaces"
msgstr "刷新接口"
msgid "SSID"
msgstr "SSID"
msgid "State"
msgstr "State"
msgid "Transmit"
msgstr "发送"
msgid "bytes"
msgstr "bytes"
msgid "carrier"
msgstr "载体"
msgid "collisions"
msgstr "冲突"
msgid "compressed"
msgstr "已压缩"
msgid "dropped"
msgstr "dropped"
msgid "errors"
msgstr "错误"
msgid "fifo"
msgstr "先进先出"
msgid "frame"
msgstr "框架"
msgid "multicast"
msgstr "多播"
msgid "packets"
msgstr "小包"
@@ -0,0 +1,13 @@
{
"admin/network/interfaces-statistics": {
"title": "Interfaces Statistics",
"order": 70,
"action": {
"type": "view",
"path": "interfaces-statistics"
},
"depends": {
"acl": [ "luci-app-interfaces-statistics" ]
}
}
}
@@ -0,0 +1,10 @@
{
"luci-app-interfaces-statistics": {
"description": "Grant access to interfaces-statistics procedures",
"read": {
"ubus": {
"network.device": [ "status" ]
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB