luci-app-shadowsocks-libev: add server links import feature

Signed-off-by: Richard Yu <yurichard3839@gmail.com>
This commit is contained in:
Richard Yu 2019-11-09 23:43:00 +08:00 committed by Yousong Zhou
parent c1f41f2919
commit dc26e2d037
2 changed files with 115 additions and 5 deletions

View file

@ -238,5 +238,71 @@ return L.Class.extend({
window.open(L.url('admin/system/opkg') + window.open(L.url('admin/system/opkg') +
'?query=' + opkg_package, '_blank', 'noopener'); '?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;
} }
}); });

View file

@ -1,21 +1,65 @@
'use strict'; 'use strict';
'require form'; 'require form';
'require uci';
'require ui';
'require shadowsocks-libev as ss'; 'require shadowsocks-libev as ss';
function startsWith(str, search) { var conf = 'shadowsocks-libev';
return str.substring(0, search.length) === search;
}
return L.view.extend({ return L.view.extend({
render: function() { render: function() {
var m, s, o; var m, s, o;
m = new form.Map('shadowsocks-libev', _('Remote Servers'), m = new form.Map(conf, _('Remote Servers'),
_('Definition of remote shadowsocks servers. \ _('Definition of remote shadowsocks servers. \
Disable any of them will also disable instances referring to it.')); Disable any of them will also disable instances referring to it.'));
s = m.section(form.GridSection, 'server'); s = m.section(form.GridSection, 'server');
s.addremove = true; 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 = s.option(form.Flag, 'disabled', _('Disable'));
o.editable = true; o.editable = true;
@ -26,7 +70,7 @@ return L.view.extend({
}, },
addFooter: function() { addFooter: function() {
var p = '#edit='; var p = '#edit=';
if (startsWith(location.hash, p)) { if (location.hash.indexOf(p) === 0) {
var section_id = location.hash.substring(p.length); var section_id = location.hash.substring(p.length);
var editBtn = document.querySelector('#cbi-shadowsocks-libev-' + section_id + ' button.cbi-button-edit'); var editBtn = document.querySelector('#cbi-shadowsocks-libev-' + section_id + ' button.cbi-button-edit');
if (editBtn) if (editBtn)