WIP работа над resolver
This commit is contained in:
parent
c1dbaa5bfc
commit
ee3f4f3fe5
|
|
@ -14,6 +14,7 @@ import ru.kirillius.pf.sdn.core.Auth.AuthManager;
|
||||||
import ru.kirillius.pf.sdn.core.Auth.TokenService;
|
import ru.kirillius.pf.sdn.core.Auth.TokenService;
|
||||||
import ru.kirillius.pf.sdn.core.Networking.BGPInfoService;
|
import ru.kirillius.pf.sdn.core.Networking.BGPInfoService;
|
||||||
import ru.kirillius.pf.sdn.core.Networking.NetworkingService;
|
import ru.kirillius.pf.sdn.core.Networking.NetworkingService;
|
||||||
|
import ru.kirillius.pf.sdn.core.Networking.ResolverService;
|
||||||
import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig;
|
import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig;
|
||||||
import ru.kirillius.pf.sdn.core.Subscription.SubscriptionService;
|
import ru.kirillius.pf.sdn.core.Subscription.SubscriptionService;
|
||||||
import ru.kirillius.pf.sdn.core.Util.Wait;
|
import ru.kirillius.pf.sdn.core.Util.Wait;
|
||||||
|
|
@ -80,7 +81,7 @@ public class App implements Context, Closeable {
|
||||||
* Instantiates all application services and performs initial wiring.
|
* Instantiates all application services and performs initial wiring.
|
||||||
*/
|
*/
|
||||||
private ServiceManager loadServiceManager() {
|
private ServiceManager loadServiceManager() {
|
||||||
var manager = new ServiceManager(this, List.of(AuthManager.class, ComponentHandlerService.class, TokenService.class, AppUpdateService.class, BGPInfoService.class, NetworkingService.class, SubscriptionService.class, ResourceUpdateService.class, WebService.class));
|
var manager = new ServiceManager(this, List.of(AuthManager.class, ComponentHandlerService.class, TokenService.class, AppUpdateService.class, BGPInfoService.class, NetworkingService.class, SubscriptionService.class, ResourceUpdateService.class, WebService.class, ResolverService.class));
|
||||||
var infoService = manager.getService(BGPInfoService.class);
|
var infoService = manager.getService(BGPInfoService.class);
|
||||||
infoService.addProvider(new HEInfoProvider());
|
infoService.addProvider(new HEInfoProvider());
|
||||||
infoService.addProvider(new RIPEInfoProvider());
|
infoService.addProvider(new RIPEInfoProvider());
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ public class SubscriptionManager implements RPC {
|
||||||
@JRPCArgument(name = "ASN") JSONArray ASN,
|
@JRPCArgument(name = "ASN") JSONArray ASN,
|
||||||
@JRPCArgument(name = "subnets") JSONArray subnets,
|
@JRPCArgument(name = "subnets") JSONArray subnets,
|
||||||
@JRPCArgument(name = "addresses") JSONArray addresses,
|
@JRPCArgument(name = "addresses") JSONArray addresses,
|
||||||
|
@JRPCArgument(name = "autoResolve") boolean autoResolve,
|
||||||
@JRPCArgument(name = "storage") String storage,
|
@JRPCArgument(name = "storage") String storage,
|
||||||
@JRPCArgument(name = "subscribe") boolean subscribe) throws IOException {
|
@JRPCArgument(name = "subscribe") boolean subscribe) throws IOException {
|
||||||
var repositoryConfig = findLocalRepo(storage);
|
var repositoryConfig = findLocalRepo(storage);
|
||||||
|
|
@ -97,7 +98,7 @@ public class SubscriptionManager implements RPC {
|
||||||
|
|
||||||
var domainList = new ArrayList<String>();
|
var domainList = new ArrayList<String>();
|
||||||
|
|
||||||
domains.forEach(d->domainList.add(d.toString()));
|
domains.forEach(d -> domainList.add(d.toString()));
|
||||||
|
|
||||||
try (var writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(directory, name + ".json"))))) {
|
try (var writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(directory, name + ".json"))))) {
|
||||||
var merged = JSONUtility.deserializeCollection(subnets, IPv4Subnet.class, null).collect(Collectors.toList());
|
var merged = JSONUtility.deserializeCollection(subnets, IPv4Subnet.class, null).collect(Collectors.toList());
|
||||||
|
|
@ -113,6 +114,7 @@ public class SubscriptionManager implements RPC {
|
||||||
.subnets(merged)
|
.subnets(merged)
|
||||||
.domains(domainList)
|
.domains(domainList)
|
||||||
.description(description)
|
.description(description)
|
||||||
|
.resolveDomains(autoResolve)
|
||||||
.build()
|
.build()
|
||||||
).toString(2));
|
).toString(2));
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +126,6 @@ public class SubscriptionManager implements RPC {
|
||||||
context.getServiceManager().getService(SubscriptionService.class).triggerUpdate();
|
context.getServiceManager().getService(SubscriptionService.class).triggerUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private RepositoryConfig findLocalRepo(String storage) throws FileNotFoundException {
|
private RepositoryConfig findLocalRepo(String storage) throws FileNotFoundException {
|
||||||
var optional = context.getConfig().getSubscriptions().stream().filter(repositoryConfig -> repositoryConfig.getType().equals(LocalFilesystemSubscription.class) && repositoryConfig.getName().equals(storage)).findFirst();
|
var optional = context.getConfig().getSubscriptions().stream().filter(repositoryConfig -> repositoryConfig.getType().equals(LocalFilesystemSubscription.class) && repositoryConfig.getName().equals(storage)).findFirst();
|
||||||
if (optional.isEmpty()) {
|
if (optional.isEmpty()) {
|
||||||
|
|
@ -134,7 +135,6 @@ public class SubscriptionManager implements RPC {
|
||||||
return optional.get();
|
return optional.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@JRPCMethod
|
@JRPCMethod
|
||||||
@ProtectedMethod
|
@ProtectedMethod
|
||||||
public void removeLocalResourceFile(
|
public void removeLocalResourceFile(
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@ public class Config {
|
||||||
@JSONProperty(required = false)
|
@JSONProperty(required = false)
|
||||||
private volatile int domainLookupInterval = 5;
|
private volatile int domainLookupInterval = 5;
|
||||||
|
|
||||||
@Getter
|
// @Getter
|
||||||
@Setter
|
// @Setter
|
||||||
@JSONProperty(required = false)
|
// @JSONProperty(required = false)
|
||||||
private volatile int autoLookupPrefixLength = 24;
|
// private volatile int autoLookupPrefixLength = 24;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time in hours
|
* Time in hours
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,10 @@ public class NetworkingService extends AppService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.execute(this::autoResolverWorker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void autoResolverWorker() {
|
public void performAutoresolve() {
|
||||||
var current = new HashSet<IPv4Subnet>();
|
var current = new HashSet<IPv4Subnet>();
|
||||||
domainCache.forEach((host, entry) -> current.addAll(entry.getAddresses().keySet()));
|
domainCache.forEach((host, entry) -> current.addAll(entry.getAddresses().keySet()));
|
||||||
|
|
||||||
|
|
@ -110,6 +110,10 @@ public class NetworkingService extends AppService {
|
||||||
rebuildInputs();
|
rebuildInputs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!context.getConfig().isCachingDomains()){
|
||||||
|
domainCache.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rebuildInputs() {
|
private void rebuildInputs() {
|
||||||
|
|
@ -186,7 +190,8 @@ public class NetworkingService extends AppService {
|
||||||
try (var os = new FileOutputStream(domainCacheFile)) {
|
try (var os = new FileOutputStream(domainCacheFile)) {
|
||||||
var json = new JSONObject();
|
var json = new JSONObject();
|
||||||
domainCache.forEach((key, entry) -> {
|
domainCache.forEach((key, entry) -> {
|
||||||
json.put(String.valueOf(key), JSONUtility.serializeStructure(domainCache.get(key)));
|
var serialized = JSONUtility.serializeStructure(entry);
|
||||||
|
json.put(String.valueOf(key), serialized);
|
||||||
});
|
});
|
||||||
os.write(json.toString().getBytes());
|
os.write(json.toString().getBytes());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
@ -254,6 +259,12 @@ public class NetworkingService extends AppService {
|
||||||
try {
|
try {
|
||||||
var subnets = task.get();
|
var subnets = task.get();
|
||||||
var entry = domainCache.get(domain);
|
var entry = domainCache.get(domain);
|
||||||
|
|
||||||
|
if(entry == null) {
|
||||||
|
entry = new ResolverCacheEntry();
|
||||||
|
domainCache.put(domain, entry);
|
||||||
|
}
|
||||||
|
|
||||||
var addresses = entry.getAddresses();
|
var addresses = entry.getAddresses();
|
||||||
entry.setLastUpdate(Instant.now());
|
entry.setLastUpdate(Instant.now());
|
||||||
subnets.forEach(subnet -> addresses.put(subnet, Instant.now()));
|
subnets.forEach(subnet -> addresses.put(subnet, Instant.now()));
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import ru.kirillius.json.JSONProperty;
|
||||||
import ru.kirillius.json.JSONSerializable;
|
import ru.kirillius.json.JSONSerializable;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@JSONSerializable
|
@JSONSerializable
|
||||||
|
|
@ -16,7 +17,7 @@ import java.util.Map;
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class ResolverCacheEntry {
|
public class ResolverCacheEntry {
|
||||||
@JSONProperty
|
@JSONProperty
|
||||||
private Instant lastUpdate;
|
private Instant lastUpdate = Instant.now();
|
||||||
@JSONMapProperty(keyType = IPv4Subnet.class, valueType = Instant.class)
|
@JSONMapProperty(keyType = IPv4Subnet.class, valueType = Instant.class)
|
||||||
private Map<IPv4Subnet, Instant> addresses;
|
private Map<IPv4Subnet, Instant> addresses = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package ru.kirillius.pf.sdn.core.Networking;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.json.JSONTokener;
|
|
||||||
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.Util.DomainUtil;
|
|
||||||
import ru.kirillius.utils.logging.SystemLogger;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
|
|
||||||
public class ResolverServiceObsolete extends AppService {
|
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
private final static String CTX = ResolverServiceObsolete.class.getSimpleName();
|
|
||||||
|
|
||||||
private final File cacheFile;
|
|
||||||
|
|
||||||
private final Map<String, ResolverCacheEntry> cacheEntries = new ConcurrentHashMap<>();
|
|
||||||
private final Future<?> backgroundWorker;
|
|
||||||
|
|
||||||
public void saveCache() throws IOException {
|
|
||||||
try (var writer = new BufferedWriter(new FileWriter(cacheFile))) {
|
|
||||||
writer.write(JSONUtility.serializeMap(cacheEntries, String.class, ResolverCacheEntry.class, null, null).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void maintainCache() {
|
|
||||||
cacheEntries.keySet().forEach(host -> {
|
|
||||||
var entry = cacheEntries.get(host);
|
|
||||||
var lastSeen = entry.getLastSeen();
|
|
||||||
for (var subnet : lastSeen.keySet()) {
|
|
||||||
if (lastSeen.get(subnet).isBefore(Instant.now().minus(context.getConfig().getDomainsTimeToLive(), ChronoUnit.HOURS))) {
|
|
||||||
lastSeen.remove(subnet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Future<List<IPv4Subnet>> resolve(String host, boolean useCache) {
|
|
||||||
return executor.submit(() -> {
|
|
||||||
if (useCache && cacheEntries.containsKey(host)) {
|
|
||||||
var cached = cacheEntries.get(host);
|
|
||||||
if (cached.getLastUpdate().isAfter(Instant.now().minus(context.getConfig().getDomainLookupInterval(), ChronoUnit.MINUTES))) {
|
|
||||||
return cached.getAddresses();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var resolved = new HashSet<IPv4Subnet>();
|
|
||||||
for (var domainResolver : context.getConfig().getDomainResolvers()) {
|
|
||||||
DomainUtil.lookup(host, domainResolver).stream().map(addr -> new IPv4Subnet(addr, 32)).forEach(resolved::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useCache) {
|
|
||||||
if (!cacheEntries.containsKey(host)) {
|
|
||||||
cacheEntries.put(host, new ResolverCacheEntry());
|
|
||||||
}
|
|
||||||
var cached = cacheEntries.get(host);
|
|
||||||
cached.setLastUpdate(Instant.now());
|
|
||||||
cached.getAddresses().addAll(resolved);
|
|
||||||
var lastSeen = cached.getLastSeen();
|
|
||||||
resolved.forEach(net -> lastSeen.put(net, Instant.now()));
|
|
||||||
resolved.addAll(cached.getAddresses());
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolved.stream().toList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the networking service, wiring subscriptions and restoring cached state.
|
|
||||||
*/
|
|
||||||
public ResolverServiceObsolete(Context context) {
|
|
||||||
super(context);
|
|
||||||
|
|
||||||
cacheFile = new File(context.getConfig().getCacheDirectory(), "resolver-cache.json");
|
|
||||||
if (cacheFile.exists() && context.getConfig().isCachingAS()) {
|
|
||||||
SystemLogger.message("Loading resolver cache file", CTX);
|
|
||||||
try (var is = new FileInputStream(cacheFile)) {
|
|
||||||
var json = new JSONObject(new JSONTokener(is));
|
|
||||||
var deserialized = JSONUtility.deserializeMap(json, String.class, ResolverCacheEntry.class, null, null);
|
|
||||||
cacheEntries.putAll(deserialized);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SystemLogger.error("Failed to load resolver cache file " + cacheFile.getPath(), CTX, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backgroundWorker = executor.submit(this::autoResolve);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final List<String> autoLookupHosts = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
private void autoResolve() {
|
|
||||||
try {
|
|
||||||
Thread.sleep(context.getConfig().getDomainLookupInterval());
|
|
||||||
autoLookupHosts.stream().toList().forEach(host -> {
|
|
||||||
//TODO продумать обновление
|
|
||||||
});
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes event subscriptions and shuts down the executor.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
backgroundWorker.cancel(true);
|
|
||||||
executor.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -34,7 +34,6 @@ public class ResourceUpdateService extends AppService {
|
||||||
updateThread.start();
|
updateThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interrupts the update thread and stops scheduling tasks.
|
* Interrupts the update thread and stops scheduling tasks.
|
||||||
*/
|
*/
|
||||||
|
|
@ -76,6 +75,11 @@ public class ResourceUpdateService extends AppService {
|
||||||
Wait.when(subscriptionManager::isUpdatingNow);
|
Wait.when(subscriptionManager::isUpdatingNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uptime % context.getConfig().getDomainLookupInterval() == 0) {
|
||||||
|
SystemLogger.message("Resolving domains...", CTX);
|
||||||
|
context.getServiceManager().getService(NetworkingService.class).performAutoresolve();
|
||||||
|
}
|
||||||
|
|
||||||
if (config.getUpdateASInterval() > 0 && uptime % (config.getUpdateASInterval() * 60L) == 0) {
|
if (config.getUpdateASInterval() > 0 && uptime % (config.getUpdateASInterval() * 60L) == 0) {
|
||||||
SystemLogger.message("Updating cached AS", CTX);
|
SystemLogger.message("Updating cached AS", CTX);
|
||||||
var networkManager = context.getServiceManager().getService(NetworkingService.class);
|
var networkManager = context.getServiceManager().getService(NetworkingService.class);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ru.kirillius.pf.sdn.core.Networking;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
class ResBundleTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInit() {
|
||||||
|
var b = new ResolverCacheEntry();
|
||||||
|
b.getAddresses().put(new IPv4Subnet("127.0.0.1/32"), Instant.now());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ const FIELD_IDS = {
|
||||||
domainsInput: 'local-storages-domains',
|
domainsInput: 'local-storages-domains',
|
||||||
asnInput: 'local-storages-asn',
|
asnInput: 'local-storages-asn',
|
||||||
subnetsInput: 'local-storages-subnets',
|
subnetsInput: 'local-storages-subnets',
|
||||||
|
resolveDomainsCheckbox: 'local-storages-resolve-domains',
|
||||||
modalStatus: 'local-storages-modal-status',
|
modalStatus: 'local-storages-modal-status',
|
||||||
modalSaveButton: 'local-storages-save-btn',
|
modalSaveButton: 'local-storages-save-btn',
|
||||||
modalCancelButton: 'local-storages-cancel-btn'
|
modalCancelButton: 'local-storages-cancel-btn'
|
||||||
|
|
@ -253,6 +254,7 @@ function openModal(mode, resourceName = '') {
|
||||||
const $domainsInput = $(SELECTORS.domainsInput);
|
const $domainsInput = $(SELECTORS.domainsInput);
|
||||||
const $asnInput = $(SELECTORS.asnInput);
|
const $asnInput = $(SELECTORS.asnInput);
|
||||||
const $subnetsInput = $(SELECTORS.subnetsInput);
|
const $subnetsInput = $(SELECTORS.subnetsInput);
|
||||||
|
const $resolveDomainsCheckbox = $(SELECTORS.resolveDomainsCheckbox);
|
||||||
const $saveButton = $(SELECTORS.modalSaveButton);
|
const $saveButton = $(SELECTORS.modalSaveButton);
|
||||||
|
|
||||||
setModalStatus('', '');
|
setModalStatus('', '');
|
||||||
|
|
@ -266,6 +268,7 @@ function openModal(mode, resourceName = '') {
|
||||||
$domainsInput.val(joinLines(resource.domains));
|
$domainsInput.val(joinLines(resource.domains));
|
||||||
$asnInput.val(joinLines(resource.ASN));
|
$asnInput.val(joinLines(resource.ASN));
|
||||||
$subnetsInput.val(joinLines(resource.subnets));
|
$subnetsInput.val(joinLines(resource.subnets));
|
||||||
|
$resolveDomainsCheckbox.prop('checked', resource.resolveDomains === true);
|
||||||
} else {
|
} else {
|
||||||
$title.text('Создание ресурса');
|
$title.text('Создание ресурса');
|
||||||
$nameInput.val('').prop('disabled', false);
|
$nameInput.val('').prop('disabled', false);
|
||||||
|
|
@ -273,6 +276,7 @@ function openModal(mode, resourceName = '') {
|
||||||
$domainsInput.val('');
|
$domainsInput.val('');
|
||||||
$asnInput.val('');
|
$asnInput.val('');
|
||||||
$subnetsInput.val('');
|
$subnetsInput.val('');
|
||||||
|
$resolveDomainsCheckbox.prop('checked', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$saveButton.prop('disabled', false).text('Сохранить');
|
$saveButton.prop('disabled', false).text('Сохранить');
|
||||||
|
|
@ -314,6 +318,7 @@ async function handleModalSubmit(event) {
|
||||||
const $domainsInput = $(SELECTORS.domainsInput);
|
const $domainsInput = $(SELECTORS.domainsInput);
|
||||||
const $asnInput = $(SELECTORS.asnInput);
|
const $asnInput = $(SELECTORS.asnInput);
|
||||||
const $subnetsInput = $(SELECTORS.subnetsInput);
|
const $subnetsInput = $(SELECTORS.subnetsInput);
|
||||||
|
const $resolveDomainsCheckbox = $(SELECTORS.resolveDomainsCheckbox);
|
||||||
|
|
||||||
const nameValue = ($nameInput.val() || '').trim();
|
const nameValue = ($nameInput.val() || '').trim();
|
||||||
const validationError = validateName(nameValue);
|
const validationError = validateName(nameValue);
|
||||||
|
|
@ -350,6 +355,7 @@ async function handleModalSubmit(event) {
|
||||||
parsedAsn.value,
|
parsedAsn.value,
|
||||||
subnetsValue,
|
subnetsValue,
|
||||||
[],
|
[],
|
||||||
|
$resolveDomainsCheckbox.is(':checked'),
|
||||||
selectedRepository,
|
selectedRepository,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
@ -509,6 +515,12 @@ export const LocalStoragesPage = {
|
||||||
<label for="${FIELD_IDS.subnetsInput}">Подсети (каждая с новой строки)</label>
|
<label for="${FIELD_IDS.subnetsInput}">Подсети (каждая с новой строки)</label>
|
||||||
<textarea id="${FIELD_IDS.subnetsInput}" class="form-control" rows="4" placeholder="192.0.2.0/24"></textarea>
|
<textarea id="${FIELD_IDS.subnetsInput}" class="form-control" rows="4" placeholder="192.0.2.0/24"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group-checkbox" style="margin-bottom: 0;">
|
||||||
|
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||||
|
<input type="checkbox" id="${FIELD_IDS.resolveDomainsCheckbox}">
|
||||||
|
Автоматически получать IP адреса доменов
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div id="${FIELD_IDS.modalStatus}" class="error-message" style="display: none;"></div>
|
<div id="${FIELD_IDS.modalStatus}" class="error-message" style="display: none;"></div>
|
||||||
<div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 8px;">
|
<div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 8px;">
|
||||||
<button type="button" id="${FIELD_IDS.modalCancelButton}" class="btn-secondary" style="padding: 10px 18px;">Отмена</button>
|
<button type="button" id="${FIELD_IDS.modalCancelButton}" class="btn-secondary" style="padding: 10px 18px;">Отмена</button>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,11 @@ const FIELD_IDS = {
|
||||||
customDomains: 'settings-custom-domains',
|
customDomains: 'settings-custom-domains',
|
||||||
filteredASN: 'settings-filtered-asn',
|
filteredASN: 'settings-filtered-asn',
|
||||||
filteredSubnets: 'settings-filtered-subnets',
|
filteredSubnets: 'settings-filtered-subnets',
|
||||||
filteredDomains: 'settings-filtered-domains'
|
filteredDomains: 'settings-filtered-domains',
|
||||||
|
cachingDomains: 'settings-caching-domains',
|
||||||
|
domainResolvers: 'settings-domain-resolvers',
|
||||||
|
domainLookupInterval: 'settings-domain-lookup-interval',
|
||||||
|
domainsTimeToLive: 'settings-domains-time-to-live'
|
||||||
};
|
};
|
||||||
|
|
||||||
const CLASS_NAMES = {
|
const CLASS_NAMES = {
|
||||||
|
|
@ -210,6 +214,10 @@ function renderSettingsForm() {
|
||||||
const mergeSubnets = !!getConfigValue('mergeSubnets', false);
|
const mergeSubnets = !!getConfigValue('mergeSubnets', false);
|
||||||
const displayDebuggingInfo = !!getConfigValue('displayDebuggingInfo', false);
|
const displayDebuggingInfo = !!getConfigValue('displayDebuggingInfo', false);
|
||||||
const mergeSubnetsWithUsage = getConfigValue('mergeSubnetsWithUsage', 80);
|
const mergeSubnetsWithUsage = getConfigValue('mergeSubnetsWithUsage', 80);
|
||||||
|
const cachingDomains = !!getConfigValue('cachingDomains', false);
|
||||||
|
const domainResolvers = getConfigValue('domainResolvers', []);
|
||||||
|
const domainLookupInterval = getConfigValue('domainLookupInterval', 60);
|
||||||
|
const domainsTimeToLive = getConfigValue('domainsTimeToLive', 24);
|
||||||
|
|
||||||
const customResources = getConfigValue('customResources', {});
|
const customResources = getConfigValue('customResources', {});
|
||||||
const filteredResources = getConfigValue('filteredResources', {});
|
const filteredResources = getConfigValue('filteredResources', {});
|
||||||
|
|
@ -261,6 +269,24 @@ function renderSettingsForm() {
|
||||||
<label for="${FIELD_IDS.mergeSubnetsWithUsage}">Объединять подсети с заполнением >= %</label>
|
<label for="${FIELD_IDS.mergeSubnetsWithUsage}">Объединять подсети с заполнением >= %</label>
|
||||||
<input type="number" min="51" max="99" id="${FIELD_IDS.mergeSubnetsWithUsage}" class="form-control" value="${mergeSubnetsWithUsage}">
|
<input type="number" min="51" max="99" id="${FIELD_IDS.mergeSubnetsWithUsage}" class="form-control" value="${mergeSubnetsWithUsage}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3 class="config-section-title" style="margin-top: 40px;">DNS Resolver</h3>
|
||||||
|
<div class="form-group checkbox-group">
|
||||||
|
<input type="checkbox" id="${FIELD_IDS.cachingDomains}" ${cachingDomains ? 'checked' : ''}>
|
||||||
|
<label for="${FIELD_IDS.cachingDomains}" style="margin-bottom: 0;">Кешировать домены</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="${FIELD_IDS.domainResolvers}">DNS серверы</label>
|
||||||
|
<textarea id="${FIELD_IDS.domainResolvers}" class="form-control" rows="3" placeholder="8.8.8.8">${textareaFromArray(domainResolvers)}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="${FIELD_IDS.domainLookupInterval}">Интервал проверки доменов (минуты): <span id="domain-lookup-value">${domainLookupInterval}</span></label>
|
||||||
|
<input type="range" min="1" max="${60 * 24}" id="${FIELD_IDS.domainLookupInterval}" class="form-control" value="${domainLookupInterval}" style="padding: 0;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="${FIELD_IDS.domainsTimeToLive}">Время жизни кешированных доменов (часы): <span id="domains-ttl-value">${domainsTimeToLive}</span></label>
|
||||||
|
<input type="range" min="1" max="${24 * 7}" id="${FIELD_IDS.domainsTimeToLive}" class="form-control" value="${domainsTimeToLive}" style="padding: 0;">
|
||||||
|
</div>
|
||||||
<div style="margin-top: 30px;">
|
<div style="margin-top: 30px;">
|
||||||
<h4 style="margin-bottom: 15px;">Дополнительные ресурсы</h4>
|
<h4 style="margin-bottom: 15px;">Дополнительные ресурсы</h4>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
@ -384,6 +410,8 @@ function collectSettingsFromForm() {
|
||||||
const updateSubscriptionsInterval = parseNumberInRange($(`#${FIELD_IDS.updateSubscriptionsInterval}`).val(), 1, 24, 'Интервал обновления подписок');
|
const updateSubscriptionsInterval = parseNumberInRange($(`#${FIELD_IDS.updateSubscriptionsInterval}`).val(), 1, 24, 'Интервал обновления подписок');
|
||||||
const updateASInterval = parseNumberInRange($(`#${FIELD_IDS.updateASInterval}`).val(), 1, 24, 'Интервал обновления ASN');
|
const updateASInterval = parseNumberInRange($(`#${FIELD_IDS.updateASInterval}`).val(), 1, 24, 'Интервал обновления ASN');
|
||||||
const mergeSubnetsWithUsage = parseNumberInRange($(`#${FIELD_IDS.mergeSubnetsWithUsage}`).val(), 51, 99, 'Процент объединения подсетей');
|
const mergeSubnetsWithUsage = parseNumberInRange($(`#${FIELD_IDS.mergeSubnetsWithUsage}`).val(), 51, 99, 'Процент объединения подсетей');
|
||||||
|
const domainLookupInterval = parseNumberInRange($(`#${FIELD_IDS.domainLookupInterval}`).val(), 1, 60 * 24, 'Интервал проверки доменов');
|
||||||
|
const domainsTimeToLive = parseNumberInRange($(`#${FIELD_IDS.domainsTimeToLive}`).val(), 1, 24 * 7, 'Время жизни кешированных доменов');
|
||||||
|
|
||||||
const httpPortValue = parseInt($(`#${FIELD_IDS.httpPort}`).val(), 10);
|
const httpPortValue = parseInt($(`#${FIELD_IDS.httpPort}`).val(), 10);
|
||||||
if (Number.isNaN(httpPortValue) || httpPortValue < 1 || httpPortValue > 65535) {
|
if (Number.isNaN(httpPortValue) || httpPortValue < 1 || httpPortValue > 65535) {
|
||||||
|
|
@ -413,6 +441,10 @@ function collectSettingsFromForm() {
|
||||||
mergeSubnets: $(`#${FIELD_IDS.mergeSubnets}`).prop('checked'),
|
mergeSubnets: $(`#${FIELD_IDS.mergeSubnets}`).prop('checked'),
|
||||||
displayDebuggingInfo: $(`#${FIELD_IDS.displayDebuggingInfo}`).prop('checked'),
|
displayDebuggingInfo: $(`#${FIELD_IDS.displayDebuggingInfo}`).prop('checked'),
|
||||||
mergeSubnetsWithUsage,
|
mergeSubnetsWithUsage,
|
||||||
|
cachingDomains: $(`#${FIELD_IDS.cachingDomains}`).prop('checked'),
|
||||||
|
domainResolvers: parseTextAreaLines($(`#${FIELD_IDS.domainResolvers}`)),
|
||||||
|
domainLookupInterval,
|
||||||
|
domainsTimeToLive,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
customResources,
|
customResources,
|
||||||
filteredResources
|
filteredResources
|
||||||
|
|
@ -468,6 +500,12 @@ function attachEventHandlers() {
|
||||||
$(this).closest(`.${CLASS_NAMES.subscriptionEntry}`).remove();
|
$(this).closest(`.${CLASS_NAMES.subscriptionEntry}`).remove();
|
||||||
});
|
});
|
||||||
$(`#${FIELD_IDS.changePasswordButton}`).off('click').on('click', handleChangePassword);
|
$(`#${FIELD_IDS.changePasswordButton}`).off('click').on('click', handleChangePassword);
|
||||||
|
$(`#${FIELD_IDS.domainLookupInterval}`).off('input').on('input', function () {
|
||||||
|
$('#domain-lookup-value').text($(this).val());
|
||||||
|
});
|
||||||
|
$(`#${FIELD_IDS.domainsTimeToLive}`).off('input').on('input', function () {
|
||||||
|
$('#domains-ttl-value').text($(this).val());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function detachEventHandlers() {
|
function detachEventHandlers() {
|
||||||
|
|
@ -475,6 +513,8 @@ function detachEventHandlers() {
|
||||||
$(`#${FIELD_IDS.addSubscriptionButton}`).off('click');
|
$(`#${FIELD_IDS.addSubscriptionButton}`).off('click');
|
||||||
$(`#${FIELD_IDS.subscriptionsList}`).off('click', `.${CLASS_NAMES.subscriptionRemove}`);
|
$(`#${FIELD_IDS.subscriptionsList}`).off('click', `.${CLASS_NAMES.subscriptionRemove}`);
|
||||||
$(`#${FIELD_IDS.changePasswordButton}`).off('click');
|
$(`#${FIELD_IDS.changePasswordButton}`).off('click');
|
||||||
|
$(`#${FIELD_IDS.domainLookupInterval}`).off('input');
|
||||||
|
$(`#${FIELD_IDS.domainsTimeToLive}`).off('input');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsPage = {
|
export const SettingsPage = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue