diff --git a/gradle.properties b/gradle.properties index 8c1213f..60a45aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.21.4 yarn_mappings=1.21.4+build.8 loader_version=0.16.10 # Mod Properties -mod_version=1.14.514.133 +mod_version=1.14.514.134 maven_group=org.example1 archives_base_name=ServerPlayerOnlineTracker # Dependencies diff --git a/src/main/java/com/example/playertime/PlayerTimeMod.java b/src/main/java/com/example/playertime/PlayerTimeMod.java index d657354..e08ee01 100644 --- a/src/main/java/com/example/playertime/PlayerTimeMod.java +++ b/src/main/java/com/example/playertime/PlayerTimeMod.java @@ -39,9 +39,7 @@ public class PlayerTimeMod implements ModInitializer { private static ModConfig config; public static LocalizationManager localizationManager; - // RSS Feed URL for releases private static final String RSS_FEED_URL = "https://git.branulf.top/Branulf/ServerPlayerOnlineTracker/releases.rss"; - // Executor for update check to avoid blocking server startup private static final ExecutorService updateCheckExecutor = Executors.newSingleThreadExecutor(); @@ -53,12 +51,10 @@ public class PlayerTimeMod implements ModInitializer { try { LOGGER.info("[在线时间] 初始化 玩家在线时长视奸Mod"); - // Check for updates asynchronously on mod initialization checkForUpdates(); - // 在 SERVER_STARTING 阶段创建 Tracker 和 WebServer 实例 ServerLifecycleEvents.SERVER_STARTING.register(server -> { - timeTracker = new PlayerTimeTracker(server); // Tracker 构造函数不再加载数据 + timeTracker = new PlayerTimeTracker(server); try { webServer = new WebServer(timeTracker, config.getWebPort(), server); webServer.start(); @@ -68,10 +64,9 @@ public class PlayerTimeMod implements ModInitializer { } }); - // 在 SERVER_STARTED 阶段加载数据 (此时 UserCache 应该已可用) ServerLifecycleEvents.SERVER_STARTED.register(server -> { if (timeTracker != null) { - timeTracker.loadData(); // 在服务器启动完成后加载数据 + timeTracker.loadData(); } else { LOGGER.error("[在线时间] PlayerTimeTracker 未在 SERVER_STARTING 阶段成功初始化!"); } @@ -97,15 +92,11 @@ public class PlayerTimeMod implements ModInitializer { webServer.stop(); } if (timeTracker != null) { - // 在服务器停止前,确保所有在线玩家的会话时间被记录 for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) { - timeTracker.onPlayerLeave(player); // onPlayerLeave 会自动保存该玩家数据 + timeTracker.onPlayerLeave(player); } - // timeTracker.saveAll(); // onPlayerLeave 已经异步保存,这里可以考虑是否还需要 saveAll - // 简单的处理是直接调用 saveAll,它会覆盖旧数据 timeTracker.saveAll(); } - // Shutdown the update check executor updateCheckExecutor.shutdownNow(); }); @@ -144,11 +135,10 @@ public class PlayerTimeMod implements ModInitializer { } private static int showOnlineTime(ServerCommandSource source, int requestedPage) { - // Commands are typically executed after SERVER_STARTED, so UserCache should be available here. ServerPlayerEntity player = source.getPlayer(); if (player == null) return 0; - // Use the server's main worker executor for command processing + Util.getMainWorkerExecutor().execute(() -> { PlayerTimeTracker tracker = getTimeTracker(); if (tracker != null) { @@ -170,18 +160,14 @@ public class PlayerTimeMod implements ModInitializer { } private static int comparePlayTime(String a, String b) { - // This parsing logic is specific to the format generated by getWhitelistedPlayerStats - // Example: "PlayerName: 10h 30m | 7d: 5h 15m | 30d: 20h 45m" - // We need to extract the total time part (e.g., "10h 30m") try { - // Find the first colon and the first pipe int firstColon = a.indexOf(':'); int firstPipe = a.indexOf('|'); String timeA; if (firstColon > 0) { timeA = (firstPipe > firstColon && firstPipe != -1) ? a.substring(firstColon + 1, firstPipe).trim() : a.substring(firstColon + 1).trim(); } else { - timeA = a.trim(); // Fallback if format changes unexpectedly + timeA = a.trim(); } @@ -191,26 +177,25 @@ public class PlayerTimeMod implements ModInitializer { if (firstColonB > 0) { timeB = (firstPipeB > firstColonB && firstPipeB != -1) ? b.substring(firstColonB + 1, firstPipeB).trim() : b.substring(firstColonB + 1).trim(); } else { - timeB = b.trim(); // Fallback if format changes unexpectedly + timeB = b.trim(); } return Long.compare(parseTimeToSeconds(timeB), parseTimeToSeconds(timeA)); // Descending order } catch (Exception e) { LOGGER.error("Error comparing play times: {} vs {}", a, b, e); - return 0; // Fallback to equal if parsing fails + return 0; } } private static long parseTimeToSeconds(String timeStr) { long seconds = 0; - // Handle potential empty or null strings if (timeStr == null || timeStr.trim().isEmpty()) { return 0; } - String[] parts = timeStr.trim().split("\\s+"); // Split by one or more spaces + String[] parts = timeStr.trim().split("\\s+"); for (String part : parts) { if (part.endsWith("h")) { try { @@ -221,14 +206,11 @@ public class PlayerTimeMod implements ModInitializer { seconds += Integer.parseInt(part.substring(0, part.length() - 1)) * 60; } catch (NumberFormatException ignored) {} } - // Ignore other parts like "d:" or numbers without units } return seconds; } private static String formatPlayerTime(String name, String timeStr) { - // timeStr is already formatted like "Total: Xh Ym | 7d: ... | 30d: ..." - // We just need to prepend the player name return String.format("§e%s§r: %s", name, timeStr); } @@ -277,13 +259,9 @@ public class PlayerTimeMod implements ModInitializer { player.sendMessage(footer, false); } - /** - * Checks the RSS feed for new releases asynchronously. - */ private void checkForUpdates() { updateCheckExecutor.submit(() -> { try { - // Get current mod version Optional modContainer = FabricLoader.getInstance().getModContainer("playertime"); if (modContainer.isEmpty()) { LOGGER.warn("[在线时间] 无法获取 Mod 容器,跳过更新检查。"); @@ -294,7 +272,6 @@ public class PlayerTimeMod implements ModInitializer { LOGGER.info("[在线时间] 正在检查更新..."); - // Fetch and parse RSS feed DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new URL(RSS_FEED_URL).openStream()); @@ -307,14 +284,12 @@ public class PlayerTimeMod implements ModInitializer { return; } - // Get the latest item (first one in the feed) Element latestItem = (Element) itemList.item(0); String latestVersionString = latestItem.getElementsByTagName("title").item(0).getTextContent(); String latestVersionLink = latestItem.getElementsByTagName("link").item(0).getTextContent(); LOGGER.info("[在线时间] 当前版本: {}, 最新版本 (RSS): {}", currentVersionString, latestVersionString); - // Compare versions if (isNewerVersion(latestVersionString, currentVersionString)) { LOGGER.warn("=================================================="); LOGGER.warn("[在线时间] 发现新版本!"); @@ -338,17 +313,12 @@ public class PlayerTimeMod implements ModInitializer { }); } - /** - * Compares two version strings (e.g., "1.2.3" vs "1.2.4"). - * Returns true if newVersion is newer than currentVersion. - * Assumes versions are dot-separated integers. - */ + private boolean isNewerVersion(String newVersion, String currentVersion) { if (newVersion == null || currentVersion == null || newVersion.isEmpty() || currentVersion.isEmpty()) { - return false; // Cannot compare + return false; } - // Clean up version strings - remove potential prefixes like "v" String cleanNewVersion = newVersion.toLowerCase().startsWith("v") ? newVersion.substring(1) : newVersion; String cleanCurrentVersion = currentVersion.toLowerCase().startsWith("v") ? currentVersion.substring(1) : currentVersion; @@ -363,15 +333,14 @@ public class PlayerTimeMod implements ModInitializer { int currentPart = (i < currentParts.length) ? parseIntOrZero(currentParts[i]) : 0; if (newPart > currentPart) { - return true; // New version is newer + return true; } if (newPart < currentPart) { - return false; // New version is older + return false; } - // If parts are equal, continue to the next part } - return false; // Versions are equal + return false; } /** @@ -381,7 +350,7 @@ public class PlayerTimeMod implements ModInitializer { try { return Integer.parseInt(s); } catch (NumberFormatException e) { - return 0; // Treat non-numeric parts as 0 for comparison + return 0; } } } diff --git a/src/main/java/com/example/playertime/PlayerTimeTracker.java b/src/main/java/com/example/playertime/PlayerTimeTracker.java index 7f69229..5906888 100644 --- a/src/main/java/com/example/playertime/PlayerTimeTracker.java +++ b/src/main/java/com/example/playertime/PlayerTimeTracker.java @@ -24,11 +24,9 @@ public class PlayerTimeTracker { public PlayerTimeTracker(MinecraftServer server) { this.server = server; this.dataFile = server.getRunDirectory().resolve("player_time_data.json"); - // loadData() is now called later in ServerLifecycleEvents.SERVER_STARTED - // loadData(); // <-- Remove this line + // loadData(); } - // Make loadData public so it can be called from PlayerTimeMod public void loadData() { if (!Files.exists(dataFile)) { PlayerTimeMod.LOGGER.info("[在线时间] 数据文件未找到,跳过加载"); @@ -53,7 +51,6 @@ public class PlayerTimeTracker { if (data.lastLogin > 0) { PlayerTimeMod.LOGGER.warn( - // 修改日志,直接使用 UUID,避免在加载阶段调用 getPlayerName() "[在线时间] 在数据加载过程中发现玩家(UUID:{})的最后登录时间大于0({})。将其重置为0。", uuid, data.lastLogin ); @@ -75,7 +72,6 @@ public class PlayerTimeTracker { } catch (JsonParseException e) { PlayerTimeMod.LOGGER.error("[在线时间] 玩家在线时间数据文件格式错误", e); } catch (Exception e) { - // 捕获更广泛的异常,以防其他未知错误 PlayerTimeMod.LOGGER.error("[在线时间] 加载玩家在线时间数据时发生未知错误", e); } } @@ -96,7 +92,6 @@ public class PlayerTimeTracker { if (sessionTime > 0) { data.totalTime += sessionTime; - // 维护30天滚动窗口 data.rolling30Days.addPlayTime(now, sessionTime); data.rolling7Days.addPlayTime(now, sessionTime); @@ -124,7 +119,6 @@ public class PlayerTimeTracker { PlayerTimeStats stats = new PlayerTimeStats(); stats.totalTime = data.totalTime; - // 检查玩家是否当前在线,只在在线时才计算 if (data.lastLogin > 0 && server.getPlayerManager().getPlayer(uuid) != null) { long currentSessionTime = now - data.lastLogin; if (currentSessionTime > 0) { @@ -142,9 +136,7 @@ public class PlayerTimeTracker { Map stats = new LinkedHashMap<>(); long now = Instant.now().getEpochSecond(); - // 获取白名单玩家UUID集合 Set whitelistUuids = new HashSet<>(); - // UserCache 应该在 SERVER_STARTED 之后可用 if (server != null && server.getPlayerManager() != null && server.getUserCache() != null) { for (String name : server.getPlayerManager().getWhitelist().getNames()) { server.getUserCache().findByName(name).ifPresent(profile -> { @@ -153,18 +145,15 @@ public class PlayerTimeTracker { } } else { PlayerTimeMod.LOGGER.error("[在线时间] 尝试获取白名单玩家统计时 UserCache 或 PlayerManager 不可用!"); - return stats; // 返回空Map避免崩溃 + return stats; } - // 遍历所有已记录玩家数据 playerData.forEach((uuid, data) -> { - // 只处理白名单玩家 if (whitelistUuids.contains(uuid)) { - String playerName = getPlayerName(uuid); // UserCache 此时应该可用 + String playerName = getPlayerName(uuid); long totalTime = data.totalTime; - // 检查玩家是否当前在线,只在在线时才计算 if (data.lastLogin > 0 && server.getPlayerManager().getPlayer(uuid) != null) { long currentSessionTime = now - data.lastLogin; if (currentSessionTime > 0) { @@ -187,13 +176,11 @@ public class PlayerTimeTracker { } public String getPlayerName(UUID uuid) { - // 这个方法现在只会在 SERVER_STARTED 之后被调用,UserCache 应该可用 ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid); if (player != null) { return player.getName().getString(); } - // 确保 UserCache 不为 null 再使用 if (server != null && server.getUserCache() != null) { Optional profile = server.getUserCache().getByUuid(uuid); if (profile.isPresent()) { @@ -215,7 +202,6 @@ public class PlayerTimeTracker { }); try { - // 确保父目录存在 Files.createDirectories(dataFile.getParent()); try (Writer writer = Files.newBufferedWriter(dataFile)) { GSON.toJson(root, writer); @@ -255,21 +241,18 @@ public class PlayerTimeTracker { root.add(uuid.toString(), GSON.toJsonTree(data)); try { - // 确保父目录存在 Files.createDirectories(dataFile.getParent()); try (Writer writer = Files.newBufferedWriter(dataFile)) { GSON.toJson(root, writer); } } catch (Exception e) { - // 异步保存失败时,尝试获取玩家名称可能会再次触发 UserCache 问题,直接使用 UUID String playerName = "UUID: " + uuid.toString(); - // 尝试获取玩家名称,但要小心 UserCache 是否可用 try { if (server != null && server.getPlayerManager() != null) { ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid); if (player != null) { playerName = player.getName().getString(); - } else if (server.getUserCache() != null) { // 只有 UserCache 可用时才尝试 + } else if (server.getUserCache() != null) { Optional profile = server.getUserCache().getByUuid(uuid); if (profile.isPresent()) { playerName = profile.get().getName(); @@ -277,7 +260,6 @@ public class PlayerTimeTracker { } } } catch (Exception nameEx) { - // Ignore error getting name during error logging } PlayerTimeMod.LOGGER.error("[在线时间] 无法异步保存玩家的在线时间数据 " + playerName, e); @@ -326,7 +308,6 @@ public class PlayerTimeTracker { private void cleanUp(long currentTime) { long cutoff = currentTime - (days * 24 * 3600L); - // 使用迭代器安全地移除元素 entries.removeIf(entry -> entry.timestamp < cutoff); } diff --git a/src/main/java/com/example/playertime/WebServer.java b/src/main/java/com/example/playertime/WebServer.java index f0e64b7..a51ff2c 100644 --- a/src/main/java/com/example/playertime/WebServer.java +++ b/src/main/java/com/example/playertime/WebServer.java @@ -69,7 +69,6 @@ public class WebServer { try { // 白名单 - // timeTracker.getWhitelistedPlayerStats() 内部已处理 UserCache null 检查 Map stats = timeTracker.getWhitelistedPlayerStats(); String response = GSON.toJson(stats); sendResponse(exchange, 200, response.getBytes(StandardCharsets.UTF_8), "application/json"); @@ -109,8 +108,7 @@ public class WebServer { .orElse(null); if (is == null) { - // Fallback to default language if configured language file is not found - langCode = "en_us"; // Default fallback + langCode = "zh_cn"; resourcePath = String.format("assets/playertime/lang/%s.json", langCode); String finalResourcePath = resourcePath; is = FabricLoader.getInstance().getModContainer("playertime") @@ -244,7 +242,6 @@ public class WebServer { JsonArray whitelistPlayers = new JsonArray(); Set whitelistUuids = new HashSet<>(); - // 增加 UserCache null 检查 if (server != null && server.getUserCache() != null) { for (String name : playerManager.getWhitelist().getNames()) { server.getUserCache().findByName(name).ifPresent(profile -> { @@ -272,11 +269,10 @@ public class WebServer { JsonArray topPlayers = new JsonArray(); timeTracker.getPlayerData().entrySet().stream() - .filter(entry -> whitelistUuids.contains(entry.getKey())) // 只筛选白名单玩家 + .filter(entry -> whitelistUuids.contains(entry.getKey())) .sorted((a, b) -> Long.compare(b.getValue().totalTime, a.getValue().totalTime)) .limit(3) .forEach(entry -> { - // timeTracker.getPlayerName() 内部已处理 UserCache null 检查 JsonObject playerJson = new JsonObject(); playerJson.addProperty("name", timeTracker.getPlayerName(entry.getKey())); playerJson.addProperty("time", PlayerTimeTracker.formatTime(entry.getValue().totalTime)); // formatTime doesn't need localization @@ -312,7 +308,6 @@ public class WebServer { // 获取白名单玩家UUID集合 Set whitelistUuids = new HashSet<>(); - // 增加 UserCache null 检查 if (minecraftServer != null && minecraftServer.getUserCache() != null) { for (String name : playerManager.getWhitelist().getNames()) { minecraftServer.getUserCache().findByName(name).ifPresent(profile -> { @@ -370,7 +365,6 @@ public class WebServer { // 获取白名单玩家UUID集合 Set whitelistUuids = new HashSet<>(); - // 增加 UserCache null 检查 if (minecraftServer != null && minecraftServer.getUserCache() != null) { for (String name : playerManager.getWhitelist().getNames()) { minecraftServer.getUserCache().findByName(name).ifPresent(profile -> { @@ -409,7 +403,7 @@ public class WebServer { server.createContext("/api/whitelist", exchange -> { handleCors(exchange); if ("OPTIONS".equals(exchange.getRequestMethod())) { - exchange.sendResponseHeaders(204, -1); // 204 No Content + exchange.sendResponseHeaders(204, -1); return; } @@ -422,18 +416,15 @@ public class WebServer { PlayerManager playerManager = minecraftServer.getPlayerManager(); JsonArray whitelist = new JsonArray(); - // 增加 UserCache null 检查 if (minecraftServer != null && minecraftServer.getUserCache() != null) { for (String name : playerManager.getWhitelist().getNames()) { JsonObject player = new JsonObject(); player.addProperty("name", name); - // 尝试获取UUID Optional profile = minecraftServer.getUserCache().findByName(name); if (profile.isPresent()) { player.addProperty("uuid", profile.get().getId().toString()); - // 检查是否在线 ServerPlayerEntity onlinePlayer = playerManager.getPlayer(profile.get().getId()); player.addProperty("online", onlinePlayer != null); } else { @@ -545,7 +536,7 @@ public class WebServer { } try { - Path dataFile = timeTracker.getDataFile(); // 从PlayerTimeTracker获取文件路径 + Path dataFile = timeTracker.getDataFile(); if (!Files.exists(dataFile)) { PlayerTimeMod.LOGGER.warn("[在线时间] 玩家数据文件未找到: {}", dataFile); diff --git a/src/main/resources/assets/playertime/lang/en_us.json b/src/main/resources/assets/playertime/lang/en_us.json index 282aa2a..2715902 100644 --- a/src/main/resources/assets/playertime/lang/en_us.json +++ b/src/main/resources/assets/playertime/lang/en_us.json @@ -42,5 +42,15 @@ "playertime.web.refresh_button": "Refresh", - "playertime.web.error.load_failed": "Failed to load data, check console" + "playertime.web.error.load_failed": "Failed to load data, check console", + + "playertime.web.server_status.disk_usage": "Disk Usage", + "playertime.web.server_status.disk_used": "Used", + "playertime.web.server_status.disk_free": "Free", + "playertime.web.server_status.disk_percent": "Usage", + "playertime.web.server_status.processors": "Processors", + "playertime.web.server_status.server_version": "Server Version", + "playertime.web.server_status.disk_total": "Total", + "playertime.web.chart.disk_used": "Used", + "playertime.web.chart.disk_free": "Free" } diff --git a/src/main/resources/assets/playertime/lang/zh_cn.json b/src/main/resources/assets/playertime/lang/zh_cn.json index 7168f7c..2ede6e0 100644 --- a/src/main/resources/assets/playertime/lang/zh_cn.json +++ b/src/main/resources/assets/playertime/lang/zh_cn.json @@ -42,5 +42,15 @@ "playertime.web.refresh_button": "刷新数据", - "playertime.web.error.load_failed": "加载数据失败,请检查控制台" + "playertime.web.error.load_failed": "加载数据失败,请检查控制台", + + "playertime.web.server_status.disk_usage": "磁盘使用", + "playertime.web.server_status.disk_used": "已用", + "playertime.web.server_status.disk_free": "可用", + "playertime.web.server_status.disk_percent": "使用率", + "playertime.web.server_status.processors": "处理器核心", + "playertime.web.server_status.server_version": "服务器版本", + "playertime.web.server_status.disk_total": "总量", + "playertime.web.chart.disk_used": "已用", + "playertime.web.chart.disk_free": "可用" } diff --git a/src/main/resources/assets/playertime/web/css/style.css b/src/main/resources/assets/playertime/web/css/style.css index f00309d..d5211c5 100644 --- a/src/main/resources/assets/playertime/web/css/style.css +++ b/src/main/resources/assets/playertime/web/css/style.css @@ -621,4 +621,93 @@ canvas { } } +.server-info-stats div { + padding: 5px 0; + border-bottom: 1px solid var(--border-color); +} + +.server-info-stats div:last-child { + border-bottom: none; +} + +.status-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin-top: 1rem; +} + +@media (max-width: 768px) { + .status-grid { + grid-template-columns: 1fr; + } +} + +.floating-refresh-btn { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 1000; + transition: transform 0.3s ease; +} + +.refresh-btn.floating { + width: 56px; + height: 56px; + border-radius: 50%; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); + display: flex; + align-items: center; + justify-content: center; + padding: 0; +} + +.refresh-btn.floating i { + font-size: 1.5rem; +} + +.refresh-btn.floating:hover { + transform: scale(1.1); + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.3); +} + +.refresh-btn.floating.loading i { + transition: transform 0.5s ease; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@media (max-width: 768px) { + .floating-refresh-btn { + bottom: 20px; + right: 20px; + } + + .refresh-btn.floating { + width: 50px; + height: 50px; + } +} + +@media (max-width: 480px) { + .floating-refresh-btn { + bottom: 15px; + right: 15px; + } + + .refresh-btn.floating { + width: 44px; + height: 44px; + } + + .refresh-btn.floating i { + font-size: 1.3rem; + } +} + + + diff --git a/src/main/resources/assets/playertime/web/index.html b/src/main/resources/assets/playertime/web/index.html index b4f122b..18e3611 100644 --- a/src/main/resources/assets/playertime/web/index.html +++ b/src/main/resources/assets/playertime/web/index.html @@ -90,6 +90,26 @@ +
+

磁盘使用

+ +
+
总量: 0 GB
+
已用: 0 GB
+
可用: 0 GB
+
使用率: 0%
+
+
+ +
+

服务器信息

+
+
服务器版本: 未知
+
处理器核心: 0
+
在线玩家: 0/0
+
+
+
@@ -127,5 +147,10 @@

Copyright © 2025 BRanulf

+
+ +
diff --git a/src/main/resources/assets/playertime/web/js/app.js b/src/main/resources/assets/playertime/web/js/app.js index 82c8602..1f280c9 100644 --- a/src/main/resources/assets/playertime/web/js/app.js +++ b/src/main/resources/assets/playertime/web/js/app.js @@ -5,6 +5,9 @@ document.addEventListener('DOMContentLoaded', function() { let memoryChart = null; let performanceChart = null; let lang = {}; + let diskChart = null; + const floatingRefreshBtn = document.getElementById('floating-refresh-btn'); + // 获取语言 const elements = { @@ -93,6 +96,29 @@ document.addEventListener('DOMContentLoaded', function() { } } + if (floatingRefreshBtn) { + floatingRefreshBtn.addEventListener('click', function() { + if (this.classList.contains('loading')) return; + + this.classList.add('loading'); + loadAllData().finally(() => { + setTimeout(() => { + this.classList.remove('loading'); + }, 500); + }); + }); + } + + // window.addEventListener('scroll', function() { + // if (!floatingRefreshBtn) return; + // + // if (window.scrollY > 100) { + // floatingRefreshBtn.style.transform = 'scale(1)'; + // } else { + // floatingRefreshBtn.style.transform = 'scale(0.9)'; + // } + // }); + // 图表 function initCharts() { @@ -221,6 +247,30 @@ document.addEventListener('DOMContentLoaded', function() { } }); } + + const diskCtx = document.getElementById('disk-chart')?.getContext('2d'); + if (diskCtx) { + diskChart = new Chart(diskCtx, { + type: 'doughnut', + data: { + labels: [getLangString('playertime.web.chart.disk_used'), + getLangString('playertime.web.chart.disk_free')], + datasets: [{ + data: [0, 100], + backgroundColor: ['#4361ee', '#e9ecef'] + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top', + } + } + } + }); + } } function toggleTheme() { @@ -409,6 +459,34 @@ document.addEventListener('DOMContentLoaded', function() { } + if (data.disk) { + const totalGB = Math.round(data.disk.total / (1024 * 1024 * 1024)); + const usedGB = Math.round(data.disk.used / (1024 * 1024 * 1024)); + const freeGB = Math.round(data.disk.free / (1024 * 1024 * 1024)); + const percent = Math.round(data.disk.usage_percentage); + + if (diskChart) { + diskChart.data.datasets[0].data = [usedGB, freeGB]; + diskChart.update(); + } + + document.getElementById('disk-total').textContent = totalGB; + document.getElementById('disk-used').textContent = usedGB; + document.getElementById('disk-free').textContent = freeGB; + document.getElementById('disk-percent').textContent = percent; + } + + // 新增服务器信息 + if (data.available_processors) { + document.getElementById('processors').textContent = data.available_processors; + } + + if (data.server) { + document.getElementById('server-version').textContent = data.server.version; + document.getElementById('server-player-count').textContent = data.server.player_count; + document.getElementById('server-max-players').textContent = data.server.max_players; + } + if (elements.uptime) elements.uptime.textContent = data.uptime_formatted || '0'; }