mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-06 15:27:05 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user