892 lines
42 KiB
PHP
892 lines
42 KiB
PHP
![]() |
<!DOCTYPE html>
|
||
|
<html lang="zh-CN">
|
||
|
<head>
|
||
|
<meta charset="UTF-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
<title>MCSManager服务器管理</title>
|
||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||
|
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||
|
<script>
|
||
|
tailwind.config = {
|
||
|
theme: {
|
||
|
extend: {
|
||
|
colors: {
|
||
|
primary: '#3b82f6',
|
||
|
secondary: '#10b981',
|
||
|
danger: '#ef4444',
|
||
|
warning: '#f59e0b',
|
||
|
info: '#06b6d4',
|
||
|
dark: '#1e293b',
|
||
|
light: '#f8fafc'
|
||
|
},
|
||
|
fontFamily: {
|
||
|
inter: ['Inter', 'system-ui', 'sans-serif'],
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
<style type="text/tailwindcss">
|
||
|
@layer utilities {
|
||
|
.content-auto {
|
||
|
content-visibility: auto;
|
||
|
}
|
||
|
.server-card {
|
||
|
@apply bg-white rounded-lg shadow-md p-4 mb-4 transition-all duration-300 hover:shadow-lg;
|
||
|
}
|
||
|
.server-status {
|
||
|
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
|
||
|
}
|
||
|
.btn {
|
||
|
@apply inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
|
||
|
}
|
||
|
.btn-primary {
|
||
|
@apply bg-primary text-white hover:bg-primary/90 focus:ring-primary/50;
|
||
|
}
|
||
|
.btn-secondary {
|
||
|
@apply bg-secondary text-white hover:bg-secondary/90 focus:ring-secondary/50;
|
||
|
}
|
||
|
.btn-danger {
|
||
|
@apply bg-danger text-white hover:bg-danger/90 focus:ring-danger/50;
|
||
|
}
|
||
|
.btn-warning {
|
||
|
@apply bg-warning text-white hover:bg-warning/90 focus:ring-warning/50;
|
||
|
}
|
||
|
.btn-outline {
|
||
|
@apply border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-primary/50;
|
||
|
}
|
||
|
.badge {
|
||
|
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
|
||
|
}
|
||
|
.badge-online {
|
||
|
@apply bg-green-100 text-green-800;
|
||
|
}
|
||
|
.badge-offline {
|
||
|
@apply bg-gray-100 text-gray-800;
|
||
|
}
|
||
|
.badge-starting {
|
||
|
@apply bg-blue-100 text-blue-800;
|
||
|
}
|
||
|
.badge-stopping {
|
||
|
@apply bg-yellow-100 text-yellow-800;
|
||
|
}
|
||
|
.console-line {
|
||
|
@apply whitespace-pre font-mono text-sm;
|
||
|
}
|
||
|
.console-line-info {
|
||
|
@apply text-gray-300;
|
||
|
}
|
||
|
.console-line-warning {
|
||
|
@apply text-yellow-300;
|
||
|
}
|
||
|
.console-line-error {
|
||
|
@apply text-red-400;
|
||
|
}
|
||
|
.console-line-command {
|
||
|
@apply text-green-400;
|
||
|
}
|
||
|
.console-line-player {
|
||
|
@apply text-blue-300;
|
||
|
}
|
||
|
.loading-overlay {
|
||
|
@apply fixed inset-0 bg-black/50 flex items-center justify-center z-50;
|
||
|
}
|
||
|
.notification {
|
||
|
@apply fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transition-all duration-300 transform translate-y-20 opacity-0;
|
||
|
}
|
||
|
.notification-visible {
|
||
|
@apply translate-y-0 opacity-100;
|
||
|
}
|
||
|
.notification-success {
|
||
|
@apply bg-green-500 text-white;
|
||
|
}
|
||
|
.notification-error {
|
||
|
@apply bg-red-500 text-white;
|
||
|
}
|
||
|
.notification-info {
|
||
|
@apply bg-blue-500 text-white;
|
||
|
}
|
||
|
}
|
||
|
.hidden {
|
||
|
display: none;
|
||
|
}
|
||
|
|
||
|
</style>
|
||
|
</head>
|
||
|
<body class="bg-gray-50 font-inter text-gray-800 min-h-screen flex flex-col">
|
||
|
<!-- 导航栏 -->
|
||
|
<header class="bg-dark text-white shadow-lg">
|
||
|
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
||
|
<div class="flex items-center space-x-2">
|
||
|
<i class="fa fa-server text-primary text-2xl"></i>
|
||
|
<h1 class="text-xl font-bold">MCSManager 服务器管理</h1>
|
||
|
</div>
|
||
|
<nav>
|
||
|
<ul class="flex space-x-6">
|
||
|
<li><a href="#" class="flex items-center hover:text-primary transition-colors duration-200"><i class="fa fa-home mr-1"></i> 首页</a></li>
|
||
|
<li><a href="#" class="flex items-center hover:text-primary transition-colors duration-200"><i class="fa fa-server mr-1"></i> 服务器</a></li>
|
||
|
<li><a href="#" class="flex items-center hover:text-primary transition-colors duration-200"><i class="fa fa-cog mr-1"></i> 设置</a></li>
|
||
|
<li><a href="#" class="flex items-center hover:text-primary transition-colors duration-200"><i class="fa fa-question-circle mr-1"></i> 帮助</a></li>
|
||
|
</ul>
|
||
|
</nav>
|
||
|
</div>
|
||
|
</header>
|
||
|
|
||
|
<!-- 主内容区 -->
|
||
|
<main class="flex-grow container mx-auto px-4 py-6">
|
||
|
<!-- 面包屑导航 -->
|
||
|
<div class="mb-4 text-sm text-gray-500">
|
||
|
<a href="#" class="hover:text-primary">首页</a> > <a href="#" class="hover:text-primary">服务器列表</a> > <span class="text-gray-700 font-medium" id="breadcrumb-server-name">生存服务器</span>
|
||
|
</div>
|
||
|
|
||
|
<!-- 服务器详情 -->
|
||
|
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||
|
<!-- 服务器状态和基本信息 -->
|
||
|
<div class="p-6 border-b">
|
||
|
<div class="flex flex-col md:flex-row md:items-center justify-between">
|
||
|
<div>
|
||
|
<h2 class="text-2xl font-bold" id="detail-server-name">生存服务器</h2>
|
||
|
<div class="flex items-center mt-2">
|
||
|
<span class="badge badge-online mr-3" id="detail-server-status">在线</span>
|
||
|
<span class="text-sm text-gray-500" id="detail-server-ip">127.0.0.1:25565</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="mt-4 md:mt-0 flex space-x-3">
|
||
|
<button class="btn btn-danger" id="action-stop-server">
|
||
|
<i class="fa fa-stop mr-1"></i> 停止
|
||
|
</button>
|
||
|
<button class="btn btn-warning" id="action-restart-server">
|
||
|
<i class="fa fa-refresh mr-1"></i> 重启
|
||
|
</button>
|
||
|
<button class="btn btn-secondary" id="action-start-server">
|
||
|
<i class="fa fa-play mr-1"></i> 启动
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 服务器信息和性能 -->
|
||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 p-6 border-b">
|
||
|
<div>
|
||
|
<h3 class="text-lg font-semibold mb-3">基本信息</h3>
|
||
|
<div class="space-y-2">
|
||
|
<div class="flex justify-between">
|
||
|
<span class="text-gray-600">UUID:</span>
|
||
|
<span class="font-medium" id="detail-uuid">5f8b4c2a-3d7e-4b1a-9c5d-6e7f8a9b0c1d</span>
|
||
|
</div>
|
||
|
<div class="flex justify-between">
|
||
|
<span class="text-gray-600">守护进程ID:</span>
|
||
|
<span class="font-medium" id="detail-daemonid">daemon-1</span>
|
||
|
</div>
|
||
|
<div class="flex justify-between">
|
||
|
<span class="text-gray-600">版本:</span>
|
||
|
<span class="font-medium" id="detail-version">1.19.2</span>
|
||
|
</div>
|
||
|
<div class="flex justify-between">
|
||
|
<span class="text-gray-600">运行时间:</span>
|
||
|
<span class="font-medium" id="detail-uptime">8小时24分钟</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div>
|
||
|
<h3 class="text-lg font-semibold mb-3">玩家信息</h3>
|
||
|
<div class="space-y-2">
|
||
|
<div class="flex justify-between">
|
||
|
<span class="text-gray-600">在线玩家:</span>
|
||
|
<span class="font-medium" id="detail-players">12/20</span>
|
||
|
</div>
|
||
|
<div class="space-y-1 mt-2" id="detail-player-list">
|
||
|
<div class="flex items-center justify-between">
|
||
|
<span class="text-sm">Player1</span>
|
||
|
<span class="text-xs text-gray-500">2小时前加入</span>
|
||
|
</div>
|
||
|
<div class="flex items-center justify-between">
|
||
|
<span class="text-sm">Player2</span>
|
||
|
<span class="text-xs text-gray-500">1小时前加入</span>
|
||
|
</div>
|
||
|
<div class="flex items-center justify-between">
|
||
|
<span class="text-sm">Player3</span>
|
||
|
<span class="text-xs text-gray-500">30分钟前加入</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
<button class="text-primary text-sm mt-2 hover:underline" id="action-view-all-players">
|
||
|
查看全部 <i class="fa fa-angle-right ml-1"></i>
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div>
|
||
|
<h3 class="text-lg font-semibold mb-3">性能指标</h3>
|
||
|
<div class="space-y-4">
|
||
|
<div>
|
||
|
<div class="flex justify-between mb-1">
|
||
|
<span class="text-sm text-gray-600">内存使用</span>
|
||
|
<span class="text-sm font-medium" id="detail-memory">1536 MB / 2048 MB</span>
|
||
|
</div>
|
||
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||
|
<div class="bg-blue-600 h-2 rounded-full" style="width: 75%" id="detail-memory-bar"></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div>
|
||
|
<div class="flex justify-between mb-1">
|
||
|
<span class="text-sm text-gray-600">CPU使用率</span>
|
||
|
<span class="text-sm font-medium" id="detail-cpu">24%</span>
|
||
|
</div>
|
||
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||
|
<div class="bg-green-600 h-2 rounded-full" style="width: 24%" id="detail-cpu-bar"></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div>
|
||
|
<div class="flex justify-between mb-1">
|
||
|
<span class="text-sm text-gray-600">网络流量</span>
|
||
|
<span class="text-sm font-medium" id="detail-network">1.2 MB/s 入 / 0.8 MB/s 出</span>
|
||
|
</div>
|
||
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||
|
<div class="bg-purple-600 h-2 rounded-full" style="width: 40%" id="detail-network-bar"></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 服务器控制台 -->
|
||
|
<div class="p-6">
|
||
|
<div class="flex justify-between items-center mb-4">
|
||
|
<h3 class="text-lg font-semibold">服务器控制台</h3>
|
||
|
<div class="flex space-x-2">
|
||
|
<button class="btn btn-outline text-sm" id="action-clear-console">
|
||
|
<i class="fa fa-trash mr-1"></i> 清空
|
||
|
</button>
|
||
|
<button class="btn btn-outline text-sm" id="action-refresh-console">
|
||
|
<i class="fa fa-refresh mr-1"></i> 刷新
|
||
|
</button>
|
||
|
<button class="btn btn-outline text-sm" id="action-expand-console">
|
||
|
<i class="fa fa-expand mr-1"></i> 全屏
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 控制台输出 -->
|
||
|
<div class="bg-gray-900 text-gray-100 rounded-t-md p-3 h-64 overflow-y-auto" id="server-console">
|
||
|
<pre class="font-mono text-sm">[CCSNetwork]: 正在获取终端
|
||
|
</div>
|
||
|
|
||
|
<!-- 命令输入 -->
|
||
|
<div class="flex bg-gray-800 rounded-b-md">
|
||
|
<div class="flex items-center px-3 text-gray-400">
|
||
|
<span class="font-mono">></span>
|
||
|
</div>
|
||
|
<input type="text" id="server-command-input" placeholder="输入命令..." class="flex-grow bg-gray-800 text-gray-100 px-2 py-3 focus:outline-none font-mono">
|
||
|
<button class="px-4 py-3 bg-primary text-white hover:bg-primary/90 transition-colors" id="action-send-command">
|
||
|
<i class="fa fa-paper-plane"></i>
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 配置文件管理 -->
|
||
|
<div class="p-6 border-t">
|
||
|
<h3 class="text-lg font-semibold mb-4">配置文件管理</h3>
|
||
|
<div class="overflow-x-auto">
|
||
|
<table class="min-w-full divide-y divide-gray-200">
|
||
|
<thead class="bg-gray-50">
|
||
|
<tr>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">文件名</th>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">大小</th>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">修改时间</th>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||
|
<tr>
|
||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||
|
<div class="flex items-center">
|
||
|
<i class="fa fa-file-text-o text-blue-500 mr-2"></i>
|
||
|
<span>server.properties</span>
|
||
|
</div>
|
||
|
</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2.4 KB</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2025-06-07 12:30:45</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||
|
<button class="text-primary hover:text-primary/80 mr-3" onclick="editConfig('server.properties')">
|
||
|
<i class="fa fa-edit mr-1"></i> 编辑
|
||
|
</button>
|
||
|
<button class="text-gray-500 hover:text-gray-700">
|
||
|
<i class="fa fa-download mr-1"></i> 下载
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||
|
<div class="flex items-center">
|
||
|
<i class="fa fa-file-text-o text-blue-500 mr-2"></i>
|
||
|
<span>ops.json</span>
|
||
|
</div>
|
||
|
</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">0.5 KB</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2025-06-07 10:15:20</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||
|
<button class="text-primary hover:text-primary/80 mr-3" onclick="editConfig('ops.json')">
|
||
|
<i class="fa fa-edit mr-1"></i> 编辑
|
||
|
</button>
|
||
|
<button class="text-gray-500 hover:text-gray-700">
|
||
|
<i class="fa fa-download mr-1"></i> 下载
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||
|
<div class="flex items-center">
|
||
|
<i class="fa fa-file-text-o text-blue-500 mr-2"></i>
|
||
|
<span>whitelist.json</span>
|
||
|
</div>
|
||
|
</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">1.2 KB</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2025-06-07 09:45:10</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||
|
<button class="text-primary hover:text-primary/80 mr-3" onclick="editConfig('whitelist.json')">
|
||
|
<i class="fa fa-edit mr-1"></i> 编辑
|
||
|
</button>
|
||
|
<button class="text-gray-500 hover:text-gray-700">
|
||
|
<i class="fa fa-download mr-1"></i> 下载
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
<button class="mt-4 btn btn-outline" onclick="uploadConfig()">
|
||
|
<i class="fa fa-upload mr-1"></i> 上传配置文件
|
||
|
</button>
|
||
|
</div>
|
||
|
|
||
|
<!-- 备份管理 -->
|
||
|
<div class="p-6 border-t">
|
||
|
<div class="flex justify-between items-center mb-4">
|
||
|
<h3 class="text-lg font-semibold">备份管理</h3>
|
||
|
<button class="btn btn-primary" id="action-create-backup">
|
||
|
<i class="fa fa-plus mr-1"></i> 创建备份
|
||
|
</button>
|
||
|
</div>
|
||
|
<div class="overflow-x-auto">
|
||
|
<table class="min-w-full divide-y divide-gray-200">
|
||
|
<thead class="bg-gray-50">
|
||
|
<tr>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">备份名称</th>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">大小</th>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
|
||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||
|
<tr>
|
||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||
|
<div class="flex items-center">
|
||
|
<i class="fa fa-file-archive-o text-purple-500 mr-2"></i>
|
||
|
<span>world_backup_20250607_1200</span>
|
||
|
</div>
|
||
|
</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">128 MB</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2025-06-07 12:00:00</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||
|
<button class="text-secondary hover:text-secondary/80 mr-3" onclick="restoreBackup('world_backup_20250607_1200')">
|
||
|
<i class="fa fa-undo mr-1"></i> 恢复
|
||
|
</button>
|
||
|
<button class="text-gray-500 hover:text-gray-700 mr-3">
|
||
|
<i class="fa fa-download mr-1"></i> 下载
|
||
|
</button>
|
||
|
<button class="text-danger hover:text-danger/80" onclick="deleteBackup('world_backup_20250607_1200')">
|
||
|
<i class="fa fa-trash mr-1"></i> 删除
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||
|
<div class="flex items-center">
|
||
|
<i class="fa fa-file-archive-o text-purple-500 mr-2"></i>
|
||
|
<span>world_backup_20250606_2359</span>
|
||
|
</div>
|
||
|
</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">126 MB</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2025-06-06 23:59:59</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||
|
<button class="text-secondary hover:text-secondary/80 mr-3" onclick="restoreBackup('world_backup_20250606_2359')">
|
||
|
<i class="fa fa-undo mr-1"></i> 恢复
|
||
|
</button>
|
||
|
<button class="text-gray-500 hover:text-gray-700 mr-3">
|
||
|
<i class="fa fa-download mr-1"></i> 下载
|
||
|
</button>
|
||
|
<button class="text-danger hover:text-danger/80" onclick="deleteBackup('world_backup_20250606_2359')">
|
||
|
<i class="fa fa-trash mr-1"></i> 删除
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||
|
<div class="flex items-center">
|
||
|
<i class="fa fa-file-archive-o text-purple-500 mr-2"></i>
|
||
|
<span>world_backup_20250606_1200</span>
|
||
|
</div>
|
||
|
</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">125 MB</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2025-06-06 12:00:00</td>
|
||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||
|
<button class="text-secondary hover:text-secondary/80 mr-3" onclick="restoreBackup('world_backup_20250606_1200')">
|
||
|
<i class="fa fa-undo mr-1"></i> 恢复
|
||
|
</button>
|
||
|
<button class="text-gray-500 hover:text-gray-700 mr-3">
|
||
|
<i class="fa fa-download mr-1"></i> 下载
|
||
|
</button>
|
||
|
<button class="text-danger hover:text-danger/80" onclick="deleteBackup('world_backup_20250606_1200')">
|
||
|
<i class="fa fa-trash mr-1"></i> 删除
|
||
|
</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
<div class="mt-4 text-sm text-gray-500">
|
||
|
<i class="fa fa-info-circle mr-1"></i> 自动备份计划: 每天凌晨2点执行全量备份
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</main>
|
||
|
|
||
|
<!-- 页脚 -->
|
||
|
<footer class="bg-dark text-white py-4">
|
||
|
<div class="container mx-auto px-4 text-center">
|
||
|
<p>© 2025 MCSManager 服务器管理系统 | 版本 1.0.0</p>
|
||
|
</div>
|
||
|
</footer>
|
||
|
|
||
|
<!-- 加载遮罩层 -->
|
||
|
<div class="loading-overlay hidden" id="loading-overlay">
|
||
|
<div class="bg-white p-6 rounded-lg shadow-xl flex flex-col items-center">
|
||
|
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary mb-4"></div>
|
||
|
<p class="text-lg font-medium" id="loading-message">正在处理...</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 通知组件 -->
|
||
|
<div class="notification notification-info" id="notification">
|
||
|
<p id="notification-message">这是一条通知消息</p>
|
||
|
</div>
|
||
|
|
||
|
<!-- 配置文件编辑模态框 -->
|
||
|
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 hidden" id="config-modal">
|
||
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[80vh] flex flex-col">
|
||
|
<div class="p-4 border-b flex justify-between items-center">
|
||
|
<h3 class="text-lg font-semibold" id="config-modal-title">编辑配置文件</h3>
|
||
|
<button class="text-gray-500 hover:text-gray-700" onclick="closeConfigModal()">
|
||
|
<i class="fa fa-times"></i>
|
||
|
</button>
|
||
|
</div>
|
||
|
<div class="p-4 flex-grow overflow-y-auto">
|
||
|
<div class="mb-3">
|
||
|
<label class="block text-sm font-medium text-gray-700">文件名</label>
|
||
|
<input type="text" id="config-filename" class="mt-1 block w-full px-3 py-2 border rounded-md shadow-sm" readonly>
|
||
|
</div>
|
||
|
<div>
|
||
|
<label class="block text-sm font-medium text-gray-700">文件内容</label>
|
||
|
<textarea id="config-content" rows="20" class="mt-1 block w-full px-3 py-2 border rounded-md shadow-sm font-mono text-sm"></textarea>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="p-4 border-t flex justify-end space-x-3">
|
||
|
<button class="btn btn-outline" onclick="closeConfigModal()">取消</button>
|
||
|
<button class="btn btn-primary" onclick="saveConfig()">保存</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<script>
|
||
|
// DOM 元素
|
||
|
const serverStatusEl = document.getElementById('detail-server-status');
|
||
|
const serverNameEl = document.getElementById('detail-server-name');
|
||
|
const serverIpEl = document.getElementById('detail-server-ip');
|
||
|
const uuidEl = document.getElementById('detail-uuid');
|
||
|
const daemonIdEl = document.getElementById('detail-daemonid');
|
||
|
const versionEl = document.getElementById('detail-version');
|
||
|
const uptimeEl = document.getElementById('detail-uptime');
|
||
|
const playersEl = document.getElementById('detail-players');
|
||
|
const playerListEl = document.getElementById('detail-player-list');
|
||
|
const memoryEl = document.getElementById('detail-memory');
|
||
|
const memoryBarEl = document.getElementById('detail-memory-bar');
|
||
|
const cpuEl = document.getElementById('detail-cpu');
|
||
|
const cpuBarEl = document.getElementById('detail-cpu-bar');
|
||
|
const networkEl = document.getElementById('detail-network');
|
||
|
const networkBarEl = document.getElementById('detail-network-bar');
|
||
|
const serverConsoleEl = document.getElementById('server-console');
|
||
|
const serverCommandInputEl = document.getElementById('server-command-input');
|
||
|
const breadcrumbServerNameEl = document.getElementById('breadcrumb-server-name');
|
||
|
|
||
|
// 从URL获取参数
|
||
|
const urlParams = new URLSearchParams(window.location.search);
|
||
|
const uuid = urlParams.get('uuid') || '5f8b4c2a-3d7e-4b1a-9c5d-6e7f8a9b0c1d';
|
||
|
const daemonId = urlParams.get('daemonId') || 'daemon-1';
|
||
|
|
||
|
// 按钮
|
||
|
const startServerBtn = document.getElementById('action-start-server');
|
||
|
const stopServerBtn = document.getElementById('action-stop-server');
|
||
|
const restartServerBtn = document.getElementById('action-restart-server');
|
||
|
const sendCommandBtn = document.getElementById('action-send-command');
|
||
|
const clearConsoleBtn = document.getElementById('action-clear-console');
|
||
|
const refreshConsoleBtn = document.getElementById('action-refresh-console');
|
||
|
const expandConsoleBtn = document.getElementById('action-expand-console');
|
||
|
|
||
|
// 初始化
|
||
|
document.addEventListener('DOMContentLoaded', () => {
|
||
|
// 加载服务器详情
|
||
|
loadServerDetails();
|
||
|
|
||
|
// 添加事件监听器
|
||
|
startServerBtn.addEventListener('click', startServer);
|
||
|
stopServerBtn.addEventListener('click', stopServer);
|
||
|
restartServerBtn.addEventListener('click', restartServer);
|
||
|
sendCommandBtn.addEventListener('click', sendCommand);
|
||
|
serverCommandInputEl.addEventListener('keypress', (e) => {
|
||
|
if (e.key === 'Enter') {
|
||
|
sendCommand();
|
||
|
}
|
||
|
});
|
||
|
clearConsoleBtn.addEventListener('click', clearConsole);
|
||
|
refreshConsoleBtn.addEventListener('click', refreshConsole);
|
||
|
expandConsoleBtn.addEventListener('click', toggleFullscreenConsole);
|
||
|
|
||
|
// 定时刷新服务器状态和控制台
|
||
|
setInterval(loadServerDetails, 5000);
|
||
|
});
|
||
|
|
||
|
let isLoading = true; // 添加一个标志变量
|
||
|
|
||
|
function loadServerDetails() {
|
||
|
if (!isLoading) return; // 如果不是第一次加载,直接返回
|
||
|
|
||
|
showLoading("正在加载服务器信息...");
|
||
|
isLoading = false; // 设置标志变量为false
|
||
|
|
||
|
fetch(`proxy.php?path=instance&uuid=${uuid}&daemonId=${daemonId}`)
|
||
|
.then(response => {
|
||
|
if (!response.ok) {
|
||
|
throw new Error(`请求失败,状态码: ${response.status}`);
|
||
|
}
|
||
|
return response.json();
|
||
|
})
|
||
|
.then(data => {
|
||
|
console.log('后端返回的数据:', data); // 打印返回的数据
|
||
|
if (!data || !data.data) {
|
||
|
throw new Error('无效的服务器数据');
|
||
|
}
|
||
|
|
||
|
const server = data.data;
|
||
|
|
||
|
// 更新基本信息
|
||
|
serverNameEl.textContent = server.config.nickname || '未知服务器';
|
||
|
breadcrumbServerNameEl.textContent = serverNameEl.textContent;
|
||
|
|
||
|
serverStatusEl.textContent = server.status === 1 ? '在线' : '离线';
|
||
|
serverStatusEl.className = `badge ${server.status === 1 ? 'badge-online' : 'badge-offline'}`;
|
||
|
|
||
|
serverIpEl.textContent = server.config.pingConfig.ip || '未知IP';
|
||
|
uuidEl.textContent = server.instanceUuid || '未知UUID';
|
||
|
daemonIdEl.textContent = daemonId;
|
||
|
versionEl.textContent = server.info.version || '未知版本';
|
||
|
uptimeEl.textContent = server.processInfo.elapsed || '未知运行时间';
|
||
|
playersEl.textContent = `${server.info.currentPlayers || 0}/${server.info.maxPlayers || 0}`;
|
||
|
|
||
|
hideLoading(); // 隐藏加载遮罩
|
||
|
// 更新玩家列表
|
||
|
updatePlayerList(server.info.playersChart || []);
|
||
|
|
||
|
// 更新性能指标
|
||
|
updatePerformanceMetrics(server);
|
||
|
|
||
|
// 获取并更新控制台日志
|
||
|
fetchConsoleLog();
|
||
|
|
||
|
// 更新按钮状态
|
||
|
updateButtonStates();
|
||
|
|
||
|
})
|
||
|
.catch(error => {
|
||
|
console.error('获取服务器详情失败:', error);
|
||
|
showNotification('获取服务器详情失败', 'error');
|
||
|
hideLoading(); // 隐藏加载遮罩
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function updatePlayerList(players) {
|
||
|
playerListEl.innerHTML = '';
|
||
|
|
||
|
if (!players || players.length === 0) {
|
||
|
const emptyEl = document.createElement('div');
|
||
|
emptyEl.className = 'text-sm text-gray-500 italic';
|
||
|
emptyEl.textContent = '没有在线玩家';
|
||
|
playerListEl.appendChild(emptyEl);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
players.forEach(player => {
|
||
|
const playerEl = document.createElement('div');
|
||
|
playerEl.className = 'flex items-center justify-between';
|
||
|
playerEl.innerHTML = `
|
||
|
<span class="text-sm">${player.name}</span>
|
||
|
<span class="text-xs text-gray-500">${player.joinTime || '刚刚加入'}</span>
|
||
|
`;
|
||
|
playerListEl.appendChild(playerEl);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function updatePerformanceMetrics(server) {
|
||
|
const memoryUsed = server.processInfo.memory || 0;
|
||
|
const memoryTotal = 1024; // 假设总内存为1024MB
|
||
|
const memoryPercent = Math.round((memoryUsed / memoryTotal) * 100);
|
||
|
|
||
|
memoryEl.textContent = `${memoryUsed} MB / ${memoryTotal} MB`;
|
||
|
memoryBarEl.style.width = `${memoryPercent}%`;
|
||
|
|
||
|
const cpuPercent = server.processInfo.cpu || 0;
|
||
|
cpuEl.textContent = `${cpuPercent}%`;
|
||
|
cpuBarEl.style.width = `${cpuPercent}%`;
|
||
|
|
||
|
const networkIn = (server.processInfo.networkIn || 0).toFixed(1);
|
||
|
const networkOut = (server.processInfo.networkOut || 0).toFixed(1);
|
||
|
const networkPercent = Math.round(((networkIn + networkOut) / 10) * 100); // 假设最大10MB/s
|
||
|
|
||
|
networkEl.textContent = `${networkIn} MB/s 入 / ${networkOut} MB/s 出`;
|
||
|
networkBarEl.style.width = `${Math.min(100, networkPercent)}%`;
|
||
|
}
|
||
|
|
||
|
function updateButtonStates() {
|
||
|
const isOnline = serverStatusEl.textContent.trim().toLowerCase() === '在线';
|
||
|
|
||
|
startServerBtn.disabled = isOnline;
|
||
|
stopServerBtn.disabled = !isOnline;
|
||
|
restartServerBtn.disabled = !isOnline;
|
||
|
}
|
||
|
|
||
|
async function fetchConsoleLog() {
|
||
|
try {
|
||
|
const response = await fetch(`proxy.php?path=protected_instance/outputlog&uuid=${uuid}&daemonId=${daemonId}&size=4096`);
|
||
|
if (!response.ok) {
|
||
|
throw new Error(`请求失败,状态码: ${response.status}`);
|
||
|
}
|
||
|
const data = await response.json();
|
||
|
if (data && data.data) {
|
||
|
serverConsoleEl.innerHTML = '';
|
||
|
|
||
|
const lines = data.data.split('\n');
|
||
|
|
||
|
lines.forEach(line => {
|
||
|
const lineEl = document.createElement('div');
|
||
|
lineEl.className = 'console-line font-mono text-sm';
|
||
|
|
||
|
if (line.includes('[INFO]')) {
|
||
|
lineEl.classList.add('text-gray-700');
|
||
|
} else if (line.includes('[WARNING]')) {
|
||
|
lineEl.classList.add('text-yellow-600');
|
||
|
} else if (line.includes('[ERROR]')) {
|
||
|
lineEl.classList.add('text-red-600');
|
||
|
} else if (line.includes('issued server command')) {
|
||
|
lineEl.classList.add('text-blue-600');
|
||
|
} else if (line.includes('joined the game') || line.includes('left the game')) {
|
||
|
lineEl.classList.add('text-green-600');
|
||
|
}
|
||
|
|
||
|
lineEl.textContent = line;
|
||
|
serverConsoleEl.appendChild(lineEl);
|
||
|
});
|
||
|
|
||
|
serverConsoleEl.scrollTop = serverConsoleEl.scrollHeight;
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error('获取控制台日志失败:', error);
|
||
|
}
|
||
|
}
|
||
|
function startServer() {
|
||
|
showLoading("正在启动服务器...");
|
||
|
|
||
|
fetch(`proxy.php?path=protected_instance/open&uuid=${uuid}&daemonId=${daemonId}`, {
|
||
|
method: 'POST'
|
||
|
})
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
// 获取并打印终端日志
|
||
|
fetchConsoleLog();
|
||
|
|
||
|
// 如果有终端输出,无论状态如何都打印
|
||
|
console.log('服务器启动响应:', data);
|
||
|
|
||
|
if (data.code === 500) {
|
||
|
// 处理服务器返回500错误的情况
|
||
|
console.error('服务器返回错误: ', data.message);
|
||
|
hideLoading();
|
||
|
}
|
||
|
|
||
|
showNotification('启动命令已发送', 'success');
|
||
|
const checkInterval = setInterval(() => {
|
||
|
fetch(`proxy.php?path=instance&uuid=${uuid}&daemonId=${daemonId}`)
|
||
|
.then(res => res.json())
|
||
|
.then(res => {
|
||
|
// 获取并打印终端日志
|
||
|
fetchConsoleLog();
|
||
|
hideLoading();
|
||
|
console.log('服务器状态响应:', res);
|
||
|
|
||
|
if (res.data?.status !== 2) { // 不是启动中状态
|
||
|
clearInterval(checkInterval);
|
||
|
loadServerDetails();
|
||
|
hideLoading();
|
||
|
}
|
||
|
});
|
||
|
}, 2000);
|
||
|
})
|
||
|
.catch(error => {
|
||
|
// 获取并打印终端日志
|
||
|
fetchConsoleLog();
|
||
|
|
||
|
console.error('启动失败错误:', error);
|
||
|
showNotification(`启动失败: ${error.message}`, 'error');
|
||
|
hideLoading();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
function stopServer() {
|
||
|
if (!confirm('确定要停止服务器吗?')) return;
|
||
|
|
||
|
showLoading("正在停止服务器...");
|
||
|
|
||
|
fetch(`proxy.php?path=protected_instance/stop&uuid=${uuid}&daemonId=${daemonId}`, {
|
||
|
method: 'POST'
|
||
|
})
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.code === 200) {
|
||
|
showNotification('服务器停止请求已发送', 'success');
|
||
|
setTimeout(loadServerDetails, 2000); // 延迟刷新状态
|
||
|
} else {
|
||
|
throw new Error(data.message || '停止失败');
|
||
|
}
|
||
|
})
|
||
|
.catch(error => {
|
||
|
console.error('停止服务器失败:', error);
|
||
|
showNotification('停止服务器失败: ' + error.message, 'error');
|
||
|
hideLoading();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function restartServer() {
|
||
|
if (!confirm('确定要重启服务器吗?')) return;
|
||
|
|
||
|
showLoading("正在重启服务器...");
|
||
|
|
||
|
fetch(`proxy.php?path=protected_instance/restart&uuid=${uuid}&daemonId=${daemonId}`, {
|
||
|
method: 'POST'
|
||
|
})
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.code === 200) {
|
||
|
showNotification('服务器重启请求已发送', 'success');
|
||
|
setTimeout(loadServerDetails, 2000); // 延迟刷新状态
|
||
|
} else {
|
||
|
throw new Error(data.message || '重启失败');
|
||
|
}
|
||
|
})
|
||
|
.catch(error => {
|
||
|
console.error('重启服务器失败:', error);
|
||
|
showNotification('重启服务器失败: ' + error.message, 'error');
|
||
|
hideLoading();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function sendCommand() {
|
||
|
const command = serverCommandInputEl.value.trim();
|
||
|
if (!command) return;
|
||
|
|
||
|
showLoading("正在发送命令...");
|
||
|
|
||
|
const formData = new FormData();
|
||
|
formData.append('uuid', uuid);
|
||
|
formData.append('daemonId', daemonId);
|
||
|
formData.append('command', command);
|
||
|
|
||
|
fetch(`proxy.php?path=protected_instance/command&uuid=${uuid}&daemonId=${daemonId}&command=${command}`, {
|
||
|
method: 'POST',
|
||
|
body: formData
|
||
|
})
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.code === 200) {
|
||
|
serverCommandInputEl.value = '';
|
||
|
showNotification('命令已发送', 'success');
|
||
|
setTimeout(fetchConsoleLog, 500); // 延迟刷新控制台
|
||
|
} else {
|
||
|
throw new Error(data.message || '命令发送失败');
|
||
|
}
|
||
|
})
|
||
|
.catch(error => {
|
||
|
console.error('发送命令失败:', error);
|
||
|
showNotification('发送命令失败: ' + error.message, 'error');
|
||
|
hideLoading();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function clearConsole() {
|
||
|
serverConsoleEl.innerHTML = '';
|
||
|
}
|
||
|
|
||
|
function refreshConsole() {
|
||
|
fetchConsoleLog();
|
||
|
}
|
||
|
|
||
|
function toggleFullscreenConsole() {
|
||
|
const consoleContainer = serverConsoleEl.parentElement;
|
||
|
consoleContainer.classList.toggle('fixed');
|
||
|
consoleContainer.classList.toggle('inset-0');
|
||
|
consoleContainer.classList.toggle('z-40');
|
||
|
consoleContainer.classList.toggle('bg-gray-900');
|
||
|
consoleContainer.classList.toggle('p-6');
|
||
|
|
||
|
serverConsoleEl.classList.toggle('h-64');
|
||
|
serverConsoleEl.classList.toggle('h-full');
|
||
|
|
||
|
if (consoleContainer.classList.contains('fixed')) {
|
||
|
expandConsoleBtn.innerHTML = '<i class="fa fa-compress mr-1"></i> 退出全屏';
|
||
|
} else {
|
||
|
expandConsoleBtn.innerHTML = '<i class="fa fa-expand mr-1"></i> 全屏';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function showLoading(message) {
|
||
|
const loadingOverlay = document.getElementById('loading-overlay');
|
||
|
const loadingMessage = document.getElementById('loading-message');
|
||
|
|
||
|
loadingMessage.textContent = message;
|
||
|
loadingOverlay.classList.remove('hidden');
|
||
|
}
|
||
|
|
||
|
function hideLoading() {
|
||
|
const loadingOverlay = document.getElementById('loading-overlay');
|
||
|
loadingOverlay.classList.add('hidden');
|
||
|
}
|
||
|
|
||
|
function showNotification(message, type = 'info') {
|
||
|
const notification = document.getElementById('notification');
|
||
|
const notificationMessage = document.getElementById('notification-message');
|
||
|
|
||
|
// 设置通知类型
|
||
|
notification.className = 'notification fixed bottom-4 right-4 p-4 rounded-lg shadow-lg transform transition-all duration-300 opacity-0 translate-y-4';
|
||
|
notification.classList.add(`bg-${type === 'success' ? 'green' : type === 'error' ? 'red' : 'blue'}-500`, `text-white`);
|
||
|
|
||
|
// 设置通知内容
|
||
|
notificationMessage.textContent = message;
|
||
|
|
||
|
// 显示通知
|
||
|
setTimeout(() => {
|
||
|
notification.classList.remove('opacity-0', 'translate-y-4');
|
||
|
}, 10);
|
||
|
|
||
|
// 3秒后隐藏通知
|
||
|
setTimeout(() => {
|
||
|
notification.classList.add('opacity-0', 'translate-y-4');
|
||
|
}, 3000);
|
||
|
}
|
||
|
</script>
|