diff --git a/baseTemplate/static/baseTemplate/custom-js/system-status.js b/baseTemplate/static/baseTemplate/custom-js/system-status.js index 56a893fda..b4cf11a11 100644 --- a/baseTemplate/static/baseTemplate/custom-js/system-status.js +++ b/baseTemplate/static/baseTemplate/custom-js/system-status.js @@ -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 diff --git a/baseTemplate/templates/baseTemplate/homePage.html b/baseTemplate/templates/baseTemplate/homePage.html index da485df46..75086e292 100644 --- a/baseTemplate/templates/baseTemplate/homePage.html +++ b/baseTemplate/templates/baseTemplate/homePage.html @@ -1002,8 +1002,8 @@ - Page {$ sshLoginsCurrentPage $} / {$ getSSHLoginsTotalPages() || 1 $} - - Go to - - @@ -1213,10 +1207,6 @@
- - Trusted IP — not shown as a threat and cannot be banned from here. - -
+ {$ log.timestamp $} {$ log.message $} @@ -1274,7 +1263,7 @@ - - - Page {$ sshLogsCurrentPage $} / {$ getSSHLogsTotalPages() || 1 $} - - Go to - -
-
- - Auto-refresh paused - - -
Loading top processes...
diff --git a/baseTemplate/urls.py b/baseTemplate/urls.py index 4372548d6..fd30efb2e 100644 --- a/baseTemplate/urls.py +++ b/baseTemplate/urls.py @@ -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'), diff --git a/baseTemplate/views.py b/baseTemplate/views.py index f1d4735fb..871a8551e 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -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): diff --git a/emailPremium/views.py b/emailPremium/views.py index a78a2b6fd..8a3be354c 100644 --- a/emailPremium/views.py +++ b/emailPremium/views.py @@ -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'] diff --git a/loginSystem/webauthn_views.py b/loginSystem/webauthn_views.py index c0c693b24..9b3f3ee69 100644 --- a/loginSystem/webauthn_views.py +++ b/loginSystem/webauthn_views.py @@ -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) diff --git a/plogical/mailUtilities.py b/plogical/mailUtilities.py index bcf6062de..ec5f821ef 100644 --- a/plogical/mailUtilities.py +++ b/plogical/mailUtilities.py @@ -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: diff --git a/plogical/processUtilities.py b/plogical/processUtilities.py index c020180b7..58b270aba 100644 --- a/plogical/processUtilities.py +++ b/plogical/processUtilities.py @@ -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 diff --git a/public/static/baseTemplate/custom-js/system-status.js b/public/static/baseTemplate/custom-js/system-status.js index 64609165b..b4cf11a11 100644 --- a/public/static/baseTemplate/custom-js/system-status.js +++ b/public/static/baseTemplate/custom-js/system-status.js @@ -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 diff --git a/static/baseTemplate/custom-js/system-status.js b/static/baseTemplate/custom-js/system-status.js index c6d928c79..b4cf11a11 100644 --- a/static/baseTemplate/custom-js/system-status.js +++ b/static/baseTemplate/custom-js/system-status.js @@ -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