mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-11-07 22:06:05 +01:00
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
This commit is contained in:
@@ -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)
|
||||
@@ -2420,3 +2448,187 @@ class ContainerManager(multi.Thread):
|
||||
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)
|
||||
@@ -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="{}")
|
||||
|
||||
@@ -128,6 +128,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) {
|
||||
var yml = 'version: \'3.8\'\n\n';
|
||||
@@ -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({
|
||||
|
||||
621
dockerManager/templates/dockerManager/manageNetworks.html
Normal file
621
dockerManager/templates/dockerManager/manageNetworks.html
Normal file
@@ -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 %}
|
||||
|
||||
<style>
|
||||
.modern-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
padding: 3rem 0;
|
||||
background: linear-gradient(135deg, var(--bg-hover, #f8f9ff) 0%, var(--bg-gradient, #f0f1ff) 100%);
|
||||
border-radius: 20px;
|
||||
animation: fadeInDown 0.5s ease-out;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle at 70% 30%, var(--accent-shadow-light, rgba(91, 95, 207, 0.15)) 0%, transparent 50%);
|
||||
animation: rotate 30s linear infinite;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #1e293b);
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.network-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: var(--bg-secondary, white);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 12px var(--shadow-medium, rgba(0,0,0,0.1));
|
||||
}
|
||||
|
||||
.main-card {
|
||||
background: var(--bg-secondary, white);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 1px 3px var(--shadow-light, rgba(0,0,0,0.05)), 0 10px 40px var(--shadow-color, rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
overflow: hidden;
|
||||
margin-bottom: 2rem;
|
||||
animation: fadeInUp 0.5s ease-out;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(135deg, var(--bg-hover, #f8f9ff) 0%, var(--bg-gradient, #f0f1ff) 100%);
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid var(--border-color, #e8e9ff);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #1e293b);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent-color, #5b5fcf);
|
||||
color: var(--bg-secondary, white);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--accent-hover, #4547a9);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(91, 95, 207, 0.3);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success-bg, #d1fae5);
|
||||
color: var(--success-text, #065f46);
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #a7f3d0;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #fee2e2;
|
||||
color: var(--danger-color, #ef4444);
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #fecaca;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.network-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.network-card {
|
||||
background: var(--bg-secondary, white);
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.network-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: linear-gradient(135deg, var(--accent-focus, rgba(91, 95, 207, 0.1)) 0%, transparent 100%);
|
||||
border-radius: 0 0 0 100%;
|
||||
}
|
||||
|
||||
.network-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px var(--accent-shadow-light, rgba(91, 95, 207, 0.15));
|
||||
border-color: var(--accent-color, #5b5fcf);
|
||||
}
|
||||
|
||||
.network-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.network-name {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #1e293b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.network-driver {
|
||||
background: var(--accent-bg, #e0e7ff);
|
||||
color: var(--accent-color, #5b5fcf);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.network-info {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: var(--text-secondary, #64748b);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: var(--text-primary, #1e293b);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.network-actions {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #e8e9ff;
|
||||
border-top-color: var(--accent-color, #5b5fcf);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
display: inline-block;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
color: var(--text-secondary, #64748b);
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.network-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modern-container" ng-controller="manageNetworks">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">
|
||||
<div class="network-icon">
|
||||
<i class="fas fa-network-wired" style="color: var(--accent-color, #5b5fcf); font-size: 1.75rem;"></i>
|
||||
</div>
|
||||
{% trans "Docker Network Management" %}
|
||||
</h1>
|
||||
<p style="color: var(--text-secondary, #64748b); font-size: 1.125rem; margin: 0;">
|
||||
{% trans "Manage Docker networks for container connectivity" %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Networks Overview -->
|
||||
<div class="main-card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">
|
||||
<i class="fas fa-list"></i>
|
||||
{% trans "Available Networks" %}
|
||||
<img id="networkLoading" src="/static/images/loading.gif" style="display: none;" class="loading-spinner">
|
||||
</h2>
|
||||
<div>
|
||||
<button class="btn btn-primary" ng-click="refreshNetworks()" ng-disabled="loading">
|
||||
<i class="fas fa-sync"></i>
|
||||
{% trans "Refresh" %}
|
||||
</button>
|
||||
<button class="btn btn-success" ng-click="showCreateNetworkModal()">
|
||||
<i class="fas fa-plus"></i>
|
||||
{% trans "Create Network" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div ng-show="loading" class="text-center" style="padding: 2rem;">
|
||||
<div class="loading-spinner" style="width: 40px; height: 40px; margin: 0 auto;"></div>
|
||||
<p style="margin-top: 1rem; color: var(--text-secondary, #64748b);">{% trans "Loading networks..." %}</p>
|
||||
</div>
|
||||
|
||||
<div ng-show="!loading && networks.length === 0" class="empty-state">
|
||||
<i class="fas fa-network-wired"></i>
|
||||
<h3>{% trans "No Networks Found" %}</h3>
|
||||
<p>{% trans "Create your first Docker network to get started." %}</p>
|
||||
<button class="btn btn-primary" ng-click="showCreateNetworkModal()">
|
||||
<i class="fas fa-plus"></i>
|
||||
{% trans "Create Network" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ng-show="!loading && networks.length > 0" class="network-grid">
|
||||
<div ng-repeat="network in networks" class="network-card">
|
||||
<div class="network-header">
|
||||
<h3 class="network-name">{$ network.name $}</h3>
|
||||
<span class="network-driver">{$ network.driver $}</span>
|
||||
</div>
|
||||
|
||||
<div class="network-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "ID" %}</span>
|
||||
<span class="info-value">{$ network.id | limitTo: 12 $}...</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "Scope" %}</span>
|
||||
<span class="info-value">{$ network.scope $}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "Containers" %}</span>
|
||||
<span class="info-value">{$ network.containers $}</span>
|
||||
</div>
|
||||
<div class="info-item" ng-if="network.ipam.config.length > 0">
|
||||
<span class="info-label">{% trans "Subnet" %}</span>
|
||||
<span class="info-value">{$ network.ipam.config[0].subnet $}</span>
|
||||
</div>
|
||||
<div class="info-item" ng-if="network.ipam.config.length > 0 && network.ipam.config[0].gateway">
|
||||
<span class="info-label">{% trans "Gateway" %}</span>
|
||||
<span class="info-value">{$ network.ipam.config[0].gateway $}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="network-actions">
|
||||
<button class="btn btn-danger btn-sm" ng-click="removeNetwork(network)"
|
||||
ng-disabled="network.containers > 0"
|
||||
title="{% trans 'Cannot remove network with running containers' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
{% trans "Remove" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Network Modal -->
|
||||
<div id="createNetworkModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<i class="fas fa-plus" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "Create Docker Network" %}
|
||||
</h4>
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
style="font-size: 1.5rem; background: transparent; border: none;">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="createNetworkForm" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{% trans "Network Name" %}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="newNetwork.name"
|
||||
placeholder="{% trans 'e.g., my-custom-network' %}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{% trans "Driver" %}</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" ng-model="newNetwork.driver">
|
||||
<option value="bridge">{% trans "Bridge" %}</option>
|
||||
<option value="overlay">{% trans "Overlay" %}</option>
|
||||
<option value="macvlan">{% trans "MacVLAN" %}</option>
|
||||
<option value="ipvlan">{% trans "IPvLAN" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{% trans "Subnet" %}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="newNetwork.subnet"
|
||||
placeholder="{% trans 'e.g., 172.20.0.0/16' %}">
|
||||
<small class="help-block">{% trans "Optional: Specify a custom subnet for the network" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{% trans "Gateway" %}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="newNetwork.gateway"
|
||||
placeholder="{% trans 'e.g., 172.20.0.1' %}">
|
||||
<small class="help-block">{% trans "Optional: Specify a custom gateway IP" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{% trans "IP Range" %}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="newNetwork.ip_range"
|
||||
placeholder="{% trans 'e.g., 172.20.0.0/24' %}">
|
||||
<small class="help-block">{% trans "Optional: Specify an IP range for container IPs" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<img id="createNetworkLoading" src="/static/images/loading.gif" style="display: none;" class="loading-spinner">
|
||||
<button type="button" class="btn btn-primary" ng-disabled="creatingNetwork" ng-click="createNetwork()">
|
||||
<i class="fas fa-plus"></i> {% trans "Create Network" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<i class="fas fa-times"></i> {% trans "Cancel" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer_scripts %}
|
||||
<script src="{% static 'dockerManager/dockerManager.js' %}"></script>
|
||||
<script>
|
||||
// Network management controller
|
||||
app.controller('manageNetworks', function ($scope, $http) {
|
||||
$scope.networks = [];
|
||||
$scope.loading = true;
|
||||
$scope.creatingNetwork = false;
|
||||
|
||||
$scope.newNetwork = {
|
||||
name: '',
|
||||
driver: 'bridge',
|
||||
subnet: '',
|
||||
gateway: '',
|
||||
ip_range: ''
|
||||
};
|
||||
|
||||
// Load networks
|
||||
$scope.loadNetworks = function() {
|
||||
$scope.loading = true;
|
||||
var url = "/docker/getDockerNetworks";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, {}, config).then(function(response) {
|
||||
$scope.loading = false;
|
||||
if (response.data.status === 1) {
|
||||
$scope.networks = response.data.networks;
|
||||
} else {
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: 'Failed to load networks: ' + response.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.loading = false;
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: 'Error loading networks: ' + error.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Refresh networks
|
||||
$scope.refreshNetworks = function() {
|
||||
$scope.loadNetworks();
|
||||
};
|
||||
|
||||
// Show create network modal
|
||||
$scope.showCreateNetworkModal = function() {
|
||||
$scope.newNetwork = {
|
||||
name: '',
|
||||
driver: 'bridge',
|
||||
subnet: '',
|
||||
gateway: '',
|
||||
ip_range: ''
|
||||
};
|
||||
$('#createNetworkModal').modal('show');
|
||||
};
|
||||
|
||||
// Create network
|
||||
$scope.createNetwork = function() {
|
||||
if (!$scope.newNetwork.name.trim()) {
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: 'Network name is required',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
$('#createNetworkLoading').show();
|
||||
$scope.creatingNetwork = true;
|
||||
|
||||
var url = "/docker/createDockerNetwork";
|
||||
var data = {
|
||||
name: $scope.newNetwork.name,
|
||||
driver: $scope.newNetwork.driver,
|
||||
subnet: $scope.newNetwork.subnet,
|
||||
gateway: $scope.newNetwork.gateway,
|
||||
ip_range: $scope.newNetwork.ip_range
|
||||
};
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
$('#createNetworkLoading').hide();
|
||||
$scope.creatingNetwork = false;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
$('#createNetworkModal').modal('hide');
|
||||
$scope.loadNetworks();
|
||||
new PNotify({
|
||||
title: 'Success',
|
||||
text: 'Network created successfully',
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: 'Failed to create network: ' + response.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}, function(error) {
|
||||
$('#createNetworkLoading').hide();
|
||||
$scope.creatingNetwork = false;
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: 'Error creating network: ' + error.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Remove network
|
||||
$scope.removeNetwork = function(network) {
|
||||
if (network.containers > 0) {
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: 'Cannot remove network with running containers',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('Are you sure you want to remove network "' + network.name + '"?')) {
|
||||
// Implementation for removing network would go here
|
||||
new PNotify({
|
||||
title: 'Info',
|
||||
text: 'Network removal not implemented in this demo',
|
||||
type: 'info'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize
|
||||
$scope.loadNetworks();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -755,12 +755,76 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Network Configuration Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon">
|
||||
<i class="fas fa-network-wired"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="section-title">{% trans "Network Configuration" %}</h2>
|
||||
<p class="section-subtitle">{% trans "Configure network settings and extra options for the container" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label">
|
||||
{% trans "Network" %}
|
||||
<div class="tooltip">
|
||||
<i class="fas fa-question-circle tooltip-icon"></i>
|
||||
<div class="tooltip-content">
|
||||
{% trans "Select the Docker network for the container" %}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<select class="form-control" ng-model="selectedNetwork" ng-init="selectedNetwork='bridge'">
|
||||
<option value="bridge">{% trans "Default Bridge" %}</option>
|
||||
<option value="host">{% trans "Host Network" %}</option>
|
||||
<option value="none">{% trans "No Network" %}</option>
|
||||
<option ng-repeat="network in availableNetworks" value="{$ network.name $}">
|
||||
{$ network.name $} ({$ network.driver $})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label">
|
||||
{% trans "Extra Hosts" %}
|
||||
<div class="tooltip">
|
||||
<i class="fas fa-question-circle tooltip-icon"></i>
|
||||
<div class="tooltip-content">
|
||||
{% trans "Add custom host entries (e.g., host.docker.internal:host-gateway)" %}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<input type="text" class="form-control" ng-model="extraHosts"
|
||||
placeholder="{% trans 'host.docker.internal:host-gateway, example.com:1.2.3.4' %}">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label">
|
||||
{% trans "Network Mode" %}
|
||||
<div class="tooltip">
|
||||
<i class="fas fa-question-circle tooltip-icon"></i>
|
||||
<div class="tooltip-content">
|
||||
{% trans "Override network mode (bridge, host, none, or custom network)" %}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<select class="form-control" ng-model="networkMode" ng-init="networkMode='bridge'">
|
||||
<option value="bridge">{% trans "Bridge" %}</option>
|
||||
<option value="host">{% trans "Host" %}</option>
|
||||
<option value="none">{% trans "None" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Port Configuration Section -->
|
||||
{% if portConfig %}
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon">
|
||||
<i class="fas fa-network-wired"></i>
|
||||
<i class="fas fa-plug"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="section-title">{% trans "Port Mapping" %}</h2>
|
||||
|
||||
@@ -761,6 +761,11 @@
|
||||
<i class="fas fa-code action-icon" style="color: #10b981;"></i>
|
||||
<div class="action-text">{% trans "Run Command" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="action-btn" ng-click="showPortEditModal()" ng-disabled="status!='running'">
|
||||
<i class="fas fa-edit action-icon" style="color: #8b5cf6;"></i>
|
||||
<div class="action-text">{% trans "Edit Ports" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1239,6 +1244,78 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Port Editing Modal -->
|
||||
<div id="portEditModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<i class="fas fa-edit" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "Edit Port Mappings" %}
|
||||
</h4>
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
style="font-size: 1.5rem; background: transparent; border: none;">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>{% trans "Important:" %}</strong> {% trans "Editing port mappings will temporarily stop and recreate the container. Any unsaved data in the container may be lost." %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">
|
||||
<i class="fas fa-plug" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "Port Mappings" %}
|
||||
</label>
|
||||
<div class="port-mapping-container">
|
||||
<div ng-repeat="(containerPort, hostPort) in currentPorts" class="port-mapping-row" style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 8px;">
|
||||
<div style="flex: 1;">
|
||||
<label style="font-weight: 600; color: #495057; margin-bottom: 0.25rem;">{% trans "Container Port" %}</label>
|
||||
<input type="text" class="form-control" ng-model="containerPort" disabled style="font-family: monospace;">
|
||||
</div>
|
||||
<div style="color: #007bff; font-size: 1.25rem;">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<label style="font-weight: 600; color: #495057; margin-bottom: 0.25rem;">{% trans "Host Port" %}</label>
|
||||
<input type="number" class="form-control" ng-model="currentPorts[containerPort]"
|
||||
placeholder="{% trans 'e.g., 8080' %}" min="1024" max="65535">
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; height: 38px;">
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-click="removePortMapping(containerPort)"
|
||||
ng-show="Object.keys(currentPorts).length > 1" title="{% trans 'Remove port mapping' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="Object.keys(currentPorts).length === 0" class="text-center text-muted" style="padding: 2rem;">
|
||||
<i class="fas fa-info-circle fa-2x" style="margin-bottom: 1rem;"></i>
|
||||
<p>{% trans "No port mappings configured" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-primary" ng-click="addNewPortMapping()">
|
||||
<i class="fas fa-plus"></i>
|
||||
{% trans "Add Port Mapping" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<img id="portEditLoading" src="/static/images/loading.gif" style="display: none;" class="loading-spinner">
|
||||
<button type="button" class="btn btn-primary" ng-disabled="updatingPorts" ng-click="updatePortMappings()">
|
||||
<i class="fas fa-save"></i> {% trans "Update Port Mappings" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<i class="fas fa-times"></i> {% trans "Cancel" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -27,6 +27,11 @@ urlpatterns = [
|
||||
re_path(r'^getImageHistory$', views.getImageHistory, name='getImageHistory'),
|
||||
re_path(r'^removeImage$', views.removeImage, name='removeImage'),
|
||||
re_path(r'^pullImage$', views.pullImage, name='pullImage'),
|
||||
# Network management endpoints
|
||||
re_path(r'^getDockerNetworks$', views.getDockerNetworks, name='getDockerNetworks'),
|
||||
re_path(r'^createDockerNetwork$', views.createDockerNetwork, name='createDockerNetwork'),
|
||||
re_path(r'^updateContainerPorts$', views.updateContainerPorts, name='updateContainerPorts'),
|
||||
re_path(r'^manageNetworks$', views.manageNetworks, name='manageNetworks'),
|
||||
re_path(r'^updateContainer$', views.updateContainer, name='updateContainer'),
|
||||
re_path(r'^listContainers$', views.listContainers, name='listContainers'),
|
||||
re_path(r'^deleteContainerWithData$', views.deleteContainerWithData, name='deleteContainerWithData'),
|
||||
|
||||
@@ -766,3 +766,86 @@ def listContainers(request):
|
||||
return coreResult
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
@preDockerRun
|
||||
def getDockerNetworks(request):
|
||||
"""
|
||||
Get list of all Docker networks
|
||||
"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
cm = ContainerManager()
|
||||
coreResult = cm.getDockerNetworks(userID)
|
||||
|
||||
return coreResult
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
@preDockerRun
|
||||
def createDockerNetwork(request):
|
||||
"""
|
||||
Create a new Docker network
|
||||
"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
cm = ContainerManager()
|
||||
coreResult = cm.createDockerNetwork(userID, json.loads(request.body))
|
||||
|
||||
return coreResult
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
@preDockerRun
|
||||
def updateContainerPorts(request):
|
||||
"""
|
||||
Update port mappings for an existing container
|
||||
"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
cm = ContainerManager()
|
||||
coreResult = cm.updateContainerPorts(userID, json.loads(request.body))
|
||||
|
||||
return coreResult
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
@preDockerRun
|
||||
def manageNetworks(request):
|
||||
"""
|
||||
Display the network management page
|
||||
"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadError()
|
||||
|
||||
template = 'dockerManager/manageNetworks.html'
|
||||
proc = httpProc(request, template, {}, 'admin')
|
||||
return proc.render()
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
@@ -2204,6 +2204,22 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
|
||||
except:
|
||||
pass
|
||||
|
||||
# Add new fields for network configuration and extra options
|
||||
try:
|
||||
cursor.execute('ALTER TABLE dockerManager_containers ADD network VARCHAR(100) DEFAULT "bridge"')
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
cursor.execute('ALTER TABLE dockerManager_containers ADD network_mode VARCHAR(50) DEFAULT "bridge"')
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
cursor.execute('ALTER TABLE dockerManager_containers ADD extra_options LONGTEXT DEFAULT "{}"')
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
connection.close()
|
||||
except:
|
||||
|
||||
Reference in New Issue
Block a user