From 70e76967ec9d6d01bb8bf777c213c4994cdcab7d Mon Sep 17 00:00:00 2001 From: Master3395 Date: Sat, 20 Sep 2025 21:14:12 +0200 Subject: [PATCH] Enhance container log retrieval and display: Implement formatted log retrieval with timestamps and improved error handling in ContainerManager. Update frontend to support log formatting, auto-scrolling, and additional log controls. Modify container command execution to temporarily start stopped containers, ensuring better user feedback on command execution status. --- dockerManager/container.py | 116 ++++++++++++++++-- .../static/dockerManager/dockerManager.js | 109 +++++++++++++++- .../dockerManager/listContainers.html | 52 ++++++-- .../dockerManager/viewContainer.html | 12 +- 4 files changed, 265 insertions(+), 24 deletions(-) diff --git a/dockerManager/container.py b/dockerManager/container.py index 275891abf..8d351b40c 100644 --- a/dockerManager/container.py +++ b/dockerManager/container.py @@ -270,15 +270,72 @@ class ContainerManager(multi.Thread): dockerAPI = docker.APIClient() container = client.containers.get(name) - logs = container.logs().decode("utf-8") + + # Get logs with proper formatting + try: + # Get logs with timestamps and proper formatting + logs = container.logs( + stdout=True, + stderr=True, + timestamps=True, + tail=1000 # Limit to last 1000 lines for performance + ).decode("utf-8", errors='replace') + + # Clean up the logs for better display + if logs: + # Split into lines and clean up + log_lines = logs.split('\n') + cleaned_lines = [] + + for line in log_lines: + # Remove Docker's log prefix if present + if line.startswith('[') and ']' in line: + # Extract timestamp and message + try: + timestamp_end = line.find(']') + if timestamp_end > 0: + timestamp = line[1:timestamp_end] + message = line[timestamp_end + 1:].strip() + + # Format the line nicely + if message: + cleaned_lines.append(f"[{timestamp}] {message}") + else: + cleaned_lines.append(line) + except: + cleaned_lines.append(line) + else: + cleaned_lines.append(line) + + logs = '\n'.join(cleaned_lines) + else: + logs = "No logs available for this container." + + except Exception as log_err: + # Fallback to basic logs if timestamped logs fail + try: + logs = container.logs().decode("utf-8", errors='replace') + if not logs: + logs = "No logs available for this container." + except: + logs = f"Error retrieving logs: {str(log_err)}" - data_ret = {'containerLogStatus': 1, 'containerLog': logs, 'error_message': "None"} - json_data = json.dumps(data_ret) + data_ret = { + 'containerLogStatus': 1, + 'containerLog': logs, + 'error_message': "None", + 'container_status': container.status, + 'log_count': len(logs.split('\n')) if logs else 0 + } + json_data = json.dumps(data_ret, ensure_ascii=False) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'containerLogStatus': 0, 'containerLog': 'Error', 'error_message': str(msg)} + data_ret = { + 'containerLogStatus': 0, + 'containerLog': 'Error retrieving logs', + 'error_message': str(msg) + } json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1555,10 +1612,26 @@ class ContainerManager(multi.Thread): data_ret = {'commandStatus': 0, 'error_message': f'Error accessing container: {str(err)}'} return HttpResponse(json.dumps(data_ret)) - # Check if container is running + # Handle container status - try to start if not running + container_was_stopped = False if container.status != 'running': - data_ret = {'commandStatus': 0, 'error_message': 'Container must be running to execute commands'} - return HttpResponse(json.dumps(data_ret)) + try: + # Try to start the container temporarily + container.start() + container_was_stopped = True + # Wait a moment for container to fully start + import time + time.sleep(2) + + # Verify container is now running + container.reload() + if container.status != 'running': + data_ret = {'commandStatus': 0, 'error_message': 'Failed to start container for command execution'} + return HttpResponse(json.dumps(data_ret)) + + except Exception as start_err: + data_ret = {'commandStatus': 0, 'error_message': f'Container is not running and cannot be started: {str(start_err)}'} + return HttpResponse(json.dumps(data_ret)) # Log the command execution attempt self._log_command_execution(userID, name, command) @@ -1599,6 +1672,14 @@ class ContainerManager(multi.Thread): # Log successful execution self._log_command_result(userID, name, command, exit_code, len(output)) + # Stop container if it was started temporarily + if container_was_stopped: + try: + container.stop() + logging.CyberCPLogFileWriter.writeToFile(f'Stopped container {name} after command execution') + except Exception as stop_err: + logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not stop container {name} after command execution: {str(stop_err)}') + # Format the response data_ret = { 'commandStatus': 1, @@ -1606,17 +1687,34 @@ class ContainerManager(multi.Thread): 'output': output, 'exit_code': exit_code, 'command': command, - 'timestamp': time.time() + 'timestamp': time.time(), + 'container_was_started': container_was_stopped } return HttpResponse(json.dumps(data_ret, ensure_ascii=False)) except docker.errors.APIError as err: + # Stop container if it was started temporarily + if container_was_stopped: + try: + container.stop() + logging.CyberCPLogFileWriter.writeToFile(f'Stopped container {name} after API error') + except Exception as stop_err: + logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not stop container {name} after API error: {str(stop_err)}') + error_msg = f'Docker API error: {str(err)}' self._log_command_error(userID, name, command, error_msg) data_ret = {'commandStatus': 0, 'error_message': error_msg} return HttpResponse(json.dumps(data_ret)) except Exception as err: + # Stop container if it was started temporarily + if container_was_stopped: + try: + container.stop() + logging.CyberCPLogFileWriter.writeToFile(f'Stopped container {name} after execution error') + except Exception as stop_err: + logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not stop container {name} after execution error: {str(stop_err)}') + error_msg = f'Execution error: {str(err)}' self._log_command_error(userID, name, command, error_msg) data_ret = {'commandStatus': 0, 'error_message': error_msg} diff --git a/dockerManager/static/dockerManager/dockerManager.js b/dockerManager/static/dockerManager/dockerManager.js index d2ab7a9e3..b05ff5306 100644 --- a/dockerManager/static/dockerManager/dockerManager.js +++ b/dockerManager/static/dockerManager/dockerManager.js @@ -1095,6 +1095,9 @@ app.controller('listContainers', function ($scope, $http) { $scope.showLog = function (name, refresh = false) { $scope.logs = ""; + $scope.logInfo = null; + $scope.formattedLogs = ""; + if (refresh === false) { $('#logs').modal('show'); $scope.activeLog = name; @@ -1122,18 +1125,37 @@ app.controller('listContainers', function ($scope, $http) { if (response.data.containerLogStatus === 1) { $scope.logs = response.data.containerLog; + $scope.logInfo = { + container_status: response.data.container_status, + log_count: response.data.log_count + }; + + // Format logs for better display + $scope.formatLogs(); + + // Auto-scroll to bottom + setTimeout(function() { + $scope.scrollToBottom(); + }, 100); } else { + $scope.logs = response.data.error_message; + $scope.logInfo = null; + $scope.formattedLogs = ""; + new PNotify({ title: 'Unable to complete request', text: response.data.error_message, type: 'error' }); - } } function cantLoadInitialData(response) { + $scope.logs = "Error loading logs"; + $scope.logInfo = null; + $scope.formattedLogs = ""; + new PNotify({ title: 'Unable to complete request', type: 'error' @@ -1141,6 +1163,76 @@ app.controller('listContainers', function ($scope, $http) { } }; + // Format logs with syntax highlighting and better readability + $scope.formatLogs = function() { + if (!$scope.logs || $scope.logs === 'Loading...') { + $scope.formattedLogs = $scope.logs; + return; + } + + var lines = $scope.logs.split('\n'); + var formattedLines = []; + + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var formattedLine = line; + + // Escape HTML characters + formattedLine = formattedLine.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + + // Add syntax highlighting for common log patterns + if (line.match(/^\[.*?\]/)) { + // Timestamp lines + formattedLine = '' + formattedLine + ''; + } else if (line.match(/ERROR|FATAL|CRITICAL/i)) { + // Error lines + formattedLine = '' + formattedLine + ''; + } else if (line.match(/WARN|WARNING/i)) { + // Warning lines + formattedLine = '' + formattedLine + ''; + } else if (line.match(/INFO/i)) { + // Info lines + formattedLine = '' + formattedLine + ''; + } else if (line.match(/DEBUG/i)) { + // Debug lines + formattedLine = '' + formattedLine + ''; + } else if (line.match(/SUCCESS|OK|COMPLETED/i)) { + // Success lines + formattedLine = '' + formattedLine + ''; + } + + formattedLines.push(formattedLine); + } + + $scope.formattedLogs = formattedLines.join('\n'); + }; + + // Scroll functions + $scope.scrollToTop = function() { + var container = document.getElementById('logContainer'); + if (container) { + container.scrollTop = 0; + } + }; + + $scope.scrollToBottom = function() { + var container = document.getElementById('logContainer'); + if (container) { + container.scrollTop = container.scrollHeight; + } + }; + + // Clear logs function + $scope.clearLogs = function() { + $scope.logs = ""; + $scope.formattedLogs = ""; + $scope.logInfo = null; + }; + url = "/docker/getContainerList"; var data = {page: 1}; @@ -2080,13 +2172,15 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) { $scope.commandOutput = { command: response.data.command, output: response.data.output, - exit_code: response.data.exit_code + exit_code: response.data.exit_code, + container_was_started: response.data.container_was_started }; // Add to command history $scope.commandHistory.unshift({ command: response.data.command, - timestamp: new Date() + timestamp: new Date(), + container_was_started: response.data.container_was_started }); // Keep only last 10 commands @@ -2094,10 +2188,15 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) { $scope.commandHistory = $scope.commandHistory.slice(0, 10); } - // Show success notification + // Show success notification with container status info + var notificationText = 'Command completed with exit code: ' + response.data.exit_code; + if (response.data.container_was_started) { + notificationText += ' (Container was temporarily started and stopped)'; + } + new PNotify({ title: 'Command Executed', - text: 'Command completed with exit code: ' + response.data.exit_code, + text: notificationText, type: response.data.exit_code === 0 ? 'success' : 'warning' }); } diff --git a/dockerManager/templates/dockerManager/listContainers.html b/dockerManager/templates/dockerManager/listContainers.html index 959cd8e9d..686f303bb 100644 --- a/dockerManager/templates/dockerManager/listContainers.html +++ b/dockerManager/templates/dockerManager/listContainers.html @@ -766,25 +766,61 @@ @@ -1135,6 +1140,9 @@
$ {{ commandOutput.command }} (exit code: {{ commandOutput.exit_code }}) + + Container was temporarily started +