luci-app-shadowsocks-libev: add server links import feature
Signed-off-by: Richard Yu <yurichard3839@gmail.com>
This commit is contained in:
parent
c1f41f2919
commit
dc26e2d037
2 changed files with 115 additions and 5 deletions
|
@ -238,5 +238,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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,21 +1,65 @@
|
|||
'use strict';
|
||||
'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({
|
||||
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 +70,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)
|
||||
|
|
Loading…
Reference in a new issue