diff options
Diffstat (limited to 'applications/luci-app-shadowsocks-libev/htdocs/luci-static')
4 files changed, 153 insertions, 16 deletions
diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js index 3aaaa50121..c0f1ced553 100644 --- a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js @@ -1,4 +1,5 @@ 'use strict'; +'require baseclass'; 'require uci'; 'require form'; 'require network'; @@ -20,6 +21,7 @@ var names_options_client = [ ]; var names_options_common = [ + 'local_address', 'verbose', 'ipv6_first', 'fast_open', @@ -67,7 +69,7 @@ function ucival_to_bool(val) { return val === 'true' || val === '1' || val === 'yes' || val === 'on'; } -return L.Class.extend({ +return baseclass.extend({ values_actions: function(o) { o.value('bypass'); o.value('forward'); @@ -99,7 +101,7 @@ return L.Class.extend({ } }); }, - values_ipaddr: function(o, netDevs) { + values_ip4addr: function(o, netDevs) { netDevs.forEach(function(v) { v.getIPAddrs().forEach(function(a) { var host = a.split('/')[0]; @@ -107,6 +109,18 @@ return L.Class.extend({ }); }); }, + values_ip6addr: function(o, netDevs) { + netDevs.forEach(function(v) { + v.getIP6Addrs().forEach(function(a) { + var host = a.split('/')[0]; + o.value(host, '%s (%s)'.format(host, v.getShortName())); + }); + }); + }, + values_ipaddr: function(o, netDevs) { + this.values_ip4addr(o, netDevs) + this.values_ip6addr(o, netDevs) + }, options_client: function(s, tab, netDevs) { var o = s.taboption(tab, form.ListValue, 'server', _('Remote server')); this.values_serverlist(o); @@ -190,7 +204,7 @@ return L.Class.extend({ if (stype === 'ss_server') { this.cfgvalue_overview_(sdata, lines, names_options_server); this.cfgvalue_overview_(sdata, lines, names_options_common); - this.cfgvalue_overview_(sdata, lines, ['bind_address']); + this.cfgvalue_overview_(sdata, lines, ['local_ipv4_address', 'local_ipv6_address']); } else if (stype === 'ss_local' || stype === 'ss_redir' || stype === 'ss_tunnel') { this.cfgvalue_overview_(sdata, lines, names_options_client); if (stype === 'ss_tunnel') { @@ -238,5 +252,71 @@ return L.Class.extend({ window.open(L.url('admin/system/opkg') + '?query=' + opkg_package, '_blank', 'noopener'); }; + }, + parse_uri: function(uri) { + var scheme = 'ss://'; + if (uri && uri.indexOf(scheme) === 0) { + var atPos = uri.indexOf('@'), hashPos = uri.lastIndexOf('#'), tag; + if (hashPos === -1) { + hashPos = undefined; + } else { + tag = uri.slice(hashPos + 1); + } + + if (atPos !== -1) { // SIP002 format https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html + var colonPos = uri.indexOf(':', atPos + 1), slashPos = uri.indexOf('/', colonPos + 1); + if (colonPos === -1) return null; + if (slashPos === -1) slashPos = undefined; + + var userinfo = atob(uri.slice(scheme.length, atPos) + .replace(/-/g, '+').replace(/_/g, '/')), + i = userinfo.indexOf(':'); + if (i === -1) return null; + + var config = { + server: uri.slice(atPos + 1, colonPos), + server_port: uri.slice(colonPos + 1, slashPos ? slashPos : hashPos), + password: userinfo.slice(i + 1), + method: userinfo.slice(0, i) + }; + + if (slashPos) { + var search = uri.slice(slashPos + 1, hashPos); + if (search[0] === '?') search = search.slice(1); + search.split('&').forEach(function(s) { + var j = s.indexOf('='); + if (j !== -1) { + var k = s.slice(0, j), v = s.slice(j + 1); + if (k === 'plugin') { + v = decodeURIComponent(v); + var k = v.indexOf(';'); + if (k !== -1) { + config['plugin'] = v.slice(0, k); + config['plugin_opts'] = v.slice(k + 1); + } + } + } + }); + } + return [config, tag]; + } else { // Legacy format https://shadowsocks.org/en/config/quick-guide.html + var plain = atob(uri.slice(scheme.length, hashPos)), + firstColonPos = plain.indexOf(':'), + lastColonPos = plain.lastIndexOf(':'), + atPos = plain.lastIndexOf('@', lastColonPos); + if (firstColonPos === -1 || + lastColonPos === -1 || + atPos === -1) return null; + + var config = { + server: plain.slice(atPos + 1, lastColonPos), + server_port: plain.slice(lastColonPos + 1), + password: plain.slice(firstColonPos + 1, atPos), + method: plain.slice(0, firstColonPos) + }; + return [config, tag]; + } + } + return null; } }); diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/instances.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/instances.js index 27a2b950c2..671f17a9e4 100644 --- a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/instances.js +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/instances.js @@ -1,4 +1,6 @@ 'use strict'; +'require view'; +'require poll'; 'require form'; 'require uci'; 'require fs'; @@ -16,7 +18,7 @@ var callServiceList = rpc.declare({ expect: { '': {} } }); -return L.view.extend({ +return view.extend({ render: function(stats) { var m, s, o; @@ -82,12 +84,21 @@ return L.view.extend({ if (stype === 'ss_server') { ss.options_server(s, { tab: 'general' }); - o = s.taboption('general', form.Value, 'bind_address', - _('Bind address'), - _('The address ss-server will initiate connection from')); + o = s.taboption('advanced', form.Value, 'local_address', + _('Local address'), + _('The address ss-server will initiate connections from')); o.datatype = 'ipaddr'; - o.placeholder = '0.0.0.0'; ss.values_ipaddr(o, res[1]); + o = s.taboption('advanced', form.Value, 'local_ipv4_address', + _('Local IPv4 address'), + _('The IPv4 address ss-server will initiate IPv4 connections from')); + o.datatype = 'ip4addr'; + ss.values_ip4addr(o, res[1]); + o = s.taboption('advanced', form.Value, 'local_ipv6_address', + _('Local IPv6 address'), + _('The IPv6 address ss-server will initiate IPv6 connections from')); + o.datatype = 'ip6addr'; + ss.values_ip6addr(o, res[1]); } else { ss.options_client(s, 'general', res[1]); if (stype === 'ss_tunnel') { @@ -137,7 +148,7 @@ return L.view.extend({ } return m.render().finally(function() { - L.Poll.add(function() { + poll.add(function() { return L.resolveDefault(callServiceList(conf), {}) .then(function(res) { var instances = null; diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/rules.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/rules.js index 798237adbd..4cb653e57e 100644 --- a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/rules.js +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/rules.js @@ -1,4 +1,5 @@ 'use strict'; +'require view'; 'require uci'; 'require fs'; 'require form'; @@ -12,7 +13,7 @@ function src_dst_option(s /*, ... */) { o.datatype = 'or(ipaddr,cidr)'; } -return L.view.extend({ +return view.extend({ load: function() { return Promise.all([ L.resolveDefault(fs.stat('/usr/lib/iptables/libxt_recent.so'), {}), diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js index d46bfb0aa7..57756b83e6 100644 --- a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js @@ -1,21 +1,66 @@ 'use strict'; +'require view'; 'require form'; +'require uci'; +'require ui'; 'require shadowsocks-libev as ss'; -function startsWith(str, search) { - return str.substring(0, search.length) === search; -} +var conf = 'shadowsocks-libev'; -return L.view.extend({ +return view.extend({ render: function() { var m, s, o; - m = new form.Map('shadowsocks-libev', _('Remote Servers'), + m = new form.Map(conf, _('Remote Servers'), _('Definition of remote shadowsocks servers. \ Disable any of them will also disable instances referring to it.')); s = m.section(form.GridSection, 'server'); s.addremove = true; + s.handleLinkImport = function() { + var textarea = new ui.Textarea(); + ui.showModal(_('Import Links'), [ + textarea.render(), + E('div', { class: 'right' }, [ + E('button', { + class: 'btn', + click: ui.hideModal + }, [ _('Cancel') ]), + ' ', + E('button', { + class: 'btn cbi-button-action', + click: ui.createHandlerFn(this, function() { + textarea.getValue().split('\n').forEach(function(s) { + var config = ss.parse_uri(s); + if (config) { + var tag = config[1]; + if (tag && !tag.match(/^[a-zA-Z0-9_]+$/)) tag = null; + var sid = uci.add(conf, 'server', tag); + config = config[0]; + Object.keys(config).forEach(function(k) { + uci.set(conf, sid, k, config[k]); + }); + } + }); + return uci.save() + .then(L.bind(this.map.load, this.map)) + .then(L.bind(this.map.reset, this.map)) + .then(L.ui.hideModal) + .catch(function() {}); + }) + }, [ _('Import') ]) + ]) + ]); + }; + s.renderSectionAdd = function(extra_class) { + var el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments); + el.appendChild(E('button', { + 'class': 'cbi-button cbi-button-add', + 'title': _('Import Links'), + 'click': ui.createHandlerFn(this, 'handleLinkImport') + }, [ _('Import Links') ])); + return el; + }; o = s.option(form.Flag, 'disabled', _('Disable')); o.editable = true; @@ -26,7 +71,7 @@ return L.view.extend({ }, addFooter: function() { var p = '#edit='; - if (startsWith(location.hash, p)) { + if (location.hash.indexOf(p) === 0) { var section_id = location.hash.substring(p.length); var editBtn = document.querySelector('#cbi-shadowsocks-libev-' + section_id + ' button.cbi-button-edit'); if (editBtn) |