mirror of https://github.com/portainer/portainer
feat(UX): introduce new env variables UI (#4175)
* feat(app): introduce new env vars ui feat(app): introduce new env vars ui feat(UX): WIP new env variables UI feat(UX): update button and placeholder feat(UX): mention .env file in message feat(UX): allow add/remove value & load correctly feat(UX): restrict filesize to 1MB feat(UX): vertical align error message feat(UX): fill UI from file & when switching modes feat(UX): strip un-needed newline character feat(UX): introduce component to other views feat(UX): fix title alignment feat(UX): only populate editor on mode switch when key exists feat(UX): prevent trimming of whitespace on values feat(UX): change editor to async feat(UX): add message describing use feat(UX): Refactor variable text to editorText refactor(app): rename env vars controller refactor(app): move env var explanation to parent refactor(app): order env var panels refactor(app): move simple env vars mode to component refactor(app): parse env vars refactor(app): move styles to css refactor(app): rename functions refactor(container): parse env vars refactor(env-vars): move utils to helper module refactor(env-vars): use util function for parse dot env file fix(env-vars): ignore comments refactor(services): use env vars utils refactor(env-vars): rename files refactor(env-panel): use utils style(stack): revert EnvContent to Env style(service): revert EnvContent to Env style(container): revert EnvContent to Env refactor(env-vars): support default value refactor(service): use new env var component refactor(env-var): use one way data flow refactor(containers): remove unused function * fix(env-vars): prevent using non .env files * refactor(env-vars): move env vars items to a component * feat(app): fixed env vars form validation in Stack * feat(services): disable env form submit if invalid * fix(app): show key pairs correctly * fix(env-var): use the same validation as with kubernetes * fix(env-vars): parse env var Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> Co-authored-by: Felix Han <felix.han@portainer.io>pull/5143/head
parent
6e9f472723
commit
a5e8cf62d2
|
@ -67,39 +67,6 @@ angular.module('portainer.docker').factory('ServiceHelper', [
|
|||
return [];
|
||||
};
|
||||
|
||||
helper.translateEnvironmentVariables = function (env) {
|
||||
if (env) {
|
||||
var variables = [];
|
||||
env.forEach(function (variable) {
|
||||
var idx = variable.indexOf('=');
|
||||
var keyValue = [variable.slice(0, idx), variable.slice(idx + 1)];
|
||||
var originalValue = keyValue.length > 1 ? keyValue[1] : '';
|
||||
variables.push({
|
||||
key: keyValue[0],
|
||||
value: originalValue,
|
||||
originalKey: keyValue[0],
|
||||
originalValue: originalValue,
|
||||
added: true,
|
||||
});
|
||||
});
|
||||
return variables;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
helper.translateEnvironmentVariablesToEnv = function (env) {
|
||||
if (env) {
|
||||
var variables = [];
|
||||
env.forEach(function (variable) {
|
||||
if (variable.key && variable.key !== '') {
|
||||
variables.push(variable.key + '=' + variable.value);
|
||||
}
|
||||
});
|
||||
return variables;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
helper.translatePreferencesToKeyValue = function (preferences) {
|
||||
if (preferences) {
|
||||
var keyValuePreferences = [];
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
|
||||
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||
|
@ -78,6 +81,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
MemoryReservation: 0,
|
||||
CmdMode: 'default',
|
||||
EntrypointMode: 'default',
|
||||
Env: [],
|
||||
NodeName: null,
|
||||
capabilities: [],
|
||||
Sysctls: [],
|
||||
|
@ -95,6 +99,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
pullImageValidity: true,
|
||||
};
|
||||
|
||||
$scope.handleEnvVarChange = handleEnvVarChange;
|
||||
function handleEnvVarChange(value) {
|
||||
$scope.formValues.Env = value;
|
||||
}
|
||||
|
||||
$scope.refreshSlider = function () {
|
||||
$timeout(function () {
|
||||
$scope.$broadcast('rzSliderForceRender');
|
||||
|
@ -153,14 +162,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
$scope.formValues.Volumes.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function () {
|
||||
$scope.config.Env.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function (index) {
|
||||
$scope.config.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addPortBinding = function () {
|
||||
$scope.config.HostConfig.PortBindings.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
||||
};
|
||||
|
@ -254,13 +255,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
}
|
||||
|
||||
function prepareEnvironmentVariables(config) {
|
||||
var env = [];
|
||||
config.Env.forEach(function (v) {
|
||||
if (v.name && v.value) {
|
||||
env.push(v.name + '=' + v.value);
|
||||
}
|
||||
});
|
||||
config.Env = env;
|
||||
config.Env = envVarsUtils.convertToArrayOfStrings($scope.formValues.Env);
|
||||
}
|
||||
|
||||
function prepareVolumes(config) {
|
||||
|
@ -537,14 +532,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
}
|
||||
|
||||
function loadFromContainerEnvironmentVariables() {
|
||||
var envArr = [];
|
||||
for (var e in $scope.config.Env) {
|
||||
if ({}.hasOwnProperty.call($scope.config.Env, e)) {
|
||||
var arr = $scope.config.Env[e].split(/\=(.*)/);
|
||||
envArr.push({ name: arr[0], value: arr[1] });
|
||||
}
|
||||
}
|
||||
$scope.config.Env = envArr;
|
||||
$scope.formValues.Env = envVarsUtils.parseArrayOfStrings($scope.config.Env);
|
||||
}
|
||||
|
||||
function loadFromContainerLabels() {
|
||||
|
|
|
@ -583,37 +583,13 @@
|
|||
<!-- !tab-labels -->
|
||||
<!-- tab-env -->
|
||||
<div class="tab-pane" id="env">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- environment-variables -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Environment variables</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in config.Env" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="variable.name" placeholder="e.g. FOO" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar" />
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
</form>
|
||||
<environment-variables-panel
|
||||
ng-model="formValues.Env"
|
||||
explanation="These values will be applied to the container when deployed"
|
||||
on-change="(handleEnvVarChange)"
|
||||
></environment-variables-panel>
|
||||
</div>
|
||||
<!-- !tab-labels -->
|
||||
<!-- !tab-env -->
|
||||
<!-- tab-restart-policy -->
|
||||
<div class="tab-pane" id="restart-policy">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
|
||||
|
@ -109,6 +111,11 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
|
||||
$scope.allowBindMounts = false;
|
||||
|
||||
$scope.handleEnvVarChange = handleEnvVarChange;
|
||||
function handleEnvVarChange(value) {
|
||||
$scope.formValues.Env = value;
|
||||
}
|
||||
|
||||
$scope.refreshSlider = function () {
|
||||
$timeout(function () {
|
||||
$scope.$broadcast('rzSliderForceRender');
|
||||
|
@ -168,14 +175,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
$scope.formValues.Secrets.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function () {
|
||||
$scope.formValues.Env.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function (index) {
|
||||
$scope.formValues.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addPlacementConstraint = function () {
|
||||
$scope.formValues.PlacementConstraints.push({ key: '', operator: '==', value: '' });
|
||||
};
|
||||
|
@ -277,13 +276,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
|||
}
|
||||
|
||||
function prepareEnvConfig(config, input) {
|
||||
var env = [];
|
||||
input.Env.forEach(function (v) {
|
||||
if (v.name) {
|
||||
env.push(v.name + '=' + v.value);
|
||||
}
|
||||
});
|
||||
config.TaskTemplate.ContainerSpec.Env = env;
|
||||
config.TaskTemplate.ContainerSpec.Env = envVarsUtils.convertToArrayOfStrings(input.Env);
|
||||
}
|
||||
|
||||
function prepareLabelsConfig(config, input) {
|
||||
|
|
|
@ -160,6 +160,7 @@
|
|||
<li class="active interactive"><a data-target="#command" data-toggle="tab">Command & Logging</a></li>
|
||||
<li class="interactive"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="interactive"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="interactive"><a data-target="#env" data-toggle="tab">Env</a></li>
|
||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||
<li class="interactive"><a data-target="#update-config" data-toggle="tab">Update config & Restart</a></li>
|
||||
<li class="interactive" ng-if="applicationState.endpoint.apiVersion >= 1.25"><a data-target="#secrets" data-toggle="tab">Secrets</a></li>
|
||||
|
@ -202,34 +203,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- !workdir-user-input -->
|
||||
<!-- environment-variables -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Environment variables</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in formValues.Env" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="variable.name" placeholder="e.g. FOO" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar" />
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Logging
|
||||
</div>
|
||||
|
@ -443,6 +416,15 @@
|
|||
</form>
|
||||
</div>
|
||||
<!-- !tab-network -->
|
||||
<!-- tab-env -->
|
||||
<div class="tab-pane" id="env">
|
||||
<environment-variables-panel
|
||||
ng-model="formValues.Env"
|
||||
explanation="These values will be applied to the service when created"
|
||||
on-change="(handleEnvVarChange)"
|
||||
></environment-variables-panel>
|
||||
</div>
|
||||
<!-- !tab-env -->
|
||||
<!-- tab-labels -->
|
||||
<div class="tab-pane" id="labels">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<div ng-if="service.EnvironmentVariables" id="service-env-variables">
|
||||
<ng-form ng-if="service.EnvironmentVariables" id="service-env-variables" name="serviceEnvForm">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title-text="Environment variables">
|
||||
<div class="nopadding" authorization="DockerServiceUpdate">
|
||||
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating ||addEnvironmentVariable(service)" ng-disabled="isUpdating">
|
||||
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating || addEnvironmentVariable(service)" ng-disabled="isUpdating">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> environment variable
|
||||
</a>
|
||||
</div>
|
||||
|
@ -10,49 +10,20 @@
|
|||
<rd-widget-body ng-if="service.EnvironmentVariables.length === 0">
|
||||
<p>There are no environment variables for this service.</p>
|
||||
</rd-widget-body>
|
||||
<rd-widget-body ng-if="service.EnvironmentVariables.length > 0" classes="no-padding">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="var in service.EnvironmentVariables | orderBy: 'originalKey'">
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">name</span>
|
||||
<input type="text" class="form-control" ng-model="var.key" ng-disabled="var.added || isUpdating" placeholder="e.g. FOO" />
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">value</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="var.value"
|
||||
ng-change="updateEnvironmentVariable(service, var)"
|
||||
placeholder="e.g. bar"
|
||||
ng-disabled="isUpdating"
|
||||
disable-authorization="DockerServiceUpdate"
|
||||
/>
|
||||
<span class="input-group-btn" authorization="DockerServiceUpdate">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable(service, var)" ng-disabled="isUpdating">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<rd-widget-body ng-if="service.EnvironmentVariables.length > 0">
|
||||
<environment-variables-panel is-name-disabled="true" ng-model="service.EnvironmentVariables" on-change="(onChangeEnvVars)"></environment-variables-panel>
|
||||
</rd-widget-body>
|
||||
<rd-widget-footer authorization="DockerServiceUpdate">
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['EnvironmentVariables'])" ng-click="updateService(service)">Apply changes</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="!hasChanges(service, ['EnvironmentVariables']) || serviceEnvForm.$invalid"
|
||||
ng-click="updateService(service)"
|
||||
>
|
||||
Apply changes
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
@ -64,4 +35,4 @@
|
|||
</div>
|
||||
</rd-widget-footer>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</ng-form>
|
||||
|
|
|
@ -18,6 +18,9 @@ require('./includes/tasks.html');
|
|||
require('./includes/updateconfig.html');
|
||||
|
||||
import _ from 'lodash-es';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
|
||||
angular.module('portainer.docker').controller('ServiceController', [
|
||||
|
@ -114,21 +117,25 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function addEnvironmentVariable(service) {
|
||||
service.EnvironmentVariables.push({ key: '', value: '', originalValue: '' });
|
||||
service.EnvironmentVariables.push({ name: '', value: '' });
|
||||
updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables);
|
||||
};
|
||||
$scope.removeEnvironmentVariable = function removeEnvironmentVariable(service, item) {
|
||||
const index = service.EnvironmentVariables.indexOf(item);
|
||||
const removedElement = service.EnvironmentVariables.splice(index, 1);
|
||||
if (removedElement !== null) {
|
||||
updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables);
|
||||
}
|
||||
};
|
||||
$scope.updateEnvironmentVariable = function updateEnvironmentVariable(service, variable) {
|
||||
if (variable.value !== variable.originalValue || variable.key !== variable.originalKey) {
|
||||
updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.onChangeEnvVars = onChangeEnvVars;
|
||||
|
||||
function onChangeEnvVars(env) {
|
||||
const service = $scope.service;
|
||||
|
||||
const orgEnv = service.EnvironmentVariables;
|
||||
service.EnvironmentVariables = env.map((v) => {
|
||||
const orgVar = orgEnv.find(({ name }) => v.name === name);
|
||||
const added = orgVar && orgVar.added;
|
||||
return { ...v, added };
|
||||
});
|
||||
|
||||
updateServiceArray(service, 'EnvironmentVariables', service.EnvironmentVariables);
|
||||
}
|
||||
|
||||
$scope.addConfig = function addConfig(service, config) {
|
||||
if (
|
||||
config &&
|
||||
|
@ -395,7 +402,7 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
config.Name = service.Name;
|
||||
config.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceLabels);
|
||||
config.TaskTemplate.ContainerSpec.Env = ServiceHelper.translateEnvironmentVariablesToEnv(service.EnvironmentVariables);
|
||||
config.TaskTemplate.ContainerSpec.Env = envVarsUtils.convertToArrayOfStrings(service.EnvironmentVariables);
|
||||
config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceContainerLabels);
|
||||
|
||||
if ($scope.hasChanges(service, ['Image'])) {
|
||||
|
@ -625,7 +632,10 @@ angular.module('portainer.docker').controller('ServiceController', [
|
|||
function translateServiceArrays(service) {
|
||||
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
|
||||
service.ServiceConfigs = service.Configs ? service.Configs.map(ConfigHelper.flattenConfig) : [];
|
||||
service.EnvironmentVariables = ServiceHelper.translateEnvironmentVariables(service.Env);
|
||||
service.EnvironmentVariables = envVarsUtils
|
||||
.parseArrayOfStrings(service.Env)
|
||||
.map((v) => ({ ...v, added: true }))
|
||||
.sort((v1, v2) => (v1.name > v2.name ? 1 : -1));
|
||||
service.LogDriverOpts = ServiceHelper.translateLogDriverOptsToKeyValue(service.LogDriverOpts);
|
||||
service.ServiceLabels = LabelHelper.fromLabelHashToKeyValue(service.Labels);
|
||||
service.ServiceContainerLabels = LabelHelper.fromLabelHashToKeyValue(service.ContainerLabels);
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { parseDotEnvFile, convertToArrayOfStrings } from '@/portainer/helpers/env-vars';
|
||||
|
||||
export default class EnvironmentVariablesPanelController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.mode = 'simple';
|
||||
this.editorText = '';
|
||||
|
||||
this.switchEnvMode = this.switchEnvMode.bind(this);
|
||||
this.editorUpdate = this.editorUpdate.bind(this);
|
||||
this.handleSimpleChange = this.handleSimpleChange.bind(this);
|
||||
}
|
||||
|
||||
switchEnvMode() {
|
||||
if (this.mode === 'simple') {
|
||||
const editorText = convertToArrayOfStrings(this.ngModel).join('\n');
|
||||
|
||||
this.editorText = editorText;
|
||||
|
||||
this.mode = 'advanced';
|
||||
} else {
|
||||
this.mode = 'simple';
|
||||
}
|
||||
}
|
||||
|
||||
handleSimpleChange(value) {
|
||||
this.onChange(value);
|
||||
}
|
||||
|
||||
editorUpdate(cm) {
|
||||
this.editorText = cm.getValue();
|
||||
this.onChange(parseDotEnvFile(this.editorText));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.environment-variables-panel {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.environment-variables-panel--explanation {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.environment-variables-panel--advanced > * + * {
|
||||
margin-top: 5px;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<ng-form class="form-horizontal environment-variables-panel" name="$ctrl.envVarsForm">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 10px; margin-left: 15px; width: 98%;">
|
||||
Environment variables
|
||||
</div>
|
||||
<div class="col-sm-12 environment-variables-panel--explanation">
|
||||
{{::$ctrl.explanation}}
|
||||
</div>
|
||||
|
||||
<environment-variables-simple-mode
|
||||
ng-if="$ctrl.mode == 'simple'"
|
||||
ng-model="$ctrl.ngModel"
|
||||
on-change="($ctrl.handleSimpleChange)"
|
||||
on-switch-mode-click="($ctrl.switchEnvMode)"
|
||||
></environment-variables-simple-mode>
|
||||
|
||||
<div ng-if="$ctrl.mode == 'advanced'" class="environment-variables-panel--advanced">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive" ng-click="$ctrl.switchEnvMode()"> <i class="fa fa-list-ol space-right" aria-hidden="true"></i> Simple mode </a>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<i class="fa fa-info-circle blue-icon space-right" aria-hidden="true"></i>
|
||||
Switch to simple mode to define variables line by line, or load from .env file
|
||||
</div>
|
||||
<div class="form-group" style="margin-left: 1px;">
|
||||
<code-editor identifier="environment-variables-editor" placeholder="e.g. key=value" value="$ctrl.editorText" yml="false" on-change="($ctrl.editorUpdate)"></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
|
@ -0,0 +1,41 @@
|
|||
import { KEY_REGEX, VALUE_REGEX } from '@/portainer/helpers/env-vars';
|
||||
|
||||
class EnvironmentVariablesSimpleModeItemController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.KEY_REGEX = KEY_REGEX;
|
||||
this.VALUE_REGEX = VALUE_REGEX;
|
||||
}
|
||||
|
||||
onChangeName(name) {
|
||||
const fieldIsInvalid = typeof name === 'undefined';
|
||||
if (fieldIsInvalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onChange(this.index, { ...this.variable, name });
|
||||
}
|
||||
|
||||
onChangeValue(value) {
|
||||
const fieldIsInvalid = typeof value === 'undefined';
|
||||
if (fieldIsInvalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onChange(this.index, { ...this.variable, value });
|
||||
}
|
||||
|
||||
hasValue() {
|
||||
return typeof this.variable.value !== 'undefined';
|
||||
}
|
||||
|
||||
removeValue() {
|
||||
this.onChange(this.index, { name: this.variable.name });
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.formName = `variableForm${this.index}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default EnvironmentVariablesSimpleModeItemController;
|
|
@ -0,0 +1,63 @@
|
|||
<ng-form class="env-item" name="$ctrl.{{ $ctrl.formName }}">
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group input-group-sm env-item-key w-full">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
class="form-control"
|
||||
placeholder="e.g. FOO"
|
||||
ng-model="$ctrl.variable.name"
|
||||
ng-disabled="$ctrl.variable.added"
|
||||
ng-pattern="$ctrl.KEY_REGEX"
|
||||
ng-change="$ctrl.onChangeName($ctrl.variable.name)"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 5px;" ng-show="$ctrl[$ctrl.formName].name.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="$ctrl[$ctrl.formName].name.$error">
|
||||
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Name is required. </p>
|
||||
<p ng-message="pattern">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
This field must consist alphanumeric characters, '-' or '_', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-var', or 'MY_VAR123').
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="$ctrl.hasValue()" class="col-sm-6">
|
||||
<div class="w-full env-item-value">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.variable.value"
|
||||
placeholder="e.g. bar"
|
||||
ng-trim="false"
|
||||
ng-pattern="$ctrl.VALUE_REGEX"
|
||||
name="value"
|
||||
ng-change="$ctrl.onChangeValue($ctrl.variable.value)"
|
||||
/>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary" type="button" ng-click="$ctrl.removeValue()"> <i class="fa fa-minus" aria-hidden="true"></i> Remove value </button>
|
||||
</div>
|
||||
<div class="form-group" ng-show="$ctrl[$ctrl.formName].value.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="$ctrl[$ctrl.formName].value.$error">
|
||||
<p ng-message="required"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Value is required. </p>
|
||||
<p ng-message="pattern"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Value is invalid. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-primary" type="button" ng-if="!$ctrl.hasValue()" ng-click="$ctrl.onChangeValue('')">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> Add value
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemove($ctrl.index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-form>
|
|
@ -0,0 +1,17 @@
|
|||
import angular from 'angular';
|
||||
import controller from './environment-variables-simple-mode-item.controller.js';
|
||||
|
||||
export const environmentVariablesSimpleModeItem = {
|
||||
templateUrl: './environment-variables-simple-mode-item.html',
|
||||
controller,
|
||||
|
||||
bindings: {
|
||||
variable: '<',
|
||||
index: '<',
|
||||
|
||||
onChange: '<',
|
||||
onRemove: '<',
|
||||
},
|
||||
};
|
||||
|
||||
angular.module('portainer.app').component('environmentVariablesSimpleModeItem', environmentVariablesSimpleModeItem);
|
|
@ -0,0 +1,43 @@
|
|||
import { parseDotEnvFile } from '@/portainer/helpers/env-vars';
|
||||
|
||||
export default class EnvironmentVariablesSimpleModeController {
|
||||
/* @ngInject */
|
||||
constructor($async) {
|
||||
this.$async = $async;
|
||||
|
||||
this.onChangeVariable = this.onChangeVariable.bind(this);
|
||||
this.remove = this.remove.bind(this);
|
||||
}
|
||||
|
||||
add() {
|
||||
this.onChange([...this.ngModel, { name: '', value: '' }]);
|
||||
}
|
||||
|
||||
remove(index) {
|
||||
this.onChange(this.ngModel.filter((_, i) => i !== index));
|
||||
}
|
||||
|
||||
addFromFile(file) {
|
||||
return this.$async(async () => {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const text = await this.getTextFromFile(file);
|
||||
const parsed = parseDotEnvFile(text);
|
||||
this.onChange(this.ngModel.concat(parsed));
|
||||
});
|
||||
}
|
||||
|
||||
getTextFromFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const temporaryFileReader = new FileReader();
|
||||
temporaryFileReader.readAsText(file);
|
||||
temporaryFileReader.onload = (event) => resolve(event.target.result);
|
||||
temporaryFileReader.onerror = (error) => reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
onChangeVariable(index, variable) {
|
||||
this.onChange(this.ngModel.map((v, i) => (i !== index ? v : variable)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
.advanced-actions > * + * {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.environment-variables-simple-mode--actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.env-items-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.env-items-list > * + * {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.env-items-list .env-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.env-item .env-item-key {
|
||||
}
|
||||
|
||||
.env-item .env-item-value {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.env-item .env-item-value .input-group {
|
||||
flex: 1;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<div class="environment-variables-simple-mode">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive" ng-click="$ctrl.onSwitchModeClick()"> <i class="fa fa-list-ol space-right" aria-hidden="true"></i> Advanced mode </a>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<i class="fa fa-info-circle blue-icon space-right" aria-hidden="true"></i>
|
||||
Switch to advanced mode to copy & paste multiple variables
|
||||
</div>
|
||||
<div class="col-sm-12 environment-variables-simple-mode--actions">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="$ctrl.add()"> <i class="fa fa-plus-circle" aria-hidden="true"></i> Add an environment variable </button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-default"
|
||||
ngf-select="$ctrl.addFromFile($file)"
|
||||
ngf-accept="'.env'"
|
||||
ngf-pattern="'.env'"
|
||||
ngf-max-size="1MB"
|
||||
ngf-model-invalid="errorFile"
|
||||
>
|
||||
<i class="fa fa-file-upload" aria-hidden="true"></i> Load variables from .env file
|
||||
</button>
|
||||
<span class="space-left" ng-if="errorFile.$error == 'maxSize'">
|
||||
<i class="fa fa-times red-icon space-right" aria-hidden="true"></i>
|
||||
File too large! Try uploading a file smaller than 1MB
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12 form-inline env-items-list">
|
||||
<environment-variables-simple-mode-item
|
||||
ng-repeat="variable in $ctrl.ngModel"
|
||||
variable="variable"
|
||||
index="$index"
|
||||
on-change="($ctrl.onChangeVariable)"
|
||||
on-remove="($ctrl.remove)"
|
||||
></environment-variables-simple-mode-item>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import './environment-variables-simple-mode.css';
|
||||
|
||||
import controller from './environment-variables-simple-mode.controller';
|
||||
|
||||
angular.module('portainer.app').component('environmentVariablesSimpleMode', {
|
||||
templateUrl: './environment-variables-simple-mode.html',
|
||||
controller,
|
||||
bindings: {
|
||||
ngModel: '<',
|
||||
onSwitchModeClick: '<',
|
||||
onChange: '<',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import './environment-variables-panel.css';
|
||||
|
||||
import controller from './environment-variables-panel.controller.js';
|
||||
|
||||
angular.module('portainer.app').component('environmentVariablesPanel', {
|
||||
templateUrl: './environment-variables-panel.html',
|
||||
controller,
|
||||
bindings: {
|
||||
ngModel: '<',
|
||||
explanation: '@',
|
||||
onChange: '<',
|
||||
},
|
||||
});
|
|
@ -79,12 +79,20 @@ angular
|
|||
.filter('key', function () {
|
||||
'use strict';
|
||||
return function (pair, separator) {
|
||||
if (!pair.includes(separator)) {
|
||||
return pair;
|
||||
}
|
||||
|
||||
return pair.slice(0, pair.indexOf(separator));
|
||||
};
|
||||
})
|
||||
.filter('value', function () {
|
||||
'use strict';
|
||||
return function (pair, separator) {
|
||||
if (!pair.includes(separator)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return pair.slice(pair.indexOf(separator) + 1);
|
||||
};
|
||||
})
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
export const KEY_REGEX = /[a-zA-Z]([-_a-zA-Z0-9]*[a-zA-Z0-9])?/.source;
|
||||
|
||||
export const VALUE_REGEX = /(.*)?/.source;
|
||||
|
||||
const KEY_VALUE_REGEX = new RegExp(`^(${KEY_REGEX})\\s*=(${VALUE_REGEX})$`);
|
||||
const NEWLINES_REGEX = /\n|\r|\r\n/;
|
||||
|
||||
/**
|
||||
* @param {string} src the source of the .env file
|
||||
*
|
||||
* @returns {[{name: string, value: string}]} array of {name, value}
|
||||
*/
|
||||
export function parseDotEnvFile(src) {
|
||||
return parseArrayOfStrings(
|
||||
_.compact(src.split(NEWLINES_REGEX))
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => !v.startsWith('#'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* parses an array of name=value to array of {name, value}
|
||||
*
|
||||
* @param {[string]} array array of strings in format name=value
|
||||
*
|
||||
* @returns {[{name: string, value: string}]} array of {name, value}
|
||||
*/
|
||||
export function parseArrayOfStrings(array) {
|
||||
if (!array) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _.compact(
|
||||
array.map((variableString) => {
|
||||
if (!variableString.includes('=')) {
|
||||
return { name: variableString };
|
||||
}
|
||||
|
||||
const parsedKeyValArr = variableString.trim().match(KEY_VALUE_REGEX);
|
||||
if (parsedKeyValArr != null && parsedKeyValArr.length > 4) {
|
||||
return { name: parsedKeyValArr[1], value: parsedKeyValArr[3] || '' };
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
/**
|
||||
* converts an array of {name, value} to array of `name=value`, name is always defined
|
||||
*
|
||||
* @param {[{name, value}]} array array of {name, value}
|
||||
*
|
||||
* @returns {[string]} array of `name=value`
|
||||
*/
|
||||
export function convertToArrayOfStrings(array) {
|
||||
if (!array) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array.filter((variable) => variable.name).map(({ name, value }) => (value || value === '' ? `${name}=${value}` : name));
|
||||
}
|
|
@ -53,14 +53,6 @@ angular
|
|||
}
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function () {
|
||||
$scope.formValues.Env.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function (index) {
|
||||
$scope.formValues.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
|
@ -123,6 +115,11 @@ angular
|
|||
}
|
||||
}
|
||||
|
||||
$scope.handleEnvVarChange = handleEnvVarChange;
|
||||
function handleEnvVarChange(value) {
|
||||
$scope.formValues.Env = value;
|
||||
}
|
||||
|
||||
$scope.deployStack = function () {
|
||||
var name = $scope.formValues.Name;
|
||||
var method = $scope.state.Method;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<form class="form-horizontal" name="createStackForm">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="stack_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
|
@ -261,36 +261,8 @@
|
|||
</div>
|
||||
<!-- !custom-template -->
|
||||
<!-- environment-variables -->
|
||||
<div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Environment
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Environment variables</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in formValues.Env" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="variable.name" placeholder="e.g. FOO" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar" />
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
</div>
|
||||
<environment-variables-panel ng-model="formValues.Env" explanation="These values will be used as substitutions in the stack file" on-change="(handleEnvVarChange)">
|
||||
</environment-variables-panel>
|
||||
<!-- !environment-variables -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||
<!-- actions -->
|
||||
|
@ -303,6 +275,7 @@
|
|||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="state.actionInProgress
|
||||
|| !createStackForm.$valid
|
||||
|| (state.Method === 'editor' && (!formValues.StackFileContent || state.editorYamlValidationError))
|
||||
|| (state.Method === 'upload' && (!formValues.StackFile || state.uploadYamlValidationError))
|
||||
|| (state.Method === 'template' && (!formValues.StackFileContent || !selectedTemplate || state.editorYamlValidationError))
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
<!-- tab-file -->
|
||||
<uib-tab index="1" select="showEditor()" ng-if="!external">
|
||||
<uib-tab-heading> <i class="fa fa-pencil-alt space-right" aria-hidden="true"></i> Editor </uib-tab-heading>
|
||||
<form class="form-horizontal" ng-if="state.showEditorTab" style="margin-top: 10px;">
|
||||
<form class="form-horizontal" ng-if="state.showEditorTab" style="margin-top: 10px;" name="stackUpdateForm">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small" style="margin-bottom: 7px;" ng-if="stackType == 2 && composeSyntaxMaxVersion == 2">
|
||||
This stack will be deployed using the equivalent of <code>docker-compose</code>. Only Compose file format version <b>2</b> is supported at the moment.
|
||||
|
@ -152,34 +152,11 @@
|
|||
</div>
|
||||
<!-- environment-variables -->
|
||||
<div ng-if="stack && stack.Type === 1">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Environment
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;" authorization="PortainerStackUpdate">
|
||||
<label class="control-label text-left">Environment variables</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in stack.Env" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="variable.name" placeholder="e.g. FOO" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar" />
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)" authorization="PortainerStackUpdate">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<environment-variables-panel
|
||||
ng-model="formValues.Env"
|
||||
explanation="These values will be used as substitutions in the stack file"
|
||||
on-change="(handleEnvVarChange)"
|
||||
></environment-variables-panel>
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
<!-- options -->
|
||||
|
@ -207,7 +184,7 @@
|
|||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
ng-disabled="state.actionInProgress || stack.Status === 2 || !stackFileContent || orphaned"
|
||||
ng-disabled="state.actionInProgress || !stackUpdateForm.$valid || stack.Status === 2 || !stackFileContent || orphaned"
|
||||
ng-click="deployStack()"
|
||||
button-spinner="state.actionInProgress"
|
||||
>
|
||||
|
|
|
@ -61,6 +61,7 @@ angular.module('portainer.app').controller('StackController', [
|
|||
Prune: false,
|
||||
Endpoint: null,
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
Env: [],
|
||||
};
|
||||
|
||||
$window.onbeforeunload = () => {
|
||||
|
@ -69,9 +70,14 @@ angular.module('portainer.app').controller('StackController', [
|
|||
}
|
||||
};
|
||||
|
||||
$scope.handleEnvVarChange = handleEnvVarChange;
|
||||
function handleEnvVarChange(value) {
|
||||
$scope.formValues.Env = value;
|
||||
}
|
||||
|
||||
$scope.duplicateStack = function duplicateStack(name, endpointId) {
|
||||
var stack = $scope.stack;
|
||||
var env = FormHelper.removeInvalidEnvVars(stack.Env);
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
EndpointProvider.setEndpointID(endpointId);
|
||||
|
||||
return StackService.duplicateStack(name, $scope.stackFileContent, env, endpointId, stack.Type).then(onDuplicationSuccess).catch(notifyOnError);
|
||||
|
@ -195,7 +201,7 @@ angular.module('portainer.app').controller('StackController', [
|
|||
|
||||
$scope.deployStack = function () {
|
||||
var stackFile = $scope.stackFileContent;
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.stack.Env);
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
var prune = $scope.formValues.Prune;
|
||||
var stack = $scope.stack;
|
||||
|
||||
|
@ -223,14 +229,6 @@ angular.module('portainer.app').controller('StackController', [
|
|||
});
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function () {
|
||||
$scope.stack.Env.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function (index) {
|
||||
$scope.stack.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.editorUpdate = function (cm) {
|
||||
if ($scope.stackFileContent.replace(/(\r\n|\n|\r)/gm, '') !== cm.getValue().replace(/(\r\n|\n|\r)/gm, '')) {
|
||||
$scope.state.isEditorDirty = true;
|
||||
|
@ -301,6 +299,8 @@ angular.module('portainer.app').controller('StackController', [
|
|||
$scope.stack = stack;
|
||||
$scope.containerNames = ContainerHelper.getContainerNames(data.containers);
|
||||
|
||||
$scope.formValues.Env = $scope.stack.Env;
|
||||
|
||||
let resourcesPromise = Promise.resolve({});
|
||||
if (stack.Status === 1) {
|
||||
resourcesPromise = stack.Type === 1 ? retrieveSwarmStackResources(stack.Name, agentProxy) : retrieveComposeStackResources(stack.Name);
|
||||
|
|
Loading…
Reference in New Issue