Firewall: sync rulesLoading fix to public/static; harden rules fetch; cb=6

Tracked copies (public/static, static) must match firewall/static so the panel serves the fixed JS. Add rulesFetchGen, try/finally, fetchStatus==1, expose populateCurrentRecords on scope.
This commit is contained in:
master3395
2026-04-10 19:30:20 +02:00
parent 9941a09865
commit 6a52cc54bd
4 changed files with 587 additions and 133 deletions

View File

@@ -2559,7 +2559,7 @@
<script src="{% static 'managePHP/managePHP.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'serverLogs/serverLogs.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'serverStatus/serverStatus.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'firewall/firewall.js' %}?v={{ CP_VERSION }}&fw={{ FIREWALL_STATIC_VERSION|default:CP_VERSION }}&cb=4" data-cfasync="false"></script>
<script src="{% static 'firewall/firewall.js' %}?v={{ CP_VERSION }}&fw={{ FIREWALL_STATIC_VERSION|default:CP_VERSION }}&cb=6" data-cfasync="false"></script>
<script src="{% static 'emailPremium/emailPremium.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'manageServices/manageServices.js' %}?v={{ CP_VERSION }}&msModal=20260402d" data-cfasync="false"></script>
<script src="{% static 'CLManager/CLManager.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>

View File

@@ -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;
});

View File

@@ -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);
})();

View File

@@ -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);
})();