diff --git a/dockerManager/container.py b/dockerManager/container.py
index d08166a4d..331e7fd33 100644
--- a/dockerManager/container.py
+++ b/dockerManager/container.py
@@ -302,11 +302,23 @@ class ContainerManager(multi.Thread):
inspectImage = dockerAPI.inspect_image(image + ":" + tag)
portConfig = {}
- # Formatting envList for usage
+ # Formatting envList for usage - handle both simple and advanced modes
envDict = {}
- for key, value in envList.items():
- if (value['name'] != '') or (value['value'] != ''):
- envDict[value['name']] = value['value']
+
+ # Check if advanced mode is being used
+ advanced_mode = data.get('advancedEnvMode', False)
+
+ if advanced_mode:
+ # Advanced mode: envList is already a dictionary of key-value pairs
+ envDict = envList
+ else:
+ # Simple mode: envList is an array of objects with name/value properties
+ for key, value in envList.items():
+ if isinstance(value, dict) and (value.get('name', '') != '' or value.get('value', '') != ''):
+ envDict[value['name']] = value['value']
+ elif isinstance(value, str) and value != '':
+ # Handle case where value might be a string (fallback)
+ envDict[key] = value
if 'ExposedPorts' in inspectImage['Config']:
for item in inspectImage['Config']['ExposedPorts']:
@@ -975,11 +987,23 @@ class ContainerManager(multi.Thread):
con.startOnReboot = startOnReboot
if 'envConfirmation' in data and data['envConfirmation']:
- # Formatting envList for usage
+ # Formatting envList for usage - handle both simple and advanced modes
envDict = {}
- for key, value in envList.items():
- if (value['name'] != '') or (value['value'] != ''):
- envDict[value['name']] = value['value']
+
+ # Check if advanced mode is being used
+ advanced_mode = data.get('advancedEnvMode', False)
+
+ if advanced_mode:
+ # Advanced mode: envList is already a dictionary of key-value pairs
+ envDict = envList
+ else:
+ # Simple mode: envList is an array of objects with name/value properties
+ for key, value in envList.items():
+ if isinstance(value, dict) and (value.get('name', '') != '' or value.get('value', '') != ''):
+ envDict[value['name']] = value['value']
+ elif isinstance(value, str) and value != '':
+ # Handle case where value might be a string (fallback)
+ envDict[key] = value
volumes = {}
for index, volume in volList.items():
diff --git a/dockerManager/static/dockerManager/dockerManager.js b/dockerManager/static/dockerManager/dockerManager.js
index 3f7131450..5a89b644d 100644
--- a/dockerManager/static/dockerManager/dockerManager.js
+++ b/dockerManager/static/dockerManager/dockerManager.js
@@ -124,6 +124,12 @@ app.controller('runContainer', function ($scope, $http) {
$scope.iport = {};
$scope.portType = {};
$scope.envList = {};
+
+ // Advanced Environment Variable Mode
+ $scope.advancedEnvMode = false;
+ $scope.advancedEnvText = '';
+ $scope.advancedEnvCount = 0;
+ $scope.parsedEnvVars = {};
$scope.addVolField = function () {
$scope.volList[$scope.volListNumber] = {'dest': '', 'src': ''};
$scope.volListNumber = $scope.volListNumber + 1;
@@ -139,6 +145,358 @@ app.controller('runContainer', function ($scope, $http) {
$scope.envList[countEnv + 1] = {'name': '', 'value': ''};
};
+ // Advanced Environment Variable Functions
+ $scope.toggleEnvMode = function() {
+ if ($scope.advancedEnvMode) {
+ // Switching to advanced mode - convert existing envList to text format
+ $scope.convertToAdvancedFormat();
+ } else {
+ // Switching to simple mode - convert advanced text to envList
+ $scope.convertToSimpleFormat();
+ }
+ };
+
+ $scope.convertToAdvancedFormat = function() {
+ var envLines = [];
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ envLines.push($scope.envList[key].name + '=' + $scope.envList[key].value);
+ }
+ }
+ $scope.advancedEnvText = envLines.join('\n');
+ $scope.parseAdvancedEnv();
+ };
+
+ $scope.convertToSimpleFormat = function() {
+ $scope.parseAdvancedEnv();
+ var newEnvList = {};
+ var index = 0;
+ for (var key in $scope.parsedEnvVars) {
+ newEnvList[index] = {'name': key, 'value': $scope.parsedEnvVars[key]};
+ index++;
+ }
+ $scope.envList = newEnvList;
+ };
+
+ $scope.parseAdvancedEnv = function() {
+ $scope.parsedEnvVars = {};
+ $scope.advancedEnvCount = 0;
+
+ if (!$scope.advancedEnvText) {
+ return;
+ }
+
+ var lines = $scope.advancedEnvText.split('\n');
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i].trim();
+
+ // Skip empty lines and comments
+ if (!line || line.startsWith('#')) {
+ continue;
+ }
+
+ // Parse KEY=VALUE format
+ var equalIndex = line.indexOf('=');
+ if (equalIndex > 0) {
+ var key = line.substring(0, equalIndex).trim();
+ var value = line.substring(equalIndex + 1).trim();
+
+ // Remove quotes if present
+ if ((value.startsWith('"') && value.endsWith('"')) ||
+ (value.startsWith("'") && value.endsWith("'"))) {
+ value = value.slice(1, -1);
+ }
+
+ if (key && key.match(/^[A-Za-z_][A-Za-z0-9_]*$/)) {
+ $scope.parsedEnvVars[key] = value;
+ $scope.advancedEnvCount++;
+ }
+ }
+ }
+ };
+
+ $scope.loadEnvTemplate = function() {
+ var templates = {
+ 'web-app': 'NODE_ENV=production\nPORT=3000\nDATABASE_URL=postgresql://user:pass@localhost/db\nREDIS_URL=redis://localhost:6379\nJWT_SECRET=your-jwt-secret\nAPI_KEY=your-api-key',
+ 'database': 'POSTGRES_DB=myapp\nPOSTGRES_USER=user\nPOSTGRES_PASSWORD=password\nPOSTGRES_HOST=localhost\nPOSTGRES_PORT=5432',
+ 'api': 'API_HOST=0.0.0.0\nAPI_PORT=8080\nLOG_LEVEL=info\nCORS_ORIGIN=*\nRATE_LIMIT=1000\nAPI_KEY=your-secret-key',
+ 'monitoring': 'PROMETHEUS_PORT=9090\nGRAFANA_PORT=3000\nALERTMANAGER_PORT=9093\nRETENTION_TIME=15d\nSCRAPE_INTERVAL=15s'
+ };
+
+ var templateNames = Object.keys(templates);
+ var templateChoice = prompt('Choose a template:\n' + templateNames.map((name, i) => (i + 1) + '. ' + name).join('\n') + '\n\nEnter number or template name:');
+
+ if (templateChoice) {
+ var templateIndex = parseInt(templateChoice) - 1;
+ var selectedTemplate = null;
+
+ if (templateIndex >= 0 && templateIndex < templateNames.length) {
+ selectedTemplate = templates[templateNames[templateIndex]];
+ } else {
+ // Try to find by name
+ var templateName = templateChoice.toLowerCase().replace(/\s+/g, '-');
+ if (templates[templateName]) {
+ selectedTemplate = templates[templateName];
+ }
+ }
+
+ if (selectedTemplate) {
+ if ($scope.advancedEnvMode) {
+ $scope.advancedEnvText = selectedTemplate;
+ $scope.parseAdvancedEnv();
+ } else {
+ // Convert template to simple format
+ var lines = selectedTemplate.split('\n');
+ $scope.envList = {};
+ var index = 0;
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i].trim();
+ if (line && !line.startsWith('#')) {
+ var equalIndex = line.indexOf('=');
+ if (equalIndex > 0) {
+ $scope.envList[index] = {
+ 'name': line.substring(0, equalIndex).trim(),
+ 'value': line.substring(equalIndex + 1).trim()
+ };
+ index++;
+ }
+ }
+ }
+ }
+
+ new PNotify({
+ title: 'Template Loaded',
+ text: 'Environment variable template has been loaded successfully',
+ type: 'success'
+ });
+ }
+ }
+ };
+
+ // Docker Compose Functions for runContainer
+ $scope.generateDockerCompose = function() {
+ // Get container information from form
+ var containerInfo = {
+ name: $scope.name || 'my-container',
+ image: $scope.image || 'nginx:latest',
+ ports: $scope.eport || {},
+ volumes: $scope.volList || {},
+ environment: {}
+ };
+
+ // Collect environment variables
+ if ($scope.advancedEnvMode && $scope.parsedEnvVars) {
+ containerInfo.environment = $scope.parsedEnvVars;
+ } else {
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ containerInfo.environment[$scope.envList[key].name] = $scope.envList[key].value;
+ }
+ }
+ }
+
+ // Generate docker-compose.yml content
+ var composeContent = generateDockerComposeYml(containerInfo);
+
+ // Create and download file
+ var blob = new Blob([composeContent], { type: 'text/yaml' });
+ var url = window.URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = url;
+ a.download = 'docker-compose.yml';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+
+ new PNotify({
+ title: 'Docker Compose Generated',
+ text: 'docker-compose.yml file has been generated and downloaded',
+ type: 'success'
+ });
+ };
+
+ $scope.generateEnvFile = function() {
+ var envText = '';
+
+ if ($scope.advancedEnvMode && $scope.advancedEnvText) {
+ envText = $scope.advancedEnvText;
+ } else {
+ // Convert simple mode to .env format
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ envText += $scope.envList[key].name + '=' + $scope.envList[key].value + '\n';
+ }
+ }
+ }
+
+ if (!envText.trim()) {
+ new PNotify({
+ title: 'Nothing to Generate',
+ text: 'No environment variables to generate .env file',
+ type: 'warning'
+ });
+ return;
+ }
+
+ // Create and download file
+ var blob = new Blob([envText], { type: 'text/plain' });
+ var url = window.URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = url;
+ a.download = '.env';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+
+ new PNotify({
+ title: '.env File Generated',
+ text: '.env file has been generated and downloaded',
+ type: 'success'
+ });
+ };
+
+ $scope.showComposeHelp = function() {
+ var helpContent = `
+
+
How to use Docker Compose with Environment Variables
+
+
Step 1: Download Files
+
Click "Generate docker-compose.yml" and "Generate .env file" to download both files.
+
+
Step 2: Place Files
+
Place both files in the same directory on your server.
+
+
Step 3: Run Docker Compose
+
Run the following commands in your terminal:
+
docker compose up -d
+
+
Step 4: Update Environment Variables
+
To update environment variables:
+
+ - Edit the .env file
+ - Run:
docker compose up -d
+ - Only the environment variables will be reloaded (no container rebuild needed!)
+
+
+
Benefits:
+
+ - No need to recreate containers
+ - Faster environment variable updates
+ - Version control friendly
+ - Easy to share configurations
+
+
+
+ `;
+
+ // Create modal for help
+ var modal = document.createElement('div');
+ modal.className = 'modal fade';
+ modal.innerHTML = `
+
+
+
+
+ ${helpContent}
+
+
+
+
+ `;
+
+ document.body.appendChild(modal);
+ $(modal).modal('show');
+
+ // Remove modal when closed
+ $(modal).on('hidden.bs.modal', function() {
+ document.body.removeChild(modal);
+ });
+ };
+
+ $scope.loadEnvFromFile = function() {
+ var input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.env,text/plain';
+ input.onchange = function(event) {
+ var file = event.target.files[0];
+ if (file) {
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ $scope.advancedEnvText = e.target.result;
+ $scope.parseAdvancedEnv();
+ $scope.$apply();
+
+ new PNotify({
+ title: 'File Loaded',
+ text: 'Environment variables loaded from file successfully',
+ type: 'success'
+ });
+ };
+ reader.readAsText(file);
+ }
+ };
+ input.click();
+ };
+
+ $scope.copyEnvToClipboard = function() {
+ var textToCopy = '';
+
+ if ($scope.advancedEnvMode) {
+ textToCopy = $scope.advancedEnvText;
+ } else {
+ // Convert simple format to text
+ var envLines = [];
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ envLines.push($scope.envList[key].name + '=' + $scope.envList[key].value);
+ }
+ }
+ textToCopy = envLines.join('\n');
+ }
+
+ if (textToCopy) {
+ navigator.clipboard.writeText(textToCopy).then(function() {
+ new PNotify({
+ title: 'Copied to Clipboard',
+ text: 'Environment variables copied to clipboard',
+ type: 'success'
+ });
+ }).catch(function(err) {
+ // Fallback for older browsers
+ var textArea = document.createElement('textarea');
+ textArea.value = textToCopy;
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand('copy');
+ document.body.removeChild(textArea);
+
+ new PNotify({
+ title: 'Copied to Clipboard',
+ text: 'Environment variables copied to clipboard',
+ type: 'success'
+ });
+ });
+ }
+ };
+
+ $scope.clearAdvancedEnv = function() {
+ $scope.advancedEnvText = '';
+ $scope.parsedEnvVars = {};
+ $scope.advancedEnvCount = 0;
+ };
+
var statusFile;
// Watch for changes to validate ports
@@ -193,14 +551,29 @@ app.controller('runContainer', function ($scope, $http) {
var image = $scope.image;
var numberOfEnv = Object.keys($scope.envList).length;
+ // Prepare environment variables based on mode
+ var finalEnvList = {};
+ if ($scope.advancedEnvMode && $scope.parsedEnvVars) {
+ // Use parsed environment variables from advanced mode
+ finalEnvList = $scope.parsedEnvVars;
+ } else {
+ // Convert simple envList to proper format
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ finalEnvList[$scope.envList[key].name] = $scope.envList[key].value;
+ }
+ }
+ }
+
var data = {
name: name,
tag: tag,
memory: memory,
dockerOwner: dockerOwner,
image: image,
- envList: $scope.envList,
- volList: $scope.volList
+ envList: finalEnvList,
+ volList: $scope.volList,
+ advancedEnvMode: $scope.advancedEnvMode
};
@@ -580,6 +953,12 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) {
$scope.statusInterval = null;
$scope.statsInterval = null;
+ // Advanced Environment Variable Functions for viewContainer
+ $scope.advancedEnvMode = false;
+ $scope.advancedEnvText = '';
+ $scope.advancedEnvCount = 0;
+ $scope.parsedEnvVars = {};
+
// Auto-refresh status every 5 seconds
$scope.startStatusMonitoring = function() {
$scope.statusInterval = $interval(function() {
@@ -665,6 +1044,492 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) {
$scope.envList[countEnv + 1] = {'name': '', 'value': ''};
};
+ // Advanced Environment Variable Functions for viewContainer
+ $scope.toggleEnvMode = function() {
+ if ($scope.advancedEnvMode) {
+ // Switching to advanced mode - convert existing envList to text format
+ $scope.convertToAdvancedFormat();
+ } else {
+ // Switching to simple mode - convert advanced text to envList
+ $scope.convertToSimpleFormat();
+ }
+ };
+
+ $scope.convertToAdvancedFormat = function() {
+ var envLines = [];
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ envLines.push($scope.envList[key].name + '=' + $scope.envList[key].value);
+ }
+ }
+ $scope.advancedEnvText = envLines.join('\n');
+ $scope.parseAdvancedEnv();
+ };
+
+ $scope.convertToSimpleFormat = function() {
+ $scope.parseAdvancedEnv();
+ var newEnvList = {};
+ var index = 0;
+ for (var key in $scope.parsedEnvVars) {
+ newEnvList[index] = {'name': key, 'value': $scope.parsedEnvVars[key]};
+ index++;
+ }
+ $scope.envList = newEnvList;
+ };
+
+ $scope.parseAdvancedEnv = function() {
+ $scope.parsedEnvVars = {};
+ $scope.advancedEnvCount = 0;
+
+ if (!$scope.advancedEnvText) {
+ return;
+ }
+
+ var lines = $scope.advancedEnvText.split('\n');
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i].trim();
+
+ // Skip empty lines and comments
+ if (!line || line.startsWith('#')) {
+ continue;
+ }
+
+ // Parse KEY=VALUE format
+ var equalIndex = line.indexOf('=');
+ if (equalIndex > 0) {
+ var key = line.substring(0, equalIndex).trim();
+ var value = line.substring(equalIndex + 1).trim();
+
+ // Remove quotes if present
+ if ((value.startsWith('"') && value.endsWith('"')) ||
+ (value.startsWith("'") && value.endsWith("'"))) {
+ value = value.slice(1, -1);
+ }
+
+ if (key && key.match(/^[A-Za-z_][A-Za-z0-9_]*$/)) {
+ $scope.parsedEnvVars[key] = value;
+ $scope.advancedEnvCount++;
+ }
+ }
+ }
+ };
+
+ $scope.copyEnvToClipboard = function() {
+ var textToCopy = '';
+
+ if ($scope.advancedEnvMode) {
+ textToCopy = $scope.advancedEnvText;
+ } else {
+ // Convert simple format to text
+ var envLines = [];
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ envLines.push($scope.envList[key].name + '=' + $scope.envList[key].value);
+ }
+ }
+ textToCopy = envLines.join('\n');
+ }
+
+ if (textToCopy) {
+ navigator.clipboard.writeText(textToCopy).then(function() {
+ new PNotify({
+ title: 'Copied to Clipboard',
+ text: 'Environment variables copied to clipboard',
+ type: 'success'
+ });
+ }).catch(function(err) {
+ // Fallback for older browsers
+ var textArea = document.createElement('textarea');
+ textArea.value = textToCopy;
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand('copy');
+ document.body.removeChild(textArea);
+
+ new PNotify({
+ title: 'Copied to Clipboard',
+ text: 'Environment variables copied to clipboard',
+ type: 'success'
+ });
+ });
+ }
+ };
+
+ // Import/Export Functions
+ $scope.importEnvFromContainer = function() {
+ // Show modal to select container to import from
+ $scope.showContainerImportModal = true;
+ $scope.loadContainersForImport();
+ };
+
+ $scope.loadContainersForImport = function() {
+ $scope.importLoading = true;
+ $scope.importContainers = [];
+
+ $http.get('/dockerManager/loadContainersForImport/', {
+ params: {
+ currentContainer: $scope.cName
+ }
+ }).then(function(response) {
+ $scope.importContainers = response.data.containers || [];
+ $scope.importLoading = false;
+ }).catch(function(error) {
+ new PNotify({
+ title: 'Import Failed',
+ text: 'Failed to load containers for import',
+ type: 'error'
+ });
+ $scope.importLoading = false;
+ });
+ };
+
+ $scope.selectContainerForImport = function(container) {
+ $scope.selectedImportContainer = container;
+ $scope.loadEnvFromContainer(container.name);
+ };
+
+ $scope.loadEnvFromContainer = function(containerName) {
+ $scope.importEnvLoading = true;
+
+ $http.get('/dockerManager/getContainerEnv/', {
+ params: {
+ containerName: containerName
+ }
+ }).then(function(response) {
+ if (response.data.success) {
+ var envVars = response.data.envVars || {};
+
+ if ($scope.advancedEnvMode) {
+ // Convert to .env format
+ var envText = '';
+ for (var key in envVars) {
+ envText += key + '=' + envVars[key] + '\n';
+ }
+ $scope.advancedEnvText = envText;
+ $scope.parseAdvancedEnv();
+ } else {
+ // Convert to simple mode
+ $scope.envList = {};
+ var index = 0;
+ for (var key in envVars) {
+ $scope.envList[index] = {'name': key, 'value': envVars[key]};
+ index++;
+ }
+ }
+
+ $scope.showContainerImportModal = false;
+ new PNotify({
+ title: 'Import Successful',
+ text: 'Environment variables imported from ' + containerName,
+ type: 'success'
+ });
+ } else {
+ new PNotify({
+ title: 'Import Failed',
+ text: response.data.message || 'Failed to import environment variables',
+ type: 'error'
+ });
+ }
+ $scope.importEnvLoading = false;
+ }).catch(function(error) {
+ new PNotify({
+ title: 'Import Failed',
+ text: 'Failed to import environment variables',
+ type: 'error'
+ });
+ $scope.importEnvLoading = false;
+ });
+ };
+
+ $scope.exportEnvToFile = function() {
+ var envText = '';
+
+ if ($scope.advancedEnvMode && $scope.advancedEnvText) {
+ envText = $scope.advancedEnvText;
+ } else {
+ // Convert simple mode to .env format
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ envText += $scope.envList[key].name + '=' + $scope.envList[key].value + '\n';
+ }
+ }
+ }
+
+ if (!envText.trim()) {
+ new PNotify({
+ title: 'Nothing to Export',
+ text: 'No environment variables to export',
+ type: 'warning'
+ });
+ return;
+ }
+
+ // Create and download file
+ var blob = new Blob([envText], { type: 'text/plain' });
+ var url = window.URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = url;
+ a.download = $scope.cName + '_environment.env';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+
+ new PNotify({
+ title: 'Export Successful',
+ text: 'Environment variables exported to file',
+ type: 'success'
+ });
+ };
+
+ // Docker Compose Functions
+ $scope.generateDockerCompose = function() {
+ // Get container information
+ var containerInfo = {
+ name: $scope.cName,
+ image: $scope.image || 'nginx:latest',
+ ports: $scope.ports || {},
+ volumes: $scope.volList || {},
+ environment: {}
+ };
+
+ // Collect environment variables
+ if ($scope.advancedEnvMode && $scope.parsedEnvVars) {
+ containerInfo.environment = $scope.parsedEnvVars;
+ } else {
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ containerInfo.environment[$scope.envList[key].name] = $scope.envList[key].value;
+ }
+ }
+ }
+
+ // Generate docker-compose.yml content
+ var composeContent = generateDockerComposeYml(containerInfo);
+
+ // Create and download file
+ var blob = new Blob([composeContent], { type: 'text/yaml' });
+ var url = window.URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = url;
+ a.download = 'docker-compose.yml';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+
+ new PNotify({
+ title: 'Docker Compose Generated',
+ text: 'docker-compose.yml file has been generated and downloaded',
+ type: 'success'
+ });
+ };
+
+ $scope.generateEnvFile = function() {
+ var envText = '';
+
+ if ($scope.advancedEnvMode && $scope.advancedEnvText) {
+ envText = $scope.advancedEnvText;
+ } else {
+ // Convert simple mode to .env format
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ envText += $scope.envList[key].name + '=' + $scope.envList[key].value + '\n';
+ }
+ }
+ }
+
+ if (!envText.trim()) {
+ new PNotify({
+ title: 'Nothing to Generate',
+ text: 'No environment variables to generate .env file',
+ type: 'warning'
+ });
+ return;
+ }
+
+ // Create and download file
+ var blob = new Blob([envText], { type: 'text/plain' });
+ var url = window.URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = url;
+ a.download = '.env';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+
+ new PNotify({
+ title: '.env File Generated',
+ text: '.env file has been generated and downloaded',
+ type: 'success'
+ });
+ };
+
+ $scope.showComposeHelp = function() {
+ var helpContent = `
+
+
How to use Docker Compose with Environment Variables
+
+
Step 1: Download Files
+
Click "Generate docker-compose.yml" and "Generate .env file" to download both files.
+
+
Step 2: Place Files
+
Place both files in the same directory on your server.
+
+
Step 3: Run Docker Compose
+
Run the following commands in your terminal:
+
docker compose up -d
+
+
Step 4: Update Environment Variables
+
To update environment variables:
+
+ - Edit the .env file
+ - Run:
docker compose up -d
+ - Only the environment variables will be reloaded (no container rebuild needed!)
+
+
+
Benefits:
+
+ - No need to recreate containers
+ - Faster environment variable updates
+ - Version control friendly
+ - Easy to share configurations
+
+
+
+ `;
+
+ // Create modal for help
+ var modal = document.createElement('div');
+ modal.className = 'modal fade';
+ modal.innerHTML = `
+
+
+
+
+ ${helpContent}
+
+
+
+
+ `;
+
+ document.body.appendChild(modal);
+ $(modal).modal('show');
+
+ // Remove modal when closed
+ $(modal).on('hidden.bs.modal', function() {
+ document.body.removeChild(modal);
+ });
+ };
+
+ // Helper function to generate Docker Compose YAML
+ function generateDockerComposeYml(containerInfo) {
+ var yml = 'version: \'3.8\'\n\n';
+ yml += 'services:\n';
+ yml += ' ' + containerInfo.name + ':\n';
+ yml += ' image: ' + containerInfo.image + '\n';
+ yml += ' container_name: ' + containerInfo.name + '\n';
+
+ // Add ports
+ var ports = Object.keys(containerInfo.ports);
+ if (ports.length > 0) {
+ yml += ' ports:\n';
+ for (var i = 0; i < ports.length; i++) {
+ var port = ports[i];
+ if (containerInfo.ports[port]) {
+ yml += ' - "' + containerInfo.ports[port] + ':' + port + '"\n';
+ }
+ }
+ }
+
+ // Add volumes
+ var volumes = Object.keys(containerInfo.volumes);
+ if (volumes.length > 0) {
+ yml += ' volumes:\n';
+ for (var i = 0; i < volumes.length; i++) {
+ var volume = volumes[i];
+ if (containerInfo.volumes[volume]) {
+ yml += ' - ' + containerInfo.volumes[volume] + ':' + volume + '\n';
+ }
+ }
+ }
+
+ // Add environment variables
+ var envVars = Object.keys(containerInfo.environment);
+ if (envVars.length > 0) {
+ yml += ' environment:\n';
+ for (var i = 0; i < envVars.length; i++) {
+ var envVar = envVars[i];
+ yml += ' - ' + envVar + '=' + containerInfo.environment[envVar] + '\n';
+ }
+ }
+
+ // Add restart policy
+ yml += ' restart: unless-stopped\n';
+
+ return yml;
+ }
+
+ // Helper function to generate Docker Compose YAML (for runContainer)
+ function generateDockerComposeYml(containerInfo) {
+ var yml = 'version: \'3.8\'\n\n';
+ yml += 'services:\n';
+ yml += ' ' + containerInfo.name + ':\n';
+ yml += ' image: ' + containerInfo.image + '\n';
+ yml += ' container_name: ' + containerInfo.name + '\n';
+
+ // Add ports
+ var ports = Object.keys(containerInfo.ports);
+ if (ports.length > 0) {
+ yml += ' ports:\n';
+ for (var i = 0; i < ports.length; i++) {
+ var port = ports[i];
+ if (containerInfo.ports[port]) {
+ yml += ' - "' + containerInfo.ports[port] + ':' + port + '"\n';
+ }
+ }
+ }
+
+ // Add volumes
+ var volumes = Object.keys(containerInfo.volumes);
+ if (volumes.length > 0) {
+ yml += ' volumes:\n';
+ for (var i = 0; i < volumes.length; i++) {
+ var volume = volumes[i];
+ if (containerInfo.volumes[volume]) {
+ yml += ' - ' + containerInfo.volumes[volume] + ':' + volume + '\n';
+ }
+ }
+ }
+
+ // Add environment variables
+ var envVars = Object.keys(containerInfo.environment);
+ if (envVars.length > 0) {
+ yml += ' environment:\n';
+ for (var i = 0; i < envVars.length; i++) {
+ var envVar = envVars[i];
+ yml += ' - ' + envVar + '=' + containerInfo.environment[envVar] + '\n';
+ }
+ }
+
+ // Add restart policy
+ yml += ' restart: unless-stopped\n';
+
+ return yml;
+ }
+
$scope.showTop = function () {
$scope.topHead = [];
$scope.topProcesses = [];
@@ -832,13 +1697,28 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) {
url = "/docker/saveContainerSettings";
$scope.savingSettings = true;
+ // Prepare environment variables based on mode
+ var finalEnvList = {};
+ if ($scope.advancedEnvMode && $scope.parsedEnvVars) {
+ // Use parsed environment variables from advanced mode
+ finalEnvList = $scope.parsedEnvVars;
+ } else {
+ // Convert simple envList to proper format
+ for (var key in $scope.envList) {
+ if ($scope.envList[key].name && $scope.envList[key].value) {
+ finalEnvList[$scope.envList[key].name] = $scope.envList[key].value;
+ }
+ }
+ }
+
var data = {
name: $scope.cName,
memory: $scope.memory,
startOnReboot: $scope.startOnReboot,
envConfirmation: $scope.envConfirmation,
- envList: $scope.envList,
- volList: $scope.volList
+ envList: finalEnvList,
+ volList: $scope.volList,
+ advancedEnvMode: $scope.advancedEnvMode
};
diff --git a/dockerManager/templates/dockerManager/runContainer.html b/dockerManager/templates/dockerManager/runContainer.html
index feeef0c51..c56bbf6c9 100644
--- a/dockerManager/templates/dockerManager/runContainer.html
+++ b/dockerManager/templates/dockerManager/runContainer.html
@@ -510,6 +510,108 @@
to { opacity: 1; }
}
+ /* Toggle Switch Styles */
+ .switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+ }
+
+ .slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ transition: .4s;
+ border-radius: 34px;
+ }
+
+ .slider:before {
+ position: absolute;
+ content: "";
+ height: 26px;
+ width: 26px;
+ left: 4px;
+ bottom: 4px;
+ background-color: white;
+ transition: .4s;
+ border-radius: 50%;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ }
+
+ input:checked + .slider {
+ background-color: var(--accent-color, #5b5fcf);
+ }
+
+ input:checked + .slider:before {
+ transform: translateX(26px);
+ }
+
+ /* Docker Compose Information Card Styles */
+ .compose-info-card {
+ background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
+ border: 1px solid #e8e9ff;
+ border-radius: 16px;
+ padding: 2rem;
+ margin-bottom: 2rem;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.05);
+ }
+
+ .compose-benefits h4 {
+ color: #1e293b;
+ margin-bottom: 1.5rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+
+ .compose-benefits h4 i {
+ color: #007bff;
+ font-size: 1.2rem;
+ }
+
+ .compose-benefits ul {
+ margin: 1rem 0;
+ padding-left: 1.5rem;
+ }
+
+ .compose-benefits li {
+ margin-bottom: 0.75rem;
+ color: #495057;
+ line-height: 1.6;
+ }
+
+ .compose-actions {
+ margin-top: 2rem;
+ display: flex;
+ gap: 1rem;
+ flex-wrap: wrap;
+ }
+
+ .compose-actions .btn {
+ border-radius: 8px;
+ font-weight: 500;
+ padding: 0.75rem 1.5rem;
+ transition: all 0.3s ease;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ }
+
+ .compose-actions .btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(0,0,0,0.15);
+ }
+
+ .compose-actions .btn i {
+ margin-right: 0.5rem;
+ }
+
+ .slider:hover {
+ box-shadow: 0 0 8px rgba(91, 95, 207, 0.3);
+ }
+
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
@@ -689,6 +791,41 @@
{% endif %}
+
+
+
-
-