// 使用相对路径,自动适配当前域名
const API_BASE = '/api';
let currentPage = 1;
let pageSize = 10;
let total = 0;
let editingId = null;
let currentStatusFilter = ''; // 当前状态筛选:''(全部)、'activated'(已激活)、'unactivated'(未激活)
let allLicenses = []; // 存储所有License数据用于统计
// Toast 通知函数
function showToast(message, type = 'info', duration = 3000) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const icons = {
success: '✅',
error: '❌',
warning: '⚠️',
info: 'ℹ️'
};
toast.innerHTML = `
${icons[type] || icons.info}
${message}
`;
container.appendChild(toast);
// 自动移除
setTimeout(() => {
if (toast.parentElement) {
toast.style.animation = 'slideInRight 0.3s ease-out reverse';
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, 300);
}
}, duration);
}
// 确认对话框函数
let confirmCallback = null;
function showConfirmDialog(message, title = '确认操作', okText = '确定', okType = 'danger') {
return new Promise((resolve) => {
document.getElementById('confirm-title').textContent = title;
document.getElementById('confirm-message').textContent = message;
const okBtn = document.getElementById('confirm-ok-btn');
okBtn.textContent = okText;
okBtn.className = `btn btn-${okType}`;
confirmCallback = resolve;
document.getElementById('confirmDialog').classList.add('show');
});
}
function closeConfirmDialog(confirmed) {
document.getElementById('confirmDialog').classList.remove('show');
if (confirmCallback) {
confirmCallback(confirmed);
confirmCallback = null;
}
}
// 复制激活码到剪贴板
async function copyLicenseKey(key) {
try {
await navigator.clipboard.writeText(key);
showToast('激活码已复制到剪贴板', 'success', 2000);
} catch (err) {
// 降级方案:使用传统方法
const textArea = document.createElement('textarea');
textArea.value = key;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showToast('激活码已复制到剪贴板', 'success', 2000);
} catch (err) {
showToast('复制失败,请手动复制', 'error');
}
document.body.removeChild(textArea);
}
}
// 全选/取消全选
function toggleSelectAll() {
const selectAll = document.getElementById('select-all');
const selectAllHeader = document.getElementById('select-all-header');
const checkboxes = document.querySelectorAll('.license-checkbox');
// 检查当前是否所有复选框都已选中
const allChecked = checkboxes.length > 0 &&
Array.from(checkboxes).every(checkbox => checkbox.checked);
// 如果全部已选中,则取消全选;否则全选
const isChecked = !allChecked;
checkboxes.forEach(checkbox => {
checkbox.checked = isChecked;
});
// 同步两个全选复选框
selectAll.checked = isChecked;
selectAllHeader.checked = isChecked;
updateSelectedCount();
}
// 更新选中数量
function updateSelectedCount() {
const checkboxes = document.querySelectorAll('.license-checkbox:checked');
const count = checkboxes.length;
const selectedCountEl = document.getElementById('selected-count');
const batchDeleteBtn = document.getElementById('batch-delete-btn');
selectedCountEl.textContent = `已选择 ${count} 项`;
if (count > 0) {
batchDeleteBtn.style.display = 'block';
} else {
batchDeleteBtn.style.display = 'none';
}
// 更新批量操作按钮
updateBatchButtons();
// 更新全选复选框状态
const allCheckboxes = document.querySelectorAll('.license-checkbox');
const allChecked = allCheckboxes.length > 0 && checkboxes.length === allCheckboxes.length;
document.getElementById('select-all').checked = allChecked;
document.getElementById('select-all-header').checked = allChecked;
}
// 批量删除 License
async function batchDeleteLicenses() {
const checkboxes = document.querySelectorAll('.license-checkbox:checked');
const selectedIds = Array.from(checkboxes).map(cb => parseInt(cb.value));
if (selectedIds.length === 0) {
showToast('请至少选择一个 License', 'warning');
return;
}
const confirmed = await showConfirmDialog(
`确定要删除选中的 ${selectedIds.length} 个 License 吗?此操作不可恢复!`,
'确认批量删除',
'删除',
'danger'
);
if (!confirmed) {
return;
}
try {
const response = await apiRequest(`${API_BASE}/licenses/batch`, {
method: 'DELETE',
body: JSON.stringify({
ids: selectedIds
})
});
if (!response) return;
const result = await response.json();
if (result.code === 0) {
showToast(result.msg, 'success');
loadStatistics(); // 重新加载统计信息
loadLicenses(currentPage);
} else {
showToast('批量删除失败: ' + result.msg, 'error');
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
}
}
// 获取认证token
function getAuthToken() {
return localStorage.getItem('auth_token');
}
// 检查是否已登录
function checkAuth() {
const token = getAuthToken();
if (!token) {
window.location.href = '/web/login.html';
return false;
}
return true;
}
// 获取API请求头(包含token)
function getAuthHeaders() {
const token = getAuthToken();
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
};
}
// 处理API错误响应
async function handleApiError(response) {
if (response.status === 401) {
// 未授权,清除token并跳转到登录页
localStorage.removeItem('auth_token');
showToast('登录已过期,请重新登录', 'error');
setTimeout(() => {
window.location.href = '/web/login.html';
}, 1000);
return true;
}
return false;
}
// 统一的API请求函数
async function apiRequest(url, options = {}) {
const headers = getAuthHeaders();
if (options.headers) {
Object.assign(headers, options.headers);
}
const response = await fetch(url, {
...options,
headers: headers
});
if (await handleApiError(response)) {
return null;
}
return response;
}
// 页面加载时检查登录状态
window.onload = () => {
if (checkAuth()) {
loadStatistics();
loadLicenses();
}
};
// 加载统计信息
async function loadStatistics() {
try {
const response = await apiRequest(`${API_BASE}/licenses/statistics`);
if (!response) return;
const result = await response.json();
if (result.code === 0 && result.data) {
const stats = result.data;
document.getElementById('stat-total').textContent = stats.total || 0;
document.getElementById('stat-activated').textContent = stats.activated || 0;
document.getElementById('stat-unactivated').textContent = stats.unactivated || 0;
document.getElementById('stat-devices').textContent = stats.total_devices || 0;
document.getElementById('stats-container').style.display = 'grid';
}
} catch (error) {
console.error('加载统计信息失败:', error);
}
}
// 加载 License 列表
async function loadLicenses(page = 1) {
currentPage = page;
const loadingEl = document.getElementById('loading');
const tableContainer = document.getElementById('table-container');
const emptyState = document.getElementById('empty-state');
loadingEl.style.display = 'block';
tableContainer.style.display = 'none';
emptyState.style.display = 'none';
try {
// 构建查询URL
let url = `${API_BASE}/licenses?page=${page}&page_size=${pageSize}`;
if (currentStatusFilter) {
url += `&status=${currentStatusFilter}`;
}
const response = await apiRequest(url);
if (!response) return;
const result = await response.json();
if (result.code === 0) {
total = result.total;
const licenses = result.data;
if (licenses.length === 0) {
loadingEl.style.display = 'none';
emptyState.style.display = 'block';
return;
}
renderTable(licenses);
renderPagination();
loadingEl.style.display = 'none';
tableContainer.style.display = 'block';
// 更新批量操作按钮显示
updateBatchButtons();
} else {
showToast('加载失败: ' + result.msg, 'error');
loadingEl.style.display = 'none';
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
loadingEl.style.display = 'none';
}
}
// 状态筛选处理
function handleStatusFilter() {
const filterSelect = document.getElementById('status-filter');
currentStatusFilter = filterSelect.value;
loadLicenses(1); // 重置到第一页
}
// 更新批量操作按钮显示
function updateBatchButtons() {
const checkboxes = document.querySelectorAll('.license-checkbox:checked');
const batchUpdateBtn = document.getElementById('batch-update-btn');
if (checkboxes.length > 0) {
batchUpdateBtn.style.display = 'block';
} else {
batchUpdateBtn.style.display = 'none';
}
}
// 渲染表格
function renderTable(licenses) {
const tbody = document.getElementById('license-table-body');
tbody.innerHTML = licenses.map(license => {
let boundDevices = [];
try {
boundDevices = JSON.parse(license.bound_devices || '[]');
} catch (e) {
boundDevices = [];
}
// 解析设备激活时间
let deviceActivations = {};
try {
const activationsStr = JSON.parse(license.device_activations || '{}');
deviceActivations = activationsStr;
} catch (e) {
deviceActivations = {};
}
const boundCount = boundDevices.length;
const isFull = boundCount >= license.max_devices;
const createdDate = new Date(license.created_at).toLocaleString('zh-CN');
// 限制激活码显示长度为10个字符
const displayKey = license.key.length > 10 ? license.key.substring(0, 10) + '...' : license.key;
// 构建设备详情显示
let deviceDetailHtml = '';
if (boundDevices.length === 0) {
deviceDetailHtml = '无设备';
} else {
// 显示前2个设备作为预览
const previewDevices = boundDevices.slice(0, 2);
const previewText = previewDevices.map(deviceId => {
const activationTime = deviceActivations[deviceId];
if (activationTime) {
const date = new Date(activationTime);
return `${deviceId} (${date.toLocaleString('zh-CN')})`;
}
return `${deviceId} (未记录)`;
}).join('、');
const moreCount = boundDevices.length - 2;
// 使用 data 属性安全传递 licenseKey
const escapedKey = license.key.replace(/"/g, '"').replace(/'/g, ''');
deviceDetailHtml = `
${boundCount} 个设备
${previewText}${moreCount > 0 ? ` 等${moreCount}个...` : ''}
`;
}
return `
|
|
${license.id} |
${displayKey}
|
${deviceDetailHtml}
|
${boundCount} / ${license.max_devices}
|
${license.remark || '无'}
|
${createdDate} |
|
`;
}).join('');
}
// 渲染分页
function renderPagination() {
const pagination = document.getElementById('pagination');
const totalPages = Math.ceil(total / pageSize);
if (totalPages <= 1) {
pagination.innerHTML = '';
return;
}
pagination.innerHTML = `
第 ${currentPage} / ${totalPages} 页 (共 ${total} 条)
`;
}
// 打开创建 Modal
function openCreateModal() {
editingId = null;
document.getElementById('modal-title').textContent = '创建 License';
document.getElementById('license-id').value = '';
document.getElementById('license-key').value = '';
document.getElementById('license-max-devices').value = '2';
document.getElementById('license-bound-devices').value = '';
document.getElementById('license-remark').value = '';
document.getElementById('license-key').disabled = false;
document.getElementById('licenseModal').classList.add('show');
}
// 编辑 License
async function editLicense(id) {
try {
const response = await apiRequest(`${API_BASE}/licenses/${id}`);
if (!response) return;
const result = await response.json();
if (result.code === 0) {
const license = result.data;
editingId = id;
document.getElementById('modal-title').textContent = '编辑 License';
document.getElementById('license-id').value = id;
document.getElementById('license-key').value = license.key;
document.getElementById('license-max-devices').value = license.max_devices;
document.getElementById('license-bound-devices').value = license.bound_devices || '[]';
document.getElementById('license-remark').value = license.remark || '';
document.getElementById('license-key').disabled = false;
document.getElementById('licenseModal').classList.add('show');
} else {
showToast('加载失败: ' + result.msg, 'error');
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
}
}
// 关闭 Modal
function closeModal() {
document.getElementById('licenseModal').classList.remove('show');
}
// 提交表单
async function handleSubmit(event) {
event.preventDefault();
const id = document.getElementById('license-id').value;
const key = document.getElementById('license-key').value;
const maxDevices = parseInt(document.getElementById('license-max-devices').value);
const boundDevices = document.getElementById('license-bound-devices').value || '[]';
const remark = document.getElementById('license-remark').value || '';
// 验证 boundDevices 是否为有效 JSON
try {
JSON.parse(boundDevices);
} catch (e) {
showToast('已绑定设备必须是有效的 JSON 数组格式', 'error');
return;
}
try {
let response;
if (editingId) {
// 更新
const updateData = {
max_devices: maxDevices,
remark: remark // 总是发送remark字段,即使是空字符串
};
if (boundDevices) {
updateData.bound_devices = boundDevices;
}
response = await apiRequest(`${API_BASE}/licenses/${id}`, {
method: 'PUT',
body: JSON.stringify(updateData)
});
} else {
// 创建
response = await apiRequest(`${API_BASE}/licenses`, {
method: 'POST',
body: JSON.stringify({
key: key,
max_devices: maxDevices,
bound_devices: boundDevices,
remark: remark
})
});
}
if (!response) return;
const result = await response.json();
if (result.code === 0) {
showToast(editingId ? '更新成功' : '创建成功', 'success');
closeModal();
loadStatistics(); // 重新加载统计信息
loadLicenses(currentPage);
} else {
showToast('操作失败: ' + result.msg, 'error');
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
}
}
// 删除 License
async function deleteLicense(id, key) {
const confirmed = await showConfirmDialog(
`确定要删除 License "${key}" 吗?此操作不可恢复!`,
'确认删除',
'删除',
'danger'
);
if (!confirmed) {
return;
}
try {
const response = await apiRequest(`${API_BASE}/licenses/${id}`, {
method: 'DELETE'
});
if (!response) return;
const result = await response.json();
if (result.code === 0) {
showToast('删除成功', 'success');
loadStatistics(); // 重新加载统计信息
loadLicenses(currentPage);
} else {
showToast('删除失败: ' + result.msg, 'error');
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
}
}
// 打开批量生成 Modal
function openBatchModal() {
document.getElementById('batch-prefix').value = 'VIP';
document.getElementById('batch-count').value = '10';
document.getElementById('batch-max-devices').value = '2';
document.getElementById('batchModal').classList.add('show');
}
// 关闭批量生成 Modal
function closeBatchModal() {
document.getElementById('batchModal').classList.remove('show');
}
// 批量生成提交
async function handleBatchSubmit(event) {
event.preventDefault();
const prefix = document.getElementById('batch-prefix').value.trim();
const count = parseInt(document.getElementById('batch-count').value);
const maxDevices = parseInt(document.getElementById('batch-max-devices').value);
if (!prefix) {
showToast('请输入激活码前缀', 'warning');
return;
}
if (count <= 0 || count > 1000) {
showToast('生成数量必须在 1-1000 之间', 'warning');
return;
}
const confirmed = await showConfirmDialog(
`确定要批量生成 ${count} 个激活码吗?`,
'确认批量生成',
'生成',
'primary'
);
if (!confirmed) {
return;
}
try {
const response = await apiRequest(`${API_BASE}/licenses/batch`, {
method: 'POST',
body: JSON.stringify({
prefix: prefix,
count: count,
max_devices: maxDevices
})
});
if (!response) return;
const result = await response.json();
if (result.code === 0) {
showToast(result.msg, 'success', 4000);
closeBatchModal();
loadStatistics(); // 重新加载统计信息
loadLicenses(1); // 重新加载第一页
} else {
showToast('批量生成失败: ' + result.msg, 'error');
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
}
}
// 从元素获取数据并显示设备列表弹框
function showDeviceListFromElement(element) {
const licenseId = parseInt(element.getAttribute('data-license-id'));
const licenseKey = element.getAttribute('data-license-key');
showDeviceList(licenseId, licenseKey);
}
// 显示设备列表弹框
async function showDeviceList(licenseId, licenseKey) {
try {
const response = await apiRequest(`${API_BASE}/licenses/${licenseId}`);
if (!response) return;
const result = await response.json();
if (result.code === 0) {
const license = result.data;
let boundDevices = [];
try {
boundDevices = JSON.parse(license.bound_devices || '[]');
} catch (e) {
boundDevices = [];
}
// 解析设备激活时间
let deviceActivations = {};
try {
const activationsStr = JSON.parse(license.device_activations || '{}');
deviceActivations = activationsStr;
} catch (e) {
deviceActivations = {};
}
// 解析设备心跳时间
let deviceHeartbeats = {};
try {
const heartbeatsStr = JSON.parse(license.device_heartbeats || '{}');
deviceHeartbeats = heartbeatsStr;
} catch (e) {
deviceHeartbeats = {};
}
// 设置标题
document.getElementById('device-list-title').textContent = `设备列表 - ${licenseKey}`;
// 渲染设备列表
const contentEl = document.getElementById('device-list-content');
if (boundDevices.length === 0) {
contentEl.innerHTML = `
`;
} else {
const tableHtml = `
| 序号 |
设备ID |
激活时间 |
最近心跳时间 |
${boundDevices.map((deviceId, index) => {
const activationTime = deviceActivations[deviceId];
let timeDisplay = '未记录';
if (activationTime) {
const date = new Date(activationTime);
timeDisplay = date.toLocaleString('zh-CN');
}
// 处理心跳时间显示
const heartbeatTime = deviceHeartbeats[deviceId];
let heartbeatDisplay = '未记录';
if (heartbeatTime) {
const heartbeatDate = new Date(heartbeatTime);
const now = new Date();
const diff = now - heartbeatDate;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
// 格式化相对时间
let relativeTime = '';
let heartbeatColor = '#6b7280'; // 默认灰色
if (days > 0) {
relativeTime = `${days}天前`;
heartbeatColor = '#ef4444'; // 红色 - 很久没心跳
} else if (hours > 0) {
relativeTime = `${hours}小时前`;
heartbeatColor = '#f59e0b'; // 橙色 - 较久
} else if (minutes > 0) {
relativeTime = `${minutes}分钟前`;
heartbeatColor = minutes < 10 ? '#10b981' : '#f59e0b'; // 绿色(10分钟内)或橙色
} else if (seconds > 0) {
relativeTime = `${seconds}秒前`;
heartbeatColor = '#10b981'; // 绿色
} else {
relativeTime = '刚刚';
heartbeatColor = '#10b981'; // 绿色
}
heartbeatDisplay = `${heartbeatDate.toLocaleString('zh-CN')} (${relativeTime})`;
}
return `
| ${index + 1} |
${deviceId} |
${timeDisplay} |
${heartbeatDisplay} |
`;
}).join('')}
共 ${boundDevices.length} 个设备
`;
contentEl.innerHTML = tableHtml;
}
document.getElementById('deviceListModal').classList.add('show');
} else {
showToast('加载失败: ' + result.msg, 'error');
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
}
}
// 关闭设备列表弹框
function closeDeviceListModal() {
document.getElementById('deviceListModal').classList.remove('show');
}
// 导出CSV
async function exportToCSV() {
try {
// 如果有筛选条件,先获取筛选后的数据,然后前端生成CSV
if (currentStatusFilter) {
showToast('正在导出筛选后的数据...', 'info');
const listResponse = await apiRequest(`${API_BASE}/licenses?page=1&page_size=10000&status=${currentStatusFilter}`);
if (!listResponse) return;
const listResult = await listResponse.json();
if (listResult.code === 0 && listResult.data.length > 0) {
const licenses = listResult.data;
const headers = ['ID', '激活码', '最大设备数', '已绑定设备数', '绑定设备列表', '创建时间', '更新时间'];
const rows = licenses.map(license => {
let boundDevices = [];
try {
boundDevices = JSON.parse(license.bound_devices || '[]');
} catch (e) {
boundDevices = [];
}
return [
license.id,
license.key,
license.max_devices,
boundDevices.length,
boundDevices.join('; '),
new Date(license.created_at).toLocaleString('zh-CN'),
new Date(license.updated_at).toLocaleString('zh-CN')
];
});
function escapeCSVField(field) {
if (field === null || field === undefined) return '';
const str = String(field);
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
return '"' + str.replace(/"/g, '""') + '"';
}
return str;
}
const csvContent = [
headers.map(escapeCSVField).join(','),
...rows.map(row => row.map(escapeCSVField).join(','))
].join('\n');
const BOM = '\uFEFF';
const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
const now = new Date();
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '');
const statusStr = currentStatusFilter ? `_${currentStatusFilter}` : '';
link.setAttribute('download', `licenses${statusStr}_${dateStr}_${timeStr}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showToast(`成功导出 ${licenses.length} 条记录`, 'success');
} else {
showToast('没有数据可导出', 'warning');
}
} else {
// 没有筛选条件时,使用后端导出接口
const token = getAuthToken();
const url = `${API_BASE}/licenses/export?format=csv`;
// 创建一个隐藏的表单来提交带token的请求
const form = document.createElement('form');
form.method = 'GET';
form.action = url;
form.style.display = 'none';
// 添加token到请求头(通过fetch下载)
const response = await fetch(url, {
headers: getAuthHeaders()
});
if (!response.ok) {
showToast('导出失败', 'error');
return;
}
const blob = await response.blob();
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
const now = new Date();
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '');
link.setAttribute('download', `licenses_${dateStr}_${timeStr}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(downloadUrl);
showToast('导出成功', 'success');
}
} catch (error) {
showToast('导出失败: ' + error.message, 'error');
}
}
// 打开批量修改最大设备数弹框
function openBatchUpdateModal() {
const checkboxes = document.querySelectorAll('.license-checkbox:checked');
const count = checkboxes.length;
if (count === 0) {
showToast('请至少选择一个 License', 'warning');
return;
}
document.getElementById('batch-update-count').textContent = count;
document.getElementById('batch-update-max-devices').value = '2';
document.getElementById('batchUpdateModal').classList.add('show');
}
// 关闭批量修改弹框
function closeBatchUpdateModal() {
document.getElementById('batchUpdateModal').classList.remove('show');
}
// 批量修改最大设备数提交
async function handleBatchUpdateSubmit(event) {
event.preventDefault();
const checkboxes = document.querySelectorAll('.license-checkbox:checked');
const selectedIds = Array.from(checkboxes).map(cb => parseInt(cb.value));
const maxDevices = parseInt(document.getElementById('batch-update-max-devices').value);
if (selectedIds.length === 0) {
showToast('请至少选择一个 License', 'warning');
return;
}
if (maxDevices < 1) {
showToast('最大设备数必须大于0', 'warning');
return;
}
const confirmed = await showConfirmDialog(
`确定要将选中的 ${selectedIds.length} 个 License 的最大设备数修改为 ${maxDevices} 吗?`,
'确认批量修改',
'确认',
'primary'
);
if (!confirmed) {
return;
}
try {
// 使用批量更新接口
const response = await apiRequest(`${API_BASE}/licenses/batch/max-devices`, {
method: 'PUT',
body: JSON.stringify({
ids: selectedIds,
max_devices: maxDevices
})
});
if (!response) return;
const result = await response.json();
if (result.code === 0) {
showToast(result.msg, 'success');
closeBatchUpdateModal();
// 清除所有选中状态
checkboxes.forEach(cb => cb.checked = false);
updateSelectedCount();
loadStatistics(); // 重新加载统计信息
loadLicenses(currentPage); // 重新加载列表
} else {
showToast('批量修改失败: ' + result.msg, 'error');
}
} catch (error) {
showToast('请求失败: ' + error.message, 'error');
}
}
// 点击 Modal 外部关闭
window.onclick = function(event) {
const licenseModal = document.getElementById('licenseModal');
const batchModal = document.getElementById('batchModal');
const batchUpdateModal = document.getElementById('batchUpdateModal');
const deviceListModal = document.getElementById('deviceListModal');
const confirmDialog = document.getElementById('confirmDialog');
if (event.target === licenseModal) {
closeModal();
}
if (event.target === batchModal) {
closeBatchModal();
}
if (event.target === batchUpdateModal) {
closeBatchUpdateModal();
}
if (event.target === deviceListModal) {
closeDeviceListModal();
}
if (event.target === confirmDialog) {
closeConfirmDialog(false);
}
}