update 2026-10-24 23:15:37

This commit is contained in:
action committed 2026-10-24 23:15:37 +08:00
1 parent 6d61e8bf50
commit 852fced2b3
178 files changed
+381 -38050

No files matched your search

@@ -128,6 +128,15 @@ return view.extend({
'type': webUi ? null : 'button',
'disabled': webUi ? null : true
}, _('Open Doona'));
const clashUi = honk.localClashUi(status);
const openClash = E(clashUi ? 'a' : 'button', {
'class': 'cbi-button cbi-button-action',
'href': clashUi || null,
'target': clashUi ? '_blank' : null,
'rel': clashUi ? 'noopener noreferrer' : null,
'type': clashUi ? null : 'button',
'disabled': clashUi ? null : true
}, _('Open Clash panel'));
const toggle = E('button', {
'class': 'cbi-button cbi-button-action',
@@ -140,7 +149,7 @@ return view.extend({
runAction(running ? 'stop' : 'start');
});
const actions = [ openDoona, toggle ];
const actions = [ openDoona, openClash, toggle ];
if (running) {
const restart = E('button', { 'class': 'cbi-button', 'type': 'button', 'data-action': 'restart' }, _('Restart'));
restart.disabled = busy;
@@ -162,7 +171,8 @@ return view.extend({
E('div', { 'class': 'honk-meta' }, [
metaItem(_('Core version'), status.core_version),
metaItem(_('Doona version'), status.doona_version),
metaItem(_('Kernel'), status.kernel)
metaItem(_('Kernel'), status.kernel),
metaItem(_('Clash API'), status.clash_api_enabled ? status.clash_api_url : _('Off'))
])
]);
@@ -171,6 +181,8 @@ return view.extend({
!running ? _('Start Honk to open Doona.') : _('Doona is not ready yet. Check the service status.');
statusCard.appendChild(E('p', { 'class': 'honk-note', 'role': 'status' }, reason));
}
if (status.clash_api_enabled && running && !clashUi)
statusCard.appendChild(E('p', { 'class': 'honk-note', 'role': 'status' }, status.clash_api_ready ? _('Zashboard is still downloading or unavailable.') : _('The Clash API listener is unavailable. Check the service logs.')));
const errors = (Array.isArray(status.errors) ? status.errors : []).filter(function(token) {
return token !== 'disabled' && token !== 'not_initialized' && token !== 'service_not_running';
@@ -197,6 +209,9 @@ return view.extend({
const networkSelect = E('select', { 'class': 'cbi-input-select', 'name': 'lan_network', 'id': 'honk-lan-network' });
const portInput = E('input', { 'class': 'cbi-input-text', 'type': 'number', 'name': 'listen_port', 'id': 'honk-listen-port', 'min': '1024', 'max': '65535', 'step': '1' });
const bootEnabledInput = E('input', { 'type': 'checkbox', 'name': 'boot_enabled', 'id': 'honk-boot-enabled' });
const clashEnabledInput = E('input', { 'type': 'checkbox', 'name': 'clash_api_enabled', 'id': 'honk-clash-enabled' });
const clashPortInput = E('input', { 'class': 'cbi-input-text', 'type': 'number', 'name': 'clash_api_port', 'id': 'honk-clash-port', 'min': '1024', 'max': '65535', 'step': '1' });
const clashSecretInput = E('input', { 'class': 'cbi-input-password', 'type': 'password', 'name': 'clash_api_secret', 'id': 'honk-clash-secret', 'autocomplete': 'new-password' });
const message = E('p', { 'class': 'honk-status-msg', 'role': 'status' }, '');
const save = E('button', { 'class': 'cbi-button cbi-button-positive', 'type': 'submit' }, _('Save service settings'));
@@ -209,6 +224,8 @@ return view.extend({
networkSelect.value = status.lan_network;
portInput.value = status.listen_port ? String(status.listen_port) : '9527';
bootEnabledInput.checked = status.boot_enabled === true;
clashEnabledInput.checked = status.clash_api_enabled === true;
clashPortInput.value = status.clash_api_port ? String(status.clash_api_port) : '9090';
card.appendChild(E('h3', { 'class': 'honk-card-title' }, _('Service settings')));
if (!(data.networks || []).length)
@@ -216,20 +233,33 @@ return view.extend({
form.appendChild(field(_('LAN network'), networkSelect));
form.appendChild(field(_('Listen port'), portInput));
form.appendChild(field(_('Start Honk at boot'), bootEnabledInput));
form.appendChild(field(_('Enable Clash API and Zashboard'), clashEnabledInput));
form.appendChild(field(_('Clash API port'), clashPortInput));
form.appendChild(field(_('Clash API secret'), clashSecretInput, status.clash_api_secret_set ? _('A secret is set. Leave blank to keep it.') : _('Required when enabling. Use 16–128 letters, numbers, underscores or hyphens.')));
form.appendChild(E('p', { 'class': 'honk-note' }, _('Use your existing HTTPS reverse proxy for external access. Enter the secret in Zashboard when connecting.')));
form.appendChild(E('div', { 'class': 'cbi-page-actions' }, save));
form.appendChild(message);
form.addEventListener('submit', function(ev) {
ev.preventDefault();
const port = Number(portInput.value);
const clashPort = Number(clashPortInput.value);
const clashSecret = clashSecretInput.value;
if (!networkSelect.value || !Number.isInteger(port) || port < 1024 || port > 65535) {
message.textContent = _('Choose a LAN network and a port from 1024 to 65535.');
return;
}
if (!Number.isInteger(clashPort) || clashPort < 1024 || clashPort > 65535 ||
clashEnabledInput.checked && (clashPort === port || !status.clash_api_secret_set && !clashSecret ||
!!clashSecret && !/^[A-Za-z0-9_-]{16,128}$/.test(clashSecret))) {
message.textContent = _('Choose a different Clash API port and enter a valid secret.');
return;
}
save.disabled = true;
settingsBusy = true;
message.textContent = _('Saving…');
honk.settings(networkSelect.value, port, bootEnabledInput.checked).then(honk.ensureOk).then(function(result) {
honk.settings(networkSelect.value, port, bootEnabledInput.checked, clashEnabledInput.checked, clashPort, clashSecret).then(honk.ensureOk).then(function(result) {
clashSecretInput.value = '';
message.textContent = honk.resultMessage(result, _('Service settings saved.'));
}).catch(function(error) {
message.textContent = honk.errorMessage(error, _('Settings could not be applied.'));
@@ -30,7 +30,7 @@ const callRestart = rpc.declare({ object: 'honk', method: 'restart', expect: { '
const callSettings = rpc.declare({
object: 'honk',
method: 'settings',
params: [ 'lan_network', 'listen_port', 'boot_enabled' ],
params: [ 'lan_network', 'listen_port', 'boot_enabled', 'clash_api_enabled', 'clash_api_port', 'clash_api_secret' ],
expect: { '': {} }
});
@@ -122,6 +122,12 @@ function localWebUi(status) {
}
}
function localClashUi(status) {
if (!status || !status.clash_api_enabled || !status.clash_api_ready || !status.clash_ui_ready)
return null;
return localWebUi(Object.assign({}, status, { api_url: status.clash_api_url, api_ready: true }));
}
function tokenMessage(token) {
const messages = {
disabled: _('Honk is disabled.'),
@@ -198,6 +204,8 @@ function tokenMessage(token) {
invalid_lan_network: _('The selected LAN network is invalid or unavailable.'),
lan_network_unavailable: _('The selected LAN network is unavailable.'),
listen_port_occupied: _('The listen port is already in use.'),
clash_api_port_occupied: _('The Clash API port is already in use.'),
invalid_clash_api_settings: _('Enter a different Clash API port and a secret of 16–128 letters, numbers, underscores or hyphens.'),
invalid_listen_port: _('The listen port is invalid.'),
initialize_before_enabling_service: _('Complete initial setup before enabling Honk.'),
system_config_unavailable: _('Honk system configuration is unavailable.'),
@@ -304,6 +312,7 @@ return baseclass.extend({
waitJob: waitJob,
ensureOk: ensureOk,
localWebUi: localWebUi,
localClashUi: localClashUi,
statusIssue: statusIssue,
resultMessage: resultMessage,
errorMessage: errorMessage,
@@ -49,8 +49,12 @@ return view.extend({
const networkSelect = E('select', { 'class': 'cbi-input-select', 'name': 'lan_network', 'id': 'honk-lan-network' });
const portInput = E('input', { 'class': 'cbi-input-text', 'type': 'number', 'name': 'listen_port', 'id': 'honk-listen-port', 'min': '1024', 'max': '65535', 'step': '1' });
const bootEnabledInput = E('input', { 'type': 'checkbox', 'name': 'boot_enabled', 'id': 'honk-boot-enabled' });
const clashEnabledInput = E('input', { 'type': 'checkbox', 'name': 'clash_api_enabled', 'id': 'honk-clash-enabled' });
const clashPortInput = E('input', { 'class': 'cbi-input-text', 'type': 'number', 'name': 'clash_api_port', 'id': 'honk-clash-port', 'min': '1024', 'max': '65535', 'step': '1' });
const clashSecretInput = E('input', { 'class': 'cbi-input-password', 'type': 'password', 'name': 'clash_api_secret', 'id': 'honk-clash-secret', 'autocomplete': 'new-password' });
const message = E('p', { 'class': 'honk-status-msg', 'role': 'status' }, '');
const save = E('button', { 'class': 'cbi-button cbi-button-positive', 'type': 'submit' }, _('Save settings'));
save.disabled = !!data.statusError;
(data.networks || []).forEach(function(name) {
networkSelect.appendChild(E('option', { 'value': name }, name));
@@ -61,6 +65,8 @@ return view.extend({
networkSelect.value = data.status.lan_network;
portInput.value = data.status && data.status.listen_port ? String(data.status.listen_port) : '9527';
bootEnabledInput.checked = !!(data.status && data.status.boot_enabled === true);
clashEnabledInput.checked = !!(data.status && data.status.clash_api_enabled === true);
clashPortInput.value = data.status && data.status.clash_api_port ? String(data.status.clash_api_port) : '9090';
section.appendChild(E('h3', { 'class': 'honk-card-title' }, _('Honk settings')));
if (data.statusError)
@@ -72,19 +78,32 @@ return view.extend({
section.appendChild(field(_('LAN network'), networkSelect));
section.appendChild(field(_('Listen port'), portInput));
section.appendChild(field(_('Start Honk at boot'), bootEnabledInput));
section.appendChild(field(_('Enable Clash API and Zashboard'), clashEnabledInput));
section.appendChild(field(_('Clash API port'), clashPortInput));
section.appendChild(field(_('Clash API secret'), clashSecretInput));
section.appendChild(E('p', { 'class': 'honk-note' }, data.status && data.status.clash_api_secret_set ? _('A secret is set. Leave blank to keep it. Use your existing HTTPS reverse proxy for external access.') : _('A secret is required when enabling Clash API. Use 16–128 letters, numbers, underscores or hyphens.')));
section.appendChild(E('div', { 'class': 'cbi-page-actions' }, save));
section.appendChild(message);
section.addEventListener('submit', function(ev) {
ev.preventDefault();
const port = Number(portInput.value);
const clashPort = Number(clashPortInput.value);
const clashSecret = clashSecretInput.value;
if (!networkSelect.value || !Number.isInteger(port) || port < 1024 || port > 65535) {
message.textContent = _('Choose a LAN network and a port from 1024 to 65535.');
return;
}
if (!Number.isInteger(clashPort) || clashPort < 1024 || clashPort > 65535 ||
clashEnabledInput.checked && (clashPort === port || !(data.status && data.status.clash_api_secret_set) && !clashSecret ||
!!clashSecret && !/^[A-Za-z0-9_-]{16,128}$/.test(clashSecret))) {
message.textContent = _('Choose a different Clash API port and enter a valid secret.');
return;
}
save.disabled = true;
message.textContent = _('Saving…');
honk.settings(networkSelect.value, port, bootEnabledInput.checked).then(honk.ensureOk).then(function(result) {
honk.settings(networkSelect.value, port, bootEnabledInput.checked, clashEnabledInput.checked, clashPort, clashSecret).then(honk.ensureOk).then(function(result) {
clashSecretInput.value = '';
message.textContent = honk.resultMessage(result, _('Settings saved and applied.'));
}).catch(function(error) {
message.textContent = honk.errorMessage(error, _('Save failed. Check the settings and system status.'));