Merge upstream/usmannasir v2.5.5-dev into fork (resolve PR #1756 conflicts)

- Take upstream baseTemplate dashboard, mail/process utilities, webauthn, emailPremium.
- Keep fork firewall: rulesLoading/synced JS, trusted-SSH tab, SSH whitelist ban guard.
- Firewall script cache-bust stays cb=6 in index.
This commit is contained in:
master3395
2026-04-10 20:32:21 +02:00
10 changed files with 153 additions and 1056 deletions

View File

@@ -1004,16 +1004,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.topProcesses = [];
$scope.loadingTopProcesses = true;
$scope.errorTopProcesses = '';
/** When true, automatic Top Process refresh is stopped (user can read the table). */
$scope.topProcessPaused = false;
/**
* @param {boolean} silent If true, refresh rows in place without clearing the table (no loading spinner).
*/
$scope.refreshTopProcesses = function(silent) {
silent = !!silent;
if (!silent) {
$scope.loadingTopProcesses = true;
}
$scope.refreshTopProcesses = function() {
$scope.loadingTopProcesses = true;
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.get('/base/getTopProcesses', h).then(function (response) {
$scope.loadingTopProcesses = false;
@@ -1022,12 +1014,9 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
} else {
$scope.topProcesses = [];
}
$scope.errorTopProcesses = '';
}, function (err) {
$scope.loadingTopProcesses = false;
if (!silent) {
$scope.errorTopProcesses = 'Failed to load top processes.';
}
$scope.errorTopProcesses = 'Failed to load top processes.';
});
};
@@ -1035,7 +1024,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogins = [];
$scope.sshLoginsPaginated = [];
$scope.sshLoginsCurrentPage = 1;
$scope.sshLoginsPerPage = 3;
$scope.sshLoginsPerPage = 10;
$scope.sshLoginsGoToPage = 1;
$scope.loadingSSHLogins = true;
$scope.errorSSHLogins = '';
@@ -1065,10 +1054,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
console.log('updateSSHLoginsPaginated: No data, cleared paginated array');
return;
}
var per = parseInt($scope.sshLoginsPerPage, 10) || 3;
$scope.sshLoginsPerPage = per;
var start = ($scope.sshLoginsCurrentPage - 1) * per;
var end = start + per;
var start = ($scope.sshLoginsCurrentPage - 1) * $scope.sshLoginsPerPage;
var end = start + $scope.sshLoginsPerPage;
$scope.sshLoginsPaginated = $scope.sshLogins.slice(start, end);
console.log('updateSSHLoginsPaginated: start=', start, 'end=', end, 'total=', $scope.sshLogins.length, 'paginated=', $scope.sshLoginsPaginated.length);
};
@@ -1097,12 +1084,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLoginsGoToPage = $scope.sshLoginsCurrentPage;
}
};
$scope.sshLoginsChangePerPage = function() {
$scope.sshLoginsPerPage = parseInt($scope.sshLoginsPerPage, 10) || 3;
$scope.sshLoginsCurrentPage = 1;
$scope.sshLoginsGoToPage = 1;
$scope.updateSSHLoginsPaginated();
};
$scope.refreshSSHLogins = function() {
$scope.loadingSSHLogins = true;
@@ -1134,24 +1115,12 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogs = [];
$scope.sshLogsPaginated = [];
$scope.sshLogsCurrentPage = 1;
$scope.sshLogsPerPage = 3;
$scope.sshLogsPerPage = 10;
$scope.sshLogsGoToPage = 1;
$scope.loadingSSHLogs = true;
$scope.errorSSHLogs = '';
$scope.securityAlerts = [];
$scope.loadingSecurityAnalysis = false;
/** Tab badge: actionable alerts only (high/medium/low). Excludes informational SSH tips. */
$scope.actionableSecurityAlertCount = function () {
var list = $scope.securityAlerts || [];
var c = 0;
for (var i = 0; i < list.length; i++) {
var sev = (list[i] && list[i].severity) ? String(list[i].severity) : '';
if (sev !== 'info') {
c++;
}
}
return c;
};
$scope.getSSHLogsTotalPages = function() {
return Math.ceil($scope.sshLogs.length / $scope.sshLogsPerPage);
@@ -1178,10 +1147,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
console.log('updateSSHLogsPaginated: No data, cleared paginated array');
return;
}
var per = parseInt($scope.sshLogsPerPage, 10) || 3;
$scope.sshLogsPerPage = per;
var start = ($scope.sshLogsCurrentPage - 1) * per;
var end = start + per;
var start = ($scope.sshLogsCurrentPage - 1) * $scope.sshLogsPerPage;
var end = start + $scope.sshLogsPerPage;
$scope.sshLogsPaginated = $scope.sshLogs.slice(start, end);
console.log('updateSSHLogsPaginated: start=', start, 'end=', end, 'total=', $scope.sshLogs.length, 'paginated=', $scope.sshLogsPaginated.length);
};
@@ -1210,12 +1177,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogsGoToPage = $scope.sshLogsCurrentPage;
}
};
$scope.sshLogsChangePerPage = function() {
$scope.sshLogsPerPage = parseInt($scope.sshLogsPerPage, 10) || 3;
$scope.sshLogsCurrentPage = 1;
$scope.sshLogsGoToPage = 1;
$scope.updateSSHLogsPaginated();
};
$scope.refreshSSHLogs = function() {
$scope.loadingSSHLogs = true;
@@ -1254,114 +1215,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.blockingIP = null;
$scope.blockedIPs = {};
// SSH Security: trusted IPs (never blocked, excluded from analysis alerts)
// Use an object for ng-model: inputs live under ng-if child scopes; primitives would not update parent.
$scope.sshSecurityWhitelist = [];
$scope.sshWhitelistMap = {};
$scope.whitelistUi = { ip: '', label: '' };
$scope._syncWhitelistMap = function () {
$scope.sshWhitelistMap = {};
if ($scope.sshSecurityWhitelist && $scope.sshSecurityWhitelist.length) {
$scope.sshSecurityWhitelist.forEach(function (r) {
$scope.sshWhitelistMap[r.ip] = true;
});
}
};
$scope._decorateWhitelistEntries = function (entries) {
$scope.sshSecurityWhitelist = (entries || []).map(function (e) {
return {
ip: e.ip,
label: e.label || '',
updated: e.updated || 0,
_l: e.label || '',
_nip: ''
};
});
$scope._syncWhitelistMap();
};
$scope.isSshWhitelisted = function (ip) {
if (!ip) return false;
return !!$scope.sshWhitelistMap[String(ip).trim()];
};
$scope.loadSshSecurityWhitelist = function () {
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistList', {}, h).then(function (res) {
if (res.data && res.data.status === 1) {
$scope._decorateWhitelistEntries(res.data.entries);
}
});
};
$scope.addSshSecurityWhitelist = function () {
var ip = ($scope.whitelistUi && $scope.whitelistUi.ip || '').trim();
var label = ($scope.whitelistUi && $scope.whitelistUi.label || '').trim();
if (!ip) {
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Trusted IP', text: 'Enter an IP address', type: 'warning', delay: 4000 }); }
return;
}
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistAdd', { ip: ip, label: label }, h).then(function (res) {
if (res.data && res.data.status === 1) {
if ($scope.whitelistUi) {
$scope.whitelistUi.ip = '';
$scope.whitelistUi.label = '';
}
$scope._decorateWhitelistEntries(res.data.entries);
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Trusted IP', text: 'IP added to trusted list', type: 'success', delay: 4000 }); }
if ($scope.analyzeSSHSecurity) { $scope.analyzeSSHSecurity(); }
} else {
var err = (res.data && (res.data.error || res.data.message)) ? (res.data.error || res.data.message) : 'Failed to add';
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: err, type: 'error', delay: 6000 }); }
}
}, function (err) {
var msg = 'Request failed';
if (err.data && err.data.error) msg = err.data.error;
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: msg, type: 'error', delay: 6000 }); }
});
};
$scope.removeSshSecurityWhitelist = function (ip) {
if (!ip) return;
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistRemove', { ip: ip }, h).then(function (res) {
if (res.data && res.data.status === 1) {
$scope._decorateWhitelistEntries(res.data.entries);
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Trusted IP', text: 'IP removed from trusted list', type: 'success', delay: 4000 }); }
if ($scope.analyzeSSHSecurity) { $scope.analyzeSSHSecurity(); }
} else {
var err2 = (res.data && res.data.error) ? res.data.error : 'Failed to remove';
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: err2, type: 'error', delay: 6000 }); }
}
});
};
$scope.saveSshSecurityWhitelistRow = 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 h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistUpdate', payload, h).then(function (res) {
var d = res.data || {};
var st = d.status === 1 || d.status === '1';
if (st) {
$scope._decorateWhitelistEntries(d.entries);
if (typeof PNotify !== 'undefined') {
var unchanged = d.unchanged === true || d.unchanged === 'true' || d.unchanged === 1;
var txt = (d.message && String(d.message).length) ? d.message : (unchanged ? 'No changes to save.' : 'Entry updated');
new PNotify({ title: 'Trusted IP', text: txt, type: unchanged ? 'info' : 'success', delay: 4000 });
}
if ($scope.analyzeSSHSecurity) { $scope.analyzeSSHSecurity(); }
} else {
var err3 = d.error ? d.error : 'Failed to update';
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: err3, type: 'error', delay: 6000 }); }
}
});
};
$scope.analyzeSSHSecurity = function() {
$scope.loadingSecurityAnalysis = true;
$scope.showAddonRequired = false;
@@ -1374,9 +1227,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.securityAlerts = [];
} else if (response.data.status === 1) {
$scope.securityAlerts = response.data.alerts;
if (response.data.whitelist_entries) {
$scope._decorateWhitelistEntries(response.data.whitelist_entries);
}
$scope.showAddonRequired = false;
}
}
@@ -1811,47 +1661,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
// For rate calculation
var lastRx = null, lastTx = null, lastDiskRead = null, lastDiskWrite = null, lastCPU = null;
var lastCPUTimes = null;
var pollInterval = 2000; // ms (charts / dashboard stats)
/** Top Process list: slower refresh, silent updates (no table flash). */
var topProcessPollIntervalMs = 10000;
var topProcessPollPromise = null;
function cancelTopProcessPoll() {
if (topProcessPollPromise) {
$timeout.cancel(topProcessPollPromise);
topProcessPollPromise = null;
}
}
function scheduleTopProcessPoll() {
cancelTopProcessPoll();
function tick() {
if ($scope.topProcessPaused) {
return;
}
topProcessPollPromise = $timeout(function() {
topProcessPollPromise = null;
if (!$scope.topProcessPaused) {
$scope.refreshTopProcesses(true);
}
if (!$scope.topProcessPaused) {
tick();
}
}, topProcessPollIntervalMs);
}
tick();
}
$scope.toggleTopProcessPause = function() {
$scope.topProcessPaused = !$scope.topProcessPaused;
if ($scope.topProcessPaused) {
cancelTopProcessPoll();
} else {
$scope.refreshTopProcesses(true);
scheduleTopProcessPoll();
}
};
var pollInterval = 2000; // ms
var maxPoints = 30;
// Expose so switchTab can create charts on first tab click if they weren't created at load
@@ -2374,6 +2184,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
// Initial setup - fetch stats immediately
pollDashboardStats();
$scope.refreshTopProcesses();
$scope.refreshSSHLogins();
$scope.refreshSSHLogs();
@@ -2391,17 +2202,16 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.hideSystemCharts = true;
});
// Start polling for all stats (data feeds charts). Top Process is polled separately (slower, silent).
// Start polling for all stats (data feeds charts)
function pollAll() {
pollDashboardStats();
pollTraffic();
pollDiskIO();
pollCPU();
$scope.refreshTopProcesses();
$timeout(pollAll, pollInterval);
}
pollAll();
scheduleTopProcessPoll();
}, 800);
// SSH User Activity Modal

View File

@@ -1002,8 +1002,8 @@
<button class="activity-tab" onclick="switchTab('ssh-logs', this)">
<i class="fas fa-file-alt"></i>
<span>Recent SSH Logs</span>
<span ng-if="actionableSecurityAlertCount() > 0" style="background: #dc2626; color: white; padding: 2px 6px; border-radius: 4px; font-size: 10px; margin-left: 6px;" title="Actionable SSH security alerts (not log line count; tips excluded)">
{$ actionableSecurityAlertCount() $}
<span ng-if="securityAlerts.length > 0" style="background: #dc2626; color: white; padding: 2px 6px; border-radius: 4px; font-size: 10px; margin-left: 6px;">
{$ securityAlerts.length $}
</span>
</button>
<button class="activity-tab" onclick="switchTab('top-process', this)">
@@ -1046,7 +1046,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="login in sshLoginsPaginated">
<tr ng-repeat="login in sshLogins">
<td><strong>{$ login.user $}</strong></td>
<td><code style="background: #f0f0ff; padding: 2px 6px; border-radius: 4px; font-size: 11px;">{$ login.ip $}</code></td>
<td>
@@ -1080,28 +1080,22 @@
<div ng-if="!loadingSSHLogins && sshLogins.length > 0" class="pagination-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 16px; padding: 12px 0; border-top: 1px solid #e8e9ff;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="color: #64748b; font-size: 13px;">Show</span>
<select ng-model="$parent.sshLoginsPerPage" ng-change="sshLoginsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option ng-value="3">3</option>
<option ng-value="5">5</option>
<option ng-value="10">10</option>
<option ng-value="20">20</option>
<option ng-value="50">50</option>
<option ng-value="100">100</option>
<select ng-model="sshLoginsPerPage" ng-change="sshLoginsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span style="color: #64748b; font-size: 13px;">per page</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #64748b; font-size: 13px;">{$ getSSHLoginsStart() $}-{$ getSSHLoginsEnd() $} of {$ sshLogins.length $}</span>
<button ng-click="sshLoginsPrevPage()" ng-disabled="sshLoginsCurrentPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<span style="color: #64748b; font-size: 13px;">{$ (sshLoginsPage - 1) * sshLoginsPerPage + 1 $}-{$ (sshLoginsPage * sshLoginsPerPage > sshLoginsTotal ? sshLoginsTotal : sshLoginsPage * sshLoginsPerPage) $} of {$ sshLoginsTotal $}</span>
<button ng-click="sshLoginsGoToPage(sshLoginsPage - 1)" ng-disabled="sshLoginsPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<i class="fas fa-chevron-left"></i>
</button>
<span style="color: #64748b; font-size: 12px;">Page {$ sshLoginsCurrentPage $} / {$ getSSHLoginsTotalPages() || 1 $}</span>
<button ng-click="sshLoginsNextPage()" ng-disabled="sshLoginsCurrentPage >= getSSHLoginsTotalPages()" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<button ng-click="sshLoginsGoToPage(sshLoginsPage + 1)" ng-disabled="sshLoginsPage >= sshLoginsTotalPages" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<i class="fas fa-chevron-right"></i>
</button>
<span style="color: #64748b; font-size: 13px; margin-left: 8px;">Go to</span>
<input type="number" min="1" ng-model="$parent.sshLoginsGoToPage" ng-keydown="($event.which === 13 || $event.keyCode === 13) && sshLoginsGoToPageNumber()" style="width: 72px; padding: 6px 8px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px;" />
<button ng-click="sshLoginsGoToPageNumber()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;">Go</button>
</div>
</div>
@@ -1213,10 +1207,6 @@
</div>
<!-- Add to Firewall Button for all alerts with IP addresses -->
<div ng-if="alert.details && (alert.details['IP Address'] || alert.details['Top IP'])" style="margin-top: 12px;">
<span ng-if="isSshWhitelisted(alert.details['IP Address'] || alert.details['Top IP'])" style="display: inline-flex; align-items: center; gap: 6px; color: #0ea5e9; font-size: 12px; font-weight: 600;">
<i class="fas fa-shield-alt"></i> Trusted IP — not shown as a threat and cannot be banned from here.
</span>
<span ng-if="!isSshWhitelisted(alert.details['IP Address'] || alert.details['Top IP'])">
<button ng-click="blockIPAddress(alert.details['IP Address'] || alert.details['Top IP']); $event.stopPropagation()"
ng-disabled="blockingIP === (alert.details['IP Address'] || alert.details['Top IP'])"
style="background: #dc2626; color: white; border: none; padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;"
@@ -1234,7 +1224,6 @@
style="margin-left: 10px; color: #10b981; font-size: 12px; font-weight: 600;">
<i class="fas fa-check-circle"></i> Blocked
</span>
</span>
</div>
</div>
<span style="background: {$ alert.severity === 'high' ? '#fee2e2' : (alert.severity === 'medium' ? '#fef3c7' : (alert.severity === 'low' ? '#dbeafe' : '#d1fae5')) $};
@@ -1264,7 +1253,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="log in sshLogsPaginated">
<tr ng-repeat="log in sshLogs">
<td><strong>{$ log.timestamp $}</strong></td>
<td><code style="background: #f0f0ff; padding: 4px 8px; border-radius: 4px; font-size: 11px; display: inline-block; word-break: break-word; overflow-wrap: break-word;">{$ log.message $}</code></td>
<td>
@@ -1274,7 +1263,7 @@
<span ng-if="!log.ip_address" style="color: #8893a7;">-</span>
</td>
<td>
<button ng-if="log.ip_address && blockingIP !== log.ip_address && !blockedIPs[log.ip_address] && !isSshWhitelisted(log.ip_address)"
<button ng-if="log.ip_address && blockingIP !== log.ip_address && !blockedIPs[log.ip_address]"
ng-click="banIPFromSSHLog(log.ip_address)"
style="background: #dc2626; color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 11px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 4px;"
onmouseover="this.style.background='#b91c1c'"
@@ -1294,11 +1283,6 @@
<i class="fas fa-check-circle"></i>
Banned
</span>
<span ng-if="log.ip_address && isSshWhitelisted(log.ip_address)"
style="color: #0ea5e9; font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; gap: 4px;">
<i class="fas fa-shield-alt"></i>
Trusted
</span>
<span ng-if="!log.ip_address" style="color: #8893a7; font-size: 11px;">-</span>
</td>
</tr>
@@ -1309,43 +1293,28 @@
<div ng-if="!loadingSSHLogs && sshLogs.length > 0" class="pagination-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 16px; padding: 12px 0; border-top: 1px solid #e8e9ff;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="color: #64748b; font-size: 13px;">Show</span>
<select ng-model="$parent.sshLogsPerPage" ng-change="sshLogsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option ng-value="3">3</option>
<option ng-value="5">5</option>
<option ng-value="10">10</option>
<option ng-value="25">25</option>
<option ng-value="50">50</option>
<option ng-value="100">100</option>
<select ng-model="sshLogsPerPage" ng-change="sshLogsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span style="color: #64748b; font-size: 13px;">per page</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #64748b; font-size: 13px;">{$ getSSHLogsStart() $}-{$ getSSHLogsEnd() $} of {$ sshLogs.length $}</span>
<button ng-click="sshLogsPrevPage()" ng-disabled="sshLogsCurrentPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<span style="color: #64748b; font-size: 13px;">{$ (sshLogsPage - 1) * sshLogsPerPage + 1 $}-{$ (sshLogsPage * sshLogsPerPage > sshLogsTotal ? sshLogsTotal : sshLogsPage * sshLogsPerPage) $} of {$ sshLogsTotal $}</span>
<button ng-click="sshLogsGoToPage(sshLogsPage - 1)" ng-disabled="sshLogsPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<i class="fas fa-chevron-left"></i>
</button>
<span style="color: #64748b; font-size: 12px;">Page {$ sshLogsCurrentPage $} / {$ getSSHLogsTotalPages() || 1 $}</span>
<button ng-click="sshLogsNextPage()" ng-disabled="sshLogsCurrentPage >= getSSHLogsTotalPages()" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<button ng-click="sshLogsGoToPage(sshLogsPage + 1)" ng-disabled="sshLogsPage >= sshLogsTotalPages" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<i class="fas fa-chevron-right"></i>
</button>
<span style="color: #64748b; font-size: 13px; margin-left: 8px;">Go to</span>
<input type="number" min="1" ng-model="$parent.sshLogsGoToPage" ng-keydown="($event.which === 13 || $event.keyCode === 13) && sshLogsGoToPageNumber()" style="width: 72px; padding: 6px 8px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px;" />
<button ng-click="sshLogsGoToPageNumber()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;">Go</button>
</div>
</div>
</div>
<!-- Top Process Tab -->
<div id="top-process" class="tab-content">
<div ng-if="!loadingTopProcesses" style="display: flex; justify-content: flex-end; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; padding: 4px 0;">
<span ng-if="topProcessPaused" style="font-size: 12px; color: #d97706; font-weight: 600;">
<i class="fas fa-pause-circle"></i> Auto-refresh paused
</span>
<button type="button" ng-click="toggleTopProcessPause()" class="btn-primary" style="padding: 6px 14px; font-size: 12px; display: inline-flex; align-items: center; gap: 6px;">
<i class="fas" ng-class="topProcessPaused ? 'fa-play' : 'fa-pause'"></i>
<span>{$ topProcessPaused ? 'Resume updates' : 'Pause updates' $}</span>
</button>
</div>
<div ng-if="loadingTopProcesses" style="text-align: center; padding: 20px; color: #8893a7;">
Loading top processes...
</div>

View File

@@ -25,10 +25,6 @@ urlpatterns = [
re_path(r'^getSSHUserActivity$', views.getSSHUserActivity, name='getSSHUserActivity'),
re_path(r'^getTopProcesses$', views.getTopProcesses, name='getTopProcesses'),
re_path(r'^analyzeSSHSecurity$', views.analyzeSSHSecurity, name='analyzeSSHSecurity'),
re_path(r'^sshSecurityWhitelistList$', views.sshSecurityWhitelistList, name='sshSecurityWhitelistList'),
re_path(r'^sshSecurityWhitelistAdd$', views.sshSecurityWhitelistAdd, name='sshSecurityWhitelistAdd'),
re_path(r'^sshSecurityWhitelistRemove$', views.sshSecurityWhitelistRemove, name='sshSecurityWhitelistRemove'),
re_path(r'^sshSecurityWhitelistUpdate$', views.sshSecurityWhitelistUpdate, name='sshSecurityWhitelistUpdate'),
re_path(r'^blockIPAddress$', views.blockIPAddress, name='blockIPAddress'),
re_path(r'^dismiss_backup_notification$', views.dismiss_backup_notification, name='dismiss_backup_notification'),
re_path(r'^dismiss_ai_scanner_notification$', views.dismiss_ai_scanner_notification, name='dismiss_ai_scanner_notification'),

View File

@@ -861,11 +861,6 @@ def getRecentSSHLogins(request):
lines = output.strip().split('\n')
logins = []
ip_cache = {}
try:
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
ssh_whitelist_ips = SSHSecurityWhitelistUtilities.ip_set()
except Exception:
ssh_whitelist_ips = set()
for line in lines:
if not line.strip() or any(x in line for x in ['reboot', 'system boot', 'wtmp begins']):
continue
@@ -933,12 +928,6 @@ def getRecentSSHLogins(request):
country, flag = 'IPv6', ''
elif ip == '127.0.0.1' or ip == '::1':
country, flag = 'Local', ''
try:
ip_for_wl = SSHSecurityWhitelistUtilities.normalize_ip(ip)
if ip_for_wl and ip_for_wl in ssh_whitelist_ips:
continue
except Exception:
pass
logins.append({
'user': user,
'ip': ip,
@@ -997,11 +986,6 @@ def getRecentSSHLogs(request):
output = ProcessUtilities.outputExecutioner(f'tail -n 500 {log_path}')
except Exception as e:
return HttpResponse(json.dumps({'error': f'Failed to read log: {str(e)}'}), content_type='application/json', status=500)
try:
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
ssh_whitelist_ips = SSHSecurityWhitelistUtilities.ip_set()
except Exception:
ssh_whitelist_ips = set()
lines = output.split('\n')
logs = []
# IP address regex patterns (IPv4)
@@ -1031,12 +1015,6 @@ def getRecentSSHLogs(request):
if not ip_address and ip_matches:
ip_address = ip_matches[0]
try:
ip_wl = SSHSecurityWhitelistUtilities.normalize_ip(ip_address) if ip_address else ''
if ip_wl and ip_wl in ssh_whitelist_ips:
continue
except Exception:
pass
logs.append({
'timestamp': timestamp,
'message': message,
@@ -1207,21 +1185,10 @@ def analyzeSSHSecurity(request):
ip = match.group(1)
repeated_connections[ip] += 1
# Trusted IPs: never show block recommendations / never block via FirewallUtilities
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
try:
whitelist_entries = SSHSecurityWhitelistUtilities.load_entries()
wl_set = {e['ip'] for e in whitelist_entries}
except Exception:
whitelist_entries = []
wl_set = set()
# Generate alerts based on analysis
# High severity: Brute force attacks
for ip, count in failed_passwords.items():
if SSHSecurityWhitelistUtilities.normalized_ip_in_whitelist(ip, wl_set):
continue
if count >= 10:
recommendation = f'Block this IP immediately:\nfirewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address={ip} drop" && firewall-cmd --reload'
@@ -1237,30 +1204,22 @@ def analyzeSSHSecurity(request):
'recommendation': recommendation
})
# High severity: Root login attempts (exclude trusted IPs)
root_login_attempts_filtered = [
r for r in root_login_attempts
if not SSHSecurityWhitelistUtilities.normalized_ip_in_whitelist(r['ip'], wl_set)
]
if root_login_attempts_filtered:
unique_ips = set(r["ip"] for r in root_login_attempts_filtered)
top_ip = max(unique_ips, key=lambda x: sum(1 for r in root_login_attempts_filtered if r["ip"] == x))
# High severity: Root login attempts
if root_login_attempts:
alerts.append({
'title': 'Root Login Attempts Detected',
'description': f'Direct root login attempts detected from {len(unique_ips)} IP addresses. Root SSH access should be disabled.',
'description': f'Direct root login attempts detected from {len(set(r["ip"] for r in root_login_attempts))} IP addresses. Root SSH access should be disabled.',
'severity': 'high',
'details': {
'Unique IPs': len(unique_ips),
'Total Attempts': len(root_login_attempts_filtered),
'Top IP': top_ip
'Unique IPs': len(set(r["ip"] for r in root_login_attempts)),
'Total Attempts': len(root_login_attempts),
'Top IP': max(set(r["ip"] for r in root_login_attempts), key=lambda x: sum(1 for r in root_login_attempts if r["ip"] == x))
},
'recommendation': 'Disable root SSH login by setting "PermitRootLogin no" in /etc/ssh/sshd_config'
})
# Medium severity: Dictionary attacks
for ip, count in invalid_users.items():
if SSHSecurityWhitelistUtilities.normalized_ip_in_whitelist(ip, wl_set):
continue
if count >= 5:
if firewall_cmd == 'csf':
recommendation = f'Consider blocking this IP:\ncsf -d {ip} "Dictionary attack - {count} invalid users"\n\nAlso configure CSF Login Failure Daemon (lfd) for automatic blocking.'
@@ -1281,8 +1240,6 @@ def analyzeSSHSecurity(request):
# Medium severity: Port scanning
for ip, count in port_scan_attempts.items():
if SSHSecurityWhitelistUtilities.normalized_ip_in_whitelist(ip, wl_set):
continue
if count >= 3:
alerts.append({
'title': 'Port Scan Detected',
@@ -1298,8 +1255,6 @@ def analyzeSSHSecurity(request):
# Low severity: Successful login after failures
for ip, successes in successful_after_failures.items():
if SSHSecurityWhitelistUtilities.normalized_ip_in_whitelist(ip, wl_set):
continue
if successes:
max_failures = max(s['failures'] for s in successes)
if max_failures >= 3:
@@ -1317,8 +1272,6 @@ def analyzeSSHSecurity(request):
# High severity: Rapid connection attempts (DDoS/flooding)
for ip, count in repeated_connections.items():
if SSHSecurityWhitelistUtilities.normalized_ip_in_whitelist(ip, wl_set):
continue
if count >= 50:
if firewall_cmd == 'csf':
recommendation = f'Block this IP immediately to prevent resource exhaustion:\ncsf -d {ip} "SSH flooding - {count} connections"'
@@ -1395,137 +1348,12 @@ def analyzeSSHSecurity(request):
return HttpResponse(json.dumps({
'status': 1,
'alerts': alerts,
'whitelist_entries': whitelist_entries,
'alerts': alerts
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
def _ssh_whitelist_acl(request):
"""Return (user_id, error_response) or (user_id, None)."""
user_id = request.session.get('userID')
if not user_id:
return None, HttpResponse(json.dumps({'error': 'Not logged in'}), content_type='application/json', status=403)
currentACL = ACLManager.loadedACL(user_id)
if not currentACL.get('admin', 0):
return None, HttpResponse(json.dumps({'error': 'Admin only'}), content_type='application/json', status=403)
if not ACLManager.CheckForPremFeature('all'):
return None, HttpResponse(json.dumps({
'status': 0,
'error': 'SSH Security trusted IPs require the same access as SSH Security Analysis (addons).',
}), content_type='application/json', status=403)
return user_id, None
@csrf_exempt
@require_POST
def sshSecurityWhitelistList(request):
try:
_, err = _ssh_whitelist_acl(request)
if err:
return err
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
try:
SSHSecurityWhitelistUtilities.ensure_cyberpanel_public_ip_whitelisted()
except Exception:
pass
entries = SSHSecurityWhitelistUtilities.load_entries()
return HttpResponse(json.dumps({
'status': 1,
'entries': entries,
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def sshSecurityWhitelistAdd(request):
try:
_, err = _ssh_whitelist_acl(request)
if err:
return err
try:
data = json.loads(request.body.decode('utf-8'))
except (json.JSONDecodeError, UnicodeDecodeError, AttributeError):
return HttpResponse(json.dumps({'status': 0, 'error': 'Invalid JSON'}), content_type='application/json', status=400)
ip = (data.get('ip') or '').strip()
label = (data.get('label') or '').strip()
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
ok, msg = SSHSecurityWhitelistUtilities.add_entry(ip, label)
if not ok:
return HttpResponse(json.dumps({'status': 0, 'error': msg}), content_type='application/json', status=400)
return HttpResponse(json.dumps({
'status': 1,
'message': 'Trusted IP added',
'ip': msg,
'entries': SSHSecurityWhitelistUtilities.load_entries(),
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def sshSecurityWhitelistRemove(request):
try:
_, err = _ssh_whitelist_acl(request)
if err:
return err
try:
data = json.loads(request.body.decode('utf-8'))
except (json.JSONDecodeError, UnicodeDecodeError, AttributeError):
return HttpResponse(json.dumps({'status': 0, 'error': 'Invalid JSON'}), content_type='application/json', status=400)
ip = (data.get('ip') or '').strip()
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
ok, msg = SSHSecurityWhitelistUtilities.remove_entry(ip)
if not ok:
return HttpResponse(json.dumps({'status': 0, 'error': msg}), content_type='application/json', status=400)
return HttpResponse(json.dumps({
'status': 1,
'message': 'Trusted IP removed',
'entries': SSHSecurityWhitelistUtilities.load_entries(),
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def sshSecurityWhitelistUpdate(request):
try:
_, err = _ssh_whitelist_acl(request)
if err:
return err
try:
data = json.loads(request.body.decode('utf-8'))
except (json.JSONDecodeError, UnicodeDecodeError, AttributeError):
return HttpResponse(json.dumps({'status': 0, 'error': 'Invalid JSON'}), content_type='application/json', status=400)
ip = (data.get('ip') or '').strip()
new_ip = data.get('new_ip')
if new_ip is not None:
new_ip = str(new_ip).strip() or None
label = data.get('label')
if label is not None:
label = str(label).strip()
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
ok, msg, unchanged = SSHSecurityWhitelistUtilities.update_entry(ip, new_ip=new_ip, label=label)
if not ok:
return HttpResponse(json.dumps({'status': 0, 'error': msg}), content_type='application/json', status=400)
message = 'No changes to save.' if unchanged else 'Trusted IP updated.'
return HttpResponse(json.dumps({
'status': 1,
'message': message,
'unchanged': bool(unchanged),
'ip': msg,
'entries': SSHSecurityWhitelistUtilities.load_entries(),
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_POST
def blockIPAddress(request):

View File

@@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
import os
import time
import http.client
from django.shortcuts import redirect
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from loginSystem.models import Administrator
from mailServer.models import Domains, EUsers
@@ -1246,153 +1244,18 @@ def Rspamd(request):
checkIfRspamdInstalled = 0
ipAddress = '127.0.0.1'
try:
ipFile = "/etc/cyberpanel/machineIP"
with open(ipFile, 'r') as f:
ipData = f.read()
first_line = ipData.split('\n', 1)[0].strip()
if first_line:
ipAddress = first_line
except (OSError, IOError, IndexError):
pass
ipFile = "/etc/cyberpanel/machineIP"
f = open(ipFile)
ipData = f.read()
ipAddress = ipData.split('\n', 1)[0]
if mailUtilities.checkIfRspamdInstalled() == 1:
checkIfRspamdInstalled = 1
rspamd_ui_url = request.build_absolute_uri('/emailPremium/Rspamd/ui/')
proc = httpProc(request, 'emailPremium/Rspamd.html',
{
'checkIfRspamdInstalled': checkIfRspamdInstalled,
'ipAddress': ipAddress,
'rspamd_ui_url': rspamd_ui_url,
}, 'admin')
{'checkIfRspamdInstalled': checkIfRspamdInstalled, 'ipAddress': ipAddress}, 'admin')
return proc.render()
_RSPAMD_UPSTREAM = ('127.0.0.1', 11334)
_RSPAMD_HOP_RESPONSE = frozenset({
'connection', 'transfer-encoding', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'upgrade', 'content-encoding',
})
@csrf_exempt
def rspamd_ui_proxy(request, subpath=None):
"""Reverse-proxy Rspamd controller UI (localhost:11334) for logged-in admins only."""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadError()
except KeyError:
return redirect(loadLoginPage)
if mailUtilities.checkIfRspamdInstalled() != 1:
return HttpResponse(
'Rspamd is not installed.',
status=503,
content_type='text/plain; charset=utf-8',
)
proxy_base = request.build_absolute_uri('/emailPremium/Rspamd/ui').rstrip('/')
path = '/'
if subpath:
path = '/' + subpath.lstrip('/')
q = request.META.get('QUERY_STRING', '')
if q:
full_path = path + '?' + q
else:
full_path = path
forward_method = request.method
if forward_method == 'HEAD':
forward_method = 'GET'
body = None
if forward_method in ('POST', 'PUT', 'PATCH', 'DELETE'):
body = request.body
headers = {}
acc = request.META.get('HTTP_ACCEPT')
if acc:
headers['Accept'] = acc
al = request.META.get('HTTP_ACCEPT_LANGUAGE')
if al:
headers['Accept-Language'] = al
ua = request.META.get('HTTP_USER_AGENT')
if ua:
headers['User-Agent'] = ua
auth = request.META.get('HTTP_AUTHORIZATION')
if auth:
headers['Authorization'] = auth
ct = request.META.get('CONTENT_TYPE')
if ct and forward_method in ('POST', 'PUT', 'PATCH'):
headers['Content-Type'] = ct
cookie = request.META.get('HTTP_COOKIE')
if cookie:
headers['Cookie'] = cookie
xhr = request.META.get('HTTP_X_REQUESTED_WITH')
if xhr:
headers['X-Requested-With'] = xhr
conn = None
try:
conn = http.client.HTTPConnection(
_RSPAMD_UPSTREAM[0], _RSPAMD_UPSTREAM[1], timeout=120,
)
conn.request(forward_method, full_path, body=body, headers=headers)
upstream = conn.getresponse()
data = upstream.read()
status = upstream.status
except (ConnectionRefusedError, OSError, http.client.HTTPException) as _e:
logging.CyberCPLogFileWriter.writeToFile(
'rspamd_ui_proxy upstream error: %s' % (type(_e).__name__,),
)
return HttpResponse(
'Could not reach Rspamd on 127.0.0.1:11334. Is rspamd running?',
status=502,
content_type='text/plain; charset=utf-8',
)
finally:
if conn is not None:
try:
conn.close()
except Exception:
pass
if request.method == 'HEAD':
out = HttpResponse(status=status)
data = b''
else:
out = HttpResponse(data, status=status)
for hdr, val in upstream.getheaders():
key = hdr.lower()
if key in _RSPAMD_HOP_RESPONSE:
continue
if key == 'location':
val = _rewrite_rspamd_location(val, proxy_base)
if request.method == 'HEAD' and key == 'content-length':
continue
out[hdr] = val
return out
def _rewrite_rspamd_location(location, proxy_base):
if not location:
return location
if location.startswith('http://127.0.0.1:11334'):
return proxy_base + location[len('http://127.0.0.1:11334'):]
if location.startswith('http://[::1]:11334'):
return proxy_base + location[len('http://[::1]:11334'):]
if location.startswith('/') and not location.startswith('//'):
return proxy_base + location
return location
def installRspamd(request):
try:
userID = request.session['userID']

View File

@@ -162,13 +162,6 @@ class WebAuthnAuthenticationComplete(WebAuthnAPIView):
if ip_addr.find(':') > -1:
ip_addr = ':'.join(ip_addr.split(':')[:3])
request.session['ipAddr'] = ip_addr
try:
from loginSystem.models import Administrator
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
adm = Administrator.objects.select_related('acl').get(pk=int(result['user_id']))
SSHSecurityWhitelistUtilities.on_successful_panel_login(request, adm)
except Exception:
pass
redirect_url = data.get('redirect') or request.session.pop('webauthn_redirect', '/') or '/'
if '//' in redirect_url or not redirect_url.startswith('/'):
redirect_url = '/'
@@ -196,13 +189,6 @@ class WebAuthnAuthenticationComplete(WebAuthnAPIView):
if ip_addr.find(':') > -1:
ip_addr = ':'.join(ip_addr.split(':')[:3])
request.session['ipAddr'] = ip_addr
try:
from loginSystem.models import Administrator
from plogical.sshSecurityWhitelistUtilities import SSHSecurityWhitelistUtilities
adm = Administrator.objects.select_related('acl').get(pk=int(result['user_id']))
SSHSecurityWhitelistUtilities.on_successful_panel_login(request, adm)
except Exception:
pass
logger.info(f"WebAuthn authentication successful for user ID: {result['user_id']}")
return self.json_response(result)

View File

@@ -853,10 +853,6 @@ return custom_keywords
writeToFile.writelines("Configuring RSPAMD repo..\n")
writeToFile.close()
try:
os.makedirs('/etc/yum.repos.d', mode=0o755, exist_ok=True)
except OSError:
pass
command = 'curl https://rspamd.com/rpm-stable/centos-7/rspamd.repo > /etc/yum.repos.d/rspamd.repo'
ProcessUtilities.normalExecutioner(command, True)
@@ -872,39 +868,20 @@ return custom_keywords
elif ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
el_major = mailUtilities._rhel_el_major_version()
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines(
"Configuring RSPAMD repo for EL%s (rspamd.com rpm-stable)...\n" % el_major
)
writeToFile.writelines("Configuring RSPAMD repo..\n")
writeToFile.close()
try:
os.makedirs('/etc/yum.repos.d', mode=0o755, exist_ok=True)
except OSError:
pass
command = (
'curl -fsSL https://rspamd.com/rpm-stable/centos-%s/rspamd.repo '
'-o /etc/yum.repos.d/rspamd.repo' % el_major
)
command = 'curl https://rspamd.com/rpm-stable/centos-8/rspamd.repo > /etc/yum.repos.d/rspamd.repo'
ProcessUtilities.normalExecutioner(command, True)
command = 'rpm --import https://rspamd.com/rpm-stable/gpg.key'
ProcessUtilities.normalExecutioner(command, True)
if el_major == '7':
command = 'yum update -y'
ProcessUtilities.normalExecutioner(command, True)
command = (
'sudo yum install -y rspamd clamav-server clamav-data clamav-update '
'clamav-filesystem clamav clamav-scanner-systemd clamav-devel clamav-lib '
'clamav-server-systemd'
)
else:
command = 'dnf update -y'
ProcessUtilities.normalExecutioner(command, True)
command = 'dnf update -y'
ProcessUtilities.normalExecutioner(command, True)
command = 'sudo dnf install -y rspamd clamav clamd clamav-update'
command = 'sudo dnf install -y rspamd clamav clamd clamav-update'
else:
command = 'DEBIAN_FRONTEND=noninteractive apt-get install rspamd clamav clamav-daemon -y'
@@ -914,24 +891,9 @@ return custom_keywords
f.flush()
res = subprocess.call(command, stdout=f, stderr=f, shell=True)
if res != 0:
with open(mailUtilities.RspamdInstallLogPath, 'a') as lf:
lf.write(
'Package install failed (exit code %s). '
'On EL9, ensure rspamd.repo matches your OS (EL8 RPMs cause GPG errors on EL9).\n' % res
)
lf.write('Can not be installed.[404]\n')
logging.CyberCPLogFileWriter.writeToFile(
'[Could not Install Rspamd.] dnf/yum exit %s' % res
)
return 0
###### makefile
path = "/etc/rspamd/local.d/antivirus.conf"
try:
os.makedirs(os.path.dirname(path), mode=0o755, exist_ok=True)
except OSError:
pass
content ="""# ================= DO NOT MODIFY THIS FILE =================
#
# Manual changes will be lost when this file is regenerated.
@@ -995,10 +957,6 @@ clamav {
### disable dkim signing in rspamd in ref to https://github.com/usmannasir/cyberpanel/issues/1176
DKIMPath = '/etc/rspamd/local.d/dkim_signing.conf'
try:
os.makedirs(os.path.dirname(DKIMPath), mode=0o755, exist_ok=True)
except OSError:
pass
WriteToFile = open(DKIMPath, 'w')
WriteToFile.write('enabled = false;\n')
@@ -1026,10 +984,6 @@ clamav {
wpath = "/etc/rspamd/local.d/redis.conf"
try:
os.makedirs(os.path.dirname(wpath), mode=0o755, exist_ok=True)
except OSError:
pass
wdata = """
write_servers = "127.0.0.1";
read_servers = "127.0.0.1";
@@ -1040,30 +994,34 @@ read_servers = "127.0.0.1";
wirtedata2.close()
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
command = 'setsebool -P antivirus_can_scan_system 1'
cmd = shlex.split(command)
if res == 1:
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines("Can not be installed.[404]\n")
writeToFile.close()
logging.CyberCPLogFileWriter.writeToFile("[Could not Install Rspamd.]")
return 0
else:
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
command = 'setsebool -P antivirus_can_scan_system 1'
cmd = shlex.split(command)
command = 'setsebool -P clamd_use_jit 1'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'setsebool -P clamd_use_jit 1'
cmd = shlex.split(command)
command = 'usermod -a -G clamscan _rspamd'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'usermod -a -G clamscan _rspamd'
cmd = shlex.split(command)
try:
os.makedirs('/etc/clamd.d', mode=0o755, exist_ok=True)
except OSError:
pass
clamavcontent = """
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
clamavcontent = """
User clamscan
PidFile /var/run/clamd.scan/clamd.pid
TCPSocket 3310
@@ -1076,60 +1034,49 @@ ScanMail true
ScanArchive true
#LogFile /var/log/clamd.scan/clamav.log
"""
writeToFile = open('/etc/clamd.d/scan.conf', 'w')
writeToFile.write(clamavcontent)
writeToFile.close()
writeToFile = open('/etc/clamd.d/scan.conf', 'w')
writeToFile.write(clamavcontent)
writeToFile.close()
command = 'touch /var/log/clamd.scan/clamav.log'
ProcessUtilities.normalExecutioner(command, False, 'clamscan')
command = 'touch /var/log/clamd.scan/clamav.log'
ProcessUtilities.normalExecutioner(command, False, 'clamscan')
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines("Updating Freshclam database..\n")
writeToFile.close()
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines("Updating Freshclam database..\n")
writeToFile.close()
command = 'freshclam'
cmd = shlex.split(command)
command = 'freshclam'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'systemctl start clamd@scan'
cmd = shlex.split(command)
command = 'systemctl start clamd@scan'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'systemctl restart rspamd'
cmd = shlex.split(command)
command = 'systemctl restart rspamd'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
elif ProcessUtilities.decideDistro() == ProcessUtilities.ubuntu or ProcessUtilities.decideDistro() == ProcessUtilities.ubuntu20:
time.sleep(5)
command = 'usermod -a -G clamav _rspamd'
cmd = shlex.split(command)
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines("Rspamd Installed.[200]\n")
writeToFile.close()
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
elif ProcessUtilities.decideDistro() == ProcessUtilities.ubuntu or ProcessUtilities.decideDistro() == ProcessUtilities.ubuntu20:
command = 'chown -R clamav:clamav /var/run/clamav'
cmd = shlex.split(command)
command = 'usermod -a -G clamav _rspamd'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'chown -R clamav:clamav /var/run/clamav'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
try:
os.makedirs('/etc/clamav', mode=0o755, exist_ok=True)
except OSError:
pass
clamavcontent = """
clamavcontent = """
User clamav
PidFile /var/run/clamav/clamd.pid
TCPSocket 3310
@@ -1142,31 +1089,32 @@ ScanMail true
ScanArchive true
LogFile /var/log/clamav/clamav.log
"""
writeToFile = open('/etc/clamav/clamd.conf', 'w')
writeToFile.write(clamavcontent)
writeToFile.close()
writeToFile = open('/etc/clamav/clamd.conf', 'w')
writeToFile.write(clamavcontent)
writeToFile.close()
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines("Updating Freshclam database..\n")
writeToFile.close()
command = 'freshclam'
cmd = shlex.split(command)
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines("Updating Freshclam database..\n")
writeToFile.close()
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'freshclam'
cmd = shlex.split(command)
command = 'systemctl restart clamav-daemon'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'systemctl restart clamav-daemon'
cmd = shlex.split(command)
command = 'systemctl restart rspamd'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
command = 'systemctl restart rspamd'
cmd = shlex.split(command)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
time.sleep(5)
@@ -1704,21 +1652,6 @@ LogFile /var/log/clamav/clamav.log
str(msg) + " [checkIfMailScannerInstalled]")
return 0
@staticmethod
def _rhel_el_major_version():
"""Parse PLATFORM_ID from /etc/os-release (e.g. platform:el9 -> '9'). Default '8'."""
import re
try:
with open('/etc/os-release', 'r') as os_release:
for line in os_release:
if line.startswith('PLATFORM_ID='):
m = re.search(r'el(\d+)', line)
if m:
return m.group(1)
except Exception:
pass
return '8'
@staticmethod
def FetchPostfixHostname():
try:

View File

@@ -188,60 +188,17 @@ class ProcessUtilities(multi.Thread):
return ProcessUtilities.ubuntu20
return ProcessUtilities.ubuntu
# Debian (no Ubuntu): use same apt paths as Ubuntu for CyberPanel mail stack
for _line in content.splitlines():
_ls = _line.strip()
if _ls.startswith('ID='):
_id = _ls.split('=', 1)[1].strip().strip('"').lower()
if _id == 'debian':
return ProcessUtilities.ubuntu
break
# Check for RedHat-based distributions
if os.path.exists(distroPathAlma):
with open(distroPathAlma, 'r') as f:
content = f.read()
if any(x in content for x in ['CentOS Linux release 7', 'CentOS Linux release 8',
'CentOS Stream release 8', 'CentOS Stream release 9',
'AlmaLinux release 8', 'Rocky Linux release 8',
'Rocky Linux release 9', 'AlmaLinux release 9',
'CloudLinux release 9', 'CloudLinux release 8',
'AlmaLinux release 10', 'Rocky Linux release 10',
'Red Hat Enterprise Linux release 8',
'Red Hat Enterprise Linux release 9',
'Red Hat Enterprise Linux release 10']):
if any(x in content for x in ['AlmaLinux release 9', 'Rocky Linux release 9',
'AlmaLinux release 10', 'Rocky Linux release 10',
'Red Hat Enterprise Linux release 9',
'Red Hat Enterprise Linux release 10',
'CentOS Stream release 9']):
if any(x in content for x in ['CentOS Linux release 8', 'AlmaLinux release 8', 'Rocky Linux release 8',
'Rocky Linux release 9', 'AlmaLinux release 9', 'CloudLinux release 9',
'CloudLinux release 8', 'AlmaLinux release 10']):
if any(x in content for x in ['AlmaLinux release 9', 'Rocky Linux release 9', 'AlmaLinux release 10']):
ProcessUtilities.alma9check = 1
return ProcessUtilities.cent8
# Fallback: /etc/os-release for RHEL family (some minimal images lack redhat-release text we match)
if os.path.exists('/etc/os-release'):
try:
with open('/etc/os-release', 'r') as f:
os_lines = f.read()
rid = ''
for line in os_lines.splitlines():
if line.startswith('ID='):
rid = line.split('=', 1)[1].strip().strip('"').lower()
break
if rid in ('almalinux', 'rocky', 'centos', 'rhel', 'cloudlinux', 'eurolinux',
'miraclelinux', 'openeuler', 'virtuozzo'):
ver_id = ''
for line in os_lines.splitlines():
if line.startswith('VERSION_ID='):
ver_id = line.split('=', 1)[1].strip().strip('"')
break
major = ver_id.split('.')[0] if ver_id else '8'
if major in ('9', '10'):
ProcessUtilities.alma9check = 1
return ProcessUtilities.cent8
except OSError:
pass
# Default to Ubuntu if no other distribution is detected
return ProcessUtilities.ubuntu

View File

@@ -1004,16 +1004,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.topProcesses = [];
$scope.loadingTopProcesses = true;
$scope.errorTopProcesses = '';
/** When true, automatic Top Process refresh is stopped (user can read the table). */
$scope.topProcessPaused = false;
/**
* @param {boolean} silent If true, refresh rows in place without clearing the table (no loading spinner).
*/
$scope.refreshTopProcesses = function(silent) {
silent = !!silent;
if (!silent) {
$scope.loadingTopProcesses = true;
}
$scope.refreshTopProcesses = function() {
$scope.loadingTopProcesses = true;
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.get('/base/getTopProcesses', h).then(function (response) {
$scope.loadingTopProcesses = false;
@@ -1022,12 +1014,9 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
} else {
$scope.topProcesses = [];
}
$scope.errorTopProcesses = '';
}, function (err) {
$scope.loadingTopProcesses = false;
if (!silent) {
$scope.errorTopProcesses = 'Failed to load top processes.';
}
$scope.errorTopProcesses = 'Failed to load top processes.';
});
};
@@ -1035,7 +1024,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogins = [];
$scope.sshLoginsPaginated = [];
$scope.sshLoginsCurrentPage = 1;
$scope.sshLoginsPerPage = 3;
$scope.sshLoginsPerPage = 10;
$scope.sshLoginsGoToPage = 1;
$scope.loadingSSHLogins = true;
$scope.errorSSHLogins = '';
@@ -1065,10 +1054,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
console.log('updateSSHLoginsPaginated: No data, cleared paginated array');
return;
}
var per = parseInt($scope.sshLoginsPerPage, 10) || 3;
$scope.sshLoginsPerPage = per;
var start = ($scope.sshLoginsCurrentPage - 1) * per;
var end = start + per;
var start = ($scope.sshLoginsCurrentPage - 1) * $scope.sshLoginsPerPage;
var end = start + $scope.sshLoginsPerPage;
$scope.sshLoginsPaginated = $scope.sshLogins.slice(start, end);
console.log('updateSSHLoginsPaginated: start=', start, 'end=', end, 'total=', $scope.sshLogins.length, 'paginated=', $scope.sshLoginsPaginated.length);
};
@@ -1097,12 +1084,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLoginsGoToPage = $scope.sshLoginsCurrentPage;
}
};
$scope.sshLoginsChangePerPage = function() {
$scope.sshLoginsPerPage = parseInt($scope.sshLoginsPerPage, 10) || 3;
$scope.sshLoginsCurrentPage = 1;
$scope.sshLoginsGoToPage = 1;
$scope.updateSSHLoginsPaginated();
};
$scope.refreshSSHLogins = function() {
$scope.loadingSSHLogins = true;
@@ -1134,7 +1115,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogs = [];
$scope.sshLogsPaginated = [];
$scope.sshLogsCurrentPage = 1;
$scope.sshLogsPerPage = 3;
$scope.sshLogsPerPage = 10;
$scope.sshLogsGoToPage = 1;
$scope.loadingSSHLogs = true;
$scope.errorSSHLogs = '';
@@ -1166,10 +1147,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
console.log('updateSSHLogsPaginated: No data, cleared paginated array');
return;
}
var per = parseInt($scope.sshLogsPerPage, 10) || 3;
$scope.sshLogsPerPage = per;
var start = ($scope.sshLogsCurrentPage - 1) * per;
var end = start + per;
var start = ($scope.sshLogsCurrentPage - 1) * $scope.sshLogsPerPage;
var end = start + $scope.sshLogsPerPage;
$scope.sshLogsPaginated = $scope.sshLogs.slice(start, end);
console.log('updateSSHLogsPaginated: start=', start, 'end=', end, 'total=', $scope.sshLogs.length, 'paginated=', $scope.sshLogsPaginated.length);
};
@@ -1198,12 +1177,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogsGoToPage = $scope.sshLogsCurrentPage;
}
};
$scope.sshLogsChangePerPage = function() {
$scope.sshLogsPerPage = parseInt($scope.sshLogsPerPage, 10) || 3;
$scope.sshLogsCurrentPage = 1;
$scope.sshLogsGoToPage = 1;
$scope.updateSSHLogsPaginated();
};
$scope.refreshSSHLogs = function() {
$scope.loadingSSHLogs = true;
@@ -1242,114 +1215,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.blockingIP = null;
$scope.blockedIPs = {};
// SSH Security: trusted IPs (never blocked, excluded from analysis alerts)
// Use an object for ng-model: inputs live under ng-if child scopes; primitives would not update parent.
$scope.sshSecurityWhitelist = [];
$scope.sshWhitelistMap = {};
$scope.whitelistUi = { ip: '', label: '' };
$scope._syncWhitelistMap = function () {
$scope.sshWhitelistMap = {};
if ($scope.sshSecurityWhitelist && $scope.sshSecurityWhitelist.length) {
$scope.sshSecurityWhitelist.forEach(function (r) {
$scope.sshWhitelistMap[r.ip] = true;
});
}
};
$scope._decorateWhitelistEntries = function (entries) {
$scope.sshSecurityWhitelist = (entries || []).map(function (e) {
return {
ip: e.ip,
label: e.label || '',
updated: e.updated || 0,
_l: e.label || '',
_nip: ''
};
});
$scope._syncWhitelistMap();
};
$scope.isSshWhitelisted = function (ip) {
if (!ip) return false;
return !!$scope.sshWhitelistMap[String(ip).trim()];
};
$scope.loadSshSecurityWhitelist = function () {
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistList', {}, h).then(function (res) {
if (res.data && res.data.status === 1) {
$scope._decorateWhitelistEntries(res.data.entries);
}
});
};
$scope.addSshSecurityWhitelist = function () {
var ip = ($scope.whitelistUi && $scope.whitelistUi.ip || '').trim();
var label = ($scope.whitelistUi && $scope.whitelistUi.label || '').trim();
if (!ip) {
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Trusted IP', text: 'Enter an IP address', type: 'warning', delay: 4000 }); }
return;
}
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistAdd', { ip: ip, label: label }, h).then(function (res) {
if (res.data && res.data.status === 1) {
if ($scope.whitelistUi) {
$scope.whitelistUi.ip = '';
$scope.whitelistUi.label = '';
}
$scope._decorateWhitelistEntries(res.data.entries);
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Trusted IP', text: 'IP added to trusted list', type: 'success', delay: 4000 }); }
if ($scope.analyzeSSHSecurity) { $scope.analyzeSSHSecurity(); }
} else {
var err = (res.data && (res.data.error || res.data.message)) ? (res.data.error || res.data.message) : 'Failed to add';
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: err, type: 'error', delay: 6000 }); }
}
}, function (err) {
var msg = 'Request failed';
if (err.data && err.data.error) msg = err.data.error;
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: msg, type: 'error', delay: 6000 }); }
});
};
$scope.removeSshSecurityWhitelist = function (ip) {
if (!ip) return;
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistRemove', { ip: ip }, h).then(function (res) {
if (res.data && res.data.status === 1) {
$scope._decorateWhitelistEntries(res.data.entries);
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Trusted IP', text: 'IP removed from trusted list', type: 'success', delay: 4000 }); }
if ($scope.analyzeSSHSecurity) { $scope.analyzeSSHSecurity(); }
} else {
var err2 = (res.data && res.data.error) ? res.data.error : 'Failed to remove';
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: err2, type: 'error', delay: 6000 }); }
}
});
};
$scope.saveSshSecurityWhitelistRow = 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 h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.post('/base/sshSecurityWhitelistUpdate', payload, h).then(function (res) {
var d = res.data || {};
var st = d.status === 1 || d.status === '1';
if (st) {
$scope._decorateWhitelistEntries(d.entries);
if (typeof PNotify !== 'undefined') {
var unchanged = d.unchanged === true || d.unchanged === 'true' || d.unchanged === 1;
var txt = (d.message && String(d.message).length) ? d.message : (unchanged ? 'No changes to save.' : 'Entry updated');
new PNotify({ title: 'Trusted IP', text: txt, type: unchanged ? 'info' : 'success', delay: 4000 });
}
if ($scope.analyzeSSHSecurity) { $scope.analyzeSSHSecurity(); }
} else {
var err3 = d.error ? d.error : 'Failed to update';
if (typeof PNotify !== 'undefined') { new PNotify({ title: 'Error', text: err3, type: 'error', delay: 6000 }); }
}
});
};
$scope.analyzeSSHSecurity = function() {
$scope.loadingSecurityAnalysis = true;
$scope.showAddonRequired = false;
@@ -1362,9 +1227,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.securityAlerts = [];
} else if (response.data.status === 1) {
$scope.securityAlerts = response.data.alerts;
if (response.data.whitelist_entries) {
$scope._decorateWhitelistEntries(response.data.whitelist_entries);
}
$scope.showAddonRequired = false;
}
}
@@ -1799,47 +1661,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
// For rate calculation
var lastRx = null, lastTx = null, lastDiskRead = null, lastDiskWrite = null, lastCPU = null;
var lastCPUTimes = null;
var pollInterval = 2000; // ms (charts / dashboard stats)
/** Top Process list: slower refresh, silent updates (no table flash). */
var topProcessPollIntervalMs = 10000;
var topProcessPollPromise = null;
function cancelTopProcessPoll() {
if (topProcessPollPromise) {
$timeout.cancel(topProcessPollPromise);
topProcessPollPromise = null;
}
}
function scheduleTopProcessPoll() {
cancelTopProcessPoll();
function tick() {
if ($scope.topProcessPaused) {
return;
}
topProcessPollPromise = $timeout(function() {
topProcessPollPromise = null;
if (!$scope.topProcessPaused) {
$scope.refreshTopProcesses(true);
}
if (!$scope.topProcessPaused) {
tick();
}
}, topProcessPollIntervalMs);
}
tick();
}
$scope.toggleTopProcessPause = function() {
$scope.topProcessPaused = !$scope.topProcessPaused;
if ($scope.topProcessPaused) {
cancelTopProcessPoll();
} else {
$scope.refreshTopProcesses(true);
scheduleTopProcessPoll();
}
};
var pollInterval = 2000; // ms
var maxPoints = 30;
// Expose so switchTab can create charts on first tab click if they weren't created at load
@@ -2362,6 +2184,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
// Initial setup - fetch stats immediately
pollDashboardStats();
$scope.refreshTopProcesses();
$scope.refreshSSHLogins();
$scope.refreshSSHLogs();
@@ -2379,17 +2202,16 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.hideSystemCharts = true;
});
// Start polling for all stats (data feeds charts). Top Process is polled separately (slower, silent).
// Start polling for all stats (data feeds charts)
function pollAll() {
pollDashboardStats();
pollTraffic();
pollDiskIO();
pollCPU();
$scope.refreshTopProcesses();
$timeout(pollAll, pollInterval);
}
pollAll();
scheduleTopProcessPoll();
}, 800);
// SSH User Activity Modal

View File

@@ -1004,16 +1004,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.topProcesses = [];
$scope.loadingTopProcesses = true;
$scope.errorTopProcesses = '';
/** When true, automatic Top Process refresh is stopped (user can read the table). */
$scope.topProcessPaused = false;
/**
* @param {boolean} silent If true, refresh rows in place without clearing the table (no loading spinner).
*/
$scope.refreshTopProcesses = function(silent) {
silent = !!silent;
if (!silent) {
$scope.loadingTopProcesses = true;
}
$scope.refreshTopProcesses = function() {
$scope.loadingTopProcesses = true;
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.get('/base/getTopProcesses', h).then(function (response) {
$scope.loadingTopProcesses = false;
@@ -1022,12 +1014,9 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
} else {
$scope.topProcesses = [];
}
$scope.errorTopProcesses = '';
}, function (err) {
$scope.loadingTopProcesses = false;
if (!silent) {
$scope.errorTopProcesses = 'Failed to load top processes.';
}
$scope.errorTopProcesses = 'Failed to load top processes.';
});
};
@@ -1035,7 +1024,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogins = [];
$scope.sshLoginsPaginated = [];
$scope.sshLoginsCurrentPage = 1;
$scope.sshLoginsPerPage = 3;
$scope.sshLoginsPerPage = 10;
$scope.sshLoginsGoToPage = 1;
$scope.loadingSSHLogins = true;
$scope.errorSSHLogins = '';
@@ -1065,10 +1054,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
console.log('updateSSHLoginsPaginated: No data, cleared paginated array');
return;
}
var per = parseInt($scope.sshLoginsPerPage, 10) || 3;
$scope.sshLoginsPerPage = per;
var start = ($scope.sshLoginsCurrentPage - 1) * per;
var end = start + per;
var start = ($scope.sshLoginsCurrentPage - 1) * $scope.sshLoginsPerPage;
var end = start + $scope.sshLoginsPerPage;
$scope.sshLoginsPaginated = $scope.sshLogins.slice(start, end);
console.log('updateSSHLoginsPaginated: start=', start, 'end=', end, 'total=', $scope.sshLogins.length, 'paginated=', $scope.sshLoginsPaginated.length);
};
@@ -1097,12 +1084,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLoginsGoToPage = $scope.sshLoginsCurrentPage;
}
};
$scope.sshLoginsChangePerPage = function() {
$scope.sshLoginsPerPage = parseInt($scope.sshLoginsPerPage, 10) || 3;
$scope.sshLoginsCurrentPage = 1;
$scope.sshLoginsGoToPage = 1;
$scope.updateSSHLoginsPaginated();
};
$scope.refreshSSHLogins = function() {
$scope.loadingSSHLogins = true;
@@ -1134,7 +1115,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogs = [];
$scope.sshLogsPaginated = [];
$scope.sshLogsCurrentPage = 1;
$scope.sshLogsPerPage = 3;
$scope.sshLogsPerPage = 10;
$scope.sshLogsGoToPage = 1;
$scope.loadingSSHLogs = true;
$scope.errorSSHLogs = '';
@@ -1166,10 +1147,8 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
console.log('updateSSHLogsPaginated: No data, cleared paginated array');
return;
}
var per = parseInt($scope.sshLogsPerPage, 10) || 3;
$scope.sshLogsPerPage = per;
var start = ($scope.sshLogsCurrentPage - 1) * per;
var end = start + per;
var start = ($scope.sshLogsCurrentPage - 1) * $scope.sshLogsPerPage;
var end = start + $scope.sshLogsPerPage;
$scope.sshLogsPaginated = $scope.sshLogs.slice(start, end);
console.log('updateSSHLogsPaginated: start=', start, 'end=', end, 'total=', $scope.sshLogs.length, 'paginated=', $scope.sshLogsPaginated.length);
};
@@ -1198,12 +1177,6 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.sshLogsGoToPage = $scope.sshLogsCurrentPage;
}
};
$scope.sshLogsChangePerPage = function() {
$scope.sshLogsPerPage = parseInt($scope.sshLogsPerPage, 10) || 3;
$scope.sshLogsCurrentPage = 1;
$scope.sshLogsGoToPage = 1;
$scope.updateSSHLogsPaginated();
};
$scope.refreshSSHLogs = function() {
$scope.loadingSSHLogs = true;
@@ -1688,47 +1661,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
// For rate calculation
var lastRx = null, lastTx = null, lastDiskRead = null, lastDiskWrite = null, lastCPU = null;
var lastCPUTimes = null;
var pollInterval = 2000; // ms (charts / dashboard stats)
/** Top Process list: slower refresh, silent updates (no table flash). */
var topProcessPollIntervalMs = 10000;
var topProcessPollPromise = null;
function cancelTopProcessPoll() {
if (topProcessPollPromise) {
$timeout.cancel(topProcessPollPromise);
topProcessPollPromise = null;
}
}
function scheduleTopProcessPoll() {
cancelTopProcessPoll();
function tick() {
if ($scope.topProcessPaused) {
return;
}
topProcessPollPromise = $timeout(function() {
topProcessPollPromise = null;
if (!$scope.topProcessPaused) {
$scope.refreshTopProcesses(true);
}
if (!$scope.topProcessPaused) {
tick();
}
}, topProcessPollIntervalMs);
}
tick();
}
$scope.toggleTopProcessPause = function() {
$scope.topProcessPaused = !$scope.topProcessPaused;
if ($scope.topProcessPaused) {
cancelTopProcessPoll();
} else {
$scope.refreshTopProcesses(true);
scheduleTopProcessPoll();
}
};
var pollInterval = 2000; // ms
var maxPoints = 30;
// Expose so switchTab can create charts on first tab click if they weren't created at load
@@ -2251,6 +2184,7 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
// Initial setup - fetch stats immediately
pollDashboardStats();
$scope.refreshTopProcesses();
$scope.refreshSSHLogins();
$scope.refreshSSHLogs();
@@ -2268,17 +2202,16 @@ var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
$scope.hideSystemCharts = true;
});
// Start polling for all stats (data feeds charts). Top Process is polled separately (slower, silent).
// Start polling for all stats (data feeds charts)
function pollAll() {
pollDashboardStats();
pollTraffic();
pollDiskIO();
pollCPU();
$scope.refreshTopProcesses();
$timeout(pollAll, pollInterval);
}
pollAll();
scheduleTopProcessPoll();
}, 800);
// SSH User Activity Modal