Files
op-packages/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js
T

393 lines
9.8 KiB
JavaScript

/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2022-2025 ImmortalWrt.org
*/
'use strict';
'require dom';
'require form';
'require fs';
'require poll';
'require rpc';
'require uci';
'require ui';
'require view';
'require homeproxy as hp';
/* Thanks to luci-app-aria2 */
const css = ' \
#log_textarea { \
padding: 10px; \
text-align: left; \
} \
#log_textarea pre { \
padding: .5rem; \
word-break: break-all; \
margin: 0; \
} \
.description { \
background-color: #33ccff; \
} \
.homeproxy-connect-grid { \
display: grid; \
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));\
gap: 12px; \
} \
.homeproxy-connect-card { \
display: grid; \
grid-template-columns: 44px 1fr; \
gap: 10px; \
align-items: center; \
min-height: 64px; \
padding: 8px 10px; \
border-radius: 6px; \
background: #fff; \
box-shadow: 0 1px 4px rgba(0, 0, 0, .08);\
} \
.homeproxy-connect-icon { \
display: flex; \
align-items: center; \
justify-content: center; \
width: 38px; \
height: 38px; \
border-radius: 50%; \
color: #fff; \
font-weight: 700; \
font-size: 15px; \
} \
.homeproxy-connect-info { \
min-height: 42px; \
padding: 7px 12px; \
border-radius: 8px; \
background: #e9edf2; \
} \
.homeproxy-connect-title { \
color: #7d8aa0; \
font-weight: 700; \
font-size: 13px; \
line-height: 1.2; \
} \
.homeproxy-connect-result { \
margin-top: 2px; \
color: #ff4d35; \
font-weight: 700; \
font-size: 13px; \
cursor: pointer; \
line-height: 1.2; \
}';
const hp_dir = '/var/run/homeproxy';
function getConnStat(o, site) {
const callConnStat = rpc.declare({
object: 'luci.homeproxy',
method: 'connection_check',
params: ['site'],
expect: { '': {} }
});
o.default = E('div', { 'style': 'cbi-value-field' }, [
E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': ui.createHandlerFn(this, () => {
return L.resolveDefault(callConnStat(site), {}).then((ret) => {
let ele = o.default.firstElementChild.nextElementSibling;
if (ret.result) {
ele.style.setProperty('color', 'green');
ele.innerHTML = _('passed');
} else {
ele.style.setProperty('color', 'red');
ele.innerHTML = _('failed');
}
});
})
}, [ _('Check') ]),
' ',
E('strong', { 'style': 'color:gray' }, _('unchecked')),
]);
}
function renderConnCards() {
const callConnStat = rpc.declare({
object: 'luci.homeproxy',
method: 'connection_check',
params: ['site'],
expect: { '': {} }
});
const items = [
{ site: 'baidu', title: _('Baidu Connection'), icon: 'B', color: '#ff7a00' },
{ site: 'google', title: _('Google Connection'), icon: 'G', color: '#4285f4' },
{ site: 'github', title: _('GitHub Connection'), icon: 'GH', color: '#7b3bb3' },
{ site: 'youtube', title: _('YouTube Connection'), icon: 'YT', color: '#ff0033' }
];
return E('div', { 'class': 'homeproxy-connect-grid' }, items.map((item) => {
let result = E('div', {
'class': 'homeproxy-connect-result',
'click': ui.createHandlerFn(this, () => {
result.textContent = _('Checking...');
result.style.setProperty('color', '#f0ad4e');
return L.resolveDefault(callConnStat(item.site), {}).then((ret) => {
if (ret.result && ret.latency != null) {
result.style.setProperty('color', 'green');
result.textContent = _('%s ms').format(ret.latency);
} else {
result.style.setProperty('color', '#ff4d35');
result.textContent = _('failed');
}
});
})
}, [ _('Click to check') ]);
return E('div', { 'class': 'homeproxy-connect-card' }, [
E('div', {
'class': 'homeproxy-connect-icon',
'style': 'background:%s'.format(item.color)
}, [ item.icon ]),
E('div', { 'class': 'homeproxy-connect-info' }, [
E('div', { 'class': 'homeproxy-connect-title' }, [ item.title ]),
result
])
]);
}));
}
function getResVersion(o, type) {
const callResVersion = rpc.declare({
object: 'luci.homeproxy',
method: 'resources_get_version',
params: ['type'],
expect: { '': {} }
});
const callResUpdate = rpc.declare({
object: 'luci.homeproxy',
method: 'resources_update',
params: ['type'],
expect: { '': {} }
});
return L.resolveDefault(callResVersion(type), {}).then((res) => {
let spanTemp = E('div', { 'style': 'cbi-value-field' }, [
E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': ui.createHandlerFn(this, () => {
return L.resolveDefault(callResUpdate(type), {}).then((res) => {
switch (res.status) {
case 0:
o.description = _('Successfully updated.');
break;
case 1:
o.description = _('Update failed.');
break;
case 2:
o.description = _('Already in updating.');
break;
case 3:
o.description = _('Already at the latest version.');
break;
default:
o.description = _('Unknown error.');
break;
}
return o.map.reset();
});
})
}, [ _('Check update') ]),
' ',
E('strong', { 'style': (res.error ? 'color:red' : 'color:green') },
[ res.error ? 'not found' : res.version ]
),
]);
o.default = spanTemp;
});
}
function getRuntimeLog(o, name, _option_index, section_id, _in_table) {
const filename = o.option.split('_')[1];
let section, log_level_el;
switch (filename) {
case 'homeproxy':
section = null;
break;
case 'sing-box-c':
section = 'config';
break;
case 'sing-box-s':
section = 'server';
break;
}
if (section) {
const selected = uci.get('homeproxy', section, 'log_level') || 'warn';
const choices = {
trace: _('Trace'),
debug: _('Debug'),
info: _('Info'),
warn: _('Warn'),
error: _('Error'),
fatal: _('Fatal'),
panic: _('Panic')
};
log_level_el = E('select', {
'id': o.cbid(section_id),
'class': 'cbi-input-select',
'style': 'margin-left: 4px; width: 6em;',
'change': ui.createHandlerFn(this, (ev) => {
uci.set('homeproxy', section, 'log_level', ev.target.value);
return o.map.save(null, true).then(() => {
ui.changes.apply(true);
});
})
});
Object.keys(choices).forEach((v) => {
log_level_el.appendChild(E('option', {
'value': v,
'selected': (v === selected) ? '' : null
}, [ choices[v] ]));
});
}
const callLogClean = rpc.declare({
object: 'luci.homeproxy',
method: 'log_clean',
params: ['type'],
expect: { '': {} }
});
const log_textarea = E('div', { 'id': 'log_textarea' },
E('img', {
'src': L.resource('icons/loading.svg'),
'alt': _('Loading'),
'style': 'vertical-align:middle'
}, _('Collecting data...'))
);
let log;
poll.add(L.bind(() => {
return fs.read_direct(String.format('%s/%s.log', hp_dir, filename), 'text')
.then((res) => {
log = E('pre', { 'wrap': 'pre' }, [
res.trim() || _('Log is empty.')
]);
dom.content(log_textarea, log);
}).catch((err) => {
if (err.toString().includes('NotFoundError'))
log = E('pre', { 'wrap': 'pre' }, [
_('Log file does not exist.')
]);
else
log = E('pre', { 'wrap': 'pre' }, [
_('Unknown error: %s').format(err)
]);
dom.content(log_textarea, log);
});
}));
return E([
E('style', [ css ]),
E('div', {'class': 'cbi-map'}, [
E('h3', {'name': 'content', 'style': 'align-items: center; display: flex;'}, [
_('%s log').format(name),
log_level_el || '',
E('button', {
'class': 'btn cbi-button cbi-button-action',
'style': 'margin-left: 4px;',
'click': ui.createHandlerFn(this, () => {
return L.resolveDefault(callLogClean(filename), {});
})
}, [ _('Clean log') ])
]),
E('div', {'class': 'cbi-section'}, [
log_textarea,
E('div', {'style': 'text-align:right'},
E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval))
)
])
])
]);
}
return view.extend({
render() {
let m, s, o;
hp.installCloseButtonText();
m = new form.Map('homeproxy');
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Connection check'));
s.anonymous = true;
o = s.option(form.DummyValue, '_connection_check');
o.rawhtml = true;
o.render = renderConnCards;
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management'));
s.anonymous = true;
o = s.option(form.DummyValue, '_china_ip4_version', _('China IPv4 list version'));
o.cfgvalue = L.bind(getResVersion, this, o, 'china_ip4');
o.rawhtml = true;
o = s.option(form.DummyValue, '_china_ip6_version', _('China IPv6 list version'));
o.cfgvalue = L.bind(getResVersion, this, o, 'china_ip6');
o.rawhtml = true;
o = s.option(form.DummyValue, '_china_list_version', _('China list version'));
o.cfgvalue = L.bind(getResVersion, this, o, 'china_list');
o.rawhtml = true;
o = s.option(form.DummyValue, '_gfw_list_version', _('GFW list version'));
o.cfgvalue = L.bind(getResVersion, this, o, 'gfw_list');
o.rawhtml = true;
o = s.option(form.Value, 'github_token', _('GitHub token'));
o.password = true;
o.renderWidget = function() {
let node = form.Value.prototype.renderWidget.apply(this, arguments);
(node.querySelector('.control-group') || node).appendChild(E('button', {
'class': 'cbi-button cbi-button-apply',
'title': _('Save'),
'click': ui.createHandlerFn(this, () => {
return this.map.save(null, true).then(() => {
ui.changes.apply(true);
});
}, this.option)
}, [ _('Save') ]));
return node;
}
s = m.section(form.NamedSection, 'config', 'homeproxy');
s.anonymous = true;
o = s.option(form.DummyValue, '_homeproxy_logview');
o.render = L.bind(getRuntimeLog, this, o, _('HomeProxy'));
o = s.option(form.DummyValue, '_sing-box-c_logview');
o.render = L.bind(getRuntimeLog, this, o, _('sing-box client'));
o = s.option(form.DummyValue, '_sing-box-s_logview');
o.render = L.bind(getRuntimeLog, this, o, _('sing-box server'));
return m.render();
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});