diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html
index 0ea106fb6..fbb110020 100644
--- a/baseTemplate/templates/baseTemplate/index.html
+++ b/baseTemplate/templates/baseTemplate/index.html
@@ -2559,7 +2559,7 @@
-
+
diff --git a/firewall/static/firewall/firewall.js b/firewall/static/firewall/firewall.js
index 829434102..3a0445213 100644
--- a/firewall/static/firewall/firewall.js
+++ b/firewall/static/firewall/firewall.js
@@ -23,6 +23,8 @@ function getCookie(name) {
app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.rulesLoading = true;
+ /** Incremented on each rules fetch; stale HTTP responses must not touch rulesLoading. */
+ var rulesFetchGen = 0;
$scope.actionFailed = true;
$scope.actionSuccess = true;
$scope.showExportFormatModal = false;
@@ -554,7 +556,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
populateCurrentRecords();
- $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -566,7 +567,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -582,7 +583,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -596,8 +597,8 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
};
function populateCurrentRecords() {
-
- $scope.rulesLoading = false;
+ var gen = ++rulesFetchGen;
+ $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -615,21 +616,44 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
- var res = (typeof response.data === 'string') ? (function() { try { return JSON.parse(response.data); } catch (e) { return {}; } })() : response.data;
- if (res && res.fetchStatus === 1) {
- $scope.rules = typeof res.data === 'string' ? JSON.parse(res.data) : (res.data || []);
- $scope.rulesTotalCount = res.total_count != null ? res.total_count : ($scope.rules ? $scope.rules.length : 0);
- $scope.rulesPage = Math.max(1, res.page != null ? res.page : 1);
- $scope.rulesPageSize = res.page_size != null ? res.page_size : 10;
- $scope.rulesLoading = true;
+ if (gen !== rulesFetchGen) {
+ return;
}
- else {
- $scope.rulesLoading = true;
- $scope.errorMessage = (res && res.error_message) ? res.error_message : '';
+ try {
+ var res = (typeof response.data === 'string') ? (function() { try { return JSON.parse(response.data); } catch (e) { return {}; } })() : response.data;
+ if (res && res.fetchStatus == 1) {
+ var parsedRules = [];
+ if (typeof res.data === 'string') {
+ try {
+ parsedRules = JSON.parse(res.data);
+ } catch (parseErr) {
+ parsedRules = [];
+ $scope.errorMessage = (res && res.error_message) ? res.error_message : 'Invalid rules data';
+ }
+ } else {
+ parsedRules = res.data || [];
+ }
+ $scope.rules = parsedRules;
+ $scope.rulesTotalCount = res.total_count != null ? res.total_count : ($scope.rules ? $scope.rules.length : 0);
+ $scope.rulesPage = Math.max(1, res.page != null ? res.page : 1);
+ $scope.rulesPageSize = res.page_size != null ? res.page_size : 10;
+ } else {
+ $scope.errorMessage = (res && res.error_message) ? res.error_message : '';
+ }
+ } catch (e) {
+ $scope.errorMessage = 'Could not load firewall rules.';
+ } finally {
+ if (gen === rulesFetchGen) {
+ $scope.rulesLoading = false;
+ }
}
}
function cantLoadInitialDatas(response) {
+ if (gen !== rulesFetchGen) {
+ return;
+ }
+ $scope.rulesLoading = false;
$scope.couldNotConnect = false;
}
}
@@ -708,7 +732,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.errorMessage = 'Port is required';
return;
}
- $scope.rulesLoading = false;
+ $scope.rulesLoading = true;
var url = '/firewall/modifyRule';
var data = {
id: d.id,
@@ -719,19 +743,19 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
};
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function(response) {
- $scope.rulesLoading = true;
if (response.data && response.data.status === 1) {
$scope.closeModifyRuleModal();
$scope.actionFailed = true;
$scope.actionSuccess = false;
populateCurrentRecords();
} else {
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = (response.data && response.data.error_message) || 'Modify failed';
}
}, function() {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = 'Could not connect to server. Please refresh this page.';
@@ -740,7 +764,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.deleteRule = function (id, proto, port, ruleIP) {
- $scope.rulesLoading = false;
+ $scope.rulesLoading = true;
url = "/firewall/deleteRule";
@@ -768,7 +792,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
populateCurrentRecords();
- $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -780,7 +803,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -788,7 +811,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.ruleAdded = true;
$scope.couldNotConnect = true;
- $scope.rulesLoading = true;
$scope.errorMessage = response.data.error_message;
@@ -798,7 +820,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -845,7 +867,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.reload_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -857,7 +879,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -874,7 +896,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -920,7 +942,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.start_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -936,7 +958,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -953,7 +975,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -1000,7 +1022,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.stop_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -1016,7 +1038,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -1033,7 +1055,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -1498,6 +1520,8 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
}
+ $scope.populateCurrentRecords = populateCurrentRecords;
+
});
diff --git a/public/static/firewall/firewall.js b/public/static/firewall/firewall.js
index 3e11e504c..3a0445213 100644
--- a/public/static/firewall/firewall.js
+++ b/public/static/firewall/firewall.js
@@ -23,6 +23,8 @@ function getCookie(name) {
app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.rulesLoading = true;
+ /** Incremented on each rules fetch; stale HTTP responses must not touch rulesLoading. */
+ var rulesFetchGen = 0;
$scope.actionFailed = true;
$scope.actionSuccess = true;
$scope.showExportFormatModal = false;
@@ -40,9 +42,12 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
// Initialize rules array - prevents "Cannot read 'length' of undefined" when template evaluates rules.length before API loads
$scope.rules = [];
// Banned IPs variables – tab from hash so we stay on /firewall/ (avoids 404 on servers without /firewall/firewall-rules/)
+ /* Use window.location.hash only. Angular $location can disagree with the fragment on /firewall/#… and wrongly map to "rules". */
function tabFromHash() {
- var h = (window.location.hash || '').replace(/^#/, '');
- return (h === 'banned-ips') ? 'banned' : 'rules';
+ var h = String(window.location.hash || '').replace(/^#/, '').toLowerCase();
+ if (h === 'banned-ips' || h === 'banned') return 'banned';
+ if (h === 'trusted-ips' || h === 'ssh-whitelist') return 'trusted';
+ return 'rules';
}
$scope.activeTab = tabFromHash();
$scope.bannedIPs = []; // Initialize as empty array
@@ -52,7 +57,9 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
var tab = tabFromHash();
if ($scope.activeTab !== tab) {
$scope.activeTab = tab;
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
+ if (tab === 'banned') { populateBannedIPs(); }
+ else if (tab === 'trusted') { populateTrustedSSHWhitelist(); }
+ else { populateCurrentRecords(); }
if (!$scope.$$phase && !$scope.$root.$$phase) { $scope.$apply(); }
}
}
@@ -63,23 +70,51 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
window.addEventListener('load', function() { $timeout(applyTabFromHash, 0); });
}
- // Sync tab with hash and load that tab's data on switch
- $scope.setFirewallTab = function(tab) {
+ // Sync tab with hash and load that tab's data on switch (single source of truth from ng-click).
+ $scope.setFirewallTab = function(tab, $event) {
+ if ($event) {
+ try {
+ $event.stopPropagation();
+ } catch (ignoreErr) {}
+ }
$timeout(function() {
$scope.activeTab = tab;
- window.location.hash = (tab === 'banned') ? '#banned-ips' : '#rules';
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
+ function setHashIfNeeded(frag) {
+ try {
+ if ((window.location.hash || '') === frag) {
+ return;
+ }
+ var path = window.location.pathname + window.location.search + frag;
+ if (window.history && typeof window.history.replaceState === 'function') {
+ window.history.replaceState(null, '', path);
+ } else {
+ window.location.hash = frag;
+ }
+ } catch (ignoreHash) {}
+ }
+ if (tab === 'banned') {
+ setHashIfNeeded('#banned-ips');
+ populateBannedIPs();
+ } else if (tab === 'trusted') {
+ setHashIfNeeded('#trusted-ips');
+ populateTrustedSSHWhitelist();
+ } else {
+ setHashIfNeeded('#rules');
+ populateCurrentRecords();
+ }
}, 0);
};
- // Back/forward or direct hash change: sync tab and load its data
function syncTabFromHash() {
var tab = tabFromHash();
- if ($scope.activeTab !== tab) {
- $scope.activeTab = tab;
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
- if (!$scope.$$phase && !$scope.$root.$$phase) { $scope.$apply(); }
- }
+ $scope.$evalAsync(function() {
+ if ($scope.activeTab !== tab) {
+ $scope.activeTab = tab;
+ if (tab === 'banned') { populateBannedIPs(); }
+ else if (tab === 'trusted') { populateTrustedSSHWhitelist(); }
+ else { populateCurrentRecords(); }
+ }
+ });
}
window.addEventListener('hashchange', syncTabFromHash);
@@ -108,6 +143,9 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.banIP = '';
$scope.banReason = '';
$scope.banDuration = '24h';
+ $scope.trustedSSHWhitelist = [];
+ $scope.trustedForm = { ip: '', label: '' };
+ $scope.trustedSSHLoading = false;
$scope.bannedIPSearch = '';
$scope.searchBannedIPFilter = function(item) {
var q = ($scope.bannedIPSearch || '').toLowerCase().trim();
@@ -142,6 +180,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$timeout(function() {
try {
if (newVal === 'banned' && typeof populateBannedIPs === 'function') populateBannedIPs();
+ else if (newVal === 'trusted' && typeof populateTrustedSSHWhitelist === 'function') populateTrustedSSHWhitelist();
else if (newVal === 'rules' && typeof populateCurrentRecords === 'function') populateCurrentRecords();
} catch (e) {}
}, 0);
@@ -246,6 +285,156 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
);
}
+
+ function populateTrustedSSHWhitelist() {
+ $scope.trustedSSHLoading = true;
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistList', {}, config).then(
+ function(response) {
+ $scope.trustedSSHLoading = false;
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ if (res && res.status === 1) {
+ var ent = res.entries || [];
+ $scope.trustedSSHWhitelist = ent.map(function(e) {
+ return {
+ ip: e.ip,
+ label: e.label || '',
+ updated: e.updated || 0,
+ _l: e.label || '',
+ _nip: ''
+ };
+ });
+ } else {
+ $scope.trustedSSHWhitelist = [];
+ var errMsg = (res && res.error) ? res.error : 'Could not load trusted IPs';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errMsg, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(error) {
+ $scope.trustedSSHLoading = false;
+ $scope.trustedSSHWhitelist = [];
+ var msg = (error.data && error.data.error) ? error.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: msg, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ }
+
+ $scope.populateTrustedSSHWhitelist = function() {
+ populateTrustedSSHWhitelist();
+ };
+
+ $scope.addTrustedSSHWhitelist = function() {
+ var ip = ($scope.trustedForm.ip || '').trim();
+ var label = ($scope.trustedForm.label || '').trim();
+ if (!ip) {
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: 'Enter an IP address', type: 'warning', delay: 5000 });
+ }
+ return;
+ }
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistAdd', { ip: ip, label: label }, config).then(
+ function(response) {
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ if (res && res.status === 1) {
+ $scope.trustedForm.ip = '';
+ $scope.trustedForm.label = '';
+ populateTrustedSSHWhitelist();
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: 'IP added to trusted list', type: 'success', delay: 4000 });
+ }
+ } else {
+ var errAdd = (res && res.error) ? res.error : 'Failed to add';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errAdd, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(err) {
+ var em = (err.data && err.data.error) ? err.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: em, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ };
+
+ $scope.removeTrustedSSHWhitelist = function(ip) {
+ if (!ip) return;
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistRemove', { ip: ip }, config).then(
+ function(response) {
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ if (res && res.status === 1) {
+ populateTrustedSSHWhitelist();
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: 'IP removed', type: 'success', delay: 4000 });
+ }
+ } else {
+ var errRm = (res && res.error) ? res.error : 'Failed to remove';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errRm, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(err) {
+ var em2 = (err.data && err.data.error) ? err.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: em2, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ };
+
+ $scope.saveTrustedSSHWhitelistRow = function(row) {
+ if (!row || !row.ip) return;
+ var payload = { ip: row.ip, label: row._l };
+ if (row._nip && String(row._nip).trim()) {
+ payload.new_ip = String(row._nip).trim();
+ }
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistUpdate', payload, config).then(
+ function(response) {
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ var ok = res && (res.status === 1 || res.status === '1');
+ if (ok) {
+ populateTrustedSSHWhitelist();
+ if (typeof PNotify !== 'undefined') {
+ var unchanged = res.unchanged === true || res.unchanged === 'true' || res.unchanged === 1;
+ var msgOk = (res.message && String(res.message).length) ? res.message : (unchanged ? 'No changes to save.' : 'Entry updated');
+ new PNotify({ title: 'Trusted IPs', text: msgOk, type: unchanged ? 'info' : 'success', delay: 4000 });
+ }
+ } else {
+ var errUp = (res && res.error) ? res.error : 'Failed to update';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errUp, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(err) {
+ var em3 = (err.data && err.data.error) ? err.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: em3, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ };
// Expose to scope for template access
$scope.populateBannedIPs = function() {
@@ -301,7 +490,9 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
window.__firewallLoadTab = function(tab) {
$scope.$evalAsync(function() {
$scope.activeTab = tab;
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
+ if (tab === 'banned') { populateBannedIPs(); }
+ else if (tab === 'trusted') { populateTrustedSSHWhitelist(); }
+ else { populateCurrentRecords(); }
});
};
}
@@ -365,7 +556,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
populateCurrentRecords();
- $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -377,7 +567,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -393,7 +583,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -407,8 +597,8 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
};
function populateCurrentRecords() {
-
- $scope.rulesLoading = false;
+ var gen = ++rulesFetchGen;
+ $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -426,21 +616,44 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
- var res = (typeof response.data === 'string') ? (function() { try { return JSON.parse(response.data); } catch (e) { return {}; } })() : response.data;
- if (res && res.fetchStatus === 1) {
- $scope.rules = typeof res.data === 'string' ? JSON.parse(res.data) : (res.data || []);
- $scope.rulesTotalCount = res.total_count != null ? res.total_count : ($scope.rules ? $scope.rules.length : 0);
- $scope.rulesPage = Math.max(1, res.page != null ? res.page : 1);
- $scope.rulesPageSize = res.page_size != null ? res.page_size : 10;
- $scope.rulesLoading = true;
+ if (gen !== rulesFetchGen) {
+ return;
}
- else {
- $scope.rulesLoading = true;
- $scope.errorMessage = (res && res.error_message) ? res.error_message : '';
+ try {
+ var res = (typeof response.data === 'string') ? (function() { try { return JSON.parse(response.data); } catch (e) { return {}; } })() : response.data;
+ if (res && res.fetchStatus == 1) {
+ var parsedRules = [];
+ if (typeof res.data === 'string') {
+ try {
+ parsedRules = JSON.parse(res.data);
+ } catch (parseErr) {
+ parsedRules = [];
+ $scope.errorMessage = (res && res.error_message) ? res.error_message : 'Invalid rules data';
+ }
+ } else {
+ parsedRules = res.data || [];
+ }
+ $scope.rules = parsedRules;
+ $scope.rulesTotalCount = res.total_count != null ? res.total_count : ($scope.rules ? $scope.rules.length : 0);
+ $scope.rulesPage = Math.max(1, res.page != null ? res.page : 1);
+ $scope.rulesPageSize = res.page_size != null ? res.page_size : 10;
+ } else {
+ $scope.errorMessage = (res && res.error_message) ? res.error_message : '';
+ }
+ } catch (e) {
+ $scope.errorMessage = 'Could not load firewall rules.';
+ } finally {
+ if (gen === rulesFetchGen) {
+ $scope.rulesLoading = false;
+ }
}
}
function cantLoadInitialDatas(response) {
+ if (gen !== rulesFetchGen) {
+ return;
+ }
+ $scope.rulesLoading = false;
$scope.couldNotConnect = false;
}
}
@@ -519,7 +732,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.errorMessage = 'Port is required';
return;
}
- $scope.rulesLoading = false;
+ $scope.rulesLoading = true;
var url = '/firewall/modifyRule';
var data = {
id: d.id,
@@ -530,19 +743,19 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
};
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function(response) {
- $scope.rulesLoading = true;
if (response.data && response.data.status === 1) {
$scope.closeModifyRuleModal();
$scope.actionFailed = true;
$scope.actionSuccess = false;
populateCurrentRecords();
} else {
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = (response.data && response.data.error_message) || 'Modify failed';
}
}, function() {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = 'Could not connect to server. Please refresh this page.';
@@ -551,7 +764,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.deleteRule = function (id, proto, port, ruleIP) {
- $scope.rulesLoading = false;
+ $scope.rulesLoading = true;
url = "/firewall/deleteRule";
@@ -579,7 +792,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
populateCurrentRecords();
- $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -591,7 +803,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -599,7 +811,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.ruleAdded = true;
$scope.couldNotConnect = true;
- $scope.rulesLoading = true;
$scope.errorMessage = response.data.error_message;
@@ -609,7 +820,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -656,7 +867,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.reload_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -668,7 +879,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -685,7 +896,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -731,7 +942,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.start_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -747,7 +958,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -764,7 +975,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -811,7 +1022,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.stop_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -827,7 +1038,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -844,7 +1055,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -1309,6 +1520,8 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
}
+ $scope.populateCurrentRecords = populateCurrentRecords;
+
});
@@ -3254,18 +3467,20 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
function syncFirewallTabFromHash() {
var nav = document.getElementById('firewall-tab-nav');
if (!nav) return;
- var h = (window.location.hash || '').replace(/^#/, '');
- var tab = (h === 'banned-ips') ? 'banned' : 'rules';
+ var h = (window.location.hash || '').replace(/^#/, '').toLowerCase();
+ var tab = 'rules';
+ if (h === 'banned-ips' || h === 'banned') tab = 'banned';
+ else if (h === 'trusted-ips' || h === 'ssh-whitelist') tab = 'trusted';
if (window.__firewallLoadTab) {
try { window.__firewallLoadTab(tab); } catch (e) {}
}
}
+ /* Initial sync only — hashchange is handled by Angular syncTabFromHash in firewallController
+ (multiple listeners were racing and could reset #trusted-ips to #rules). */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', syncFirewallTabFromHash);
} else {
syncFirewallTabFromHash();
}
- setTimeout(syncFirewallTabFromHash, 100);
- window.addEventListener('hashchange', syncFirewallTabFromHash);
})();
\ No newline at end of file
diff --git a/static/firewall/firewall.js b/static/firewall/firewall.js
index 3e11e504c..3a0445213 100644
--- a/static/firewall/firewall.js
+++ b/static/firewall/firewall.js
@@ -23,6 +23,8 @@ function getCookie(name) {
app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.rulesLoading = true;
+ /** Incremented on each rules fetch; stale HTTP responses must not touch rulesLoading. */
+ var rulesFetchGen = 0;
$scope.actionFailed = true;
$scope.actionSuccess = true;
$scope.showExportFormatModal = false;
@@ -40,9 +42,12 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
// Initialize rules array - prevents "Cannot read 'length' of undefined" when template evaluates rules.length before API loads
$scope.rules = [];
// Banned IPs variables – tab from hash so we stay on /firewall/ (avoids 404 on servers without /firewall/firewall-rules/)
+ /* Use window.location.hash only. Angular $location can disagree with the fragment on /firewall/#… and wrongly map to "rules". */
function tabFromHash() {
- var h = (window.location.hash || '').replace(/^#/, '');
- return (h === 'banned-ips') ? 'banned' : 'rules';
+ var h = String(window.location.hash || '').replace(/^#/, '').toLowerCase();
+ if (h === 'banned-ips' || h === 'banned') return 'banned';
+ if (h === 'trusted-ips' || h === 'ssh-whitelist') return 'trusted';
+ return 'rules';
}
$scope.activeTab = tabFromHash();
$scope.bannedIPs = []; // Initialize as empty array
@@ -52,7 +57,9 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
var tab = tabFromHash();
if ($scope.activeTab !== tab) {
$scope.activeTab = tab;
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
+ if (tab === 'banned') { populateBannedIPs(); }
+ else if (tab === 'trusted') { populateTrustedSSHWhitelist(); }
+ else { populateCurrentRecords(); }
if (!$scope.$$phase && !$scope.$root.$$phase) { $scope.$apply(); }
}
}
@@ -63,23 +70,51 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
window.addEventListener('load', function() { $timeout(applyTabFromHash, 0); });
}
- // Sync tab with hash and load that tab's data on switch
- $scope.setFirewallTab = function(tab) {
+ // Sync tab with hash and load that tab's data on switch (single source of truth from ng-click).
+ $scope.setFirewallTab = function(tab, $event) {
+ if ($event) {
+ try {
+ $event.stopPropagation();
+ } catch (ignoreErr) {}
+ }
$timeout(function() {
$scope.activeTab = tab;
- window.location.hash = (tab === 'banned') ? '#banned-ips' : '#rules';
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
+ function setHashIfNeeded(frag) {
+ try {
+ if ((window.location.hash || '') === frag) {
+ return;
+ }
+ var path = window.location.pathname + window.location.search + frag;
+ if (window.history && typeof window.history.replaceState === 'function') {
+ window.history.replaceState(null, '', path);
+ } else {
+ window.location.hash = frag;
+ }
+ } catch (ignoreHash) {}
+ }
+ if (tab === 'banned') {
+ setHashIfNeeded('#banned-ips');
+ populateBannedIPs();
+ } else if (tab === 'trusted') {
+ setHashIfNeeded('#trusted-ips');
+ populateTrustedSSHWhitelist();
+ } else {
+ setHashIfNeeded('#rules');
+ populateCurrentRecords();
+ }
}, 0);
};
- // Back/forward or direct hash change: sync tab and load its data
function syncTabFromHash() {
var tab = tabFromHash();
- if ($scope.activeTab !== tab) {
- $scope.activeTab = tab;
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
- if (!$scope.$$phase && !$scope.$root.$$phase) { $scope.$apply(); }
- }
+ $scope.$evalAsync(function() {
+ if ($scope.activeTab !== tab) {
+ $scope.activeTab = tab;
+ if (tab === 'banned') { populateBannedIPs(); }
+ else if (tab === 'trusted') { populateTrustedSSHWhitelist(); }
+ else { populateCurrentRecords(); }
+ }
+ });
}
window.addEventListener('hashchange', syncTabFromHash);
@@ -108,6 +143,9 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.banIP = '';
$scope.banReason = '';
$scope.banDuration = '24h';
+ $scope.trustedSSHWhitelist = [];
+ $scope.trustedForm = { ip: '', label: '' };
+ $scope.trustedSSHLoading = false;
$scope.bannedIPSearch = '';
$scope.searchBannedIPFilter = function(item) {
var q = ($scope.bannedIPSearch || '').toLowerCase().trim();
@@ -142,6 +180,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$timeout(function() {
try {
if (newVal === 'banned' && typeof populateBannedIPs === 'function') populateBannedIPs();
+ else if (newVal === 'trusted' && typeof populateTrustedSSHWhitelist === 'function') populateTrustedSSHWhitelist();
else if (newVal === 'rules' && typeof populateCurrentRecords === 'function') populateCurrentRecords();
} catch (e) {}
}, 0);
@@ -246,6 +285,156 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
);
}
+
+ function populateTrustedSSHWhitelist() {
+ $scope.trustedSSHLoading = true;
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistList', {}, config).then(
+ function(response) {
+ $scope.trustedSSHLoading = false;
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ if (res && res.status === 1) {
+ var ent = res.entries || [];
+ $scope.trustedSSHWhitelist = ent.map(function(e) {
+ return {
+ ip: e.ip,
+ label: e.label || '',
+ updated: e.updated || 0,
+ _l: e.label || '',
+ _nip: ''
+ };
+ });
+ } else {
+ $scope.trustedSSHWhitelist = [];
+ var errMsg = (res && res.error) ? res.error : 'Could not load trusted IPs';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errMsg, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(error) {
+ $scope.trustedSSHLoading = false;
+ $scope.trustedSSHWhitelist = [];
+ var msg = (error.data && error.data.error) ? error.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: msg, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ }
+
+ $scope.populateTrustedSSHWhitelist = function() {
+ populateTrustedSSHWhitelist();
+ };
+
+ $scope.addTrustedSSHWhitelist = function() {
+ var ip = ($scope.trustedForm.ip || '').trim();
+ var label = ($scope.trustedForm.label || '').trim();
+ if (!ip) {
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: 'Enter an IP address', type: 'warning', delay: 5000 });
+ }
+ return;
+ }
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistAdd', { ip: ip, label: label }, config).then(
+ function(response) {
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ if (res && res.status === 1) {
+ $scope.trustedForm.ip = '';
+ $scope.trustedForm.label = '';
+ populateTrustedSSHWhitelist();
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: 'IP added to trusted list', type: 'success', delay: 4000 });
+ }
+ } else {
+ var errAdd = (res && res.error) ? res.error : 'Failed to add';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errAdd, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(err) {
+ var em = (err.data && err.data.error) ? err.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: em, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ };
+
+ $scope.removeTrustedSSHWhitelist = function(ip) {
+ if (!ip) return;
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistRemove', { ip: ip }, config).then(
+ function(response) {
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ if (res && res.status === 1) {
+ populateTrustedSSHWhitelist();
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: 'IP removed', type: 'success', delay: 4000 });
+ }
+ } else {
+ var errRm = (res && res.error) ? res.error : 'Failed to remove';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errRm, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(err) {
+ var em2 = (err.data && err.data.error) ? err.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: em2, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ };
+
+ $scope.saveTrustedSSHWhitelistRow = function(row) {
+ if (!row || !row.ip) return;
+ var payload = { ip: row.ip, label: row._l };
+ if (row._nip && String(row._nip).trim()) {
+ payload.new_ip = String(row._nip).trim();
+ }
+ var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') || '' } };
+ $http.post('/base/sshSecurityWhitelistUpdate', payload, config).then(
+ function(response) {
+ var res = response.data;
+ if (typeof res === 'string') {
+ try { res = JSON.parse(res); } catch (e) { res = {}; }
+ }
+ var ok = res && (res.status === 1 || res.status === '1');
+ if (ok) {
+ populateTrustedSSHWhitelist();
+ if (typeof PNotify !== 'undefined') {
+ var unchanged = res.unchanged === true || res.unchanged === 'true' || res.unchanged === 1;
+ var msgOk = (res.message && String(res.message).length) ? res.message : (unchanged ? 'No changes to save.' : 'Entry updated');
+ new PNotify({ title: 'Trusted IPs', text: msgOk, type: unchanged ? 'info' : 'success', delay: 4000 });
+ }
+ } else {
+ var errUp = (res && res.error) ? res.error : 'Failed to update';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: errUp, type: 'error', delay: 6000 });
+ }
+ }
+ },
+ function(err) {
+ var em3 = (err.data && err.data.error) ? err.data.error : 'Request failed';
+ if (typeof PNotify !== 'undefined') {
+ new PNotify({ title: 'Trusted IPs', text: em3, type: 'error', delay: 6000 });
+ }
+ }
+ );
+ };
// Expose to scope for template access
$scope.populateBannedIPs = function() {
@@ -301,7 +490,9 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
window.__firewallLoadTab = function(tab) {
$scope.$evalAsync(function() {
$scope.activeTab = tab;
- if (tab === 'banned') { populateBannedIPs(); } else { populateCurrentRecords(); }
+ if (tab === 'banned') { populateBannedIPs(); }
+ else if (tab === 'trusted') { populateTrustedSSHWhitelist(); }
+ else { populateCurrentRecords(); }
});
};
}
@@ -365,7 +556,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
populateCurrentRecords();
- $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -377,7 +567,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -393,7 +583,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -407,8 +597,8 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
};
function populateCurrentRecords() {
-
- $scope.rulesLoading = false;
+ var gen = ++rulesFetchGen;
+ $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -426,21 +616,44 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
- var res = (typeof response.data === 'string') ? (function() { try { return JSON.parse(response.data); } catch (e) { return {}; } })() : response.data;
- if (res && res.fetchStatus === 1) {
- $scope.rules = typeof res.data === 'string' ? JSON.parse(res.data) : (res.data || []);
- $scope.rulesTotalCount = res.total_count != null ? res.total_count : ($scope.rules ? $scope.rules.length : 0);
- $scope.rulesPage = Math.max(1, res.page != null ? res.page : 1);
- $scope.rulesPageSize = res.page_size != null ? res.page_size : 10;
- $scope.rulesLoading = true;
+ if (gen !== rulesFetchGen) {
+ return;
}
- else {
- $scope.rulesLoading = true;
- $scope.errorMessage = (res && res.error_message) ? res.error_message : '';
+ try {
+ var res = (typeof response.data === 'string') ? (function() { try { return JSON.parse(response.data); } catch (e) { return {}; } })() : response.data;
+ if (res && res.fetchStatus == 1) {
+ var parsedRules = [];
+ if (typeof res.data === 'string') {
+ try {
+ parsedRules = JSON.parse(res.data);
+ } catch (parseErr) {
+ parsedRules = [];
+ $scope.errorMessage = (res && res.error_message) ? res.error_message : 'Invalid rules data';
+ }
+ } else {
+ parsedRules = res.data || [];
+ }
+ $scope.rules = parsedRules;
+ $scope.rulesTotalCount = res.total_count != null ? res.total_count : ($scope.rules ? $scope.rules.length : 0);
+ $scope.rulesPage = Math.max(1, res.page != null ? res.page : 1);
+ $scope.rulesPageSize = res.page_size != null ? res.page_size : 10;
+ } else {
+ $scope.errorMessage = (res && res.error_message) ? res.error_message : '';
+ }
+ } catch (e) {
+ $scope.errorMessage = 'Could not load firewall rules.';
+ } finally {
+ if (gen === rulesFetchGen) {
+ $scope.rulesLoading = false;
+ }
}
}
function cantLoadInitialDatas(response) {
+ if (gen !== rulesFetchGen) {
+ return;
+ }
+ $scope.rulesLoading = false;
$scope.couldNotConnect = false;
}
}
@@ -519,7 +732,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.errorMessage = 'Port is required';
return;
}
- $scope.rulesLoading = false;
+ $scope.rulesLoading = true;
var url = '/firewall/modifyRule';
var data = {
id: d.id,
@@ -530,19 +743,19 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
};
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function(response) {
- $scope.rulesLoading = true;
if (response.data && response.data.status === 1) {
$scope.closeModifyRuleModal();
$scope.actionFailed = true;
$scope.actionSuccess = false;
populateCurrentRecords();
} else {
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = (response.data && response.data.error_message) || 'Modify failed';
}
}, function() {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = 'Could not connect to server. Please refresh this page.';
@@ -551,7 +764,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.deleteRule = function (id, proto, port, ruleIP) {
- $scope.rulesLoading = false;
+ $scope.rulesLoading = true;
url = "/firewall/deleteRule";
@@ -579,7 +792,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
populateCurrentRecords();
- $scope.rulesLoading = true;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -591,7 +803,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -599,7 +811,6 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
$scope.ruleAdded = true;
$scope.couldNotConnect = true;
- $scope.rulesLoading = true;
$scope.errorMessage = response.data.error_message;
@@ -609,7 +820,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -656,7 +867,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.reload_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -668,7 +879,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -685,7 +896,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -731,7 +942,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.start_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -747,7 +958,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -764,7 +975,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -811,7 +1022,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
if (response.data.stop_status == 1) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -827,7 +1038,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
else {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = false;
$scope.actionSuccess = true;
@@ -844,7 +1055,7 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
function cantLoadInitialDatas(response) {
- $scope.rulesLoading = true;
+ $scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
@@ -1309,6 +1520,8 @@ app.controller('firewallController', function ($scope, $http, $timeout) {
}
}
+ $scope.populateCurrentRecords = populateCurrentRecords;
+
});
@@ -3254,18 +3467,20 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
function syncFirewallTabFromHash() {
var nav = document.getElementById('firewall-tab-nav');
if (!nav) return;
- var h = (window.location.hash || '').replace(/^#/, '');
- var tab = (h === 'banned-ips') ? 'banned' : 'rules';
+ var h = (window.location.hash || '').replace(/^#/, '').toLowerCase();
+ var tab = 'rules';
+ if (h === 'banned-ips' || h === 'banned') tab = 'banned';
+ else if (h === 'trusted-ips' || h === 'ssh-whitelist') tab = 'trusted';
if (window.__firewallLoadTab) {
try { window.__firewallLoadTab(tab); } catch (e) {}
}
}
+ /* Initial sync only — hashchange is handled by Angular syncTabFromHash in firewallController
+ (multiple listeners were racing and could reset #trusted-ips to #rules). */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', syncFirewallTabFromHash);
} else {
syncFirewallTabFromHash();
}
- setTimeout(syncFirewallTabFromHash, 100);
- window.addEventListener('hashchange', syncFirewallTabFromHash);
})();
\ No newline at end of file