🍉 Sync 2026-07-22 20:35:20

This commit is contained in:
github-actions[bot] 2026-07-22 20:35:20 +08:00
parent 201f770d7f
commit 9df7ccb682
41 changed files with 179 additions and 2199 deletions

View File

@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=dae
PKG_VERSION:=2026.07.21
PKG_RELEASE:=20
PKG_VERSION:=2026.07.22
PKG_RELEASE:=21
PKG_SOURCE:=dae-src-2026.07.21-f6edc5e8af8c.tar.gz
PKG_SOURCE:=dae-src-2026.07.22-1ff7a6abc0ad.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip

View File

@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=daed
PKG_VERSION:=2026.07.21
PKG_RELEASE:=28
PKG_VERSION:=2026.07.22
PKG_RELEASE:=29
PKG_SOURCE:=daed-src-2026.07.21-7ab100147a57.tar.gz
PKG_SOURCE:=daed-src-2026.07.22-4a6a3994ced1.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip

View File

@ -8,10 +8,10 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=golang1.26
GO_VERSION_MAJOR_MINOR:=1.26
GO_VERSION_PATCH:=4
GO_VERSION_PATCH:=5
GO_VERSION_RC:=
GO_BOOTSTRAP_VERSION:=bootstrap
PKG_HASH:=4f668a32fbfc1132e6a881fb968c2f1dada631492a339211735fbb255a42602d
PKG_HASH:=495be4bc87176ac567392e5b4116abd98466d33d7b49d41e764ccc6976b2dc42
PKG_VERSION:=$(GO_VERSION_MAJOR_MINOR)$(if $(GO_VERSION_RC),.0)$(if $(GO_VERSION_PATCH),.$(GO_VERSION_PATCH))
PKG_FILE_VERSION:=$(GO_VERSION_MAJOR_MINOR)$(if $(GO_VERSION_RC),rc$(GO_VERSION_RC))$(if $(GO_VERSION_PATCH),.$(GO_VERSION_PATCH))

View File

@ -1,15 +0,0 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-csshnpd
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Chris Swan <chris@atsign.com>
PKG_RELEASE:=1
LUCI_TITLE:=NoPorts Web UI
LUCI_DESCRIPTION:=LuCI config app for NoPorts daemon (csshnpd)
LUCI_DEPENDS:=+luci-base +csshnpd
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -1,88 +0,0 @@
'use strict';
'require view';
'require form';
function validateAtsign(section_id, value) {
if (value.length < 1) {
return _('Must not be empty and should start with @ (e.g., "@a").');
}
return true;
}
function validateDevice(section_id, value) {
if (value.length < 1) {
return _('Must be at least one character long (e.g., "a").');
}
if (value.length == 1) {
if (!/^[a-z]+$/.test(value)) {
return _('First character should be a lowercase letter (e.g., "a").');
} else {
return true;
}
}
if (!/^[a-z][a-z0-9_-]+$/.test(value)) {
return _('Device names may contain a-z 0-9 _ or - (e.g., "my_thing1").');
}
if (value.length > 36) {
return _('Maximum device name length is 36 characters.');
}
return true;
}
function validateOTP(section_id, value) {
if (value.length != 6) {
return _('Must be six characters (e.g., "S3CR3T").');
}
return true;
}
function firstAt(section_id, value) {
if (value && !value.startsWith('@')) {
value = '@' + value; // Ensure @ at start
}
return this.super('write', [section_id, value]);
}
return view.extend({
render: function() {
let m, s, o;
m = new form.Map('sshnpd', _('NoPorts'),
_('Daemon Configuration'));
s = m.section(form.TypedSection, 'sshnpd', _('sshnpd config'));
s.anonymous = true;
o = s.option(form.Value, 'atsign', _('Device atSign'),
_('The device atSign e.g. @device'));
o.default = '@device';
o.validate = validateAtsign;
o.write = firstAt;
o = s.option(form.Value, 'manager', _('Manager atSign'),
_('The manager atSign e.g. @manager'));
o.default = '@manager';
o.validate = validateAtsign;
o.write = firstAt;
o = s.option(form.Value, 'device', _('Device name'),
_('The name for this device e.g. openwrt'));
o.default = 'openwrt';
o.validate = validateDevice;
s.option(form.Value, 'args', _('Additional arguments'),
_('Further command line arguments for the NoPorts daemon'));
o = s.option(form.Value, 'otp', _('Enrollment OTP/SPP'),
_('One Time Passcode (OTP) for device atSign enrollment'));
o.default = '000000';
o.validate = validateOTP;
o = s.option(form.Flag, 'enabled', _('Enabled'),
_('Check here to enable the service'));
o.default = '1';
o.rmempty = false;
return m.render();
},
});

View File

@ -1,97 +0,0 @@
'use strict';
'require view';
'require dom';
'require fs';
'require ui';
'require uci';
'require network';
return view.extend({
handleCommand: function(exec, args) {
let buttons = document.querySelectorAll('.diag-action > .cbi-button');
for (let i = 0; i < buttons.length; i++)
buttons[i].setAttribute('disabled', 'true');
return fs.exec(exec, args).then(function(res) {
let out = document.querySelector('textarea');
dom.content(out, [ res.stdout || '', res.stderr || '' ]);
}).catch(function(err) {
ui.addNotification(null, E('p', [ err ]))
}).finally(function() {
for (let i = 0; i < buttons.length; i++)
buttons[i].removeAttribute('disabled');
});
},
handleEnroll: function() {
return this.handleCommand('at_enroll.sh', "");
},
load: function() {
return uci.load('sshnpd').then(function() {
let atsign = uci.get_first('sshnpd','','atsign'),
keyfile = '/root/.atsign/keys/'+atsign+'_key.atKeys';
return L.resolveDefault(fs.stat(keyfile), {});
});
},
render: function(res) {
const has_atkey = res.path;
const atsign = uci.get_first('sshnpd','','atsign');
const device = uci.get_first('sshnpd','','device');
const otp = uci.get_first('sshnpd','','otp');
const enrollready = atsign && device && otp && !has_atkey;
const instructions = E('div', { 'class': 'cbi-map-descr'}, _('Press the Enroll button then run this command on a system where '+atsign+' is activated:'));
const enrollcmd = E('code','at_activate approve -a '+atsign+' --arx noports --drx '+device);
let table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, [
E('span', { 'class': 'diag-action' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': ui.createHandlerFn(this, 'handleEnroll')
}, [ _('Enroll') ])
])
]),
])
]);
const cmdwindow = E('div', {'class': 'cbi-section'}, [
E('div', { 'id' : 'command-output'},
E('textarea', {
'id': 'widget.command-output',
'style': 'width: 100%; font-family:monospace; white-space:pre',
'readonly': true,
'wrap': 'on',
'rows': '20'
})
)
]);
let view = E('div', { 'class': 'cbi-map'}, [
E('h2', {}, [ _('NoPorts atSign Enrollment') ]),
atsign ? E([]) : E('div', { 'class': 'cbi-map-descr'}, _('atSign must be configured')),
device ? E([]) : E('div', { 'class': 'cbi-map-descr'}, _('Device must be configured')),
otp ? E([]) : E('div', { 'class': 'cbi-map-descr'}, _('OTP must be configured. An OTP can be generated using:')),
otp ? E([]) : E('code','at_activate otp -a '+atsign),
has_atkey ? E('div', { 'class': 'cbi-map-descr'}, _('Existing key found at: '+has_atkey)) : E([]),
enrollready ? instructions : E([]),
enrollready ? enrollcmd : E([]),
enrollready ? table : E([]),
enrollready ? cmdwindow : E([]),
]);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -1,30 +0,0 @@
{
"admin/network/sshnpd": {
"title": "NoPorts",
"order": 70,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-csshnpd" ]
}
},
"admin/network/sshnpd/config": {
"title": "NoPorts Config",
"order": 1,
"action": {
"type": "view",
"path": "sshnpd/config"
}
},
"admin/network/sshnpd/enroll": {
"title": "NoPorts Enrollment",
"order": 2,
"action": {
"type": "view",
"path": "sshnpd/enroll"
}
}
}

View File

@ -1,15 +0,0 @@
{
"luci-app-csshnpd": {
"description": "Grant UCI access for luci-app-csshnpd",
"read": {
"uci": [ "sshnpd" ],
"file": {
"/usr/bin/at_enroll.sh": ["exec"],
"/root/.atsign/keys/*": ["stat"]
}
},
"write": {
"uci": [ "sshnpd" ]
}
}
}

View File

@ -171,9 +171,9 @@ return view.extend({
.catch(function(e) { ui.addNotification(null, E('p', e.message)) });
},
HandleStopDDnsRule(m, section_id, ev) {
handleStopDDnsRule(m, section_id, ev) {
return fs.exec('/usr/lib/ddns/dynamic_dns_lucihelper.sh',
[ '-S', section_id, '--', 'start' ])
[ '-S', section_id, '--', 'stop' ])
.then(L.bind(m.render, m))
.catch(function(e) { ui.addNotification(null, E('p', e.message)) });
},
@ -551,7 +551,7 @@ return view.extend({
},
stop_opt = {
'class': 'cbi-button cbi-button-neutral stop',
'click': ui.createHandlerFn(_this, 'HandleStopDDnsRule', m, section_id),
'click': ui.createHandlerFn(_this, 'handleStopDDnsRule', m, section_id),
'title': _('Stop this service'),
};

View File

@ -15,7 +15,12 @@ const luci_helper = '/usr/lib/ddns/dynamic_dns_lucihelper.sh';
const ddns_version_file = '/usr/share/ddns/version';
function shellquote(value) {
if (value == null)
value = '';
return "'" + replace(value, "'", "'\\''") + "'";
}
function get_dateformat() {
return uci.get('ddns', 'global', 'ddns_dateformat') || '%F %R';
@ -126,9 +131,9 @@ const methods = {
if (forceDnsTcp == 1) push(command, '-t');
// if (isGlue == 1) push(command, '-g');
push(command, '-l', lookupHost);
push(command, '-S', section);
if (length(dnsServer) > 0) push(command, '-d', dnsServer);
push(command, '-l', shellquote(lookupHost));
push(command, '-S', shellquote(section));
if (length(dnsServer) > 0) push(command, '-d', shellquote(dnsServer));
push(command, '-- get_registered_ip');
const result = system(`${join(' ', command)}`);
@ -221,50 +226,40 @@ const methods = {
const hasCommand = (command) => { return (system(`command -v ${command} 1>/dev/null`) == 0) ? true : false };
const hasWget = () => hasCommand('wget');
const hasWget = () => {
return cache.has_wget ??= hasCommand('wget');
}
const hasWgetSsl = () => {
if (cache['has_wgetssl']) return cache['has_wgetssl'];
const result = hasWget() && system(`wget 2>&1 | grep -iqF 'https'`) == 0 ? true : false;
cache['has_wgetssl'] = result;
return result;
return cache.has_wgetssl ??= hasWget() && system(`wget 2>&1 | grep -iqF 'https'`) == 0 ? true : false;
};
const hasGNUWgetSsl = () => {
if (cache['has_gnuwgetssl']) return cache['has_gnuwgetssl'];
const result = hasWget() && system(`wget -V 2>&1 | grep -iqF '+https'`) == 0 ? true : false;
cache['has_gnuwgetssl'] = result;
return result;
return cache.has_gnuwgetssl ??= hasWget() && system(`wget -V 2>&1 | grep -iqF '+https'`) == 0 ? true : false;
};
const hasCurl = () => {
if (cache['has_curl']) return cache['has_curl'];
const result = hasCommand('curl');
cache['has_curl'] = result;
return result;
return cache.has_curl ??= hasCommand('curl');
};
const hasCurlSsl = () => {
return system(`curl -V 2>&1 | grep -qF 'https'`) == 0 ? true : false;
return cache.has_curl_ssl ??= system(`curl -V 2>&1 | grep -qF 'https'`) == 0 ? true : false;
};
const hasFetch = () => {
if (cache['has_fetch']) return cache['has_fetch'];
const result = hasCommand('uclient-fetch');
cache['has_fetch'] = result;
return result;
return cache.has_fetch ??= hasCommand('uclient-fetch');
};
const hasFetchSsl = () => {
return stat('/lib/libustream-ssl.so') == 0 ? true : false;
return cache.has_fetch_ssl ??= stat('/lib/libustream-ssl.so') ? true : false;
};
const hasCurlPxy = () => {
return system(`grep -i 'all_proxy' /usr/lib/libcurl.so*`) == 0 ? true : false;
return cache.has_curl_proxy ??= system(`grep -i 'all_proxy' /usr/lib/libcurl.so*`) == 0 ? true : false;
};
const hasBbwget = () => {
return system(`wget -V 2>&1 | grep -iqF 'busybox'`) == 0 ? true : false;
return cache.has_bbwget ??= system(`wget -V 2>&1 | grep -iqF 'busybox'`) == 0 ? true : false;
};

View File

@ -1,19 +0,0 @@
#
# Copyright (C) 2022 Jaymin Patel <jem.patel@gmail.com>
#
# This is free software, licensed under the GNU General Public License v2.
#
include $(TOPDIR)/rules.mk
PKG_LICENSE:=GPL-2.0-or-later
PKG_MAINTAINER:=Jaymin Patel <jem.patel@gmail.com>
LUCI_TITLE=Luci Application for IPSec VPN (Libreswan)
LUCI_DEPENDS:=+luci-base +libreswan
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -1,71 +0,0 @@
'use strict';
'require view';
'require form';
'require network';
'require tools.widgets as widgets';
return view.extend({
load() {
return Promise.all([
network.getDevices(),
]);
},
render([netDevs]) {
let m, s, o;
m = new form.Map('libreswan', _('IPSec Global Settings'));
s = m.section(form.NamedSection, 'globals', 'libreswan');
s.anonymous = false;
s.addremove = false;
o = s.option(form.ListValue, 'debug', _('Debug Logs'));
o.default = false;
o.rmempty = false;
o.value('none', _('none - No Logging'));
o.value('base', _('base - Moderate Logging'));
o.value('cpu-usage', _('cpu-usage - Timing/Load Logging'));
o.value('crypto', _('crypto - All crypto related Logging'));
o.value('tmi', _('tmi - Too Much/Excessive Logging'));
o.value('private', _('private - Sensitive private-key/password Logging'));
o.default = 'none'
o = s.option(form.Flag, 'uniqueids', _('Uniquely Identify Remotes'),
_('Whether IDs should be considered identifying remote parties uniquely'));
o.default = false;
o.rmempty = false;
o = s.option(widgets.NetworkSelect, 'listen_interface', _('Listen Interface'),
_('Interface for IPsec to use'));
o.datatype = 'string';
o.multiple = false;
o.optional = true;
o = s.option(form.Value, 'listen', _('Listen Address'),
_('IP address to listen on, default depends on Listen Interface'));
o.datatype = 'ip4addr';
for (let nd of netDevs) {
var addrs = nd.getIPAddrs();
for (let ad of addrs) {
o.value(ad.split('/')[0]);
}
}
o.depends({ 'listen_interface' : '' });
o = s.option(form.Value, 'nflog_all', _('Enable nflog on nfgroup'),
_('NFLOG group number to log all pre-crypt and post-decrypt traffic to'));
o.datatype = 'uinteger';
o.default = 0;
o.rmempty = true;
o.optional = true;
o = s.option(form.DynamicList, 'virtual_private', _('Allowed Virtual Private'),
_('The address ranges that may live behind a NAT router through which a client connects'));
o.datatype = 'neg(ip4addr)';
o.multiple = true;
o.optional = true;
return m.render();
}
});

View File

@ -1,82 +0,0 @@
'use strict';
'require view';
'require rpc';
'require form';
'require poll';
const callLibreswanStatus = rpc.declare({
object: 'libreswan',
method: 'status',
expect: { },
});
function secondsToString(seconds) {
const numdays = Math.floor(seconds / 86400);
seconds %= 86400;
const numhours = Math.floor(seconds / 3600);
seconds %= 3600;
const numminutes = Math.floor(seconds / 60);
const numseconds = seconds % 60;
return [
numdays ? `${numdays}d` : '',
numhours ? `${numhours}h` : '',
numminutes ? `${numminutes}m` : '',
`${numseconds}s`
].filter(Boolean).join(' ');
}
return view.extend({
render: function() {
const table =
E('table', { 'class': 'table lases' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Name')),
E('th', { 'class': 'th' }, _('Remote')),
E('th', { 'class': 'th' }, _('Local Subnet')),
E('th', { 'class': 'th' }, _('Remote Subnet')),
E('th', { 'class': 'th' }, _('Tx')),
E('th', { 'class': 'th' }, _('Rx')),
E('th', { 'class': 'th' }, _('Phase1')),
E('th', { 'class': 'th' }, _('Phase2')),
E('th', { 'class': 'th' }, _('Status')),
E('th', { 'class': 'th' }, _('Uptime')),
E([])
])
]);
poll.add(function() {
return callLibreswanStatus().then(function(tunnelsInfo) {
const tunnels = Array.isArray(tunnelsInfo.tunnels) ? tunnelsInfo.tunnels : [];
cbi_update_table(table,
tunnels.map(function(tunnel) {
return [
tunnel.name,
tunnel.right,
tunnel.leftsubnet,
tunnel.rightsubnet,
tunnel.tx,
tunnel.rx,
tunnel.phase1 ? _('Up') : _('Down'),
tunnel.phase2 ? _('Up') : _('Down'),
tunnel.connected ? _('Up') : _('Down'),
secondsToString(tunnel.uptime),
];
}),
E('em', _('There are no active Tunnels'))
);
});
});
return E([
E('h3', _('IPSec Tunnels Summary')),
E('br'),
table
]);
},
handleSave: null,
handleSaveApply:null,
handleReset: null
});

View File

@ -1,76 +0,0 @@
'use strict';
'require view';
'require ui';
'require form';
'require uci';
return view.extend({
render() {
let m, s, o;
m = new form.Map('libreswan', _('IPSec Proposals'));
s = m.section(form.GridSection, 'crypto_proposal');
s.anonymous = false;
s.addremove = true;
s.nodescriptions = true;
s.addbtntitle = _('Add Proposal');
s.renderSectionAdd = function(extra_class) {
const el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments),
nameEl = el.querySelector('.cbi-section-create-name');
ui.addValidator(nameEl, 'uciname', true, function(v) {
let sections = [
...uci.sections('libreswan', 'crypto_proposal'),
...uci.sections('libreswan', 'tunnel'),
];
if (sections.find(function(s) {
return s['.name'] == v;
})) {
return _('This may not share the same name as other proposals or configured tunnels.');
}
if (v.length > 15) return _('Name length shall not exceed 15 characters');
return true;
}, 'blur', 'keyup');
return el;
};
o = s.tab('general', _('General'));
o = s.taboption('general', form.MultiValue, 'hash_algorithm', _('Hash Algorithm'), ('* = %s').format(_('Unsafe')));
o.default = 'md5';
o.value('md5', _('MD5*'));
o.value('sha1', _('SHA1*'));
o.value('sha256', _('SHA256'));
o.value('sha384', _('SHA384'));
o.value('sha512', _('SHA512'));
o = s.taboption('general', form.MultiValue, 'encryption_algorithm', _('Encryption Method'), ('* = %s').format(_('Unsafe')));
o.default = 'aes';
o.value('3des', _('3DES*'))
o.value('aes', _('AES'))
o.value('aes_ctr', _('AES_CTR'));
o.value('aes_cbc', _('AES_CBC'));
o.value('aes128', _('AES128'));
o.value('aes192', _('AES192'));
o.value('aes256', _('AES256'));
o.value('camellia_cbc', _('CAMELLIA_CBC'));
o = s.taboption('general', form.MultiValue, 'dh_group', _('DH Group'),
('* = %s <a href="%s">RFC8247</a>.').format(_('Unsafe, See'), 'https://www.rfc-editor.org/rfc/rfc8247#section-2.4'));
o.default = 'modp1536';
o.value('modp1536', _('DH Group 5*'));
o.value('modp2048', _('DH Group 14'));
o.value('modp3072', _('DH Group 15'));
o.value('modp4096', _('DH Group 16'));
o.value('modp6144', _('DH Group 17'));
o.value('modp8192', _('DH Group 18'));
o.value('dh19', _('DH Group 19'));
o.value('dh20', _('DH Group 20'));
o.value('dh21', _('DH Group 21'));
o.value('dh22', _('DH Group 22*'));
o.value('dh31', _('DH Group 31'));
return m.render();
}
});

View File

@ -1,274 +0,0 @@
'use strict';
'require view';
'require form';
'require ui';
'require uci';
'require network';
'require validation';
'require tools.widgets as widgets';
function calculateNetwork(addr, mask) {
const parsedAddr = validation.parseIPv4(String(addr));
if (parsedAddr == null) return null;
const parsedMask = !isNaN(mask)
? validation.parseIPv4(network.prefixToMask(+mask))
: validation.parseIPv4(String(mask));
if (parsedMask == null) return null;
const networkAddr = parsedAddr.map((byte, i) => byte & (parsedMask[i] >>> 0 & 255));
return `${networkAddr.join('.')}/${network.maskToPrefix(parsedMask.join('.'))}`;
}
return view.extend({
load() {
return Promise.all([
network.getDevices(),
uci.load('libreswan'),
]);
},
render([netDevs]) {
let m, s, o;
let proposals = uci.sections('libreswan', 'crypto_proposal');
if (proposals == '') {
ui.addNotification(null, E('p', _('Proposals must be configured for Tunnels')));
return;
}
m = new form.Map('libreswan', 'IPSec Tunnels');
s = m.section(form.GridSection, 'tunnel');
s.anonymous = false;
s.addremove = true;
s.nodedescription = false;
s.addbtntitle = _('Add Tunnel');
s.renderSectionAdd = function(extra_class) {
const el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments);
const nameEl = el.querySelector('.cbi-section-create-name');
ui.addValidator(nameEl, 'uciname', true, function(v) {
let sections = [
...uci.sections('libreswan', 'crypto_proposal'),
...uci.sections('libreswan', 'tunnel'),
];
if (sections.find(function(s) {
return s['.name'] == v;
})) {
return _('This may not share the same name as other proposals or configured tunnels.');
}
if (v.length > 15) return _('Name length shall not exceed 15 characters');
return true;
}, 'blur', 'keyup');
return el;
};
o = s.tab('general', _('General'));
o = s.tab('authentication', _('Authentication'));
o = s.tab('interface', _('Interface'));
o = s.tab('advanced', _('Advanced'));
o = s.taboption('general', form.ListValue, 'auto', _('Mode'));
o.default = 'start';
o.value('add', _('Listen'));
o.value('start', _('Initiate'));
o = s.taboption('general', widgets.NetworkSelect, 'left_interface', _('Left Interface'));
o.datatype = 'string';
o.multiple = false;
o.optional = true;
o = s.taboption('general', form.Value, 'left', _('Left IP/Device'));
o.datatype = 'or(string, ipaddr)';
netDevs.forEach(netDev => {
netDev.getIPAddrs().forEach(addr => {
o.value(addr.split('/')[0]);
});
});
netDevs.forEach(netDev => {
o.value(`%${netDev.device}`);
});
o.value('%defaultroute');
o.optional = false;
o.depends({ 'left_interface' : '' });
o = s.taboption('general', form.Value, 'leftid', _('Left ID'));
o.datatype = 'string';
o.value('%any');
o.modalonly = true;
o = s.taboption('general', form.Value, 'right', _('Remote IP'));
o.datatype = 'or(string, ipaddr)';
o.value('0.0.0.0');
o.value('%any');
o.optional = false;
o = s.taboption('general', form.Value, 'rightid', _('Right ID'));
o.datatype = 'string';
o.value('%any');
o.modalonly = true;
o = s.taboption('general', form.Value, 'leftsourceip', _('Local Source IP'));
o.datatype = 'ipaddr';
netDevs.forEach(netDev => {
netDev.getIPAddrs().forEach(addr => {
o.value(addr.split('/')[0]);
});
});
o.optional = false;
o.modalonly = true;
o = s.taboption('general', form.Value, 'rightsourceip', _('Remote Source IP'));
o.datatype = 'ipaddr';
o.optional = false;
o.modalonly = true;
o = s.taboption('general', form.DynamicList, 'leftsubnets', _('Local Subnets'));
o.datatype = 'ipaddr';
for (let nd of netDevs) {
const addrs = nd.getIPAddrs();
for (let ad of addrs) {
const subnet = calculateNetwork(ad.split('/')[0], ad.split('/')[1]);
if (subnet) {
o.value(subnet);
}
}
}
o.value('0.0.0.0/0');
o = s.taboption('general', form.DynamicList, 'rightsubnets', _('Remote Subnets'));
o.datatype = 'ipaddr';
o.value('0.0.0.0/0');
o = s.taboption('authentication', form.ListValue, 'authby', _('Auth Method'));
o.default = 'secret'
o.value('secret', _('Shared Secret'));
o.value('never', 'Never');
o.value('null', 'Null');
o.modalonly = true;
o.optional = false;
o = s.taboption('authentication', form.Value, 'psk', _('Preshared Key'));
o.datatype = 'and(string, minlength(8))'
o.depends({ 'authby' : 'secret' });
o.password = true;
o.modalonly = true;
o.optional = false;
o = s.taboption('advanced', form.ListValue, 'ikev2', _('IKE V2'));
o.default = 'yes';
o.value('yes', _('IKE Version 2'));
o.value('no', _('IKE Version 1'));
o.modalonly = true;
o = s.taboption('advanced', form.MultiValue, 'ike', _('Phase1 Proposals'));
for (let prop of proposals) {
o.value(prop['.name']);
}
o.modalonly = true;
function timevalidate(section_id, value) {
if (!/^[0-9]{1,3}[smhd]$/.test(value)) {
return _('Acceptable values are an integer followed by m, h, d');
}
return true;
}
o = s.taboption('advanced', form.Value, 'ikelifetime', _('IKE Life Time'), _('Acceptable values are an integer followed by m, h, d'));
o.default = '8h';
o.value('1h', '1h');
o.value('2h', '2h');
o.value('4h', '4h');
o.value('8h', '8h');
o.value('12h', '12h');
o.value('16h', '16h');
o.value('24h', '24h');
o.modalonly = false;
o.modalonly = true;
o.validate = timevalidate;
o = s.taboption('advanced', form.Flag, 'rekey', _('Rekey'));
o.default = false;
o.modalonly = false;
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'rekeymargin', _('Rekey Margin Time'), _('Acceptable values are an integer followed by m, h, d'));
o.default = '9m';
o.value('5m', '5m');
o.value('9m', '9m');
o.value('15m', '15m');
o.value('20m', '20m');
o.value('30m', '30m');
o.value('60m', '60m');
o.modalonly = false;
o.modalonly = true;
o.validate = timevalidate;
o = s.taboption('advanced', form.ListValue, 'dpdaction', _('DPD Action'));
o.default = 'restart';
o.value('none', _('None'));
o.value('clear', _('Clear'));
o.value('hold', _('Hold'));
o.value('restart', _('Restart'));
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'dpddelay', _('DPD Delay'));
o.datatype = 'uinteger';
o.default = 30;
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'dpdtimeout', _('DPD Timeout'));
o.datatype = 'uinteger';
o.default = 150;
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'phase2', _('Phase2 Protocol'));
o.default = 'esp';
o.value('esp', 'ESP');
o.modalonly = true;
o.optional = false;
o = s.taboption('advanced', form.MultiValue, 'phase2alg', _('Phase2 Proposals'));
for (let prop of proposals) {
o.value(prop['.name']);
}
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'nflog', _('Enable nflog on nfgroup'));
o.default = 0;
o.datatype = 'uinteger';
o.rmempty = true;
o.optional = true;
o.modalonly = true;
let interfaces = uci.sections('network', 'interface');
o = s.taboption('advanced', form.ListValue, 'interface', _('Tunnel Interface'),
_('Lists XFRM interfaces in format "ipsecN", N denotes ifid of xfrm interface') + '<br>' +
_('Lists VTI interfaces configured with ikey and okey'));
o.datatype = 'string';
o.rmempty = true;
o.modalonly = true;
o.value('');
interfaces.forEach(iface => {
const { proto, ikey, okey, ifid, ['.name']: name } = iface;
if (proto === "vti" && ikey && okey) {
o.value(name, `VTI - ${name}`);
}
if (proto === "xfrm" && ifid && name.match(`ipsec${ifid}`)) {
o.value(name, `XFRM - ${name}`);
}
});
o = s.taboption('advanced', form.Flag, 'update_peeraddr', _('Update Peer Address'),
_('Auto Update Peer Address of VTI interface'));
o.rmempty = true;
o.modalonly = true;
return m.render();
}
});

View File

@ -1,45 +0,0 @@
{
"admin/vpn/libreswan": {
"title": "Libreswan IPSec",
"order": 90,
"action": {
"type": "firstchild"
}
},
"admin/vpn/libreswan/overview": {
"title": "Overview",
"order": 10,
"action": {
"type": "view",
"path": "libreswan/overview"
}
},
"admin/vpn/libreswan/globals": {
"title": "IPSec Globals",
"order": 20,
"action": {
"type": "view",
"path": "libreswan/globals"
}
},
"admin/vpn/libreswan/proposals": {
"title": "IPSec Proposals",
"order": 30,
"action": {
"type": "view",
"path": "libreswan/proposals"
}
},
"admin/vpn/libreswan/tunnels": {
"title": "IPSec Tunnels",
"order": 40,
"action": {
"type": "view",
"path": "libreswan/tunnels"
}
}
}

View File

@ -1,14 +0,0 @@
{
"luci-app-libreswan" : {
"description" : "Grant access to LuCI app Libreswan IPSec",
"read" : {
"ubus" : {
"libreswan" : [ "*" ]
},
"uci": [ "libreswan" ]
},
"write" : {
"uci": [ "libreswan" ]
}
}
}

View File

@ -1,4 +0,0 @@
{
"config": "libreswan",
"init": "ipsec"
}

View File

@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.7.16
PKG_RELEASE:=189
PKG_RELEASE:=190
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \

View File

@ -1049,7 +1049,7 @@ function gen_config(var)
api.log(" - 加载 Xray 负载均衡 节点【" .. (_node.remarks or "") .. "】,子节点数量:" .. #(blc_nodes or {}))
local valid_nodes = {}
local valid_nodes, valid_fallback = {}, true
for i = 1, #(blc_nodes or {}) do
local blc_node_id = blc_nodes[i]
local blc_node_tag = "blc-" .. blc_node_id
@ -1067,12 +1067,16 @@ function gen_config(var)
valid_nodes[#valid_nodes + 1] = outboundTag
end
end
-- Check if balancing node duplicates fallback node
if _node.fallback_node == blc_node_id then
valid_fallback = false
end
end
if #valid_nodes == 0 then return nil end
-- fallback node
local fallback_node_id = _node.fallback_node
fallback_node_id = (fallback_node_id and fallback_node_id ~= "") and fallback_node_id or nil
fallback_node_id = (valid_fallback and fallback_node_id and fallback_node_id ~= "") and fallback_node_id or nil
local fallback_node_tag = (fallback_node_id == "_direct") and "direct" or "blackhole"
if fallback_node_id and fallback_node_id ~= "_direct" then
local is_new_node = true

View File

@ -1,17 +0,0 @@
# Copyright 2021 Nicholas Smith (nicholas@nbembedded.com)
# Copyright (C) 2023 TDT AG <development@tdt.de>
#
# This is free software, licensed under the GNU General Public License v2.
include $(TOPDIR)/rules.mk
PKG_LICENSE:=GPL-2.0-or-later
PKG_MAINTAINER:=Nicholas Smith <nicholas@nbembedded.com>, Lukas Voegl <lvoegl@tdt.de>
LUCI_TITLE:=LuCI support for strongSwan via swanctl
LUCI_DESCRIPTION:=Status and configuration for strongSwan based on swanctl
LUCI_DEPENDS:=+strongswan-swanctl +swanmon
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -1,143 +0,0 @@
'use strict';
'require baseclass';
return baseclass.extend({
_encryptionAlgorithms: new Map([
['3des', true],
['cast128', true],
['blowfish128', true],
['blowfish192', true],
['blowfish256', true],
['null', true],
['aes128'],
['aes192'],
['aes256'],
['aes128ctr'],
['aes192ctr'],
['aes256ctr'],
['camellia128'],
['camellia192'],
['camellia256'],
['camellia128ctr'],
['camellia192ctr'],
['camellia256ctr']
]),
_authenticatedEncryptionAlgorithms: new Map([
['aes128ccm64'],
['aes192ccm64'],
['aes256ccm64'],
['aes128ccm96'],
['aes192ccm96'],
['aes256ccm96'],
['aes128ccm128'],
['aes192ccm128'],
['aes256ccm128'],
['aes128gcm64'],
['aes192gcm64'],
['aes256gcm64'],
['aes128gcm96'],
['aes192gcm96'],
['aes256gcm96'],
['aes128gcm128'],
['aes192gcm128'],
['aes256gcm128'],
['aes128gmac'],
['aes192gmac'],
['aes256gmac'],
['camellia128ccm64'],
['camellia192ccm64'],
['camellia256ccm64'],
['camellia128ccm96'],
['camellia192ccm96'],
['camellia256ccm96'],
['camellia128ccm128'],
['camellia192ccm128'],
['camellia256ccm128'],
['chacha20poly1305']
]),
_hashAlgorithms: new Map([
['md5', true],
['md5_128', true],
['sha1', true],
['sha1_160', true],
['aesxcbc'],
['aescmac'],
['aes128gmac'],
['aes192gmac'],
['aes256gmac'],
['sha256'],
['sha384'],
['sha512'],
['sha256_96']
]),
_dhAlgorithms: new Map([
['modp768', true],
['modp1024', true],
['modp1536', true],
['modp2048'],
['modp3072'],
['modp4096'],
['modp6144'],
['modp8192'],
['modp1024s160', true],
['modp2048s224', true],
['modp2048s256', true],
['ecp192', true],
['ecp224'],
['ecp256'],
['ecp384'],
['ecp521'],
['ecp224bp'],
['ecp256bp'],
['ecp384bp'],
['ecp512bp'],
['curve25519'],
['curve448']
]),
_prfAlgorithms: new Map([
['prfmd5', true],
['prfsha1', true],
['prfaesxcbc'],
['prfaescmac'],
['prfsha256'],
['prfsha384'],
['prfsha512']
]),
_getAlgorithmNames(algorithms) {
return Array.from(algorithms.keys());
},
isInsecure(algorithmName) {
return this._encryptionAlgorithms.get(algorithmName) == true
|| this._authenticatedEncryptionAlgorithms.get(algorithmName) == true
|| this._hashAlgorithms.get(algorithmName) == true
|| this._dhAlgorithms.get(algorithmName) == true
|| this._prfAlgorithms.get(algorithmName) == true;
},
getEncryptionAlgorithms() {
return this._getAlgorithmNames(this._encryptionAlgorithms);
},
getAuthenticatedEncryptionAlgorithms() {
return this._getAlgorithmNames(this._authenticatedEncryptionAlgorithms);
},
getHashAlgorithms() {
return this._getAlgorithmNames(this._hashAlgorithms);
},
getDiffieHellmanAlgorithms() {
return this._getAlgorithmNames(this._dhAlgorithms);
},
getPrfAlgorithms() {
return this._getAlgorithmNames(this._prfAlgorithms);
}
});

View File

@ -1,189 +0,0 @@
'use strict';
'require view';
'require dom';
'require poll';
'require fs';
'require ui';
function formatTime(seconds, selectCount) {
const days = Math.floor(seconds / (60 * 60 * 24));
const hours = Math.floor(seconds / (60 * 60)) % 24;
const minutes = Math.floor(seconds / 60) % 60;
seconds = Math.floor(seconds % 60);
const times = [
[days, _('Day'), _('Days')],
[hours, _('Hour'), _('Hours')],
[minutes, _('Minute'), _('Minutes')],
[seconds, _('Second'), _('Seconds')]
].filter(function ([time, singular, plural]) {
return time > 0;
});
const selectedTimes = times.slice(0, selectCount);
return selectedTimes.map(function ([time, singular, plural]) {
const unit = time > 1 ? plural : singular;
return '%d %s'.format(time, unit);
}).join(', ');
}
function buildSection(name, table) {
return E('div', { 'class': 'cbi-section' }, [
E('h2', [name]),
table
]);
}
function buildTable(rows) {
return E('table', { 'class': 'table', }, rows);
}
function buildKeyValueTable(kvPairs) {
const rows = kvPairs.map(function (row) {
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'width': '33%' }, E('strong', [row[0]])),
E('td', { 'class': 'td' }, [row[1]])
]);
});
return buildTable(rows);
}
function collectErrorMessages(results) {
const errorMessages = results.reduce(function (messages, result) {
return messages.concat(result.errors.map(function (error) {
return error.message;
}));
}, []);
const uniqueErrorMessages = new Set(errorMessages);
return [...uniqueErrorMessages];
}
return view.extend({
load() {
return Promise.all([
fs.exec_direct('/usr/sbin/swanmon', ['version'], 'json'),
fs.exec_direct('/usr/sbin/swanmon', ['stats'], 'json'),
fs.exec_direct('/usr/sbin/swanmon', ['list-sas'], 'json')
]);
},
pollData(container) {
poll.add(L.bind(function () {
return this.load().then(L.bind(function (results) {
dom.content(container, this.renderContent(results));
}, this));
}, this));
},
renderContent(results) {
const node = E('div', [E('div')]);
const firstNode = node.firstElementChild;
const errorMessages = collectErrorMessages(results);
if (errorMessages.length > 0) {
const messageEls = errorMessages.map(function (message) {
return E('li', message);
});
firstNode.appendChild(E('h4', _('Querying strongSwan failed')));
firstNode.appendChild(E('ul', messageEls));
return node;
}
const [version, stats, sas] = results.map(function (r) {
return r.data;
});
const uptimeSeconds = (new Date() - new Date(stats.uptime.since)) / 1000;
const statsSection = buildSection(_('Stats'), buildKeyValueTable([
[_('Version'), version.version],
[_('Uptime'), formatTime(uptimeSeconds, 2)],
[_('Daemon'), version.daemon],
[_('Active IKE_SAs'), stats.ikesas.total],
[_('Half-Open IKE_SAs'), stats.ikesas['half-open']]
]));
firstNode.appendChild(statsSection);
const tableRows = sas.map(function (conn) {
const name = Object.keys(conn)[0];
const data = conn[name];
const childSas = [];
Object.entries(data['child-sas']).forEach(function ([name, data]) {
const table = buildKeyValueTable([
[_('State'), data.state],
[_('Mode'), data.mode],
[_('Protocol'), data.protocol],
[_('Local Traffic Selectors'), data['local-ts'].join(', ')],
[_('Remote Traffic Selectors'), data['remote-ts'].join(', ')],
[_('Encryption Algorithm'), data['encr-alg']],
[_('Encryption Keysize'), data['encr-keysize']],
[_('Bytes in'), data['bytes-in']],
[_('Bytes out'), data['bytes-out']],
[_('Life Time'), formatTime(data['life-time'], 2)],
[_('Install Time'), formatTime(data['install-time'], 2)],
[_('Rekey in'), formatTime(data['rekey-time'], 2)],
[_('SPI in'), data['spi-in']],
[_('SPI out'), data['spi-out']]
]);
childSas.push(E('div', { 'class': 'cbi-section' }, [
E('h4', { 'style': 'margin-top: 0; padding-top: 0;' }, [name]),
table
]));
});
childSas.push(E('button', {
'class': 'btn cbi-button cbi-button-apply',
'click': ui.hideModal
}, _('Close')));
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, [name]),
E('td', { 'class': 'td' }, [data.state]),
E('td', { 'class': 'td' }, [data['remote-host']]),
E('td', { 'class': 'td' }, [data.version]),
E('td', { 'class': 'td' }, [formatTime(data.established, 2)]),
E('td', { 'class': 'td' }, [formatTime(data['reauth-time'], 2)]),
E('td', { 'class': 'td' }, [E('button', {
'class': 'btn cbi-button cbi-button-apply',
'click': function (ev) {
ui.showModal(_('CHILD_SAs'), childSas)
}
}, _('Show Details'))])
]);
});
const connSection = buildSection(_('Security Associations (SAs)'), buildTable([
E('tr', { 'class': 'tr' }, [
E('th', { 'class': 'th' }, [_('Name')]),
E('th', { 'class': 'th' }, [_('State')]),
E('th', { 'class': 'th' }, [_('Remote')]),
E('th', { 'class': 'th' }, [_('IKE Version')]),
E('th', { 'class': 'th' }, [_('Established for')]),
E('th', { 'class': 'th' }, [_('Reauthentication in')]),
E('th', { 'class': 'th' }, [_('Details')])
]),
...tableRows
]));
firstNode.appendChild(connSection);
return node;
},
render(results) {
const content = E([], [
E('h2', [_('strongSwan Status')]),
E('div')
]);
const container = content.lastElementChild;
dom.content(container, this.renderContent(results));
this.pollData(container);
return content;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -1,435 +0,0 @@
'use strict';
'require view';
'require form';
'require uci';
'require ui';
'require tools.widgets as widgets';
'require strongswan_algorithms';
function validateTimeFormat(section_id, value) {
if (value && !value.match(/^\d+[smhd]$/)) {
return _('Number must have suffix s, m, h or d');
}
return true;
}
function addAlgorithms(o, algorithms) {
algorithms.forEach(function (algorithm) {
if (strongswan_algorithms.isInsecure(algorithm)) {
o.value(algorithm, '%s*'.format(algorithm));
} else {
o.value(algorithm);
}
});
}
function sectionNameCheck(extra_class) {
var el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments),
nameEl = el.querySelector('.cbi-section-create-name');
ui.addValidator(nameEl, 'uciname', true, function(v) {
let sections = [
...uci.sections('ipsec', 'remote'),
...uci.sections('ipsec', 'tunnel'),
...uci.sections('ipsec', 'crypto_proposal'),
];
if (sections.find(function(s) {
return s['.name'] == v;
})) {
return _('Remotes, Encryption Proposals and Tunnels may not share the same names.') + ' ' +
_('Use combinations like tunnel1_phase1 that do not exceed 15 characters.');
}
if (v.length > 15) return _('Name length shall not exceed 15 characters');
return true;
}, 'blur', 'keyup');
return el;
};
return view.extend({
load: function () {
return uci.load('network');
},
render: function () {
let m, s, o;
m = new form.Map('ipsec', _('strongSwan Configuration'),
_('Configure strongSwan for secure VPN connections.'));
m.tabbed = true;
// strongSwan General Settings
s = m.section(form.TypedSection, 'ipsec', _('General Settings'));
s.anonymous = true;
s.addremove = true;
o = s.option(widgets.ZoneSelect, 'zone', _('Zone'),
_('Firewall zone that has to match the defined firewall zone'));
o.default = 'lan';
o.multiple = true;
o = s.option(widgets.NetworkSelect, 'listen', _('Listening Interfaces'),
_('Interfaces that accept VPN traffic'));
o.datatype = 'interface';
o.placeholder = _('Select an interface or leave empty for all interfaces');
o.default = 'wan';
o.multiple = true;
o.rmempty = false;
o = s.option(form.Value, 'debug', _('Debug Level'),
_('Trace level: 0 is least verbose, 4 is most'));
o.default = '0';
o.datatype = 'range(0,4)';
// Remote Configuration
s = m.section(form.GridSection, 'remote', _('Remote Configuration'),
_('Define Remote IKE Configurations.'));
s.addremove = true;
s.nodescriptions = true;
s.renderSectionAdd = sectionNameCheck
o = s.tab('general', _('General'));
o = s.tab('authentication', _('Authentication'));
o = s.tab('advanced', _('Advanced'));
o = s.taboption('general', form.Flag, 'enabled', _('Enabled'),
_('Configuration is enabled or not'));
o.rmempty = false;
o = s.taboption('general', form.Value, 'gateway', _('Gateway (Remote Endpoint)'),
_('IP address or FQDN name of the tunnel remote endpoint'));
o.datatype = 'or(hostname,ipaddr)';
o.rmempty = false;
o = s.taboption('general', form.Value, 'local_gateway', _('Local Gateway'),
_('IP address or FQDN of the tunnel local endpoint'));
o.datatype = 'or(hostname,ipaddr)';
o.modalonly = true;
o = s.taboption('general', form.Value, 'local_sourceip', _('Local Source IP'),
_('Virtual IP(s) to request in IKEv2 configuration payloads requests'));
o.datatype = 'ipaddr';
o.modalonly = true;
o = s.taboption('general', form.Value, 'local_ip', _('Local IP'),
_('Local address(es) to use in IKE negotiation'));
o.datatype = 'ipaddr';
o.modalonly = true;
o = s.taboption('general', form.MultiValue, 'crypto_proposal', _('Crypto Proposal'),
_('List of IKE (phase 1) proposals to use for authentication'));
o.load = function (section_id) {
this.keylist = [];
this.vallist = [];
var sections = uci.sections('ipsec', 'crypto_proposal').filter(function (section) {
return section.is_esp != '1';
});
if (sections.length == 0) {
this.value('', _('Please create a Proposal first'));
} else {
sections.forEach(L.bind(function (section) {
this.value(section['.name']);
}, this));
}
return this.super('load', [section_id]);
};
o.rmempty = false;
o = s.taboption('general', form.MultiValue, 'tunnel', _('Tunnel'),
_('The Tunnel containing the ESP (phase 2) section'));
o.load = function (section_id) {
this.keylist = [];
this.vallist = [];
var sections = uci.sections('ipsec', 'tunnel');
if (sections.length == 0) {
this.value('', _('Please create a Tunnel first'));
} else {
sections.forEach(L.bind(function (section) {
this.value(section['.name']);
}, this));
}
return this.super('load', [section_id]);
};
o.rmempty = false;
o = s.taboption('authentication', form.ListValue, 'authentication_method',
_('Authentication Method'), _('IKE authentication (phase 1)'));
o.modalonly = true;
o.value('psk', 'Pre-shared Key');
o.value('pubkey', 'Public Key');
o = s.taboption('authentication', form.Value, 'local_identifier', _('Local Identifier'),
_('Local identifier for IKE (phase 1)'));
o.datatype = 'string';
o.placeholder = 'C=US, O=Acme Corporation, CN=headquarters';
o.modalonly = true;
o = s.taboption('authentication', form.Value, 'remote_identifier', _('Remote Identifier'),
_('Remote identifier for IKE (phase 1)'));
o.datatype = 'string';
o.placeholder = 'C=US, O=Acme Corporation, CN=soho';
o.modalonly = true;
o = s.taboption('authentication', form.Value, 'pre_shared_key', _('Pre-Shared Key'),
_('The pre-shared key for the tunnel'));
o.datatype = 'string';
o.password = true;
o.modalonly = true;
o.rmempty = false;
o.depends('authentication_method', 'psk');
o = s.taboption('authentication', form.Value, 'local_cert', _('Local Certificate'),
_('Certificate pathname to use for authentication'));
o.datatype = 'file';
o.depends('authentication_method', 'pubkey');
o.modalonly = true;
o = s.taboption('authentication', form.Value, 'local_key', _('Local Key'),
_('Private key pathname to use with above certificate'));
o.datatype = 'file';
o.modalonly = true;
o = s.taboption('authentication', form.Value, 'ca_cert', _('CA Certificate'),
_("CA certificate that need to lie in remote peer's certificate's path of trust"));
o.datatype = 'file';
o.depends('authentication_method', 'pubkey');
o.modalonly = true;
o = s.taboption('advanced', form.Flag, 'mobike', _('MOBIKE'),
_('MOBIKE (IKEv2 Mobility and Multihoming Protocol)'));
o.default = '1';
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'fragmentation', _('IKE Fragmentation'),
_('Use IKE fragmentation'));
o.value('yes');
o.value('no');
o.value('force');
o.value('accept');
o.default = 'yes';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'keyingtries', _('Keying Retries'),
_('Number of retransmissions attempts during initial negotiation'));
o.datatype = 'uinteger';
o.default = '3';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'dpddelay', _('DPD Delay'),
_('Interval to check liveness of a peer'));
o.validate = validateTimeFormat;
o.default = '30s';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'inactivity', _('Inactivity'),
_('Interval before closing an inactive CHILD_SA'));
o.validate = validateTimeFormat;
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'rekeytime', _('Rekey Time'),
_('IKEv2 interval to refresh keying material; also used to compute lifetime'));
o.validate = validateTimeFormat;
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'overtime', _('Overtime'),
_('Limit on time to complete rekeying/reauthentication'));
o.validate = validateTimeFormat;
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'keyexchange', _('Keyexchange'),
_('Version of IKE for negotiation'));
o.value('ikev1', 'IKEv1 (%s)', _('deprecated'));
o.value('ikev2', 'IKEv2');
o.value('ike', 'IKE (%s, %s)'.format(_('both'), _('deprecated')));
o.default = 'ikev2';
o.modalonly = true;
// Tunnel Configuration
s = m.section(form.GridSection, 'tunnel', _('Tunnel Configuration'),
_('Define Connection Children to be used as Tunnels in Remote Configurations.'));
s.addremove = true;
s.nodescriptions = true;
s.renderSectionAdd = sectionNameCheck;
o = s.tab('general', _('General'));
o = s.tab('advanced', _('Advanced'));
o = s.taboption('general', form.DynamicList, 'local_subnet', _('Local Subnet'),
_('Local network(s)'));
o.datatype = 'subnet';
o.placeholder = '192.168.1.1/24';
o.rmempty = false;
o = s.taboption('general', form.DynamicList, 'remote_subnet', _('Remote Subnet'),
_('Remote network(s)'));
o.datatype = 'subnet';
o.placeholder = '192.168.2.1/24';
o.rmempty = false;
o = s.taboption('general', form.Value, 'local_nat', _('Local NAT'),
_('NAT range for tunnels with overlapping IP addresses'));
o.datatype = 'subnet';
o.modalonly = true;
o = s.taboption('general', form.ListValue, 'if_id', ('XFRM Interface ID'),
_('XFRM interface ID set on input and output interfaces'));
o.load = function (section_id) {
this.keylist = [];
this.vallist = [];
var xfrmSections = uci.sections('network').filter(function (section) {
return section.proto == 'xfrm';
});
xfrmSections.forEach(L.bind(function (section) {
this.value(section.ifid,
'%s (%s)'.format(section.ifid, section['.name']));
}, this));
return this.super('load', [section_id]);
}
o.optional = true;
o.modalonly = true;
o = s.taboption('general', form.ListValue, 'startaction', _('Start Action'),
_('Action on initial configuration load'));
o.value('none');
o.value('trap');
o.value('start');
o.default = 'trap';
o.modalonly = true;
o = s.taboption('general', form.ListValue, 'closeaction', _('Close Action'),
_('Action when CHILD_SA is closed'));
o.value('none');
o.value('trap');
o.value('start');
o.optional = true;
o.modalonly = true;
o = s.taboption('general', form.MultiValue, 'crypto_proposal',
_('Crypto Proposal (Phase 2)'),
_('List of ESP (phase two) proposals. Only Proposals with checked ESP flag are selectable'));
o.load = function (section_id) {
this.keylist = [];
this.vallist = [];
var sections = uci.sections('ipsec', 'crypto_proposal').filter(function (section) {
return section.is_esp == '1';
});
if (sections.length == 0) {
this.value('', _('Please create an ESP Proposal first'));
} else {
sections.forEach(L.bind(function (section) {
this.value(section['.name']);
}, this));
}
return this.super('load', [section_id]);
};
o.rmempty = false;
o = s.taboption('advanced', form.Value, 'updown', _('Up/Down Script Path'),
_('Path to script to run on CHILD_SA up/down events'));
o.datatype = 'file';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'lifetime', _('Lifetime'),
_('Maximum duration of the CHILD_SA before closing'));
o.validate = validateTimeFormat;
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'dpdaction', _('DPD Action'),
_('Action when DPD timeout occurs'));
o.value('none');
o.value('clear');
o.value('trap');
o.value('start');
o.optional = true;
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'rekeytime', _('Rekey Time'),
_('Duration of the CHILD_SA before rekeying'));
o.validate = validateTimeFormat;
o.modalonly = true;
o = s.taboption('advanced', form.Flag, 'ipcomp', _('IPComp'),
_('Enable ipcomp compression'));
o.default = '0';
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'hw_offload', _('H/W Offload'),
_('Enable Hardware offload'));
o.value('yes');
o.value('no');
o.value('auto');
o.optional = true;
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'priority', _('Priority'),
_('Priority of the CHILD_SA'));
o.datatype = 'uinteger';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'replay_window', _('Replay Window'),
'%s; %s'.format(_('Replay Window of the CHILD_SA'),
_('Values larger than 32 are supported by the Netlink backend only')));
o.datatype = 'uinteger';
o.modalonly = true;
// Crypto Proposals
s = m.section(form.GridSection, 'crypto_proposal',
_('Encryption Proposals'),
_('Configure Cipher Suites to define IKE (Phase 1) or ESP (Phase 2) Proposals.'));
s.addremove = true;
s.nodescriptions = true;
s.renderSectionAdd = sectionNameCheck;
o = s.option(form.Flag, 'is_esp', _('ESP Proposal'),
_('Whether this is an ESP (phase 2) proposal or not'));
o = s.option(form.ListValue, 'encryption_algorithm',
_('Encryption Algorithm'),
_('Algorithms marked with * are considered insecure'));
o.default = 'aes256gcm128';
addAlgorithms(o, strongswan_algorithms.getEncryptionAlgorithms());
addAlgorithms(o, strongswan_algorithms.getAuthenticatedEncryptionAlgorithms());
o = s.option(form.ListValue, 'hash_algorithm', _('Hash Algorithm'),
_('Algorithms marked with * are considered insecure'));
strongswan_algorithms.getEncryptionAlgorithms().forEach(function (algorithm) {
o.depends('encryption_algorithm', algorithm);
});
o.default = 'sha512';
o.rmempty = false;
addAlgorithms(o, strongswan_algorithms.getHashAlgorithms());
o = s.option(form.ListValue, 'dh_group', _('Diffie-Hellman Group'),
_('Algorithms marked with * are considered insecure'));
o.default = 'modp3072';
addAlgorithms(o, strongswan_algorithms.getDiffieHellmanAlgorithms());
o = s.option(form.ListValue, 'prf_algorithm', _('PRF Algorithm'),
_('Algorithms marked with * are considered insecure'));
o.validate = function (section_id, value) {
var encryptionAlgorithm = this.section.formvalue(section_id, 'encryption_algorithm');
if (strongswan_algorithms.getAuthenticatedEncryptionAlgorithms().includes(
encryptionAlgorithm) && !value) {
return _('PRF Algorithm must be configured when using an Authenticated Encryption Algorithm');
}
return true;
};
o.optional = true;
o.depends('is_esp', '0');
addAlgorithms(o, strongswan_algorithms.getPrfAlgorithms());
return m.render();
}
});

View File

@ -1,28 +0,0 @@
{
"admin/vpn/strongswan-swanctl": {
"title": "strongSwan IPsec",
"order": 90,
"action": {
"type": "view",
"path": "strongswan-swanctl/swanctl"
},
"depends": {
"acl": [
"luci-app-strongswan-swanctl"
]
}
},
"admin/status/strongswan": {
"title": "strongSwan IPsec",
"order": 90,
"action": {
"type": "view",
"path": "strongswan-swanctl/status"
},
"depends": {
"acl": [
"luci-app-strongswan-swanctl"
]
}
}
}

View File

@ -1,16 +0,0 @@
{
"luci-app-strongswan-swanctl": {
"description": "Grant access to luci-app-strongswan-swanctl",
"read": {
"file": {
"/usr/sbin/swanmon version": [ "exec" ],
"/usr/sbin/swanmon stats": [ "exec" ],
"/usr/sbin/swanmon list-sas": [ "exec" ]
},
"uci": [ "ipsec" ]
},
"write": {
"uci": [ "ipsec" ]
}
}
}

View File

@ -1,11 +0,0 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for WiFi Station History
LUCI_DEPENDS:=+luci-base +rpcd-mod-iwinfo +ucode +ucode-mod-fs +ucode-mod-ubus
LUCI_DESCRIPTION:=Track and display history of WiFi associated stations
PKG_LICENSE:=Apache-2.0
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -1,181 +0,0 @@
'use strict';
'require view';
'require poll';
'require rpc';
'require ui';
const callGetHistory = rpc.declare({
object: 'luci.wifihistory',
method: 'getHistory',
expect: { history: {} }
});
const callClearHistory = rpc.declare({
object: 'luci.wifihistory',
method: 'clearHistory',
expect: { result: true }
});
function formatDate(epoch) {
if (!epoch || epoch <= 0)
return '-';
return new Date(epoch * 1000).toLocaleString();
}
return view.extend({
load() {
return callGetHistory();
},
updateTable(table, history) {
const stations = Object.values(history);
/* Sort connected stations first, then by most recently seen */
stations.sort((a, b) => {
if (a.connected !== b.connected)
return a.connected ? -1 : 1;
return (b.last_seen || 0) - (a.last_seen || 0);
});
const rows = [];
for (const s of stations) {
let hint;
if (s.hostname && s.ipv4 && s.ipv6)
hint = '%s (%s, %s)'.format(s.hostname, s.ipv4, s.ipv6);
else if (s.hostname && (s.ipv4 || s.ipv6))
hint = '%s (%s)'.format(s.hostname, s.ipv4 || s.ipv6);
else if (s.ipv4 || s.ipv6)
hint = s.ipv4 || s.ipv6;
else
hint = '-';
let sig_value = '-';
let sig_title = '';
if (s.signal && s.signal !== 0) {
if (s.noise && s.noise !== 0) {
sig_value = '%d/%d\xa0%s'.format(s.signal, s.noise, _('dBm'));
sig_title = '%s: %d %s / %s: %d %s / %s %d'.format(
_('Signal'), s.signal, _('dBm'),
_('Noise'), s.noise, _('dBm'),
_('SNR'), s.signal - s.noise);
}
else {
sig_value = '%d\xa0%s'.format(s.signal, _('dBm'));
sig_title = '%s: %d %s'.format(_('Signal'), s.signal, _('dBm'));
}
}
let icon;
if (!s.connected) {
icon = L.resource('icons/signal-none.svg');
}
else {
/* Estimate signal quality as percentage:
* Map dBm range [-110, -40] to [0%, 100%] */
const q = Math.min((s.signal + 110) / 70 * 100, 100);
if (q == 0)
icon = L.resource('icons/signal-000-000.svg');
else if (q < 25)
icon = L.resource('icons/signal-000-025.svg');
else if (q < 50)
icon = L.resource('icons/signal-025-050.svg');
else if (q < 75)
icon = L.resource('icons/signal-050-075.svg');
else
icon = L.resource('icons/signal-075-100.svg');
}
rows.push([
E('span', {
'class': 'ifacebadge',
'style': s.connected ? '' : 'opacity:0.5',
'title': s.connected ? _('Connected') : _('Disconnected')
}, [
E('img', { 'src': icon, 'style': 'width:16px;height:16px' }),
E('span', {}, [ ' ', s.connected ? _('Yes') : _('No') ])
]),
s.mac,
hint,
s.network || '-',
E('span', { 'title': sig_title }, sig_value),
formatDate(s.first_seen),
formatDate(s.last_seen)
]);
}
cbi_update_table(table, rows, E('em', _('No station history available')));
},
handleClearHistory(ev) {
return ui.showModal(_('Clear Station History'), [
E('p', _('This will permanently delete all recorded station history. Are you sure?')),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')), ' ',
E('button', {
'class': 'btn cbi-button-negative',
'click': ui.createHandlerFn(this, function() {
return callClearHistory().then(L.bind(function() {
ui.hideModal();
return callGetHistory().then(L.bind(function(history) {
this.updateTable('#wifi_history_table', history);
}, this));
}, this));
})
}, _('Clear'))
])
]);
},
render(history) {
const isReadonlyView = !L.hasViewPermission();
const v = E([], [
E('h2', _('Station History')),
E('div', { 'class': 'cbi-map-descr' },
_('This page displays a history of all WiFi stations that have connected to this device, including currently connected and previously seen devices.')),
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'right', 'style': 'margin-bottom:1em' }, [
E('button', {
'class': 'btn cbi-button-negative',
'disabled': isReadonlyView,
'click': ui.createHandlerFn(this, 'handleClearHistory')
}, _('Clear History'))
]),
E('table', { 'class': 'table', 'id': 'wifi_history_table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Connected')),
E('th', { 'class': 'th' }, _('MAC address')),
E('th', { 'class': 'th' }, _('Host')),
E('th', { 'class': 'th' }, _('Network')),
E('th', { 'class': 'th' }, '%s / %s'.format(_('Signal'), _('Noise'))),
E('th', { 'class': 'th' }, _('First seen')),
E('th', { 'class': 'th' }, _('Last seen'))
])
])
])
]);
this.updateTable(v.querySelector('#wifi_history_table'), history);
poll.add(L.bind(function() {
return callGetHistory().then(L.bind(function(history) {
this.updateTable('#wifi_history_table', history);
}, this));
}, this), 5);
return v;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -1,82 +0,0 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2026-05-03 08:19+0000\n"
"Last-Translator: BoneNI <bounkirdni@gmail.com>\n"
"Language-Team: Lao <https://hosted.weblate.org/projects/openwrt/"
"luciapplicationswifihistory/lo/>\n"
"Language: lo\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.17.1\n"
#~ msgid "Cancel"
#~ msgstr "ຍົກເລີກ"
#~ msgid "Clear"
#~ msgstr "ລ້າງ"
#~ msgid "Clear History"
#~ msgstr "ລ້າງປະຫວັດ"
#~ msgid "Clear Station History"
#~ msgstr "ລ້າງປະຫວັດສະຖານີ"
#~ msgid "Connected"
#~ msgstr "ເຊື່ອມຕໍ່ແລ້ວ"
#~ msgid "Disconnected"
#~ msgstr "ຕັດການເຊື່ອມຕໍ່ແລ້ວ"
#~ msgid "First seen"
#~ msgstr "ເຫັນຄັ້ງທຳອິດ"
#~ msgid "Grant access to WiFi station history"
#~ msgstr "ອະນຸຍາດໃຫ້ເຂົ້າເຖິງປະຫວັດສະຖານີ WiFi"
#~ msgid "Host"
#~ msgstr "ໂຮສ (Host)"
#~ msgid "Last seen"
#~ msgstr "ເຫັນຄັ້ງສຸດທ້າຍ"
#~ msgid "MAC address"
#~ msgstr "ທີ່ຢູ່ MAC"
#~ msgid "Network"
#~ msgstr "ເຄືອຂ່າຍ"
#~ msgid "No"
#~ msgstr "ບໍ່"
#~ msgid "No station history available"
#~ msgstr "ບໍ່ມີຂໍ້ມູນປະຫວັດສະຖານີ"
#~ msgid "Noise"
#~ msgstr "ສັນຍານລົບກວນ"
#~ msgid "SNR"
#~ msgstr "SNR"
#~ msgid "Signal"
#~ msgstr "ສັນຍານ"
#~ msgid "Station History"
#~ msgstr "ປະຫວັດສະຖານີ"
#~ msgid ""
#~ "This page displays a history of all WiFi stations that have connected to "
#~ "this device, including currently connected and previously seen devices."
#~ msgstr ""
#~ "ໜ້ານີ້ສະແດງປະຫວັດຂອງທຸກສະຖານີ WiFi ທີ່ເຄີຍເຊື່ອມຕໍ່ກັບອຸປະກອນນີ້, ລວມທັງອຸປະກອນທີ່ກຳລັງເຊື່ອມຕໍ່ຢູ່ "
#~ "ແລະ ອຸປະກອນທີ່ເຄີຍເຫັນກ່ອນໜ້ານີ້."
#~ msgid ""
#~ "This will permanently delete all recorded station history. Are you sure?"
#~ msgstr "ນີ້ຈະເປັນການລຶບປະຫວັດສະຖານີທີ່ບັນທຶກໄວ້ທັງໝົດແບບຖາວອນ. ທ່ານແນ່ໃຈຫຼືບໍ່?"
#~ msgid "Yes"
#~ msgstr "ແມ່ນ"
#~ msgid "dBm"
#~ msgstr "dBm"

View File

@ -1,24 +0,0 @@
#!/bin/sh /etc/rc.common
START=99
STOP=10
USE_PROCD=1
NAME=wifihistory
PROG=/usr/sbin/wifihistory
INTERVAL=30
start_service() {
mkdir -p /var/lib/wifihistory
procd_open_instance "$NAME"
procd_set_param command /bin/sh -c "while true; do /usr/bin/ucode $PROG; sleep $INTERVAL; done"
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger "wireless"
}

View File

@ -1,124 +0,0 @@
#!/usr/bin/env ucode
'use strict';
import { readfile, writefile, open, mkdir, rename } from 'fs';
import { connect } from 'ubus';
const HISTORY_DIR = '/var/lib/wifihistory';
const HISTORY_FILE = HISTORY_DIR + '/history.json';
const LOCK_FILE = '/var/lock/wifihistory.lock';
function load_history() {
let content = readfile(HISTORY_FILE);
if (content == null)
return {};
try {
return json(content) || {};
}
catch (e) {
return {};
}
}
function save_history(data) {
let tmp = HISTORY_FILE + '.tmp';
writefile(tmp, sprintf('%J', data));
rename(tmp, HISTORY_FILE);
}
function poll_stations() {
let ubus = connect();
if (!ubus) {
warn('Failed to connect to ubus\n');
return;
}
let now = time();
let history = load_history();
let seen_macs = {};
let wifi_status = ubus.call('network.wireless', 'status');
if (!wifi_status)
return;
let hints = ubus.call('luci-rpc', 'getHostHints') || {};
for (let radio in wifi_status) {
let ifaces = wifi_status[radio]?.interfaces;
if (!ifaces)
continue;
for (let iface in ifaces) {
let ifname = iface?.ifname;
if (!ifname)
continue;
let info = ubus.call('iwinfo', 'info', { device: ifname });
let ssid = info?.ssid || '';
let assoc = ubus.call('iwinfo', 'assoclist', { device: ifname });
if (!assoc?.results)
continue;
for (let bss in assoc.results) {
let mac = bss?.mac;
if (!mac)
continue;
mac = uc(mac);
seen_macs[mac] = true;
let hostname = hints?.[mac]?.name || '';
let ipv4 = hints?.[mac]?.ipaddrs?.[0] || '';
let ipv6 = hints?.[mac]?.ip6addrs?.[0] || '';
let existing = history[mac];
let first_seen = existing?.first_seen || now;
history[mac] = {
mac: mac,
hostname: hostname,
ipv4: ipv4,
ipv6: ipv6,
network: ssid,
ifname: ifname,
connected: true,
signal: bss?.signal || 0,
noise: bss?.noise || 0,
first_seen: first_seen,
last_seen: now
};
}
}
}
for (let mac in history)
if (!seen_macs[mac])
history[mac].connected = false;
ubus.disconnect();
save_history(history);
}
mkdir(HISTORY_DIR);
let lock_fd = open(LOCK_FILE, 'w');
if (!lock_fd) {
warn('Failed to open lock file\n');
exit(1);
}
if (!lock_fd.lock('xn')) {
warn('Another instance is already running\n');
lock_fd.close();
exit(1);
}
poll_stations();
lock_fd.lock('u');
lock_fd.close();

View File

@ -1,14 +0,0 @@
{
"admin/status/wifihistory": {
"title": "Station History",
"order": 8,
"action": {
"type": "view",
"path": "status/wifihistory"
},
"depends": {
"acl": [ "luci-app-wifihistory" ],
"uci": { "wireless": { "@wifi-device": true } }
}
}
}

View File

@ -1,16 +0,0 @@
{
"luci-app-wifihistory": {
"description": "Grant access to WiFi station history",
"read": {
"ubus": {
"iwinfo": [ "assoclist" ],
"luci.wifihistory": [ "getHistory" ]
}
},
"write": {
"ubus": {
"luci.wifihistory": [ "clearHistory" ]
}
}
}
}

View File

@ -1,33 +0,0 @@
#!/usr/bin/env ucode
'use strict';
import { readfile, writefile } from 'fs';
const HISTORY_FILE = '/var/lib/wifihistory/history.json';
const methods = {
getHistory: {
call: function() {
let content = readfile(HISTORY_FILE);
if (content == null)
return { history: {} };
try {
return { history: json(content) || {} };
}
catch (e) {
return { history: {} };
}
}
},
clearHistory: {
call: function() {
writefile(HISTORY_FILE, '{}');
return { result: true };
}
}
};
return { 'luci.wifihistory': methods };

View File

@ -11,7 +11,7 @@ PKG_NAME:=luci-theme-$(THEME_NAME)
LUCI_TITLE:=Kucat Theme by sirpdboy
LUCI_DEPENDS:=+wget +curl +jsonfilter
PKG_VERSION:=3.3.2
PKG_RELEASE:=2
PKG_RELEASE:=3
define Package/luci-theme-$(THEME_NAME)/conffiles
/www/luci-static/resources/background/

View File

@ -1428,7 +1428,6 @@ button:hover,
.btn:hover,
.cbi-button:hover
{
color: var(--menu-hover-color);
transform: translateY(-2px) ;
box-shadow: 0 0.3rem 0.5rem var(--input-boxhovercolor);
-webkit-box-shadow: 0 0.3rem 0.5rem var(--input-boxhovercolor);
@ -1463,6 +1462,32 @@ button:active {
box-shadow: none
}
.modal label.btn {
color: var(--body-color);
background-color: rgba(var(--primary-rgbm), 0);
display: inline-flex;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
border: none;
background-image: none;
box-shadow: none;
-ms-user-select: none;
}
.modal label.btn:focus,
.modal label.btn:hover
{
color: var(--body-color);
background-color: rgba(var(--primary-rgbm), 0.1);
transform: none;
box-shadow: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
border: none;
}
.cbi-dynlist>.add-item:not([ondrop])>input,
.cbi-dynlist>.item>span,
.cbi-dynlist>.add-item input {
@ -2690,7 +2715,7 @@ td>.ifacebadge,
}
.ifacebox-head {
width: 7rem;
width: 6rem;
min-width: 100%;
background-color: rgba(var(--primary-rgbm), 0.3);
transform: translate(-50, -50%);

View File

@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=qBittorrent-Enhanced-Edition
PKG_VERSION:=5.2.1.10
PKG_RELEASE:=11
PKG_VERSION:=5.2.3.10
PKG_RELEASE:=12
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/c0re100/qBittorrent-Enhanced-Edition/tar.gz/release-$(PKG_VERSION)?

View File

@ -1,6 +1,6 @@
--- a/src/base/bittorrent/sessionimpl.cpp
+++ b/src/base/bittorrent/sessionimpl.cpp
@@ -128,7 +128,7 @@ const std::chrono::seconds FREEDISKSPACE
@@ -129,7 +129,7 @@ const std::chrono::seconds FREEDISKSPACE
namespace
{
const char PEER_ID[] = "qB";
@ -14,7 +14,7 @@
@@ -31,7 +31,7 @@
#define QBT_VERSION_MAJOR 5
#define QBT_VERSION_MINOR 2
#define QBT_VERSION_BUGFIX 1
#define QBT_VERSION_BUGFIX 3
-#define QBT_VERSION_BUILD 10
+#define QBT_VERSION_BUILD 0
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!

View File

@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=rustdesk-server
PKG_VERSION:=1.1.15
PKG_RELEASE:=2
PKG_VERSION:=1.1.16
PKG_RELEASE:=3
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/rustdesk/rustdesk-server.git

View File

@ -9,9 +9,9 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=watchcat
PKG_VERSION:=1
PKG_RELEASE:=4
PKG_RELEASE:=5
PKG_MAINTAINER:=Roger D <rogerdammit@gmail.com>
PKG_MAINTAINER:=Daniel F. Dickinson <dfdpublic@wildtechgarden.ca>, Dharmik Parmar <dharmikparmar2004@yahoo.com>
PKG_LICENSE:=GPL-2.0
include $(INCLUDE_DIR)/package.mk

View File

@ -7,6 +7,77 @@
#
. /lib/network/config.sh
. /lib/functions/network.sh
# Accept the historical real-device input while also handling @logical
# interface references used by other OpenWrt configs.
watchcat_resolve_ping_iface() {
local iface="$1"
local logical device network
[ -n "$iface" ] || return 1
case "$iface" in
@*)
logical="${iface#@}"
[ -n "$logical" ] || return 1
if network_get_device device "$logical"; then
printf '%s\n' "$device"
return 0
fi
printf '%s\n' "$iface"
return 1
;;
esac
network="$(find_config "$iface")"
if [ -n "$network" ]; then
printf '%s\n' "$iface"
return 0
fi
if network_get_device device "$iface"; then
printf '%s\n' "$device"
return 0
fi
printf '%s\n' "$iface"
return 1
}
watchcat_resolve_restart_iface() {
local iface="$1"
local network device
[ -n "$iface" ] || return 1
case "$iface" in
@*)
network="${iface#@}"
[ -n "$network" ] || return 1
if ! network_get_device device "$network"; then
printf '%s\n' "$network"
return 1
fi
printf '%s\n' "$network"
return 0
;;
esac
network="$(find_config "$iface")"
if [ -n "$network" ]; then
printf '%s\n' "$network"
return 0
fi
if network_get_device device "$iface"; then
printf '%s\n' "$iface"
return 0
fi
printf '%s\n' "$iface"
return 1
}
get_ping_size() {
ps=$1
@ -84,8 +155,11 @@ watchcat_restart_modemmanager_iface() {
}
watchcat_restart_network_iface() {
local network
network="$(find_config "$1")"
local iface="$1"
local network="$2"
[ -z "$network" ] && network="$(watchcat_resolve_restart_iface "$iface")"
logger -p daemon.info -t "watchcat[$$]" "Restarting network interface: \"$1\" (network: \"$network\")."
ifup "$network"
}
@ -110,11 +184,26 @@ watchcat_monitor_network() {
mm_iface_unlock_bands="$7"
address_family="$8"
script="$9"
ping_iface=""
restart_iface=""
reset_failure_timer=""
if [ "$#" -gt 9 ]; then
shift 9
reset_failure_timer="$1"
fi
[ "$mm_iface_name" = "null" ] && mm_iface_name=""
if [ "$iface" != "" ]; then
if ! ping_iface="$(watchcat_resolve_ping_iface "$iface")"; then
logger -p daemon.warn -t "watchcat[$$]" "Could not resolve interface \"$iface\" for pinging."
case "$iface" in
@*) ping_iface="" ;;
*) ping_iface="$iface" ;;
esac
fi
if ! restart_iface="$(watchcat_resolve_restart_iface "$iface")"; then
logger -p daemon.warn -t "watchcat[$$]" "Could not resolve interface \"$iface\" for restart."
fi
fi
time_now="$(cat /proc/uptime)"
time_now="${time_now%%.*}"
@ -143,9 +232,9 @@ watchcat_monitor_network() {
time_lastcheck="$time_now"
for host in $ping_hosts; do
if [ "$iface" != "" ]; then
if [ "$ping_iface" != "" ]; then
ping_result="$(
ping $ping_family -I "$iface" -s "$ping_size" -c 1 "$host" &> /dev/null
ping $ping_family -I "$ping_iface" -s "$ping_size" -c 1 "$host" &> /dev/null
echo $?
)"
else
@ -178,7 +267,7 @@ watchcat_monitor_network() {
watchcat_restart_modemmanager_iface "$mm_iface_name" "$mm_iface_unlock_bands"
fi
if [ "$iface" != "" ]; then
watchcat_restart_network_iface "$iface"
watchcat_restart_network_iface "$iface" "$restart_iface"
else
watchcat_restart_all_network
fi
@ -207,6 +296,16 @@ watchcat_ping() {
ping_size="$5"
address_family="$6"
iface="$7"
ping_iface=""
if [ "$iface" != "" ]; then
if ! ping_iface="$(watchcat_resolve_ping_iface "$iface")"; then
logger -p daemon.warn -t "watchcat[$$]" "Could not resolve interface \"$iface\" for pinging."
case "$iface" in
@*) ping_iface="" ;;
*) ping_iface="$iface" ;;
esac
fi
fi
time_now="$(cat /proc/uptime)"
time_now="${time_now%%.*}"
@ -235,9 +334,9 @@ watchcat_ping() {
time_lastcheck="$time_now"
for host in $ping_hosts; do
if [ "$iface" != "" ]; then
if [ "$ping_iface" != "" ]; then
ping_result="$(
ping $ping_family -I "$iface" -s "$ping_size" -c 1 "$host" &> /dev/null
ping $ping_family -I "$ping_iface" -s "$ping_size" -c 1 "$host" &> /dev/null
echo $?
)"
else