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') +
|
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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue