mirror of
https://github.com/lbl8603/vnts.git
synced 2025-12-24 12:47:51 +08:00
569 lines
19 KiB
HTML
569 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<script src="./js/jquery-3.7.1.min.js"></script>
|
|
<script src="./js/g6.min.js"></script>
|
|
<script src="./js/qrcode.min.js"></script>
|
|
<script src="./js/api-post.js"></script>
|
|
|
|
<link rel="stylesheet" href="./css/select.css">
|
|
<link rel="stylesheet" href="./css/index.css">
|
|
<title>vnts-web</title>
|
|
<style>
|
|
|
|
.select-content:before {
|
|
content: "组网标识";
|
|
position: absolute;
|
|
left: -76px;
|
|
line-height: 48px;
|
|
}
|
|
|
|
#container_content {
|
|
position: relative;
|
|
margin-left: 240px;
|
|
padding-left: 10px;
|
|
height: 100%;
|
|
}
|
|
|
|
#container {
|
|
height: calc(100vh - 260px);
|
|
}
|
|
|
|
|
|
.network_info > div {
|
|
height: 20px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
#group_info_show {
|
|
max-height: calc(100vh - 200px);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
table, th, td {
|
|
border: 1px solid #ccc;
|
|
}
|
|
|
|
th, td {
|
|
padding: 8px;
|
|
text-align: left;
|
|
}
|
|
|
|
th {
|
|
background-color: #f2f2f2;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="bigbox">
|
|
<!-- 点击效果 -->
|
|
<!-- <input type="checkbox" id="checkbox"/>
|
|
<label for="checkbox">
|
|
<img src="./svg/组网交换机.sxvg" alt="功能按钮">
|
|
<img src="./svg/组网交换机.sxvg" alt="功能按钮">
|
|
<img src="./svg/组网交换机.sxvg" alt="功能按钮">
|
|
</label>-->
|
|
<ul>
|
|
<li>
|
|
<img src="./svg/组网交换机.svg" alt="VNT"/>
|
|
<span>VNT</span>
|
|
</li>
|
|
<li>
|
|
<a href="#">
|
|
<img src="svg/组网管理.svg" alt="1">
|
|
<span>组网管理</span>
|
|
</a>
|
|
</li>
|
|
<!-- <li>
|
|
<a href="#">
|
|
<img src="./svg/控制面板.svg" alt="3">
|
|
<span>控制面板</span>
|
|
</a>
|
|
</li>-->
|
|
</ul>
|
|
|
|
</div>
|
|
<div class="topBox">
|
|
<div class="select-content">
|
|
<!-- <label>组网标识:</label>-->
|
|
<input type="hidden" name="groupId">
|
|
<input type="text" name="select_input" id="select_input" class="select-input" value="" autocomplete="off"
|
|
placeholder="Search..."/>
|
|
<div id="search_select" class="search-select">
|
|
<ul id="select_ul" class="select-ul">
|
|
|
|
</ul>
|
|
</div>
|
|
<span class="group_len"></span>
|
|
</div>
|
|
<button id="addWireGuard">接入WireGuard客户端</button>
|
|
</div>
|
|
<div id="container_content">
|
|
<div id="group_info">
|
|
<div class="network_info">
|
|
<div class="gateway_ip"></div>
|
|
<div class="mask_ip"></div>
|
|
<div class="network_ip"></div>
|
|
</div>
|
|
<div id="group_info_show">
|
|
<div id="container">
|
|
|
|
</div>
|
|
<table id="deviceTable">
|
|
<thead>
|
|
<tr>
|
|
<th>虚拟 IP</th>
|
|
<th>名称</th>
|
|
<th>版本</th>
|
|
<th>在线状态</th>
|
|
<th>客户端加密</th>
|
|
<th>服务器加密</th>
|
|
<th>连接时间</th>
|
|
<th>链接地址</th>
|
|
<th>设备 ID</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<!-- wg信息弹窗 -->
|
|
<div id="wgConfigModal" class="modal">
|
|
<div class="modal-content">
|
|
<span class="title">使用WireGuard客户端接入</span>
|
|
<span class="close">×</span>
|
|
<div id="qrcode" class="visible"></div>
|
|
<pre id="textConfig" class="hidden"></pre>
|
|
<button id="toggleButton">显示文本配置</button>
|
|
</div>
|
|
</div>
|
|
<!-- 添加wg弹窗 -->
|
|
<div id="addModal" class="modal">
|
|
<div class="add-modal-content">
|
|
<span class="close">×</span>
|
|
<h2>WireGuard配置</h2>
|
|
<div class="form-group">
|
|
<label for="groupId">组网编号</label>
|
|
<input type="text" id="groupId" placeholder="组网编号">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="virtualIP">虚拟IP</label>
|
|
<input type="text" id="virtualIP" placeholder="为空则自动分配">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="deviceName">设备名称</label>
|
|
<input type="text" id="deviceName" placeholder="设备名称">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="privateKey">PrivateKey</label>
|
|
<input type="text" id="privateKey" placeholder="PrivateKey">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="endpoint">Endpoint</label>
|
|
<input type="text" id="endpoint" placeholder="服务器UDP链接地址">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="persistentKeepalive">PersistentKeepalive</label>
|
|
<input type="number" id="persistentKeepalive" value="10" placeholder="PersistentKeepalive">
|
|
</div>
|
|
<div class="button-container">
|
|
<button id="confirmButton">确认</button>
|
|
</div>
|
|
<div class="error" id="addWGError"></div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
<script src="js/group-node.js"></script>
|
|
|
|
<script>
|
|
if (!auth) {
|
|
window.location.replace("login.html");
|
|
}
|
|
|
|
|
|
console.log("auth" + auth)
|
|
</script>
|
|
<script>
|
|
/* 点击按钮设置下拉菜单的显示与隐藏 */
|
|
|
|
function newOptions(tempArr) {
|
|
var listArr = [];
|
|
for (var i = 0; i < tempArr.length; i++) {
|
|
if (tempArr[i].indexOf($('#select_input').val()) > -1) {
|
|
listArr.push(tempArr[i]);
|
|
}
|
|
}
|
|
var options = '';
|
|
for (var i = 0; i < listArr.length; i++) {
|
|
opt = '<li class="li-select" data-groupId="' + listArr[i] + '">' + listArr[i] + '</li>';
|
|
options += opt;
|
|
}
|
|
if (options == '') {
|
|
$('#search_select').hide();
|
|
} else {
|
|
$('#search_select').show();
|
|
$('#select_ul').html('').append(options);
|
|
}
|
|
}
|
|
|
|
function searchInput(tempArr) {
|
|
$('#select_input').on('keyup', function () {
|
|
newOptions(tempArr);
|
|
});
|
|
$('#select_input').on('focus', function () {
|
|
$('#search_select').show();
|
|
newOptions(tempArr);
|
|
});
|
|
$('#select_ul .li-disabled').on('click', function () {
|
|
$('#search_select').show();
|
|
});
|
|
$('#search_select').on('mouseover', function () {
|
|
$(this).addClass('ul-hover');
|
|
});
|
|
$('#search_select').on('mouseout', function () {
|
|
$(this).removeClass('ul-hover');
|
|
});
|
|
$('#select_input').on('blur', function () {
|
|
if ($('#search_select').hasClass('ul-hover')) {
|
|
$('#search_select').show();
|
|
} else {
|
|
$('#search_select').hide();
|
|
}
|
|
});
|
|
$('#select_ul').delegate('.li-select', 'click', function () {
|
|
$('#select_ul .li-select').removeClass('li-hover');
|
|
var selectText = $(this).html();
|
|
var groupIdVal = $($(this)[0]).attr("data-groupId");
|
|
$('#select_input').val(selectText);
|
|
$('#search_select').hide();
|
|
$("input[name='groupId']").val(groupIdVal);
|
|
getGroupInfoFunc(groupIdVal)
|
|
|
|
});
|
|
$('#select_ul').delegate('.li-select', 'mouseover', function () {
|
|
$('#select_ul .li-select').removeClass('li-hover');
|
|
$(this).addClass('li-hover');
|
|
});
|
|
if (tempArr.length > 0) {
|
|
$('#select_input').val(tempArr[0])
|
|
getGroupInfoFunc(tempArr[0])
|
|
}
|
|
$('.group_len').html('(' + tempArr.length + ')')
|
|
}
|
|
|
|
function displayDeviceInfo(groupId, data) {
|
|
let devices = data.clients;
|
|
const tableBody = document.getElementById('deviceTable').getElementsByTagName('tbody')[0];
|
|
tableBody.innerHTML = "";
|
|
devices.forEach(device => {
|
|
const row = document.createElement('tr');
|
|
|
|
const virtualIpCell = document.createElement('td');
|
|
virtualIpCell.textContent = device.virtual_ip;
|
|
row.appendChild(virtualIpCell);
|
|
|
|
const nameCell = document.createElement('td');
|
|
nameCell.textContent = device.name;
|
|
row.appendChild(nameCell);
|
|
|
|
const versionCell = document.createElement('td');
|
|
versionCell.textContent = device.version;
|
|
row.appendChild(versionCell);
|
|
|
|
const onlineCell = document.createElement('td');
|
|
onlineCell.textContent = device.online ? '在线' : '离线';
|
|
row.appendChild(onlineCell);
|
|
|
|
const clientSecretCell = document.createElement('td');
|
|
clientSecretCell.textContent = device.client_secret ? '是' : '否';
|
|
row.appendChild(clientSecretCell);
|
|
|
|
const serverSecretCell = document.createElement('td');
|
|
serverSecretCell.textContent = device.server_secret ? '是' : '否';
|
|
row.appendChild(serverSecretCell);
|
|
|
|
const lastJoinTimeCell = document.createElement('td');
|
|
lastJoinTimeCell.textContent = device.last_join_time;
|
|
row.appendChild(lastJoinTimeCell);
|
|
|
|
const addressCell = document.createElement('td');
|
|
addressCell.textContent = device.address;
|
|
row.appendChild(addressCell);
|
|
|
|
const deviceIdCell = document.createElement('td');
|
|
deviceIdCell.textContent = device.device_id;
|
|
row.appendChild(deviceIdCell);
|
|
// 操作栏
|
|
const optionCell = document.createElement('td');
|
|
optionCell.className = 'option-cell';
|
|
const deleteButton = document.createElement('button');
|
|
optionCell.appendChild(deleteButton);
|
|
deleteButton.className = 'delete-button';
|
|
deleteButton.textContent = '删除';
|
|
deleteButton.onclick = function () {
|
|
const confirmed = window.confirm('你确定要删除这条记录吗?');
|
|
if (confirmed) {
|
|
postRemoveClient({'group_id': groupId, 'virtual_ip': device.virtual_ip}, function () {
|
|
location.reload();
|
|
});
|
|
row.remove();
|
|
}
|
|
|
|
};
|
|
let wg_config = device.wg_config;
|
|
if (wg_config) {
|
|
function generateWireguardConfig(privateKey, ip, prefix, publicKey, allowedIPs, endpoint, persistentKeepalive) {
|
|
return `[Interface]
|
|
PrivateKey = ${privateKey}
|
|
Address = ${ip}/${prefix}
|
|
[Peer]
|
|
PublicKey = ${publicKey}
|
|
AllowedIPs = ${allowedIPs}
|
|
Endpoint = ${endpoint}
|
|
PersistentKeepalive = ${persistentKeepalive}`;
|
|
}
|
|
|
|
const wireguardConfig = generateWireguardConfig(wg_config.secret_key, wg_config.ip, wg_config.prefix,
|
|
data.vnts_public_key, wg_config.vnts_allowed_ips, wg_config.vnts_endpoint, wg_config.persistent_keepalive);
|
|
console.log(wireguardConfig);
|
|
const viewButton = document.createElement('button');
|
|
viewButton.textContent = '接入';
|
|
viewButton.className = 'view-button';
|
|
viewButton.onclick = function () {
|
|
// 显示弹窗
|
|
$('#wgConfigModal').show();
|
|
// 设置文本配置
|
|
$('#textConfig').text(wireguardConfig);
|
|
$('#qrcode').html('');
|
|
// 生成二维码
|
|
new QRCode(document.getElementById("qrcode"), {
|
|
correctLevel: 3,
|
|
text: wireguardConfig,
|
|
width: 256,
|
|
height: 256
|
|
});
|
|
|
|
};
|
|
optionCell.appendChild(viewButton);
|
|
}
|
|
row.appendChild(optionCell);
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
</script>
|
|
<script>
|
|
let groupId;
|
|
let deviceId = 'wg' + new Date().getTime();
|
|
$('#addWireGuard').on('click', function () {
|
|
$('#groupId').val(groupId);
|
|
$('#deviceName').val('WireGuard');
|
|
postWgPrivateKey({}, function (data) {
|
|
$('#privateKey').val(data.data);
|
|
$('.error').text('');
|
|
$('#addModal').show();
|
|
})
|
|
});
|
|
$('#confirmButton').on('click', function () {
|
|
// 清除以前的错误信息
|
|
$('.error').text('');
|
|
// 获取输入值
|
|
const groupId = $('#groupId').val().trim();
|
|
const virtualIP = $('#virtualIP').val().trim();
|
|
const deviceName = $('#deviceName').val().trim();
|
|
const privateKey = $('#privateKey').val().trim();
|
|
const endpoint = $('#endpoint').val().trim();
|
|
const persistentKeepalive = $('#persistentKeepalive').val().trim();
|
|
// 校验输入值
|
|
if (!groupId) {
|
|
$('#addWGError').text('组网编号不能为空');
|
|
return
|
|
}
|
|
|
|
if (!deviceName) {
|
|
$('#addWGError').text('设备名称不能为空');
|
|
return;
|
|
}
|
|
|
|
if (!privateKey) {
|
|
$('#addWGError').text('PrivateKey不能为空');
|
|
return;
|
|
}
|
|
|
|
if (!endpoint) {
|
|
$('#addWGError').text('Endpoint不能为空');
|
|
return;
|
|
}
|
|
|
|
if (persistentKeepalive === '') {
|
|
$('#addWGError').text('PersistentKeepalive不能为空');
|
|
return;
|
|
} else if (isNaN(persistentKeepalive) || persistentKeepalive < 0 || persistentKeepalive > 65535) {
|
|
$('#addWGError').text('PersistentKeepalive必须是0~65535');
|
|
return;
|
|
}
|
|
postCreateWG({
|
|
'group_id': groupId, 'virtual_ip': virtualIP, 'device_id': deviceId, 'name': deviceName,
|
|
'config': {
|
|
'vnts_endpoint': endpoint,
|
|
'private_key': privateKey,
|
|
'persistent_keepalive': parseInt(persistentKeepalive),
|
|
}
|
|
}, function (data) {
|
|
if (data && data.code === 200) {
|
|
location.reload();
|
|
$('#addModal').hide(); // 关闭模态框
|
|
} else {
|
|
$('#addWGError').text('添加失败:' + data.message);
|
|
}
|
|
|
|
})
|
|
});
|
|
|
|
function formatBytes(bytes) {
|
|
if (!bytes) {
|
|
return ''
|
|
}
|
|
const gigabytes = Math.floor(bytes / (1024 * 1024 * 1024));
|
|
let remaining_bytes = bytes % (1024 * 1024 * 1024);
|
|
const megabytes = Math.floor(remaining_bytes / (1024 * 1024));
|
|
remaining_bytes = remaining_bytes % (1024 * 1024);
|
|
const kilobytes = Math.floor(remaining_bytes / 1024);
|
|
remaining_bytes = remaining_bytes % 1024;
|
|
let s = '';
|
|
if (gigabytes > 0) {
|
|
s += gigabytes + ' GB ';
|
|
}
|
|
if (megabytes > 0) {
|
|
s += megabytes + ' MB ';
|
|
}
|
|
if (kilobytes > 0) {
|
|
s += kilobytes + ' KB ';
|
|
}
|
|
if (remaining_bytes > 0) {
|
|
s += remaining_bytes + ' bytes';
|
|
}
|
|
return s.trim();
|
|
}
|
|
|
|
let nodeInitFunc = function (gateway_ip, clients) {
|
|
let nodes = [{
|
|
id: gateway_ip,
|
|
label: gateway_ip,
|
|
x: 0,
|
|
y: 0,
|
|
type: 'ellipse',
|
|
size: [80, 60],
|
|
icon: {
|
|
show: true,
|
|
img: './svg/路由器.svg',
|
|
width: 40,
|
|
height: 40,
|
|
},
|
|
labelCfg: {
|
|
style: {
|
|
fill: '#1890ff',
|
|
fontSize: 14,
|
|
background: {
|
|
fill: '#ffffff',
|
|
stroke: '#9EC9FF',
|
|
padding: [2, 2, 2, 2],
|
|
radius: 2,
|
|
},
|
|
},
|
|
position: 'bottom',
|
|
}
|
|
}];
|
|
let edges = [];
|
|
for (let index in clients) {
|
|
let client = clients[index];
|
|
let status_info = client.status_info;
|
|
if (!status_info) {
|
|
status_info = {};
|
|
}
|
|
let node = {
|
|
clientName: client.name,
|
|
clientVersion: client.version,
|
|
deviceId: client.device_id,
|
|
lastJoinTime: client.last_join_time,
|
|
natType: status_info.is_cone == null ? '' : (status_info.is_cone ? '锥形' : '对称'),
|
|
upStream: formatBytes(status_info.up_stream),
|
|
downStream: formatBytes(status_info.down_stream),
|
|
clientSecret: client.client_secret,
|
|
id: client.virtual_ip,
|
|
name: '',
|
|
ip: client.virtual_ip,
|
|
nodeError: !client.online,
|
|
dataType: client.name,
|
|
keyInfo: client.address,
|
|
};
|
|
nodes.push(node);
|
|
if (client.online) {
|
|
if (client.status_info && client.status_info.p2p_list) {
|
|
let p2p_list = client.status_info.p2p_list;
|
|
for (let index in p2p_list) {
|
|
edges.push({
|
|
source: client.virtual_ip,
|
|
target: p2p_list[index],
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let data = {
|
|
nodes: nodes,
|
|
edges: edges,
|
|
};
|
|
console.log(data)
|
|
|
|
graph.data(data);
|
|
graph.render();
|
|
}
|
|
let getGroupInfoFunc = function (group) {
|
|
groupId = group;
|
|
postGroupInfo({'group': group}, function (response) {
|
|
if (response && response.code === 200) {
|
|
let data = response.data;
|
|
$('.gateway_ip').html(data.gateway_ip);
|
|
$('.mask_ip').html(data.mask_ip);
|
|
$('.network_ip').html(data.network_ip);
|
|
nodeInitFunc(data.gateway_ip, data.clients);
|
|
displayDeviceInfo(group, data);
|
|
} else {
|
|
window.alert("调用服务失败")
|
|
}
|
|
|
|
})
|
|
}
|
|
postGroupList({}, function (result) {
|
|
let group_list = result.data.group_list;
|
|
console.log(group_list);
|
|
searchInput(group_list);
|
|
});
|
|
$('.close').on('click', function () {
|
|
$('#wgConfigModal').hide();
|
|
$('#addModal').hide();
|
|
});
|
|
$('#toggleButton').on('click', function () {
|
|
if ($('#qrcode').hasClass('visible')) {
|
|
$('#qrcode').removeClass('visible').addClass('hidden');
|
|
$('#textConfig').removeClass('hidden').addClass('visible');
|
|
$('#toggleButton').text('显示二维码');
|
|
} else {
|
|
$('#qrcode').removeClass('hidden').addClass('visible');
|
|
$('#textConfig').removeClass('visible').addClass('hidden');
|
|
$('#toggleButton').text('显示文本配置');
|
|
}
|
|
});
|
|
</script>
|
|
</html> |