Files
vnts/static/index.html
2024-07-20 10:34:55 +08:00

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">&times;</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">&times;</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>