mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 12:25:22 +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
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
|||
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);
|
Loading…
Add table
Add a link
Reference in a new issue