diff --git a/baseTemplate/static/baseTemplate/custom-js/system-status.js b/baseTemplate/static/baseTemplate/custom-js/system-status.js index 63c4e4e42..b1f6e34b1 100644 --- a/baseTemplate/static/baseTemplate/custom-js/system-status.js +++ b/baseTemplate/static/baseTemplate/custom-js/system-status.js @@ -936,43 +936,187 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { // SSH Logins $scope.sshLogins = []; + $scope.sshLoginsPaginated = []; + $scope.sshLoginsCurrentPage = 1; + $scope.sshLoginsPerPage = 10; + $scope.sshLoginsGoToPage = 1; $scope.loadingSSHLogins = true; $scope.errorSSHLogins = ''; + + $scope.getSSHLoginsTotalPages = function() { + return Math.ceil($scope.sshLogins.length / $scope.sshLoginsPerPage); + }; + + $scope.getSSHLoginsStart = function() { + if (!$scope.sshLogins || $scope.sshLogins.length === 0) { + return 0; + } + return ($scope.sshLoginsCurrentPage - 1) * $scope.sshLoginsPerPage + 1; + }; + + $scope.getSSHLoginsEnd = function() { + if (!$scope.sshLogins || $scope.sshLogins.length === 0) { + return 0; + } + var end = $scope.sshLoginsCurrentPage * $scope.sshLoginsPerPage; + return Math.min(end, $scope.sshLogins.length); + }; + + $scope.updateSSHLoginsPaginated = function() { + if (!$scope.sshLogins || $scope.sshLogins.length === 0) { + $scope.sshLoginsPaginated = []; + console.log('updateSSHLoginsPaginated: No data, cleared paginated array'); + return; + } + 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); + }; + + $scope.sshLoginsPrevPage = function() { + if ($scope.sshLoginsCurrentPage > 1) { + $scope.sshLoginsCurrentPage--; + $scope.updateSSHLoginsPaginated(); + } + }; + + $scope.sshLoginsNextPage = function() { + if ($scope.sshLoginsCurrentPage < $scope.getSSHLoginsTotalPages()) { + $scope.sshLoginsCurrentPage++; + $scope.updateSSHLoginsPaginated(); + } + }; + + $scope.sshLoginsGoToPageNumber = function() { + var page = parseInt($scope.sshLoginsGoToPage); + var totalPages = $scope.getSSHLoginsTotalPages(); + if (page >= 1 && page <= totalPages) { + $scope.sshLoginsCurrentPage = page; + $scope.updateSSHLoginsPaginated(); + } else { + $scope.sshLoginsGoToPage = $scope.sshLoginsCurrentPage; + } + }; + $scope.refreshSSHLogins = function() { $scope.loadingSSHLogins = true; $http.get('/base/getRecentSSHLogins').then(function (response) { $scope.loadingSSHLogins = false; - if (response.data && response.data.logins) { + console.log('SSH Logins response:', response.data); + if (response.data && response.data.logins && Array.isArray(response.data.logins)) { $scope.sshLogins = response.data.logins; + $scope.sshLoginsCurrentPage = 1; + $scope.sshLoginsGoToPage = 1; + console.log('SSH Logins loaded:', $scope.sshLogins.length, 'items'); + $scope.updateSSHLoginsPaginated(); + console.log('SSH Logins paginated:', $scope.sshLoginsPaginated.length, 'items'); } else { + console.warn('SSH Logins: No data or invalid format', response.data); $scope.sshLogins = []; + $scope.sshLoginsPaginated = []; } }, function (err) { $scope.loadingSSHLogins = false; + console.error('SSH Logins error:', err); $scope.errorSSHLogins = 'Failed to load SSH logins.'; + $scope.sshLogins = []; + $scope.sshLoginsPaginated = []; }); }; // SSH Logs $scope.sshLogs = []; + $scope.sshLogsPaginated = []; + $scope.sshLogsCurrentPage = 1; + $scope.sshLogsPerPage = 10; + $scope.sshLogsGoToPage = 1; $scope.loadingSSHLogs = true; $scope.errorSSHLogs = ''; $scope.securityAlerts = []; $scope.loadingSecurityAnalysis = false; + + $scope.getSSHLogsTotalPages = function() { + return Math.ceil($scope.sshLogs.length / $scope.sshLogsPerPage); + }; + + $scope.getSSHLogsStart = function() { + if (!$scope.sshLogs || $scope.sshLogs.length === 0) { + return 0; + } + return ($scope.sshLogsCurrentPage - 1) * $scope.sshLogsPerPage + 1; + }; + + $scope.getSSHLogsEnd = function() { + if (!$scope.sshLogs || $scope.sshLogs.length === 0) { + return 0; + } + var end = $scope.sshLogsCurrentPage * $scope.sshLogsPerPage; + return Math.min(end, $scope.sshLogs.length); + }; + + $scope.updateSSHLogsPaginated = function() { + if (!$scope.sshLogs || $scope.sshLogs.length === 0) { + $scope.sshLogsPaginated = []; + console.log('updateSSHLogsPaginated: No data, cleared paginated array'); + return; + } + 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); + }; + + $scope.sshLogsPrevPage = function() { + if ($scope.sshLogsCurrentPage > 1) { + $scope.sshLogsCurrentPage--; + $scope.updateSSHLogsPaginated(); + } + }; + + $scope.sshLogsNextPage = function() { + if ($scope.sshLogsCurrentPage < $scope.getSSHLogsTotalPages()) { + $scope.sshLogsCurrentPage++; + $scope.updateSSHLogsPaginated(); + } + }; + + $scope.sshLogsGoToPageNumber = function() { + var page = parseInt($scope.sshLogsGoToPage); + var totalPages = $scope.getSSHLogsTotalPages(); + if (page >= 1 && page <= totalPages) { + $scope.sshLogsCurrentPage = page; + $scope.updateSSHLogsPaginated(); + } else { + $scope.sshLogsGoToPage = $scope.sshLogsCurrentPage; + } + }; + $scope.refreshSSHLogs = function() { $scope.loadingSSHLogs = true; $http.get('/base/getRecentSSHLogs').then(function (response) { $scope.loadingSSHLogs = false; - if (response.data && response.data.logs) { + console.log('SSH Logs response:', response.data); + if (response.data && response.data.logs && Array.isArray(response.data.logs)) { $scope.sshLogs = response.data.logs; + $scope.sshLogsCurrentPage = 1; + $scope.sshLogsGoToPage = 1; + console.log('SSH Logs loaded:', $scope.sshLogs.length, 'items'); + $scope.updateSSHLogsPaginated(); + console.log('SSH Logs paginated:', $scope.sshLogsPaginated.length, 'items'); // Analyze logs for security issues $scope.analyzeSSHSecurity(); } else { + console.warn('SSH Logs: No data or invalid format', response.data); $scope.sshLogs = []; + $scope.sshLogsPaginated = []; } }, function (err) { $scope.loadingSSHLogs = false; + console.error('SSH Logs error:', err); $scope.errorSSHLogs = 'Failed to load SSH logs.'; + $scope.sshLogs = []; + $scope.sshLogsPaginated = []; }); }; @@ -1499,16 +1643,48 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { var match = login.raw.match(/(pts\/[0-9]+)/); if (match) tty = match[1]; } - $http.post('/base/getSSHUserActivity', { user: login.user, tty: tty }).then(function(response) { + console.log('Fetching SSH activity for user:', login.user, 'IP:', login.ip, 'TTY:', tty); + $http.post('/base/getSSHUserActivity', { user: login.user, tty: tty, ip: login.ip || '' }, { + timeout: 30000 + }).then(function(response) { + console.log('SSH Activity response received:', response); $scope.loadingSSHActivity = false; - if (response.data) { + if (response.data && response.data.error) { + console.error('SSH Activity error:', response.data.error); + $scope.errorSSHActivity = response.data.error; + $scope.sshActivity = { processes: [], w: [], shell_history: [], geoip: {}, disk_usage: '' }; + } else if (response.data) { + console.log('SSH Activity data:', response.data); $scope.sshActivity = response.data; + $scope.errorSSHActivity = ''; } else { - $scope.sshActivity = { processes: [], w: [] }; + console.warn('SSH Activity: No data in response'); + $scope.sshActivity = { processes: [], w: [], shell_history: [], geoip: {}, disk_usage: '' }; + $scope.errorSSHActivity = 'No data received from server.'; } }, function(err) { $scope.loadingSSHActivity = false; - $scope.errorSSHActivity = (err.data && err.data.error) ? err.data.error : 'Failed to fetch activity.'; + console.error('Error fetching SSH activity:', err); + console.error('Error status:', err.status); + console.error('Error data:', err.data); + if (err.status === 0) { + $scope.errorSSHActivity = 'Network error: Unable to connect to server. Please check your connection.'; + } else if (err.status === -1) { + $scope.errorSSHActivity = 'Request timeout. The server took too long to respond.'; + } else if (err.data && err.data.error) { + $scope.errorSSHActivity = err.data.error; + } else if (err.data && err.data.errorMessage) { + $scope.errorSSHActivity = err.data.errorMessage; + } else if (err.status === 403) { + $scope.errorSSHActivity = 'Access denied. Admin privileges required.'; + } else if (err.status === 400) { + $scope.errorSSHActivity = 'Invalid request. Please try again.'; + } else if (err.status === 500) { + $scope.errorSSHActivity = 'Server error. Please try again later.'; + } else { + $scope.errorSSHActivity = 'Failed to fetch activity. Status: ' + (err.status || 'Unknown') + '. Please check your connection and try again.'; + } + $scope.sshActivity = { processes: [], w: [], shell_history: [], geoip: {}, disk_usage: '' }; }); }; @@ -1526,4 +1702,79 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { $scope.closeSSHActivityModal(); } }; + + // Kill a specific process + $scope.killProcess = function(pid, user) { + if (!confirm('Are you sure you want to kill process ' + pid + '? This action cannot be undone.')) { + return; + } + + console.log('Killing process:', pid, 'for user:', user); + $http.post('/base/killSSHProcess', { pid: pid, user: user }, { timeout: 10000 }).then(function(response) { + if (response.data && response.data.success) { + alert('Process ' + pid + ' killed successfully.'); + // Reload activity data + if ($scope.sshActivityUser) { + var login = { user: $scope.sshActivityUser, ip: '', tty: '' }; + $scope.viewSSHActivity(login); + } + } else if (response.data && response.data.error) { + alert('Error: ' + response.data.error); + } else { + alert('Unknown error occurred.'); + } + }, function(err) { + console.error('Error killing process:', err); + var errorMsg = 'Failed to kill process. '; + if (err.data && err.data.error) { + errorMsg += err.data.error; + } else if (err.status === 403) { + errorMsg += 'Access denied.'; + } else if (err.status === 404) { + errorMsg += 'Process not found.'; + } else { + errorMsg += 'Please try again.'; + } + alert(errorMsg); + }); + }; + + // Kill all sessions for a user + $scope.killSession = function(user) { + if (!confirm('WARNING: This will force close ALL active sessions for user "' + user + '". This action cannot be undone.\n\nAre you sure you want to continue?')) { + return; + } + + if (!confirm('Final confirmation: Kill all sessions for user "' + user + '"?')) { + return; + } + + console.log('Killing session for user:', user); + $http.post('/base/killSSHSession', { user: user }, { timeout: 10000 }).then(function(response) { + if (response.data && response.data.success) { + alert('All sessions for user ' + user + ' have been terminated successfully.'); + // Close modal and refresh SSH logins + $scope.closeSSHActivityModal(); + // Refresh SSH logins list + if (typeof $scope.loadSSHLogins === 'function') { + $scope.loadSSHLogins(); + } + } else if (response.data && response.data.error) { + alert('Error: ' + response.data.error); + } else { + alert('Unknown error occurred.'); + } + }, function(err) { + console.error('Error killing session:', err); + var errorMsg = 'Failed to kill session. '; + if (err.data && err.data.error) { + errorMsg += err.data.error; + } else if (err.status === 403) { + errorMsg += 'Access denied.'; + } else { + errorMsg += 'Please try again.'; + } + alert(errorMsg); + }); + }; }); \ No newline at end of file diff --git a/baseTemplate/templates/baseTemplate/homePage.html b/baseTemplate/templates/baseTemplate/homePage.html index d6b4409c2..96c50990d 100644 --- a/baseTemplate/templates/baseTemplate/homePage.html +++ b/baseTemplate/templates/baseTemplate/homePage.html @@ -234,25 +234,161 @@ width: 100%; border-collapse: collapse; margin-top: 15px; + display: table; + } + + .activity-table thead { + display: table-header-group; + } + + .activity-table tbody { + display: table-row-group; + } + + .activity-table tr { + display: table-row; + } + + .activity-table th, + .activity-table td { + display: table-cell !important; + } + + .activity-table tr { + display: table-row !important; + } + + .activity-table thead { + display: table-header-group !important; + } + + .activity-table tbody { + display: table-row-group !important; } .activity-table th { text-align: left; - padding: 12px 15px; + padding: 14px 12px; font-size: 11px; font-weight: 700; - color: var(--text-secondary, #64748b); + color: #ffffff; text-transform: uppercase; letter-spacing: 0.8px; - border-bottom: 2px solid var(--border-color, #e8e9ff); + border-bottom: 2px solid rgba(91, 95, 207, 0.3); + background: linear-gradient(135deg, #5b5fcf 0%, #4a4fc7 100%); + position: sticky; + top: 0; + z-index: 10; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + vertical-align: middle; + white-space: nowrap; + } + + .activity-table thead { + background: linear-gradient(135deg, #5b5fcf 0%, #4a4fc7 100%); + } + + .activity-table tbody tr { + border-bottom: 1px solid var(--border-color, #f0f0ff); + } + + .activity-table tbody tr:hover { background: var(--bg-hover, #f8f9ff); } .activity-table td { - padding: 12px 15px; + padding: 12px 12px; font-size: 13px; color: var(--text-primary, #2f3640); border-bottom: 1px solid var(--border-color, #f0f0ff); + vertical-align: middle; + word-wrap: break-word; + overflow-wrap: break-word; + } + + .activity-table td:nth-child(1) { + min-width: 80px; + max-width: 120px; + } + + .activity-table td:nth-child(2) { + min-width: 120px; + max-width: 180px; + font-family: monospace; + font-size: 12px; + } + + .activity-table td:nth-child(3) { + min-width: 80px; + max-width: 120px; + } + + .activity-table td:nth-child(4) { + min-width: 140px; + max-width: 200px; + white-space: nowrap; + } + + .activity-table td:nth-child(5) { + min-width: 150px; + max-width: 300px; + } + + .activity-table td:nth-child(6) { + min-width: 120px; + text-align: center; + } + + /* SSH Logs table specific column widths */ + .ssh-logs-table th:nth-child(1), + .ssh-logs-table td:nth-child(1) { + min-width: 180px; + max-width: 220px; + white-space: nowrap; + } + + .ssh-logs-table th:nth-child(2), + .ssh-logs-table td:nth-child(2) { + min-width: 300px; + word-break: break-word; + overflow-wrap: break-word; + } + + /* Process table specific column widths (for modal only) */ + .process-table thead th:nth-child(1), + .process-table tbody td:nth-child(1) { + min-width: 70px; + max-width: 90px; + } + + .process-table thead th:nth-child(2), + .process-table tbody td:nth-child(2) { + min-width: 80px; + max-width: 100px; + } + + .process-table thead th:nth-child(3), + .process-table tbody td:nth-child(3) { + min-width: 90px; + max-width: 120px; + } + + .process-table thead th:nth-child(4), + .process-table tbody td:nth-child(4) { + min-width: 200px; + } + + .process-table thead th:nth-child(5), + .process-table tbody td:nth-child(5) { + min-width: 150px; + max-width: 250px; + } + + .process-table thead th:nth-child(6), + .process-table tbody td:nth-child(6) { + min-width: 100px; + max-width: 120px; + text-align: center; } .activity-table tr:hover { @@ -278,6 +414,87 @@ box-shadow: 0 2px 4px rgba(91,95,207,0.3); } + /* Pagination Styles */ + .pagination-container { + margin-top: 20px; + padding: 16px 20px; + background: #ffffff; + border: 1px solid #e8e9ff; + border-radius: 12px; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 12px; + } + + .pagination-info { + color: #64748b; + font-size: 13px; + font-weight: 500; + } + + .pagination-controls { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + } + + .pagination-btn { + background: #f8f9ff; + border: 1px solid #e8e9ff; + color: #5b5fcf; + padding: 8px 16px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 6px; + } + + .pagination-btn:hover:not(:disabled) { + background: #5b5fcf; + color: #ffffff; + border-color: #5b5fcf; + } + + .pagination-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .pagination-page-info { + color: #2f3640; + font-size: 13px; + font-weight: 600; + padding: 0 8px; + } + + .pagination-goto { + display: flex; + align-items: center; + gap: 6px; + } + + .pagination-goto input { + width: 80px; + padding: 6px 8px; + border: 1px solid #e8e9ff; + border-radius: 6px; + font-size: 12px; + margin-right: 6px; + } + + .pagination-goto input:focus { + outline: none; + border-color: #5b5fcf; + box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1); + } + .chart-container { height: 280px; position: relative; @@ -297,17 +514,21 @@ height: 100vh; background: rgba(0,0,0,0.5); z-index: 10000; - display: flex; + display: none; align-items: center; justify-content: center; padding: 20px; backdrop-filter: blur(2px); - /* Initially hidden to prevent flicker on page load */ - display: none; + overflow-y: auto; } .modal-backdrop.show { - display: flex; + display: flex !important; + } + + /* Ensure modal is hidden when ng-show is false */ + .modal-backdrop.ng-hide { + display: none !important; } .modal-content { @@ -321,6 +542,31 @@ position: relative; overflow-y: auto; animation: modalFadeIn 0.3s ease-out; + margin: auto; + align-self: center; + } + + /* Ensure tables inside modal render correctly */ + .modal-content table { + display: table !important; + width: 100% !important; + } + + .modal-content table thead { + display: table-header-group !important; + } + + .modal-content table tbody { + display: table-row-group !important; + } + + .modal-content table tr { + display: table-row !important; + } + + .modal-content table th, + .modal-content table td { + display: table-cell !important; } @keyframes modalFadeIn { @@ -545,11 +791,14 @@ - {$ login.user $} - {$ login.ip $} - {$ login.country $} + {$ login.user $} + {$ login.ip $} + + + {$ login.country || 'N/A' $} + {$ login.date $} - {$ login.session $} + {$ login.session $} @@ -681,7 +930,7 @@
No recent SSH logs found.
- +
@@ -690,8 +939,8 @@ - - + +
TIMESTAMP
{$ log.timestamp $}{$ log.message $}{$ log.timestamp $}{$ log.message $}
@@ -754,7 +1003,7 @@ -