🍉 Sync 2026-10-20 11:10:09

This commit is contained in:
github-actions[bot] committed 2026-10-20 11:10:09 +08:00
1 parent cbd3ce4959
commit 0e175ff6b9
8 files changed
+2576 -234

No files matched your search

+5 -4
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-trafficctl
PKG_VERSION:=1.22.0
PKG_RELEASE:=12
PKG_VERSION:=1.23.0
PKG_RELEASE:=13
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Denis Iusupov <yusdyr@gmail.com>
@@ -57,5 +57,6 @@ fi
exit 0
endef
# call BuildPackage - OpenWrt buildroot will pick up the standard LuCI layout
$(eval $(call BuildPackage,$(PKG_NAME)))
# No explicit $(eval $(call BuildPackage,...)) here: luci.mk already calls it
# for the main package, so the explicit call was a duplicate. Spotted by
# @ntbowen in #73.
@@ -45,9 +45,9 @@ function fmtRate(kbit) {
kbit = kbit || 0;
if (kbit >= 1000) {
var m = kbit / 1000;
return (m === Math.floor(m) ? m : m.toFixed(1)) + ' Mbit/s';
return (m === Math.floor(m) ? m : m.toFixed(1)) + ' ' + _('Mbit/s');
}
return kbit + ' kbit/s';
return kbit + ' ' + _('kbit/s');
}
function scopeOf(row) {
@@ -122,7 +122,7 @@ return view.extend({
'class': 'cbi-button cbi-button-remove',
'style': 'font-size:11px;padding:1px 6px;margin-left:6px',
'title': _('Remove limit')
}, '✕');
}, _('✕'));
rmBtn.addEventListener('click', function() {
rmBtn.disabled = true;
self.doCtl('limit', row, 0);
@@ -136,7 +136,7 @@ return view.extend({
var b = E('button', {
'class': 'cbi-button',
'style': 'font-size:10px;padding:0 5px;margin-right:2px',
'title': _('Limit inbound to') + ' ' + fmtRate(p.kbit)
'title': _('Limit inbound to %s').format(fmtRate(p.kbit))
}, p.label);
b.addEventListener('click', function() {
b.disabled = true;
@@ -249,13 +249,13 @@ var callConfigGet = rpc.declare({
var RATE_PRESETS = [
{v:'0', l: _('Off')},
{v:'1000', l:'1 Mbit/s'},
{v:'2000', l:'2 Mbit/s'},
{v:'5000', l:'5 Mbit/s'},
{v:'10000', l:'10 Mbit/s'},
{v:'25000', l:'25 Mbit/s'},
{v:'50000', l:'50 Mbit/s'},
{v:'100000', l:'100 Mbit/s'},
{v:'1000', l:_('1 Mbit/s')},
{v:'2000', l:_('2 Mbit/s')},
{v:'5000', l:_('5 Mbit/s')},
{v:'10000', l:_('10 Mbit/s')},
{v:'25000', l:_('25 Mbit/s')},
{v:'50000', l:_('50 Mbit/s')},
{v:'100000', l:_('100 Mbit/s')},
{v:'custom', l: _('Custom…')}
];
@@ -334,11 +334,11 @@ function renderTotalCell(cell, total, liveVal, since, pending, degraded) {
}
cell.appendChild(document.createTextNode(fmtBytes(total)));
var tip = since
? (_('Accumulated since') + ' ' + new Date(since * 1000).toLocaleString())
? (_('Accumulated since %s').format(new Date(since * 1000).toLocaleString()))
: _('Accumulated on the router');
tip += '\n' + _('Resets when the router reboots.');
if (liveVal != null && liveVal >= 0) {
tip += '\n' + _('Currently tracked connections hold') + ' ' + fmtBytes(liveVal);
tip += '\n' + _('Currently tracked connections hold %s').format(fmtBytes(liveVal));
}
cell.title = tip;
}
@@ -346,16 +346,16 @@ function renderTotalCell(cell, total, liveVal, since, pending, degraded) {
function fmtSpeed(bps) {
if (!bps || bps < 1) return '—';
var bits = bps * 8;
if (bits < 1000) return bits.toFixed(0) + ' bit/s';
if (bits < 1000000) { var k = bits/1000; return (k % 1 === 0 ? k.toFixed(0) : k.toFixed(1)) + ' Kbit/s'; }
if (bits < 1000000000) { var m = bits/1000000; return (m % 1 === 0 ? m.toFixed(0) : m.toFixed(1)) + ' Mbit/s'; }
var g = bits/1000000000; return (g % 1 === 0 ? g.toFixed(0) : g.toFixed(2)) + ' Gbit/s';
if (bits < 1000) return bits.toFixed(0) + ' ' + _('bit/s');
if (bits < 1000000) { var k = bits/1000; return (k % 1 === 0 ? k.toFixed(0) : k.toFixed(1)) + ' ' + _('Kbit/s'); }
if (bits < 1000000000) { var m = bits/1000000; return (m % 1 === 0 ? m.toFixed(0) : m.toFixed(1)) + ' ' + _('Mbit/s'); }
var g = bits/1000000000; return (g % 1 === 0 ? g.toFixed(0) : g.toFixed(2)) + ' ' + _('Gbit/s');
}
function fmtRate(kbit) {
if (!kbit || kbit <= 0) return '—';
var mbit = kbit / 1000;
if (mbit >= 1) return (mbit % 1 === 0 ? mbit.toFixed(0) : mbit.toFixed(1)) + ' Mbit/s';
return kbit + ' kbit/s';
if (mbit >= 1) return (mbit % 1 === 0 ? mbit.toFixed(0) : mbit.toFixed(1)) + ' ' + _('Mbit/s');
return kbit + ' ' + _('kbit/s');
}
// Render a "↓ down / ↑ up" speed cell. Both halves come from the same
@@ -703,14 +703,14 @@ function renderFullGraph(history, limitKbit, width, height) {
dlLeg.setAttribute('x', legendX); dlLeg.setAttribute('y', legendY);
dlLeg.setAttribute('text-anchor', 'end'); dlLeg.setAttribute('font-size', '9');
dlLeg.setAttribute('fill', 'var(--tc-speed)'); dlLeg.setAttribute('font-weight', '600');
dlLeg.textContent = '↓ DL';
dlLeg.textContent = _('↓ DL');
svg.appendChild(dlLeg);
if (hasUpload) {
var ulLeg = document.createElementNS(ns, 'text');
ulLeg.setAttribute('x', legendX); ulLeg.setAttribute('y', legendY + 12);
ulLeg.setAttribute('text-anchor', 'end'); ulLeg.setAttribute('font-size', '9');
ulLeg.setAttribute('fill', 'var(--tc-ok)'); ulLeg.setAttribute('font-weight', '600');
ulLeg.textContent = '↑ UL';
ulLeg.textContent = _('↑ UL');
svg.appendChild(ulLeg);
}
@@ -967,10 +967,10 @@ function buildOverviewPanel(ifaces, ifHistory, speedMap, nameByIp, showOther, on
// ── headline tiles ──────────────────────────────────────────────────
var head = E('div', { 'class': 'tc-ov-tiles' }, [
mkOvTile(_('Download') + ' · ' + (primary.label || primary.dev),
fmtSpeed(primaryRate.down), fmtBytes(primary.rx_bytes) + ' ' + _('total'), 'tc-c-speed'),
mkOvTile(_('Upload') + ' · ' + (primary.label || primary.dev),
fmtSpeed(primaryRate.up), fmtBytes(primary.tx_bytes) + ' ' + _('total'), 'tc-c-ok'),
mkOvTile(_('Download · %s').format(primary.label || primary.dev),
fmtSpeed(primaryRate.down), _('%s total').format(fmtBytes(primary.rx_bytes)), 'tc-c-speed'),
mkOvTile(_('Upload · %s').format(primary.label || primary.dev),
fmtSpeed(primaryRate.up), _('%s total').format(fmtBytes(primary.tx_bytes)), 'tc-c-ok'),
mkOvTile(_('Active devices'), String(activeDevices),
_('sending or receiving now'), '')
]);
@@ -1337,7 +1337,7 @@ function buildSummaryTable(rows, sortCol, sortDir, onSort, onSelect, speedMap, d
if (sd && (sd.current_up||0) > 1024) { cellMap._speed_up.className = 'td tc-right tc-mono tc-speed-active'; cellMap._speed_up.textContent = fmtSpeed(sd.current_up); }
else { cellMap._speed_up.className = 'td tc-right tc-mono tc-speed-idle'; cellMap._speed_up.textContent = sd ? fmtSpeed(sd.current_up||0) : '—'; }
var sparkTip = r._throttle_kbit > 0 ? (_('Limit') + ': ' + fmtRate(r._throttle_kbit)) : '';
var sparkTip = r._throttle_kbit > 0 ? (_('Limit: %s').format(fmtRate(r._throttle_kbit))) : '';
cellMap._spark = E('div', { 'class': 'td tc-center', 'style': 'padding:2px 4px', 'data-spark-ip': r.ip, 'data-tip': sparkTip || undefined });
var sparkSvg = renderSparkline(speedHistory[r.ip], globalSpeedMax, 60, 20, r._throttle_kbit);
if (sparkSvg) cellMap._spark.appendChild(sparkSvg);
@@ -1369,7 +1369,7 @@ function buildSummaryTable(rows, sortCol, sortDir, onSort, onSelect, speedMap, d
if (lastTs) {
var ago = Math.floor((Date.now()/1000) - lastTs);
var agoStr = ago < 60 ? ago + 's' : ago < 3600 ? Math.floor(ago/60) + 'm' : Math.floor(ago/3600) + 'h';
tip = _('Last seen') + ': ' + lastType + ', ' + agoStr + ' ' + _('ago');
tip = _('Last seen: %s, %s ago').format(lastType, agoStr);
}
}
linkBadge = E('span', { 'class': 'tc-c-faint', 'style': 'cursor:help', 'title': tip }, '?');
@@ -1425,7 +1425,7 @@ function buildSummaryTable(rows, sortCol, sortDir, onSort, onSelect, speedMap, d
cellMap._backlog = E('div', { 'class': 'td tc-center', 'data-backlog-ip': r.ip }, backlogBadge);
var cells = visibleCols.map(function(c) { return cellMap[c.key]; });
var row = E('div', { 'class': 'tr', 'title': _('Click to inspect') + ' ' + r.name }, cells);
var row = E('div', { 'class': 'tr', 'title': _('Click to inspect %s').format(r.name) }, cells);
row.addEventListener('click', function() { addRecentDevice(r.ip, r.name); onSelect(r.ip, r.name); });
return row;
});
@@ -1519,7 +1519,7 @@ function buildExtendedStatsPanel(ip, shapeMap, dropMap, speedMap) {
}
if (sm.memory_used != null) addRow(_('Queue memory'), fmtBytes(sm.memory_used), null);
if (sm.lended != null || sm.borrowed != null) {
addRow(_('Lended') + ' / ' + _('Borrowed'), (sm.lended || 0) + ' / ' + (sm.borrowed || 0), null);
addRow(_('Lended / Borrowed'), (sm.lended || 0) + ' / ' + (sm.borrowed || 0), null);
}
if (spd && sm.rate_kbit > 0) {
var currentBps = spd.current || 0;
@@ -1612,7 +1612,7 @@ function buildExtendedStatsLegend(shapeMap, dropMap) {
}
return E('div', { 'class': 'tc-ext-panel tc-ext-panel--sticky' }, [
E('div', { 'class': 'tc-ext-panel__title' }, _('Extended Statistics') + ' (' + _('all devices') + ')'),
E('div', { 'class': 'tc-ext-panel__title' }, _('Extended Statistics (all devices)')),
E('div', { 'class': 'tc-ext-col-flex' }, rows)
]);
}
@@ -2302,8 +2302,8 @@ return view.extend({
'class': 'tc-custom-input' });
var customUnitBtns = E('span', {'class':'tc-custom-unit-btns'});
var _customUnit = 'mbit';
var mbitBtn = E('span', {'style':'padding:4px 8px;font-size:11px;cursor:pointer;background:var(--tc-speed);color:#fff'}, 'Mbit/s');
var kbitBtn = E('span', {'style':'padding:4px 8px;font-size:11px;cursor:pointer;background:var(--tc-bg);color:currentColor'}, 'kbit/s');
var mbitBtn = E('span', {'style':'padding:4px 8px;font-size:11px;cursor:pointer;background:var(--tc-speed);color:#fff'}, _('Mbit/s'));
var kbitBtn = E('span', {'style':'padding:4px 8px;font-size:11px;cursor:pointer;background:var(--tc-bg);color:currentColor'}, _('kbit/s'));
function updateUnitBtns() {
mbitBtn.style.background = _customUnit === 'mbit' ? 'var(--tc-speed)' : 'var(--tc-bg)';
mbitBtn.style.color = _customUnit === 'mbit' ? '#fff' : 'currentColor';
@@ -2552,7 +2552,7 @@ return view.extend({
function removeSubnetLimit(target, mode, btn) {
btn.disabled = true;
setStatus(statusDiv, 'loading', _('Removing limit on') + ' ' + target + '…');
setStatus(statusDiv, 'loading', _('Removing limit on %s…').format(target));
callRatelimit(target, 0, '', mode).then(function(res) {
setStatus(statusDiv, (res && res.ok) ? 'ok' : 'error',
(res && res.msg) || _('Throttle removed'));
@@ -2591,7 +2591,7 @@ return view.extend({
}, isShared ? _('shared between them') : _('each')),
E('span', {'class':'tc-c-muted tc-subnet-limits__drops'},
l.packets > 0
? (String(l.packets) + ' ' + _('packets dropped') + ' (' + fmtBytes(l.bytes) + ')')
? (_('%d packets dropped (%s)').format(l.packets, fmtBytes(l.bytes)))
: _('nothing dropped yet')),
removeBtn
]));
@@ -2604,8 +2604,8 @@ return view.extend({
list.forEach(function(s) {
if (!s || !s.cidr) { return; }
addSubnetChip(s.cidr, s.cidr, s.kind === 'routed'
? (_('Routed via') + ' ' + s.device)
: (_('On') + ' ' + s.device));
? (_('Routed via %s').format(s.device))
: (_('On %s').format(s.device)));
});
updateSubnetChips();
updateScopeExplain();
@@ -3098,10 +3098,10 @@ return view.extend({
var protoUdp = Number(data.protocols.udp) || 0;
var protoOther = Number(data.protocols.other) || 0;
var connCount = protoTcp + protoUdp + protoOther;
var parts = [_('Connections') + ': <b>'+connCount+'</b>'];
var parts = [_('Connections: <b>%d</b>').format(connCount)];
if (connCount > 0) {
parts.push('TCP: <b>'+protoTcp+'</b>');
parts.push('UDP: <b>'+protoUdp+'</b>');
parts.push(_('TCP: <b>%d</b>').format(protoTcp));
parts.push(_('UDP: <b>%d</b>').format(protoUdp));
if (data.tcp_states) {
Object.keys(data.tcp_states).forEach(function(s) {
parts.push(escHtml(s)+': <b>'+(Number(data.tcp_states[s]) || 0)+'</b>');
@@ -3109,15 +3109,15 @@ return view.extend({
}
}
if ((data.shape_kbit || 0) > 0) {
parts.push(_('Shaped') + ': <b style="color:var(--tc-speed)">🌊 '+fmtRate(data.shape_kbit)+'</b>');
parts.push(_('Shaped: <b style=\"color:var(--tc-speed)\">🌊 %s</b>').format(fmtRate(data.shape_kbit)));
var sm = self._shapeMap[data.ip || searchSelect.getValue()] || {};
if ((sm.backlog||0) > 0) parts.push(_('Queued') + ': <b style="color:var(--tc-speed)">'+fmtBytes(sm.backlog)+'</b>');
if ((sm.bytes||0) > 0) parts.push(_('Passed') + ': <b>'+fmtBytes(sm.bytes)+'</b>');
if ((sm.backlog||0) > 0) parts.push(_('Queued: <b style=\"color:var(--tc-speed)\">%s</b>').format(fmtBytes(sm.backlog)));
if ((sm.bytes||0) > 0) parts.push(_('Passed: <b>%s</b>').format(fmtBytes(sm.bytes)));
} else if ((data.rate_limit_kbit || 0) > 0) {
parts.push(_('Speed limit') + ': <b style="color:var(--tc-warn)">⚡ '+fmtRate(data.rate_limit_kbit)+'</b>');
parts.push(_('Speed limit: <b style=\"color:var(--tc-warn)\">⚡ %s</b>').format(fmtRate(data.rate_limit_kbit)));
var dm = self._dropMap[data.ip || searchSelect.getValue()] || {};
if ((dm.packets||0) > 0) {
parts.push(_('Dropped') + ': <b style="color:var(--tc-err)">🚫 '+(Number(dm.packets) || 0)+' pkts / '+fmtBytes(dm.bytes||0)+'</b>');
parts.push(_('Dropped: <b style="color:var(--tc-err)">🚫 %d pkts / %s</b>').format(Number(dm.packets) || 0, fmtBytes(dm.bytes||0)));
}
}
// Listed but still associated is not a block, so it must not
@@ -3130,11 +3130,11 @@ return view.extend({
} else if (data.wifi_blocked) {
wifiPart = ' &nbsp;|&nbsp; <b style="color:var(--tc-warn)">📵 ' + _('WiFi blocked') + '</b> ('+escHtml(data.mac||'') + ')';
} else {
wifiPart = data.mac ? ' &nbsp;|&nbsp; <span style="color:var(--tc-faint)">MAC: '+escHtml(data.mac)+'</span>' : '';
wifiPart = data.mac ? ' &nbsp;|&nbsp; <span style="color:var(--tc-faint)">' + _('MAC:') + ' ' + escHtml(data.mac) + '</span>' : '';
}
statsDiv.className = 'alert-message ' + (data.blocked ? 'error' : 'info');
statsDiv.innerHTML = (data.blocked
? '<b>⛔ ' + _('BLOCKED') + '</b> — '+(Number(data.block_packets) || 0)+' pkts, '+fmtBytes(Number(data.block_bytes) || 0)+' ' + _('dropped') + ' &nbsp;|&nbsp; '
? '<b>⛔ ' + _('BLOCKED') + '</b> — ' + _('%d pkts, %s dropped').format(Number(data.block_packets) || 0, fmtBytes(Number(data.block_bytes) || 0)) + ' &nbsp;|&nbsp; '
: '') + parts.join(' &nbsp;|&nbsp; ') + wifiPart;
}
@@ -3323,9 +3323,9 @@ return view.extend({
}
var parts = [];
parts.push(E('span', {}, [document.createTextNode(_('Active') + ': '), E('b', {}, String(rows.length))]));
parts.push(E('span', {}, [document.createTextNode(_('Blocked') + ': '), mkFilterVal('blocked', 'var(--tc-err)', String(blocked))]));
parts.push(E('span', {}, [document.createTextNode(_('WiFi') + ': '), mkFilterVal('wifi_blocked', 'var(--tc-warn)', String(wifiBlk))]));
parts.push(E('span', {}, [document.createTextNode(_('Active:') + ' '), E('b', {}, String(rows.length))]));
parts.push(E('span', {}, [document.createTextNode(_('Blocked:') + ' '), mkFilterVal('blocked', 'var(--tc-err)', String(blocked))]));
parts.push(E('span', {}, [document.createTextNode(_('WiFi:') + ' '), mkFilterVal('wifi_blocked', 'var(--tc-warn)', String(wifiBlk))]));
// Without this the header counts a device as blocked on the strength of
// the uci maclist alone — which is how a router can report "WiFi: 2"
// while both of those devices are browsing.
@@ -3333,13 +3333,13 @@ return view.extend({
parts.push(E('span', {
'style': 'color:var(--tc-err);font-weight:700;cursor:help',
'title': _('Devices on the WiFi deny list that are still connected — those blocks are not in effect on the running radio.')
}, '⚠ ' + wifiPending + ' ' + _('not applied')));
}, '⚠ ' + _('%d not applied').format(wifiPending)));
}
if (limited > 0) parts.push(E('span', {}, [document.createTextNode(_('Limited') + ': '), mkFilterVal('limited', 'var(--tc-warn)', '⚡' + limited)]));
if (shaped > 0) parts.push(E('span', {}, [document.createTextNode(_('Shaped') + ': '), mkFilterVal('shaped', 'var(--tc-speed)', '🌊' + shaped)]));
if (limited > 0) parts.push(E('span', {}, [document.createTextNode(_('Limited:') + ' '), mkFilterVal('limited', 'var(--tc-warn)', '⚡' + limited)]));
if (shaped > 0) parts.push(E('span', {}, [document.createTextNode(_('Shaped:') + ' '), mkFilterVal('shaped', 'var(--tc-speed)', '🌊' + shaped)]));
if (totalDropPkts > 0) {
parts.push(E('span', {}, [
document.createTextNode(_('Dropped') + ': '), E('b', {'style':'color:var(--tc-err)'}, '🚫' + totalDropPkts)
document.createTextNode(_('Dropped:') + ' '), E('b', {'style':'color:var(--tc-err)'}, '🚫' + totalDropPkts)
]));
}
@@ -3912,7 +3912,7 @@ return view.extend({
var limitBtns = [];
RATE_PRESETS.forEach(function(p) {
if (p.v === '0' || p.v === 'custom') return;
limitBtns.push(E('span', {'class':'tg-kbd-btn'}, p.l.replace(' Mbit/s', 'M').replace(/\s/g, '')));
limitBtns.push(E('span', {'class':'tg-kbd-btn'}, p.l.replace(' Mbit/s','M').replace(/\s/g,'')));
});
for (var li = 0; li < limitBtns.length; li += 3) {
kbdBubble.appendChild(E('div', {'class':'tg-kbd-row'}, limitBtns.slice(li, li + 3)));
@@ -3922,7 +3922,7 @@ return view.extend({
var shapeBtns = [];
RATE_PRESETS.forEach(function(p) {
if (p.v === '0' || p.v === 'custom') return;
shapeBtns.push(E('span', {'class':'tg-kbd-btn'}, '🔧 ' + p.l.replace(' Mbit/s', 'M').replace(/\s/g, '')));
shapeBtns.push(E('span', {'class':'tg-kbd-btn'}, '🔧 ' + p.l.replace(' Mbit/s','M').replace(/\s/g,'')));
});
for (var si = 0; si < shapeBtns.length; si += 3) {
kbdBubble.appendChild(E('div', {'class':'tg-kbd-row'}, shapeBtns.slice(si, si + 3)));
@@ -4180,17 +4180,14 @@ return view.extend({
var modeLabels = {
'none': ['⊘', _('No offload'), 'var(--tc-muted)'],
'software': ['◑', _('Software offload'), 'var(--tc-speed)'],
// tctl_get_offload_mode splits software offload on the flowtable
// counter flag, so this value reaches the badge too. Without an
// entry the badge fell through to '?' and the raw mode string.
'software-counter': ['◑', _('Software offload'), 'var(--tc-speed)'],
'software-counter': ['◑', _('Software offload (counter)'), 'var(--tc-speed)'],
'hardware-counter': ['●', _('Hardware offload'), 'var(--tc-warn)'],
'hardware': ['●', _('Hardware offload'), 'var(--tc-warn)']
};
var ml = modeLabels[cfg.offload_mode] || ['?', cfg.offload_mode, 'var(--tc-muted)'];
var modeBadge = E('div', {'style':'margin-bottom:10px;font-size:12px'}, [
E('span', {'style':'color:'+ml[2]+';font-size:15px;margin-right:4px'}, ml[0]),
E('span', {'style':'color:var(--tc-muted)'}, _('Current mode: ')),
E('span', {'style':'color:var(--tc-muted)'}, _('Current mode:') + ' '),
E('b', {}, ml[1])
]);
@@ -4338,9 +4335,9 @@ return view.extend({
var sep = E('span', {'style': 'opacity:0.35;margin:0 6px'}, '|');
var docLinks = para([
link(openwrtUrl, 'OpenWrt: Flow offloading ↗'), sep.cloneNode(true),
link(kernelUrl, 'Linux kernel: nf_flowtable ↗'), sep.cloneNode(true),
link(nftUrl, 'nftables: Flowtables ↗')
link(openwrtUrl, _('OpenWrt: Flow offloading ↗')), sep.cloneNode(true),
link(kernelUrl, _('Linux kernel: nf_flowtable ↗')), sep.cloneNode(true),
link(nftUrl, _('nftables: Flowtables ↗'))
]);
if (mode === 'hardware-counter') {
@@ -4349,15 +4346,15 @@ return view.extend({
icon = '⚠️';
body = E('div', {}, [
para(bold(_('Hardware flow offloading active — real-time speed monitoring unavailable.'))),
para([_('The flowtable '),
para([_('The flowtable') + ' ',
E('code', {}, 'counter'),
_(' flag should sync hardware byte counts back to conntrack, but on many ' +
' ' + _('flag should sync hardware byte counts back to conntrack, but on many ' +
'platforms (e.g. Mediatek Filogic) the driver does not implement the stats ' +
'callback, so conntrack counters remain frozen for active flows.')]),
para(bold(_('To restore speed monitoring:'))),
E('ul', {'class': 'tc-offload-ul'}, [
E('li', {}, [
_('Disable hardware offload (keeps software offload): '),
_('Disable hardware offload (keeps software offload):') + ' ',
E('code', {}, 'uci set firewall.@defaults[0].flow_offloading_hw=0 && uci commit firewall && fw4 reload'),
]),
E('li', {}, _('Or disable all flow offload in LuCI → Network → Firewall → General Settings.')),
@@ -4379,12 +4376,12 @@ return view.extend({
E('li', {}, _('Traffic shaping (tc/HTB) — bypassed for offloaded flows')),
E('li', {}, _('Rate limiting — applies only to new connections')),
]),
para([_('WiFi blocking and internet blocking of new connections still work. '),
para([_('WiFi blocking and internet blocking of new connections still work.') + ' ',
_('Shaped devices are usually not offloaded (kernel detects the HTB qdisc).')]),
para(bold(_('How to get full functionality without disabling offload:'))),
para([_('The flowtable '),
para([_('The flowtable') + ' ',
E('code', {}, 'counter'),
_(' flag (Linux 5.7+, set automatically by fw4/nftables) periodically syncs ' +
' ' + _('flag (Linux 5.7+, set automatically by fw4/nftables) periodically syncs ' +
'hardware byte counts back to conntrack — trafficctl detects this and all features work normally.')]),
para(_('If your current firmware uses fw3 (iptables) or ships a kernel older than 5.7, ' +
'a router with modern OpenWrt and fw4 support will have this working out of the box.')),
@@ -4407,9 +4404,9 @@ return view.extend({
para(bold(_('Software flow offloading is active, with counter sync.'))),
para(_('The kernel fast-paths established connections through a flowtable for ' +
'higher throughput.')),
para([_('The flowtable carries the '),
para([_('The flowtable carries the') + ' ',
E('code', {}, 'counter'),
_(' flag (Linux 5.7+, set automatically by fw4), so offloaded byte counts are ' +
' ' + _('flag (Linux 5.7+, set automatically by fw4), so offloaded byte counts are ' +
'synced back to conntrack. Speed and totals come from conntrack as usual and ' +
'are accurate — the nftables counter fallback is not needed on this router.')]),
para(_('Traffic shaping and blocking are not affected.')),
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
zh_Hans
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Status Pages
LUCI_DEPENDS:=+luci-base +luci-app-oui +luci-mod-dashboard +libiwinfo +rpcd-mod-iwinfo +rpcd-mod-rrdns +ucode +ucode-mod-fs +ucode-mod-ubus +ucode-mod-socket
PKG_RELEASE:=34
PKG_RELEASE:=35
PKG_BUILD_DEPENDS:=iwinfo
PKG_LICENSE:=Apache-2.0
@@ -214,8 +214,9 @@ function addResolvedPort(knownPorts, seenPorts, role, device, mapping, board, sw
});
}
else {
const deviceInfo = L.isObject(board) && L.isObject(board['network-device'])
? board['network-device'][value] : null;
const devices = L.isObject(board)
? (board['network-device'] || board.network_device) : null;
const deviceInfo = L.isObject(devices) ? devices[value] : null;
addKnownPort(knownPorts, seenPorts, role, value,
L.isObject(deviceInfo) && isString(deviceInfo.label) ? { label: deviceInfo.label } : null);