mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2025-12-22 09:29:50 +00:00
webapp: implemented UI options for serve stale, logging and block list settings.
This commit is contained in:
@@ -720,16 +720,49 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Logging</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input id="chkEnableLogging" type="checkbox"> Enable Logging
|
||||
</label>
|
||||
</div>
|
||||
<div style="padding-top: 5px;">Enable this option to log error and audit logs into the log file.</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input id="chkLogQueries" type="checkbox"> Log All Queries
|
||||
</label>
|
||||
</div>
|
||||
<div style="padding-top: 5px;">Enable this option to log every query received by this DNS Server and the corresponding response answers into the log file.</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input id="chkUseLocalTime" type="checkbox"> Use Local Time
|
||||
</label>
|
||||
</div>
|
||||
<div style="padding-top: 5px;">Enable this option to use local time instead of UTC for logging.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>Enabling query logging will significantly increase the log file size. Error and audit logs are enabled by default.</div>
|
||||
<div class="form-group">
|
||||
<label for="txtLogFolderPath" class="col-sm-3 control-label">Log Folder Path</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" id="txtLogFolderPath" placeholder="Log Folder Path On Server">
|
||||
</div>
|
||||
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The folder path on the server where the log files should be saved. The path can be relative to the DNS server config folder.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="txtMaxLogFileDays" class="col-sm-3 control-label">Max Log File Days</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" class="form-control" id="txtMaxLogFileDays" placeholder="Max Days" style="width: 100px; display: inline;">
|
||||
<span>days (default 365, must be greater than 1)</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">Max number of days to keep the log files. Log files older than the specified number of days will be deleted automatically.</div>
|
||||
</div>
|
||||
|
||||
<div>Warning: Enabling query logging will significantly increase the log file size.</div>
|
||||
</div>
|
||||
|
||||
<div class="well well-sm form-horizontal">
|
||||
@@ -762,6 +795,29 @@
|
||||
<div>Disable recursion if you wish this server to act only as authoritative name server for the configured zones.</div>
|
||||
</div>
|
||||
|
||||
<div class="well well-sm form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Cache</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input id="chkServeStale" type="checkbox"> Serve Stale
|
||||
</label>
|
||||
</div>
|
||||
<div style="padding-top: 5px;">Enable the serve stale feature to improve resiliency by using expired or stale records in cache when the DNS server is unable to reach the upstream or authoritative name servers.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="txtServeStaleTtl" class="col-sm-3 control-label">Serve Stale TTL</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" class="form-control" id="txtServeStaleTtl" placeholder="seconds" style="width: 100px; display: inline;">
|
||||
<span>(recommended 259200 seconds i.e. 3 days)</span>
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The TTL value in seconds which should be used for cached records that are expired. When the serve stale TTL too expires for a stale record, it gets removed from the cache. Recommended value is between 1-3 days and maximum supported value is 7 days.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="well well-sm form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="txtCachePrefetchEligibility" class="col-sm-3 control-label">Prefetch Eligibility</label>
|
||||
@@ -833,9 +889,6 @@
|
||||
<option value="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/gambling-porn-social/hosts">Steven Black [adware + malware + gambling + porn + social] (https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/gambling-porn-social/hosts)</option>
|
||||
<option value="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts">Steven Black [adware + malware + fakenews + gambling + porn + social] (https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts)</option>
|
||||
|
||||
<option value="https://mirror1.malwaredomains.com/files/justdomains">Malware Domains (https://mirror1.malwaredomains.com/files/justdomains)</option>
|
||||
|
||||
|
||||
<option value="https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt">Disconnect.me [tracking] (https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt)</option>
|
||||
<option value="https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt">Disconnect.me [ads] (https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt)</option>
|
||||
|
||||
@@ -846,7 +899,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px;">DNS Server will use the data returned by the block list URLs to update the blocked zone automatically every 24 hours. The expected file format is standard <code>hosts</code> file format or plain text file containing list of domains to block.</div>
|
||||
<div class="form-group">
|
||||
<label for="txtBlockListUpdateIntervalHours" class="col-sm-3 control-label">Block List Update Interval</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" class="form-control" id="txtBlockListUpdateIntervalHours" placeholder="hours" style="width: 100px; display: inline;">
|
||||
<span>hours (default 24, valid range 1-168)</span>
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The interval in hours to automatically download and update the block lists.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="lblBlockListNextUpdatedOn" class="col-sm-3 control-label">Block List Next Update On</label>
|
||||
<div class="col-sm-6">
|
||||
<span id="lblBlockListNextUpdatedOn" style="margin-right: 15px;"></span>
|
||||
<button id="btnUpdateBlockListsNow" type="button" class="btn btn-default" style="padding: 2px 0; width: 100px;" data-loading-text="Updating..." onclick="return forceUpdateBlockLists();">Update Now</button>
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">Click the 'Update Now' button to reset the next update schedule and force download and update of the block lists.</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px;">DNS Server will use the data returned by the block list URLs to update the block list zone automatically. The expected file format is standard <code>hosts</code> file format or plain text file containing list of domains to block.</div>
|
||||
<div style="margin-top: 10px;"><a href="https://blog.technitium.com/2018/10/blocking-internet-ads-using-dns-sinkhole.html" target="_blank">Help: Blocking Internet Ads Using DNS Sinkhole</a></div>
|
||||
</div>
|
||||
|
||||
@@ -1180,32 +1251,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="well well-sm form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="txtDhcpScopeServerAddress" class="col-sm-3 control-label">Bootstrap Server Address</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" id="txtDhcpScopeServerAddress" placeholder="Bootstrap Server Address">
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The bootstrap TFTP server IP address to be used by the clients. If not specified, the DHCP server's IP address is used.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="txtDhcpScopeServerHostName" class="col-sm-3 control-label">Bootstrap Server Host Name</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" id="txtDhcpScopeServerHostName" placeholder="Bootstrap Server Host Name">
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The optional bootstrap TFTP server host name to be used by the clients to identify the TFTP server.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="txtDhcpScopeBootFileName" class="col-sm-3 control-label">Boot File Name</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" id="txtDhcpScopeBootFileName" placeholder="Bootstrap File Name">
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The boot file name stored on the bootstrap TFTP server to be used by the clients.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="well well-sm form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="txtDhcpScopeRouterAddress" class="col-sm-3 control-label">Router Address</label>
|
||||
@@ -1268,6 +1313,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="well well-sm form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="txtDhcpScopeServerAddress" class="col-sm-3 control-label">Bootstrap Server Address</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" id="txtDhcpScopeServerAddress" placeholder="Bootstrap Server Address">
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The bootstrap TFTP server IP address to be used by the clients. If not specified, the DHCP server's IP address is used.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="txtDhcpScopeServerHostName" class="col-sm-3 control-label">Bootstrap Server Host Name</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" id="txtDhcpScopeServerHostName" placeholder="Bootstrap Server Host Name">
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The optional bootstrap TFTP server host name to be used by the clients to identify the TFTP server.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="txtDhcpScopeBootFileName" class="col-sm-3 control-label">Boot File Name</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" id="txtDhcpScopeBootFileName" placeholder="Bootstrap File Name">
|
||||
</div>
|
||||
<div class="col-sm-offset-3 col-sm-8" style="padding-top: 5px;">The boot file name stored on the bootstrap TFTP server to be used by the clients.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="well well-sm form-horizontal">
|
||||
<div class="form-group" style="margin-bottom: 0px;">
|
||||
<label for="tableDhcpScopeVendorInfo" class="col-sm-3 control-label">Vendor Specific Information</label>
|
||||
|
||||
@@ -141,6 +141,11 @@ $(function () {
|
||||
$("#chkAllowRecursionOnlyForPrivateNetworks").prop('disabled', !allowRecursion);
|
||||
});
|
||||
|
||||
$("#chkServeStale").click(function () {
|
||||
var serveStale = $("#chkServeStale").prop("checked");
|
||||
$("#txtServeStaleTtl").prop("disabled", !serveStale);
|
||||
});
|
||||
|
||||
$("#optQuickBlockList").change(function () {
|
||||
|
||||
var selectedOption = $("#optQuickBlockList").val();
|
||||
@@ -157,7 +162,6 @@ $(function () {
|
||||
var defaultList = "";
|
||||
|
||||
defaultList += "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" + "\n";
|
||||
defaultList += "https://mirror1.malwaredomains.com/files/justdomains" + "\n";
|
||||
defaultList += "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt" + "\n";
|
||||
defaultList += "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt" + "\n";
|
||||
|
||||
@@ -602,12 +606,22 @@ function loadDnsSettings() {
|
||||
$("#txtTlsCertificatePassword").val(responseJSON.response.tlsCertificatePassword);
|
||||
|
||||
$("#chkPreferIPv6").prop("checked", responseJSON.response.preferIPv6);
|
||||
|
||||
$("#chkEnableLogging").prop("checked", responseJSON.response.enableLogging);
|
||||
$("#chkLogQueries").prop("checked", responseJSON.response.logQueries);
|
||||
$("#chkUseLocalTime").prop("checked", responseJSON.response.useLocalTime);
|
||||
$("#txtLogFolderPath").val(responseJSON.response.logFolder);
|
||||
$("#txtMaxLogFileDays").val(responseJSON.response.maxLogFileDays);
|
||||
|
||||
$("#chkAllowRecursion").prop("checked", responseJSON.response.allowRecursion);
|
||||
$("#chkAllowRecursionOnlyForPrivateNetworks").prop('disabled', !responseJSON.response.allowRecursion);
|
||||
$("#chkAllowRecursionOnlyForPrivateNetworks").prop("checked", responseJSON.response.allowRecursionOnlyForPrivateNetworks);
|
||||
$("#chkRandomizeName").prop("checked", responseJSON.response.randomizeName);
|
||||
|
||||
$("#chkServeStale").prop("checked", responseJSON.response.serveStale);
|
||||
$("#txtServeStaleTtl").prop("disabled", !responseJSON.response.serveStale);
|
||||
$("#txtServeStaleTtl").val(responseJSON.response.serveStaleTtl);
|
||||
|
||||
$("#txtCachePrefetchEligibility").val(responseJSON.response.cachePrefetchEligibility);
|
||||
$("#txtCachePrefetchTrigger").val(responseJSON.response.cachePrefetchTrigger);
|
||||
$("#txtCachePrefetchSampleIntervalInMinutes").val(responseJSON.response.cachePrefetchSampleIntervalInMinutes);
|
||||
@@ -725,6 +739,9 @@ function loadDnsSettings() {
|
||||
optCustomLocalBlockList.text("Custom Local Block List (http://localhost:" + responseJSON.response.webServicePort + "/blocklist.txt)");
|
||||
}
|
||||
|
||||
$("#txtBlockListUpdateIntervalHours").val(responseJSON.response.blockListUpdateIntervalHours);
|
||||
$("#lblBlockListNextUpdatedOn").text(responseJSON.response.blockListNextUpdatedOn);
|
||||
|
||||
divDnsSettingsLoader.hide();
|
||||
divDnsSettings.show();
|
||||
},
|
||||
@@ -769,11 +786,20 @@ function saveDnsSettings() {
|
||||
var tlsCertificatePassword = $("#txtTlsCertificatePassword").val();
|
||||
|
||||
var preferIPv6 = $("#chkPreferIPv6").prop('checked');
|
||||
|
||||
var enableLogging = $("#chkEnableLogging").prop('checked');
|
||||
var logQueries = $("#chkLogQueries").prop('checked');
|
||||
var useLocalTime = $("#chkUseLocalTime").prop('checked');
|
||||
var logFolder = $("#txtLogFolderPath").val();
|
||||
var maxLogFileDays = $("#txtMaxLogFileDays").val();
|
||||
|
||||
var allowRecursion = $("#chkAllowRecursion").prop('checked');
|
||||
var allowRecursionOnlyForPrivateNetworks = $("#chkAllowRecursionOnlyForPrivateNetworks").prop('checked');
|
||||
var randomizeName = $("#chkRandomizeName").prop('checked');
|
||||
|
||||
var serveStale = $("#chkServeStale").prop("checked");
|
||||
var serveStaleTtl = $("#txtServeStaleTtl").val();
|
||||
|
||||
var cachePrefetchEligibility = $("#txtCachePrefetchEligibility").val();
|
||||
if ((cachePrefetchEligibility === null) || (cachePrefetchEligibility === "")) {
|
||||
showAlert("warning", "Missing!", "Please enter cache prefetch eligibility value.");
|
||||
@@ -850,14 +876,17 @@ function saveDnsSettings() {
|
||||
else
|
||||
$("#txtBlockListUrls").val(blockListUrls.replace(/,/g, "\n") + "\n");
|
||||
|
||||
var blockListUpdateIntervalHours = $("#txtBlockListUpdateIntervalHours").val();
|
||||
|
||||
var btn = $("#btnSaveDnsSettings").button('loading');
|
||||
|
||||
HTTPRequest({
|
||||
url: "/api/setDnsSettings?token=" + token + "&serverDomain=" + serverDomain + "&webServicePort=" + webServicePort + "&dnsServerLocalEndPoints=" + encodeURIComponent(dnsServerLocalEndPoints)
|
||||
+ "&enableDnsOverHttp=" + enableDnsOverHttp + "&enableDnsOverTls=" + enableDnsOverTls + "&enableDnsOverHttps=" + enableDnsOverHttps + "&tlsCertificatePath=" + encodeURIComponent(tlsCertificatePath) + "&tlsCertificatePassword=" + encodeURIComponent(tlsCertificatePassword)
|
||||
+ "&preferIPv6=" + preferIPv6 + "&logQueries=" + logQueries + "&allowRecursion=" + allowRecursion + "&allowRecursionOnlyForPrivateNetworks=" + allowRecursionOnlyForPrivateNetworks + "&randomizeName=" + randomizeName
|
||||
+ "&cachePrefetchEligibility=" + cachePrefetchEligibility + "&cachePrefetchTrigger=" + cachePrefetchTrigger + "&cachePrefetchSampleIntervalInMinutes=" + cachePrefetchSampleIntervalInMinutes + "&cachePrefetchSampleEligibilityHitsPerHour=" + cachePrefetchSampleEligibilityHitsPerHour
|
||||
+ proxy + "&forwarders=" + encodeURIComponent(forwarders) + "&forwarderProtocol=" + forwarderProtocol + "&blockListUrls=" + encodeURIComponent(blockListUrls),
|
||||
+ "&preferIPv6=" + preferIPv6 + "&enableLogging=" + enableLogging + "&logQueries=" + logQueries + "&useLocalTime=" + useLocalTime + "&logFolder=" + encodeURIComponent(logFolder) + "&maxLogFileDays=" + maxLogFileDays
|
||||
+ "&allowRecursion=" + allowRecursion + "&allowRecursionOnlyForPrivateNetworks=" + allowRecursionOnlyForPrivateNetworks + "&randomizeName=" + randomizeName
|
||||
+ "&serveStale=" + serveStale + "&serveStaleTtl=" + serveStaleTtl + "&cachePrefetchEligibility=" + cachePrefetchEligibility + "&cachePrefetchTrigger=" + cachePrefetchTrigger + "&cachePrefetchSampleIntervalInMinutes=" + cachePrefetchSampleIntervalInMinutes + "&cachePrefetchSampleEligibilityHitsPerHour=" + cachePrefetchSampleEligibilityHitsPerHour
|
||||
+ proxy + "&forwarders=" + encodeURIComponent(forwarders) + "&forwarderProtocol=" + forwarderProtocol + "&blockListUrls=" + encodeURIComponent(blockListUrls) + "&blockListUpdateIntervalHours=" + blockListUpdateIntervalHours,
|
||||
success: function (responseJSON) {
|
||||
document.title = "Technitium DNS Server " + responseJSON.response.version + " - " + responseJSON.response.serverDomain;
|
||||
$("#lblServerDomain").text(" - " + responseJSON.response.serverDomain);
|
||||
@@ -886,6 +915,30 @@ function saveDnsSettings() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function forceUpdateBlockLists() {
|
||||
if (!confirm("Are you sure to force download and update the block lists?"))
|
||||
return false;
|
||||
|
||||
var btn = $("#btnUpdateBlockListsNow").button('loading');
|
||||
|
||||
HTTPRequest({
|
||||
url: "/api/forceUpdateBlockLists?token=" + token,
|
||||
success: function (responseJSON) {
|
||||
btn.button('reset');
|
||||
showAlert("success", "Updating Block List!", "Block list update was triggered successfully.");
|
||||
},
|
||||
error: function () {
|
||||
btn.button('reset');
|
||||
},
|
||||
invalidToken: function () {
|
||||
btn.button('reset');
|
||||
showPageLogin();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function cleanTextList(text) {
|
||||
text = text.replace(/\n/g, ",");
|
||||
|
||||
@@ -1611,12 +1664,19 @@ function refreshLogFilesList() {
|
||||
success: function (responseJSON) {
|
||||
var logFiles = responseJSON.response.logFiles;
|
||||
|
||||
var list = "";
|
||||
var list = "<div class=\"log\" style=\"font-size: 14px; padding-bottom: 6px;\"><a href=\"#\" onclick=\"return deleteAllStats();\"><b>[delete all stats]</b></a></div>";
|
||||
|
||||
for (var i = 0; i < logFiles.length; i++) {
|
||||
var logFile = logFiles[i];
|
||||
if (logFiles.length == 0) {
|
||||
list += "<div class=\"log\">No Log Was Found</div>";
|
||||
}
|
||||
else {
|
||||
list += "<div class=\"log\" style=\"font-size: 14px; padding-bottom: 6px;\"><a href=\"#\" onclick=\"return deleteAllLogs();\"><b>[delete all logs]</b></a></div>";
|
||||
|
||||
list += "<div class=\"log\"><a href=\"#\" onclick=\"return viewLog('" + logFile.fileName + "');\">" + logFile.fileName + " [" + logFile.size + "]</a></div>"
|
||||
for (var i = 0; i < logFiles.length; i++) {
|
||||
var logFile = logFiles[i];
|
||||
|
||||
list += "<div class=\"log\"><a href=\"#\" onclick=\"return viewLog('" + logFile.fileName + "');\">" + logFile.fileName + " [" + logFile.size + "]</a></div>"
|
||||
}
|
||||
}
|
||||
|
||||
lstLogFiles.html(list);
|
||||
@@ -1698,6 +1758,46 @@ function deleteLog() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteAllLogs() {
|
||||
|
||||
if (!confirm("Are you sure you want to permanently delete all log files?"))
|
||||
return false;
|
||||
|
||||
HTTPRequest({
|
||||
url: "/api/deleteAllLogs?token=" + token,
|
||||
success: function (responseJSON) {
|
||||
refreshLogFilesList();
|
||||
|
||||
$("#divLogViewer").hide();
|
||||
|
||||
showAlert("success", "Logs Deleted!", "All log files were deleted successfully.");
|
||||
},
|
||||
invalidToken: function () {
|
||||
showPageLogin();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteAllStats() {
|
||||
|
||||
if (!confirm("Are you sure you want to permanently delete all stats files?"))
|
||||
return false;
|
||||
|
||||
HTTPRequest({
|
||||
url: "/api/deleteAllStats?token=" + token,
|
||||
success: function (responseJSON) {
|
||||
showAlert("success", "Stats Deleted!", "All stats files were deleted successfully.");
|
||||
},
|
||||
invalidToken: function () {
|
||||
showPageLogin();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resetImportAllowedZonesModal() {
|
||||
|
||||
$("#divImportAllowedZonesAlert").html("");
|
||||
|
||||
Reference in New Issue
Block a user