awa
This commit is contained in:
parent
23a0518ca7
commit
1ffa91a98a
@ -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.128
|
mod_version=1.14.514.129
|
||||||
maven_group=org.example1
|
maven_group=org.example1
|
||||||
archives_base_name=playerOnlineTimeTrackerMod
|
archives_base_name=playerOnlineTimeTrackerMod
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
@ -9,6 +9,7 @@ public class ModConfig {
|
|||||||
private final Path configPath;
|
private final Path configPath;
|
||||||
private int webPort = 60048;
|
private int webPort = 60048;
|
||||||
private String language = "zh_cn";
|
private String language = "zh_cn";
|
||||||
|
private long autoSaveSeconds = 300;
|
||||||
|
|
||||||
public ModConfig(Path configDir) {
|
public ModConfig(Path configDir) {
|
||||||
this.configPath = configDir.resolve("playertime-config.json");
|
this.configPath = configDir.resolve("playertime-config.json");
|
||||||
@ -17,7 +18,7 @@ public class ModConfig {
|
|||||||
|
|
||||||
private void loadConfig() {
|
private void loadConfig() {
|
||||||
if (!Files.exists(configPath)) {
|
if (!Files.exists(configPath)) {
|
||||||
PlayerTimeMod.LOGGER.info("[在线时间] 配置文件未找到,正在创建默认配置"); // 使用英文日志,或者也本地化日志?这里先用英文
|
PlayerTimeMod.LOGGER.info("[在线时间] 配置文件未找到,正在创建默认配置");
|
||||||
saveConfig();
|
saveConfig();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -35,6 +36,9 @@ public class ModConfig {
|
|||||||
|
|
||||||
if (json.has("webPort")) {
|
if (json.has("webPort")) {
|
||||||
webPort = json.get("webPort").getAsInt();
|
webPort = json.get("webPort").getAsInt();
|
||||||
|
} else {
|
||||||
|
PlayerTimeMod.LOGGER.info("[在线时间] 配置文件缺少“webPort”字段,添加默认值'%s'并保存", webPort);
|
||||||
|
saveConfig();
|
||||||
}
|
}
|
||||||
if (json.has("language")) {
|
if (json.has("language")) {
|
||||||
language = json.get("language").getAsString();
|
language = json.get("language").getAsString();
|
||||||
@ -42,6 +46,12 @@ public class ModConfig {
|
|||||||
PlayerTimeMod.LOGGER.info("[在线时间] 配置文件缺少“language”字段,添加默认值'%s'并保存", language);
|
PlayerTimeMod.LOGGER.info("[在线时间] 配置文件缺少“language”字段,添加默认值'%s'并保存", language);
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
|
if (json.has("autoSaveSeconds")) {
|
||||||
|
autoSaveSeconds = json.get("autoSaveSeconds").getAsLong();
|
||||||
|
} else {
|
||||||
|
PlayerTimeMod.LOGGER.info("[在线时间] 配置文件缺少“autoSaveSeconds”字段,添加默认值'%s'并保存", autoSaveSeconds);
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
PlayerTimeMod.LOGGER.error("[在线时间] 加载配置文件失败,使用默认配置", e);
|
PlayerTimeMod.LOGGER.error("[在线时间] 加载配置文件失败,使用默认配置", e);
|
||||||
@ -54,6 +64,7 @@ public class ModConfig {
|
|||||||
JsonObject json = new JsonObject();
|
JsonObject json = new JsonObject();
|
||||||
json.addProperty("webPort", webPort);
|
json.addProperty("webPort", webPort);
|
||||||
json.addProperty("language", language);
|
json.addProperty("language", language);
|
||||||
|
json.addProperty("autoSaveSeconds", autoSaveSeconds);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(configPath.getParent());
|
Files.createDirectories(configPath.getParent());
|
||||||
@ -75,4 +86,9 @@ public class ModConfig {
|
|||||||
public String getLanguage() {
|
public String getLanguage() {
|
||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取保存间隔
|
||||||
|
public long getSeconds() {
|
||||||
|
return autoSaveSeconds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,13 +35,13 @@ public class PlayerTimeMod implements ModInitializer {
|
|||||||
// TODO 定时保存配置文件没整,暂时硬编码
|
// TODO 定时保存配置文件没整,暂时硬编码
|
||||||
private ScheduledExecutorService scheduler;
|
private ScheduledExecutorService scheduler;
|
||||||
private ScheduledFuture<?> saveTask;
|
private ScheduledFuture<?> saveTask;
|
||||||
private static final long AUTO_SAVE_INTERVAL_SECONDS = 5 * 60;
|
private static long AUTO_SAVE_INTERVAL_SECONDS;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
|
|
||||||
config = new ModConfig(FabricLoader.getInstance().getConfigDir());
|
config = new ModConfig(FabricLoader.getInstance().getConfigDir());
|
||||||
localizationManager = new LocalizationManager(config.getLanguage()); // 初始化本地化管理器
|
localizationManager = new LocalizationManager(config.getLanguage());
|
||||||
|
|
||||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
@ -49,6 +49,8 @@ public class PlayerTimeMod implements ModInitializer {
|
|||||||
try {
|
try {
|
||||||
LOGGER.info("[在线时间] 初始化 玩家在线时长视奸Mod");
|
LOGGER.info("[在线时间] 初始化 玩家在线时长视奸Mod");
|
||||||
|
|
||||||
|
AUTO_SAVE_INTERVAL_SECONDS = config.getSeconds();
|
||||||
|
|
||||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
|
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
|
||||||
timeTracker = new PlayerTimeTracker(server);
|
timeTracker = new PlayerTimeTracker(server);
|
||||||
try {
|
try {
|
||||||
@ -102,7 +104,7 @@ public class PlayerTimeMod implements ModInitializer {
|
|||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.error("[在线时间] 调度程序终止被中断", e);
|
LOGGER.error("[在线时间] 调度程序终止被中断", e);
|
||||||
Thread.currentThread().interrupt(); // Restore interrupted status
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
LOGGER.info("[在线时间] 调度程序关闭");
|
LOGGER.info("[在线时间] 调度程序关闭");
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// 1. 首先初始化所有变量
|
// 初始化变量
|
||||||
const tpsHistory = Array(30).fill(20);
|
const tpsHistory = Array(30).fill(20);
|
||||||
const msptHistory = Array(30).fill(50);
|
const msptHistory = Array(30).fill(50);
|
||||||
let memoryChart = null;
|
let memoryChart = null;
|
||||||
let performanceChart = null;
|
let performanceChart = null;
|
||||||
let lang = {}; // 新增:存储语言文件内容
|
let lang = {};
|
||||||
|
|
||||||
// 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'),
|
||||||
statsTableBody: document.getElementById('stats-table')?.getElementsByTagName('tbody')[0], // Changed ID for clarity
|
statsTableBody: document.getElementById('stats-table')?.getElementsByTagName('tbody')[0],
|
||||||
statsTableHeader: document.getElementById('stats-table')?.getElementsByTagName('thead')[0], // Added for header localization
|
statsTableHeader: document.getElementById('stats-table')?.getElementsByTagName('thead')[0],
|
||||||
whitelistPlayers: document.getElementById('whitelist-players'),
|
whitelistPlayers: document.getElementById('whitelist-players'),
|
||||||
nonWhitelistPlayers: document.getElementById('non-whitelist-players'),
|
nonWhitelistPlayers: document.getElementById('non-whitelist-players'),
|
||||||
totalCount: document.getElementById('total-count'),
|
totalCount: document.getElementById('total-count'),
|
||||||
@ -20,30 +20,27 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
memoryUsed: document.getElementById('memory-used'),
|
memoryUsed: document.getElementById('memory-used'),
|
||||||
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'), // This element seems unused in updateServerStatus now, can remove if not needed
|
avgTick: document.getElementById('avg-tick'),
|
||||||
uptime: document.getElementById('uptime'),
|
uptime: document.getElementById('uptime'),
|
||||||
tpsValue: document.getElementById('tps-value'),
|
tpsValue: document.getElementById('tps-value'),
|
||||||
msptValue: document.getElementById('mspt-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. 初始加载语言文件,然后加载其他数据并初始化图表
|
// 加载语言
|
||||||
loadLanguage().then(() => {
|
loadLanguage().then(() => {
|
||||||
translatePage(); // 本地化页面静态文本
|
translatePage();
|
||||||
initCharts(); // 初始化图表(需要语言文本)
|
initCharts();
|
||||||
loadAllData(); // 加载动态数据
|
loadAllData();
|
||||||
// 7. 设置定时刷新(10秒)
|
// 定时刷新
|
||||||
const refreshInterval = setInterval(loadAllData, 10000);
|
const refreshInterval = setInterval(loadAllData, 10000);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('Failed to load language or initial data:', error);
|
console.error('Failed to load language or initial data:', error);
|
||||||
showError('Failed to load language or initial data.'); // Fallback error message
|
showError('Failed to load language or initial data.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 5. 事件监听器
|
|
||||||
if (elements.themeToggle) {
|
if (elements.themeToggle) {
|
||||||
elements.themeToggle.addEventListener('click', toggleTheme);
|
elements.themeToggle.addEventListener('click', toggleTheme);
|
||||||
}
|
}
|
||||||
@ -52,9 +49,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
elements.refreshBtn.addEventListener('click', handleRefresh);
|
elements.refreshBtn.addEventListener('click', handleRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 功能函数 ***/
|
|
||||||
|
|
||||||
// 新增:加载语言文件
|
// 后端加载语言
|
||||||
async function loadLanguage() {
|
async function loadLanguage() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/lang');
|
const response = await fetch('/api/lang');
|
||||||
@ -63,18 +59,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.log('Language file loaded.');
|
console.log('Language file loaded.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load language file:', error);
|
console.error('Failed to load language file:', error);
|
||||||
// Fallback to English keys if language file fails to load
|
|
||||||
lang = {};
|
lang = {};
|
||||||
throw error; // Propagate error to stop further loading if language is critical
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:根据data-lang-key属性本地化页面元素
|
|
||||||
function translatePage() {
|
function translatePage() {
|
||||||
document.querySelectorAll('[data-lang-key]').forEach(element => {
|
document.querySelectorAll('[data-lang-key]').forEach(element => {
|
||||||
const key = element.getAttribute('data-lang-key');
|
const key = element.getAttribute('data-lang-key');
|
||||||
if (lang[key]) {
|
if (lang[key]) {
|
||||||
// Special handling for title tag
|
|
||||||
if (element.tagName === 'TITLE') {
|
if (element.tagName === 'TITLE') {
|
||||||
document.title = lang[key];
|
document.title = lang[key];
|
||||||
} else {
|
} else {
|
||||||
@ -86,11 +79,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:获取本地化字符串
|
|
||||||
function getLangString(key, ...args) {
|
function getLangString(key, ...args) {
|
||||||
const pattern = lang[key] || key; // Use key as fallback
|
const pattern = lang[key] || key;
|
||||||
try {
|
try {
|
||||||
// Simple placeholder replacement {0}, {1}, etc.
|
|
||||||
let result = pattern;
|
let result = pattern;
|
||||||
args.forEach((arg, index) => {
|
args.forEach((arg, index) => {
|
||||||
result = result.replace(new RegExp('\\{' + index + '\\}', 'g'), arg);
|
result = result.replace(new RegExp('\\{' + index + '\\}', 'g'), arg);
|
||||||
@ -98,19 +89,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to format string for key: ${key}`, e);
|
console.error(`Failed to format string for key: ${key}`, e);
|
||||||
return pattern; // Return unformatted pattern on error
|
return pattern;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 图表
|
||||||
function initCharts() {
|
function initCharts() {
|
||||||
// 内存图表
|
|
||||||
const memoryCtx = document.getElementById('memory-chart')?.getContext('2d');
|
const memoryCtx = document.getElementById('memory-chart')?.getContext('2d');
|
||||||
if (memoryCtx) {
|
if (memoryCtx) {
|
||||||
memoryChart = new Chart(memoryCtx, {
|
memoryChart = new Chart(memoryCtx, {
|
||||||
type: 'doughnut',
|
type: 'doughnut',
|
||||||
data: {
|
data: {
|
||||||
// Use localized labels
|
|
||||||
labels: [getLangString('playertime.web.chart.memory_used'), getLangString('playertime.web.chart.memory_free')],
|
labels: [getLangString('playertime.web.chart.memory_used'), getLangString('playertime.web.chart.memory_free')],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
data: [0, 100],
|
data: [0, 100],
|
||||||
@ -129,7 +119,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 性能图表
|
|
||||||
const perfCtx = document.getElementById('performance-chart')?.getContext('2d');
|
const perfCtx = document.getElementById('performance-chart')?.getContext('2d');
|
||||||
if (perfCtx) {
|
if (perfCtx) {
|
||||||
performanceChart = new Chart(perfCtx, {
|
performanceChart = new Chart(perfCtx, {
|
||||||
@ -138,7 +127,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
labels: Array(30).fill(''),
|
labels: Array(30).fill(''),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
// Use localized labels
|
|
||||||
label: getLangString('playertime.web.chart.tps'),
|
label: getLangString('playertime.web.chart.tps'),
|
||||||
data: tpsHistory,
|
data: tpsHistory,
|
||||||
borderColor: '#4CAF50',
|
borderColor: '#4CAF50',
|
||||||
@ -150,7 +138,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
yAxisID: 'y'
|
yAxisID: 'y'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Use localized labels
|
|
||||||
label: getLangString('playertime.web.chart.mspt'),
|
label: getLangString('playertime.web.chart.mspt'),
|
||||||
data: msptHistory,
|
data: msptHistory,
|
||||||
borderColor: '#FF5722',
|
borderColor: '#FF5722',
|
||||||
@ -203,7 +190,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
position: 'left',
|
position: 'left',
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
// Use localized labels
|
|
||||||
text: getLangString('playertime.web.chart.tps'),
|
text: getLangString('playertime.web.chart.tps'),
|
||||||
font: {
|
font: {
|
||||||
weight: 'bold'
|
weight: 'bold'
|
||||||
@ -221,7 +207,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
position: 'right',
|
position: 'right',
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
// Use localized labels
|
|
||||||
text: getLangString('playertime.web.chart.mspt'),
|
text: getLangString('playertime.web.chart.mspt'),
|
||||||
font: {
|
font: {
|
||||||
weight: 'bold'
|
weight: 'bold'
|
||||||
@ -246,10 +231,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleRefresh() {
|
function handleRefresh() {
|
||||||
if (this.classList.contains('loading')) return; // Prevent multiple clicks
|
if (this.classList.contains('loading')) return;
|
||||||
this.classList.add('loading');
|
this.classList.add('loading');
|
||||||
loadAllData().finally(() => { // Use finally to ensure loading class is removed
|
loadAllData().finally(() => {
|
||||||
setTimeout(() => { // Add a small delay for visual feedback
|
setTimeout(() => {
|
||||||
this.classList.remove('loading');
|
this.classList.remove('loading');
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
@ -257,7 +242,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
async function loadAllData() {
|
async function loadAllData() {
|
||||||
try {
|
try {
|
||||||
// Fetch data concurrently
|
|
||||||
const [statsData, onlinePlayersData, playerCountsData, serverStatusData] = await Promise.all([
|
const [statsData, onlinePlayersData, playerCountsData, serverStatusData] = await Promise.all([
|
||||||
fetchData('/api/stats'),
|
fetchData('/api/stats'),
|
||||||
fetchData('/api/online-players'),
|
fetchData('/api/online-players'),
|
||||||
@ -265,7 +249,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
fetchData('/api/server-status')
|
fetchData('/api/server-status')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Update UI with fetched data
|
|
||||||
updateTable(statsData);
|
updateTable(statsData);
|
||||||
updateOnlinePlayers(onlinePlayersData);
|
updateOnlinePlayers(onlinePlayersData);
|
||||||
updatePlayerCounts(playerCountsData);
|
updatePlayerCounts(playerCountsData);
|
||||||
@ -273,14 +256,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载数据出错:', error);
|
console.error('加载数据出错:', error);
|
||||||
showError(getLangString('playertime.web.error.load_failed')); // Use localized error message
|
showError(getLangString('playertime.web.error.load_failed'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchData(url) {
|
async function fetchData(url) {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// Attempt to read error body if available
|
|
||||||
const errorBody = await response.text().catch(() => 'Unknown Error');
|
const errorBody = await response.text().catch(() => 'Unknown Error');
|
||||||
throw new Error(`HTTP error! status: ${response.status}, URL: ${url}, Body: ${errorBody}`);
|
throw new Error(`HTTP error! status: ${response.status}, URL: ${url}, Body: ${errorBody}`);
|
||||||
}
|
}
|
||||||
@ -290,9 +272,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
function updateTable(data) {
|
function updateTable(data) {
|
||||||
if (!elements.statsTableBody || !elements.statsTableHeader) return;
|
if (!elements.statsTableBody || !elements.statsTableHeader) return;
|
||||||
|
|
||||||
elements.statsTableBody.innerHTML = ''; // Clear existing rows
|
elements.statsTableBody.innerHTML = '';
|
||||||
|
|
||||||
// Update table headers using localization
|
|
||||||
const headers = elements.statsTableHeader.querySelectorAll('th[data-lang-key]');
|
const headers = elements.statsTableHeader.querySelectorAll('th[data-lang-key]');
|
||||||
headers.forEach(th => {
|
headers.forEach(th => {
|
||||||
const key = th.getAttribute('data-lang-key');
|
const key = th.getAttribute('data-lang-key');
|
||||||
@ -304,25 +284,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
const sortedPlayers = Object.entries(data)
|
const sortedPlayers = Object.entries(data)
|
||||||
.map(([name, statString]) => {
|
.map(([name, statString]) => {
|
||||||
// Parse the localized stat string
|
|
||||||
const stats = {};
|
const stats = {};
|
||||||
// Split by " | " first
|
|
||||||
statString.split(" | ").forEach(part => {
|
statString.split(" | ").forEach(part => {
|
||||||
// Find the first colon to split label and value
|
|
||||||
const firstColonIndex = part.indexOf(':');
|
const firstColonIndex = part.indexOf(':');
|
||||||
if (firstColonIndex > 0) {
|
if (firstColonIndex > 0) {
|
||||||
const label = part.substring(0, firstColonIndex).trim();
|
const label = part.substring(0, firstColonIndex).trim();
|
||||||
const value = part.substring(firstColonIndex + 1).trim();
|
const value = part.substring(firstColonIndex + 1).trim();
|
||||||
stats[label] = value;
|
stats[label] = value;
|
||||||
} else {
|
} else {
|
||||||
// Handle cases without colon if necessary, or log a warning
|
|
||||||
console.warn(`Could not parse stat part: ${part}`);
|
console.warn(`Could not parse stat part: ${part}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Map localized labels back to internal keys for sorting
|
|
||||||
const internalStats = {};
|
const internalStats = {};
|
||||||
// This mapping assumes the order in the format string is consistent
|
|
||||||
const formatParts = getLangString('playertime.stats.format').split(" | ").map(p => p.split(":")[0].trim());
|
const formatParts = getLangString('playertime.stats.format').split(" | ").map(p => p.split(":")[0].trim());
|
||||||
|
|
||||||
if (formatParts.length >= 3) {
|
if (formatParts.length >= 3) {
|
||||||
@ -331,7 +305,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
internalStats['7Days'] = stats[formatParts[2]];
|
internalStats['7Days'] = stats[formatParts[2]];
|
||||||
} else {
|
} else {
|
||||||
console.error("Unexpected format string structure:", getLangString('playertime.stats.format'));
|
console.error("Unexpected format string structure:", getLangString('playertime.stats.format'));
|
||||||
// Fallback to using the raw statString if parsing fails
|
|
||||||
internalStats.totalTime = statString.split(" | ")[0]?.split(": ")[1] || "0h 00m";
|
internalStats.totalTime = statString.split(" | ")[0]?.split(": ")[1] || "0h 00m";
|
||||||
internalStats['30Days'] = statString.split(" | ")[1]?.split(": ")[1] || "0h 00m";
|
internalStats['30Days'] = statString.split(" | ")[1]?.split(": ")[1] || "0h 00m";
|
||||||
internalStats['7Days'] = statString.split(" | ")[2]?.split(": ")[1] || "0h 00m";
|
internalStats['7Days'] = statString.split(" | ")[2]?.split(": ")[1] || "0h 00m";
|
||||||
@ -345,8 +318,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (sortedPlayers.length === 0) {
|
if (sortedPlayers.length === 0) {
|
||||||
const row = elements.statsTableBody.insertRow();
|
const row = elements.statsTableBody.insertRow();
|
||||||
const cell = row.insertCell(0);
|
const cell = row.insertCell(0);
|
||||||
cell.colSpan = 4; // Span across all columns
|
cell.colSpan = 4;
|
||||||
cell.textContent = getLangString('playertime.web.stats_table.empty'); // Use localized empty message
|
cell.textContent = getLangString('playertime.web.stats_table.empty');
|
||||||
cell.style.textAlign = 'center';
|
cell.style.textAlign = 'center';
|
||||||
cell.style.fontStyle = 'italic';
|
cell.style.fontStyle = 'italic';
|
||||||
return;
|
return;
|
||||||
@ -374,7 +347,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
element.appendChild(li);
|
element.appendChild(li);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Use localized empty message
|
|
||||||
element.innerHTML = `<li>${getLangString(emptyMessageKey)}</li>`;
|
element.innerHTML = `<li>${getLangString(emptyMessageKey)}</li>`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -390,7 +362,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateServerStatus(data) {
|
function updateServerStatus(data) {
|
||||||
// Update memory info
|
|
||||||
if (data.memory) {
|
if (data.memory) {
|
||||||
const usedMB = Math.round(data.memory.used / (1024 * 1024));
|
const usedMB = Math.round(data.memory.used / (1024 * 1024));
|
||||||
const freeMB = Math.round((data.memory.max - data.memory.used) / (1024 * 1024));
|
const freeMB = Math.round((data.memory.max - data.memory.used) / (1024 * 1024));
|
||||||
@ -398,7 +369,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
if (memoryChart) {
|
if (memoryChart) {
|
||||||
memoryChart.data.datasets[0].data = [usedMB, freeMB];
|
memoryChart.data.datasets[0].data = [usedMB, freeMB];
|
||||||
// Update chart labels if they weren't set during initCharts (e.g., if lang loaded later)
|
|
||||||
memoryChart.data.labels = [getLangString('playertime.web.chart.memory_used'), getLangString('playertime.web.chart.memory_free')];
|
memoryChart.data.labels = [getLangString('playertime.web.chart.memory_used'), getLangString('playertime.web.chart.memory_free')];
|
||||||
memoryChart.update();
|
memoryChart.update();
|
||||||
}
|
}
|
||||||
@ -408,60 +378,49 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (elements.memoryPercent) elements.memoryPercent.textContent = percent;
|
if (elements.memoryPercent) elements.memoryPercent.textContent = percent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update performance info
|
|
||||||
if (data.server) {
|
if (data.server) {
|
||||||
// Calculate TPS (limit between 0-20)
|
|
||||||
const tps = Math.min(20, 1000 / (data.server.average_tick_time_ms || 50)).toFixed(1);
|
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);
|
const mspt = data.server.average_tick_time_ms.toFixed(1);
|
||||||
|
|
||||||
// Update display
|
|
||||||
if (elements.tpsValue) elements.tpsValue.textContent = tps;
|
if (elements.tpsValue) elements.tpsValue.textContent = tps;
|
||||||
if (elements.msptValue) elements.msptValue.textContent = mspt;
|
if (elements.msptValue) elements.msptValue.textContent = mspt;
|
||||||
|
|
||||||
// Update chart data
|
|
||||||
if (performanceChart) {
|
if (performanceChart) {
|
||||||
// Update history data
|
|
||||||
tpsHistory.shift();
|
tpsHistory.shift();
|
||||||
tpsHistory.push(parseFloat(tps));
|
tpsHistory.push(parseFloat(tps));
|
||||||
msptHistory.shift();
|
msptHistory.shift();
|
||||||
msptHistory.push(parseFloat(mspt));
|
msptHistory.push(parseFloat(mspt));
|
||||||
|
|
||||||
// Update chart labels if they weren't set during initCharts
|
|
||||||
performanceChart.data.datasets[0].label = getLangString('playertime.web.chart.tps');
|
performanceChart.data.datasets[0].label = getLangString('playertime.web.chart.tps');
|
||||||
performanceChart.data.datasets[1].label = getLangString('playertime.web.chart.mspt');
|
performanceChart.data.datasets[1].label = getLangString('playertime.web.chart.mspt');
|
||||||
if (performanceChart.options.scales.y.title) performanceChart.options.scales.y.title.text = getLangString('playertime.web.chart.tps');
|
if (performanceChart.options.scales.y.title) performanceChart.options.scales.y.title.text = getLangString('playertime.web.chart.tps');
|
||||||
if (performanceChart.options.scales.y1.title) performanceChart.options.scales.y1.title.text = getLangString('playertime.web.chart.mspt');
|
if (performanceChart.options.scales.y1.title) performanceChart.options.scales.y1.title.text = getLangString('playertime.web.chart.mspt');
|
||||||
|
|
||||||
|
|
||||||
// Only update data (do not recreate chart)
|
|
||||||
performanceChart.data.datasets[0].data = tpsHistory;
|
performanceChart.data.datasets[0].data = tpsHistory;
|
||||||
performanceChart.data.datasets[1].data = msptHistory;
|
performanceChart.data.datasets[1].data = msptHistory;
|
||||||
|
|
||||||
// Smooth update
|
|
||||||
performanceChart.update('none');
|
performanceChart.update('none');
|
||||||
|
|
||||||
// Set color based on value
|
setMetricColor(elements.tpsValue, tps, 15, 10, false);
|
||||||
setMetricColor(elements.tpsValue, tps, 15, 10, false); // TPS: higher is better
|
setMetricColor(elements.msptValue, mspt, 50, 100, true);
|
||||||
setMetricColor(elements.msptValue, mspt, 50, 100, true); // MSPT: lower is better
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (elements.avgTick) elements.avgTick.textContent = data.server.average_tick_time_ms?.toFixed(2) || '0'; // This element seems unused
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements.uptime) elements.uptime.textContent = data.uptime_formatted || '0'; // uptime_formatted is already localized by server
|
if (elements.uptime) elements.uptime.textContent = data.uptime_formatted || '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTimeToSeconds(timeStr) {
|
function parseTimeToSeconds(timeStr) {
|
||||||
if (!timeStr) return 0;
|
if (!timeStr) return 0;
|
||||||
// Ensure parsing works even if labels are present, by only looking for h and m
|
|
||||||
const parts = timeStr.match(/(\d+h)?\s*(\d+m)?/);
|
const parts = timeStr.match(/(\d+h)?\s*(\d+m)?/);
|
||||||
let seconds = 0;
|
let seconds = 0;
|
||||||
if (parts) {
|
if (parts) {
|
||||||
if (parts[1]) { // hours part
|
if (parts[1]) { // hour
|
||||||
seconds += parseInt(parts[1].replace('h', '')) * 3600;
|
seconds += parseInt(parts[1].replace('h', '')) * 3600;
|
||||||
}
|
}
|
||||||
if (parts[2]) { // minutes part
|
if (parts[2]) { // min
|
||||||
seconds += parseInt(parts[2].replace('m', '')) * 60;
|
seconds += parseInt(parts[2].replace('m', '')) * 60;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -480,18 +439,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
errorEl.classList.remove('show');
|
errorEl.classList.remove('show');
|
||||||
setTimeout(() => document.body.removeChild(errorEl), 300);
|
setTimeout(() => document.body.removeChild(errorEl), 300);
|
||||||
}, 5000); // Display for 5 seconds
|
}, 5000);
|
||||||
}, 10); // Small delay to allow CSS transition
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to set color based on metric value and thresholds
|
|
||||||
function setMetricColor(element, value, goodThreshold, badThreshold, reverse = false) {
|
function setMetricColor(element, value, goodThreshold, badThreshold, reverse = false) {
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
const numValue = parseFloat(value);
|
const numValue = parseFloat(value);
|
||||||
let color = '';
|
let color = '';
|
||||||
|
|
||||||
if (reverse) { // For MSPT: lower is better
|
if (reverse) {
|
||||||
if (numValue <= goodThreshold) {
|
if (numValue <= goodThreshold) {
|
||||||
color = '#4CAF50'; // Green
|
color = '#4CAF50'; // Green
|
||||||
} else if (numValue <= badThreshold) {
|
} else if (numValue <= badThreshold) {
|
||||||
@ -499,7 +457,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
} else {
|
} else {
|
||||||
color = '#FF5722'; // Red
|
color = '#FF5722'; // Red
|
||||||
}
|
}
|
||||||
} else { // For TPS: higher is better
|
} else {
|
||||||
if (numValue >= goodThreshold) {
|
if (numValue >= goodThreshold) {
|
||||||
color = '#4CAF50'; // Green
|
color = '#4CAF50'; // Green
|
||||||
} else if (numValue >= badThreshold) {
|
} else if (numValue >= badThreshold) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"webPort": 60048,
|
"webPort": 60048,
|
||||||
"language": "zh_cn"
|
"language": "zh_cn",
|
||||||
|
"autoSaveSeconds": 60
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user