v1.0.14.4 — enable adaptive proxy health monitoring in JFX, show per-service direct/proxy status in settings UI

This commit is contained in:
SashegDev
2026-07-31 12:04:08 +00:00
parent 7235017493
commit c51d441743
6 changed files with 109 additions and 3 deletions
@@ -474,6 +474,7 @@ public class JFXLauncher extends Application {
server.createContext("/api/launch", this::handleLaunch);
server.createContext("/api/install", this::handleInstall);
server.createContext("/api/install/progress", this::handleInstallProgress);
server.createContext("/api/network/status", this::handleNetworkStatus);
server.createContext("/api/logs", this::handleLogs);
server.createContext("/api/logs/stream", this::handleLogsStream);
server.createContext("/api/game-logs", this::handleGameLogs);
@@ -842,6 +843,14 @@ public class JFXLauncher extends Application {
}
}
private void handleNetworkStatus(HttpExchange exchange) {
try {
sendJson(exchange, Map.of("success", true, "data", ZHttpClient.getNetworkStatus()));
} catch (Exception e) {
sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
}
}
private void handleLogs(HttpExchange exchange) {
sendJson(exchange, Map.of("success", true, "data", getLauncherLogs()));
}
@@ -15,6 +15,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -34,6 +35,7 @@ public class ZHttpClient {
private static final AtomicBoolean useProxyMode = new AtomicBoolean(false);
private static final AtomicBoolean proxyTested = new AtomicBoolean(false);
private static final AtomicBoolean healthThreadStarted = new AtomicBoolean(false);
public static void setBaseUrl(String url) {
BASE_URL = url;
@@ -137,8 +139,8 @@ public class ZHttpClient {
}
proxyTested.set(true);
startHealthCheckThread();
if (verbose) {
startHealthCheckThread();
printStats();
}
}
@@ -192,6 +194,8 @@ public class ZHttpClient {
}
private static void startHealthCheckThread() {
if (!healthThreadStarted.compareAndSet(false, true)) return;
Thread healthThread = new Thread(() -> {
while (true) {
try {
@@ -575,6 +579,29 @@ public class ZHttpClient {
return useProxyMode.get();
}
public static Map<String, Object> getNetworkStatus() {
Map<String, Object> status = new HashMap<>();
status.put("initialized", proxyTested.get());
status.put("globalProxy", useProxyMode.get());
status.put("directSuccess", directSuccessCount);
status.put("directFail", directFailCount);
status.put("proxySuccess", proxySuccessCount);
List<Map<String, Object>> services = new ArrayList<>();
for (ServiceType type : ServiceType.values()) {
if (type.isAlwaysDirect()) continue;
Map<String, Object> s = new HashMap<>();
s.put("name", type.name());
s.put("baseUrl", type.getBaseUrl());
s.put("healthy", serviceHealthy.getOrDefault(type, false));
s.put("proxy", serviceProxyMode.getOrDefault(type, false));
s.put("fails", serviceFailCount.getOrDefault(type, 0));
services.add(s);
}
status.put("services", services);
return status;
}
public static void printStats() {
System.out.println(ZAnsi.cyan("\n=== Network Stats ==="));
System.out.println(ZAnsi.white("Global proxy: ") + (useProxyMode.get() ? "ON" : "OFF"));
@@ -438,6 +438,15 @@
<span class="setting-badge" id="server-status">Checking...</span>
</div>
</div>
<div class="setting-card" id="network-card">
<div class="setting-info">
<h4 data-i18n="settings.network.title">Network</h4>
<p data-i18n="settings.network.desc">Per-service connection mode (direct / via proxy)</p>
</div>
<div class="setting-network-list" id="network-list">
<span class="setting-badge" data-i18n="settings.network.loading">Checking...</span>
</div>
</div>
<div class="setting-card">
<div class="setting-info">
<h4 data-i18n="settings.language.title">Language</h4>
+50 -1
View File
@@ -38,6 +38,13 @@ const LOCALES = {
'settings.server.checking': 'Checking...',
'settings.server.connected': 'Connected',
'settings.server.disconnected': 'Disconnected',
'settings.network.title': 'Network',
'settings.network.desc': 'Per-service connection mode (direct / via proxy)',
'settings.network.direct': 'Direct',
'settings.network.proxy': 'Proxy',
'settings.network.down': 'Unavailable',
'settings.network.loading': 'Checking...',
'settings.network.unavailable': 'Not available',
'settings.language.title': 'Language',
'settings.language.desc': 'Interface language',
'settings.systemJvm.title': 'System-based JVM Optimization',
@@ -222,6 +229,13 @@ const LOCALES = {
'settings.server.checking': 'Проверка...',
'settings.server.connected': 'Подключено',
'settings.server.disconnected': 'Отключено',
'settings.network.title': 'Сеть',
'settings.network.desc': 'Режим подключения по сервисам (напрямую / через прокси)',
'settings.network.direct': 'Напрямую',
'settings.network.proxy': 'Прокси',
'settings.network.down': 'Недоступен',
'settings.network.loading': 'Проверка...',
'settings.network.unavailable': 'Недоступно',
'settings.language.title': 'Язык',
'settings.language.desc': 'Язык интерфейса',
'settings.systemJvm.title': 'Системная оптимизация JVM',
@@ -759,6 +773,40 @@ class ZernMCLauncher {
switchView(view) {
document.querySelectorAll('.nav-btn').forEach(b => b.classList.toggle('active', b.dataset.view === view));
document.querySelectorAll('.view').forEach(v => v.classList.toggle('active', v.id === 'view-' + view));
if (view === 'settings') {
this.loadNetworkStatus();
if (!this.networkPoller) {
this.networkPoller = setInterval(() => this.loadNetworkStatus(), 15000);
}
} else if (this.networkPoller) {
clearInterval(this.networkPoller);
this.networkPoller = null;
}
}
async loadNetworkStatus() {
const list = document.getElementById('network-list');
if (!list) return;
try {
const r = await this.req('/network/status');
if (!r.success || !r.data) return;
const services = r.data.services || [];
if (!services.length) {
list.innerHTML = '<span class="setting-badge">' + t('settings.network.unavailable') + '</span>';
return;
}
let html = '';
services.forEach(function(s) {
const state = s.proxy ? 'proxy' : (s.healthy ? 'direct' : 'down');
const labels = { direct: t('settings.network.direct'), proxy: t('settings.network.proxy'), down: t('settings.network.down') };
const colors = { direct: 'var(--success)', proxy: 'var(--warning)', down: 'var(--error)' };
html += '<div class="network-row">'
+ '<span class="network-name">' + s.name.toLowerCase() + '</span>'
+ '<span class="network-state" style="color:' + colors[state] + '">' + labels[state] + '</span>'
+ '</div>';
});
list.innerHTML = html;
} catch (e) {}
}
// ==================== INSTANCES ====================
@@ -1860,7 +1908,8 @@ class ZernMCLauncher {
stopProgressPoll() {
if (this.progressPoller) {
clearInterval(this.progressPoller);
this.progressPoller = null;
this.progressPoller = null;
this.networkPoller = null;
}
}
@@ -527,6 +527,18 @@ body {
background: var(--bg-surface); color: var(--text-secondary); border: 1px solid var(--border-light);
}
.setting-pass { display: flex; align-items: center; gap: 8px; }
.setting-network-list {
display: flex; flex-direction: column; gap: 6px; min-width: 260px; flex-shrink: 0;
}
.network-row {
display: flex; align-items: center; justify-content: space-between; gap: 12px;
font-size: 12px; padding: 4px 10px; border-radius: 4px;
background: var(--bg-surface); border: 1px solid var(--border-light);
}
.network-name {
font-family: var(--mono); color: var(--text-secondary); text-transform: uppercase;
}
.network-state { font-weight: 600; }
.pass-input {
width: 160px; padding: 6px 12px; border-radius: var(--radius-sm);
background: var(--bg-inset); border: 1px solid var(--border-light);
+1 -1
View File
@@ -19,7 +19,7 @@
<properties>
<revision>1.0.14</revision>
<hotfix>3</hotfix>
<hotfix>4</hotfix>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>