Merge pull request #1638 from master3395/v2.5.5-dev

V2.5.5 dev
This commit is contained in:
Master3395 2026-01-04 03:59:51 +01:00 committed by GitHub
commit 0a331ab00c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 1002 additions and 172 deletions

View File

@ -944,12 +944,19 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.loadingSSHLogins = false;
if (response.data && response.data.logins) {
$scope.sshLogins = response.data.logins;
// Debug: Log first login to see structure
if ($scope.sshLogins.length > 0) {
console.log('First SSH login object:', $scope.sshLogins[0]);
console.log('IP field:', $scope.sshLogins[0].ip);
console.log('All keys:', Object.keys($scope.sshLogins[0]));
}
} else {
$scope.sshLogins = [];
}
}, function (err) {
$scope.loadingSSHLogins = false;
$scope.errorSSHLogins = 'Failed to load SSH logins.';
console.error('Failed to load SSH logins:', err);
});
};
@ -1552,28 +1559,419 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.loadingSSHActivity = false;
$scope.errorSSHActivity = '';
$scope.viewSSHActivity = function(login) {
$scope.viewSSHActivity = function(login, event) {
$scope.showSSHActivityModal = true;
$scope.sshActivity = { processes: [], w: [] };
$scope.sshActivityUser = login.user;
// Extract IP from multiple sources - comprehensive extraction for IPv4 and IPv6
var extractedIP = '';
// Method 1: Direct property access (highest priority - from backend)
if (login && login.ip) {
extractedIP = login.ip.toString().trim();
} else if (login && login['ip']) {
extractedIP = login['ip'].toString().trim();
}
// Method 2: Alternative field names
if (!extractedIP && login) {
if (login.ipAddress) extractedIP = login.ipAddress.toString().trim();
else if (login['IP Address']) extractedIP = login['IP Address'].toString().trim();
else if (login['IP']) extractedIP = login['IP'].toString().trim();
}
// Method 3: Extract from raw line using regex (IPv4 and IPv6)
if (!extractedIP && login && login.raw) {
// Try IPv4 first (most common)
var ipv4Match = login.raw.match(/\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/);
if (ipv4Match && ipv4Match[1]) {
var ipv4 = ipv4Match[1].trim();
if (ipv4 !== '127.0.0.1' && ipv4 !== '0.0.0.0') {
extractedIP = ipv4;
}
}
// If no valid IPv4, try IPv6
if (!extractedIP) {
// IPv6 pattern: matches full IPv6 addresses and compressed forms
var ipv6Pattern = /([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}/;
var ipv6Match = login.raw.match(ipv6Pattern);
if (ipv6Match && ipv6Match[0]) {
var ipv6 = ipv6Match[0].trim();
if (ipv6 !== '::1' && ipv6.length > 0) {
extractedIP = ipv6;
}
}
}
}
// Method 4: Try to get from event target data attribute as fallback
if (!extractedIP && event && event.currentTarget) {
var dataIP = event.currentTarget.getAttribute('data-ip');
if (dataIP) extractedIP = dataIP.toString().trim();
}
// Final fallback: search entire raw line for any IP
if (!extractedIP && login && login.raw) {
// Try all IPv4 addresses
var allIPv4s = login.raw.match(/\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/g);
if (allIPv4s && allIPv4s.length > 0) {
for (var i = 0; i < allIPv4s.length; i++) {
var ip = allIPv4s[i].trim();
if (ip !== '127.0.0.1' && ip !== '0.0.0.0' && ip.length > 0) {
extractedIP = ip;
break;
}
}
}
// If no IPv4, try all IPv6 addresses
if (!extractedIP) {
var allIPv6s = login.raw.match(/([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}/g);
if (allIPv6s && allIPv6s.length > 0) {
for (var j = 0; j < allIPv6s.length; j++) {
var ip6 = allIPv6s[j].trim();
if (ip6 !== '::1' && ip6.length > 0) {
extractedIP = ip6;
break;
}
}
}
}
}
// Final cleanup
$scope.sshActivityIP = (extractedIP || '').toString().trim();
$scope.sshActivityTTY = ''; // Store TTY for kill session
// Check both 'session' and 'activity' fields for status
$scope.sshActivityStatus = login.session || login.activity || '';
// Use backend is_active field if available (most reliable)
// Fallback to checking session text if is_active is not set
// IMPORTANT: Check for both boolean true and string 'true' (JSON might serialize differently)
if (login.is_active !== undefined && login.is_active !== null) {
// Backend explicitly set is_active
$scope.isActiveSession = (login.is_active === true || login.is_active === 'true' || login.is_active === 1 || login.is_active === '1');
console.log('Using backend is_active field:', login.is_active, '-> isActiveSession:', $scope.isActiveSession);
} else {
// Fallback: check session text
var sessionStatus = ($scope.sshActivityStatus || '').toLowerCase();
$scope.isActiveSession = (sessionStatus.indexOf('still logged in') !== -1);
console.log('Using fallback session text check:', sessionStatus, '-> isActiveSession:', $scope.isActiveSession);
}
// If IP is still empty, try one more time with more aggressive extraction
if (!$scope.sshActivityIP && login) {
console.log('IP still empty, trying aggressive extraction...');
// Try every possible field name variation
var possibleIPFields = ['ip', 'IP', 'ipAddress', 'IP Address', 'ip_address', 'IP_ADDRESS', 'client_ip', 'clientIP', 'source_ip', 'sourceIP'];
for (var k = 0; k < possibleIPFields.length; k++) {
if (login[possibleIPFields[k]]) {
var testIP = login[possibleIPFields[k]].toString().trim();
// Validate it looks like an IP (IPv4 or IPv6)
if (testIP.match(/^(\d{1,3}\.){3}\d{1,3}$/) || testIP.match(/^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/)) {
if (testIP !== '127.0.0.1' && testIP !== '0.0.0.0' && testIP !== '::1') {
$scope.sshActivityIP = testIP;
console.log('Found IP in field', possibleIPFields[k], ':', $scope.sshActivityIP);
break;
}
}
}
}
// Last resort: check if IP is in the table cell itself (from DOM)
if (!$scope.sshActivityIP && event && event.currentTarget) {
try {
var row = event.currentTarget.closest('tr');
if (row) {
// Try different column positions (IP could be in different positions)
var cells = row.querySelectorAll('td');
for (var cellIdx = 0; cellIdx < cells.length; cellIdx++) {
var cellText = cells[cellIdx].textContent.trim();
// Check if this cell contains an IP address
var ipMatch = cellText.match(/^(\d{1,3}\.){3}\d{1,3}$/) || cellText.match(/^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/);
if (ipMatch && cellText !== '127.0.0.1' && cellText !== '0.0.0.0' && cellText !== '::1') {
$scope.sshActivityIP = cellText;
console.log('Found IP from table cell (column ' + cellIdx + '):', $scope.sshActivityIP);
break;
}
}
}
} catch (e) {
console.log('Error extracting IP from DOM:', e);
}
}
}
// Debug logging - detailed inspection
console.log('View SSH Activity - Login object:', login);
console.log('Login keys:', Object.keys(login));
console.log('login.ip:', login.ip);
console.log('login.is_active:', login.is_active);
console.log('Extracted IP:', $scope.sshActivityIP);
console.log('Session status:', $scope.sshActivityStatus);
console.log('Is active session:', $scope.isActiveSession);
$scope.showFullJSON = false; // Collapsible JSON view
$scope.loadingSSHActivity = true;
$scope.errorSSHActivity = '';
$scope.killingProcess = null;
$scope.killingSession = false;
var tty = '';
// Try to extract tty from login.raw or login.session if available
if (login.raw) {
var match = login.raw.match(/(pts\/[0-9]+)/);
if (match) tty = match[1];
if (match) {
tty = match[1];
$scope.sshActivityTTY = tty;
}
}
$http.post('/base/getSSHUserActivity', { user: login.user, tty: tty }).then(function(response) {
// Also try to extract from session field or raw line
if (!tty && login.session) {
var sessionMatch = login.session.match(/(pts\/[0-9]+)/);
if (sessionMatch) {
tty = sessionMatch[1];
$scope.sshActivityTTY = tty;
}
}
// Also check raw line for TTY
if (!tty && login.raw) {
var rawMatch = login.raw.match(/(pts\/[0-9]+)/);
if (rawMatch) {
tty = rawMatch[1];
$scope.sshActivityTTY = tty;
}
}
// Make API call with IP included - reduced timeout for faster response
var requestData = {
user: login.user,
tty: tty,
ip: $scope.sshActivityIP
};
// Set shorter timeout for faster feedback
var timeoutPromise = $timeout(function() {
$scope.loadingSSHActivity = false;
$scope.errorSSHActivity = 'Request timed out. The user may not have any active processes.';
$scope.sshActivity = { processes: [], w: [] };
}, 5000); // 5 second timeout (reduced from 10)
$http.post('/base/getSSHUserActivity', requestData, { timeout: 3000 }).then(function(response) {
$timeout.cancel(timeoutPromise); // Cancel timeout on success
$scope.loadingSSHActivity = false;
if (response.data) {
$scope.sshActivity = response.data;
// Check if response has error field
if (response.data.error) {
$scope.errorSSHActivity = response.data.error;
$scope.sshActivity = { processes: [], w: [] };
} else {
$scope.sshActivity = response.data;
// Ensure all expected fields exist
if (!$scope.sshActivity.processes) $scope.sshActivity.processes = [];
if (!$scope.sshActivity.w) $scope.sshActivity.w = [];
if (!$scope.sshActivity.process_tree) $scope.sshActivity.process_tree = [];
if (!$scope.sshActivity.shell_history) $scope.sshActivity.shell_history = [];
// Try to extract TTY from processes if not already set
if (!$scope.sshActivityTTY && response.data.processes && response.data.processes.length > 0) {
var firstProcess = response.data.processes[0];
if (firstProcess.tty) {
$scope.sshActivityTTY = firstProcess.tty;
}
}
// Update active session status - prioritize backend is_active field
// The backend already determined if session is active, so trust that first
// Only update if we have additional evidence (processes/w output)
var hasProcesses = response.data.processes && response.data.processes.length > 0;
var hasActiveW = response.data.w && response.data.w.length > 0;
// If backend says it's active, keep it active (don't override)
// If backend says inactive but we find processes/w, mark as active
if ($scope.isActiveSession === true) {
// Backend already marked as active, keep it that way
// (processes might not have loaded yet, but session is still active)
} else if (hasProcesses || hasActiveW) {
// Backend said inactive, but we found evidence it's active
$scope.isActiveSession = true;
}
// If backend said inactive and no processes found, keep as inactive
// Debug logging
console.log('SSH Activity loaded:', {
processes: response.data.processes ? response.data.processes.length : 0,
w: response.data.w ? response.data.w.length : 0,
hasProcesses: hasProcesses,
hasActiveW: hasActiveW,
originalStatus: $scope.sshActivityStatus,
isActiveSession: $scope.isActiveSession,
ip: $scope.sshActivityIP
});
}
} else {
$scope.sshActivity = { processes: [], w: [] };
$scope.errorSSHActivity = 'No data returned from server.';
}
}, function(err) {
$timeout.cancel(timeoutPromise); // Cancel timeout on error
$scope.loadingSSHActivity = false;
$scope.errorSSHActivity = (err.data && err.data.error) ? err.data.error : 'Failed to fetch activity.';
var errorMsg = 'Failed to fetch activity.';
// Handle different error scenarios
if (err.data) {
// Server returned error data
if (err.data.error) {
errorMsg = err.data.error;
} else if (typeof err.data === 'string') {
errorMsg = err.data;
} else if (err.data.message) {
errorMsg = err.data.message;
}
} else if (err.status === 0 || err.status === -1) {
errorMsg = 'Network error. Please check your connection and try again.';
} else if (err.status >= 500) {
errorMsg = 'Server error (HTTP ' + err.status + '). Please try again later.';
} else if (err.status === 404) {
errorMsg = 'Endpoint not found. Please refresh the page.';
} else if (err.status === 403) {
errorMsg = 'Access denied. Admin privileges required.';
} else if (err.status === 400) {
errorMsg = 'Invalid request. Please check the user information.';
} else if (err.status) {
errorMsg = 'Request failed with status ' + err.status + '.';
} else if (err.message) {
errorMsg = err.message;
}
$scope.errorSSHActivity = errorMsg;
// Set empty activity data so modal can still display
$scope.sshActivity = {
processes: [],
w: [],
process_tree: [],
shell_history: [],
disk_usage: '',
geoip: {}
};
// Log error for debugging
console.error('SSH Activity fetch error:', err);
});
};
// Kill individual process
$scope.killProcess = function(pid, user) {
if (!confirm('Are you sure you want to force kill process ' + pid + '? This action cannot be undone.')) {
return;
}
$scope.killingProcess = pid;
var data = {
pid: pid,
user: user
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('/base/killSSHProcess', data, config).then(function(response) {
$scope.killingProcess = null;
if (response.data && response.data.success) {
new PNotify({
title: 'Process Killed',
text: response.data.message || 'Process ' + pid + ' has been terminated.',
type: 'success',
delay: 3000
});
// Refresh activity to update process list
$scope.viewSSHActivity({ user: user, ip: $scope.sshActivityIP, raw: '', session: '' });
} else {
new PNotify({
title: 'Error',
text: (response.data && response.data.error) || 'Failed to kill process.',
type: 'error',
delay: 5000
});
}
}, function(err) {
$scope.killingProcess = null;
var errorMsg = 'Failed to kill process.';
if (err.data && err.data.error) {
errorMsg = err.data.error;
} else if (err.data && err.data.message) {
errorMsg = err.data.message;
}
new PNotify({
title: 'Error',
text: errorMsg,
type: 'error',
delay: 5000
});
});
};
// Kill entire SSH session
$scope.killSSHSession = function(user, tty) {
var confirmMsg = 'Are you sure you want to kill all processes for user ' + user;
if (tty) {
confirmMsg += ' on terminal ' + tty;
}
confirmMsg += '? This will terminate their SSH session.';
if (!confirm(confirmMsg)) {
return;
}
$scope.killingSession = true;
var data = {
user: user,
tty: tty || ''
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('/base/killSSHSession', data, config).then(function(response) {
$scope.killingSession = false;
if (response.data && response.data.success) {
new PNotify({
title: 'Session Terminated',
text: response.data.message || 'SSH session has been terminated successfully.',
type: 'success',
delay: 3000
});
// Close modal and refresh page after a delay
setTimeout(function() {
$scope.closeSSHActivityModal();
location.reload();
}, 1500);
} else {
new PNotify({
title: 'Error',
text: (response.data && response.data.error) || 'Failed to kill session.',
type: 'error',
delay: 5000
});
}
}, function(err) {
$scope.killingSession = false;
var errorMsg = 'Failed to kill session.';
if (err.data && err.data.error) {
errorMsg = err.data.error;
} else if (err.data && err.data.message) {
errorMsg = err.data.message;
}
new PNotify({
title: 'Error',
text: errorMsg,
type: 'error',
delay: 5000
});
});
};
@ -1581,8 +1979,15 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.showSSHActivityModal = false;
$scope.sshActivity = { processes: [], w: [] };
$scope.sshActivityUser = '';
$scope.sshActivityIP = ''; // Clear IP when closing modal
$scope.sshActivityTTY = ''; // Clear TTY when closing modal
$scope.sshActivityStatus = ''; // Clear activity status
$scope.isActiveSession = false; // Reset active session flag
$scope.showFullJSON = false; // Reset JSON view
$scope.loadingSSHActivity = false;
$scope.errorSSHActivity = '';
$scope.killingProcess = null;
$scope.killingSession = false;
};
// Close modal when clicking backdrop

View File

@ -230,34 +230,114 @@
display: block;
}
.session-status {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.active-session {
background-color: #d1fae5;
color: #065f46;
}
.active-session i {
color: #10b981;
font-size: 8px;
margin-right: 6px;
}
.inactive-session {
background-color: #fee2e2;
color: #991b1b;
}
.inactive-session i {
color: #ef4444;
font-size: 8px;
margin-right: 6px;
}
.session-status span {
color: #64748b;
font-weight: normal;
margin-left: 6px;
font-size: 11px;
}
.activity-table {
width: 100%;
border-collapse: collapse;
background: var(--bg-primary, white);
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border-primary, #e8e9ff);
margin-top: 15px;
table-layout: fixed;
display: table;
border-collapse: separate;
border-spacing: 0;
}
.activity-table-wrapper {
width: 100%;
overflow-x: auto;
}
.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;
background: linear-gradient(135deg, #5b5fcf 0%, #4a4fc7 100%);
}
.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 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);
}
.activity-table tr:hover {
background: var(--bg-hover, #f8f9ff);
vertical-align: middle;
word-wrap: break-word;
overflow-wrap: break-word;
}
.view-activity-btn {
@ -298,23 +378,37 @@
height: 100vh;
background: rgba(0,0,0,0.5);
z-index: 10000;
display: flex;
display: none !important;
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;
}
.modal-backdrop.ng-cloak {
display: none !important;
}
.modal-backdrop.show.ng-cloak {
display: flex !important;
}
/* Ensure modal is hidden when ng-show is false */
.modal-backdrop:not(.show) {
display: none !important;
visibility: hidden;
opacity: 0;
}
.modal-content {
max-width: 90vw;
max-height: 90vh;
width: 600px;
width: 800px;
background: var(--bg-secondary, #fff);
border-radius: 16px;
box-shadow: 0 8px 40px rgba(0,0,0,0.18);
@ -322,6 +416,202 @@
position: relative;
overflow-y: auto;
animation: modalFadeIn 0.3s ease-out;
margin: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid var(--border-color, #e8e9ff);
}
.modal-title {
font-size: 1.5rem;
font-weight: 700;
color: #5b5fcf;
display: flex;
align-items: center;
gap: 10px;
}
.modal-close-btn {
border: none;
background: none;
font-size: 1.8rem;
color: #64748b;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
transition: all 0.2s ease;
}
.modal-close-btn:hover {
background: var(--bg-hover, #f8f9ff);
color: #2f3640;
}
.activity-content {
background: #f8f9fa;
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
max-height: 400px;
overflow-y: auto;
}
.activity-json {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.6;
color: #2f3640;
}
.process-list {
margin: 15px 0;
}
.process-item {
background: white;
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 8px;
padding: 12px 15px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.process-info {
flex: 1;
}
.process-pid {
font-weight: 600;
color: #5b5fcf;
margin-right: 10px;
}
.process-cmd {
color: #64748b;
font-size: 13px;
}
.process-actions {
display: flex;
gap: 8px;
}
.kill-process-btn {
background: #ef4444;
color: white;
border: none;
padding: 6px 14px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.kill-process-btn:hover {
background: #dc2626;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
}
.kill-process-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.modal-actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
margin-top: 20px;
padding-top: 20px;
border-top: 2px solid var(--border-color, #e8e9ff);
flex-wrap: wrap;
}
.modal-actions-left {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.modal-actions-right {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.kill-session-btn {
background: #f59e0b;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.kill-session-btn:hover {
background: #d97706;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
}
.kill-session-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.ban-ip-btn {
background: #dc2626;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.ban-ip-btn:hover:not(:disabled) {
background: #b91c1c;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
}
.ban-ip-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@keyframes modalFadeIn {
@ -506,19 +796,19 @@
{$ securityAlerts.length $}
</span>
</button>
<button class="activity-tab" onclick="switchTab('top-process', this)" ng-hide="hideSystemCharts">
<button class="activity-tab" onclick="switchTab('top-process', this)">
<i class="fas fa-microchip"></i>
<span>Top Process</span>
</button>
<button class="activity-tab" onclick="switchTab('traffic', this)" ng-hide="hideSystemCharts">
<button class="activity-tab" onclick="switchTab('traffic', this)">
<i class="fas fa-chart-line"></i>
<span>Traffic</span>
</button>
<button class="activity-tab" onclick="switchTab('diskio', this)" ng-hide="hideSystemCharts">
<button class="activity-tab" onclick="switchTab('diskio', this)">
<i class="fas fa-hard-drive"></i>
<span>Disk IO</span>
</button>
<button class="activity-tab" onclick="switchTab('cpu-usage', this)" ng-hide="hideSystemCharts">
<button class="activity-tab" onclick="switchTab('cpu-usage', this)">
<i class="fas fa-tachometer-alt"></i>
<span>CPU Usage</span>
</button>
@ -533,7 +823,8 @@
<div ng-if="!loadingSSHLogins && sshLogins.length === 0" style="text-align: center; padding: 20px; color: #8893a7;">
No recent SSH logins found.
</div>
<table class="activity-table" ng-if="!loadingSSHLogins && sshLogins.length > 0">
<div class="activity-table-wrapper">
<table class="activity-table records-table" ng-if="!loadingSSHLogins && sshLogins.length > 0">
<thead>
<tr>
<th style="width: 15%;">USER</th>
@ -548,26 +839,35 @@
<tr ng-repeat="login in sshLogins">
<td>{$ login.user $}</td>
<td>{$ login.ip $}</td>
<td>{$ login.country $}</td>
<td>{$ login.date $}</td>
<td>{$ login.session $}</td>
<td>
<div style="display: flex; gap: 8px; align-items: center;">
<button class="view-activity-btn" ng-click="viewSSHActivity(login)">View Activity</button>
<button class="ban-ip-btn" ng-click="blockIPAddress(login.ip)"
style="background: #dc2626; color: white; border: 1px solid #dc2626; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer;"
onmouseover="this.style.background='#b91c1c'"
onmouseout="this.style.background='#dc2626'">
<i class="fas fa-ban"></i> Ban IP
</button>
<div style="display: flex; align-items: center; gap: 6px;">
<img ng-if="login.flag" ng-src="{$ login.flag $}" alt="{$ login.country $}" style="width: 24px; height: 18px; border: 1px solid #e8e9ff; border-radius: 2px;" />
<span>{$ login.country $}</span>
</div>
</td>
<td>{$ login.date $}</td>
<td>
<span ng-if="login.is_active" class="session-status active-session">
<i class="fas fa-circle" style="color: #10b981; font-size: 8px; margin-right: 6px;"></i>
<strong>ACTIVE</strong>
<span style="color: #64748b; font-weight: normal; margin-left: 6px;">({$ login.session $})</span>
</span>
<span ng-if="!login.is_active" class="session-status inactive-session">
<i class="fas fa-circle" style="color: #ef4444; font-size: 8px; margin-right: 6px;"></i>
<strong>INACTIVE</strong>
<span style="color: #64748b; font-weight: normal; margin-left: 6px;">({$ login.session $})</span>
</span>
</td>
<td>
<button class="view-activity-btn" ng-click="viewSSHActivity(login, $event)">View Activity</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Dummy data for demonstration -->
<table class="activity-table" ng-if="loadingSSHLogins">
<table class="activity-table records-table" ng-if="loadingSSHLogins">
<thead>
<tr>
<th style="width: 15%;">USER</th>
@ -586,12 +886,7 @@
<td>Wed Jun 4 20:59</td>
<td>Still Logged In</td>
<td>
<div style="display: flex; gap: 8px; align-items: center;">
<button class="view-activity-btn" style="background: #5b5fcf; color: white; border-color: #5b5fcf;">View Activity</button>
<button class="ban-ip-btn" style="background: #dc2626; color: white; border: 1px solid #dc2626; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer;">
<i class="fas fa-ban"></i> Ban IP
</button>
</div>
<button class="view-activity-btn" style="background: #5b5fcf; color: white; border-color: #5b5fcf;">View Activity</button>
</td>
</tr>
</tbody>
@ -715,7 +1010,7 @@
<div ng-if="!loadingSSHLogs && sshLogs.length === 0" style="text-align: center; padding: 20px; color: #8893a7;">
No recent SSH logs found.
</div>
<table class="activity-table" ng-if="!loadingSSHLogs && sshLogs.length > 0">
<table class="activity-table records-table" ng-if="!loadingSSHLogs && sshLogs.length > 0">
<thead>
<tr>
<th>TIMESTAMP</th>
@ -732,7 +1027,7 @@
</div>
<!-- Top Process Tab -->
<div id="top-process" class="tab-content" ng-hide="hideSystemCharts">
<div id="top-process" class="tab-content">
<div ng-if="loadingTopProcesses" style="text-align: center; padding: 20px; color: #8893a7;">
Loading top processes...
</div>
@ -742,7 +1037,7 @@
<div ng-if="!loadingTopProcesses && !errorTopProcesses && topProcesses.length === 0" style="text-align: center; padding: 20px; color: #8893a7;">
No process information available.
</div>
<table class="activity-table" ng-if="!loadingTopProcesses && !errorTopProcesses && topProcesses.length > 0">
<table class="activity-table records-table" ng-if="!loadingTopProcesses && !errorTopProcesses && topProcesses.length > 0">
<thead>
<tr>
<th>PID</th>
@ -765,21 +1060,21 @@
</div>
<!-- Traffic Tab -->
<div id="traffic" class="tab-content" ng-hide="hideSystemCharts">
<div id="traffic" class="tab-content">
<div class="chart-container">
<canvas id="trafficChart"></canvas>
</div>
</div>
<!-- Disk IO Tab -->
<div id="diskio" class="tab-content" ng-hide="hideSystemCharts">
<div id="diskio" class="tab-content">
<div class="chart-container">
<canvas id="diskIOChart"></canvas>
</div>
</div>
<!-- CPU Usage Tab -->
<div id="cpu-usage" class="tab-content" ng-hide="hideSystemCharts">
<div id="cpu-usage" class="tab-content">
<div class="chart-container">
<canvas id="cpuChart"></canvas>
</div>
@ -788,19 +1083,168 @@
</div>
<!-- SSH Activity Modal -->
<div ng-show="showSSHActivityModal" class="modal-backdrop show ng-cloak" ng-click="closeModalOnBackdrop($event)">
<div class="modal-content">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px;">
<div style="font-size: 1.2rem; font-weight: 800; color: #5b5fcf;">
<div ng-if="showSSHActivityModal" class="modal-backdrop" ng-class="{'show': showSSHActivityModal}" ng-click="closeModalOnBackdrop($event)">
<div class="modal-content" ng-click="$event.stopPropagation()">
<div class="modal-header">
<div class="modal-title">
<i class="fas fa-user-circle"></i>
User Activity: <span style="color: #2f3640;">{$ sshActivityUser $}</span>
<span ng-if="sshActivityIP" style="font-size: 0.9rem; color: #64748b; font-weight: 500; margin-left: 10px;">
(<i class="fas fa-network-wired"></i> {$ sshActivityIP $})
</span>
</div>
<button class="btn btn-sm" style="border: none; background: none; font-size: 1.5rem; cursor: pointer;" ng-click="closeSSHActivityModal()">&times;</button>
<button class="modal-close-btn" ng-click="closeSSHActivityModal()" title="Close">
&times;
</button>
</div>
<div ng-if="loadingSSHActivity" style="text-align: center; color: #8893a7; padding: 20px 0;">Loading activity...</div>
<div ng-if="errorSSHActivity" style="color: #e53935; padding: 10px 0;">{$ errorSSHActivity $}</div>
<div ng-if="loadingSSHActivity" style="text-align: center; color: #8893a7; padding: 40px 20px;">
<i class="fas fa-spinner fa-spin" style="font-size: 2rem; margin-bottom: 10px; display: block;"></i>
<div>Loading activity...</div>
</div>
<div ng-if="errorSSHActivity" style="background: #fee2e2; border: 1px solid #fecaca; color: #991b1b; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
<i class="fas fa-exclamation-circle"></i> {$ errorSSHActivity $}
</div>
<div ng-if="!loadingSSHActivity && !errorSSHActivity">
<!-- Activity content will be displayed here -->
<pre style="background: #f8f9fa; padding: 15px; border-radius: 8px;">{$ sshActivity | json $}</pre>
<!-- Session Status Info -->
<div style="background: #f8f9ff; border-left: 4px solid #5b5fcf; padding: 12px 16px; border-radius: 6px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 8px;">
<i class="fas fa-info-circle" style="color: #5b5fcf;"></i>
<strong style="color: #2f3640;">Session Status:</strong>
<span ng-if="isActiveSession && sshActivity.processes && sshActivity.processes.length > 0" style="color: #10b981; font-weight: 600;">
<i class="fas fa-circle" style="font-size: 8px;"></i> ACTIVE - User is logged in with running processes
</span>
<span ng-if="isActiveSession && (!sshActivity.processes || sshActivity.processes.length === 0) && sshActivity.w && sshActivity.w.length > 0" style="color: #f59e0b; font-weight: 600;">
<i class="fas fa-circle" style="font-size: 8px;"></i> IDLE - User is logged in but no processes running (shell waiting)
</span>
<span ng-if="isActiveSession && (!sshActivity.processes || sshActivity.processes.length === 0) && (!sshActivity.w || sshActivity.w.length === 0)" style="color: #ef4444; font-weight: 600;">
<i class="fas fa-circle" style="font-size: 8px;"></i> INCONSISTENT - Marked as active but no processes or sessions found
</span>
<span ng-if="!isActiveSession" style="color: #ef4444; font-weight: 600;">
<i class="fas fa-circle" style="font-size: 8px;"></i> INACTIVE - Session has ended
</span>
</div>
<div style="font-size: 12px; color: #64748b; margin-left: 24px;">
<div ng-if="sshActivityStatus">
<strong>Login Status:</strong> {$ sshActivityStatus $}
</div>
<div ng-if="sshActivity.processes && sshActivity.processes.length > 0" style="margin-top: 4px;">
<strong>Running Processes:</strong> {$ sshActivity.processes.length $} process(es) found
</div>
<div ng-if="sshActivity.w && sshActivity.w.length > 0" style="margin-top: 4px;">
<strong>Active Sessions (w command):</strong> {$ sshActivity.w.length $} session(s) detected
</div>
<div ng-if="(!sshActivity.processes || sshActivity.processes.length === 0) && sshActivity.w && sshActivity.w.length > 0" style="margin-top: 4px; color: #f59e0b;">
<em>⚠️ User is logged in (detected by 'w' command) but no processes found. This usually means the shell is idle, waiting for input.</em>
</div>
<div ng-if="(!sshActivity.processes || sshActivity.processes.length === 0) && (!sshActivity.w || sshActivity.w.length === 0)" style="margin-top: 4px; color: #ef4444;">
<em>⚠️ No processes or active sessions found. The session may have just ended or the user logged out.</em>
</div>
</div>
</div>
<!-- Processes List -->
<div ng-if="sshActivity.processes && sshActivity.processes.length > 0" class="process-list">
<h4 style="font-size: 14px; font-weight: 600; color: #2f3640; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
<i class="fas fa-microchip"></i> Running Processes ({$ sshActivity.processes.length $})
</h4>
<div ng-repeat="process in sshActivity.processes" class="process-item">
<div class="process-info">
<span class="process-pid">PID: {$ process.pid $}</span>
<span ng-if="process.ppid" style="color: #94a3b8; font-size: 12px;">PPID: {$ process.ppid $}</span>
<div class="process-cmd">
<strong>Command:</strong> {$ process.cmd $}
<span ng-if="process.tty" style="margin-left: 10px; color: #94a3b8;">TTY: {$ process.tty $}</span>
<span ng-if="process.time" style="margin-left: 10px; color: #94a3b8;">Time: {$ process.time $}</span>
</div>
<div ng-if="process.cwd" style="font-size: 11px; color: #94a3b8; margin-top: 4px;">
<i class="fas fa-folder"></i> {$ process.cwd $}
</div>
</div>
<div class="process-actions">
<button class="kill-process-btn"
ng-click="killProcess(process.pid, sshActivityUser)"
ng-disabled="killingProcess === process.pid"
title="Force Kill Process">
<i class="fas fa-skull" ng-if="killingProcess !== process.pid"></i>
<i class="fas fa-spinner fa-spin" ng-if="killingProcess === process.pid"></i>
<span ng-if="killingProcess !== process.pid">Kill</span>
<span ng-if="killingProcess === process.pid">Killing...</span>
</button>
</div>
</div>
</div>
<!-- Session Info (w command output) -->
<div ng-if="sshActivity.w && sshActivity.w.length > 0" style="margin-top: 20px;">
<h4 style="font-size: 14px; font-weight: 600; color: #2f3640; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
<i class="fas fa-terminal"></i> Session Information
</h4>
<div style="background: white; border: 1px solid var(--border-color, #e8e9ff); border-radius: 8px; padding: 15px;">
<div ng-repeat="session in sshActivity.w" style="font-family: 'Courier New', monospace; font-size: 12px; color: #2f3640; margin-bottom: 8px;">
{$ session $}
</div>
</div>
</div>
<!-- Full Activity JSON (Collapsible) -->
<div style="margin-top: 20px;">
<button ng-click="showFullJSON = !showFullJSON"
style="background: var(--bg-hover, #f8f9ff); border: 1px solid var(--border-color, #e8e9ff); color: #5b5fcf; padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; width: 100%;">
<i class="fas" ng-class="showFullJSON ? 'fa-chevron-up' : 'fa-chevron-down'"></i>
<span ng-if="!showFullJSON">Show Full Activity JSON</span>
<span ng-if="showFullJSON">Hide Full Activity JSON</span>
</button>
<div ng-if="showFullJSON" class="activity-content">
<pre class="activity-json">{$ sshActivity | json $}</pre>
</div>
</div>
<!-- Action Buttons -->
<div class="modal-actions">
<div class="modal-actions-left">
<!-- Only show Kill Session button for active sessions -->
<button class="kill-session-btn"
ng-click="killSSHSession(sshActivityUser, sshActivityTTY)"
ng-disabled="killingSession || !isActiveSession || !sshActivity.processes || sshActivity.processes.length === 0"
ng-if="isActiveSession && sshActivity.processes && sshActivity.processes.length > 0"
title="Kill All Processes for This Active Session">
<i class="fas fa-power-off" ng-if="!killingSession"></i>
<i class="fas fa-spinner fa-spin" ng-if="killingSession"></i>
<span ng-if="!killingSession">Kill Session</span>
<span ng-if="killingSession">Killing...</span>
</button>
<div ng-if="!isActiveSession || (isActiveSession && (!sshActivity.processes || sshActivity.processes.length === 0))" style="color: #94a3b8; font-size: 13px; padding: 10px 20px; display: inline-flex; align-items: center; gap: 8px;">
<i class="fas fa-info-circle"></i>
<span ng-if="!isActiveSession">Session is inactive - cannot kill an ended session</span>
<span ng-if="isActiveSession && (!sshActivity.processes || sshActivity.processes.length === 0) && sshActivity.w && sshActivity.w.length > 0">User is logged in but idle (no processes to kill) - shell is waiting for input</span>
<span ng-if="isActiveSession && (!sshActivity.processes || sshActivity.processes.length === 0) && (!sshActivity.w || sshActivity.w.length === 0)">No processes or sessions found - session may have just ended</span>
</div>
</div>
<div class="modal-actions-right">
<!-- Always show Ban IP button if IP is available - show for all sessions -->
<div ng-if="!sshActivityIP" style="color: #94a3b8; font-size: 13px; padding: 10px 20px; display: inline-flex; align-items: center; gap: 8px;">
<i class="fas fa-info-circle"></i>
<span>IP address not available</span>
</div>
<button ng-click="blockIPAddress(sshActivityIP)"
ng-disabled="blockingIP === sshActivityIP || !sshActivityIP"
class="ban-ip-btn"
ng-if="sshActivityIP && sshActivityIP.trim() !== ''"
title="Ban this IP address permanently">
<i class="fas fa-ban" ng-if="blockingIP !== sshActivityIP"></i>
<i class="fas fa-spinner fa-spin" ng-if="blockingIP === sshActivityIP"></i>
<span ng-if="blockingIP !== sshActivityIP">Ban IP Permanently</span>
<span ng-if="blockingIP === sshActivityIP">Banning...</span>
</button>
<span ng-if="blockedIPs && blockedIPs[sshActivityIP] && sshActivityIP && sshActivityIP.trim() !== ''"
style="display: inline-flex; align-items: center; gap: 6px; color: #10b981; font-size: 14px; font-weight: 600; padding: 10px 20px;">
<i class="fas fa-check-circle"></i> Already Blocked
</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -713,25 +713,57 @@ def getRecentSSHLogins(request):
date_match = re.search(r'([A-Za-z]{3} [A-Za-z]{3} +\d+ [\d:]+)', line)
date_str = date_match.group(1) if date_match else ''
session_info = ''
if '-' in line:
# Session ended
session_info = line.split('-')[-1].strip()
elif 'still logged in' in line:
is_active = False
if 'still logged in' in line:
session_info = 'still logged in'
# GeoIP lookup (cache per request)
is_active = True
elif '-' in line:
# Session ended - parse the end time and duration
# Format: "Tue May 27 11:34 - 13:47 (02:13)" or "crash (00:40)"
end_part = line.split('-')[-1].strip()
# Check if it's a crash or normal logout
if 'crash' in end_part.lower():
# Extract crash duration if available
crash_match = re.search(r'crash\s*\(([^)]+)\)', end_part, re.IGNORECASE)
if crash_match:
session_info = f"crash ({crash_match.group(1)})"
else:
session_info = 'crash'
else:
# Normal session end - try to extract duration
duration_match = re.search(r'\(([^)]+)\)', end_part)
if duration_match:
session_info = f"ended ({duration_match.group(1)})"
else:
# Just show the end time
time_match = re.search(r'([A-Za-z]{3}\s+[A-Za-z]{3}\s+\d+\s+[\d:]+)', end_part)
if time_match:
session_info = f"ended at {time_match.group(1)}"
else:
session_info = 'ended'
is_active = False
# GeoIP lookup (cache per request) - support both IPv4 and IPv6
country = flag = ''
if re.match(r'\d+\.\d+\.\d+\.\d+', ip) and ip != '127.0.0.1':
# Check if IP is IPv4
is_ipv4 = re.match(r'^\d+\.\d+\.\d+\.\d+$', ip)
# Check if IP is IPv6 (simplified check)
is_ipv6 = ':' in ip and not is_ipv4
if is_ipv4 and ip != '127.0.0.1':
if ip in ip_cache:
country, flag = ip_cache[ip]
else:
try:
geo = requests.get(f'http://ip-api.com/json/{ip}', timeout=2).json()
geo = requests.get(f'http://ip-api.com/json/{ip}', timeout=1).json()
country = geo.get('countryCode', '')
flag = f"https://flagcdn.com/24x18/{country.lower()}.png" if country else ''
ip_cache[ip] = (country, flag)
except Exception:
country, flag = '', ''
elif ip == '127.0.0.1':
elif is_ipv6 and ip != '::1':
# IPv6 - set flag to indicate IPv6 (GeoIP API may not support IPv6 well)
country, flag = 'IPv6', ''
elif ip == '127.0.0.1' or ip == '::1':
country, flag = 'Local', ''
logins.append({
'user': user,
@ -740,6 +772,7 @@ def getRecentSSHLogins(request):
'flag': flag,
'date': date_str,
'session': session_info,
'is_active': is_active,
'raw': line
})
return HttpResponse(json.dumps({'logins': logins}), content_type='application/json')
@ -1254,115 +1287,63 @@ def getSSHUserActivity(request):
login_ip = data.get('ip', '')
if not user:
return HttpResponse(json.dumps({'error': 'Missing user'}), content_type='application/json', status=400)
# Get processes for the user
ps_cmd = f"ps -u {user} -o pid,ppid,tty,time,cmd --no-headers"
try:
ps_output = ProcessUtilities.outputExecutioner(ps_cmd)
except Exception as e:
ps_output = ''
processes = []
pid_map = {}
if ps_output:
for line in ps_output.strip().split('\n'):
parts = line.split(None, 4)
if len(parts) == 5:
pid, ppid, tty_val, time_val, cmd = parts
if tty and tty not in tty_val:
continue
# Try to get CWD
cwd = ''
try:
cwd_path = f"/proc/{pid}/cwd"
if os.path.islink(cwd_path):
cwd = os.readlink(cwd_path)
except Exception:
cwd = ''
proc = {
'pid': pid,
'ppid': ppid,
'tty': tty_val,
'time': time_val,
'cmd': cmd,
'cwd': cwd
}
processes.append(proc)
pid_map[pid] = proc
# Build process tree
tree = []
def build_tree(parent_pid, level=0):
for proc in processes:
if proc['ppid'] == parent_pid:
proc_copy = proc.copy()
proc_copy['level'] = level
tree.append(proc_copy)
build_tree(proc['pid'], level+1)
build_tree('1', 0) # Start from init
# Find main shell process for history
shell_history = []
try:
try:
website = Websites.objects.get(externalApp=user)
shell_home = f'/home/{website.domain}'
except Exception:
shell_home = pwd.getpwnam(user).pw_dir
except Exception:
shell_home = f"/home/{user}"
history_file = ''
for shell in ['.bash_history', '.zsh_history']:
path = os.path.join(shell_home, shell)
if os.path.exists(path):
history_file = path
break
if history_file:
try:
with open(history_file, 'r') as f:
lines = f.readlines()
shell_history = [l.strip() for l in lines[-10:]]
except Exception:
shell_history = []
# Disk usage
disk_usage = ''
if os.path.exists(shell_home):
try:
du_out = ProcessUtilities.outputExecutioner(f'du -sh {shell_home}')
disk_usage = du_out.strip().split('\t')[0] if du_out else ''
except Exception:
disk_usage = ''
else:
disk_usage = 'Home directory does not exist'
# GeoIP details
geoip = {}
if login_ip and login_ip not in ['127.0.0.1', 'localhost']:
try:
geo = requests.get(f'http://ip-api.com/json/{login_ip}?fields=status,message,country,regionName,city,isp,org,as,query', timeout=2).json()
if geo.get('status') == 'success':
geoip = {
'country': geo.get('country'),
'region': geo.get('regionName'),
'city': geo.get('city'),
'isp': geo.get('isp'),
'org': geo.get('org'),
'as': geo.get('as'),
'ip': geo.get('query')
}
except Exception:
geoip = {}
# Optionally, get 'w' output for more info
w_cmd = f"w -h {user}"
try:
w_output = ProcessUtilities.outputExecutioner(w_cmd)
except Exception as e:
w_output = ''
# Get 'w' output first (fastest, most important for session status)
w_lines = []
if w_output:
for line in w_output.strip().split('\n'):
w_lines.append(line)
try:
w_cmd = f"w -h {user} 2>/dev/null | head -10"
w_output = ProcessUtilities.outputExecutioner(w_cmd)
if w_output:
for line in w_output.strip().split('\n'):
if line.strip():
w_lines.append(line)
except Exception:
w_lines = []
# Get processes for the user (limit to 50 for speed)
# If TTY is specified, filter by TTY; otherwise get all user processes
processes = []
try:
if tty:
# Filter by specific TTY
ps_cmd = f"ps -u {user} -o pid,ppid,tty,time,cmd --no-headers 2>/dev/null | grep '{tty}' | head -50"
else:
# Get all processes for user
ps_cmd = f"ps -u {user} -o pid,ppid,tty,time,cmd --no-headers 2>/dev/null | head -50"
ps_output = ProcessUtilities.outputExecutioner(ps_cmd)
if ps_output:
for line in ps_output.strip().split('\n'):
if not line.strip():
continue
parts = line.split(None, 4)
if len(parts) >= 5:
pid, ppid, tty_val, time_val, cmd = parts[0], parts[1], parts[2], parts[3], parts[4]
# Additional TTY check if tty was specified
if tty and tty not in tty_val:
continue
# Skip CWD lookup for speed
proc = {
'pid': pid,
'ppid': ppid,
'tty': tty_val,
'time': time_val,
'cmd': cmd[:200] if len(cmd) > 200 else cmd, # Limit command length
'cwd': '' # Skip for speed
}
processes.append(proc)
except Exception:
processes = []
# Skip slow operations for fast response:
# - Process tree (can be computed client-side if needed)
# - Shell history (not critical for initial load)
# - Disk usage (not critical for initial load)
# - GeoIP (can be fetched async later if needed)
return HttpResponse(json.dumps({
'processes': processes,
'process_tree': tree,
'shell_history': shell_history,
'disk_usage': disk_usage,
'geoip': geoip,
'process_tree': [], # Empty for speed
'shell_history': [], # Empty for speed
'disk_usage': '', # Empty for speed
'geoip': {}, # Empty for speed
'w': w_lines
}), content_type='application/json')
except Exception as e: