forked from KNEMC/KLALB
fix(config): sync Swing settings after Web updates
Add revisioned configuration events and detached commits so Web changes refresh clean Swing forms without overwriting unsaved local edits. Preserve configuration formatting and document the synchronized update workflow.
This commit is contained in:
@@ -580,7 +580,63 @@ public class KLALBController {
|
||||
|
||||
private KLALBRoutingProtocolAPIClient apiClient;
|
||||
|
||||
private KLALBControllerConfigItem configItem;
|
||||
private volatile KLALBControllerConfigItem configItem;
|
||||
|
||||
private static <T> List<T> copyConfigList(List<T> values) {
|
||||
return values == null ? null : new ArrayList<T>(values);
|
||||
}
|
||||
|
||||
private static KLALBControllerConfigItem copyConfigItem(KLALBControllerConfigItem source) {
|
||||
KLALBControllerConfigItem copy = new KLALBControllerConfigItem();
|
||||
copy.setLanguage(source.getLanguage());
|
||||
copy.setNogui(source.isNogui());
|
||||
copy.setVirtualAddress(source.getVirtualAddress());
|
||||
copy.setVirtualASN(source.getVirtualASN());
|
||||
copy.setDNS(copyConfigList(source.getDNS()));
|
||||
copy.setTCPListen(source.getTCPListen());
|
||||
copy.setUDPListen(source.getUDPListen());
|
||||
copy.setVirtualSocketName(source.getVirtualSocketName());
|
||||
copy.setExternalEndpoints(copyConfigList(source.getExternalEndpoints()));
|
||||
copy.setAutoConnections(copyConfigList(source.getAutoConnections()));
|
||||
copy.setNtpServers(copyConfigList(source.getNtpServers()));
|
||||
copy.setExtraRoutes(copyConfigList(source.getExtraRoutes()));
|
||||
copy.setDenyExternalEndpointQuery(source.isDenyExternalEndpointQuery());
|
||||
copy.setDenyExternalEndpointBroadcast(source.isDenyExternalEndpointBroadcast());
|
||||
copy.setCongestionAlgorithm(source.getCongestionAlgorithm());
|
||||
copy.setBurstLimit(source.getBurstLimit());
|
||||
copy.setDelayUpperBound(source.getDelayUpperBound());
|
||||
copy.setDelayLowerBound(source.getDelayLowerBound());
|
||||
copy.setNagleDelayTime(source.getNagleDelayTime());
|
||||
copy.setLinkNagleDelayTime(source.getLinkNagleDelayTime());
|
||||
copy.setLinkConnectionsCount(source.getLinkConnectionsCount());
|
||||
copy.setEnableTUN(source.isEnableTUN());
|
||||
copy.setTUNName(source.getTUNName());
|
||||
copy.setPerformanceStrategy(source.getPerformanceStrategy());
|
||||
copy.setDeviceName(source.getDeviceName());
|
||||
copy.setDeviceDescription(source.getDeviceDescription());
|
||||
copy.setNetworkInterfaceExcepts(copyConfigList(source.getNetworkInterfaceExcepts()));
|
||||
copy.setWebUI(source.isWebUI());
|
||||
copy.setWebListen(source.getWebListen());
|
||||
return copy;
|
||||
}
|
||||
|
||||
public void applyConfigItem(KLALBControllerConfigItem committedConfigItem) {
|
||||
if (committedConfigItem == null) {
|
||||
throw new IllegalArgumentException("Controller configuration is required");
|
||||
}
|
||||
KLALBControllerConfigItem detachedConfigItem = copyConfigItem(committedConfigItem);
|
||||
synchronized (externalEndpoints) {
|
||||
configItem = detachedConfigItem;
|
||||
if (srv6Router != null) {
|
||||
PerformanceStrategy strategy = PerformanceStrategy.fromDescription(detachedConfigItem.getPerformanceStrategy());
|
||||
if (strategy != null) {
|
||||
srv6Router.setPerformanceStrategy(strategy);
|
||||
}
|
||||
}
|
||||
publishNodeInfoLocked(detachedConfigItem.getDeviceName(), detachedConfigItem.getDeviceDescription(),
|
||||
detachedConfigItem.getExternalEndpoints(), detachedConfigItem.getExtraRoutes());
|
||||
}
|
||||
}
|
||||
|
||||
public void addRemoteLines(List<MultiProtocolSocketAddress> select) {
|
||||
for (MultiProtocolSocketAddress target : select) {
|
||||
|
||||
@@ -4,11 +4,21 @@ import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetAddress;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashSet;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.kne.cloud.network.*;
|
||||
import org.kne.cloud.network.klalb.ui.KLALBStateGUI3;
|
||||
@@ -19,8 +29,9 @@ import org.kne.cloud.network.klalb.web.KLALBWebServer;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
@@ -29,13 +40,82 @@ import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
public class KLALBProxySystem {
|
||||
private Set<Proxy> proxys=new HashSet<>();
|
||||
private KLALBController klalbController;
|
||||
private KLALBWebServer webServer;
|
||||
private KLALBConfig config;
|
||||
private Gson gson;
|
||||
private File jsonFile;
|
||||
public class KLALBProxySystem {
|
||||
public enum ConfigChangeSource {
|
||||
WEB, SWING
|
||||
}
|
||||
|
||||
public static final class ControllerConfigSnapshot {
|
||||
private final long revision;
|
||||
private final String controllerJson;
|
||||
|
||||
private ControllerConfigSnapshot(long revision, String controllerJson) {
|
||||
this.revision = revision;
|
||||
this.controllerJson = controllerJson;
|
||||
}
|
||||
|
||||
public long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public String getControllerJson() {
|
||||
return controllerJson;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ConfigChangeEvent {
|
||||
private final ControllerConfigSnapshot snapshot;
|
||||
private final ConfigChangeSource source;
|
||||
|
||||
private ConfigChangeEvent(ControllerConfigSnapshot snapshot, ConfigChangeSource source) {
|
||||
this.snapshot = snapshot;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public ControllerConfigSnapshot getSnapshot() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public ConfigChangeSource getSource() {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CommitResult {
|
||||
private final boolean success;
|
||||
private final long revision;
|
||||
private final ControllerConfigSnapshot snapshot;
|
||||
|
||||
private CommitResult(boolean success, long revision, ControllerConfigSnapshot snapshot) {
|
||||
this.success = success;
|
||||
this.revision = revision;
|
||||
this.snapshot = snapshot;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public long getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public ControllerConfigSnapshot getSnapshot() {
|
||||
return snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Proxy> proxys=new HashSet<>();
|
||||
private KLALBController klalbController;
|
||||
private KLALBWebServer webServer;
|
||||
private KLALBConfig config;
|
||||
private Gson gson;
|
||||
private File jsonFile;
|
||||
private final Object configLock = new Object();
|
||||
private long configRevision;
|
||||
private volatile ControllerConfigSnapshot controllerConfigSnapshot = new ControllerConfigSnapshot(0L, null);
|
||||
private final CopyOnWriteArraySet<Consumer<ConfigChangeEvent>> configChangeListeners = new CopyOnWriteArraySet<>();
|
||||
private JsonArray rawConfigJson;
|
||||
{
|
||||
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
||||
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||
@@ -141,13 +221,27 @@ public class KLALBProxySystem {
|
||||
public void loadConfigJson(Reader json) {
|
||||
loadConfigJson(new JsonParser().parse(json));
|
||||
}
|
||||
public void loadConfigJson(JsonElement json) {
|
||||
KLALBConfig config= gson.fromJson(json, KLALBConfig.class);
|
||||
loadConfig(config);
|
||||
}
|
||||
public void loadConfig(KLALBConfig config) {
|
||||
this.config=config;
|
||||
for(KLALBConfigItem item:config) {
|
||||
public void loadConfigJson(JsonElement json) {
|
||||
JsonElement rawJson = new JsonParser().parse(json.toString());
|
||||
KLALBConfig config= gson.fromJson(json, KLALBConfig.class);
|
||||
JsonArray rawArray = rawJson instanceof JsonArray ? (JsonArray) rawJson : null;
|
||||
synchronized (configLock) {
|
||||
installConfigLocked(config, rawArray);
|
||||
}
|
||||
}
|
||||
public void loadConfig(KLALBConfig config) {
|
||||
JsonElement rawJson = gson.toJsonTree(config);
|
||||
JsonArray rawArray = rawJson instanceof JsonArray ? (JsonArray) rawJson : null;
|
||||
synchronized (configLock) {
|
||||
installConfigLocked(config, rawArray);
|
||||
}
|
||||
}
|
||||
|
||||
private void installConfigLocked(KLALBConfig config, JsonArray rawArray) {
|
||||
this.config=config;
|
||||
this.configRevision=0L;
|
||||
rawConfigJson=rawArray;
|
||||
for(KLALBConfigItem item:config) {
|
||||
if(item instanceof KLALBControllerConfigItem) {
|
||||
KLALBControllerConfigItem kcci=(KLALBControllerConfigItem) item;
|
||||
String lstr=kcci.getLanguage();
|
||||
@@ -227,41 +321,180 @@ public class KLALBProxySystem {
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
controllerConfigSnapshot = createControllerConfigSnapshotLocked();
|
||||
}
|
||||
|
||||
|
||||
public void saveConfigToFile() {
|
||||
if (jsonFile != null && config != null) {
|
||||
String json = gson.toJson(config);
|
||||
try (FileWriter fw = new FileWriter(jsonFile)) {
|
||||
fw.write(json);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
public void saveConfigToFile() {
|
||||
synchronized (configLock) {
|
||||
if (jsonFile != null && config != null) {
|
||||
try {
|
||||
KLALBControllerConfigItem item = findControllerConfigItemLocked();
|
||||
if (item == null) {
|
||||
persistConfigJsonLocked(gson.toJson(config));
|
||||
} else {
|
||||
String controllerJson = gson.toJson(item);
|
||||
JsonArray completeConfig = createCompleteConfigJsonLocked(controllerJson);
|
||||
persistConfigJsonLocked(gson.toJson(completeConfig));
|
||||
rawConfigJson = completeConfig;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private KLALBControllerConfigItem findControllerConfigItemLocked() {
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
for (KLALBConfigItem item : config) {
|
||||
if (item instanceof KLALBControllerConfigItem) {
|
||||
return (KLALBControllerConfigItem) item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int findControllerConfigIndexLocked() {
|
||||
if (config == null) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < config.size(); i++) {
|
||||
if (config.get(i) instanceof KLALBControllerConfigItem) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private ControllerConfigSnapshot createControllerConfigSnapshotLocked() {
|
||||
KLALBControllerConfigItem item = findControllerConfigItemLocked();
|
||||
return new ControllerConfigSnapshot(configRevision, item == null ? null : gson.toJson(item));
|
||||
}
|
||||
|
||||
public ControllerConfigSnapshot getControllerConfigSnapshot() {
|
||||
synchronized (configLock) {
|
||||
controllerConfigSnapshot = createControllerConfigSnapshotLocked();
|
||||
return controllerConfigSnapshot;
|
||||
}
|
||||
}
|
||||
|
||||
public KLALBControllerConfigItem parseControllerConfigCandidate(ControllerConfigSnapshot snapshot) {
|
||||
if (snapshot == null || snapshot.getControllerJson() == null) {
|
||||
return null;
|
||||
}
|
||||
return gson.fromJson(snapshot.getControllerJson(), KLALBControllerConfigItem.class);
|
||||
}
|
||||
|
||||
private void persistConfigJsonLocked(String json) throws IOException {
|
||||
if (jsonFile == null) {
|
||||
return;
|
||||
}
|
||||
Path target = jsonFile.toPath().toAbsolutePath();
|
||||
Path parent = target.getParent();
|
||||
Path temporary = Files.createTempFile(parent, target.getFileName().toString(), ".tmp");
|
||||
try {
|
||||
Files.write(temporary, json.getBytes(StandardCharsets.UTF_8), StandardOpenOption.TRUNCATE_EXISTING,
|
||||
StandardOpenOption.WRITE);
|
||||
try {
|
||||
Files.move(temporary, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
Files.move(temporary, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
} finally {
|
||||
Files.deleteIfExists(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonArray createCompleteConfigJsonLocked(String controllerJson) throws IOException {
|
||||
JsonElement source = rawConfigJson == null ? gson.toJsonTree(config) : rawConfigJson;
|
||||
if (!(source instanceof JsonArray)) {
|
||||
throw new IOException("Configuration is not an array");
|
||||
}
|
||||
JsonArray sourceArray = (JsonArray) source;
|
||||
int index = findControllerConfigIndexLocked();
|
||||
if (index < 0 || index >= sourceArray.size() || sourceArray.size() != config.size()) {
|
||||
throw new IOException("Configuration item layout changed");
|
||||
}
|
||||
JsonArray mergedConfig = new JsonArray();
|
||||
JsonElement controllerElement = new JsonParser().parse(controllerJson);
|
||||
for (int i = 0; i < sourceArray.size(); i++) {
|
||||
mergedConfig.add(i == index ? controllerElement : sourceArray.get(i));
|
||||
}
|
||||
return mergedConfig;
|
||||
}
|
||||
|
||||
public void addConfigChangeListener(Consumer<ConfigChangeEvent> listener) {
|
||||
configChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeConfigChangeListener(Consumer<ConfigChangeEvent> listener) {
|
||||
configChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
public CommitResult commitControllerConfig(long expectedRevision, KLALBControllerConfigItem candidate,
|
||||
ConfigChangeSource source) throws IOException {
|
||||
if (candidate == null || source == null) {
|
||||
throw new IllegalArgumentException("Candidate and source are required");
|
||||
}
|
||||
ConfigChangeEvent event;
|
||||
CommitResult result;
|
||||
synchronized (configLock) {
|
||||
if (expectedRevision != configRevision) {
|
||||
ControllerConfigSnapshot currentSnapshot = createControllerConfigSnapshotLocked();
|
||||
controllerConfigSnapshot = currentSnapshot;
|
||||
return new CommitResult(false, configRevision, currentSnapshot);
|
||||
}
|
||||
int index = findControllerConfigIndexLocked();
|
||||
if (index < 0) {
|
||||
throw new IOException("Controller configuration is missing");
|
||||
}
|
||||
String candidateJson = gson.toJson(candidate);
|
||||
KLALBControllerConfigItem committedCandidate = gson.fromJson(candidateJson,
|
||||
KLALBControllerConfigItem.class);
|
||||
if (PerformanceStrategy.fromDescription(committedCandidate.getPerformanceStrategy()) == null) {
|
||||
throw new IllegalArgumentException("Unknown performanceStrategy: "
|
||||
+ committedCandidate.getPerformanceStrategy());
|
||||
}
|
||||
com.google.gson.JsonArray mergedConfig = createCompleteConfigJsonLocked(candidateJson);
|
||||
persistConfigJsonLocked(gson.toJson(mergedConfig));
|
||||
rawConfigJson = mergedConfig;
|
||||
|
||||
config.set(index, committedCandidate);
|
||||
if (klalbController != null) {
|
||||
klalbController.applyConfigItem(committedCandidate);
|
||||
}
|
||||
configRevision++;
|
||||
controllerConfigSnapshot = createControllerConfigSnapshotLocked();
|
||||
event = new ConfigChangeEvent(controllerConfigSnapshot, source);
|
||||
result = new CommitResult(true, configRevision, controllerConfigSnapshot);
|
||||
}
|
||||
for (Consumer<ConfigChangeEvent> listener : configChangeListeners) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private KLALBStateGUI3 kgui;
|
||||
public KLALBStateGUI3 getKLALBGUI() {
|
||||
if(kgui==null) {
|
||||
kgui=new KLALBStateGUI3(klalbController);
|
||||
kgui.loadConfig(config);
|
||||
kgui.setSaveComsumer((cfg)->{
|
||||
saveConfigToFile();
|
||||
});
|
||||
}
|
||||
public KLALBStateGUI3 getKLALBGUI() {
|
||||
if(kgui==null) {
|
||||
kgui=new KLALBStateGUI3(klalbController);
|
||||
kgui.bindConfigSystem(this);
|
||||
}
|
||||
return kgui;
|
||||
}
|
||||
public KLALBConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
public KLALBControllerConfigItem getControllerConfig() {
|
||||
for(KLALBConfigItem item:config) {
|
||||
if(item instanceof KLALBControllerConfigItem)
|
||||
return (KLALBControllerConfigItem) item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public KLALBControllerConfigItem getControllerConfig() {
|
||||
return parseControllerConfigCandidate(getControllerConfigSnapshot());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +104,82 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
private JCheckBox nogui;
|
||||
|
||||
// ==================== 配置和回调 ====================
|
||||
private KLALBConfig config; // 配置文件
|
||||
private Consumer<KLALBConfig> saveComsumer; // 保存配置的回调函数
|
||||
private KLALBConfig config; // 配置文件
|
||||
private Consumer<KLALBConfig> saveComsumer; // 保存配置的回调函数
|
||||
private KLALBProxySystem configSystem;
|
||||
private KLALBProxySystem.ControllerConfigSnapshot loadedSnapshot;
|
||||
private long loadedRevision;
|
||||
private SettingsFormState loadedFormState;
|
||||
private KLALBProxySystem.ControllerConfigSnapshot pendingExternalSnapshot;
|
||||
private long pendingExternalRevision;
|
||||
private boolean configSystemClosed;
|
||||
private long bindingGeneration;
|
||||
private Consumer<KLALBProxySystem.ConfigChangeEvent> boundConfigChangeListener;
|
||||
|
||||
private static final class SettingsFormState {
|
||||
private final String language, deviceName, deviceDescription, address, dns, extraRoutes;
|
||||
private final String asn, tunName, webListen, tcpListen, udpListen, openLines, connectLines, ntp;
|
||||
private final String performance, congestion;
|
||||
private final boolean enableTun, webApi, nogui, denyQuery, denyBroadcast;
|
||||
private final List<String> interfaces;
|
||||
private final int connections, burst, upper, lower, nagle, linkNagle;
|
||||
|
||||
private SettingsFormState(KLALBStateGUI3 gui) {
|
||||
Language languageItem = (Language) gui.comboLang.getSelectedItem();
|
||||
language = languageItem == null ? null : languageItem.name();
|
||||
deviceName = gui.deviceNameSet.getText();
|
||||
deviceDescription = gui.deviceDescriptionSet.getText();
|
||||
address = gui.addressFieldSet.getText();
|
||||
dns = gui.dnsAreaSet.getText();
|
||||
extraRoutes = gui.extraRoutesSet.getText();
|
||||
asn = gui.asnFieldSet.getText();
|
||||
tunName = gui.tunDeviceName.getText();
|
||||
webListen = gui.webListenSet.getText();
|
||||
tcpListen = gui.tcpListeningSet.getText();
|
||||
udpListen = gui.udpListeningSet.getText();
|
||||
openLines = gui.openLineTabelSet.getText();
|
||||
connectLines = gui.connectLineTabelSet.getText();
|
||||
ntp = gui.ntpServerSet.getText();
|
||||
PerformanceStrategyItem performanceItem = (PerformanceStrategyItem) gui.comboPerformance.getSelectedItem();
|
||||
performance = performanceItem == null ? null : performanceItem.getStrategy().toString();
|
||||
congestion = String.valueOf(gui.congestions.getComboBox().getSelectedItem());
|
||||
enableTun = gui.enableTUN.isSelected();
|
||||
webApi = gui.webApiEnabled.isSelected();
|
||||
nogui = gui.nogui.isSelected();
|
||||
denyQuery = gui.denyQuery.isSelected();
|
||||
denyBroadcast = gui.denyBroadcast.isSelected();
|
||||
interfaces = new ArrayList<>();
|
||||
for (int i = 0; i < gui.nilsimdl.getSize(); i++) interfaces.add(gui.nilsimdl.getElementAt(i).getName());
|
||||
connections = gui.linkConnectionsCount.getSlider().getValue();
|
||||
burst = gui.burstLimit.getSlider().getValue();
|
||||
upper = gui.delayHbound.getSlider().getValue();
|
||||
lower = gui.delayLbound.getSlider().getValue();
|
||||
nagle = gui.nagleDelayTime.getSlider().getValue();
|
||||
linkNagle = gui.linkNagleDelayTime.getSlider().getValue();
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object obj) {
|
||||
if (!(obj instanceof SettingsFormState)) return false;
|
||||
SettingsFormState o = (SettingsFormState) obj;
|
||||
return enableTun == o.enableTun && webApi == o.webApi && nogui == o.nogui
|
||||
&& denyQuery == o.denyQuery && denyBroadcast == o.denyBroadcast
|
||||
&& connections == o.connections && burst == o.burst && upper == o.upper
|
||||
&& lower == o.lower && nagle == o.nagle && linkNagle == o.linkNagle
|
||||
&& Objects.equals(language, o.language) && Objects.equals(deviceName, o.deviceName)
|
||||
&& Objects.equals(deviceDescription, o.deviceDescription) && Objects.equals(address, o.address)
|
||||
&& Objects.equals(dns, o.dns) && Objects.equals(extraRoutes, o.extraRoutes)
|
||||
&& Objects.equals(asn, o.asn) && Objects.equals(tunName, o.tunName)
|
||||
&& Objects.equals(webListen, o.webListen) && Objects.equals(tcpListen, o.tcpListen)
|
||||
&& Objects.equals(udpListen, o.udpListen) && Objects.equals(openLines, o.openLines)
|
||||
&& Objects.equals(connectLines, o.connectLines) && Objects.equals(ntp, o.ntp)
|
||||
&& Objects.equals(performance, o.performance) && Objects.equals(congestion, o.congestion)
|
||||
&& Objects.equals(interfaces, o.interfaces);
|
||||
}
|
||||
@Override public int hashCode() { return Objects.hash(language, deviceName, deviceDescription, address, dns,
|
||||
extraRoutes, asn, tunName, webListen, tcpListen, udpListen, openLines, connectLines, ntp,
|
||||
performance, congestion, enableTun, webApi, nogui, denyQuery, denyBroadcast, interfaces,
|
||||
connections, burst, upper, lower, nagle, linkNagle); }
|
||||
}
|
||||
|
||||
// ==================== 尺寸常量 ====================
|
||||
private Dimension dashSize = new Dimension((int) (145 * 0.7), (int) (165 * 0.7)); // 仪表盘尺寸
|
||||
@@ -1055,15 +1129,10 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
/**
|
||||
* 保存配置到文件
|
||||
*/
|
||||
private void saveConfig() {
|
||||
if (config == null) {
|
||||
config = new KLALBConfig();
|
||||
config.add(new KLALBControllerConfigItem());
|
||||
}
|
||||
|
||||
for (KLALBConfigItem item : config) {
|
||||
if (item instanceof KLALBControllerConfigItem) {
|
||||
KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item;
|
||||
private void saveConfig() {
|
||||
if (configSystem == null || loadedSnapshot == null) return;
|
||||
KLALBControllerConfigItem kck = configSystem.parseControllerConfigCandidate(loadedSnapshot);
|
||||
if (kck == null) return;
|
||||
String oldDeviceName = kck.getDeviceName();
|
||||
String oldDeviceDescription = kck.getDeviceDescription();
|
||||
List<MultiProtocolSocketAddress> oldExternalEndpoints = kck.getExternalEndpoints() == null
|
||||
@@ -1314,30 +1383,32 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
|
||||
kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L);
|
||||
|
||||
boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName);
|
||||
boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription);
|
||||
boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints);
|
||||
boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes);
|
||||
if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) {
|
||||
if (kcontroller != null) {
|
||||
kcontroller.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints,
|
||||
newExtraRoutes);
|
||||
} else {
|
||||
kck.setDeviceName(newDeviceName);
|
||||
kck.setDeviceDescription(newDeviceDescription);
|
||||
kck.setExternalEndpoints(newExternalEndpoints);
|
||||
kck.setExtraRoutes(newExtraRoutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调用保存回调
|
||||
if (saveComsumer != null) {
|
||||
saveComsumer.accept(config);
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("saveconfigsuccess"));
|
||||
}
|
||||
}
|
||||
kck.setDeviceName(newDeviceName);
|
||||
kck.setDeviceDescription(newDeviceDescription);
|
||||
kck.setExternalEndpoints(newExternalEndpoints);
|
||||
kck.setExtraRoutes(newExtraRoutes);
|
||||
try {
|
||||
KLALBProxySystem.CommitResult result = configSystem.commitControllerConfig(loadedRevision, kck,
|
||||
KLALBProxySystem.ConfigChangeSource.SWING);
|
||||
if (!result.isSuccess()) {
|
||||
resolveConfigConflict(result.getSnapshot(), "configsaveconflict");
|
||||
return;
|
||||
}
|
||||
loadedSnapshot = result.getSnapshot();
|
||||
loadedRevision = result.getRevision();
|
||||
loadedFormState = new SettingsFormState(this);
|
||||
pendingExternalSnapshot = null;
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("saveconfigsuccess"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("configsavefailed"),
|
||||
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("configsavefailed"),
|
||||
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 刷新任务创建 ====================
|
||||
/**
|
||||
@@ -1565,11 +1636,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
/**
|
||||
* 加载配置文件
|
||||
*/
|
||||
public void loadConfig(KLALBConfig config) {
|
||||
this.config = config;
|
||||
for (KLALBConfigItem item : config) {
|
||||
if (item instanceof KLALBControllerConfigItem) {
|
||||
KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item;
|
||||
private void loadConfigCandidate(KLALBControllerConfigItem kck) {
|
||||
|
||||
// 加载语言设置
|
||||
String lg = kck.getLanguage();
|
||||
@@ -1695,9 +1762,92 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
|
||||
long delr=kck.getLinkNagleDelayTime();
|
||||
linkNagleDelayTime.getSlider().setValue((int)(delr/100000L));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadConfig(KLALBConfig config) {
|
||||
if (configSystem != null) return;
|
||||
this.config = config;
|
||||
if (config == null) return;
|
||||
for (KLALBConfigItem item : config) {
|
||||
if (item instanceof KLALBControllerConfigItem) {
|
||||
loadConfigCandidate((KLALBControllerConfigItem) item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void bindConfigSystem(KLALBProxySystem system) {
|
||||
if (configSystemClosed || system == null) return;
|
||||
final long generation = ++bindingGeneration;
|
||||
Runnable bind = () -> {
|
||||
if (configSystemClosed || generation != bindingGeneration) return;
|
||||
if (configSystem != null && boundConfigChangeListener != null)
|
||||
configSystem.removeConfigChangeListener(boundConfigChangeListener);
|
||||
configSystem = system;
|
||||
final Consumer<KLALBProxySystem.ConfigChangeEvent> listener = event ->
|
||||
SwingUtilities.invokeLater(() -> handleConfigChange(system, generation, event));
|
||||
boundConfigChangeListener = listener;
|
||||
pendingExternalSnapshot = null;
|
||||
pendingExternalRevision = 0L;
|
||||
loadedSnapshot = null;
|
||||
loadedRevision = 0L;
|
||||
loadedFormState = null;
|
||||
system.addConfigChangeListener(listener);
|
||||
loadedSnapshot = system.getControllerConfigSnapshot();
|
||||
KLALBControllerConfigItem candidate = system.parseControllerConfigCandidate(loadedSnapshot);
|
||||
if (candidate != null) {
|
||||
loadConfigCandidate(candidate);
|
||||
loadedRevision = loadedSnapshot.getRevision();
|
||||
loadedFormState = new SettingsFormState(this);
|
||||
}
|
||||
};
|
||||
if (SwingUtilities.isEventDispatchThread()) bind.run();
|
||||
else SwingUtilities.invokeLater(bind);
|
||||
}
|
||||
|
||||
private void handleConfigChange(KLALBProxySystem sourceSystem, long generation,
|
||||
KLALBProxySystem.ConfigChangeEvent event) {
|
||||
if (configSystemClosed || generation != bindingGeneration || sourceSystem != configSystem
|
||||
|| event.getSource() == KLALBProxySystem.ConfigChangeSource.SWING) return;
|
||||
KLALBProxySystem.ControllerConfigSnapshot snapshot = event.getSnapshot();
|
||||
if (snapshot == null || snapshot.getRevision() <= Math.max(loadedRevision, pendingExternalRevision)) return;
|
||||
if (loadedFormState != null && loadedFormState.equals(new SettingsFormState(this))) {
|
||||
applyExternalSnapshot(snapshot);
|
||||
return;
|
||||
}
|
||||
if (pendingExternalSnapshot == null || snapshot.getRevision() > pendingExternalRevision) {
|
||||
boolean hadPending = pendingExternalSnapshot != null;
|
||||
pendingExternalSnapshot = snapshot;
|
||||
pendingExternalRevision = snapshot.getRevision();
|
||||
if (!hadPending) resolveConfigConflict(snapshot, "configexternalupdate");
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveConfigConflict(KLALBProxySystem.ControllerConfigSnapshot snapshot, String messageKey) {
|
||||
if (snapshot != null && snapshot.getRevision() > Math.max(loadedRevision, pendingExternalRevision)) {
|
||||
pendingExternalSnapshot = snapshot;
|
||||
pendingExternalRevision = snapshot.getRevision();
|
||||
}
|
||||
Object[] options = { UIEnv.getRsb().getString("configreload"), UIEnv.getRsb().getString("configcontinue") };
|
||||
int choice = JOptionPane.showOptionDialog(this, UIEnv.getRsb().getString(messageKey),
|
||||
UIEnv.getRsb().getString("configconflicttitle"), JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE, null, options, options[0]);
|
||||
if (choice == 0 && pendingExternalSnapshot != null
|
||||
&& pendingExternalSnapshot.getRevision() >= loadedRevision) {
|
||||
applyExternalSnapshot(pendingExternalSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyExternalSnapshot(KLALBProxySystem.ControllerConfigSnapshot snapshot) {
|
||||
KLALBControllerConfigItem candidate = configSystem.parseControllerConfigCandidate(snapshot);
|
||||
if (candidate == null) return;
|
||||
loadConfigCandidate(candidate);
|
||||
loadedSnapshot = snapshot;
|
||||
loadedRevision = snapshot.getRevision();
|
||||
loadedFormState = new SettingsFormState(this);
|
||||
pendingExternalSnapshot = null;
|
||||
pendingExternalRevision = 0L;
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
/**
|
||||
@@ -1772,8 +1922,9 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
/**
|
||||
* 设置保存配置的回调函数
|
||||
*/
|
||||
public void setSaveComsumer(Consumer<KLALBConfig> saveComsumer) {
|
||||
this.saveComsumer = saveComsumer;
|
||||
public void setSaveComsumer(Consumer<KLALBConfig> saveComsumer) {
|
||||
if (configSystem != null) return;
|
||||
this.saveComsumer = saveComsumer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1788,6 +1939,11 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
* 关闭窗口并清理资源
|
||||
*/
|
||||
public void close() {
|
||||
bindingGeneration++;
|
||||
configSystemClosed = true;
|
||||
if (configSystem != null && boundConfigChangeListener != null)
|
||||
configSystem.removeConfigChangeListener(boundConfigChangeListener);
|
||||
boundConfigChangeListener = null;
|
||||
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
|
||||
Component component = tabbedPane.getComponentAt(i);
|
||||
if (component instanceof NodeInformationPanel) {
|
||||
|
||||
@@ -869,17 +869,11 @@ public class KLALBWebServer {
|
||||
String body = readRequestBody(exchange);
|
||||
try {
|
||||
JsonObject json = new JsonParser().parse(body).getAsJsonObject();
|
||||
KLALBControllerConfigItem current = proxySystem.getControllerConfig();
|
||||
KLALBProxySystem.ControllerConfigSnapshot baseSnapshot = proxySystem.getControllerConfigSnapshot();
|
||||
KLALBControllerConfigItem current = proxySystem.parseControllerConfigCandidate(baseSnapshot);
|
||||
if (current != null) {
|
||||
String oldDeviceName = current.getDeviceName();
|
||||
String oldDeviceDescription = current.getDeviceDescription();
|
||||
List<MultiProtocolSocketAddress> oldExternalEndpoints = current.getExternalEndpoints() == null
|
||||
? null
|
||||
: new ArrayList<MultiProtocolSocketAddress>(current.getExternalEndpoints());
|
||||
List<String> oldExtraRoutes = current.getExtraRoutes() == null
|
||||
? null : new ArrayList<String>(current.getExtraRoutes());
|
||||
String newDeviceName = oldDeviceName;
|
||||
String newDeviceDescription = oldDeviceDescription;
|
||||
String newDeviceName = current.getDeviceName();
|
||||
String newDeviceDescription = current.getDeviceDescription();
|
||||
List<MultiProtocolSocketAddress> newExternalEndpoints = current.getExternalEndpoints();
|
||||
List<String> newExtraRoutes = current.getExtraRoutes();
|
||||
if (json.has("deviceName") && !json.get("deviceName").isJsonNull()) {
|
||||
@@ -1078,37 +1072,32 @@ public class KLALBWebServer {
|
||||
current.setDenyExternalEndpointBroadcast(json.get("denyLineTableBroadcast").getAsBoolean());
|
||||
}
|
||||
|
||||
boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName);
|
||||
boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription);
|
||||
boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints);
|
||||
boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes);
|
||||
if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) {
|
||||
KLALBController kc = proxySystem.getKlalbController();
|
||||
if (kc != null) {
|
||||
kc.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints,
|
||||
newExtraRoutes);
|
||||
} else {
|
||||
current.setDeviceName(newDeviceName);
|
||||
current.setDeviceDescription(newDeviceDescription);
|
||||
current.setExternalEndpoints(newExternalEndpoints);
|
||||
current.setExtraRoutes(newExtraRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger GUI save consumer or save directly
|
||||
if (proxySystem.getKLALBGUI() != null && proxySystem.getKLALBGUI().getSaveComsumer() != null) {
|
||||
proxySystem.getKLALBGUI().getSaveComsumer().accept(proxySystem.getConfig());
|
||||
} else {
|
||||
proxySystem.saveConfigToFile();
|
||||
current.setDeviceName(newDeviceName);
|
||||
current.setDeviceDescription(newDeviceDescription);
|
||||
current.setExternalEndpoints(newExternalEndpoints);
|
||||
current.setExtraRoutes(newExtraRoutes);
|
||||
KLALBProxySystem.CommitResult commitResult = proxySystem.commitControllerConfig(
|
||||
baseSnapshot.getRevision(), current,
|
||||
KLALBProxySystem.ConfigChangeSource.WEB);
|
||||
if (!commitResult.isSuccess()) {
|
||||
JsonObject conflict = new JsonObject();
|
||||
conflict.addProperty("success", false);
|
||||
conflict.addProperty("revision", commitResult.getRevision());
|
||||
conflict.addProperty("error", "Configuration revision conflict");
|
||||
sendJsonResponse(exchange, 409, conflict);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject resp = new JsonObject();
|
||||
resp.addProperty("success", true);
|
||||
resp.addProperty("revision", commitResult.getRevision());
|
||||
resp.addProperty("message", "Configuration updated successfully");
|
||||
sendJsonResponse(exchange, 200, resp);
|
||||
} else {
|
||||
sendError(exchange, 500, "Current configuration is null");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sendError(exchange, 500, "Failed to persist configuration: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
sendError(exchange, 400, "Failed to update configuration: " + e.getMessage());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user