+ обновление при смене конфига

+ страница со списком ресурсов
+ удаление старых версий
+ фиксы TDNS
This commit is contained in:
kirillius 2025-10-10 02:26:54 +03:00
parent 42ccd99b30
commit 952ba3b588
10 changed files with 140 additions and 15 deletions

View File

@ -22,9 +22,12 @@ import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static ru.kirillius.pf.sdn.core.Util.CommandLineUtils.getArgument;
@ -106,6 +109,41 @@ public class App implements Context, Closeable {
serviceManager.getService(ComponentHandlerService.class).syncComponentsWithConfig();
serviceManager.getService(ResourceUpdateService.class).start();
removeObsoleteVersions();
}
private void removeObsoleteVersions() {
var appVersion = serviceManager.getService(AppUpdateService.class).getAppVersion();
var listedFiles = launcherConfig.getAppLibrary().listFiles();
if (listedFiles == null) {
SystemLogger.error("Failed to list files in library path", CTX);
return;
}
var versions = Arrays.stream(listedFiles)
.filter(file -> file.getName().endsWith(AppUpdateService.EXTENSION))
.collect(Collectors.toMap(file -> file.getName().replace(Pattern.quote(AppUpdateService.EXTENSION), ""), file -> file));
if (!versions.containsKey(appVersion + AppUpdateService.EXTENSION)) {
SystemLogger.error("Unable to remove obsolete version because current version file is not found!", CTX);
return;
}
versions.forEach((key, file) -> {
if (key.equals(appVersion + AppUpdateService.EXTENSION)) {
return;
}
try {
SystemLogger.message("Removing obsolete version " + key, CTX);
if (!file.delete()) {
throw new RuntimeException("Delete failed");
}
} catch (Exception ex) {
SystemLogger.error("Failed to delete file " + file.getName(), CTX, ex);
}
});
}
/**

View File

@ -57,11 +57,14 @@ public final class TDNS extends AbstractComponent<TDNS.TechnitiumConfig> {
api.createForwarderZone(zoneName, instance.forwarder);
}
});
} catch (IOException e) {
SystemLogger.message("Instance is updated", CTX);
} catch (Exception e) {
SystemLogger.error("Error happened on DNS server " + instance.server + " sync", CTX, e);
}
}
SystemLogger.message("Update is completed", CTX);
}

View File

@ -56,9 +56,15 @@ public class System implements RPC {
@ProtectedMethod
@JRPCMethod
public void setConfig(@JRPCArgument(name = "config") JSONObject json) {
public void setConfig(@JRPCArgument(name = "config") JSONObject json) throws Exception {
var config = JSONUtility.deserializeStructure(json, Config.class);
var initial = new Config();
initial.merge(context.getConfig());
context.getConfig().merge(config);
context.getEventsHandler().getConfigChangeEvent().invoke(ContextEventsHandler.ConfigChangeContext.builder()
.initial(initial)
.current(config)
.build());
}

View File

@ -0,0 +1,14 @@
package ru.kirillius.pf.sdn.External.API;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class TDNS {
@Test
public void t() throws IOException {
try (TDNSAPI t = new TDNSAPI("http://8.8.8.8", "sdfdfdsf")) {
t.getZones();
}
}
}

View File

@ -25,6 +25,7 @@ import java.util.regex.Pattern;
* Handles application update discovery by polling a repository and downloading new packages.
*/
public class AppUpdateService extends AppService {
public final static String EXTENSION = ".pfapp";
private static final String CTX = AppUpdateService.class.getSimpleName();
private static final Pattern VERSION_LINK_PATTERN = Pattern.compile("<a\\s+[^>]*href=\"([0-9]+(?:\\.[0-9]+)*\\.pfapp)\"", Pattern.CASE_INSENSITIVE);
@ -140,7 +141,7 @@ public class AppUpdateService extends AppService {
return;
}
var fileName = version + ".pfapp";
var fileName = version + EXTENSION;
var targetDirectory = appLibraryPath;
var tempFile = targetDirectory.resolve(fileName + ".download");
var targetFile = targetDirectory.resolve(fileName);
@ -207,7 +208,7 @@ public class AppUpdateService extends AppService {
*/
private URI buildVersionUri(String version) {
var base = repository.endsWith("/") ? repository : repository + "/";
return URI.create(base + version + ".pfapp");
return URI.create(base + version + EXTENSION);
}
/**
@ -276,7 +277,7 @@ public class AppUpdateService extends AppService {
if (href == null || href.isBlank()) {
continue;
}
var version = href.endsWith(".pfapp") ? href.substring(0, href.length() - 6) : href;
var version = href.endsWith(EXTENSION) ? href.substring(0, href.length() - 6) : href;
if (version.isEmpty()) {
continue;
}

View File

@ -1,5 +1,6 @@
package ru.kirillius.pf.sdn.core;
import lombok.Builder;
import lombok.Getter;
import ru.kirillius.java.utils.events.ConcurrentEventHandler;
import ru.kirillius.java.utils.events.EventHandler;
@ -25,4 +26,15 @@ public final class ContextEventsHandler {
*/
@Getter
private final EventHandler<JSONRPCServlet> RPCInitEvent = new ConcurrentEventHandler<>();
@Getter
private final EventHandler<ConfigChangeContext> configChangeEvent = new ConcurrentEventHandler<>();
@Builder
public final static class ConfigChangeContext {
@Getter
private Config initial;
@Getter
private Config current;
}
}

View File

@ -7,6 +7,7 @@ import ru.kirillius.json.JSONSerializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Container for grouped network identifiers used to configure filtering and subscriptions.
@ -33,6 +34,17 @@ public class NetworkResourceBundle {
@JSONArrayProperty(type = String.class)
private List<String> domains = new ArrayList<>();
@Override
public boolean equals(Object o) {
if (!(o instanceof NetworkResourceBundle that)) return false;
return Objects.equals(ASN, that.ASN) && Objects.equals(subnets, that.subnets) && Objects.equals(domains, that.domains);
}
@Override
public int hashCode() {
return Objects.hash(ASN, subnets, domains);
}
/**
* Clears all stored network identifiers.
*/

View File

@ -7,12 +7,15 @@ import ru.kirillius.java.utils.events.EventListener;
import ru.kirillius.json.JSONUtility;
import ru.kirillius.pf.sdn.core.AppService;
import ru.kirillius.pf.sdn.core.Context;
import ru.kirillius.pf.sdn.core.ContextEventsHandler;
import ru.kirillius.pf.sdn.core.Subscription.SubscriptionService;
import ru.kirillius.pf.sdn.core.Util.IPv4Util;
import ru.kirillius.utils.logging.SystemLogger;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
@ -23,7 +26,15 @@ public class NetworkingService extends AppService {
private final static String CTX = NetworkingService.class.getSimpleName();
private final File cacheFile;
private final EventListener<NetworkResourceBundle> subscription;
private final EventListener<NetworkResourceBundle> resourceUpdateSubscription;
private final EventListener<ContextEventsHandler.ConfigChangeContext> configChangeSubscription;
private void rebuildInputs() {
inputResources.clear();
inputResources.add(context.getConfig().getCustomResources());
inputResources.add(context.getServiceManager().getService(SubscriptionService.class).getOutputResources());
triggerUpdate(false);
}
/**
* Creates the networking service, wiring subscriptions and restoring cached state.
@ -32,12 +43,13 @@ public class NetworkingService extends AppService {
super(context);
inputResources.clear();
inputResources.add(context.getConfig().getCustomResources());
subscription = context.getEventsHandler().getSubscriptionsUpdateEvent().add(bundle -> {
var config = context.getConfig();
inputResources.clear();
inputResources.add(config.getCustomResources());
inputResources.add(bundle);
triggerUpdate(false);
resourceUpdateSubscription = context.getEventsHandler().getSubscriptionsUpdateEvent().add(bundle -> rebuildInputs());
configChangeSubscription = context.getEventsHandler().getConfigChangeEvent().add(changeContext -> {
var filtersChanges = !changeContext.getCurrent().getFilteredResources().equals(changeContext.getInitial().getFilteredResources());
var resChanges = !changeContext.getCurrent().getCustomResources().equals(changeContext.getInitial().getCustomResources());
if (resChanges || filtersChanges) {
NetworkingService.this.rebuildInputs();
}
});
cacheFile = new File(context.getConfig().getCacheDirectory(), "as-cache.json");
if (cacheFile.exists() && context.getConfig().isCachingAS()) {
@ -119,8 +131,14 @@ public class NetworkingService extends AppService {
SystemLogger.message("Trying to summary " + subnets.size() + " subnets...", CTX);
var merged = IPv4Util.summarySubnets(subnets, config.getMergeSubnetsWithUsage());
var unmerged = new AtomicInteger();
subnets.forEach(subnet -> {
if (!merged.getMergedSubnets().contains(subnet)) {
unmerged.getAndIncrement();
}
});
SystemLogger.message(merged.getMergedSubnets().size() + " subnets has been merged to " + merged.getResult().size() + " new subnets", CTX);
SystemLogger.message(subnets.size() + " subnets has been summarized and merged to " + merged.getResult().size() + " new subnets. Unmerged: " + unmerged.get(), CTX);
var domains = new HashSet<>(inputResources.getDomains());
filteredResources.getDomains().forEach(domains::remove);
@ -147,7 +165,7 @@ public class NetworkingService extends AppService {
try {
context.getEventsHandler().getNetworkManagerUpdateEvent().invoke(outputResources);
} catch (Exception e) {
throw new RuntimeException(e);
SystemLogger.error("Unable to invoke update event", CTX, e);
}
}));
}
@ -181,7 +199,9 @@ public class NetworkingService extends AppService {
*/
@Override
public void close() throws IOException {
context.getEventsHandler().getSubscriptionsUpdateEvent().remove(subscription);
context.getEventsHandler().getSubscriptionsUpdateEvent().remove(resourceUpdateSubscription);
context.getEventsHandler().getConfigChangeEvent().remove(configChangeSubscription);
executor.shutdown();
}
}

View File

@ -11,6 +11,7 @@ import { TDNSConfig } from '../pages/TDNS.js';
import { FRRConfig } from '../pages/FRR.js';
import { SettingsPage } from '../pages/Settings.js';
import { LogsPage } from '../pages/Logs.js';
import { NetworkResourcesPage } from '../pages/NetworkResources.js';
// Переменная для отслеживания текущего активного хеша (для корректного unmount)
@ -28,6 +29,7 @@ const allMenuItems = [
{ label: 'Настройка TDNS', path: 'tdns', component: 'ru.kirillius.pf.sdn.External.API.Components.TDNS' },
{ label: 'Настройка FRR', path: 'frr', component: 'ru.kirillius.pf.sdn.External.API.Components.FRR' },
{ label: 'Журнал', path: 'logs', component: null },
{ label: 'Сетевые ресурсы', path: 'network-resources', component: null },
];
// 2. Определение страниц
@ -76,6 +78,11 @@ const routes = {
render: LogsPage.render,
mount: LogsPage.mount,
unmount: LogsPage.unmount
},
'#network-resources': {
render: NetworkResourcesPage.render,
mount: NetworkResourcesPage.mount,
unmount: NetworkResourcesPage.unmount
}
};

View File

@ -399,6 +399,18 @@ html.dark-theme, body {
margin-right: 8px;
}
#network-resources-content .network-resource-link,
#network-resources-content .network-resource-link:visited {
color: var(--color-text);
text-decoration: none;
}
#network-resources-content .network-resource-link:hover,
#network-resources-content .network-resource-link:focus {
color: var(--color-text);
text-decoration: underline;
}
/* 🔥 ОБНОВЛЕНО: Сделаем resource-key (используется для shortName) крупнее */
.resource-key {
font-weight: 600; /* Жирный шрифт */