mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
refactor(namespace): migrate namespace edit to react [r8s-125] (#38)
This commit is contained in:
parent
40c7742e46
commit
ce7e0d8d60
108 changed files with 3183 additions and 2194 deletions
|
@ -478,10 +478,10 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
|||
|
||||
const resourcePool = {
|
||||
name: 'kubernetes.resourcePools.resourcePool',
|
||||
url: '/:id',
|
||||
url: '/:id?tab',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'kubernetesResourcePoolView',
|
||||
component: 'namespaceView',
|
||||
},
|
||||
},
|
||||
data: {
|
||||
|
|
|
@ -17,6 +17,6 @@ export const clusterManagementModule = angular
|
|||
'resourceEventsDatatable',
|
||||
r2a(
|
||||
withUIRouter(withReactQuery(withCurrentUser(ResourceEventsDatatable))),
|
||||
['resourceId', 'storageKey', 'namespace']
|
||||
['resourceId', 'storageKey', 'namespace', 'noWidget']
|
||||
)
|
||||
).name;
|
||||
|
|
|
@ -4,7 +4,6 @@ import { r2a } from '@/react-tools/react2angular';
|
|||
import { IngressClassDatatableAngular } from '@/react/kubernetes/cluster/ingressClass/IngressClassDatatable/IngressClassDatatableAngular';
|
||||
import { NamespacesSelector } from '@/react/kubernetes/cluster/RegistryAccessView/NamespacesSelector';
|
||||
import { NamespaceAccessUsersSelector } from '@/react/kubernetes/namespaces/AccessView/NamespaceAccessUsersSelector';
|
||||
import { RegistriesSelector } from '@/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesSelector';
|
||||
import { KubeServicesForm } from '@/react/kubernetes/applications/CreateView/application-services/KubeServicesForm';
|
||||
import { kubeServicesValidation } from '@/react/kubernetes/applications/CreateView/application-services/kubeServicesValidation';
|
||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
|
@ -106,15 +105,6 @@ export const ngModule = angular
|
|||
'name',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'createNamespaceRegistriesSelector',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(RegistriesSelector))), [
|
||||
'inputId',
|
||||
'onChange',
|
||||
'options',
|
||||
'value',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'kubeNodesDatatable',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(NodesDatatable))), [])
|
||||
|
|
|
@ -3,26 +3,11 @@ import angular from 'angular';
|
|||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
import { NamespacesDatatable } from '@/react/kubernetes/namespaces/ListView/NamespacesDatatable';
|
||||
import { NamespaceAppsDatatable } from '@/react/kubernetes/namespaces/ItemView/NamespaceAppsDatatable';
|
||||
import { AccessDatatable } from '@/react/kubernetes/namespaces/AccessView/AccessDatatable/AccessDatatable';
|
||||
|
||||
export const namespacesModule = angular
|
||||
.module('portainer.kubernetes.react.components.namespaces', [])
|
||||
.component(
|
||||
'kubernetesNamespacesDatatable',
|
||||
r2a(withUIRouter(withCurrentUser(NamespacesDatatable)), [])
|
||||
)
|
||||
.component(
|
||||
'kubernetesNamespaceApplicationsDatatable',
|
||||
r2a(withUIRouter(withCurrentUser(NamespaceAppsDatatable)), [
|
||||
'dataset',
|
||||
'isLoading',
|
||||
'onRefresh',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'namespaceAccessDatatable',
|
||||
r2a(withUIRouter(withReactQuery(AccessDatatable)), [])
|
||||
).name;
|
||||
|
|
|
@ -19,6 +19,7 @@ import { ServiceAccountsView } from '@/react/kubernetes/more-resources/ServiceAc
|
|||
import { ClusterRolesView } from '@/react/kubernetes/more-resources/ClusterRolesView';
|
||||
import { RolesView } from '@/react/kubernetes/more-resources/RolesView';
|
||||
import { VolumesView } from '@/react/kubernetes/volumes/ListView/VolumesView';
|
||||
import { NamespaceView } from '@/react/kubernetes/namespaces/ItemView/NamespaceView';
|
||||
import { AccessView } from '@/react/kubernetes/namespaces/AccessView/AccessView';
|
||||
|
||||
export const viewsModule = angular
|
||||
|
@ -27,6 +28,10 @@ export const viewsModule = angular
|
|||
'kubernetesCreateNamespaceView',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(CreateNamespaceView))), [])
|
||||
)
|
||||
.component(
|
||||
'namespaceView',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(NamespaceView))), [])
|
||||
)
|
||||
.component(
|
||||
'kubernetesNamespacesView',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(NamespacesView))), [])
|
||||
|
|
|
@ -3,7 +3,7 @@ import _ from 'lodash-es';
|
|||
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 { getServices } from '@/react/kubernetes/services/useNamespaceServices';
|
||||
import { KubernetesConfigurationKinds } from 'Kubernetes/models/configuration/models';
|
||||
import { getGlobalDeploymentOptions } from '@/react/portainer/settings/settings.service';
|
||||
|
||||
|
@ -25,11 +25,11 @@ import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
|||
import { KubernetesNodeHelper } from 'Kubernetes/node/helper';
|
||||
import { updateIngress, getIngresses } from '@/react/kubernetes/ingresses/service';
|
||||
import { confirmUpdateAppIngress } from '@/react/kubernetes/applications/CreateView/UpdateIngressPrompt';
|
||||
import { KUBE_STACK_NAME_VALIDATION_REGEX } from '@/react/kubernetes/DeployView/StackName/constants';
|
||||
import { isVolumeUsed } from '@/react/kubernetes/volumes/utils';
|
||||
import { confirm, confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { KUBE_STACK_NAME_VALIDATION_REGEX } from '@/react/kubernetes/DeployView/StackName/constants';
|
||||
import { isVolumeUsed } from '@/react/kubernetes/volumes/utils';
|
||||
|
||||
class KubernetesCreateApplicationController {
|
||||
/* #region CONSTRUCTOR */
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import controller from './storage-class-switch.controller.js';
|
||||
|
||||
export const storageClassSwitch = {
|
||||
templateUrl: './storage-class-switch.html',
|
||||
controller,
|
||||
bindings: {
|
||||
value: '<',
|
||||
onChange: '<',
|
||||
name: '<',
|
||||
},
|
||||
};
|
||||
|
||||
angular.module('portainer.kubernetes').component('storageClassSwitch', storageClassSwitch);
|
|
@ -1,16 +0,0 @@
|
|||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
class StorageClassSwitchController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.featureId = FeatureId.K8S_RESOURCE_POOL_STORAGE_QUOTA;
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
handleChange(value) {
|
||||
this.onChange(this.name, value);
|
||||
}
|
||||
}
|
||||
|
||||
export default StorageClassSwitchController;
|
|
@ -1,13 +0,0 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
data-cy="'k8sNamespaceCreate-enableQuotaToggle'"
|
||||
label="'Enable quota'"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
name="'k8s-resourcepool-storagequota'"
|
||||
feature-id="$ctrl.featureId"
|
||||
checked="$ctrl.value"
|
||||
on-change="($ctrl.handleChange)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
|
@ -1,278 +0,0 @@
|
|||
<page-header
|
||||
ng-if="$ctrl.state.viewReady"
|
||||
title="'Create a namespace'"
|
||||
breadcrumbs="[{ label:'Namespaces', link:'kubernetes.resourcePools' }, 'Create a namespace']"
|
||||
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-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" autocomplete="off" name="resourcePoolCreationForm">
|
||||
<!-- #region NAME INPUT -->
|
||||
<div class="form-group">
|
||||
<label for="pool_name" class="col-sm-3 col-lg-2 control-label required text-left">Name</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="pool_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-project"
|
||||
data-cy="k8sNamespaceCreate-namespaceNameInput"
|
||||
required
|
||||
auto-focus
|
||||
/>
|
||||
<span class="help-block">
|
||||
<div class="form-group" ng-show="resourcePoolCreationForm.pool_name.$invalid || $ctrl.state.isAlreadyExist || $ctrl.state.hasPrefixKube">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="resourcePoolCreationForm.pool_name.$error">
|
||||
<p class="vertical-center" ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>This field is required.</p>
|
||||
<p class="vertical-center" ng-message="pattern"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'"></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 class="vertical-center" ng-if="$ctrl.state.hasPrefixKube"
|
||||
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Prefix "kube-" is reserved for Kubernetes system namespaces.</p
|
||||
>
|
||||
<p class="vertical-center" ng-if="$ctrl.state.isAlreadyExist">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> A namespace with the same name already exists.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 !p-0">
|
||||
<annotations-be-teaser></annotations-be-teaser>
|
||||
</div>
|
||||
|
||||
<!-- #endregion -->
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Quota </div>
|
||||
<!-- #region QUOTA -->
|
||||
<!-- quotas-switch -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<p class="vertical-center">
|
||||
<pr-icon class="vertical-center" icon="'info'" mode="'primary'"></pr-icon>
|
||||
A namespace segments the underlying physical Kubernetes cluster into smaller virtual clusters. You should assign a capped limit of resources to this namespace or
|
||||
disable for the safe operation of your platform.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
data-cy="'k8sNamespaceCreate-resourceAssignmentToggle'"
|
||||
label="'Resource assignment'"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
name="'k8s-resourcepool-resourcequota'"
|
||||
checked="$ctrl.formValues.HasQuota"
|
||||
on-change="($ctrl.onToggleResourceQuota)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !quotas-switch -->
|
||||
<div ng-if="$ctrl.formValues.HasQuota">
|
||||
<div class="col-sm-12 form-section-title"> Resource limits </div>
|
||||
<div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 small text-warning" ng-switch on="$ctrl.formValues.HasQuota && !$ctrl.isQuotaValid()">
|
||||
<p class="vertical-center mb-0" ng-switch-when="true"
|
||||
><pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'"></pr-icon> At least a single limit must be set for the quota to be valid.
|
||||
</p>
|
||||
<p class="vertical-center mb-0" ng-switch-default></p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- memory-limit-input -->
|
||||
<div class="form-group !mb-0 flex flex-row">
|
||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left"> Memory limit (MB) </label>
|
||||
<div class="col-xs-6">
|
||||
<por-slider
|
||||
min="$ctrl.defaults.MemoryLimit"
|
||||
max="$ctrl.state.sliderMaxMemory"
|
||||
step="128"
|
||||
ng-if="$ctrl.state.sliderMaxMemory"
|
||||
value="$ctrl.formValues.MemoryLimit"
|
||||
on-change="($ctrl.handleMemoryLimitChange)"
|
||||
visible-tooltip="true"
|
||||
data-cy="k8sNamespaceCreate-memoryLimitSlider"
|
||||
></por-slider>
|
||||
</div>
|
||||
<div class="col-sm-2 vertical-center pt-6">
|
||||
<input
|
||||
name="memory_limit"
|
||||
type="number"
|
||||
min="{{ $ctrl.defaults.MemoryLimit }}"
|
||||
max="{{ $ctrl.state.sliderMaxMemory }}"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.formValues.MemoryLimit"
|
||||
id="memory-limit"
|
||||
data-cy="k8sNamespaceCreate-memoryLimitInput"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full flex-row">
|
||||
<span class="col-sm-3 col-lg-2"></span>
|
||||
<span class="help-block col-sm-9 col-lg-10">
|
||||
<div ng-show="resourcePoolCreationForm.memory_limit.$invalid">
|
||||
<div class="small text-warning">
|
||||
<div ng-messages="resourcePoolCreationForm.pool_name.$error">
|
||||
<p class="vertical-center"
|
||||
><pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'"></pr-icon> Value must be between {{ $ctrl.defaults.MemoryLimit }} and
|
||||
{{ $ctrl.state.sliderMaxMemory }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<!-- !memory-limit-input -->
|
||||
<!-- cpu-limit-input -->
|
||||
<div class="form-group flex flex-row">
|
||||
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left"> CPU limit </label>
|
||||
<div class="col-xs-8">
|
||||
<por-slider
|
||||
min="$ctrl.defaults.CpuLimit"
|
||||
max="$ctrl.state.sliderMaxCpu"
|
||||
step="0.1"
|
||||
ng-if="$ctrl.state.sliderMaxCpu"
|
||||
value="$ctrl.formValues.CpuLimit"
|
||||
on-change="($ctrl.handleCpuLimitChange)"
|
||||
data-cy="k8sNamespaceCreate-cpuLimitSlider"
|
||||
visible-tooltip="true"
|
||||
></por-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !cpu-limit-input -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region LOAD-BALANCERS -->
|
||||
<div class="col-sm-12 form-section-title"> Load balancers </div>
|
||||
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'" class="vertical-center"></pr-icon>
|
||||
You can set a quota on the amount of external load balancers that can be created inside this namespace. Set this quota to 0 to effectively disable the use of load
|
||||
balancers in this namespace.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
data-cy="'k8sNamespaceCreate-loadBalancerQuotaToggle'"
|
||||
label="'Load Balancer quota'"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
name="'k8s-resourcepool-lbquota'"
|
||||
feature-id="$ctrl.LBQuotaFeatureId"
|
||||
checked="$ctrl.formValues.UseLoadBalancersQuota"
|
||||
on-change="($ctrl.onToggleLoadBalancerQuota)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<div ng-if="$ctrl.state.ingressAvailabilityPerNamespace">
|
||||
<!-- #region INGRESSES -->
|
||||
<div class="col-sm-12 form-section-title"> Networking </div>
|
||||
<ingress-class-datatable
|
||||
ng-if="$ctrl.state.ingressAvailabilityPerNamespace"
|
||||
on-change-controllers="($ctrl.onChangeIngressControllerAvailability)"
|
||||
ingress-controllers="$ctrl.ingressControllers"
|
||||
initial-ingress-controllers="$ctrl.initialIngressControllers"
|
||||
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
|
||||
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"
|
||||
view="'namespace'"
|
||||
></ingress-class-datatable>
|
||||
<!-- #endregion -->
|
||||
</div>
|
||||
|
||||
<!-- #region REGISTRIES -->
|
||||
<div class="col-sm-12 form-section-title"> Registries </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<p class="vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
Define which registries can be used by users who have access to this namespace.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label !pt-0 text-left" for="registries-selector"> Select registries </label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<span class="small text-muted" ng-if="!$ctrl.registries.length && $ctrl.state.isAdmin">
|
||||
No registries available. Head over to the <a ui-sref="portainer.registries">registry view</a> to define a container registry.
|
||||
</span>
|
||||
<span class="small text-muted" ng-if="!$ctrl.registries.length && !$ctrl.state.isAdmin">
|
||||
No registries available. Contact your administrator to create a container registry.
|
||||
</span>
|
||||
<create-namespace-registries-selector
|
||||
input-id="'registries-selector'"
|
||||
value="$ctrl.formValues.Registries"
|
||||
on-change="($ctrl.onRegistriesChange)"
|
||||
options="$ctrl.registries"
|
||||
>
|
||||
</create-namespace-registries-selector>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region STORAGES -->
|
||||
<div class="col-sm-12 form-section-title"> Storage </div>
|
||||
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
Quotas can be set on each storage option to prevent users from exceeding a specific threshold when deploying applications. You can set a quota to 0 to effectively
|
||||
prevent the usage of a specific storage option inside this namespace.
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title vertical-center">
|
||||
<pr-icon icon="'svg-route'"></pr-icon>
|
||||
standard
|
||||
</div>
|
||||
|
||||
<storage-class-switch value="sc.Selected" name="sc.Name" on-change="(ctrl.onToggleStorageQuota)" authorization="K8sResourcePoolDetailsW"></storage-class-switch>
|
||||
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- summary -->
|
||||
<kubernetes-summary-view ng-if="resourcePoolCreationForm.$valid && !$ctrl.isCreateButtonDisabled()" form-values="$ctrl.formValues"></kubernetes-summary-view>
|
||||
<!-- !summary -->
|
||||
|
||||
<div class="col-sm-12 form-section-title"> Actions </div>
|
||||
<!-- #region ACTIONS -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm !ml-0"
|
||||
ng-disabled="!resourcePoolCreationForm.$valid || $ctrl.isCreateButtonDisabled()"
|
||||
ng-click="$ctrl.createResourcePool()"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress" data-cy="k8sNamespace-createNamespaceButton">Create namespace</span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Creation in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #endregion -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,236 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
import filesizeParser from 'filesize-parser';
|
||||
import { KubernetesResourceQuotaDefaults } from 'Kubernetes/models/resource-quota/models';
|
||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
||||
import { KubernetesResourcePoolFormValues, KubernetesResourcePoolIngressClassHostFormValue } from 'Kubernetes/models/resource-pool/formValues';
|
||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
|
||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { getIngressControllerClassMap, updateIngressControllerClassMap } from '@/react/kubernetes/cluster/ingressClass/useIngressControllerClassMap';
|
||||
|
||||
class KubernetesCreateResourcePoolController {
|
||||
/* #region CONSTRUCTOR */
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $scope, Notifications, KubernetesNodeService, KubernetesResourcePoolService, KubernetesIngressService, Authentication, EndpointService) {
|
||||
Object.assign(this, {
|
||||
$async,
|
||||
$state,
|
||||
$scope,
|
||||
Notifications,
|
||||
KubernetesNodeService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesIngressService,
|
||||
Authentication,
|
||||
EndpointService,
|
||||
});
|
||||
|
||||
this.IngressClassTypes = KubernetesIngressClassTypes;
|
||||
this.EndpointService = EndpointService;
|
||||
this.LBQuotaFeatureId = FeatureId.K8S_RESOURCE_POOL_LB_QUOTA;
|
||||
|
||||
this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this);
|
||||
this.onToggleLoadBalancerQuota = this.onToggleLoadBalancerQuota.bind(this);
|
||||
this.onToggleResourceQuota = this.onToggleResourceQuota.bind(this);
|
||||
this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this);
|
||||
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
||||
this.handleMemoryLimitChange = this.handleMemoryLimitChange.bind(this);
|
||||
this.handleCpuLimitChange = this.handleCpuLimitChange.bind(this);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
onRegistriesChange(registries) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.Registries = registries;
|
||||
});
|
||||
}
|
||||
|
||||
onToggleStorageQuota(storageClassName, enabled) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.formValues.StorageClasses = this.formValues.StorageClasses.map((sClass) => (sClass.Name !== storageClassName ? sClass : { ...sClass, Selected: enabled }));
|
||||
});
|
||||
}
|
||||
|
||||
onToggleLoadBalancerQuota(enabled) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.formValues.UseLoadBalancersQuota = enabled;
|
||||
});
|
||||
}
|
||||
|
||||
onToggleResourceQuota(enabled) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.formValues.HasQuota = enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/* #region INGRESS MANAGEMENT */
|
||||
onChangeIngressControllerAvailability(controllerClassMap) {
|
||||
this.ingressControllers = controllerClassMap;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
isCreateButtonDisabled() {
|
||||
return (
|
||||
this.state.actionInProgress ||
|
||||
(this.formValues.HasQuota && !this.isQuotaValid()) ||
|
||||
this.state.isAlreadyExist ||
|
||||
this.state.hasPrefixKube ||
|
||||
this.state.duplicates.ingressHosts.hasRefs
|
||||
);
|
||||
}
|
||||
|
||||
onChangeName() {
|
||||
this.state.isAlreadyExist = _.find(this.resourcePools, (resourcePool) => resourcePool.Namespace.Name === this.formValues.Name) !== undefined;
|
||||
this.state.hasPrefixKube = /^kube-/.test(this.formValues.Name);
|
||||
}
|
||||
|
||||
isQuotaValid() {
|
||||
if (
|
||||
this.state.sliderMaxCpu < this.formValues.CpuLimit ||
|
||||
this.state.sliderMaxMemory < this.formValues.MemoryLimit ||
|
||||
(this.formValues.CpuLimit === 0 && this.formValues.MemoryLimit === 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
checkDefaults() {
|
||||
if (this.formValues.CpuLimit < this.defaults.CpuLimit) {
|
||||
this.formValues.CpuLimit = this.defaults.CpuLimit;
|
||||
}
|
||||
if (this.formValues.MemoryLimit < KubernetesResourceReservationHelper.megaBytesValue(this.defaults.MemoryLimit)) {
|
||||
this.formValues.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(this.defaults.MemoryLimit);
|
||||
}
|
||||
}
|
||||
|
||||
handleMemoryLimitChange(memoryLimit) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.MemoryLimit = memoryLimit;
|
||||
});
|
||||
}
|
||||
|
||||
handleCpuLimitChange(cpuLimit) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.CpuLimit = cpuLimit;
|
||||
});
|
||||
}
|
||||
|
||||
/* #region CREATE NAMESPACE */
|
||||
createResourcePool() {
|
||||
return this.$async(async () => {
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
this.checkDefaults();
|
||||
this.formValues.Owner = this.Authentication.getUserDetails().username;
|
||||
await this.KubernetesResourcePoolService.create(this.formValues);
|
||||
await updateIngressControllerClassMap(this.endpoint.Id, this.ingressControllers || [], this.formValues.Name);
|
||||
this.Notifications.success('Namespace successfully created', this.formValues.Name);
|
||||
this.$state.go('kubernetes.resourcePools');
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create namespace');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region GET INGRESSES */
|
||||
getIngresses() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.allIngresses = await this.KubernetesIngressService.get();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve ingresses.');
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region GET NAMESPACES */
|
||||
getResourcePools() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve namespaces');
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region GET REGISTRIES */
|
||||
getRegistries() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.registries = await this.EndpointService.registries(this.endpoint.Id);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region ON INIT */
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const endpoint = await this.EndpointService.endpoint(this.endpoint.Id);
|
||||
this.defaults = KubernetesResourceQuotaDefaults;
|
||||
this.formValues = new KubernetesResourcePoolFormValues(this.defaults);
|
||||
this.formValues.EndpointId = this.endpoint.Id;
|
||||
this.formValues.HasQuota = false;
|
||||
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
sliderMaxMemory: 0,
|
||||
sliderMaxCpu: 0,
|
||||
viewReady: false,
|
||||
isAlreadyExist: false,
|
||||
hasPrefixKube: false,
|
||||
canUseIngress: false,
|
||||
duplicates: {
|
||||
ingressHosts: new KubernetesFormValidationReferences(),
|
||||
},
|
||||
isAdmin: this.Authentication.isAdmin(),
|
||||
ingressAvailabilityPerNamespace: endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace,
|
||||
};
|
||||
|
||||
const nodes = await this.KubernetesNodeService.get();
|
||||
|
||||
this.ingressControllers = [];
|
||||
if (this.state.ingressAvailabilityPerNamespace) {
|
||||
this.ingressControllers = await getIngressControllerClassMap({ environmentId: this.endpoint.Id, allowedOnly: true });
|
||||
this.initialIngressControllers = structuredClone(this.ingressControllers);
|
||||
}
|
||||
|
||||
_.forEach(nodes, (item) => {
|
||||
this.state.sliderMaxMemory += filesizeParser(item.Memory);
|
||||
this.state.sliderMaxCpu += item.CPU;
|
||||
});
|
||||
this.state.sliderMaxMemory = KubernetesResourceReservationHelper.megaBytesValue(this.state.sliderMaxMemory);
|
||||
await this.getResourcePools();
|
||||
if (this.state.canUseIngress) {
|
||||
await this.getIngresses();
|
||||
const ingressClasses = endpoint.Kubernetes.Configuration.IngressClasses;
|
||||
this.formValues.IngressClasses = KubernetesIngressConverter.ingressClassesToFormValues(ingressClasses);
|
||||
}
|
||||
_.forEach(this.formValues.IngressClasses, (ic) => {
|
||||
if (ic.Hosts.length === 0) {
|
||||
ic.Hosts.push(new KubernetesResourcePoolIngressClassHostFormValue());
|
||||
}
|
||||
});
|
||||
|
||||
await this.getRegistries();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
}
|
||||
|
||||
export default KubernetesCreateResourcePoolController;
|
|
@ -1,302 +0,0 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'Namespace details'"
|
||||
breadcrumbs="[{ label:'Namespaces', link:'kubernetes.resourcePools' }, ctrl.pool.Namespace.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)">
|
||||
<uib-tab-heading class="vertical-center"> <pr-icon icon="'layers'"></pr-icon> Namespace </uib-tab-heading>
|
||||
<form class="form-horizontal widget-body" autocomplete="off" name="resourcePoolEditForm" style="margin-top: 10px">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="w-[40%]">Name</td>
|
||||
<td>
|
||||
{{ ctrl.pool.Namespace.Name }}
|
||||
<span class="label label-info image-tag label-margins" ng-if="ctrl.isSystem">system</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="col-sm-12 !p-0">
|
||||
<annotations-be-teaser></annotations-be-teaser>
|
||||
</div>
|
||||
|
||||
<!-- !name-input -->
|
||||
<div ng-if="ctrl.isAdmin && ctrl.isEditable" class="col-sm-12 form-section-title">Resource quota</div>
|
||||
<!-- quotas-switch -->
|
||||
<div ng-if="ctrl.isAdmin && ctrl.isEditable" class="form-group">
|
||||
<div class="col-sm-12 mt-2" ng-if="ctrl.state.resourceOverCommitEnabled">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-3 col-lg-2">
|
||||
<label class="control-label text-left"> Resource assignment </label>
|
||||
</div>
|
||||
<div class="col-sm-9 pt-2">
|
||||
<label class="switch">
|
||||
<input type="checkbox" ng-model="ctrl.formValues.HasQuota" />
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="ctrl.formValues.HasQuota">
|
||||
<kubernetes-resource-reservation
|
||||
ng-if="ctrl.pool.Quota"
|
||||
description="Resource reservation represents the total amount of resource assigned to all the applications deployed inside this namespace."
|
||||
cpu-reservation="ctrl.state.resourceReservation.CPU"
|
||||
memory-reservation="ctrl.state.resourceReservation.Memory"
|
||||
cpu-limit="ctrl.formValues.CpuLimit"
|
||||
memory-limit="ctrl.formValues.MemoryLimit"
|
||||
display-usage="ctrl.state.useServerMetrics"
|
||||
cpu-usage="ctrl.state.resourceUsage.CPU"
|
||||
memory-usage="ctrl.state.resourceUsage.Memory"
|
||||
>
|
||||
</kubernetes-resource-reservation>
|
||||
</div>
|
||||
<!-- !quotas-switch -->
|
||||
<div ng-if="ctrl.formValues.HasQuota && ctrl.isAdmin && ctrl.isEditable">
|
||||
<div class="col-sm-12 form-section-title"> Resource limits </div>
|
||||
<div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 small text-warning" ng-switch on="ctrl.formValues.HasQuota && ctrl.isAdmin && ctrl.isEditable && !ctrl.isQuotaValid()">
|
||||
<p class="vertical-center mb-0" ng-switch-when="true"
|
||||
><pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'"></pr-icon> At least a single limit must be set for the quota to be valid.
|
||||
</p>
|
||||
<p class="vertical-center mb-0" ng-switch-default></p>
|
||||
</span>
|
||||
</div>
|
||||
<!-- memory-limit-input -->
|
||||
<div class="form-group flex">
|
||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label vertical-center text-left"> Memory limit (MB) </label>
|
||||
<div class="col-sm-6">
|
||||
<por-slider
|
||||
min="ctrl.ResourceQuotaDefaults.MemoryLimit"
|
||||
max="ctrl.state.sliderMaxMemory"
|
||||
step="128"
|
||||
ng-if="ctrl.state.sliderMaxMemory"
|
||||
value="ctrl.formValues.MemoryLimit"
|
||||
on-change="(ctrl.handleMemoryLimitChange)"
|
||||
visible-tooltip="true"
|
||||
data-cy="k8sNamespaceEdit-memoryLimitSlider"
|
||||
></por-slider>
|
||||
</div>
|
||||
<div class="col-sm-2 vertical-center pt-6">
|
||||
<input
|
||||
name="memory_limit"
|
||||
type="number"
|
||||
data-cy="k8sNamespaceEdit-memoryLimitInput"
|
||||
min="{{ ctrl.ResourceQuotaDefaults.MemoryLimit }}"
|
||||
max="{{ ctrl.state.sliderMaxMemory }}"
|
||||
class="form-control"
|
||||
ng-model="ctrl.formValues.MemoryLimit"
|
||||
id="memory-limit"
|
||||
data-cy="k8sNamespaceEdit-memoryLimitInput"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="resourcePoolEditForm.memory_limit.$invalid">
|
||||
<div class="col-sm-3 col-lg-2"></div>
|
||||
<div class="col-sm-8 small text-warning">
|
||||
<div ng-messages="resourcePoolEditForm.pool_name.$error">
|
||||
<p class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Value must be between {{ ctrl.ResourceQuotaDefaults.MemoryLimit }} and
|
||||
{{ ctrl.state.sliderMaxMemory }}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !memory-limit-input -->
|
||||
<!-- cpu-limit-input -->
|
||||
<div class="form-group">
|
||||
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px"> CPU limit </label>
|
||||
<div class="col-sm-8">
|
||||
<por-slider
|
||||
min="ctrl.ResourceQuotaDefaults.CpuLimit"
|
||||
max="ctrl.state.sliderMaxCpu"
|
||||
step="0.1"
|
||||
ng-if="ctrl.state.sliderMaxCpu"
|
||||
value="ctrl.formValues.CpuLimit"
|
||||
on-change="(ctrl.handleCpuLimitChange)"
|
||||
data-cy="k8sNamespaceEdit-cpuLimitSlider"
|
||||
visible-tooltip="true"
|
||||
></por-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !cpu-limit-input -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- #region LOADBALANCERS -->
|
||||
<div class="col-sm-12 form-section-title"> Load balancers </div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<p class="vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
You can set a quota on the amount of external load balancers that can be created inside this namespace. Set this quota to 0 to effectively disable the use of
|
||||
load balancers in this namespace.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
data-cy="'k8sNamespaceCreate-loadBalancerQuotaToggle'"
|
||||
label="'Load Balancer quota'"
|
||||
label-class="'col-sm-3 col-lg-2'"
|
||||
name="'k8s-resourcepool-Lbquota'"
|
||||
feature-id="ctrl.LBQuotaFeatureId"
|
||||
checked="ctrl.formValues.UseLoadBalancersQuota"
|
||||
on-change="(ctrl.onToggleLoadBalancersQuota)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
<div ng-if="ctrl.isAdmin && ctrl.isEditable && ctrl.state.ingressAvailabilityPerNamespace">
|
||||
<!-- #region INGRESSES -->
|
||||
<div class="col-sm-12 form-section-title"> Networking </div>
|
||||
<ingress-class-datatable
|
||||
ng-if="ctrl.state.ingressAvailabilityPerNamespace"
|
||||
on-change-controllers="(ctrl.onChangeIngressControllerAvailability)"
|
||||
ingress-controllers="ctrl.ingressControllers"
|
||||
initial-ingress-controllers="$ctrl.initialIngressControllers"
|
||||
description="'Enable the ingress controllers that users can select when publishing applications in this namespace.'"
|
||||
no-ingress-controller-label="'No ingress controllers found in the cluster. Go to the cluster setup view to configure and allow the use of ingress controllers in the cluster.'"
|
||||
view="'namespace'"
|
||||
></ingress-class-datatable>
|
||||
|
||||
<!-- #endregion -->
|
||||
</div>
|
||||
<!-- #region REGISTRIES -->
|
||||
<div>
|
||||
<div class="col-sm-12 form-section-title"> Registries </div>
|
||||
|
||||
<div class="form-group" ng-if="!ctrl.isAdmin || ctrl.isSystem">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left" style="padding-top: 0"> Selected registries </label>
|
||||
<div class="col-sm-9 col-lg-4"> {{ ctrl.selectedRegistries ? ctrl.selectedRegistries : 'None' }} </div>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.isAdmin && !ctrl.isSystem">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 small text-muted">
|
||||
<p class="vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
Define which registries can be used by users who have access to this namespace.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label !pt-0 text-left" for="registries-selector"> Select registries </label>
|
||||
<div class="col-sm-9 col-lg-4">
|
||||
<create-namespace-registries-selector
|
||||
input-id="'registries-selector'"
|
||||
value="ctrl.formValues.Registries"
|
||||
on-change="(ctrl.onRegistriesChange)"
|
||||
options="ctrl.registries"
|
||||
>
|
||||
</create-namespace-registries-selector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region STORAGES -->
|
||||
<div class="col-sm-12 form-section-title"> Storage </div>
|
||||
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
<p class="vertical-center">
|
||||
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
||||
Quotas can be set on each storage option to prevent users from exceeding a specific threshold when deploying applications. You can set a quota to 0 to
|
||||
effectively prevent the usage of a specific storage option inside this namespace.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-section-title text-muted col-sm-12" style="width: 100%">
|
||||
<div class="vertical-center"> <pr-icon icon="'svg-route'"></pr-icon>standard </div>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<storage-class-switch value="sc.Selected" name="sc.Name" on-change="(ctrl.onToggleStorageQuota)" authorization="K8sResourcePoolDetailsW"> </storage-class-switch>
|
||||
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- summary -->
|
||||
<kubernetes-summary-view
|
||||
ng-if="resourcePoolEditForm.$valid && !ctrl.isUpdateButtonDisabled()"
|
||||
form-values="ctrl.formValues"
|
||||
old-form-values="ctrl.savedFormValues"
|
||||
></kubernetes-summary-view>
|
||||
<!-- !summary -->
|
||||
|
||||
<!-- actions -->
|
||||
<div ng-if="ctrl.isAdmin" class="col-sm-12 form-section-title"> Actions </div>
|
||||
<div ng-if="ctrl.isAdmin" class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
ng-if="!ctrl.isSystem"
|
||||
class="btn btn-primary btn-sm !ml-0 !mr-1"
|
||||
ng-disabled="!resourcePoolEditForm.$valid || ctrl.isUpdateButtonDisabled()"
|
||||
ng-click="ctrl.updateResourcePool()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="ctrl.state.actionInProgress" data-cy="k8sNamespaceEdit-updateNamespaceButton">Update namespace</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Update in progress...</span>
|
||||
</button>
|
||||
<button
|
||||
ng-if="!ctrl.isDefaultNamespace"
|
||||
type="button"
|
||||
class="btn btn-light btn-sm !ml-0"
|
||||
ng-click="ctrl.markUnmarkAsSystem()"
|
||||
button-spinner="ctrl.state.actionInProgress"
|
||||
data-cy="k8sNamespaceEdit-markSystem"
|
||||
>
|
||||
<span ng-if="ctrl.isSystem">Unmark as system</span>
|
||||
<span ng-if="!ctrl.isSystem">Mark as system</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</uib-tab>
|
||||
<uib-tab index="1" classes="btn-sm" select="ctrl.selectTab(1)">
|
||||
<uib-tab-heading class="vertical-center">
|
||||
<pr-icon icon="'history'"></pr-icon> Events
|
||||
<div ng-if="ctrl.hasEventWarnings()">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" class-name="'mr-0.5'"></pr-icon>
|
||||
{{ ctrl.state.eventWarningCount }} warning(s)
|
||||
</div>
|
||||
</uib-tab-heading>
|
||||
<resource-events-datatable namespace="ctrl.pool.Namespace.Name" storage-key="'kubernetes.resourcepool.events'"> </resource-events-datatable>
|
||||
</uib-tab>
|
||||
<uib-tab index="2" ng-if="ctrl.pool.Yaml" select="ctrl.showEditor()" classes="btn-sm">
|
||||
<uib-tab-heading class="vertical-center"><pr-icon icon="'code'"></pr-icon> YAML </uib-tab-heading>
|
||||
<div class="px-5" ng-if="ctrl.state.showEditorTab">
|
||||
<kube-yaml-inspector identifier="'namespace-yaml'" data="ctrl.pool.Yaml" data-cy="k8sNamespaceEdit-yamlEditor" hide-message="true" />
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.applications && ctrl.applications.length > 0">
|
||||
<kubernetes-namespace-applications-datatable dataset="ctrl.applications" on-refresh="(ctrl.getApplications)" is-loading="ctrl.state.applicationsLoading">
|
||||
</kubernetes-namespace-applications-datatable>
|
||||
</div>
|
||||
</div>
|
|
@ -1,8 +0,0 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesResourcePoolView', {
|
||||
templateUrl: './resourcePool.html',
|
||||
controller: 'KubernetesResourcePoolController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
endpoint: '<',
|
||||
},
|
||||
});
|
|
@ -1,405 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import filesizeParser from 'filesize-parser';
|
||||
import { KubernetesResourceQuotaDefaults } from 'Kubernetes/models/resource-quota/models';
|
||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
||||
import { KubernetesResourceReservation } from 'Kubernetes/models/resource-reservation/models';
|
||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
|
||||
import { KubernetesResourcePoolFormValues } from 'Kubernetes/models/resource-pool/formValues';
|
||||
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
|
||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||
import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuota';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import { updateIngressControllerClassMap, getIngressControllerClassMap } from '@/react/kubernetes/cluster/ingressClass/useIngressControllerClassMap';
|
||||
import { confirmUpdate } from '@@/modals/confirm';
|
||||
import { confirmUpdateNamespace } from '@/react/kubernetes/namespaces/ItemView/ConfirmUpdateNamespace';
|
||||
import { getMetricsForAllPods } from '@/react/kubernetes/metrics/metrics.ts';
|
||||
|
||||
class KubernetesResourcePoolController {
|
||||
/* #region CONSTRUCTOR */
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$async,
|
||||
$state,
|
||||
$scope,
|
||||
Authentication,
|
||||
Notifications,
|
||||
LocalStorage,
|
||||
EndpointService,
|
||||
KubernetesResourceQuotaService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesEventService,
|
||||
KubernetesPodService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesIngressService,
|
||||
KubernetesVolumeService,
|
||||
KubernetesNamespaceService,
|
||||
KubernetesNodeService
|
||||
) {
|
||||
Object.assign(this, {
|
||||
$async,
|
||||
$state,
|
||||
$scope,
|
||||
Authentication,
|
||||
Notifications,
|
||||
LocalStorage,
|
||||
EndpointService,
|
||||
KubernetesResourceQuotaService,
|
||||
KubernetesResourcePoolService,
|
||||
KubernetesEventService,
|
||||
KubernetesPodService,
|
||||
KubernetesApplicationService,
|
||||
KubernetesIngressService,
|
||||
KubernetesVolumeService,
|
||||
KubernetesNamespaceService,
|
||||
KubernetesNodeService,
|
||||
});
|
||||
|
||||
this.IngressClassTypes = KubernetesIngressClassTypes;
|
||||
this.ResourceQuotaDefaults = KubernetesResourceQuotaDefaults;
|
||||
this.EndpointService = EndpointService;
|
||||
|
||||
this.LBQuotaFeatureId = FeatureId.K8S_RESOURCE_POOL_LB_QUOTA;
|
||||
this.StorageQuotaFeatureId = FeatureId.K8S_RESOURCE_POOL_STORAGE_QUOTA;
|
||||
this.StorageQuotaFeatureId = FeatureId.K8S_RESOURCE_POOL_STORAGE_QUOTA;
|
||||
|
||||
this.updateResourcePoolAsync = this.updateResourcePoolAsync.bind(this);
|
||||
this.getEvents = this.getEvents.bind(this);
|
||||
this.onToggleLoadBalancersQuota = this.onToggleLoadBalancersQuota.bind(this);
|
||||
this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this);
|
||||
this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this);
|
||||
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
||||
this.handleMemoryLimitChange = this.handleMemoryLimitChange.bind(this);
|
||||
this.handleCpuLimitChange = this.handleCpuLimitChange.bind(this);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
onRegistriesChange(registries) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.Registries = registries;
|
||||
});
|
||||
}
|
||||
|
||||
onToggleLoadBalancersQuota(checked) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.UseLoadBalancersQuota = checked;
|
||||
});
|
||||
}
|
||||
|
||||
onToggleStorageQuota(storageClassName, enabled) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.formValues.StorageClasses = this.formValues.StorageClasses.map((sClass) => (sClass.Name !== storageClassName ? sClass : { ...sClass, Selected: enabled }));
|
||||
});
|
||||
}
|
||||
|
||||
onChangeIngressControllerAvailability(controllerClassMap) {
|
||||
this.ingressControllers = controllerClassMap;
|
||||
}
|
||||
|
||||
selectTab(index) {
|
||||
this.LocalStorage.storeActiveTab('resourcePool', index);
|
||||
}
|
||||
|
||||
isUpdateButtonDisabled() {
|
||||
return this.state.actionInProgress || (this.formValues.HasQuota && !this.isQuotaValid()) || this.state.duplicates.ingressHosts.hasRefs;
|
||||
}
|
||||
|
||||
isQuotaValid() {
|
||||
if (
|
||||
this.state.sliderMaxCpu < this.formValues.CpuLimit ||
|
||||
this.state.sliderMaxMemory < this.formValues.MemoryLimit ||
|
||||
(this.formValues.CpuLimit === 0 && this.formValues.MemoryLimit === 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
checkDefaults() {
|
||||
if (this.formValues.CpuLimit < KubernetesResourceQuotaDefaults.CpuLimit) {
|
||||
this.formValues.CpuLimit = KubernetesResourceQuotaDefaults.CpuLimit;
|
||||
}
|
||||
if (this.formValues.MemoryLimit < KubernetesResourceReservationHelper.megaBytesValue(KubernetesResourceQuotaDefaults.MemoryLimit)) {
|
||||
this.formValues.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(KubernetesResourceQuotaDefaults.MemoryLimit);
|
||||
}
|
||||
}
|
||||
|
||||
handleMemoryLimitChange(memoryLimit) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.MemoryLimit = memoryLimit;
|
||||
});
|
||||
}
|
||||
|
||||
handleCpuLimitChange(cpuLimit) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.CpuLimit = cpuLimit;
|
||||
});
|
||||
}
|
||||
|
||||
showEditor() {
|
||||
this.state.showEditorTab = true;
|
||||
this.selectTab(2);
|
||||
}
|
||||
|
||||
hasResourceQuotaBeenReduced() {
|
||||
if (this.formValues.HasQuota && this.oldQuota) {
|
||||
const cpuLimit = this.formValues.CpuLimit;
|
||||
const memoryLimit = KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit);
|
||||
if (cpuLimit < this.oldQuota.CpuLimit || memoryLimit < this.oldQuota.MemoryLimit) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* #region UPDATE NAMESPACE */
|
||||
async updateResourcePoolAsync(oldFormValues, newFormValues) {
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
this.checkDefaults();
|
||||
await this.KubernetesResourcePoolService.patch(oldFormValues, newFormValues);
|
||||
await updateIngressControllerClassMap(this.endpoint.Id, this.ingressControllers || [], this.formValues.Name);
|
||||
this.Notifications.success('Namespace successfully updated', this.pool.Namespace.Name);
|
||||
this.$state.reload(this.$state.current);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create namespace');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateResourcePool() {
|
||||
const ingressesToDelete = _.filter(this.formValues.IngressClasses, { WasSelected: true, Selected: false });
|
||||
const registriesToDelete = _.filter(this.registries, { WasChecked: true, Checked: false });
|
||||
const warnings = {
|
||||
quota: this.hasResourceQuotaBeenReduced(),
|
||||
ingress: ingressesToDelete.length !== 0,
|
||||
registries: registriesToDelete.length !== 0,
|
||||
};
|
||||
|
||||
if (warnings.quota || warnings.registries) {
|
||||
confirmUpdateNamespace(warnings.quota, warnings.ingress, warnings.registries).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.updateResourcePoolAsync, this.savedFormValues, this.formValues);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return this.$async(this.updateResourcePoolAsync, this.savedFormValues, this.formValues);
|
||||
}
|
||||
}
|
||||
|
||||
async confirmMarkUnmarkAsSystem() {
|
||||
const message = this.isSystem
|
||||
? 'Unmarking this namespace as system will allow non administrator users to manage it and the resources in contains depending on the access control settings. Are you sure?'
|
||||
: 'Marking this namespace as a system namespace will prevent non administrator users from managing it and the resources it contains. Are you sure?';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
confirmUpdate(message, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
markUnmarkAsSystem() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const namespaceName = this.$state.params.id;
|
||||
this.state.actionInProgress = true;
|
||||
|
||||
const confirmed = await this.confirmMarkUnmarkAsSystem();
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
await this.KubernetesResourcePoolService.toggleSystem(this.endpoint.Id, namespaceName, !this.isSystem);
|
||||
|
||||
this.Notifications.success('Namespace successfully updated', namespaceName);
|
||||
this.$state.reload(this.$state.current);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create namespace');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
hasEventWarnings() {
|
||||
return this.state.eventWarningCount;
|
||||
}
|
||||
|
||||
/* #region GET EVENTS */
|
||||
getEvents() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state.eventsLoading = true;
|
||||
this.events = await this.KubernetesEventService.get(this.pool.Namespace.Name);
|
||||
this.state.eventWarningCount = KubernetesEventHelper.warningCount(this.events);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve namespace related events');
|
||||
} finally {
|
||||
this.state.eventsLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region GET APPLICATIONS */
|
||||
getApplications() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state.applicationsLoading = true;
|
||||
this.applications = await this.KubernetesApplicationService.get(this.pool.Namespace.Name);
|
||||
this.applications = _.map(this.applications, (app) => {
|
||||
const resourceReservation = KubernetesResourceReservationHelper.computeResourceReservation(app.Pods);
|
||||
app.CPU = resourceReservation.CPU;
|
||||
app.Memory = resourceReservation.Memory;
|
||||
return app;
|
||||
});
|
||||
|
||||
if (this.state.useServerMetrics) {
|
||||
await this.getResourceUsage(this.pool.Namespace.Name);
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve applications.');
|
||||
} finally {
|
||||
this.state.applicationsLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region GET REGISTRIES */
|
||||
getRegistries() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const namespace = this.$state.params.id;
|
||||
|
||||
if (this.isAdmin) {
|
||||
this.registries = await this.EndpointService.registries(this.endpoint.Id);
|
||||
this.registries.forEach((reg) => {
|
||||
if (reg.RegistryAccesses && reg.RegistryAccesses[this.endpoint.Id] && reg.RegistryAccesses[this.endpoint.Id].Namespaces.includes(namespace)) {
|
||||
reg.Checked = true;
|
||||
reg.WasChecked = true;
|
||||
this.formValues.Registries.push(reg);
|
||||
}
|
||||
});
|
||||
this.selectedRegistries = this.formValues.Registries.map((r) => r.Name).join(', ');
|
||||
return;
|
||||
}
|
||||
|
||||
const registries = await this.EndpointService.registries(this.endpoint.Id, namespace);
|
||||
this.selectedRegistries = registries.map((r) => r.Name).join(', ');
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
}
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
async getResourceUsage(namespace) {
|
||||
try {
|
||||
const namespaceMetrics = await getMetricsForAllPods(this.$state.params.endpointId, namespace);
|
||||
// extract resource usage of all containers within each pod of the namespace
|
||||
const containerResourceUsageList = namespaceMetrics.items.flatMap((i) => i.containers.map((c) => c.usage));
|
||||
const namespaceResourceUsage = containerResourceUsageList.reduce((total, u) => {
|
||||
total.CPU += KubernetesResourceReservationHelper.parseCPU(u.cpu);
|
||||
total.Memory += KubernetesResourceReservationHelper.megaBytesValue(u.memory);
|
||||
return total;
|
||||
}, new KubernetesResourceReservation());
|
||||
this.state.resourceUsage = namespaceResourceUsage;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve namespace resource usage');
|
||||
}
|
||||
}
|
||||
|
||||
/* #region ON INIT */
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.endpoint = await this.EndpointService.endpoint(this.endpoint.Id);
|
||||
this.isAdmin = this.Authentication.isAdmin();
|
||||
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
sliderMaxMemory: 0,
|
||||
sliderMaxCpu: 0,
|
||||
cpuUsage: 0,
|
||||
memoryUsage: 0,
|
||||
resourceReservation: { CPU: 0, Memory: 0 },
|
||||
activeTab: 0,
|
||||
currentName: this.$state.$current.name,
|
||||
showEditorTab: false,
|
||||
eventsLoading: true,
|
||||
applicationsLoading: true,
|
||||
ingressesLoading: true,
|
||||
viewReady: false,
|
||||
eventWarningCount: 0,
|
||||
useServerMetrics: this.endpoint.Kubernetes.Configuration.UseServerMetrics,
|
||||
duplicates: {
|
||||
ingressHosts: new KubernetesFormValidationReferences(),
|
||||
},
|
||||
ingressAvailabilityPerNamespace: this.endpoint.Kubernetes.Configuration.IngressAvailabilityPerNamespace,
|
||||
};
|
||||
|
||||
this.state.activeTab = this.LocalStorage.getActiveTab('resourcePool');
|
||||
|
||||
const name = this.$state.params.id;
|
||||
|
||||
const [nodes, pool] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get(name)]);
|
||||
|
||||
this.ingressControllers = [];
|
||||
if (this.state.ingressAvailabilityPerNamespace) {
|
||||
this.ingressControllers = await getIngressControllerClassMap({ environmentId: this.endpoint.Id, namespace: name });
|
||||
this.initialIngressControllers = structuredClone(this.ingressControllers);
|
||||
}
|
||||
|
||||
this.pool = pool;
|
||||
this.formValues = new KubernetesResourcePoolFormValues(KubernetesResourceQuotaDefaults);
|
||||
this.formValues.Name = this.pool.Namespace.Name;
|
||||
this.formValues.EndpointId = this.endpoint.Id;
|
||||
this.formValues.IsSystem = this.pool.Namespace.IsSystem;
|
||||
|
||||
_.forEach(nodes, (item) => {
|
||||
this.state.sliderMaxMemory += filesizeParser(item.Memory);
|
||||
this.state.sliderMaxCpu += item.CPU;
|
||||
});
|
||||
this.state.sliderMaxMemory = KubernetesResourceReservationHelper.megaBytesValue(this.state.sliderMaxMemory);
|
||||
|
||||
const quota = this.pool.Quota;
|
||||
if (quota) {
|
||||
this.oldQuota = angular.copy(quota);
|
||||
this.formValues = KubernetesResourceQuotaConverter.quotaToResourcePoolFormValues(quota);
|
||||
this.formValues.EndpointId = this.endpoint.Id;
|
||||
|
||||
this.state.resourceReservation.CPU = quota.CpuLimitUsed;
|
||||
this.state.resourceReservation.Memory = KubernetesResourceReservationHelper.megaBytesValue(quota.MemoryLimitUsed);
|
||||
}
|
||||
this.isSystem = KubernetesNamespaceHelper.isSystemNamespace(this.pool.Namespace.Name);
|
||||
this.isDefaultNamespace = KubernetesNamespaceHelper.isDefaultNamespace(this.pool.Namespace.Name);
|
||||
this.isEditable = !this.isSystem && !this.isDefaultNamespace;
|
||||
|
||||
await this.getEvents();
|
||||
await this.getApplications();
|
||||
|
||||
await this.getRegistries();
|
||||
|
||||
this.savedFormValues = angular.copy(this.formValues);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
$onDestroy() {
|
||||
if (this.state.currentName !== this.$state.$current.name) {
|
||||
this.LocalStorage.storeActiveTab('resourcePool', 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesResourcePoolController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesResourcePoolController', KubernetesResourcePoolController);
|
|
@ -1,7 +0,0 @@
|
|||
<page-header ng-if="ctrl.state.viewReady" title="'Namespace list'" breadcrumbs="['Namespaces']" on-reload="(ctrl.onReload)" reload="true"></page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<kubernetes-namespaces-datatable dataset="ctrl.resourcePools" on-remove="(ctrl.removeAction)" on-refresh="(ctrl.getResourcePools)"></kubernetes-namespaces-datatable>
|
||||
</div>
|
|
@ -1,8 +0,0 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesResourcePoolsView', {
|
||||
templateUrl: './resourcePools.html',
|
||||
controller: 'KubernetesResourcePoolsController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
endpoint: '<',
|
||||
},
|
||||
});
|
|
@ -1,109 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { dispatchCacheRefreshEvent } from '@/portainer/services/http-request.helper';
|
||||
|
||||
class KubernetesResourcePoolsController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Notifications, KubernetesResourcePoolService, KubernetesNamespaceService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.Notifications = Notifications;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesNamespaceService = KubernetesNamespaceService;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.getResourcePools = this.getResourcePools.bind(this);
|
||||
this.getResourcePoolsAsync = this.getResourcePoolsAsync.bind(this);
|
||||
this.removeAction = this.removeAction.bind(this);
|
||||
this.removeActionAsync = this.removeActionAsync.bind(this);
|
||||
this.onReload = this.onReload.bind(this);
|
||||
}
|
||||
|
||||
async onReload() {
|
||||
this.$state.reload(this.$state.current);
|
||||
}
|
||||
|
||||
async removeActionAsync(selectedItems) {
|
||||
let actionCount = selectedItems.length;
|
||||
for (const pool of selectedItems) {
|
||||
try {
|
||||
const isTerminating = pool.Namespace.Status === 'Terminating';
|
||||
if (isTerminating) {
|
||||
const ns = await this.KubernetesNamespaceService.getJSONAsync(pool.Namespace.Name);
|
||||
ns.$promise.then(async (namespace) => {
|
||||
const n = JSON.parse(namespace.data);
|
||||
if (n.spec && n.spec.finalizers) {
|
||||
delete n.spec.finalizers;
|
||||
}
|
||||
await this.KubernetesNamespaceService.updateFinalizeAsync(n);
|
||||
});
|
||||
} else {
|
||||
await this.KubernetesResourcePoolService.delete(pool);
|
||||
}
|
||||
this.Notifications.success('Namespace successfully removed', pool.Namespace.Name);
|
||||
const index = this.resourcePools.indexOf(pool);
|
||||
this.resourcePools.splice(index, 1);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to remove namespace');
|
||||
} finally {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
this.$state.reload(this.$state.current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeAction(selectedItems) {
|
||||
const isTerminatingNS = selectedItems.some((pool) => pool.Namespace.Status === 'Terminating');
|
||||
const message = isTerminatingNS
|
||||
? 'At least one namespace is in a terminating state. For terminating state namespaces, you may continue and force removal, but doing so without having properly cleaned up may lead to unstable and unpredictable behavior. Are you sure you wish to proceed?'
|
||||
: 'Do you want to remove the selected namespace(s)? All the resources associated to the selected namespace(s) will be removed too. Are you sure you wish to proceed?';
|
||||
confirm({
|
||||
title: isTerminatingNS ? 'Force namespace removal' : 'Are you sure?',
|
||||
message,
|
||||
confirmButton: buildConfirmButton('Remove', 'danger'),
|
||||
|
||||
modalType: ModalType.Destructive,
|
||||
}).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
return this.$async(this.removeActionAsync, selectedItems);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getResourcePoolsAsync() {
|
||||
try {
|
||||
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
|
||||
// make sure table refreshes with fresh data when namespaces are in a terminating state
|
||||
if (this.resourcePools.some((namespace) => namespace.Namespace.Status === 'Terminating')) {
|
||||
dispatchCacheRefreshEvent();
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retreive namespaces');
|
||||
}
|
||||
}
|
||||
|
||||
getResourcePools() {
|
||||
return this.$async(this.getResourcePoolsAsync);
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
this.state = {
|
||||
viewReady: false,
|
||||
};
|
||||
|
||||
await this.getResourcePools();
|
||||
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesResourcePoolsController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesResourcePoolsController', KubernetesResourcePoolsController);
|
Loading…
Add table
Add a link
Reference in a new issue