From 09c9d67536ef212ffe9d49bdcdb141ed1bed528e Mon Sep 17 00:00:00 2001 From: Master3395 Date: Sun, 21 Sep 2025 21:14:34 +0200 Subject: [PATCH] Implement Docker network management features: Add endpoints for retrieving and creating Docker networks, and update container management to support network configuration and port mapping updates. Enhance UI for network selection and port editing in the container management interface. Update database schema to include network-related fields. https://github.com/usmannasir/cyberpanel/issues/923 --- dockerManager/container.py | 222 ++++++- dockerManager/models.py | 3 + .../static/dockerManager/dockerManager.js | 111 +++- .../dockerManager/manageNetworks.html | 621 ++++++++++++++++++ .../templates/dockerManager/runContainer.html | 66 +- .../dockerManager/viewContainer.html | 85 ++- dockerManager/urls.py | 5 + dockerManager/views.py | 83 +++ plogical/upgrade.py | 16 + 9 files changed, 1200 insertions(+), 12 deletions(-) create mode 100644 dockerManager/templates/dockerManager/manageNetworks.html diff --git a/dockerManager/container.py b/dockerManager/container.py index 9044008b5..ea1d08f6d 100644 --- a/dockerManager/container.py +++ b/dockerManager/container.py @@ -213,8 +213,8 @@ class ContainerManager(multi.Thread): proc = httpProc(request, template, data, 'admin') return proc.render() except Exception as e: - secure_log_error(e, \'container_operation\') - return HttpResponse(\'Operation failed\') + secure_log_error(e, 'container_operation') + return HttpResponse('Operation failed') def listContainers(self, request=None, userID=None, data=None): client = docker.from_env() @@ -333,8 +333,8 @@ class ContainerManager(multi.Thread): return HttpResponse(json_data) except Exception as e: - secure_log_error(e, \'containerLogStatus\') - data_ret = secure_error_response(e, \'Failed to containerLogStatus\') + secure_log_error(e, 'containerLogStatus') + data_ret = secure_error_response(e, 'Failed to containerLogStatus') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -417,6 +417,22 @@ class ContainerManager(multi.Thread): if isinstance(volume, dict) and 'src' in volume and 'dest' in volume: volumes[volume['src']] = {'bind': volume['dest'], 'mode': 'rw'} + # Network configuration + network = data.get('network', 'bridge') # Default to bridge network + network_mode = data.get('network_mode', 'bridge') + + # Extra options support (like --add-host) + extra_hosts = {} + extra_options = data.get('extraOptions', {}) + if extra_options: + for option, value in extra_options.items(): + if option == 'add_host' and value: + # Parse --add-host entries (format: hostname:ip) + for host_entry in value.split(','): + if ':' in host_entry: + hostname, ip = host_entry.strip().split(':', 1) + extra_hosts[hostname.strip()] = ip.strip() + ## Create Configurations admin = Administrator.objects.get(userName=dockerOwner) @@ -426,7 +442,16 @@ class ContainerManager(multi.Thread): 'ports': portConfig, 'publish_all_ports': True, 'environment': envDict, - 'volumes': volumes} + 'volumes': volumes, + 'network_mode': network_mode} + + # Add network configuration + if network != 'bridge' or network_mode == 'bridge': + containerArgs['network'] = network + + # Add extra hosts if specified + if extra_hosts: + containerArgs['extra_hosts'] = extra_hosts containerArgs['mem_limit'] = memory * 1048576; # Converts MB to bytes ( 0 * x = 0 for unlimited memory) @@ -467,6 +492,9 @@ class ContainerManager(multi.Thread): image=image, memory=memory, ports=json.dumps(portConfig), + network=network, + network_mode=network_mode, + extra_options=json.dumps(extra_options), volumes=json.dumps(volumes), env=json.dumps(envDict), cid=container.id) @@ -2419,4 +2447,188 @@ class ContainerManager(multi.Thread): logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [ContainerManager.listContainers]') data_ret = {'status': 0, 'error_message': str(msg)} json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + def getDockerNetworks(self, userID=None): + """ + Get list of all Docker networks + """ + try: + admin = Administrator.objects.get(pk=userID) + if admin.acl.adminStatus != 1: + return ACLManager.loadError() + + client = docker.from_env() + + # Get all networks + networks = client.networks.list() + + network_list = [] + for network in networks: + network_info = { + 'id': network.id, + 'name': network.name, + 'driver': network.attrs.get('Driver', 'unknown'), + 'scope': network.attrs.get('Scope', 'local'), + 'created': network.attrs.get('Created', ''), + 'containers': len(network.attrs.get('Containers', {})), + 'ipam': network.attrs.get('IPAM', {}), + 'labels': network.attrs.get('Labels', {}) + } + network_list.append(network_info) + + data_ret = { + 'status': 1, + 'error_message': 'None', + 'networks': network_list + } + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + except Exception as msg: + logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [ContainerManager.getDockerNetworks]') + data_ret = {'status': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + def createDockerNetwork(self, userID=None, data=None): + """ + Create a new Docker network + """ + try: + admin = Administrator.objects.get(pk=userID) + if admin.acl.adminStatus != 1: + return ACLManager.loadError() + + client = docker.from_env() + + name = data.get('name') + driver = data.get('driver', 'bridge') + subnet = data.get('subnet', '') + gateway = data.get('gateway', '') + ip_range = data.get('ip_range', '') + + if not name: + data_ret = {'status': 0, 'error_message': 'Network name is required'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + # Prepare IPAM configuration + ipam_config = [] + if subnet: + ipam_entry = {'subnet': subnet} + if gateway: + ipam_entry['gateway'] = gateway + if ip_range: + ipam_entry['ip_range'] = ip_range + ipam_config.append(ipam_entry) + + ipam = {'driver': 'default', 'config': ipam_config} if ipam_config else None + + # Create the network + network = client.networks.create( + name=name, + driver=driver, + ipam=ipam + ) + + data_ret = { + 'status': 1, + 'error_message': 'None', + 'network_id': network.id, + 'message': f'Network {name} created successfully' + } + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + except Exception as msg: + logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [ContainerManager.createDockerNetwork]') + data_ret = {'status': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + def updateContainerPorts(self, userID=None, data=None): + """ + Update port mappings for an existing container + """ + try: + admin = Administrator.objects.get(pk=userID) + if admin.acl.adminStatus != 1: + return ACLManager.loadError() + + client = docker.from_env() + + container_name = data.get('name') + new_ports = data.get('ports', {}) + + if not container_name: + data_ret = {'status': 0, 'error_message': 'Container name is required'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + # Get the container + try: + container = client.containers.get(container_name) + except docker.errors.NotFound: + data_ret = {'status': 0, 'error_message': f'Container {container_name} not found'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + # Check if container is running + if container.status != 'running': + data_ret = {'status': 0, 'error_message': 'Container must be running to update port mappings'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + # Get current container configuration + container_config = container.attrs['Config'] + host_config = container.attrs['HostConfig'] + + # Update port bindings + port_bindings = {} + for container_port, host_port in new_ports.items(): + if host_port: # Only add if host port is specified + port_bindings[container_port] = host_port + + # Stop the container + container.stop(timeout=10) + + # Create new container with updated port configuration + new_container = client.containers.create( + image=container_config['Image'], + name=f"{container_name}_temp", + ports=list(new_ports.keys()), + host_config=client.api.create_host_config(port_bindings=port_bindings), + environment=container_config.get('Env', []), + volumes=host_config.get('Binds', []), + detach=True + ) + + # Remove old container and rename new one + container.remove() + new_container.rename(container_name) + + # Start the updated container + new_container.start() + + # Update database record if it exists + try: + db_container = Containers.objects.get(name=container_name) + db_container.ports = json.dumps(new_ports) + db_container.save() + except Containers.DoesNotExist: + pass # Container not in database, that's okay + + data_ret = { + 'status': 1, + 'error_message': 'None', + 'message': f'Port mappings updated for container {container_name}' + } + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + except Exception as msg: + logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [ContainerManager.updateContainerPorts]') + data_ret = {'status': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) return HttpResponse(json_data) \ No newline at end of file diff --git a/dockerManager/models.py b/dockerManager/models.py index 0b96636ca..f4b4afa6d 100644 --- a/dockerManager/models.py +++ b/dockerManager/models.py @@ -16,3 +16,6 @@ class Containers(models.Model): volumes = models.TextField(default="{}") env = models.TextField(default="{}") startOnReboot = models.IntegerField(default=0) + network = models.CharField(max_length=100, default='bridge') + network_mode = models.CharField(max_length=50, default='bridge') + extra_options = models.TextField(default="{}") diff --git a/dockerManager/static/dockerManager/dockerManager.js b/dockerManager/static/dockerManager/dockerManager.js index b05ff5306..769d537df 100644 --- a/dockerManager/static/dockerManager/dockerManager.js +++ b/dockerManager/static/dockerManager/dockerManager.js @@ -127,6 +127,33 @@ app.controller('runContainer', function ($scope, $http) { // Advanced Environment Variable Mode $scope.advancedEnvMode = false; + + // Network configuration + $scope.selectedNetwork = 'bridge'; + $scope.networkMode = 'bridge'; + $scope.extraHosts = ''; + $scope.availableNetworks = []; + + // Load available Docker networks + $scope.loadAvailableNetworks = function() { + var url = "/docker/getDockerNetworks"; + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post(url, {}, config).then(function(response) { + if (response.data.status === 1) { + $scope.availableNetworks = response.data.networks; + } + }, function(error) { + console.error('Failed to load networks:', error); + }); + }; + + // Initialize networks on page load + $scope.loadAvailableNetworks(); // Helper function to generate Docker Compose YAML $scope.generateDockerComposeYml = function(containerInfo) { @@ -622,8 +649,12 @@ app.controller('runContainer', function ($scope, $http) { image: image, envList: finalEnvList, volList: $scope.volList, - advancedEnvMode: $scope.advancedEnvMode - + advancedEnvMode: $scope.advancedEnvMode, + network: $scope.selectedNetwork, + network_mode: $scope.networkMode, + extraOptions: { + add_host: $scope.extraHosts + } }; try { @@ -2137,6 +2168,82 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) { $("#commandModal").modal("show"); }; + // Port editing functionality + $scope.showPortEditModal = function() { + // Initialize current ports from container data + $scope.currentPorts = {}; + if ($scope.ports) { + for (var iport in $scope.ports) { + var eport = $scope.ports[iport]; + if (eport && eport.length > 0) { + $scope.currentPorts[iport] = eport[0].HostPort; + } + } + } + $("#portEditModal").modal("show"); + }; + + $scope.addNewPortMapping = function() { + var containerPort = prompt('Enter container port (e.g., 80/tcp):'); + if (containerPort) { + $scope.currentPorts[containerPort] = ''; + $scope.$apply(); + } + }; + + $scope.removePortMapping = function(containerPort) { + if (confirm('Are you sure you want to remove this port mapping?')) { + delete $scope.currentPorts[containerPort]; + } + }; + + $scope.updatePortMappings = function() { + $("#portEditLoading").show(); + $scope.updatingPorts = true; + + var url = "/docker/updateContainerPorts"; + var data = { + name: $scope.cName, + ports: $scope.currentPorts + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post(url, data, config).then(function(response) { + $("#portEditLoading").hide(); + $scope.updatingPorts = false; + + if (response.data.status === 1) { + $("#portEditModal").modal("hide"); + // Refresh container status and ports + $scope.refreshContainerInfo(); + new PNotify({ + title: 'Success', + text: 'Port mappings updated successfully', + type: 'success' + }); + } else { + new PNotify({ + title: 'Error', + text: 'Failed to update port mappings: ' + response.data.error_message, + type: 'error' + }); + } + }, function(error) { + $("#portEditLoading").hide(); + $scope.updatingPorts = false; + new PNotify({ + title: 'Error', + text: 'Error updating port mappings: ' + error.data.error_message, + type: 'error' + }); + }); + }; + $scope.executeCommand = function() { if (!$scope.commandToExecute.trim()) { new PNotify({ diff --git a/dockerManager/templates/dockerManager/manageNetworks.html b/dockerManager/templates/dockerManager/manageNetworks.html new file mode 100644 index 000000000..db51154f0 --- /dev/null +++ b/dockerManager/templates/dockerManager/manageNetworks.html @@ -0,0 +1,621 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} +{% block title %}{% trans "Docker Network Management" %}{% endblock %} +{% block content %} + +{% load static %} +{% get_current_language as LANGUAGE_CODE %} + + + +
+ + + +
+
+

+ + {% trans "Available Networks" %} + +

+
+ + +
+
+
+
+
+

{% trans "Loading networks..." %}

+
+ +
+ +

{% trans "No Networks Found" %}

+

{% trans "Create your first Docker network to get started." %}

+ +
+ +
+
+
+

{$ network.name $}

+ {$ network.driver $} +
+ +
+
+ {% trans "ID" %} + {$ network.id | limitTo: 12 $}... +
+
+ {% trans "Scope" %} + {$ network.scope $} +
+
+ {% trans "Containers" %} + {$ network.containers $} +
+
+ {% trans "Subnet" %} + {$ network.ipam.config[0].subnet $} +
+
+ {% trans "Gateway" %} + {$ network.ipam.config[0].gateway $} +
+
+ +
+ +
+
+
+
+
+
+ + + + +{% endblock %} + +{% block footer_scripts %} + + +{% endblock %} diff --git a/dockerManager/templates/dockerManager/runContainer.html b/dockerManager/templates/dockerManager/runContainer.html index c56bbf6c9..f716703cc 100644 --- a/dockerManager/templates/dockerManager/runContainer.html +++ b/dockerManager/templates/dockerManager/runContainer.html @@ -755,12 +755,76 @@ + +
+
+
+ +
+
+

{% trans "Network Configuration" %}

+

{% trans "Configure network settings and extra options for the container" %}

+
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ {% if portConfig %}
- +

{% trans "Port Mapping" %}

diff --git a/dockerManager/templates/dockerManager/viewContainer.html b/dockerManager/templates/dockerManager/viewContainer.html index 8a9604ae0..3fdca0479 100644 --- a/dockerManager/templates/dockerManager/viewContainer.html +++ b/dockerManager/templates/dockerManager/viewContainer.html @@ -757,10 +757,15 @@
{% trans "Processes" %}
-
- -
{% trans "Run Command" %}
-
+
+ +
{% trans "Run Command" %}
+
+ +
+ +
{% trans "Edit Ports" %}
+
@@ -1239,6 +1244,78 @@ + + +