2023-06-01 21:12:23 +08:00
|
|
|
|
'use strict';
|
|
|
|
|
'require view';
|
|
|
|
|
'require fs';
|
|
|
|
|
'require ui';
|
2024-07-04 14:26:28 +08:00
|
|
|
|
'require poll';
|
2024-07-05 14:45:17 +08:00
|
|
|
|
'require uci';
|
2023-06-01 21:12:23 +08:00
|
|
|
|
|
|
|
|
|
return view.extend({
|
|
|
|
|
load: function () {
|
2024-07-05 00:04:43 +08:00
|
|
|
|
var self = this;
|
|
|
|
|
// 清除 localStorage 中的排序设置
|
|
|
|
|
localStorage.removeItem('sortColumn');
|
|
|
|
|
localStorage.removeItem('sortDirection');
|
2024-07-05 14:45:17 +08:00
|
|
|
|
uci.load('wechatpush')
|
2024-07-05 00:04:43 +08:00
|
|
|
|
return this.fetchAndRenderDevices().then(function () {
|
|
|
|
|
self.setupAutoRefresh();
|
|
|
|
|
});
|
2023-06-01 21:12:23 +08:00
|
|
|
|
},
|
2024-07-05 00:04:43 +08:00
|
|
|
|
|
|
|
|
|
fetchAndRenderDevices: function () {
|
|
|
|
|
var self = this;
|
|
|
|
|
return this.fetchDevices().then(function (data) {
|
|
|
|
|
var container = self.render(data);
|
|
|
|
|
self.switchContent(container);
|
|
|
|
|
}).catch(function (error) {
|
|
|
|
|
console.error('Error fetching or rendering devices:', error);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fetchDevices: function () {
|
2024-07-07 07:22:17 +08:00
|
|
|
|
return fs.read('/tmp/wechatpush/devices.json').then(function (content) {
|
|
|
|
|
try {
|
|
|
|
|
var data = JSON.parse(content);
|
|
|
|
|
return { devices: data.devices };
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('Error parsing JSON:', e);
|
|
|
|
|
return { devices: [] };
|
|
|
|
|
}
|
2024-07-05 00:04:43 +08:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2023-06-01 21:12:23 +08:00
|
|
|
|
render: function (data) {
|
2024-07-05 00:04:43 +08:00
|
|
|
|
if (!data || !data.devices || !Array.isArray(data.devices)) {
|
|
|
|
|
return document.createElement('div');
|
|
|
|
|
}
|
2024-07-07 21:47:59 +08:00
|
|
|
|
var devices = data.devices.filter(device => device.status === 'online' || device.status === 'unknown');
|
2023-06-01 21:12:23 +08:00
|
|
|
|
var totalDevices = devices.length;
|
|
|
|
|
var headers = [_('Hostname'), _('IPv4 address'), _('MAC address'), _('Interfaces'), _('Online time'), _('Details')];
|
|
|
|
|
var columns = ['name', 'ip', 'mac', 'interface', 'uptime', 'usage'];
|
|
|
|
|
var visibleColumns = [];
|
|
|
|
|
var hasData = false;
|
|
|
|
|
|
2024-07-05 14:45:17 +08:00
|
|
|
|
// 获取配置中的默认排序列
|
|
|
|
|
var defaultSortColumn = uci.get('wechatpush', 'config', 'defaultSortColumn') || 'ip';
|
|
|
|
|
var defaultSortDirection = (defaultSortColumn === 'uptime') ? 'desc' : 'asc';
|
2024-07-04 12:31:19 +08:00
|
|
|
|
|
2024-07-05 00:04:43 +08:00
|
|
|
|
// 获取存储的排序设置,如果没有则使用默认设置
|
|
|
|
|
var storedSortColumn = localStorage.getItem('sortColumn');
|
|
|
|
|
var storedSortDirection = localStorage.getItem('sortDirection');
|
|
|
|
|
|
|
|
|
|
var currentSortColumn = storedSortColumn || defaultSortColumn;
|
|
|
|
|
var currentSortDirection = storedSortDirection || defaultSortDirection;
|
|
|
|
|
|
2024-07-04 12:31:19 +08:00
|
|
|
|
devices.sort(function (a, b) {
|
2024-07-05 20:40:39 +08:00
|
|
|
|
return compareDevices(a, b, currentSortColumn, currentSortDirection);
|
2024-07-04 12:31:19 +08:00
|
|
|
|
});
|
|
|
|
|
|
2024-07-04 14:26:28 +08:00
|
|
|
|
// 根据数据源决定可见列
|
2023-06-01 21:12:23 +08:00
|
|
|
|
for (var i = 0; i < columns.length; i++) {
|
|
|
|
|
var column = columns[i];
|
|
|
|
|
var hasColumnData = false;
|
|
|
|
|
|
|
|
|
|
for (var j = 0; j < devices.length; j++) {
|
|
|
|
|
if (devices[j][column] !== '') {
|
|
|
|
|
hasColumnData = true;
|
|
|
|
|
hasData = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasColumnData) {
|
|
|
|
|
visibleColumns.push(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var style = `
|
2024-07-11 10:52:10 +08:00
|
|
|
|
/* 设备表格样式 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
.device-table {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
width: 80%; /* 表格宽度占满父容器 */
|
|
|
|
|
border-collapse: collapse; /* 合并边框 */
|
|
|
|
|
margin-top: 10px; /* 顶部外边距 */
|
|
|
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* 阴影效果 */
|
|
|
|
|
border-radius: 8px; /* 圆角边框 */
|
|
|
|
|
overflow: hidden; /* 内容溢出隐藏 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
2023-06-01 21:12:23 +08:00
|
|
|
|
.device-table th,
|
|
|
|
|
.device-table td {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
padding: 10px; /* 单元格内边距 */
|
|
|
|
|
text-align: center; /* 文本居中 */
|
|
|
|
|
border: 1px solid #ddd; /* 边框样式 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
2023-06-01 21:12:23 +08:00
|
|
|
|
.device-table th {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
background-color: rgba(0, 0, 0, 0.05); /* 表头背景色,透明 */
|
|
|
|
|
font-weight: bold; /* 粗体字体 */
|
|
|
|
|
cursor: pointer; /* 鼠标指针样式为指针 */
|
|
|
|
|
position: relative; /* 相对定位 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
|
|
|
|
.device-table th.sortable::after {
|
|
|
|
|
right: 10px; /* 右侧距离 */
|
|
|
|
|
top: 50%; /* 顶部偏移50% */
|
|
|
|
|
transform: translateY(-50%); /* 垂直居中 */
|
|
|
|
|
border-width: 5px 5px 0; /* 边框宽度 */
|
|
|
|
|
border-style: solid; /* 边框样式为实线 */
|
|
|
|
|
opacity: 0.6; /* 透明度 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
|
|
|
|
.device-table th.asc::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
right: 10px;
|
|
|
|
|
top: 50%;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
border-width: 6px;
|
|
|
|
|
border-style: solid;
|
|
|
|
|
border-color: #666 transparent transparent transparent; /* 向上箭头颜色 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
|
|
|
|
.device-table th.desc::after {
|
2023-06-01 21:12:23 +08:00
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
2024-07-11 10:52:10 +08:00
|
|
|
|
right: 10px;
|
2023-06-01 21:12:23 +08:00
|
|
|
|
top: 50%;
|
|
|
|
|
transform: translateY(-50%);
|
2024-07-11 10:52:10 +08:00
|
|
|
|
border-width: 6px;
|
2023-06-01 21:12:23 +08:00
|
|
|
|
border-style: solid;
|
2024-07-11 10:52:10 +08:00
|
|
|
|
border-color: transparent transparent #666 transparent; /* 向下箭头颜色 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.device-table tbody tr:nth-child(even) {
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.05); /* 偶数行背景色,透明 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
|
|
|
|
.device-table td:first-child {
|
|
|
|
|
text-align: left; /* 第一列文本左对齐 */
|
|
|
|
|
padding-left: 20px; /* 第一列左侧内边距 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-table td a {
|
|
|
|
|
color: #007bff; /* 链接颜色 */
|
|
|
|
|
text-decoration: none; /* 去掉下划线 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
|
|
|
|
.device-table td a:hover {
|
|
|
|
|
text-decoration: underline; /* 鼠标悬停下划线 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
2023-06-01 21:12:23 +08:00
|
|
|
|
.device-table .hide {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
display: none; /* 隐藏元素 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
2023-06-01 21:12:23 +08:00
|
|
|
|
@media (max-width: 767px) {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
.device-table {
|
|
|
|
|
width: 100%; /* 表格宽度占满父容器 */
|
|
|
|
|
overflow: hidden; /* 内容溢出隐藏 */
|
|
|
|
|
}
|
|
|
|
|
.device-table th,
|
|
|
|
|
.device-table td {
|
|
|
|
|
padding: 3px; /* 单元格内边距 */
|
|
|
|
|
text-align: center; /* 文本居中 */
|
|
|
|
|
border: 0.35px solid #ddd; /* 边框样式 */
|
|
|
|
|
}
|
|
|
|
|
.device-table td:first-child {
|
|
|
|
|
max-width: 80px;
|
|
|
|
|
}
|
|
|
|
|
.device-table td:nth-of-type(5) { /* 控制第五列(Online time)的样式 */
|
|
|
|
|
font-size: 14px; /* 调整字体大小 */
|
2024-07-05 13:50:18 +08:00
|
|
|
|
}
|
2024-07-11 10:52:10 +08:00
|
|
|
|
.device-table td:first-child {
|
|
|
|
|
text-align: left; /* 第一列文本左对齐 */
|
|
|
|
|
padding-left: 2px; /* 第一列左侧内边距 */
|
|
|
|
|
}
|
2023-06-01 21:12:23 +08:00
|
|
|
|
.device-table th:nth-of-type(4),
|
|
|
|
|
.device-table td:nth-of-type(4) {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
display: none; /* 在小屏幕下隐藏第四列 */
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
function createTable() {
|
|
|
|
|
var table = document.createElement('table');
|
|
|
|
|
table.classList.add('device-table');
|
|
|
|
|
|
|
|
|
|
var thead = document.createElement('thead');
|
|
|
|
|
var tr = document.createElement('tr');
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < headers.length; i++) {
|
|
|
|
|
var th = document.createElement('th');
|
|
|
|
|
th.textContent = headers[i];
|
|
|
|
|
|
|
|
|
|
if (visibleColumns.includes(i)) {
|
|
|
|
|
th.classList.add('sortable');
|
|
|
|
|
th.dataset.column = columns[i];
|
2024-07-05 00:04:43 +08:00
|
|
|
|
if (columns[i] === currentSortColumn) {
|
|
|
|
|
th.classList.add(currentSortDirection === 'asc' ? 'asc' : 'desc');
|
|
|
|
|
}
|
2023-06-01 21:12:23 +08:00
|
|
|
|
} else {
|
|
|
|
|
th.classList.add('hide');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tr.appendChild(th);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
thead.appendChild(tr);
|
|
|
|
|
table.appendChild(thead);
|
|
|
|
|
|
|
|
|
|
var tbody = document.createElement('tbody');
|
|
|
|
|
devices.forEach(function (device) {
|
|
|
|
|
var row = document.createElement('tr');
|
|
|
|
|
for (var i = 0; i < columns.length; i++) {
|
|
|
|
|
if (visibleColumns.includes(i)) {
|
|
|
|
|
var cell = document.createElement('td');
|
2024-07-04 14:26:28 +08:00
|
|
|
|
if (columns[i] === 'uptime') {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
cell.textContent = calculateUptime(device['uptime'], window.innerWidth <= 767);
|
2024-07-04 16:12:08 +08:00
|
|
|
|
} else if (columns[i] === 'ip' && device['http_access']) {
|
|
|
|
|
var link = document.createElement('a');
|
|
|
|
|
link.href = `${device['http_access']}://${device['ip']}`;
|
|
|
|
|
link.textContent = device['ip'];
|
|
|
|
|
link.target = '_blank';
|
|
|
|
|
cell.appendChild(link);
|
2024-07-04 14:26:28 +08:00
|
|
|
|
} else {
|
|
|
|
|
cell.textContent = device[columns[i]];
|
|
|
|
|
}
|
2023-06-01 21:12:23 +08:00
|
|
|
|
row.appendChild(cell);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tbody.appendChild(row);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
table.appendChild(tbody);
|
|
|
|
|
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 10:52:10 +08:00
|
|
|
|
function calculateUptime(uptime, simpleFormat = false) {
|
2024-07-04 14:26:28 +08:00
|
|
|
|
var startTimeStamp = parseInt(uptime);
|
|
|
|
|
var currentTimeStamp = Math.floor(Date.now() / 1000);
|
|
|
|
|
var uptimeInSeconds = currentTimeStamp - startTimeStamp;
|
|
|
|
|
|
|
|
|
|
var days = Math.floor(uptimeInSeconds / (3600 * 24));
|
|
|
|
|
var hours = Math.floor((uptimeInSeconds % (3600 * 24)) / 3600);
|
|
|
|
|
var minutes = Math.floor((uptimeInSeconds % 3600) / 60);
|
|
|
|
|
var seconds = uptimeInSeconds % 60;
|
|
|
|
|
|
2024-07-11 10:52:10 +08:00
|
|
|
|
if (simpleFormat) {
|
|
|
|
|
if (days > 0) {
|
|
|
|
|
return days + 'd ' + hours + 'h';
|
|
|
|
|
} else if (hours > 0) {
|
|
|
|
|
return hours + 'h ' + minutes + 'm';
|
|
|
|
|
} else if (minutes > 0) {
|
|
|
|
|
return minutes + 'm ' + seconds + 's';
|
|
|
|
|
} else {
|
|
|
|
|
return seconds + 's';
|
|
|
|
|
}
|
2024-07-04 14:26:28 +08:00
|
|
|
|
} else {
|
2024-07-11 10:52:10 +08:00
|
|
|
|
if (days > 0) {
|
|
|
|
|
return days + ' 天 ' + hours + ' 小时';
|
|
|
|
|
} else if (hours > 0) {
|
|
|
|
|
return hours + ' 小时 ' + minutes + ' 分';
|
|
|
|
|
} else if (minutes > 0) {
|
|
|
|
|
return minutes + ' 分 ' + seconds + ' 秒';
|
|
|
|
|
} else {
|
|
|
|
|
return seconds + ' 秒';
|
|
|
|
|
}
|
2024-07-04 14:26:28 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 10:52:10 +08:00
|
|
|
|
|
2024-07-05 20:40:39 +08:00
|
|
|
|
function compareDevices(a, b, column, direction) {
|
|
|
|
|
var value1 = getValueForSorting(a, column);
|
|
|
|
|
var value2 = getValueForSorting(b, column);
|
|
|
|
|
|
|
|
|
|
if (value1 < value2) {
|
|
|
|
|
return direction === 'asc' ? -1 : 1;
|
|
|
|
|
} else if (value1 > value2) {
|
|
|
|
|
return direction === 'asc' ? 1 : -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 00:04:43 +08:00
|
|
|
|
function getValueForSorting(device, column) {
|
|
|
|
|
var value = device[column];
|
|
|
|
|
if (column === 'uptime') {
|
|
|
|
|
// 使用时间戳排序
|
|
|
|
|
return parseInt(device['uptime']);
|
|
|
|
|
} else if (column === 'ip') {
|
|
|
|
|
return ipToNumber(value);
|
2024-07-04 14:26:28 +08:00
|
|
|
|
}
|
2024-07-05 00:04:43 +08:00
|
|
|
|
return value;
|
2024-07-04 14:26:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ipToNumber(ipAddress) {
|
|
|
|
|
var parts = ipAddress.split('.');
|
|
|
|
|
var number = 0;
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < parts.length; i++) {
|
|
|
|
|
number = number * 256 + parseInt(parts[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return number;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-01 21:12:23 +08:00
|
|
|
|
var container = document.createElement('div');
|
|
|
|
|
container.appendChild(document.createElement('h2')).textContent = _('当前共 ') + totalDevices + _(' 台设备在线');
|
|
|
|
|
container.appendChild(createTable());
|
|
|
|
|
container.appendChild(document.createElement('style')).textContent = style;
|
|
|
|
|
|
2024-07-04 12:31:19 +08:00
|
|
|
|
container.addEventListener('click', function (event) {
|
2024-07-05 00:04:43 +08:00
|
|
|
|
if (event.target.tagName === 'TH' && event.target.parentNode.rowIndex === 0) {
|
2024-07-04 12:31:19 +08:00
|
|
|
|
var columnIndex = event.target.cellIndex;
|
2024-07-05 00:04:43 +08:00
|
|
|
|
var column = columns[columnIndex];
|
|
|
|
|
var direction = 'asc';
|
2024-07-05 20:40:39 +08:00
|
|
|
|
|
|
|
|
|
// 使在线时间第一次点击方向为倒序
|
|
|
|
|
if (column === 'uptime') {
|
|
|
|
|
direction = currentSortDirection === 'desc' ? 'asc' : 'desc';
|
|
|
|
|
} else if (column === currentSortColumn) {
|
2024-07-05 00:04:43 +08:00
|
|
|
|
direction = currentSortDirection === 'asc' ? 'desc' : 'asc';
|
|
|
|
|
}
|
2024-07-05 20:40:39 +08:00
|
|
|
|
|
2024-07-05 00:04:43 +08:00
|
|
|
|
sortTable(column, direction, container);
|
2024-07-04 12:31:19 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-07-05 00:04:43 +08:00
|
|
|
|
function sortTable(column, direction, container) {
|
|
|
|
|
devices.sort(function (a, b) {
|
2024-07-05 20:40:39 +08:00
|
|
|
|
return compareDevices(a, b, column, direction);
|
2023-06-01 21:12:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
2024-07-05 00:04:43 +08:00
|
|
|
|
currentSortColumn = column;
|
|
|
|
|
currentSortDirection = direction;
|
2023-06-01 21:12:23 +08:00
|
|
|
|
|
2024-07-05 00:04:43 +08:00
|
|
|
|
// 存储排序设置
|
|
|
|
|
localStorage.setItem('sortColumn', currentSortColumn);
|
|
|
|
|
localStorage.setItem('sortDirection', currentSortDirection);
|
2023-06-01 21:12:23 +08:00
|
|
|
|
|
2024-07-05 00:04:43 +08:00
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
container.appendChild(document.createElement('h2')).textContent = _('当前共 ') + totalDevices + _(' 台设备在线');
|
|
|
|
|
container.appendChild(createTable());
|
|
|
|
|
container.appendChild(document.createElement('style')).textContent = style;
|
2023-06-01 21:12:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return container;
|
|
|
|
|
},
|
2024-07-05 00:04:43 +08:00
|
|
|
|
|
|
|
|
|
setupAutoRefresh: function () {
|
|
|
|
|
var self = this;
|
|
|
|
|
poll.add(L.bind(function () {
|
|
|
|
|
self.fetchAndRenderDevices();
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
switchContent: function (newContent) {
|
|
|
|
|
var existingContainer = document.querySelector('#view');
|
|
|
|
|
if (!existingContainer) {
|
|
|
|
|
console.error('Table container not found.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
existingContainer.innerHTML = '';
|
|
|
|
|
existingContainer.appendChild(newContent);
|
|
|
|
|
},
|
|
|
|
|
|
2023-06-01 21:12:23 +08:00
|
|
|
|
handleSave: null,
|
|
|
|
|
handleSaveApply: null,
|
|
|
|
|
handleReset: null
|
|
|
|
|
});
|