mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-10-26 00:36:34 +02:00
Enhance environment variable management with advanced mode and import/export features
- Implement advanced environment variable mode for bulk editing and easier management. - Add functionality to import environment variables from existing Docker containers. - Introduce export options for environment variables to .env files. - Update UI to toggle between simple and advanced modes, with corresponding input fields. - Enhance Docker Compose integration with environment variable handling and user guidance.
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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 = `
|
||||
<div class="compose-help-content">
|
||||
<h4><i class="fas fa-info-circle"></i> How to use Docker Compose with Environment Variables</h4>
|
||||
<div class="help-steps">
|
||||
<h5>Step 1: Download Files</h5>
|
||||
<p>Click "Generate docker-compose.yml" and "Generate .env file" to download both files.</p>
|
||||
|
||||
<h5>Step 2: Place Files</h5>
|
||||
<p>Place both files in the same directory on your server.</p>
|
||||
|
||||
<h5>Step 3: Run Docker Compose</h5>
|
||||
<p>Run the following commands in your terminal:</p>
|
||||
<pre><code>docker compose up -d</code></pre>
|
||||
|
||||
<h5>Step 4: Update Environment Variables</h5>
|
||||
<p>To update environment variables:</p>
|
||||
<ol>
|
||||
<li>Edit the .env file</li>
|
||||
<li>Run: <code>docker compose up -d</code></li>
|
||||
<li>Only the environment variables will be reloaded (no container rebuild needed!)</li>
|
||||
</ol>
|
||||
|
||||
<h5>Benefits:</h5>
|
||||
<ul>
|
||||
<li>No need to recreate containers</li>
|
||||
<li>Faster environment variable updates</li>
|
||||
<li>Version control friendly</li>
|
||||
<li>Easy to share configurations</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Create modal for help
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal fade';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
Docker Compose Help
|
||||
</h4>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${helpContent}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div class="compose-help-content">
|
||||
<h4><i class="fas fa-info-circle"></i> How to use Docker Compose with Environment Variables</h4>
|
||||
<div class="help-steps">
|
||||
<h5>Step 1: Download Files</h5>
|
||||
<p>Click "Generate docker-compose.yml" and "Generate .env file" to download both files.</p>
|
||||
|
||||
<h5>Step 2: Place Files</h5>
|
||||
<p>Place both files in the same directory on your server.</p>
|
||||
|
||||
<h5>Step 3: Run Docker Compose</h5>
|
||||
<p>Run the following commands in your terminal:</p>
|
||||
<pre><code>docker compose up -d</code></pre>
|
||||
|
||||
<h5>Step 4: Update Environment Variables</h5>
|
||||
<p>To update environment variables:</p>
|
||||
<ol>
|
||||
<li>Edit the .env file</li>
|
||||
<li>Run: <code>docker compose up -d</code></li>
|
||||
<li>Only the environment variables will be reloaded (no container rebuild needed!)</li>
|
||||
</ol>
|
||||
|
||||
<h5>Benefits:</h5>
|
||||
<ul>
|
||||
<li>No need to recreate containers</li>
|
||||
<li>Faster environment variable updates</li>
|
||||
<li>Version control friendly</li>
|
||||
<li>Easy to share configurations</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Create modal for help
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal fade';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
Docker Compose Help
|
||||
</h4>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${helpContent}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Docker Compose Information Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="section-title">{% trans "Docker Compose Benefits" %}</h2>
|
||||
<p class="section-subtitle">{% trans "Use Docker Compose for easier environment variable management" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="compose-info-card">
|
||||
<div class="compose-benefits">
|
||||
<h4><i class="fas fa-rocket"></i> {% trans "With Docker Compose, you can:" %}</h4>
|
||||
<ul>
|
||||
<li>{% trans "Keep your environment variables in a separate .env file" %}</li>
|
||||
<li>{% trans "When you change the .env, you don't need to rebuild the entire container image" %}</li>
|
||||
<li>{% trans "You can simply run docker compose up -d again, and only the parts that changed (like the environment variables) will be reloaded" %}</li>
|
||||
</ul>
|
||||
<div class="compose-actions">
|
||||
<button type="button" class="btn btn-primary" ng-click="generateDockerCompose()">
|
||||
<i class="fas fa-file-code"></i> {% trans "Generate docker-compose.yml" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-success" ng-click="generateEnvFile()">
|
||||
<i class="fas fa-file-alt"></i> {% trans "Generate .env file" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-info" ng-click="showComposeHelp()">
|
||||
<i class="fas fa-question-circle"></i> {% trans "How to use" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Environment Variables Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
@@ -701,34 +838,138 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dynamic-section">
|
||||
<div class="dynamic-header">
|
||||
<span class="dynamic-title">{% trans "Environment Variables" %}</span>
|
||||
<button type="button" class="btn btn-secondary" ng-click="addEnvField()">
|
||||
<i class="fas fa-plus"></i>
|
||||
{% trans "Add Variable" %}
|
||||
</button>
|
||||
<!-- Environment Variable Mode Toggle -->
|
||||
<div class="env-mode-toggle" style="margin-bottom: 1.5rem;">
|
||||
<div class="toggle-container" style="display: flex; align-items: center; gap: 1rem; padding: 1rem; background: var(--bg-hover, #f8f9ff); border-radius: 12px; border: 1px solid var(--border-color, #e8e9ff);">
|
||||
<div style="flex: 1;">
|
||||
<label style="font-weight: 600; color: var(--text-primary, #1e293b); margin-bottom: 0.5rem; display: block;">
|
||||
{% trans "Environment Variable Mode" %}
|
||||
</label>
|
||||
<p style="font-size: 0.875rem; color: var(--text-secondary, #64748b); margin: 0;">
|
||||
{% trans "Choose between simple line-by-line input or advanced bulk editing mode" %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="toggle-switch">
|
||||
<label class="switch" style="position: relative; display: inline-block; width: 60px; height: 34px;">
|
||||
<input type="checkbox" ng-model="advancedEnvMode" ng-change="toggleEnvMode()" style="opacity: 0; width: 0; height: 0;">
|
||||
<span class="slider" style="position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px;">
|
||||
<span class="slider-thumb" style="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);"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div style="text-align: center; margin-top: 0.5rem;">
|
||||
<span style="font-size: 0.75rem; font-weight: 600; color: var(--text-secondary, #64748b);">
|
||||
<span ng-show="!advancedEnvMode">{% trans "Simple Mode" %}</span>
|
||||
<span ng-show="advancedEnvMode">{% trans "Advanced Mode" %}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span ng-init="envList = {}"></span>
|
||||
{% for env, value in envList.items %}
|
||||
<span ng-init="envList[{{ forloop.counter0 }}] = {'name':'{{ env }}', 'value':'{{ value }}'}"></span>
|
||||
{% endfor %}
|
||||
<!-- Simple Mode: Line-by-line input -->
|
||||
<div ng-show="!advancedEnvMode" class="simple-env-mode">
|
||||
<div class="dynamic-section">
|
||||
<div class="dynamic-header">
|
||||
<span class="dynamic-title">{% trans "Environment Variables" %}</span>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button type="button" class="btn btn-secondary" ng-click="loadEnvTemplate()">
|
||||
<i class="fas fa-file-import"></i>
|
||||
{% trans "Load Template" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" ng-click="addEnvField()">
|
||||
<i class="fas fa-plus"></i>
|
||||
{% trans "Add Variable" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="env in envList track by $index" class="dynamic-item">
|
||||
<input type="text" class="form-control" ng-model="envList[$index].name"
|
||||
placeholder="{% trans 'Variable name' %}">
|
||||
<input type="text" class="form-control" ng-model="envList[$index].value"
|
||||
placeholder="{% trans 'Value' %}">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeEnvField($index)"
|
||||
ng-show="Object.keys(envList).length > 0">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<span ng-init="envList = {}"></span>
|
||||
{% for env, value in envList.items %}
|
||||
<span ng-init="envList[{{ forloop.counter0 }}] = {'name':'{{ env }}', 'value':'{{ value }}'}"></span>
|
||||
{% endfor %}
|
||||
|
||||
<div ng-repeat="env in envList track by $index" class="dynamic-item">
|
||||
<input type="text" class="form-control" ng-model="envList[$index].name"
|
||||
placeholder="{% trans 'Variable name' %}">
|
||||
<input type="text" class="form-control" ng-model="envList[$index].value"
|
||||
placeholder="{% trans 'Value' %}">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeEnvField($index)"
|
||||
ng-show="Object.keys(envList).length > 0">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ng-show="Object.keys(envList).length === 0" style="text-align: center; padding: 2rem; color: #64748b;">
|
||||
<i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "No environment variables configured. Click 'Add Variable' to add one." %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="Object.keys(envList).length === 0" style="text-align: center; padding: 2rem; color: #64748b;">
|
||||
<i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "No environment variables configured. Click 'Add Variable' to add one." %}
|
||||
<!-- Advanced Mode: Bulk input -->
|
||||
<div ng-show="advancedEnvMode" class="advanced-env-mode">
|
||||
<div class="advanced-env-container" style="background: var(--bg-secondary, white); border-radius: 12px; border: 1px solid var(--border-color, #e8e9ff); overflow: hidden;">
|
||||
<div class="advanced-env-header" style="background: var(--bg-hover, #f8f9ff); padding: 1rem 1.5rem; border-bottom: 1px solid var(--border-color, #e8e9ff); display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<h4 style="margin: 0; font-size: 1rem; font-weight: 600; color: var(--text-primary, #1e293b);">
|
||||
{% trans "Advanced Environment Variables" %}
|
||||
</h4>
|
||||
<p style="margin: 0.25rem 0 0 0; font-size: 0.875rem; color: var(--text-secondary, #64748b);">
|
||||
{% trans "Switch to advanced mode to copy & paste multiple variables" %}
|
||||
</p>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button type="button" class="btn btn-secondary" ng-click="loadEnvFromFile()" style="padding: 0.5rem 1rem; font-size: 0.875rem;">
|
||||
<i class="fas fa-upload"></i>
|
||||
{% trans "Load from .env" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" ng-click="copyEnvToClipboard()" style="padding: 0.5rem 1rem; font-size: 0.875rem;">
|
||||
<i class="fas fa-copy"></i>
|
||||
{% trans "Copy" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="advanced-env-content" style="padding: 1.5rem;">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label style="display: block; font-weight: 600; color: var(--text-primary, #1e293b); margin-bottom: 0.5rem;">
|
||||
{% trans "Environment Variables (one per line)" %}
|
||||
</label>
|
||||
<p style="font-size: 0.875rem; color: var(--text-secondary, #64748b); margin-bottom: 1rem;">
|
||||
{% trans "Enter environment variables in KEY=VALUE format, one per line. Example:" %}
|
||||
</p>
|
||||
<div style="background: var(--bg-hover, #f8f9ff); padding: 0.75rem; border-radius: 8px; margin-bottom: 1rem; font-family: monospace; font-size: 0.875rem; color: var(--text-secondary, #64748b);">
|
||||
DATABASE_URL=postgresql://user:pass@localhost/db<br>
|
||||
API_KEY=your-secret-key<br>
|
||||
DEBUG=true<br>
|
||||
PORT=3000
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea ng-model="advancedEnvText"
|
||||
ng-change="parseAdvancedEnv()"
|
||||
placeholder="DATABASE_URL=postgresql://user:pass@localhost/db API_KEY=your-secret-key DEBUG=true PORT=3000"
|
||||
style="width: 100%; height: 200px; padding: 1rem; border: 1px solid var(--border-color, #e8e9ff); border-radius: 8px; font-family: 'Courier New', monospace; font-size: 0.875rem; resize: vertical; background: var(--bg-secondary, white);"
|
||||
ng-init="advancedEnvText = ''"></textarea>
|
||||
|
||||
<div style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="font-size: 0.875rem; color: var(--text-secondary, #64748b);">
|
||||
<i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
|
||||
<span ng-show="advancedEnvCount > 0">{% trans "Parsed" %} {{ advancedEnvCount }} {% trans "environment variables" %}</span>
|
||||
<span ng-show="advancedEnvCount === 0">{% trans "No environment variables detected" %}</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button type="button" class="btn btn-secondary" ng-click="clearAdvancedEnv()" style="padding: 0.5rem 1rem; font-size: 0.875rem;">
|
||||
<i class="fas fa-trash"></i>
|
||||
{% trans "Clear" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" ng-click="loadEnvTemplate()" style="padding: 0.5rem 1rem; font-size: 0.875rem;">
|
||||
<i class="fas fa-magic"></i>
|
||||
{% trans "Load Template" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -836,35 +836,141 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Environment Variable Mode Toggle -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{% trans "Environment Mode" %}</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="toggle-container" style="display: flex; align-items: center; gap: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 8px; border: 1px solid #dee2e6;">
|
||||
<div style="flex: 1;">
|
||||
<label style="font-weight: 600; color: #495057; margin-bottom: 0.25rem; display: block;">
|
||||
{% trans "Advanced Environment Mode" %}
|
||||
</label>
|
||||
<p style="font-size: 0.875rem; color: #6c757d; margin: 0;">
|
||||
{% trans "Enable advanced mode for bulk editing environment variables" %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="toggle-switch">
|
||||
<label class="switch" style="position: relative; display: inline-block; width: 60px; height: 34px;">
|
||||
<input type="checkbox" ng-model="advancedEnvMode" ng-change="toggleEnvMode()" style="opacity: 0; width: 0; height: 0;">
|
||||
<span class="slider" style="position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px;">
|
||||
<span class="slider-thumb" style="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);"></span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Simple Mode: Line-by-line input -->
|
||||
<div ng-show="!advancedEnvMode">
|
||||
<!-- Docker Compose Information -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="alert alert-info docker-compose-info">
|
||||
<div class="compose-header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>{% trans "Docker Compose Environment Variables" %}</strong>
|
||||
</div>
|
||||
<div class="compose-benefits">
|
||||
<p><strong>{% trans "With Docker Compose, you can:" %}</strong></p>
|
||||
<ul>
|
||||
<li>{% trans "Keep your environment variables in a separate .env file" %}</li>
|
||||
<li>{% trans "When you change the .env, you don't need to rebuild the entire container image" %}</li>
|
||||
<li>{% trans "You can simply run docker compose up -d again, and only the parts that changed (like the environment variables) will be reloaded" %}</li>
|
||||
</ul>
|
||||
<div class="compose-actions">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="generateDockerCompose()">
|
||||
<i class="fas fa-file-code"></i> {% trans "Generate docker-compose.yml" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-success btn-sm" ng-click="generateEnvFile()">
|
||||
<i class="fas fa-file-alt"></i> {% trans "Generate .env file" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-sm" ng-click="showComposeHelp()">
|
||||
<i class="fas fa-question-circle"></i> {% trans "How to use" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Environment Variables -->
|
||||
<span ng-init="envList = {}"></span>
|
||||
{% for env, value in envList.items %}
|
||||
<span ng-init="envList[{{ forloop.counter0 }}] = {'name':'{{ env }}', 'value':'{{ value }}'}"></span>
|
||||
{% endfor %}
|
||||
|
||||
<div ng-repeat="env in envList track by $index">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label" ng-show="$first">
|
||||
{% trans "Environment Variables" %}
|
||||
</label>
|
||||
<label class="col-sm-3" ng-hide="$first"></label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" ng-disabled="!envConfirmation"
|
||||
ng-model="envList[$index].name" placeholder="Variable name" required>
|
||||
<div ng-repeat="env in envList track by $index">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label" ng-show="$first">
|
||||
{% trans "Environment Variables" %}
|
||||
</label>
|
||||
<label class="col-sm-3" ng-hide="$first"></label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" ng-disabled="!envConfirmation"
|
||||
ng-model="envList[$index].name" placeholder="Variable name" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-disabled="!envConfirmation"
|
||||
ng-model="envList[$index].value" placeholder="Value" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-disabled="!envConfirmation"
|
||||
ng-model="envList[$index].value" placeholder="Value" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-9">
|
||||
<button type="button" class="btn btn-info" ng-disabled="!envConfirmation"
|
||||
ng-click="addEnvField()">
|
||||
<i class="fas fa-plus"></i> {% trans "Add Environment Variable" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-9">
|
||||
<button type="button" class="btn btn-info" ng-disabled="!envConfirmation"
|
||||
ng-click="addEnvField()">
|
||||
<i class="fas fa-plus"></i> {% trans "Add Environment Variable" %}
|
||||
</button>
|
||||
<!-- Advanced Mode: Bulk input -->
|
||||
<div ng-show="advancedEnvMode">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{% trans "Environment Variables" %}</label>
|
||||
<div class="col-sm-9">
|
||||
<div style="background: white; border-radius: 8px; border: 1px solid #dee2e6; overflow: hidden;">
|
||||
<div style="background: #f8f9fa; padding: 1rem; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<h5 style="margin: 0; font-size: 1rem; font-weight: 600; color: #495057;">
|
||||
{% trans "Advanced Environment Variables" %}
|
||||
</h5>
|
||||
<p style="margin: 0.25rem 0 0 0; font-size: 0.875rem; color: #6c757d;">
|
||||
{% trans "Edit environment variables in bulk using KEY=VALUE format" %}
|
||||
</p>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" ng-click="importEnvFromContainer()" title="{% trans 'Import from another container' %}">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-success" ng-click="exportEnvToFile()" title="{% trans 'Export to .env file' %}">
|
||||
<i class="fas fa-file-export"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" ng-click="copyEnvToClipboard()" title="{% trans 'Copy to clipboard' %}">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 1rem;">
|
||||
<textarea ng-model="advancedEnvText"
|
||||
ng-change="parseAdvancedEnv()"
|
||||
placeholder="DATABASE_URL=postgresql://user:pass@localhost/db API_KEY=your-secret-key DEBUG=true PORT=3000"
|
||||
style="width: 100%; height: 150px; padding: 0.75rem; border: 1px solid #ced4da; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 0.875rem; resize: vertical;"
|
||||
ng-disabled="!envConfirmation"
|
||||
ng-init="advancedEnvText = ''"></textarea>
|
||||
|
||||
<div style="margin-top: 0.5rem; font-size: 0.875rem; color: #6c757d;">
|
||||
<i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
|
||||
<span ng-show="advancedEnvCount > 0">{% trans "Parsed" %} {{ advancedEnvCount }} {% trans "environment variables" %}</span>
|
||||
<span ng-show="advancedEnvCount === 0">{% trans "No environment variables detected" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1052,6 +1158,220 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Container Import Modal -->
|
||||
<div class="modal fade" id="containerImportModal" tabindex="-1" role="dialog" ng-show="showContainerImportModal">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<i class="fas fa-download"></i>
|
||||
{% trans "Import Environment Variables from Container" %}
|
||||
</h4>
|
||||
<button type="button" class="close" ng-click="showContainerImportModal = false">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div ng-show="importLoading" class="text-center">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p>{% trans "Loading containers..." %}</p>
|
||||
</div>
|
||||
|
||||
<div ng-hide="importLoading">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
{% trans "Select a container to import its environment variables. This will replace your current environment variables." %}
|
||||
</div>
|
||||
|
||||
<div class="container-list">
|
||||
<div ng-repeat="container in importContainers"
|
||||
class="container-item"
|
||||
ng-click="selectContainerForImport(container)"
|
||||
ng-class="{'selected': selectedImportContainer && selectedImportContainer.name === container.name}">
|
||||
<div class="container-info">
|
||||
<div class="container-name">
|
||||
<i class="fas fa-cube"></i>
|
||||
{{ container.name }}
|
||||
</div>
|
||||
<div class="container-details">
|
||||
<span class="container-image">{{ container.image }}</span>
|
||||
<span class="container-status" ng-class="'status-' + container.status">
|
||||
{{ container.status }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="container-env-count" ng-if="container.envCount">
|
||||
<i class="fas fa-list"></i>
|
||||
{{ container.envCount }} {% trans "environment variables" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-actions">
|
||||
<button class="btn btn-primary btn-sm" ng-click="selectContainerForImport(container)">
|
||||
<i class="fas fa-download"></i>
|
||||
{% trans "Import" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="importContainers.length === 0" class="text-center text-muted">
|
||||
<i class="fas fa-info-circle fa-2x"></i>
|
||||
<p>{% trans "No other containers found to import from" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="importEnvLoading" class="text-center">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p>{% trans "Importing environment variables..." %}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" ng-click="showContainerImportModal = false">
|
||||
{% trans "Cancel" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.container-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.container-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.container-item.selected {
|
||||
background-color: #e3f2fd;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.container-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.container-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.container-name i {
|
||||
margin-right: 0.5rem;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.container-details {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.container-image {
|
||||
font-family: monospace;
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.container-status {
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.container-status.status-running {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.container-status.status-stopped {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.container-env-count {
|
||||
font-size: 0.75rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.container-env-count i {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.container-actions {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* Docker Compose Information Styles */
|
||||
.docker-compose-info {
|
||||
border-left: 4px solid #007bff;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.compose-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.compose-header i {
|
||||
color: #007bff;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.compose-benefits ul {
|
||||
margin: 0.5rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.compose-benefits li {
|
||||
margin-bottom: 0.5rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.compose-actions {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.compose-actions .btn {
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.compose-actions .btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer_scripts %}
|
||||
|
||||
@@ -20,6 +20,8 @@ urlpatterns = [
|
||||
re_path(r'^saveContainerSettings$', views.saveContainerSettings, name='saveContainerSettings'),
|
||||
re_path(r'^getContainerTop$', views.getContainerTop, name='getContainerTop'),
|
||||
re_path(r'^assignContainer$', views.assignContainer, name='assignContainer'),
|
||||
re_path(r'^loadContainersForImport$', views.loadContainersForImport, name='loadContainersForImport'),
|
||||
re_path(r'^getContainerEnv$', views.getContainerEnv, name='getContainerEnv'),
|
||||
re_path(r'^searchImage$', views.searchImage, name='searchImage'),
|
||||
re_path(r'^manageImages$', views.manageImages, name='manageImages'),
|
||||
re_path(r'^getImageHistory$', views.getImageHistory, name='getImageHistory'),
|
||||
|
||||
@@ -556,5 +556,111 @@ def executeContainerCommand(request):
|
||||
coreResult = cm.executeContainerCommand(userID, json.loads(request.body))
|
||||
|
||||
return coreResult
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
|
||||
def loadContainersForImport(request):
|
||||
"""
|
||||
Load all containers for import selection, excluding the current container
|
||||
"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
currentContainer = request.GET.get('currentContainer', '')
|
||||
|
||||
# Get all containers using Docker API
|
||||
import docker
|
||||
dockerClient = docker.from_env()
|
||||
containers = dockerClient.containers.list(all=True)
|
||||
|
||||
containerList = []
|
||||
for container in containers:
|
||||
# Skip the current container
|
||||
if container.name == currentContainer:
|
||||
continue
|
||||
|
||||
# Get container info
|
||||
containerInfo = {
|
||||
'name': container.name,
|
||||
'image': container.image.tags[0] if container.image.tags else container.image.id,
|
||||
'status': container.status,
|
||||
'id': container.short_id
|
||||
}
|
||||
|
||||
# Count environment variables
|
||||
try:
|
||||
envVars = container.attrs.get('Config', {}).get('Env', [])
|
||||
containerInfo['envCount'] = len(envVars)
|
||||
except:
|
||||
containerInfo['envCount'] = 0
|
||||
|
||||
containerList.append(containerInfo)
|
||||
|
||||
return HttpResponse(json.dumps({
|
||||
'success': 1,
|
||||
'containers': containerList
|
||||
}), content_type='application/json')
|
||||
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({
|
||||
'success': 0,
|
||||
'message': str(e)
|
||||
}), content_type='application/json')
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
|
||||
def getContainerEnv(request):
|
||||
"""
|
||||
Get environment variables from a specific container
|
||||
"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
containerName = request.GET.get('containerName', '')
|
||||
|
||||
if not containerName:
|
||||
return HttpResponse(json.dumps({
|
||||
'success': 0,
|
||||
'message': 'Container name is required'
|
||||
}), content_type='application/json')
|
||||
|
||||
# Get container using Docker API
|
||||
import docker
|
||||
dockerClient = docker.from_env()
|
||||
container = dockerClient.containers.get(containerName)
|
||||
|
||||
# Extract environment variables
|
||||
envVars = {}
|
||||
envList = container.attrs.get('Config', {}).get('Env', [])
|
||||
|
||||
for envVar in envList:
|
||||
if '=' in envVar:
|
||||
key, value = envVar.split('=', 1)
|
||||
envVars[key] = value
|
||||
|
||||
return HttpResponse(json.dumps({
|
||||
'success': 1,
|
||||
'envVars': envVars
|
||||
}), content_type='application/json')
|
||||
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({
|
||||
'success': 0,
|
||||
'message': str(e)
|
||||
}), content_type='application/json')
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
Reference in New Issue
Block a user