2025-04-02 14:29:44 +08:00

276 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="mb-6 flex justify-between items-center">
<div class="flex items-center">
<a href="/admin/robots" class="text-gray-500 hover:text-gray-700 mr-4 transition-colors">
<i class="fas fa-arrow-left"></i>
</a>
<h1 class="text-2xl font-semibold text-gray-800">{{.Robot.Nickname}}</h1>
<span class="ml-3 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{if eq .Robot.Status "online"}}
bg-emerald-100 text-emerald-800
{{else if eq .Robot.Status "error"}}
bg-red-100 text-red-800
{{else}}
bg-gray-100 text-gray-800
{{end}}">
{{if eq .Robot.Status "online"}}在线{{else if eq .Robot.Status "error"}}错误{{else}}离线{{end}}
</span>
</div>
<div>
<a href="/admin/robots/{{.Robot.ID}}/login" class="inline-flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors mr-2">
<i class="fas fa-qrcode mr-2"></i> 登录
</a>
<button data-confirm="确定要删除此机器人吗?" data-confirm-type="danger" onclick="deleteRobot({{.Robot.ID}})" class="inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 transition-colors">
<i class="fas fa-trash mr-2"></i> 删除
</button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 左侧:机器人信息 -->
<div class="lg:col-span-1">
<div class="clean-card overflow-hidden">
<div class="pattern-dots h-24 relative flex items-center justify-center bg-gray-50 border-b border-gray-100">
<div class="absolute w-20 h-20 rounded-full bg-white shadow-sm border border-gray-100 flex items-center justify-center">
<i class="fas fa-robot text-3xl text-gray-500"></i>
</div>
</div>
<div class="pt-12 pb-6 px-6 text-center">
<h3 class="text-xl font-semibold text-gray-800">{{.Robot.Nickname}}</h3>
{{if .Robot.WechatID}}
<p class="text-gray-500">{{.Robot.WechatID}}</p>
{{else}}
<p class="text-gray-400 italic text-sm">未登录</p>
{{end}}
</div>
<!-- 机器人详情信息 -->
<div class="border-t border-gray-100">
<div class="p-6 space-y-4">
<div class="flex justify-between text-sm">
<span class="text-gray-600">创建时间</span>
<span class="text-gray-800">{{.Robot.CreatedAt.Format "2006-01-02 15:04:05"}}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">状态</span>
<span class="{{if eq .Robot.Status "online"}}text-emerald-600{{else if eq .Robot.Status "error"}}text-red-600{{else}}text-gray-600{{end}}">
{{if eq .Robot.Status "online"}}在线{{else if eq .Robot.Status "error"}}错误{{else}}离线{{end}}
</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">容器ID</span>
<span class="text-gray-800 font-mono text-xs" title="{{.Robot.ContainerID}}">
{{if .Robot.ContainerID}}{{slice .Robot.ContainerID 0 12}}{{else}}未知{{end}}
</span>
</div>
{{if .Robot.LastLoginAt}}
<div class="flex justify-between text-sm">
<span class="text-gray-600">最后登录</span>
<span class="text-gray-800">{{.Robot.LastLoginAt.Format "2006-01-02 15:04:05"}}</span>
</div>
{{end}}
</div>
</div>
<!-- 操作按钮 -->
<div class="bg-gray-50 border-t border-gray-100 p-6 flex space-x-3">
{{if eq .Robot.Status "online"}}
<form action="/admin/robots/{{.Robot.ID}}/logout" method="POST" class="w-full">
<button type="submit" class="w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-800 hover:bg-gray-700 focus:outline-none transition-colors">
<i class="fas fa-sign-out-alt mr-2"></i> 退出登录
</button>
</form>
{{else}}
<a href="/admin/robots/{{.Robot.ID}}/login" class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none transition-colors">
<i class="fas fa-qrcode mr-2"></i> 扫码登录
</a>
{{end}}
</div>
</div>
</div>
<!-- 右侧:容器日志和状态信息 -->
<div class="lg:col-span-2 space-y-6">
<!-- 容器状态 -->
<div class="clean-card overflow-hidden">
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-100">
<h3 class="font-medium text-gray-800">容器状态</h3>
<button id="refresh-btn" class="inline-flex items-center justify-center p-1.5 rounded-full text-gray-600 hover:bg-gray-100 transition-colors">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<div class="p-6 grid grid-cols-1 sm:grid-cols-2 gap-6">
<div class="space-y-2">
<p class="text-sm text-gray-500">CPU 使用率</p>
<div class="flex items-center">
<div class="w-full bg-gray-100 rounded-full h-2 mr-3">
<div id="cpu-bar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div>
</div>
<span id="cpu-usage" class="text-sm font-medium text-gray-700 min-w-[40px]">0%</span>
</div>
</div>
<div class="space-y-2">
<p class="text-sm text-gray-500">内存使用率</p>
<div class="flex items-center">
<div class="w-full bg-gray-100 rounded-full h-2 mr-3">
<div id="memory-bar" class="bg-green-500 h-2 rounded-full" style="width: 0%"></div>
</div>
<span id="memory-usage" class="text-sm font-medium text-gray-700 min-w-[40px]">0%</span>
</div>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">网络接收</p>
<p id="network-rx" class="text-lg font-medium text-gray-800">0 KB/s</p>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">网络发送</p>
<p id="network-tx" class="text-lg font-medium text-gray-800">0 KB/s</p>
</div>
</div>
</div>
<!-- 容器日志 -->
<div class="clean-card overflow-hidden">
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-100">
<div class="flex items-center">
<h3 class="font-medium text-gray-800">容器日志</h3>
<div id="logs-loading" class="ml-3 hidden">
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-500"></div>
</div>
</div>
<div class="flex items-center space-x-3">
<select id="log-lines" class="text-sm border border-gray-200 rounded-md py-1 pl-2 pr-8 bg-white focus:ring-1 focus:ring-gray-200 focus:border-gray-300">
<option value="50">50行</option>
<option value="100" selected>100行</option>
<option value="200">200行</option>
<option value="500">500行</option>
</select>
<button id="reload-logs" class="inline-flex items-center justify-center p-1.5 rounded-full text-gray-600 hover:bg-gray-100 transition-colors">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
<div class="p-6">
<pre id="container-logs" class="text-xs font-mono bg-gray-50 border border-gray-100 rounded-md p-4 overflow-auto h-80 text-gray-700">正在加载日志...</pre>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const containerLogs = document.getElementById('container-logs');
const logsLoading = document.getElementById('logs-loading');
const logLines = document.getElementById('log-lines');
const reloadLogs = document.getElementById('reload-logs');
const refreshBtn = document.getElementById('refresh-btn');
// 容器ID可能长度不一致确保使用完整ID
const containerId = "{{.Robot.ContainerID}}";
// 加载日志
function loadLogs() {
logsLoading.style.display = 'flex';
fetch(`/api/v1/containers/${containerId}/logs?lines=${logLines.value}`)
.then(response => response.json())
.then(data => {
if (data.success && data.logs) {
containerLogs.textContent = data.logs;
containerLogs.scrollTop = containerLogs.scrollHeight;
} else {
containerLogs.textContent = '无法获取日志: ' + (data.message || '未知错误');
}
logsLoading.style.display = 'none';
})
.catch(error => {
console.error('Error fetching logs:', error);
containerLogs.textContent = '加载日志时发生错误: ' + error.message;
logsLoading.style.display = 'none';
});
}
// 加载状态信息
function loadStats() {
fetch(`/api/v1/containers/${containerId}/stats`)
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新CPU使用率
const cpuPercent = (data.stats.cpu_percent || 0).toFixed(2);
document.getElementById('cpu-usage').textContent = `${cpuPercent}%`;
document.getElementById('cpu-bar').style.width = `${Math.min(cpuPercent, 100)}%`;
// 更新内存使用率
const memoryUsage = data.stats.memory_usage || 0;
const memoryLimit = data.stats.memory_limit || 1;
const memoryPercent = ((memoryUsage / memoryLimit) * 100).toFixed(2);
document.getElementById('memory-usage').textContent = `${memoryPercent}%`;
document.getElementById('memory-bar').style.width = `${Math.min(memoryPercent, 100)}%`;
// 更新网络信息
const networkRx = formatBytes(data.stats.network_rx || 0) + '/s';
const networkTx = formatBytes(data.stats.network_tx || 0) + '/s';
document.getElementById('network-rx').textContent = networkRx;
document.getElementById('network-tx').textContent = networkTx;
}
})
.catch(error => {
console.error('加载状态信息失败:', error);
});
}
// 格式化字节数
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 B';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// 初始加载日志和状态
loadLogs();
loadStats();
// 设置事件监听器
reloadLogs.addEventListener('click', loadLogs);
logLines.addEventListener('change', loadLogs);
refreshBtn.addEventListener('click', function() {
this.classList.add('animate-spin');
loadStats();
setTimeout(() => {
this.classList.remove('animate-spin');
}, 1000);
});
// 定期更新状态
setInterval(loadStats, 5000); // 每5秒更新一次
});
// 删除机器人
function deleteRobot(id) {
fetch(`/admin/robots/${id}`, {
method: 'DELETE',
})
.then(response => {
if (response.redirected) {
window.location.href = response.url;
} else {
window.location.href = '/admin/robots';
}
})
.catch(error => {
console.error('Error:', error);
alert('删除机器人失败');
});
}
</script>