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:
@@ -46,8 +46,9 @@ pnpm dev
|
||||
|
||||
- `KLALBConfigItem` is a polymorphic JSON array keyed by `Type`. Adding a type requires a subclass and cases in both default config serializer and deserializer; unknown types must remain preserved.
|
||||
- `/api/config` is field-by-field parsing, not whole-object Gson mapping. Keep legacy key aliases in sync with new fields.
|
||||
- Vendored Gson is `2.1`: responses that are `JsonElement` instances must be serialized with `JsonElement.toString()`, not reflective `gson.toJson(Object)`.
|
||||
- Vendored Gson is `2.1`: HTTP responses that are `JsonElement` instances must be serialized with `JsonElement.toString()`, not reflective `gson.toJson(Object)`; configuration files must use the configured pretty-print Gson path rather than `JsonElement.toString()`.
|
||||
- UI strings use `UIEnv.getRsb()`; add keys to both `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
|
||||
- Web and Swing configuration writes must use `KLALBProxySystem`'s revisioned detached-candidate commit/event path; do not mutate the canonical config object directly.
|
||||
- `KLALBController.PublishedNodeInfo` is the thread-safe source for Tiny/Full node-info responses. Publish name, description, external endpoints, and Extra Routes through the controller method so snapshots and Tiny/Full update flags stay consistent.
|
||||
- `RouterInfo` no longer carries a device name. Its wire format retains an empty legacy UTF slot and `RouterInfoPacket` has optional Tiny/Full invalidation flags. Treat codec changes as compatibility work: preserve old-reader behavior and review a whole-mesh rollout.
|
||||
- Full node-info carries `extraRoutes` separately from the endpoint `data` list. Keep absent fields compatible with older peers.
|
||||
|
||||
@@ -35,6 +35,12 @@ reconnectall=Reconnect All
|
||||
remotelines=Remote lines
|
||||
settings=Settings
|
||||
saveconfigsuccess=Save config success
|
||||
configexternalupdate=The configuration was updated by another source.
|
||||
configreload=Reload
|
||||
configcontinue=Continue editing
|
||||
configsaveconflict=Save failed: the configuration was updated. Reload and try again.
|
||||
configsavefailed=Failed to save configuration.
|
||||
configconflicttitle=Configuration conflict
|
||||
warning=Warning
|
||||
invaildipv6addr=IPv6 address:Invaild Input
|
||||
invailddnsserver=DNS server:Invaild Input
|
||||
|
||||
@@ -35,6 +35,12 @@ reconnectall=全部重连
|
||||
remotelines=远程链路
|
||||
settings=设置
|
||||
saveconfigsuccess=保存配置成功
|
||||
configexternalupdate=配置已被其他来源更新。
|
||||
configreload=重新加载
|
||||
configcontinue=继续编辑
|
||||
configsaveconflict=保存失败:配置已被更新,请重新加载后再试。
|
||||
configsavefailed=保存配置失败。
|
||||
configconflicttitle=配置冲突
|
||||
warning=警告
|
||||
invaildipv6addr=IPv6地址:非法输入
|
||||
invailddnsserver=DNS服务器:非法输入
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -8,7 +8,17 @@ import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
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;
|
||||
@@ -20,6 +30,7 @@ import java.util.Set;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
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;
|
||||
@@ -30,12 +41,81 @@ import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
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);
|
||||
@@ -142,11 +222,25 @@ public class KLALBProxySystem {
|
||||
loadConfigJson(new JsonParser().parse(json));
|
||||
}
|
||||
public void loadConfigJson(JsonElement json) {
|
||||
JsonElement rawJson = new JsonParser().parse(json.toString());
|
||||
KLALBConfig config= gson.fromJson(json, KLALBConfig.class);
|
||||
loadConfig(config);
|
||||
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;
|
||||
@@ -229,28 +323,171 @@ public class KLALBProxySystem {
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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();
|
||||
});
|
||||
kgui.bindConfigSystem(this);
|
||||
}
|
||||
return kgui;
|
||||
}
|
||||
@@ -258,10 +495,6 @@ public class KLALBProxySystem {
|
||||
return config;
|
||||
}
|
||||
public KLALBControllerConfigItem getControllerConfig() {
|
||||
for(KLALBConfigItem item:config) {
|
||||
if(item instanceof KLALBControllerConfigItem)
|
||||
return (KLALBControllerConfigItem) item;
|
||||
}
|
||||
return null;
|
||||
return parseControllerConfigCandidate(getControllerConfigSnapshot());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,80 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
// ==================== 配置和回调 ====================
|
||||
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)); // 仪表盘尺寸
|
||||
@@ -1056,14 +1130,9 @@ 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;
|
||||
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,28 +1383,30 @@ 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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 调用保存回调
|
||||
if (saveComsumer != null) {
|
||||
saveComsumer.accept(config);
|
||||
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,10 +1762,93 @@ 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;
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
/**
|
||||
* 创建图标
|
||||
@@ -1773,6 +1923,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
* 设置保存配置的回调函数
|
||||
*/
|
||||
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