🤞 Sync 2026-09-30 23:44:05

This commit is contained in:
github-actions[bot] committed 2026-09-30 23:44:05 +08:00
1 parent e3fde93105
commit 44bb740ab2
206 files changed
+5736 -2799

No files matched your search

@@ -255,13 +255,17 @@ function renderLogs(logs_data) {
}, lines);
}
function renderDevices(status) {
function renderPeerRows(status, query) {
if (!status || !status.hasOwnProperty('status')) {
return E('em', {}, _('Collecting data ...'));
return [E('tr', { 'class': 'cbi-section-table-row' }, [
E('td', { 'class': 'cbi-value-field', 'colspan': 8 }, E('em', {}, _('Collecting data ...')))
])];
}
if (status.status != 'running') {
return E('em', {}, _('Tailscale status error'));
return [E('tr', { 'class': 'cbi-section-table-row' }, [
E('td', { 'class': 'cbi-value-field', 'colspan': 8 }, E('em', {}, _('Tailscale status error')))
])];
}
if (Object.keys(regionCodeMap).length === 0) {
@@ -270,45 +274,28 @@ function renderDevices(status) {
const peers = status.peers;
if (!peers || Object.keys(peers).length === 0) {
return E('p', {}, _('No peer devices found.'));
return [E('tr', { 'class': 'cbi-section-table-row' }, [
E('td', { 'class': 'cbi-value-field', 'colspan': 8 }, E('p', {}, _('No peer devices found.')))
])];
}
const peerTableHeaders = [
{ text: _('Status'), style: 'width: 80px;' },
{ text: _('Hostname') },
{ text: _('Tailscale IP') },
{ text: _('OS') },
{ text: _('Connection Info') },
{ text: _('RX') },
{ text: _('TX') },
{ text: _('Last Seen') }
];
const q = (query || '').toLowerCase().trim();
const td_style = 'padding-right: 20px;';
const rows = [];
const existingFilter = document.getElementById('tailscale_devices_filter');
const currentQuery = existingFilter ? existingFilter.value : '';
const searchInput = E('input', {
'id': 'tailscale_devices_filter',
'class': 'cbi-input-text',
'type': 'text',
'placeholder': _('Filter by hostname, IP, OS...'),
'style': 'margin-bottom: 8px; width: 100%; max-width: 320px;'
});
if (currentQuery) {
searchInput.value = currentQuery;
}
const rows = Object.entries(peers).map(([peerid, peer]) => {
const td_style = 'padding-right: 20px;';
for (const id in peers) {
const peer = peers[id];
const hostname = peer.hostname || '';
const ip = peer.ip || '';
const ostype = peer.ostype || '';
const searchText = `${hostname} ${ip} ${ostype}`.toLowerCase();
const tr = E('tr', {
'class': 'cbi-rowstyle-1',
'data-search-text': searchText
}, [
if (q && !hostname.toLowerCase().includes(q) &&
!ip.toLowerCase().includes(q) &&
!ostype.toLowerCase().includes(q)) {
continue;
}
rows.push(E('tr', { 'class': 'cbi-rowstyle-1' }, [
E('td', { 'class': 'cbi-value-field', 'style': td_style },
E('span', {
'style': `color:${peer.exit_node ? 'blue' : (peer.online ? 'green' : 'gray')};`,
@@ -322,58 +309,71 @@ function renderDevices(status) {
E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.rx)),
E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.tx)),
E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatLastSeen(peer.lastseen))
]);
if (currentQuery && !searchText.includes(currentQuery.toLowerCase().trim())) {
tr.style.display = 'none';
}
return tr;
});
searchInput.addEventListener('input', function(ev) {
const q = (ev.target.value || '').toLowerCase().trim();
for (const tr of rows) {
const text = tr.getAttribute('data-search-text') || '';
tr.style.display = (!q || text.includes(q)) ? '' : 'none';
}
});
if (existingFilter && document.activeElement === existingFilter) {
requestAnimationFrame(() => {
searchInput.focus();
const valLen = searchInput.value.length;
searchInput.setSelectionRange(valLen, valLen);
});
]));
}
const existingContainer = document.getElementById('tailscale_devices_table_container');
const scrollPos = existingContainer ? existingContainer.scrollTop : 0;
if (rows.length === 0) {
return [E('tr', { 'class': 'cbi-section-table-row' }, [
E('td', { 'class': 'cbi-value-field', 'colspan': 8 }, E('p', {}, _('No peer devices found.')))
])];
}
const table = E('table', { 'class': 'cbi-table' }, [
E('tr', { 'class': 'cbi-table-header' }, peerTableHeaders.map(header => {
let th_style = 'padding-right: 20px; text-align: left; position: sticky; top: 0; background: inherit; z-index: 1;';
return rows;
}
function renderDevices(status) {
const thead = E('thead', {}, [
E('tr', {
'class': 'tr cbi-section-table-titles',
'style': 'position: sticky; top: 0; z-index: 10; background-color: var(--background-color, #fff);'
}, peerTableHeaders.map(function(header) {
let th_style = 'padding-right: 20px; text-align: left;';
if (header.style) {
th_style += header.style;
}
return E('th', { 'class': 'cbi-table-cell', 'style': th_style }, header.text);
})),
...rows
return E('th', { 'class': 'th cbi-table-cell', 'style': th_style }, header.text);
}))
]);
const tbody = E('tbody', { 'id': 'tailscale_devices_tbody' }, renderPeerRows(status, ''));
const table = E('table', { 'class': 'table cbi-table' }, [
thead,
tbody
]);
const tableContainer = E('div', {
'id': 'tailscale_devices_table_container',
'style': 'max-height: 480px; overflow-y: auto; overflow-x: auto; border: 1px solid #ddd; border-radius: 3px;'
'style': 'max-height: 480px; overflow-y: auto; overflow-x: auto;'
}, [table]);
if (scrollPos > 0) {
requestAnimationFrame(() => {
tableContainer.scrollTop = scrollPos;
});
}
const searchInput = E('input', {
'id': 'tailscale_devices_filter',
'type': 'text',
'class': 'cbi-input-text',
'placeholder': _('Filter peers by name, IP, or OS...'),
'style': 'width: 100%; box-sizing: border-box;'
});
searchInput.addEventListener('input', function() {
const targetBody = document.getElementById('tailscale_devices_tbody');
if (targetBody && lastDevicesStatus) {
targetBody.replaceChildren(...renderPeerRows(lastDevicesStatus, this.value));
}
});
const filterContainer = E('div', {
'style': 'display: flex; align-items: center; gap: 0.5em; margin-bottom: 0.5em;'
}, [
E('label', {
'for': 'tailscale_devices_filter',
'style': 'white-space: nowrap; font-weight: bold;'
}, _('Filter:')),
E('div', { 'style': 'flex: 1;' }, [searchInput])
]);
return E('div', { 'style': 'width: 100%;' }, [
searchInput,
filterContainer,
tableContainer
]);
}
@@ -440,9 +440,12 @@ return view.extend({
view.replaceChildren(content);
}
const devicesView = document.getElementById("tailscale_devices_display");
if (devicesView) {
devicesView.replaceChildren(renderDevices(res));
lastDevicesStatus = res;
const devicesTbody = document.getElementById("tailscale_devices_tbody");
if (devicesTbody) {
const filterInput = document.getElementById("tailscale_devices_filter");
const currentQuery = filterInput ? filterInput.value : '';
devicesTbody.replaceChildren(...renderPeerRows(res, currentQuery));
}
// login button only available when logged out
@@ -644,6 +647,7 @@ return view.extend({
s.tab('devices', _('Devices List'));
const devicesSection = s.taboption('devices', form.DummyValue, '_devices');
devicesSection.render = function () {
lastDevicesStatus = status;
return E('div', { 'id': 'tailscale_devices_display', 'class': 'cbi-value' }, renderDevices(status));
};