mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 00:09:40 +02:00
feat(config): separate configmaps and secrets [EE-5078] (#9029)
This commit is contained in:
parent
4a331b71e1
commit
d7fc2046d7
102 changed files with 2845 additions and 665 deletions
|
@ -349,28 +349,28 @@
|
|||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region CONFIGURATIONS -->
|
||||
<!-- #region CONFIGMAPS -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 vertical-center">
|
||||
<label class="control-label !pt-0 text-left">ConfigMap or Secret</label>
|
||||
<label class="control-label !pt-0 text-left">ConfigMap</label>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted vertical-center" style="margin-top: 15px" ng-if="ctrl.formValues.Configurations.length">
|
||||
<div class="col-sm-12 small text-muted vertical-center" style="margin-top: 15px" ng-if="ctrl.formValues.ConfigMaps.length">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
Portainer will automatically expose all the keys of a ConfigMap or Secret as environment variables. This behavior can be overridden to filesystem mounts for
|
||||
each key via the override option.
|
||||
Portainer will automatically expose all the keys of a ConfigMap as environment variables. This behavior can be overridden to filesystem mounts for each key
|
||||
via the override option.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- config-element -->
|
||||
<div class="form-inline clearfix" ng-repeat="(index, config) in ctrl.formValues.Configurations">
|
||||
<div class="form-inline clearfix" ng-repeat="(index, config) in ctrl.formValues.ConfigMaps">
|
||||
<div class="col-sm-12 !p-0">
|
||||
<div class="input-group input-group-sm !mr-1">
|
||||
<span class="input-group-addon">name</span>
|
||||
<select
|
||||
class="form-control col-sm-6"
|
||||
ng-model="config.SelectedConfiguration"
|
||||
ng-options="c as c.Name for c in ctrl.configurations track by c.Name"
|
||||
ng-change="ctrl.resetConfiguration(index)"
|
||||
ng-options="c as c.Name for c in ctrl.configMaps track by c.Name"
|
||||
ng-change="ctrl.resetConfigMap(index)"
|
||||
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||
data-cy="k8sAppCreate-addConfigSelect_{{ $index }}"
|
||||
></select>
|
||||
|
@ -380,7 +380,7 @@
|
|||
<label
|
||||
class="btn btn-md btn-light vertical-center !ml-0"
|
||||
type="button"
|
||||
ng-click="ctrl.resetConfiguration(index)"
|
||||
ng-click="ctrl.resetConfigMap(index)"
|
||||
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||
data-cy="k8sAppCreate-configAutoButton_{{ $index }}"
|
||||
uib-btn-radio="false"
|
||||
|
@ -390,7 +390,7 @@
|
|||
</label>
|
||||
<label
|
||||
class="btn btn-md btn-light vertical-center !ml-0"
|
||||
ng-click="ctrl.overrideConfiguration(index)"
|
||||
ng-click="ctrl.overrideConfigMap(index)"
|
||||
ng-disabled="!config.SelectedConfiguration || ctrl.formValues.Containers.length > 1"
|
||||
data-cy="k8sAppCreate-configOverrideButton_{{ $index }}"
|
||||
uib-btn-radio="true"
|
||||
|
@ -403,7 +403,7 @@
|
|||
<button
|
||||
class="btn btn-md btn-dangerlight btn-only-icon vertical-center"
|
||||
type="button"
|
||||
ng-click="ctrl.removeConfiguration(index)"
|
||||
ng-click="ctrl.removeConfigMap(index)"
|
||||
ng-if="ctrl.formValues.Containers.length <= 1"
|
||||
data-cy="k8sAppCreate-configRemoveButton"
|
||||
>
|
||||
|
@ -414,8 +414,7 @@
|
|||
<div class="row clearfix" ng-if="config.SelectedConfiguration && !config.Overriden">
|
||||
<div class="col-sm-9 small text-muted !mt-2 !p-0">
|
||||
The following keys will be loaded from the <code>{{ config.SelectedConfiguration.Name }}</code>
|
||||
<span ng-if="config.SelectedConfiguration.Kind === 1">ConfigMap</span><span ng-if="config.SelectedConfiguration.Kind === 2">Secret</span> as environment
|
||||
variables:
|
||||
ConfigMap as environment variables:
|
||||
<span ng-repeat="(key, _) in config.SelectedConfiguration.Data">
|
||||
<code>{{ key }}</code
|
||||
>{{ $last ? '' : ', ' }}
|
||||
|
@ -451,7 +450,7 @@
|
|||
name="overriden_key_path_{{ index }}_{{ keyIndex }}"
|
||||
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||
required
|
||||
ng-change="ctrl.onChangeConfigurationPath()"
|
||||
ng-change="ctrl.onChangeConfigMapPath()"
|
||||
data-cy="k8sAppCreate-pathOnDiskInput"
|
||||
/>
|
||||
</div>
|
||||
|
@ -486,11 +485,154 @@
|
|||
<div class="col-sm-12 !p-0">
|
||||
<span
|
||||
class="btn btn-primary btn-sm btn btn-sm btn-light mb-2 !ml-0"
|
||||
ng-click="ctrl.addConfiguration()"
|
||||
ng-click="ctrl.addConfigMap()"
|
||||
ng-if="ctrl.formValues.Containers.length <= 1"
|
||||
data-cy="k8sAppCreate-addConfigButton"
|
||||
>
|
||||
<pr-icon icon="'plus'" size="'sm'"></pr-icon> Add ConfigMap and Secret
|
||||
<pr-icon icon="'plus'" size="'sm'"></pr-icon> Add ConfigMap
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- #region SECRETS -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 vertical-center">
|
||||
<label class="control-label !pt-0 text-left">Secret</label>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted vertical-center" style="margin-top: 15px" ng-if="ctrl.formValues.Configurations.length">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
Portainer will automatically expose all the keys of a Secret as environment variables. This behavior can be overridden to filesystem mounts for each key via
|
||||
the override option.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- config-element -->
|
||||
<div class="form-inline clearfix" ng-repeat="(index, config) in ctrl.formValues.Secrets">
|
||||
<div class="col-sm-12 !p-0">
|
||||
<div class="input-group input-group-sm !mr-1">
|
||||
<span class="input-group-addon">name</span>
|
||||
<select
|
||||
class="form-control col-sm-6"
|
||||
ng-model="config.SelectedConfiguration"
|
||||
ng-options="c as c.Name for c in ctrl.secrets track by c.Name"
|
||||
ng-change="ctrl.resetSecret(index)"
|
||||
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||
data-cy="k8sAppCreate-addSecretSelect_{{ $index }}"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div class="input-group btn-group btn-group-sm">
|
||||
<label
|
||||
class="btn btn-md btn-light vertical-center !ml-0"
|
||||
type="button"
|
||||
ng-click="ctrl.resetSecret(index)"
|
||||
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||
data-cy="k8sAppCreate-secretAutoButton_{{ $index }}"
|
||||
uib-btn-radio="false"
|
||||
ng-model="config.Overriden"
|
||||
>
|
||||
<pr-icon icon="'rotate-cw'" size="'md'"></pr-icon> Auto
|
||||
</label>
|
||||
<label
|
||||
class="btn btn-md btn-light vertical-center !ml-0"
|
||||
ng-click="ctrl.overrideSecret(index)"
|
||||
ng-disabled="!config.SelectedConfiguration || ctrl.formValues.Containers.length > 1"
|
||||
data-cy="k8sAppCreate-secretOverrideButton_{{ $index }}"
|
||||
uib-btn-radio="true"
|
||||
ng-model="config.Overriden"
|
||||
>
|
||||
<pr-icon icon="'list'" size="'md'"></pr-icon> Override
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-md btn-dangerlight btn-only-icon vertical-center"
|
||||
type="button"
|
||||
ng-click="ctrl.removeSecret(index)"
|
||||
ng-if="ctrl.formValues.Containers.length <= 1"
|
||||
data-cy="k8sAppCreate-secretRemoveButton"
|
||||
>
|
||||
<pr-icon icon="'trash-2'" size="'md'"></pr-icon>
|
||||
</button>
|
||||
</div>
|
||||
<!-- no-override -->
|
||||
<div class="row clearfix" ng-if="config.SelectedConfiguration && !config.Overriden">
|
||||
<div class="col-sm-9 small text-muted !mt-2 !p-0">
|
||||
The following keys will be loaded from the <code>{{ config.SelectedConfiguration.Name }}</code> Secret as environment variables:
|
||||
<span ng-repeat="(key, _) in config.SelectedConfiguration.Data">
|
||||
<code>{{ key }}</code
|
||||
>{{ $last ? '' : ', ' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !no-override -->
|
||||
|
||||
<!-- has-override -->
|
||||
<div class="col-sm-12 !mt-2 !mb-4 !p-0" ng-if="config.Overriden" ng-repeat="(keyIndex, overridenKey) in config.OverridenKeys" style="margin-top: 2px">
|
||||
<div class="input-group input-group-sm !mr-1">
|
||||
<span class="input-group-addon">key</span>
|
||||
<input type="text" class="form-control" ng-value="overridenKey.Key" disabled />
|
||||
</div>
|
||||
|
||||
<div class="input-group btn-group btn-group-sm !mr-1">
|
||||
<label class="btn btn-light" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT">
|
||||
<pr-icon icon="'list'"></pr-icon> Environment
|
||||
</label>
|
||||
<label class="btn btn-light" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
|
||||
<pr-icon icon="'file-text'"></pr-icon> Filesystem
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group !ml-0 !mr-0 !align-top" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon required">path on disk</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="overridenKey.Path"
|
||||
placeholder="/etc/myapp/conf.d"
|
||||
name="overriden_key_path_{{ index }}_{{ keyIndex }}"
|
||||
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||
required
|
||||
ng-change="ctrl.onChangeSecretPath()"
|
||||
data-cy="k8sAppCreate-secretPathOnDiskInput"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="small"
|
||||
ng-show="
|
||||
kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$invalid ||
|
||||
ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined
|
||||
"
|
||||
>
|
||||
<div class="text-warning" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
|
||||
<div
|
||||
ng-show="
|
||||
kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$invalid ||
|
||||
ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined
|
||||
"
|
||||
>
|
||||
<ng-messages for="kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$error">
|
||||
<p class="vertical-center" ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Path is required.</p>
|
||||
</ng-messages>
|
||||
<p class="vertical-center" ng-if="ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This path is already used.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !has-override -->
|
||||
</div>
|
||||
<!-- !config-element -->
|
||||
<div class="col-sm-12 !p-0">
|
||||
<span
|
||||
class="btn btn-primary btn-sm btn btn-sm btn-light mb-2 !ml-0"
|
||||
ng-click="ctrl.addSecret()"
|
||||
ng-if="ctrl.formValues.Containers.length <= 1"
|
||||
data-cy="k8sAppCreate-addSecretButton"
|
||||
>
|
||||
<pr-icon icon="'plus'" size="'sm'"></pr-icon> Add Secret
|
||||
</span>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
|
|
@ -4,6 +4,7 @@ import filesizeParser from 'filesize-parser';
|
|||
import * as JsonPatch from 'fast-json-patch';
|
||||
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
||||
import { getServices } from '@/react/kubernetes/networks/services/service';
|
||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
|
||||
|
||||
import {
|
||||
KubernetesApplicationDataAccessPolicies,
|
||||
|
@ -240,20 +241,20 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region CONFIGURATION UI MANAGEMENT */
|
||||
addConfiguration() {
|
||||
/* #region CONFIGMAP UI MANAGEMENT */
|
||||
addConfigMap() {
|
||||
let config = new KubernetesApplicationConfigurationFormValue();
|
||||
config.SelectedConfiguration = this.configurations[0];
|
||||
this.formValues.Configurations.push(config);
|
||||
config.SelectedConfiguration = this.configMaps[0];
|
||||
this.formValues.ConfigMaps.push(config);
|
||||
}
|
||||
|
||||
removeConfiguration(index) {
|
||||
this.formValues.Configurations.splice(index, 1);
|
||||
this.onChangeConfigurationPath();
|
||||
removeConfigMap(index) {
|
||||
this.formValues.ConfigMaps.splice(index, 1);
|
||||
this.onChangeConfigMapPath();
|
||||
}
|
||||
|
||||
overrideConfiguration(index) {
|
||||
const config = this.formValues.Configurations[index];
|
||||
overrideConfigMap(index) {
|
||||
const config = this.formValues.ConfigMaps[index];
|
||||
config.Overriden = true;
|
||||
config.OverridenKeys = _.map(_.keys(config.SelectedConfiguration.Data), (key) => {
|
||||
const res = new KubernetesApplicationConfigurationFormValueOverridenKey();
|
||||
|
@ -262,22 +263,22 @@ class KubernetesCreateApplicationController {
|
|||
});
|
||||
}
|
||||
|
||||
resetConfiguration(index) {
|
||||
const config = this.formValues.Configurations[index];
|
||||
resetConfigMap(index) {
|
||||
const config = this.formValues.ConfigMaps[index];
|
||||
config.Overriden = false;
|
||||
config.OverridenKeys = [];
|
||||
this.onChangeConfigurationPath();
|
||||
this.onChangeConfigMapPath();
|
||||
}
|
||||
|
||||
clearConfigurations() {
|
||||
this.formValues.Configurations = [];
|
||||
clearConfigMaps() {
|
||||
this.formValues.ConfigMaps = [];
|
||||
}
|
||||
|
||||
onChangeConfigurationPath() {
|
||||
onChangeConfigMapPath() {
|
||||
this.state.duplicates.configurationPaths.refs = [];
|
||||
|
||||
const paths = _.reduce(
|
||||
this.formValues.Configurations,
|
||||
this.formValues.ConfigMaps,
|
||||
(result, config) => {
|
||||
const uniqOverridenKeysPath = _.uniq(_.map(config.OverridenKeys, 'Path'));
|
||||
return _.concat(result, uniqOverridenKeysPath);
|
||||
|
@ -287,7 +288,7 @@ class KubernetesCreateApplicationController {
|
|||
|
||||
const duplicatePaths = KubernetesFormValidationHelper.getDuplicates(paths);
|
||||
|
||||
_.forEach(this.formValues.Configurations, (config, index) => {
|
||||
_.forEach(this.formValues.ConfigMaps, (config, index) => {
|
||||
_.forEach(config.OverridenKeys, (overridenKey, keyIndex) => {
|
||||
const findPath = _.find(duplicatePaths, (path) => path === overridenKey.Path);
|
||||
if (findPath) {
|
||||
|
@ -300,6 +301,66 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region SECRET UI MANAGEMENT */
|
||||
addSecret() {
|
||||
let secret = new KubernetesApplicationConfigurationFormValue();
|
||||
secret.SelectedConfiguration = this.secrets[0];
|
||||
this.formValues.Secrets.push(secret);
|
||||
}
|
||||
|
||||
removeSecret(index) {
|
||||
this.formValues.Secrets.splice(index, 1);
|
||||
this.onChangeSecretPath();
|
||||
}
|
||||
|
||||
overrideSecret(index) {
|
||||
const secret = this.formValues.Secrets[index];
|
||||
secret.Overriden = true;
|
||||
secret.OverridenKeys = _.map(_.keys(secret.SelectedConfiguration.Data), (key) => {
|
||||
const res = new KubernetesApplicationConfigurationFormValueOverridenKey();
|
||||
res.Key = key;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
resetSecret(index) {
|
||||
const secret = this.formValues.Secrets[index];
|
||||
secret.Overriden = false;
|
||||
secret.OverridenKeys = [];
|
||||
this.onChangeSecretPath();
|
||||
}
|
||||
|
||||
clearSecrets() {
|
||||
this.formValues.Secrets = [];
|
||||
}
|
||||
|
||||
onChangeSecretPath() {
|
||||
this.state.duplicates.configurationPaths.refs = [];
|
||||
|
||||
const paths = _.reduce(
|
||||
this.formValues.Secrets,
|
||||
(result, secret) => {
|
||||
const uniqOverridenKeysPath = _.uniq(_.map(secret.OverridenKeys, 'Path'));
|
||||
return _.concat(result, uniqOverridenKeysPath);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const duplicatePaths = KubernetesFormValidationHelper.getDuplicates(paths);
|
||||
|
||||
_.forEach(this.formValues.Secrets, (secret, index) => {
|
||||
_.forEach(secret.OverridenKeys, (overridenKey, keyIndex) => {
|
||||
const findPath = _.find(duplicatePaths, (path) => path === overridenKey.Path);
|
||||
if (findPath) {
|
||||
this.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] = findPath;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.state.duplicates.configurationPaths.hasRefs = Object.keys(this.state.duplicates.configurationPaths.refs).length > 0;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region ENVIRONMENT UI MANAGEMENT */
|
||||
addEnvironmentVariable() {
|
||||
this.formValues.EnvironmentVariables.push(new KubernetesApplicationEnvironmentVariableFormValue());
|
||||
|
@ -959,6 +1020,8 @@ class KubernetesCreateApplicationController {
|
|||
return this.$async(async () => {
|
||||
try {
|
||||
this.configurations = await this.KubernetesConfigurationService.get(namespace);
|
||||
this.configMaps = this.configurations.filter((configuration) => configuration.Kind === KubernetesConfigurationKinds.CONFIGMAP);
|
||||
this.secrets = this.configurations.filter((configuration) => configuration.Kind === KubernetesConfigurationKinds.SECRET);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve configurations');
|
||||
}
|
||||
|
@ -1029,7 +1092,8 @@ class KubernetesCreateApplicationController {
|
|||
}
|
||||
|
||||
resetFormValues() {
|
||||
this.clearConfigurations();
|
||||
this.clearConfigMaps();
|
||||
this.clearSecrets();
|
||||
this.resetPersistedFolders();
|
||||
this.resetPublishedPorts();
|
||||
}
|
||||
|
@ -1051,6 +1115,8 @@ class KubernetesCreateApplicationController {
|
|||
this.state.actionInProgress = true;
|
||||
try {
|
||||
this.formValues.ApplicationOwner = this.Authentication.getUserDetails().username;
|
||||
// combine the secrets and configmap form values when submitting the form
|
||||
this.formValues.Configurations = [...this.formValues.ConfigMaps, ...this.formValues.Secrets];
|
||||
_.remove(this.formValues.Configurations, (item) => item.SelectedConfiguration === undefined);
|
||||
await this.KubernetesApplicationService.create(this.formValues);
|
||||
this.Notifications.success('Request to deploy application successfully submitted', this.formValues.Name);
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'Create ConfigMap'"
|
||||
breadcrumbs="[{ label:'ConfigMaps and Secrets', link:'kubernetes.configurations', linkParams:{ tab: 'configmaps' } }, 'Create a ConfigMap']"
|
||||
reload="true"
|
||||
></page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="kubernetesConfigurationCreationForm" autocomplete="off">
|
||||
<!-- resource-pool -->
|
||||
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
|
||||
<label for="resource-pool-selector" class="col-sm-3 col-lg-2 control-label text-left">Namespace</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="resource-pool-selector"
|
||||
ng-model="ctrl.formValues.ResourcePool"
|
||||
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
||||
ng-change="ctrl.onResourcePoolSelectionChange()"
|
||||
data-cy="k8sConfigCreate-namespaceDropdown"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
This namespace has exhausted its resource capacity and you will not be able to deploy the configuration. Contact your administrator to expand the capacity of the
|
||||
namespace.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
You do not have access to any namespace. Contact your administrator to get access to a namespace.
|
||||
</div>
|
||||
</div>
|
||||
<!-- !resource-pool -->
|
||||
|
||||
<!-- name -->
|
||||
<div class="form-group">
|
||||
<label for="configuration_name" class="col-sm-3 col-lg-2 control-label required text-left">Name</label>
|
||||
<div class="col-sm-8 col-lg-9 mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="configuration_name"
|
||||
ng-model="ctrl.formValues.Name"
|
||||
ng-pattern="/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/"
|
||||
ng-change="ctrl.onChangeName()"
|
||||
placeholder="my-configmap"
|
||||
auto-focus
|
||||
required
|
||||
data-cy="k8sConfigCreate-nameInput"
|
||||
/>
|
||||
<div ng-show="kubernetesConfigurationCreationForm.configuration_name.$invalid || ctrl.state.alreadyExist">
|
||||
<div class="help-block small text-warning">
|
||||
<div ng-messages="kubernetesConfigurationCreationForm.configuration_name.$error">
|
||||
<p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This field is required.</p>
|
||||
<p ng-message="pattern" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric characters, '-' or
|
||||
'.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
|
||||
>
|
||||
</div>
|
||||
<p ng-if="ctrl.state.alreadyExist" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> A configuration with the same name already exists inside the selected namespace.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name -->
|
||||
|
||||
<div class="col-sm-12 !p-0">
|
||||
<annotations-be-teaser></annotations-be-teaser>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.formValues.ResourcePool">
|
||||
<kubernetes-configuration-data
|
||||
ng-if="ctrl.formValues"
|
||||
form-values="ctrl.formValues"
|
||||
is-docker-config="ctrl.state.isDockerConfig"
|
||||
is-valid="ctrl.state.isDataValid"
|
||||
on-change-validation="ctrl.isFormValid()"
|
||||
is-creation="true"
|
||||
is-editor-dirty="ctrl.state.isEditorDirty"
|
||||
></kubernetes-configuration-data>
|
||||
</div>
|
||||
|
||||
<!-- summary -->
|
||||
<kubernetes-summary-view
|
||||
ng-if="!(!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress)"
|
||||
form-values="ctrl.formValues"
|
||||
></kubernetes-summary-view>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 10px"> Actions </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm !ml-0"
|
||||
ng-disabled="!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress || !ctrl.formValues.ResourcePool"
|
||||
ng-click="ctrl.createConfiguration()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
data-cy="k8sConfigCreate-CreateConfigButton"
|
||||
>
|
||||
<span ng-hide="ctrl.state.actionInProgress">Create {{ ctrl.formValues.Kind | kubernetesConfigurationKindText }}</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Creation in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesCreateConfigMapView', {
|
||||
templateUrl: './createConfigMap.html',
|
||||
controller: 'KubernetesCreateConfigMapController',
|
||||
controllerAs: 'ctrl',
|
||||
});
|
|
@ -0,0 +1,170 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import { KubernetesConfigurationFormValues, KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { getServiceAccounts } from 'Kubernetes/rest/serviceAccount';
|
||||
import { typeOptions } from '@/react/kubernetes/configs/CreateView/options';
|
||||
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../../validation';
|
||||
|
||||
class KubernetesCreateConfigMapController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $scope, $window, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService, EndpointProvider) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$scope = $scope;
|
||||
this.$window = $window;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.Notifications = Notifications;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
|
||||
|
||||
this.typeOptions = typeOptions;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.createConfigurationAsync = this.createConfigurationAsync.bind(this);
|
||||
this.getConfigurationsAsync = this.getConfigurationsAsync.bind(this);
|
||||
this.onResourcePoolSelectionChangeAsync = this.onResourcePoolSelectionChangeAsync.bind(this);
|
||||
}
|
||||
|
||||
onChangeName() {
|
||||
const filteredConfigurations = _.filter(
|
||||
this.configurations,
|
||||
(config) => config.Namespace === this.formValues.ResourcePool.Namespace.Name && config.Kind === this.formValues.Kind
|
||||
);
|
||||
this.state.alreadyExist = _.find(filteredConfigurations, (config) => config.Name === this.formValues.Name) !== undefined;
|
||||
}
|
||||
|
||||
async onResourcePoolSelectionChangeAsync() {
|
||||
try {
|
||||
this.onChangeName();
|
||||
this.availableServiceAccounts = await getServiceAccounts(this.environmentId, this.formValues.ResourcePool.Namespace.Name);
|
||||
this.formValues.ServiceAccountName = this.availableServiceAccounts.length > 0 ? this.availableServiceAccounts[0].metadata.name : '';
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load service accounts');
|
||||
}
|
||||
}
|
||||
onResourcePoolSelectionChange() {
|
||||
this.$async(this.onResourcePoolSelectionChangeAsync);
|
||||
}
|
||||
|
||||
addRequiredKeysToForm(keys) {
|
||||
// remove data entries that have an empty value
|
||||
this.formValues.Data = this.formValues.Data.filter((entry) => entry.Value);
|
||||
|
||||
keys.forEach((key) => {
|
||||
// if the key doesn't exist on the form, add a new formValues.Data entry
|
||||
if (!this.formValues.Data.some((data) => data.Key === key)) {
|
||||
this.formValues.Data.push(new KubernetesConfigurationFormValuesEntry());
|
||||
const index = this.formValues.Data.length - 1;
|
||||
this.formValues.Data[index].Key = key;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isFormValid() {
|
||||
const [isValid] = isConfigurationFormValid(this.state.alreadyExist, this.state.isDataValid, this.formValues);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
async createConfigurationAsync() {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
this.formValues.ConfigurationOwner = this.Authentication.getUserDetails().username;
|
||||
if (!this.formValues.IsSimple) {
|
||||
this.formValues.Data = KubernetesConfigurationHelper.parseYaml(this.formValues);
|
||||
}
|
||||
|
||||
await this.KubernetesConfigurationService.create(this.formValues);
|
||||
this.Notifications.success('Success', `ConfigMap successfully created`);
|
||||
this.state.isEditorDirty = false;
|
||||
this.$state.go('kubernetes.configurations', { tab: 'configmaps' });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, `Unable to create ConfigMap`);
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
createConfiguration() {
|
||||
return this.$async(this.createConfigurationAsync);
|
||||
}
|
||||
|
||||
async getConfigurationsAsync() {
|
||||
try {
|
||||
this.configurations = await this.KubernetesConfigurationService.get();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve ConfigMaps');
|
||||
}
|
||||
}
|
||||
|
||||
getConfigurations() {
|
||||
return this.$async(this.getConfigurationsAsync);
|
||||
}
|
||||
|
||||
async uiCanExit() {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml && this.state.isEditorDirty) {
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
viewReady: false,
|
||||
alreadyExist: false,
|
||||
isDataValid: true,
|
||||
isEditorDirty: false,
|
||||
isDockerConfig: false,
|
||||
};
|
||||
|
||||
this.formValues = new KubernetesConfigurationFormValues();
|
||||
this.formValues.Kind = KubernetesConfigurationKinds.CONFIGMAP;
|
||||
this.formValues.Data = [new KubernetesConfigurationFormValuesEntry()];
|
||||
|
||||
try {
|
||||
const resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
this.resourcePools = _.filter(
|
||||
resourcePools,
|
||||
(resourcePool) => !KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name) && resourcePool.Namespace.Status === 'Active'
|
||||
);
|
||||
|
||||
this.formValues.ResourcePool = this.resourcePools[0];
|
||||
if (!this.formValues.ResourcePool) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.getConfigurations();
|
||||
|
||||
this.environmentId = this.EndpointProvider.endpointID();
|
||||
this.availableServiceAccounts = await getServiceAccounts(this.environmentId, this.resourcePools[0].Namespace.Name);
|
||||
this.formValues.ServiceAccountName = this.availableServiceAccounts.length > 0 ? this.availableServiceAccounts[0].metadata.name : '';
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
|
||||
this.$window.onbeforeunload = () => {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml && this.state.isEditorDirty) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
this.state.isEditorDirty = false;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesCreateConfigMapController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesCreateConfigMapController', KubernetesCreateConfigMapController);
|
|
@ -0,0 +1,173 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'ConfigMap details'"
|
||||
breadcrumbs="[
|
||||
{ label:'Namespaces', link:'kubernetes.resourcePools' },
|
||||
{
|
||||
label:ctrl.configuration.Namespace,
|
||||
link: 'kubernetes.resourcePools.resourcePool',
|
||||
linkParams:{ id: ctrl.configuration.Namespace }
|
||||
},
|
||||
{ label:'ConfigMaps and Secrets', link:'kubernetes.configurations', linkParams:{ tab: 'configmaps' } },
|
||||
ctrl.configuration.Name,
|
||||
]"
|
||||
reload="true"
|
||||
>
|
||||
</page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<uib-tabset active="ctrl.state.activeTab" justified="true" type="pills">
|
||||
<uib-tab index="0" classes="btn-sm" select="ctrl.selectTab(0)" data-cy="k8sConfigDetail-configTab">
|
||||
<uib-tab-heading>
|
||||
<pr-icon icon="'file-code'"></pr-icon>
|
||||
ConfigMap
|
||||
</uib-tab-heading>
|
||||
<div style="padding: 20px">
|
||||
<table class="table" data-cy="k8sConfigDetail-configTable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="w-[40%] !border-none !pl-0">Name</td>
|
||||
<td class="!border-none">
|
||||
{{ ctrl.configuration.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="!pl-0">Namespace</td>
|
||||
<td>
|
||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.configuration.Namespace })">{{ ctrl.configuration.Namespace }}</a>
|
||||
<span style="margin-left: 5px" class="label label-info image-tag" ng-if="ctrl.isSystemNamespace()">system</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</uib-tab>
|
||||
<uib-tab index="1" classes="btn-sm" select="ctrl.selectTab(1)" data-cy="k8sConfigDetail-eventsTab">
|
||||
<uib-tab-heading>
|
||||
<pr-icon icon="'history'"></pr-icon>
|
||||
Events
|
||||
<div ng-if="ctrl.hasEventWarnings()">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
{{ ctrl.state.eventWarningCount }} warning(s)
|
||||
</div>
|
||||
</uib-tab-heading>
|
||||
<kubernetes-events-datatable
|
||||
title-text="Events"
|
||||
title-icon="history"
|
||||
dataset="ctrl.events"
|
||||
table-key="kubernetes.configuration.events"
|
||||
order-by="Date"
|
||||
reverse-order="true"
|
||||
loading="ctrl.state.eventsLoading"
|
||||
refresh-callback="ctrl.getEvents"
|
||||
>
|
||||
</kubernetes-events-datatable>
|
||||
</uib-tab>
|
||||
<uib-tab index="2" ng-if="ctrl.configuration.Yaml" classes="btn-sm" select="ctrl.showEditor()" data-cy="k8sConfigDetail-yamlTab">
|
||||
<uib-tab-heading>
|
||||
<pr-icon icon="'code'"></pr-icon>
|
||||
YAML
|
||||
</uib-tab-heading>
|
||||
<div class="px-5 !pt-5" ng-if="ctrl.state.showEditorTab">
|
||||
<kubernetes-yaml-inspector key="configuration-yaml" data="ctrl.configuration.Yaml"></kubernetes-yaml-inspector>
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form ng-if="!ctrl.isSystemConfig()" class="form-horizontal" name="kubernetesConfigurationCreationForm" autocomplete="off">
|
||||
<div class="col-sm-12 !p-0">
|
||||
<annotations-be-teaser></annotations-be-teaser>
|
||||
</div>
|
||||
|
||||
<kubernetes-configuration-data
|
||||
ng-if="ctrl.formValues"
|
||||
form-values="ctrl.formValues"
|
||||
is-docker-config="false"
|
||||
is-valid="ctrl.state.isDataValid"
|
||||
on-change-validation="ctrl.isFormValid()"
|
||||
is-creation="false"
|
||||
is-editor-dirty="ctrl.state.isEditorDirty"
|
||||
></kubernetes-configuration-data>
|
||||
|
||||
<!-- summary -->
|
||||
<kubernetes-summary-view
|
||||
ng-if="!(!ctrl.isFormValid() || !kubernetesConfigurationCreationForm.$valid || ctrl.state.actionInProgress)"
|
||||
form-values="ctrl.formValues"
|
||||
></kubernetes-summary-view>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 10px"> Actions </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm !ml-0"
|
||||
ng-disabled="!ctrl.isFormValid() || !kubernetesConfigurationCreationForm.$valid || ctrl.state.actionInProgress"
|
||||
ng-click="ctrl.updateConfiguration()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
data-cy="k8sConfigDetail-updateConfig"
|
||||
>
|
||||
<span ng-hide="ctrl.state.actionInProgress">Update {{ ctrl.configuration.Kind | kubernetesConfigurationKindText }}</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Update in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
<div ng-if="ctrl.isSystemConfig()">
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 10px"> Data </div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="text-muted">
|
||||
<td style="width: 10%; border-top: none">Key</td>
|
||||
<td style="width: 90%; border-top: none">Value</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="item in ctrl.formValues.Data track by $index">
|
||||
<td>{{ item.Key }}</td>
|
||||
<td>
|
||||
<div style="white-space: pre-wrap">{{ item.Value }}</div>
|
||||
<div style="margin-top: 2px">
|
||||
<span class="btn btn-primary btn-xs" ng-click="ctrl.copyConfigurationValue($index)"> <pr-icon icon="'copy'" class-name="'mr-0.5'"></pr-icon>Copy </span>
|
||||
<span id="copyValueNotification_{{ $index }}" style="display: none; color: #23ae89; margin-left: 5px" class="small">
|
||||
<pr-icon icon="'check'"></pr-icon> copied
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="ctrl.configuration.Used">
|
||||
<div class="col-sm-12">
|
||||
<kubernetes-integrated-applications-datatable
|
||||
dataset="ctrl.configuration.Applications"
|
||||
table-key="kubernetes.configurations.applications"
|
||||
order-by="Name"
|
||||
refresh-callback="ctrl.getApplications"
|
||||
title-text="Applications using this ConfigMap"
|
||||
title-icon="svg-laptopcode"
|
||||
>
|
||||
</kubernetes-integrated-applications-datatable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesConfigurationView', {
|
||||
templateUrl: './configuration.html',
|
||||
controller: 'KubernetesConfigurationController',
|
||||
angular.module('portainer.kubernetes').component('kubernetesConfigMapView', {
|
||||
templateUrl: './configMap.html',
|
||||
controller: 'KubernetesConfigMapController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
|
@ -0,0 +1,304 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
|
||||
import { KubernetesConfigurationFormValues } from 'Kubernetes/models/configuration/formvalues';
|
||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
|
||||
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
|
||||
import KubernetesConfigurationConverter from 'Kubernetes/converters/configuration';
|
||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
import { pluralize } from '@/portainer/helpers/strings';
|
||||
|
||||
import { confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../../validation';
|
||||
|
||||
class KubernetesConfigMapController {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async,
|
||||
$state,
|
||||
$window,
|
||||
clipboard,
|
||||
EndpointProvider,
|
||||
Notifications,
|
||||
LocalStorage,
|
||||
Authentication,
|
||||
KubernetesConfigurationService,
|
||||
KubernetesConfigMapService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesEventService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$window = $window;
|
||||
this.clipboard = clipboard;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.Notifications = Notifications;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.Authentication = Authentication;
|
||||
this.KubernetesConfigurationService = KubernetesConfigurationService;
|
||||
this.KubernetesConfigMapService = KubernetesConfigMapService;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||
this.KubernetesEventService = KubernetesEventService;
|
||||
this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.getConfigurationAsync = this.getConfigurationAsync.bind(this);
|
||||
this.getEvents = this.getEvents.bind(this);
|
||||
this.getEventsAsync = this.getEventsAsync.bind(this);
|
||||
this.getApplications = this.getApplications.bind(this);
|
||||
this.getApplicationsAsync = this.getApplicationsAsync.bind(this);
|
||||
this.getConfigurationsAsync = this.getConfigurationsAsync.bind(this);
|
||||
this.updateConfiguration = this.updateConfiguration.bind(this);
|
||||
this.updateConfigurationAsync = this.updateConfigurationAsync.bind(this);
|
||||
}
|
||||
|
||||
isSystemNamespace() {
|
||||
return KubernetesNamespaceHelper.isSystemNamespace(this.configuration.Namespace);
|
||||
}
|
||||
|
||||
isSystemConfig() {
|
||||
return this.isSystemNamespace();
|
||||
}
|
||||
|
||||
selectTab(index) {
|
||||
this.LocalStorage.storeActiveTab('configuration', index);
|
||||
}
|
||||
|
||||
showEditor() {
|
||||
this.state.showEditorTab = true;
|
||||
this.selectTab(2);
|
||||
}
|
||||
|
||||
copyConfigurationValue(idx) {
|
||||
this.clipboard.copyText(this.formValues.Data[idx].Value);
|
||||
$('#copyValueNotification_' + idx)
|
||||
.show()
|
||||
.fadeOut(2500);
|
||||
}
|
||||
|
||||
isFormValid() {
|
||||
const [isValid] = isConfigurationFormValid(this.state.alreadyExist, this.state.isDataValid, this.formValues);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// TODO: refactor
|
||||
// It looks like we're still doing a create/delete process but we've decided to get rid of this
|
||||
// approach.
|
||||
async updateConfigurationAsync() {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
if (
|
||||
this.formValues.Kind !== this.configuration.Kind ||
|
||||
this.formValues.ResourcePool.Namespace.Name !== this.configuration.Namespace ||
|
||||
this.formValues.Name !== this.configuration.Name
|
||||
) {
|
||||
await this.KubernetesConfigurationService.create(this.formValues);
|
||||
await this.KubernetesConfigurationService.delete(this.configuration);
|
||||
this.Notifications.success('Success', `ConfigMap successfully updated`);
|
||||
this.$state.go(
|
||||
'kubernetes.configurations.configmap',
|
||||
{
|
||||
namespace: this.formValues.ResourcePool.Namespace.Name,
|
||||
name: this.formValues.Name,
|
||||
},
|
||||
{ reload: true }
|
||||
);
|
||||
} else {
|
||||
await this.KubernetesConfigurationService.update(this.formValues, this.configuration);
|
||||
this.Notifications.success('Success', `ConfigMap successfully updated`);
|
||||
this.$state.reload(this.$state.current);
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, `Unable to update ConfigMap`);
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateConfiguration() {
|
||||
if (this.configuration.Used) {
|
||||
confirmUpdate(
|
||||
`The changes will be propagated to ${this.configuration.Applications.length} running ${pluralize(
|
||||
this.configuration.Applications.length,
|
||||
'application'
|
||||
)}. Are you sure you want to update this ConfigMap?`,
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateConfigurationAsync);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return this.$async(this.updateConfigurationAsync);
|
||||
}
|
||||
}
|
||||
|
||||
async getConfigurationAsync() {
|
||||
try {
|
||||
this.state.configurationLoading = true;
|
||||
const name = this.$transition$.params().name;
|
||||
const namespace = this.$transition$.params().namespace;
|
||||
try {
|
||||
const configMap = await this.KubernetesConfigMapService.get(namespace, name);
|
||||
this.configuration = KubernetesConfigurationConverter.configMapToConfiguration(configMap);
|
||||
this.formValues.Data = configMap.Data;
|
||||
} catch (err) {
|
||||
if (err.status === 403) {
|
||||
this.$state.go('kubernetes.configurations', { tab: 'configmaps' });
|
||||
throw new Error('Not authorized to edit ConfigMap');
|
||||
}
|
||||
}
|
||||
|
||||
this.formValues.ResourcePool = _.find(this.resourcePools, (resourcePool) => resourcePool.Namespace.Name === this.configuration.Namespace);
|
||||
this.formValues.Id = this.configuration.Id;
|
||||
this.formValues.Name = this.configuration.Name;
|
||||
this.formValues.Type = this.configuration.Type;
|
||||
this.formValues.Kind = this.configuration.Kind;
|
||||
this.oldDataYaml = this.formValues.DataYaml;
|
||||
|
||||
return this.configuration;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve ConfigMap');
|
||||
} finally {
|
||||
this.state.configurationLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
getConfiguration() {
|
||||
return this.$async(this.getConfigurationAsync);
|
||||
}
|
||||
|
||||
async getApplicationsAsync(namespace) {
|
||||
try {
|
||||
this.state.applicationsLoading = true;
|
||||
const applications = await this.KubernetesApplicationService.get(namespace);
|
||||
this.configuration.Applications = KubernetesConfigurationHelper.getUsingApplications(this.configuration, applications);
|
||||
KubernetesConfigurationHelper.setConfigurationUsed(this.configuration);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve applications');
|
||||
} finally {
|
||||
this.state.applicationsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
getApplications(namespace) {
|
||||
return this.$async(this.getApplicationsAsync, namespace);
|
||||
}
|
||||
|
||||
hasEventWarnings() {
|
||||
return this.state.eventWarningCount;
|
||||
}
|
||||
|
||||
async getEventsAsync(namespace) {
|
||||
try {
|
||||
this.state.eventsLoading = true;
|
||||
this.events = await this.KubernetesEventService.get(namespace);
|
||||
this.events = _.filter(this.events, (event) => event.Involved.uid === this.configuration.Id);
|
||||
this.state.eventWarningCount = KubernetesEventHelper.warningCount(this.events);
|
||||
} catch (err) {
|
||||
this.Notifications('Failure', err, 'Unable to retrieve events');
|
||||
} finally {
|
||||
this.state.eventsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
getEvents(namespace) {
|
||||
return this.$async(this.getEventsAsync, namespace);
|
||||
}
|
||||
|
||||
async getConfigurationsAsync() {
|
||||
try {
|
||||
this.configurations = await this.KubernetesConfigurationService.get();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve configurations');
|
||||
}
|
||||
}
|
||||
|
||||
getConfigurations() {
|
||||
return this.$async(this.getConfigurationsAsync);
|
||||
}
|
||||
|
||||
tagUsedDataKeys() {
|
||||
const configName = this.$transition$.params().name;
|
||||
const usedDataKeys = _.uniq(
|
||||
this.configuration.Applications.flatMap((app) =>
|
||||
app.Env.filter((e) => e.valueFrom && e.valueFrom.configMapKeyRef && e.valueFrom.configMapKeyRef.name === configName).map((e) => e.name)
|
||||
)
|
||||
);
|
||||
|
||||
this.formValues.Data = this.formValues.Data.map((variable) => {
|
||||
if (!usedDataKeys.includes(variable.Key)) {
|
||||
return variable;
|
||||
}
|
||||
|
||||
return { ...variable, Used: true };
|
||||
});
|
||||
}
|
||||
|
||||
async uiCanExit() {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml !== this.oldDataYaml && this.state.isEditorDirty) {
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
try {
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
configurationLoading: true,
|
||||
applicationsLoading: true,
|
||||
eventsLoading: true,
|
||||
showEditorTab: false,
|
||||
viewReady: false,
|
||||
eventWarningCount: 0,
|
||||
activeTab: 0,
|
||||
currentName: this.$state.$current.name,
|
||||
isDataValid: true,
|
||||
isEditorDirty: false,
|
||||
};
|
||||
|
||||
this.state.activeTab = this.LocalStorage.getActiveTab('configuration');
|
||||
|
||||
this.formValues = new KubernetesConfigurationFormValues();
|
||||
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get();
|
||||
|
||||
const configuration = await this.getConfiguration();
|
||||
if (configuration) {
|
||||
await this.getApplications(this.configuration.Namespace);
|
||||
await this.getEvents(this.configuration.Namespace);
|
||||
await this.getConfigurations();
|
||||
}
|
||||
|
||||
this.tagUsedDataKeys();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
|
||||
this.$window.onbeforeunload = () => {
|
||||
if (!this.formValues.IsSimple && this.formValues.DataYaml !== this.oldDataYaml && this.state.isEditorDirty) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
if (this.state.currentName !== this.$state.$current.name) {
|
||||
this.LocalStorage.storeActiveTab('configuration', 0);
|
||||
}
|
||||
this.state.isEditorDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesConfigMapController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesConfigMapController', KubernetesConfigMapController);
|
|
@ -1,17 +0,0 @@
|
|||
<page-header ng-if="ctrl.state.viewReady" title="'ConfigMaps & Secrets list'" breadcrumbs="['ConfigMaps & Secrets']" reload="true"></page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<kubernetes-configurations-datatable
|
||||
dataset="ctrl.configurations"
|
||||
table-key="kubernetes.configurations"
|
||||
order-by="Name"
|
||||
refresh-callback="ctrl.refreshCallback"
|
||||
remove-action="ctrl.removeAction"
|
||||
></kubernetes-configurations-datatable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesConfigurationsView', {
|
||||
templateUrl: './configurations.html',
|
||||
controller: 'KubernetesConfigurationsController',
|
||||
controllerAs: 'ctrl',
|
||||
});
|
|
@ -1,239 +0,0 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'Create ConfigMap or Secret'"
|
||||
breadcrumbs="[{ label:'ConfigMaps and Secrets', link:'kubernetes.configurations' }, 'Create a ConfigMap or Secret']"
|
||||
reload="true"
|
||||
></page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="kubernetesConfigurationCreationForm" autocomplete="off">
|
||||
<!-- resource-pool -->
|
||||
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
|
||||
<label for="resource-pool-selector" class="col-sm-3 col-lg-2 control-label text-left">Namespace</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="resource-pool-selector"
|
||||
ng-model="ctrl.formValues.ResourcePool"
|
||||
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
||||
ng-change="ctrl.onResourcePoolSelectionChange()"
|
||||
data-cy="k8sConfigCreate-namespaceDropdown"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
This namespace has exhausted its resource capacity and you will not be able to deploy the configuration. Contact your administrator to expand the capacity of the
|
||||
namespace.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
You do not have access to any namespace. Contact your administrator to get access to a namespace.
|
||||
</div>
|
||||
</div>
|
||||
<!-- !resource-pool -->
|
||||
|
||||
<!-- name -->
|
||||
<div class="form-group">
|
||||
<label for="configuration_name" class="col-sm-3 col-lg-2 control-label required text-left">Name</label>
|
||||
<div class="col-sm-8 col-lg-9 mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="configuration_name"
|
||||
ng-model="ctrl.formValues.Name"
|
||||
ng-pattern="/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/"
|
||||
ng-change="ctrl.onChangeName()"
|
||||
placeholder="my-configmap-or-secret"
|
||||
auto-focus
|
||||
required
|
||||
data-cy="k8sConfigCreate-nameInput"
|
||||
/>
|
||||
<div ng-show="kubernetesConfigurationCreationForm.configuration_name.$invalid || ctrl.state.alreadyExist">
|
||||
<div class="help-block small text-warning">
|
||||
<div ng-messages="kubernetesConfigurationCreationForm.configuration_name.$error">
|
||||
<p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This field is required.</p>
|
||||
<p ng-message="pattern" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric characters, '-' or
|
||||
'.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
|
||||
>
|
||||
</div>
|
||||
<p ng-if="ctrl.state.alreadyExist" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> A configuration with the same name already exists inside the selected namespace.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name -->
|
||||
|
||||
<div class="col-sm-12 !p-0">
|
||||
<annotations-be-teaser></annotations-be-teaser>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 form-section-title"> Kind </div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted"> Select the kind of data that you want to save. </div>
|
||||
</div>
|
||||
|
||||
<box-selector options="ctrl.typeOptions" value="ctrl.formValues.Kind" on-change="(ctrl.onChangeKind)" radio-name="'Kind'" slim="true"> </box-selector>
|
||||
|
||||
<div ng-if="ctrl.formValues.Kind === ctrl.KubernetesConfigurationKinds.SECRET">
|
||||
<div class="col-sm-12 form-section-title"> Information </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>
|
||||
More information about types of secret can be found in the official
|
||||
<a class="hyperlink" href="https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" target="_blank">kubernetes documentation</a>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="configuration_data_type" class="col-sm-3 col-lg-2 control-label text-left">Type</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="configuration_data_type"
|
||||
ng-model="ctrl.formValues.Type"
|
||||
ng-options="value.value as value.name for (name, value) in ctrl.KubernetesSecretTypeOptions"
|
||||
ng-change="ctrl.onSecretTypeChange()"
|
||||
></select>
|
||||
|
||||
<div class="col-sm-3 col-lg-2"></div>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.SERVICEACCOUNTTOKEN.value" class="col-sm-12 small text-warning vertical-center pt-5">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span
|
||||
>You should only create a service account token Secret object if you can't use the TokenRequest API to obtain a token, and the security exposure of persisting
|
||||
a non-expiring token credential in a readable API object is acceptable to you. <br />See
|
||||
<a href="https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets" target="_blank">service account token secrets</a> in the
|
||||
kubernetes documentation.</span
|
||||
>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.DOCKERCFG.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>Ensure the Secret data field contains a <code>.dockercfg</code> key whose value is content of a legacy <code>~/.dockercfg</code> file.</span>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.DOCKERCONFIGJSON.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>Ensure the Secret data field contains a <code>.dockerconfigjson</code> key whose value is content of a <code>~/.docker/config.json</code> file.</span>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.TLS.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>Ensure the Secret data field contains a <code>tls.key</code> key and a <code>tls.crt</code> key.</span>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.BOOTSTRAPTOKEN.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span
|
||||
>Ensure the Secret data field contains a <code>token-id</code> key and a <code>token-secret</code> key. See
|
||||
<a href="https://kubernetes.io/docs/concepts/configuration/secret/#bootstrap-token-secrets" target="_blank">bootstrap token secrets</a> in the kubernetes
|
||||
documentation for optional keys.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.CUSTOM.value">
|
||||
<label for="configuration_data_customtype" class="col-sm-3 col-lg-2 control-label required text-left">Custom Type</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<input
|
||||
type="text"
|
||||
name="custom_type"
|
||||
class="form-control"
|
||||
id="configuration_data_customtype"
|
||||
ng-model="ctrl.formValues.customType"
|
||||
ng-pattern="/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/"
|
||||
required
|
||||
/>
|
||||
<div ng-show="kubernetesConfigurationCreationForm.custom_type.$invalid">
|
||||
<div class="help-block small text-warning">
|
||||
<div ng-messages="kubernetesConfigurationCreationForm.custom_type.$error">
|
||||
<p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This field is required.</p>
|
||||
<p ng-message="pattern" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric characters, '-'
|
||||
or '.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.SERVICEACCOUNTTOKEN.value">
|
||||
<label for="service_account" class="col-sm-3 col-lg-2 control-label required text-left">Service Account</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="service_account"
|
||||
ng-selected="$first"
|
||||
ng-model="ctrl.formValues.ServiceAccountName"
|
||||
ng-options="value.metadata.name as value.metadata.name for (name, value) in ctrl.availableServiceAccounts"
|
||||
data-cy="k8sConfigCreate-serviceAccountDropdown"
|
||||
ng-change="ctrl.onChangeServiceAccount()"
|
||||
required
|
||||
></select>
|
||||
<div class="help-block small text-warning" ng-messages="kubernetesConfigurationCreationForm.service_account.$error">
|
||||
<p class="vertical-center" ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<kubernetes-configuration-data
|
||||
ng-if="ctrl.formValues"
|
||||
form-values="ctrl.formValues"
|
||||
is-docker-config="ctrl.state.isDockerConfig"
|
||||
is-valid="ctrl.state.isDataValid"
|
||||
on-change-validation="ctrl.isFormValid()"
|
||||
is-creation="true"
|
||||
is-editor-dirty="ctrl.state.isEditorDirty"
|
||||
></kubernetes-configuration-data>
|
||||
|
||||
<div class="form-group" ng-if="ctrl.state.secretWarningMessage">
|
||||
<div class="col-sm-12 small text-warning vertical-center pt-5">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span>{{ ctrl.state.secretWarningMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- summary -->
|
||||
<kubernetes-summary-view
|
||||
ng-if="!(!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress)"
|
||||
form-values="ctrl.formValues"
|
||||
></kubernetes-summary-view>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 10px"> Actions </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm !ml-0"
|
||||
ng-disabled="!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress || !ctrl.formValues.ResourcePool"
|
||||
ng-click="ctrl.createConfiguration()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
data-cy="k8sConfigCreate-CreateConfigButton"
|
||||
>
|
||||
<span ng-hide="ctrl.state.actionInProgress">Create {{ ctrl.formValues.Kind | kubernetesConfigurationKindText }}</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Creation in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesCreateConfigurationView', {
|
||||
templateUrl: './createConfiguration.html',
|
||||
controller: 'KubernetesCreateConfigurationController',
|
||||
controllerAs: 'ctrl',
|
||||
});
|
|
@ -0,0 +1,229 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'Create Secret'"
|
||||
breadcrumbs="[{ label:'ConfigMaps and Secrets', link:'kubernetes.configurations', linkParams:{ tab: 'secrets' } }, 'Create a Secret']"
|
||||
reload="true"
|
||||
></page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="kubernetesConfigurationCreationForm" autocomplete="off">
|
||||
<!-- resource-pool -->
|
||||
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
|
||||
<label for="resource-pool-selector" class="col-sm-3 col-lg-2 control-label text-left">Namespace</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="resource-pool-selector"
|
||||
ng-model="ctrl.formValues.ResourcePool"
|
||||
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
||||
ng-change="ctrl.onResourcePoolSelectionChange()"
|
||||
data-cy="k8sConfigCreate-namespaceDropdown"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
This namespace has exhausted its resource capacity and you will not be able to deploy the configuration. Contact your administrator to expand the capacity of the
|
||||
namespace.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 small text-warning vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
You do not have access to any namespace. Contact your administrator to get access to a namespace.
|
||||
</div>
|
||||
</div>
|
||||
<!-- !resource-pool -->
|
||||
|
||||
<!-- name -->
|
||||
<div class="form-group">
|
||||
<label for="configuration_name" class="col-sm-3 col-lg-2 control-label required text-left">Name</label>
|
||||
<div class="col-sm-8 col-lg-9 mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="configuration_name"
|
||||
ng-model="ctrl.formValues.Name"
|
||||
ng-pattern="/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/"
|
||||
ng-change="ctrl.onChangeName()"
|
||||
placeholder="my-secret"
|
||||
auto-focus
|
||||
required
|
||||
data-cy="k8sConfigCreate-nameInput"
|
||||
/>
|
||||
<div ng-show="kubernetesConfigurationCreationForm.configuration_name.$invalid || ctrl.state.alreadyExist">
|
||||
<div class="help-block small text-warning">
|
||||
<div ng-messages="kubernetesConfigurationCreationForm.configuration_name.$error">
|
||||
<p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This field is required.</p>
|
||||
<p ng-message="pattern" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric characters, '-' or
|
||||
'.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
|
||||
>
|
||||
</div>
|
||||
<p ng-if="ctrl.state.alreadyExist" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> A configuration with the same name already exists inside the selected namespace.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name -->
|
||||
|
||||
<div class="col-sm-12 !p-0">
|
||||
<annotations-be-teaser></annotations-be-teaser>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.formValues.ResourcePool">
|
||||
<div class="col-sm-12 form-section-title"> Information </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>
|
||||
More information about types of secret can be found in the official
|
||||
<a class="hyperlink" href="https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" target="_blank">kubernetes documentation</a>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="configuration_data_type" class="col-sm-3 col-lg-2 control-label text-left">Type</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="configuration_data_type"
|
||||
ng-model="ctrl.formValues.Type"
|
||||
ng-options="value.value as value.name for (name, value) in ctrl.KubernetesSecretTypeOptions"
|
||||
ng-change="ctrl.onSecretTypeChange()"
|
||||
></select>
|
||||
|
||||
<div class="col-sm-3 col-lg-2"></div>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.SERVICEACCOUNTTOKEN.value" class="col-sm-12 small text-warning vertical-center pt-5">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span
|
||||
>You should only create a service account token Secret object if you can't use the TokenRequest API to obtain a token, and the security exposure of persisting a
|
||||
non-expiring token credential in a readable API object is acceptable to you. <br />See
|
||||
<a href="https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets" target="_blank">service account token secrets</a> in the
|
||||
kubernetes documentation.</span
|
||||
>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.DOCKERCFG.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>Ensure the Secret data field contains a <code>.dockercfg</code> key whose value is content of a legacy <code>~/.dockercfg</code> file.</span>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.DOCKERCONFIGJSON.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>Ensure the Secret data field contains a <code>.dockerconfigjson</code> key whose value is content of a <code>~/.docker/config.json</code> file.</span>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.TLS.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span>Ensure the Secret data field contains a <code>tls.key</code> key and a <code>tls.crt</code> key.</span>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.BOOTSTRAPTOKEN.value" class="col-sm-12 small text-muted vertical-center pt-5">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
<span
|
||||
>Ensure the Secret data field contains a <code>token-id</code> key and a <code>token-secret</code> key. See
|
||||
<a href="https://kubernetes.io/docs/concepts/configuration/secret/#bootstrap-token-secrets" target="_blank">bootstrap token secrets</a> in the kubernetes
|
||||
documentation for optional keys.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.CUSTOM.value">
|
||||
<label for="configuration_data_customtype" class="col-sm-3 col-lg-2 control-label required text-left">Custom Type</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<input
|
||||
type="text"
|
||||
name="custom_type"
|
||||
class="form-control"
|
||||
id="configuration_data_customtype"
|
||||
ng-model="ctrl.formValues.customType"
|
||||
ng-pattern="/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/"
|
||||
required
|
||||
/>
|
||||
<div ng-show="kubernetesConfigurationCreationForm.custom_type.$invalid">
|
||||
<div class="help-block small text-warning">
|
||||
<div ng-messages="kubernetesConfigurationCreationForm.custom_type.$error">
|
||||
<p ng-message="required" class="vertical-center"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This field is required.</p>
|
||||
<p ng-message="pattern" class="vertical-center"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'" class="vertical-center"></pr-icon> This field must consist of lower case alphanumeric characters, '-'
|
||||
or '.', and contain at most 63 characters, and must start and end with an alphanumeric character.</p
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="ctrl.formValues.Type === ctrl.KubernetesSecretTypeOptions.SERVICEACCOUNTTOKEN.value">
|
||||
<label for="service_account" class="col-sm-3 col-lg-2 control-label required text-left">Service Account</label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<select
|
||||
class="form-control"
|
||||
id="service_account"
|
||||
ng-selected="$first"
|
||||
ng-model="ctrl.formValues.ServiceAccountName"
|
||||
ng-options="value.metadata.name as value.metadata.name for (name, value) in ctrl.availableServiceAccounts"
|
||||
data-cy="k8sConfigCreate-serviceAccountDropdown"
|
||||
ng-change="ctrl.onChangeServiceAccount()"
|
||||
required
|
||||
></select>
|
||||
<div class="help-block small text-warning" ng-messages="kubernetesConfigurationCreationForm.service_account.$error">
|
||||
<p class="vertical-center" ng-message="required"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<kubernetes-configuration-data
|
||||
ng-if="ctrl.formValues"
|
||||
form-values="ctrl.formValues"
|
||||
is-docker-config="ctrl.state.isDockerConfig"
|
||||
is-valid="ctrl.state.isDataValid"
|
||||
on-change-validation="ctrl.isFormValid()"
|
||||
is-creation="true"
|
||||
is-editor-dirty="ctrl.state.isEditorDirty"
|
||||
></kubernetes-configuration-data>
|
||||
|
||||
<div class="form-group" ng-if="ctrl.state.secretWarningMessage">
|
||||
<div class="col-sm-12 small text-warning vertical-center pt-5">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span>{{ ctrl.state.secretWarningMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- summary -->
|
||||
<kubernetes-summary-view
|
||||
ng-if="!(!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress)"
|
||||
form-values="ctrl.formValues"
|
||||
></kubernetes-summary-view>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 10px"> Actions </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm !ml-0"
|
||||
ng-disabled="!kubernetesConfigurationCreationForm.$valid || !ctrl.isFormValid() || ctrl.state.actionInProgress || !ctrl.formValues.ResourcePool"
|
||||
ng-click="ctrl.createConfiguration()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
data-cy="k8sConfigCreate-CreateConfigButton"
|
||||
>
|
||||
<span ng-hide="ctrl.state.actionInProgress">Create {{ ctrl.formValues.Kind | kubernetesConfigurationKindText }}</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Creation in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesCreateSecretView', {
|
||||
templateUrl: './createSecret.html',
|
||||
controller: 'KubernetesCreateSecretController',
|
||||
controllerAs: 'ctrl',
|
||||
});
|
|
@ -8,9 +8,9 @@ import { getServiceAccounts } from 'Kubernetes/rest/serviceAccount';
|
|||
import { typeOptions } from '@/react/kubernetes/configs/CreateView/options';
|
||||
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../validation';
|
||||
import { isConfigurationFormValid } from '../../validation';
|
||||
|
||||
class KubernetesCreateConfigurationController {
|
||||
class KubernetesCreateSecretController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $scope, $window, Notifications, Authentication, KubernetesConfigurationService, KubernetesResourcePoolService, EndpointProvider) {
|
||||
this.$async = $async;
|
||||
|
@ -32,7 +32,6 @@ class KubernetesCreateConfigurationController {
|
|||
this.getConfigurationsAsync = this.getConfigurationsAsync.bind(this);
|
||||
this.onResourcePoolSelectionChangeAsync = this.onResourcePoolSelectionChangeAsync.bind(this);
|
||||
this.onSecretTypeChange = this.onSecretTypeChange.bind(this);
|
||||
this.onChangeKind = this.onChangeKind.bind(this);
|
||||
}
|
||||
|
||||
onChangeName() {
|
||||
|
@ -43,30 +42,13 @@ class KubernetesCreateConfigurationController {
|
|||
this.state.alreadyExist = _.find(filteredConfigurations, (config) => config.Name === this.formValues.Name) !== undefined;
|
||||
}
|
||||
|
||||
onChangeKind(value) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.formValues.Kind = value;
|
||||
this.onChangeName();
|
||||
// if there is no data field, add one
|
||||
if (this.formValues.Data.length === 0) {
|
||||
this.formValues.Data.push(new KubernetesConfigurationFormValuesEntry());
|
||||
}
|
||||
// if changing back to a secret, that is a service account token, remove the data field
|
||||
if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
|
||||
this.onSecretTypeChange();
|
||||
} else {
|
||||
this.isDockerConfig = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onResourcePoolSelectionChangeAsync() {
|
||||
try {
|
||||
this.onChangeName();
|
||||
this.availableServiceAccounts = await getServiceAccounts(this.environmentId, this.formValues.ResourcePool.Namespace.Name);
|
||||
this.formValues.ServiceAccountName = this.availableServiceAccounts.length > 0 ? this.availableServiceAccounts[0].metadata.name : '';
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable load service accounts');
|
||||
this.Notifications.error('Failure', err, 'Unable to load service accounts');
|
||||
}
|
||||
}
|
||||
onResourcePoolSelectionChange() {
|
||||
|
@ -140,11 +122,6 @@ class KubernetesCreateConfigurationController {
|
|||
}
|
||||
|
||||
async createConfigurationAsync() {
|
||||
let kind = 'ConfigMap';
|
||||
if (this.formValues.Kind === this.KubernetesConfigurationKinds.SECRET) {
|
||||
kind = 'Secret';
|
||||
}
|
||||
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
this.formValues.ConfigurationOwner = this.Authentication.getUserDetails().username;
|
||||
|
@ -154,11 +131,11 @@ class KubernetesCreateConfigurationController {
|
|||
|
||||
await this.KubernetesConfigurationService.create(this.formValues);
|
||||
|
||||
this.Notifications.success('Success', `${kind} successfully created`);
|
||||
this.Notifications.success('Success', `Secret successfully created`);
|
||||
this.state.isEditorDirty = false;
|
||||
this.$state.go('kubernetes.configurations');
|
||||
this.$state.go('kubernetes.configurations', { tab: 'secrets' });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, `Unable to create ${kind}`);
|
||||
this.Notifications.error('Failure', err, `Unable to create secret`);
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
|
@ -172,7 +149,7 @@ class KubernetesCreateConfigurationController {
|
|||
try {
|
||||
this.configurations = await this.KubernetesConfigurationService.get();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve ConfigMaps and Secrets');
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Secrets');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,6 +175,7 @@ class KubernetesCreateConfigurationController {
|
|||
};
|
||||
|
||||
this.formValues = new KubernetesConfigurationFormValues();
|
||||
this.formValues.Kind = KubernetesConfigurationKinds.SECRET;
|
||||
this.formValues.Data = [new KubernetesConfigurationFormValuesEntry()];
|
||||
|
||||
try {
|
||||
|
@ -235,5 +213,5 @@ class KubernetesCreateConfigurationController {
|
|||
}
|
||||
}
|
||||
|
||||
export default KubernetesCreateConfigurationController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesCreateConfigurationController', KubernetesCreateConfigurationController);
|
||||
export default KubernetesCreateSecretController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesCreateSecretController', KubernetesCreateSecretController);
|
|
@ -1,6 +1,6 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'ConfigMap or Secret details'"
|
||||
title="'Secret details'"
|
||||
breadcrumbs="[
|
||||
{ label:'Namespaces', link:'kubernetes.resourcePools' },
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
|||
link: 'kubernetes.resourcePools.resourcePool',
|
||||
linkParams:{ id: ctrl.configuration.Namespace }
|
||||
},
|
||||
{ label:'ConfigMaps and Secrets', link:'kubernetes.configurations' },
|
||||
{ label:'ConfigMaps and Secrets', link:'kubernetes.configurations', linkParams:{ tab: 'secrets' } },
|
||||
ctrl.configuration.Name,
|
||||
]"
|
||||
reload="true"
|
||||
|
@ -25,8 +25,8 @@
|
|||
<uib-tabset active="ctrl.state.activeTab" justified="true" type="pills">
|
||||
<uib-tab index="0" classes="btn-sm" select="ctrl.selectTab(0)" data-cy="k8sConfigDetail-configTab">
|
||||
<uib-tab-heading>
|
||||
<pr-icon icon="'file-code'"></pr-icon>
|
||||
Configuration
|
||||
<pr-icon icon="'lock'"></pr-icon>
|
||||
Secret
|
||||
</uib-tab-heading>
|
||||
<div style="padding: 20px">
|
||||
<table class="table" data-cy="k8sConfigDetail-configTable">
|
||||
|
@ -45,12 +45,6 @@
|
|||
<span style="margin-left: 5px" class="label label-info image-tag" ng-if="ctrl.isSystemNamespace()">system</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="!pl-0">Kind</td>
|
||||
<td>
|
||||
{{ ctrl.configuration.Kind | kubernetesConfigurationKindText }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="ctrl.secretTypeName">
|
||||
<td class="!pl-0">Secret Type</td>
|
||||
<td>
|
||||
|
@ -88,7 +82,7 @@
|
|||
YAML
|
||||
</uib-tab-heading>
|
||||
<div class="px-5 !pt-5" ng-if="ctrl.state.showEditorTab">
|
||||
<kubernetes-yaml-inspector key="configuration-yaml" data="ctrl.configuration.Yaml"> </kubernetes-yaml-inspector>
|
||||
<kubernetes-yaml-inspector key="configuration-yaml" data="ctrl.configuration.Yaml"></kubernetes-yaml-inspector>
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -184,7 +178,7 @@
|
|||
table-key="kubernetes.configurations.applications"
|
||||
order-by="Name"
|
||||
refresh-callback="ctrl.getApplications"
|
||||
title-text="Applications using this configuration"
|
||||
title-text="Applications using this secret"
|
||||
title-icon="svg-laptopcode"
|
||||
>
|
||||
</kubernetes-integrated-applications-datatable>
|
|
@ -0,0 +1,8 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesSecretView', {
|
||||
templateUrl: './secret.html',
|
||||
controller: 'KubernetesSecretController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
});
|
|
@ -8,10 +8,12 @@ import KubernetesConfigurationConverter from 'Kubernetes/converters/configuratio
|
|||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
import { confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../validation';
|
||||
import { pluralize } from '@/portainer/helpers/strings';
|
||||
|
||||
class KubernetesConfigurationController {
|
||||
import { confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { isConfigurationFormValid } from '../../validation';
|
||||
|
||||
class KubernetesSecretController {
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async,
|
||||
|
@ -21,7 +23,6 @@ class KubernetesConfigurationController {
|
|||
Notifications,
|
||||
LocalStorage,
|
||||
KubernetesConfigurationService,
|
||||
KubernetesConfigMapService,
|
||||
KubernetesSecretService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesApplicationService,
|
||||
|
@ -39,7 +40,6 @@ class KubernetesConfigurationController {
|
|||
this.KubernetesEventService = KubernetesEventService;
|
||||
this.KubernetesConfigurationKinds = KubernetesConfigurationKinds;
|
||||
this.KubernetesSecretTypeOptions = KubernetesSecretTypeOptions;
|
||||
this.KubernetesConfigMapService = KubernetesConfigMapService;
|
||||
this.KubernetesSecretService = KubernetesSecretService;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
|
@ -48,7 +48,6 @@ class KubernetesConfigurationController {
|
|||
this.getEventsAsync = this.getEventsAsync.bind(this);
|
||||
this.getApplications = this.getApplications.bind(this);
|
||||
this.getApplicationsAsync = this.getApplicationsAsync.bind(this);
|
||||
this.getConfigurationsAsync = this.getConfigurationsAsync.bind(this);
|
||||
this.updateConfiguration = this.updateConfiguration.bind(this);
|
||||
this.updateConfigurationAsync = this.updateConfigurationAsync.bind(this);
|
||||
}
|
||||
|
@ -87,11 +86,6 @@ class KubernetesConfigurationController {
|
|||
// It looks like we're still doing a create/delete process but we've decided to get rid of this
|
||||
// approach.
|
||||
async updateConfigurationAsync() {
|
||||
let kind = 'ConfigMap';
|
||||
if (this.formValues.Kind === KubernetesConfigurationKinds.SECRET) {
|
||||
kind = 'Secret';
|
||||
}
|
||||
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
if (
|
||||
|
@ -101,9 +95,9 @@ class KubernetesConfigurationController {
|
|||
) {
|
||||
await this.KubernetesConfigurationService.create(this.formValues);
|
||||
await this.KubernetesConfigurationService.delete(this.configuration);
|
||||
this.Notifications.success('Success', `${kind} successfully updated`);
|
||||
this.Notifications.success('Success', `Secret successfully updated`);
|
||||
this.$state.go(
|
||||
'kubernetes.configurations.configuration',
|
||||
'kubernetes.secrets.secret',
|
||||
{
|
||||
namespace: this.formValues.ResourcePool.Namespace.Name,
|
||||
name: this.formValues.Name,
|
||||
|
@ -112,11 +106,11 @@ class KubernetesConfigurationController {
|
|||
);
|
||||
} else {
|
||||
await this.KubernetesConfigurationService.update(this.formValues, this.configuration);
|
||||
this.Notifications.success('Success', `${kind} successfully updated`);
|
||||
this.Notifications.success('Success', `Secret successfully updated`);
|
||||
this.$state.reload(this.$state.current);
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, `Unable to update ${kind}`);
|
||||
this.Notifications.error('Failure', err, `Unable to update Secret`);
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
|
@ -124,10 +118,11 @@ class KubernetesConfigurationController {
|
|||
|
||||
updateConfiguration() {
|
||||
if (this.configuration.Used) {
|
||||
const plural = this.configuration.Applications.length > 1 ? 's' : '';
|
||||
const thisorthese = this.configuration.Applications.length > 1 ? 'these' : 'this';
|
||||
confirmUpdate(
|
||||
`The changes will be propagated to ${this.configuration.Applications.length} running application${plural}. Are you sure you want to update ${thisorthese} ConfigMap${plural} or Secret${plural}?`,
|
||||
`The changes will be propagated to ${this.configuration.Applications.length} running ${pluralize(
|
||||
this.configuration.Applications.length,
|
||||
'application'
|
||||
)}. Are you sure you want to update this Secret?`,
|
||||
(confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateConfigurationAsync);
|
||||
|
@ -144,18 +139,15 @@ class KubernetesConfigurationController {
|
|||
this.state.configurationLoading = true;
|
||||
const name = this.$transition$.params().name;
|
||||
const namespace = this.$transition$.params().namespace;
|
||||
const [configMap, secret] = await Promise.allSettled([this.KubernetesConfigMapService.get(namespace, name), this.KubernetesSecretService.get(namespace, name)]);
|
||||
if (secret.status === 'rejected' && secret.reason.err.status === 403) {
|
||||
this.$state.go('kubernetes.configurations');
|
||||
throw new Error('Not authorized to edit secret');
|
||||
}
|
||||
if (secret.status === 'fulfilled') {
|
||||
this.configuration = KubernetesConfigurationConverter.secretToConfiguration(secret.value);
|
||||
this.formValues.Data = secret.value.Data;
|
||||
// this.formValues.ServiceAccountName = secret.value.ServiceAccountName;
|
||||
} else {
|
||||
this.configuration = KubernetesConfigurationConverter.configMapToConfiguration(configMap.value);
|
||||
this.formValues.Data = configMap.value.Data;
|
||||
try {
|
||||
const secret = await this.KubernetesSecretService.get(namespace, name);
|
||||
this.configuration = KubernetesConfigurationConverter.secretToConfiguration(secret);
|
||||
this.formValues.Data = secret.Data;
|
||||
} catch (err) {
|
||||
if (err.status === 403) {
|
||||
this.$state.go('kubernetes.configurations', { tab: 'secrets' });
|
||||
throw new Error('Not authorized to edit secret');
|
||||
}
|
||||
}
|
||||
this.formValues.ResourcePool = _.find(this.resourcePools, (resourcePool) => resourcePool.Namespace.Name === this.configuration.Namespace);
|
||||
this.formValues.Id = this.configuration.Id;
|
||||
|
@ -166,7 +158,7 @@ class KubernetesConfigurationController {
|
|||
|
||||
return this.configuration;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve configuration');
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve secret');
|
||||
} finally {
|
||||
this.state.configurationLoading = false;
|
||||
}
|
||||
|
@ -214,18 +206,6 @@ class KubernetesConfigurationController {
|
|||
return this.$async(this.getEventsAsync, namespace);
|
||||
}
|
||||
|
||||
async getConfigurationsAsync() {
|
||||
try {
|
||||
this.configurations = await this.KubernetesConfigurationService.get();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve configurations');
|
||||
}
|
||||
}
|
||||
|
||||
getConfigurations() {
|
||||
return this.$async(this.getConfigurationsAsync);
|
||||
}
|
||||
|
||||
tagUsedDataKeys() {
|
||||
const configName = this.$transition$.params().name;
|
||||
const usedDataKeys = _.uniq(
|
||||
|
@ -276,7 +256,6 @@ class KubernetesConfigurationController {
|
|||
if (configuration) {
|
||||
await this.getApplications(this.configuration.Namespace);
|
||||
await this.getEvents(this.configuration.Namespace);
|
||||
await this.getConfigurations();
|
||||
}
|
||||
|
||||
// after loading the configuration, check if it is a docker config secret type
|
||||
|
@ -325,5 +304,5 @@ class KubernetesConfigurationController {
|
|||
}
|
||||
}
|
||||
|
||||
export default KubernetesConfigurationController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesConfigurationController', KubernetesConfigurationController);
|
||||
export default KubernetesSecretController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesSecretController', KubernetesSecretController);
|
|
@ -284,6 +284,11 @@ class KubernetesDeployController {
|
|||
this.Notifications.success('Success', 'Request to deploy manifest successfully submitted');
|
||||
this.state.isEditorDirty = false;
|
||||
|
||||
if (this.$state.params.referrer && this.$state.params.tab) {
|
||||
this.$state.go(this.$state.params.referrer, { tab: this.$state.params.tab });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$state.params.referrer) {
|
||||
this.$state.go(this.$state.params.referrer);
|
||||
return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue