🍓 Sync 2026-09-15 02:13:06

This commit is contained in:
github-actions[bot] committed 2026-09-15 02:13:06 +08:00
1 parent 0e5393594c
commit 1dd96ac2bb
146 files changed
+26541 -508

No files matched your search

@@ -0,0 +1,106 @@
'use strict';
'require view';
'require form';
'require rpc';
var callGetCiphers = rpc.declare({
object: 'luci.ssserver',
method: 'get_ciphers',
expect: {}
});
var callGetTFOStatus = rpc.declare({
object: 'luci.ssserver',
method: 'get_tfo_status',
expect: {}
});
var callGetLanIP = rpc.declare({
object: 'luci.ssserver',
method: 'get_lan_ip',
expect: {}
});
return view.extend({
load: function() {
return Promise.all([
callGetCiphers(),
callGetTFOStatus(),
callGetLanIP()
]);
},
render: function(data) {
var m, s, o;
var ciphers = data[0].ciphers || [];
var tfo_val = data[1].value || 0;
var lan_ip = data[2].ip || '192.168.1.1';
m = new form.Map('ssserver', _('Shadowsocks Server Title') + ' - ' + _('Settings'),
_('A fast and secure tunnel proxy that helps you securely access your local network. ' +
'This version uses shadowsocks-rust.'));
s = m.section(form.TypedSection, 'ssserver', _('Settings'));
s.anonymous = true;
s.addremove = false;
o = s.option(form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
o = s.option(form.Value, 'server', _('Server Address'),
_('The address the server will bind to. Use 0.0.0.0 for all interfaces.'));
o.datatype = 'ipaddr';
o.placeholder = '0.0.0.0';
o = s.option(form.Value, 'server_port', _('Server Port'),
_('The port the server will listen on.'));
o.datatype = 'port';
o.placeholder = '8388';
o = s.option(form.Value, 'password', _('Password'));
o.password = true;
o = s.option(form.ListValue, 'method', _('Encryption Method'));
for (var i = 0; i < ciphers.length; i++) {
o.value(ciphers[i]);
}
if (ciphers.indexOf('chacha20-ietf-poly1305') === -1 && ciphers.length === 0) {
o.value('chacha20-ietf-poly1305');
o.value('aes-256-gcm');
o.value('aes-128-gcm');
}
o = s.option(form.Value, 'timeout', _('ssserver timeout'), _('Connection timeout in seconds.'));
o.datatype = 'uinteger';
o.placeholder = '300';
o = s.option(form.ListValue, 'mode', _('Network Mode'));
o.value('tcp_and_udp', _('TCP and UDP'));
o.value('tcp_only', _('TCP only'));
o.value('udp_only', _('UDP only'));
o.default = 'tcp_and_udp';
o = s.option(form.Value, 'dns_resolver', _('DNS Resolver'),
_('DNS server for the shadowsocks server to resolve addresses.'));
o.datatype = 'ipaddr';
o.placeholder = lan_ip;
o.default = lan_ip;
o = s.option(form.Flag, 'open_firewall', _('Open Firewall'),
_('Automatically open the server port in the firewall (WAN).'));
var tfo_status_msg = '';
if (tfo_val === 0) {
tfo_status_msg = '<span style="color:red"> (' + _('Not supported or disabled in kernel') + ')</span>';
} else if (tfo_val === 1) {
tfo_status_msg = '<span style="color:orange"> (' + _('Enabled for client only') + ')</span>';
} else if (tfo_val === 2 || tfo_val === 3) {
tfo_status_msg = '<span style="color:green"> (' + _('Enabled and supported') + ')</span>';
}
o = s.option(form.Flag, 'fast_open', _('TCP Fast Open'),
_('Enable TCP Fast Open (requires kernel support).') + tfo_status_msg);
return m.render();
}
});
@@ -0,0 +1,47 @@
'use strict';
'require view';
'require rpc';
'require poll';
var callGetLog = rpc.declare({
object: 'luci.ssserver',
method: 'get_log',
expect: {}
});
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
render: function() {
var log_view = E('textarea', {
id: 'ssserver_log_content',
style: 'width:100%; height:500px; background:#f4f4f4; color:#333; padding:10px; border:1px solid #ccc; border-radius:3px; font-family:monospace; font-size:12px; overflow-y:auto; white-space:pre-wrap; word-break:break-all; margin-top:10px;',
readonly: 'readonly'
}, _('Loading logs...'));
poll.add(function() {
return callGetLog().then(function(res) {
var area = document.getElementById('ssserver_log_content');
if (area) {
if (res && res.log) {
area.value = res.log;
area.scrollTop = area.scrollHeight;
} else {
area.value = _('No log entries found.');
}
}
});
}, 5);
return E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Shadowsocks Server Title') + ' - ' + _('Log')),
E('div', { 'class': 'cbi-map-descr' }, _('Logs are updated every 5 seconds.')),
E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('Log')),
E('div', { 'class': 'cbi-section-node' }, log_view)
])
]);
}
});
@@ -0,0 +1,189 @@
'use strict';
'require view';
'require rpc';
'require poll';
'require ui';
'require uci';
const callSsserverStatus = rpc.declare({
object: 'luci.ssserver',
method: 'get_status'
});
const callServiceAction = rpc.declare({
object: 'luci.ssserver',
method: 'service_action',
params: [ 'action' ]
});
const callSsserverLanIp = rpc.declare({
object: 'luci.ssserver',
method: 'get_lan_ip'
});
function renderState(status) {
let text = _('Stopped');
let color = 'red';
if (status.running) {
text = _('Running');
if (status.pid) {
text += ' [PID: ' + status.pid + ']';
}
color = 'green';
}
return { text, color };
}
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return Promise.all([
callSsserverStatus(),
uci.load('ssserver'),
callSsserverLanIp().catch(function() { return { ip: '' }; })
]);
},
render: function(data) {
const status = data && data[0] ? data[0] : {};
const stateInfo = renderState(status);
const lanIpInfo = data && data[2] ? data[2] : { ip: '' };
const lanIp = lanIpInfo.ip || '8.8.8.8';
// Read configurations from UCI
const ssserverConfig = uci.get('ssserver', 'main') || {};
const server = ssserverConfig.server || '0.0.0.0';
const port = ssserverConfig.server_port || '8388';
const method = ssserverConfig.method || 'chacha20-ietf-poly1305';
const timeout = ssserverConfig.timeout || '300';
let modeText = _('TCP and UDP');
if (ssserverConfig.mode === 'tcp_only') modeText = _('TCP only');
if (ssserverConfig.mode === 'udp_only') modeText = _('UDP only');
const dns = ssserverConfig.dns_resolver || lanIp;
const tfo = (ssserverConfig.fast_open === '1') ? _('Enabled') : _('Disabled');
const firewall = (ssserverConfig.open_firewall === '1') ? _('Enabled') : _('Disabled');
const container = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Shadowsocks Server Title') + ' - ' + _('Overview')),
E('div', { 'class': 'cbi-map-descr' }, _('Overview of ssserver running status and actual runtime configurations.'))
]);
// Service Status Section (Aligned with lucky style)
const statusSection = E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('Service Status')),
E('div', { 'class': 'cbi-section-node' }, [
E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' }, _('Current Status')),
E('td', { 'class': 'td left' }, [
E('span', {
id: 'ssserver-status-text',
'style': 'font-weight:bold; color:' + stateInfo.color + '; margin-right:15px;'
}, stateInfo.text),
E('button', {
id: 'ssserver-restart-btn',
'class': 'btn cbi-button cbi-button-apply',
'style': 'display:' + (status.running ? 'inline-block' : 'none') + ';',
click: ui.createHandlerFn(this, function(ev) {
if (!ev || !ev.currentTarget) return;
const btn = ev.currentTarget;
btn.disabled = true;
ui.addNotification(null, E('p', _('Restarting Shadowsocks Server, please wait...')), 'info');
return callServiceAction('restart').then(function(res) {
btn.disabled = false;
if (res && res.success) {
ui.addNotification(null, E('p', _('Service restarted successfully.')), 'info');
} else {
ui.addNotification(null, E('p', _('Failed to restart service.')), 'error');
}
}).catch(function(err) {
btn.disabled = false;
ui.addNotification(null, E('p', _('An error occurred: ') + err.message), 'error');
});
})
}, _('Restart'))
])
])
])
])
]);
container.appendChild(statusSection);
// Runtime Configuration Section (Aligned with lucky style)
const configSection = E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('Runtime Configurations')),
E('div', { 'class': 'cbi-section-node' }, [
E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' }, _('Server Address')),
E('td', { 'class': 'td left' }, E('code', {}, server))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Server Port')),
E('td', { 'class': 'td left' }, E('code', {}, port))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Encryption Method')),
E('td', { 'class': 'td left' }, E('code', {}, method))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Network Mode')),
E('td', { 'class': 'td left' }, modeText)
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('ssserver timeout')),
E('td', { 'class': 'td left' }, timeout + ' ' + _('seconds'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('DNS Resolver')),
E('td', { 'class': 'td left' }, E('code', {}, dns))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('TCP Fast Open')),
E('td', { 'class': 'td left' }, tfo)
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Open Firewall')),
E('td', { 'class': 'td left' }, firewall)
])
])
])
]);
container.appendChild(configSection);
// Add live status polling
poll.add(function() {
return callSsserverStatus().then(function(status) {
const textNode = document.getElementById('ssserver-status-text');
const restartBtn = document.getElementById('ssserver-restart-btn');
if (textNode && restartBtn) {
const stateInfo = renderState(status || {});
textNode.textContent = stateInfo.text;
textNode.style.color = stateInfo.color;
// Toggle button visibility based on runtime state
if (status.running) {
restartBtn.style.display = 'inline-block';
} else {
restartBtn.style.display = 'none';
}
}
});
}, 5);
return container;
}
});