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:
@@ -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);
|
||||
if (verbose) {
|
||||
startHealthCheckThread();
|
||||
if (verbose) {
|
||||
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>
|
||||
|
||||
@@ -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 ====================
|
||||
@@ -1861,6 +1909,7 @@ class ZernMCLauncher {
|
||||
if (this.progressPoller) {
|
||||
clearInterval(this.progressPoller);
|
||||
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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user