Compare commits
No commits in common. "master" and "1.14.514.120-1.21.4" have entirely different histories.
master
...
1.14.514.1
@ -1,4 +1,4 @@
|
|||||||
# 我会永远视奸你们的👀
|
# Player_OnlineTime
|
||||||
|
|
||||||
一个监视服务器玩家在线时间的mod,带web服务器,目前并不完善
|
一个监视服务器玩家在线时间的mod,带web服务器,目前并不完善
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ minecraft_version=1.21.4
|
|||||||
yarn_mappings=1.21.4+build.8
|
yarn_mappings=1.21.4+build.8
|
||||||
loader_version=0.16.10
|
loader_version=0.16.10
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version=1.14.514.121
|
mod_version=1.14.514.120
|
||||||
maven_group=org.example1
|
maven_group=org.example1
|
||||||
archives_base_name=playerOnlineTimeTrackerMod
|
archives_base_name=playerOnlineTimeTrackerMod
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
@ -1,26 +1,12 @@
|
|||||||
package com.example.playertime;
|
package com.example.playertime;
|
||||||
|
|
||||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
|
||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.minecraft.server.command.CommandManager;
|
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
|
||||||
import net.minecraft.text.ClickEvent;
|
|
||||||
import net.minecraft.text.MutableText;
|
|
||||||
import net.minecraft.text.Text;
|
|
||||||
import net.minecraft.util.Util;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class PlayerTimeMod implements ModInitializer {
|
public class PlayerTimeMod implements ModInitializer {
|
||||||
public static final Logger LOGGER = LoggerFactory.getLogger("PlayerTimeTracker");
|
public static final Logger LOGGER = LoggerFactory.getLogger("PlayerTimeTracker");
|
||||||
private static PlayerTimeTracker timeTracker;
|
private static PlayerTimeTracker timeTracker;
|
||||||
@ -74,7 +60,6 @@ public class PlayerTimeMod implements ModInitializer {
|
|||||||
LOGGER.error("[在线时间] Mod 初始化失败!", e);
|
LOGGER.error("[在线时间] Mod 初始化失败!", e);
|
||||||
throw new RuntimeException("[在线时间] Mod 初始化失败", e);
|
throw new RuntimeException("[在线时间] Mod 初始化失败", e);
|
||||||
}
|
}
|
||||||
registerCommands();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ModConfig getConfig() {
|
public static ModConfig getConfig() {
|
||||||
@ -86,107 +71,4 @@ public class PlayerTimeMod implements ModInitializer {
|
|||||||
public static PlayerTimeTracker getTimeTracker() {
|
public static PlayerTimeTracker getTimeTracker() {
|
||||||
return timeTracker;
|
return timeTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void registerCommands() {
|
|
||||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
|
||||||
dispatcher.register(CommandManager.literal("onlineTime")
|
|
||||||
.requires(source -> source.hasPermissionLevel(0))
|
|
||||||
.executes(context -> showOnlineTime(context.getSource(), 1)) // 默认第一页
|
|
||||||
.then(CommandManager.argument("page", IntegerArgumentType.integer(1))
|
|
||||||
.executes(context -> showOnlineTime(
|
|
||||||
context.getSource(),
|
|
||||||
IntegerArgumentType.getInteger(context, "page")
|
|
||||||
))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int showOnlineTime(ServerCommandSource source, int requestedPage) {
|
|
||||||
ServerPlayerEntity player = source.getPlayer();
|
|
||||||
if (player == null) return 0;
|
|
||||||
|
|
||||||
CompletableFuture.runAsync(() -> {
|
|
||||||
PlayerTimeTracker tracker = getTimeTracker();
|
|
||||||
if (tracker != null) {
|
|
||||||
Map<String, String> stats = tracker.getWhitelistedPlayerStats();
|
|
||||||
|
|
||||||
List<String> sorted = stats.entrySet().stream()
|
|
||||||
.sorted((a, b) -> comparePlayTime(b.getValue(), a.getValue()))
|
|
||||||
.map(entry -> formatPlayerTime(entry.getKey(), entry.getValue()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
sendPaginatedMessage(player, sorted, requestedPage);
|
|
||||||
}
|
|
||||||
}, Util.getMainWorkerExecutor());
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static int comparePlayTime(String a, String b) {
|
|
||||||
return Long.compare(parseTimeToSeconds(a), parseTimeToSeconds(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long parseTimeToSeconds(String timeStr) {
|
|
||||||
long seconds = 0;
|
|
||||||
String[] parts = timeStr.split(" ");
|
|
||||||
for (String part : parts) {
|
|
||||||
if (part.contains("h")) {
|
|
||||||
seconds += Integer.parseInt(part.replace("h", "")) * 3600;
|
|
||||||
} else if (part.contains("m")) {
|
|
||||||
seconds += Integer.parseInt(part.replace("m", "")) * 60;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatPlayerTime(String name, String timeStr) {
|
|
||||||
return String.format("§e%s§r: %s", name, timeStr.split(" \\| ")[0]); // 只显示总时长
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendPaginatedMessage(ServerPlayerEntity player, List<String> lines, int page) {
|
|
||||||
int pageSize = 10;
|
|
||||||
int totalPages = (lines.size() + pageSize - 1) / pageSize;
|
|
||||||
page = Math.max(1, Math.min(page, totalPages));
|
|
||||||
|
|
||||||
int from = (page - 1) * pageSize;
|
|
||||||
int to = Math.min(from + pageSize, lines.size());
|
|
||||||
|
|
||||||
// 标题
|
|
||||||
player.sendMessage(Text.literal("§6===== 玩家在线时长 (第 " + page + "/" + totalPages + " 页) ====="), false);
|
|
||||||
|
|
||||||
// 内容
|
|
||||||
for (int i = from; i < to; i++) {
|
|
||||||
player.sendMessage(Text.literal(lines.get(i)), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 翻页按钮
|
|
||||||
MutableText footer = Text.literal("");
|
|
||||||
if (page > 1) {
|
|
||||||
int finalPage = page;
|
|
||||||
footer.append(Text.literal("§a[上一页]")
|
|
||||||
.styled(style -> style.withClickEvent(new ClickEvent(
|
|
||||||
ClickEvent.Action.RUN_COMMAND,
|
|
||||||
"/onlineTime " + (finalPage - 1)
|
|
||||||
)))
|
|
||||||
.append(" "));
|
|
||||||
}
|
|
||||||
|
|
||||||
footer.append(Text.literal("§7共 " + lines.size() + " 位玩家"));
|
|
||||||
|
|
||||||
if (page < totalPages) {
|
|
||||||
int finalPage1 = page;
|
|
||||||
footer.append(" ").append(Text.literal("§a[下一页]")
|
|
||||||
.styled(style -> style.withClickEvent(new ClickEvent(
|
|
||||||
ClickEvent.Action.RUN_COMMAND,
|
|
||||||
"/onlineTime " + (finalPage1 + 1)
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
|
|
||||||
player.sendMessage(footer, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -345,6 +345,7 @@ public class WebServer {
|
|||||||
Runtime runtime = Runtime.getRuntime();
|
Runtime runtime = Runtime.getRuntime();
|
||||||
JsonObject status = new JsonObject();
|
JsonObject status = new JsonObject();
|
||||||
|
|
||||||
|
// 内存使用情况
|
||||||
long maxMemory = runtime.maxMemory();
|
long maxMemory = runtime.maxMemory();
|
||||||
long totalMemory = runtime.totalMemory();
|
long totalMemory = runtime.totalMemory();
|
||||||
long freeMemory = runtime.freeMemory();
|
long freeMemory = runtime.freeMemory();
|
||||||
|
@ -532,93 +532,3 @@ canvas {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.performance-metrics {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric {
|
|
||||||
background: var(--card-bg);
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-label {
|
|
||||||
font-weight: bold;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: var(--primary-color-c);
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-value {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.performance-metrics {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-wrapper {
|
|
||||||
position: relative;
|
|
||||||
height: 300px; /* 固定高度 */
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-display {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric {
|
|
||||||
background: var(--card-bg);
|
|
||||||
padding: 12px 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
min-width: 120px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-label {
|
|
||||||
font-weight: bold;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: var(--primary-color-c);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-value {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
font-family: monospace;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.chart-wrapper {
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-display {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric {
|
|
||||||
padding: 10px 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,27 +69,15 @@
|
|||||||
<div>使用率: <span id="memory-percent">0</span>%</div>
|
<div>使用率: <span id="memory-percent">0</span>%</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-item">
|
<div class="status-item">
|
||||||
<h3>实时性能</h3>
|
<h3>服务器性能</h3>
|
||||||
<div class="chart-container">
|
|
||||||
<div class="metric-display">
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">TPS:</span>
|
|
||||||
<span class="metric-value" id="tps-value">0.0</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">MSPT:</span>
|
|
||||||
<span class="metric-value" id="mspt-value">0.0</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chart-wrapper">
|
|
||||||
<canvas id="performance-chart"></canvas>
|
<canvas id="performance-chart"></canvas>
|
||||||
|
<div class="performance-stats">
|
||||||
|
<div>平均Tick: <span id="avg-tick">0</span> ms</div>
|
||||||
|
<div>运行时间: <span id="uptime">0</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 玩家时长统计表格 -->
|
<!-- 玩家时长统计表格 -->
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// 1. 首先初始化所有变量
|
// 获取DOM元素(带安全检查)
|
||||||
const tpsHistory = Array(30).fill(20);
|
|
||||||
const msptHistory = Array(30).fill(50);
|
|
||||||
let memoryChart = null;
|
|
||||||
let performanceChart = null;
|
|
||||||
|
|
||||||
// 2. 获取DOM元素(带安全检查)
|
|
||||||
const elements = {
|
const elements = {
|
||||||
refreshBtn: document.getElementById('refresh-btn'),
|
refreshBtn: document.getElementById('refresh-btn'),
|
||||||
themeToggle: document.getElementById('theme-toggle'),
|
themeToggle: document.getElementById('theme-toggle'),
|
||||||
@ -19,107 +13,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
memoryFree: document.getElementById('memory-free'),
|
memoryFree: document.getElementById('memory-free'),
|
||||||
memoryPercent: document.getElementById('memory-percent'),
|
memoryPercent: document.getElementById('memory-percent'),
|
||||||
avgTick: document.getElementById('avg-tick'),
|
avgTick: document.getElementById('avg-tick'),
|
||||||
uptime: document.getElementById('uptime'),
|
uptime: document.getElementById('uptime')
|
||||||
tpsValue: document.getElementById('tps-value'),
|
|
||||||
msptValue: document.getElementById('mspt-value')
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. 初始化主题
|
// 初始化主题
|
||||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||||
|
|
||||||
// 4. 初始化图表(必须在其他函数之前)
|
// 主题切换
|
||||||
initCharts();
|
|
||||||
|
|
||||||
// 5. 事件监听器
|
|
||||||
if (elements.themeToggle) {
|
if (elements.themeToggle) {
|
||||||
elements.themeToggle.addEventListener('click', toggleTheme);
|
elements.themeToggle.addEventListener('click', toggleTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新按钮
|
||||||
if (elements.refreshBtn) {
|
if (elements.refreshBtn) {
|
||||||
elements.refreshBtn.addEventListener('click', handleRefresh);
|
elements.refreshBtn.addEventListener('click', handleRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 初始加载数据
|
// 初始化图表
|
||||||
|
const memoryChart = initMemoryChart();
|
||||||
|
const performanceChart = initPerformanceChart();
|
||||||
|
|
||||||
|
// 初始加载数据
|
||||||
loadAllData();
|
loadAllData();
|
||||||
|
|
||||||
// 7. 设置定时刷新(10秒)
|
// 设置定时刷新(10秒)
|
||||||
const refreshInterval = setInterval(loadAllData, 10000);
|
setInterval(loadAllData, 10000);
|
||||||
|
|
||||||
/*** 功能函数 ***/
|
/*** 功能函数 ***/
|
||||||
function initCharts() {
|
|
||||||
// 内存图表
|
|
||||||
const memoryCtx = document.getElementById('memory-chart')?.getContext('2d');
|
|
||||||
if (memoryCtx) {
|
|
||||||
memoryChart = new Chart(memoryCtx, {
|
|
||||||
type: 'doughnut',
|
|
||||||
data: {
|
|
||||||
labels: ['已使用', '未使用'],
|
|
||||||
datasets: [{
|
|
||||||
data: [0, 100],
|
|
||||||
backgroundColor: ['#4361ee', '#e9ecef']
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 性能图表
|
|
||||||
const perfCtx = document.getElementById('performance-chart')?.getContext('2d');
|
|
||||||
if (perfCtx) {
|
|
||||||
performanceChart = new Chart(perfCtx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: Array(30).fill(''),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'TPS',
|
|
||||||
data: tpsHistory,
|
|
||||||
borderColor: '#4CAF50',
|
|
||||||
tension: 0.1,
|
|
||||||
yAxisID: 'y'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'MSPT',
|
|
||||||
data: msptHistory,
|
|
||||||
borderColor: '#FF5722',
|
|
||||||
tension: 0.1,
|
|
||||||
yAxisID: 'y1'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
interaction: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
type: 'linear',
|
|
||||||
display: true,
|
|
||||||
position: 'left',
|
|
||||||
title: { display: true, text: 'TPS' },
|
|
||||||
min: 0,
|
|
||||||
max: 20
|
|
||||||
},
|
|
||||||
y1: {
|
|
||||||
type: 'linear',
|
|
||||||
display: true,
|
|
||||||
position: 'right',
|
|
||||||
title: { display: true, text: 'MSPT (ms)' },
|
|
||||||
min: 0,
|
|
||||||
grid: { drawOnChartArea: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleTheme() {
|
function toggleTheme() {
|
||||||
const currentTheme = document.documentElement.getAttribute('data-theme');
|
const currentTheme = document.documentElement.getAttribute('data-theme');
|
||||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||||
@ -135,13 +56,65 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initMemoryChart() {
|
||||||
|
const ctx = document.getElementById('memory-chart')?.getContext('2d');
|
||||||
|
if (!ctx) return null;
|
||||||
|
|
||||||
|
return new Chart(ctx, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: ['已使用', '未使用'],
|
||||||
|
datasets: [{
|
||||||
|
data: [0, 100],
|
||||||
|
backgroundColor: ['#4361ee', '#e9ecef']
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPerformanceChart() {
|
||||||
|
const ctx = document.getElementById('performance-chart')?.getContext('2d');
|
||||||
|
if (!ctx) return null;
|
||||||
|
|
||||||
|
return new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: Array(10).fill('').map((_, i) => i + 1),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Tick时间 (ms)',
|
||||||
|
data: Array(10).fill(0),
|
||||||
|
borderColor: '#4361ee',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: '毫秒'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function loadAllData() {
|
async function loadAllData() {
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchData('/api/stats', updateTable),
|
fetchData('/api/stats', updateTable),
|
||||||
fetchData('/api/online-players', updateOnlinePlayers),
|
fetchData('/api/online-players', updateOnlinePlayers),
|
||||||
fetchData('/api/player-count', updatePlayerCounts),
|
fetchData('/api/player-count', updatePlayerCounts),
|
||||||
fetchData('/api/server-status', updateServerStatus)
|
fetchData('/api/server-status', (data) => updateServerStatus(data, memoryChart, performanceChart))
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载数据出错:', error);
|
console.error('加载数据出错:', error);
|
||||||
@ -211,7 +184,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (elements.nonWhitelistCount) elements.nonWhitelistCount.textContent = data.non_whitelisted || 0;
|
if (elements.nonWhitelistCount) elements.nonWhitelistCount.textContent = data.non_whitelisted || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateServerStatus(data) {
|
function updateServerStatus(data, memoryChart, performanceChart) {
|
||||||
// 更新内存信息
|
// 更新内存信息
|
||||||
if (data.memory) {
|
if (data.memory) {
|
||||||
const usedMB = Math.round(data.memory.used / (1024 * 1024));
|
const usedMB = Math.round(data.memory.used / (1024 * 1024));
|
||||||
@ -230,59 +203,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// 更新性能信息
|
// 更新性能信息
|
||||||
if (data.server) {
|
if (data.server) {
|
||||||
// 计算TPS (限制在0-20之间)
|
if (performanceChart && data.server.recent_tick_samples_ms) {
|
||||||
const tps = Math.min(20, 1000 / (data.server.average_tick_time_ms || 50)).toFixed(1);
|
performanceChart.data.datasets[0].data = data.server.recent_tick_samples_ms;
|
||||||
const mspt = data.server.average_tick_time_ms.toFixed(1);
|
|
||||||
|
|
||||||
// 更新显示
|
|
||||||
if (elements.tpsValue) elements.tpsValue.textContent = tps;
|
|
||||||
if (elements.msptValue) elements.msptValue.textContent = mspt;
|
|
||||||
|
|
||||||
// 更新图表数据
|
|
||||||
if (performanceChart) {
|
|
||||||
// 更新历史数据
|
|
||||||
tpsHistory.shift();
|
|
||||||
tpsHistory.push(parseFloat(tps));
|
|
||||||
msptHistory.shift();
|
|
||||||
msptHistory.push(parseFloat(mspt));
|
|
||||||
|
|
||||||
// 只更新数据不重新创建图表
|
|
||||||
performanceChart.data.datasets[0].data = tpsHistory;
|
|
||||||
performanceChart.data.datasets[1].data = msptHistory;
|
|
||||||
performanceChart.update();
|
performanceChart.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements.avgTick) elements.avgTick.textContent = data.server.average_tick_time_ms?.toFixed(2) || '0';
|
if (elements.avgTick) elements.avgTick.textContent = data.server.average_tick_time_ms?.toFixed(2) || '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新性能图表
|
|
||||||
if (performanceChart && data.server) {
|
|
||||||
// 计算TPS和MSPT
|
|
||||||
const tps = Math.min(20, 1000 / (data.server.average_tick_time_ms || 50)).toFixed(1);
|
|
||||||
const mspt = data.server.average_tick_time_ms.toFixed(1);
|
|
||||||
|
|
||||||
// 更新历史数据
|
|
||||||
tpsHistory.shift();
|
|
||||||
tpsHistory.push(parseFloat(tps));
|
|
||||||
msptHistory.shift();
|
|
||||||
msptHistory.push(parseFloat(mspt));
|
|
||||||
|
|
||||||
// 只更新数据(不重新创建图表)
|
|
||||||
performanceChart.data.datasets[0].data = tpsHistory;
|
|
||||||
performanceChart.data.datasets[1].data = msptHistory;
|
|
||||||
|
|
||||||
// 平滑更新
|
|
||||||
performanceChart.update('none');
|
|
||||||
|
|
||||||
// 更新数值显示
|
|
||||||
if (elements.tpsValue) elements.tpsValue.textContent = tps;
|
|
||||||
if (elements.msptValue) elements.msptValue.textContent = mspt;
|
|
||||||
|
|
||||||
// 根据数值设置颜色
|
|
||||||
setMetricColor(elements.tpsValue, tps, 15, 10);
|
|
||||||
setMetricColor(elements.msptValue, mspt, 50, 100, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elements.uptime) elements.uptime.textContent = data.uptime_formatted || '0';
|
if (elements.uptime) elements.uptime.textContent = data.uptime_formatted || '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,120 +237,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPerformanceChart() {
|
|
||||||
const perfCtx = document.getElementById('performance-chart')?.getContext('2d');
|
|
||||||
if (!perfCtx) return null;
|
|
||||||
|
|
||||||
return new Chart(perfCtx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: Array(30).fill(''),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'TPS',
|
|
||||||
data: tpsHistory,
|
|
||||||
borderColor: '#4CAF50',
|
|
||||||
backgroundColor: 'rgba(76, 175, 80, 0.1)',
|
|
||||||
borderWidth: 2,
|
|
||||||
pointRadius: 3,
|
|
||||||
pointHoverRadius: 5,
|
|
||||||
tension: 0.3,
|
|
||||||
yAxisID: 'y'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'MSPT',
|
|
||||||
data: msptHistory,
|
|
||||||
borderColor: '#FF5722',
|
|
||||||
backgroundColor: 'rgba(255, 87, 34, 0.1)',
|
|
||||||
borderWidth: 2,
|
|
||||||
pointRadius: 3,
|
|
||||||
pointHoverRadius: 5,
|
|
||||||
tension: 0.3,
|
|
||||||
yAxisID: 'y1'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
animation: {
|
|
||||||
duration: 500
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
position: 'top',
|
|
||||||
labels: {
|
|
||||||
boxWidth: 12,
|
|
||||||
padding: 20,
|
|
||||||
font: {
|
|
||||||
size: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false,
|
|
||||||
bodySpacing: 8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
grid: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: 'linear',
|
|
||||||
display: true,
|
|
||||||
position: 'left',
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'TPS',
|
|
||||||
font: {
|
|
||||||
weight: 'bold'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
max: 20,
|
|
||||||
grid: {
|
|
||||||
color: 'rgba(0, 0, 0, 0.05)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y1: {
|
|
||||||
type: 'linear',
|
|
||||||
display: true,
|
|
||||||
position: 'right',
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'MSPT (ms)',
|
|
||||||
font: {
|
|
||||||
weight: 'bold'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
grid: {
|
|
||||||
drawOnChartArea: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMetricColor(element, value, warnThreshold, dangerThreshold, reverse = false) {
|
|
||||||
if (!element) return;
|
|
||||||
|
|
||||||
const numValue = parseFloat(value);
|
|
||||||
element.style.color =
|
|
||||||
reverse ?
|
|
||||||
(numValue > dangerThreshold ? '#FF5722' : numValue > warnThreshold ? '#FFC107' : '#4CAF50') :
|
|
||||||
(numValue < dangerThreshold ? '#FF5722' : numValue < warnThreshold ? '#FFC107' : '#4CAF50');
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user