1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

refactor(app): migrate app summary section [EE-6239] (#10910)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run

This commit is contained in:
Ali 2024-01-05 15:42:36 +13:00 committed by GitHub
parent 7a4314032a
commit abf517de28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 1461 additions and 661 deletions

View file

@ -3,7 +3,7 @@ import _ from 'lodash-es';
import KubernetesStackHelper from 'Kubernetes/helpers/stackHelper';
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants';
import { KubernetesPortainerApplicationStackNameLabel } from 'Kubernetes/models/application/models';
import { confirmDelete } from '@@/modals/confirm';
import { getDeploymentOptions } from '@/react/portainer/environments/environment.service';
@ -90,7 +90,7 @@ class KubernetesApplicationsController {
let actionCount = selectedItems.length;
for (const application of selectedItems) {
try {
if (application.ApplicationType === KubernetesApplicationTypes.HELM) {
if (application.ApplicationType === KubernetesApplicationTypes.Helm) {
await this.HelmService.uninstall(this.endpoint.Id, application);
} else {
await this.KubernetesApplicationService.delete(application);

View file

@ -103,9 +103,16 @@
></kube-services-form>
</div>
<!-- kubernetes services options -->
<kubernetes-summary-view form-values="ctrl.formValues" old-form-values="ctrl.savedFormValues"></kubernetes-summary-view>
<!-- #region ACTIONS -->
<!-- kubernetes summary for external application -->
<application-summary-section
application-kind="ctrl.formValues.ApplicationType"
form-values="ctrl.formValues"
old-form-values="ctrl.savedFormValues"
ng-if="ctrl.isExternalApplication()"
></application-summary-section>
<!-- kubernetes summary for external application -->
<div class="col-sm-12 form-section-title !mt-6" ng-if="ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.GIT" ng-hide="ctrl.stack.IsComposeFormat"> Actions </div>
<!-- #region ACTIONS -->
<div class="form-group" ng-hide="ctrl.stack.IsComposeFormat">
<div class="col-sm-12">
<button
@ -423,12 +430,12 @@
<!-- #region DATA ACCESS POLICY -->
<div ng-if="ctrl.showDataAccessPolicySection()">
<data-access-policy-form-section
<access-policy-form-section
value="ctrl.formValues.DataAccessPolicy"
on-change="(ctrl.onDataAccessPolicyChange)"
is-edit="ctrl.state.isEdit"
persisted-folders-use-existing-volumes="ctrl.state.persistedFoldersUseExistingVolumes"
></data-access-policy-form-section>
></access-policy-form-section>
</div>
<!-- #endregion -->
@ -451,7 +458,7 @@
></app-deployment-type-form-section>
<!-- replica count -->
<div ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.REPLICATED">
<div ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.Replicated">
<replication-form-section
values="{replicaCount: ctrl.formValues.ReplicaCount}"
on-change="(ctrl.onChangeReplicaCount)"
@ -466,9 +473,9 @@
<!-- #endregion -->
<!-- #region AUTO SCALING -->
<div ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL">
<div ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.Global">
<auto-scaling-form-section
values="{isUsed: ctrl.formValues.AutoScaler.IsUsed, minReplicas: ctrl.formValues.AutoScaler.MinReplicas, maxReplicas: ctrl.formValues.AutoScaler.MaxReplicas, targetCpuUtilizationPercentage: ctrl.formValues.AutoScaler.TargetCPUUtilization}"
values="ctrl.formValues.AutoScaler"
on-change="(ctrl.onAutoScaleChange)"
validation-data="{autoScalerOverflow: ctrl.autoScalerOverflow()}"
is-metrics-enabled="ctrl.state.useServerMetrics"
@ -478,7 +485,7 @@
</div>
<placement-form-section
ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.REPLICATED"
ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.Replicated"
values="{placements: ctrl.formValues.Placements, placementType: ctrl.formValues.PlacementType}"
on-change="(ctrl.onChangePlacements)"
></placement-form-section>
@ -497,11 +504,12 @@
></kube-services-form>
</div>
<!-- kubernetes services options -->
<kubernetes-summary-view
<application-summary-section
application-kind="ctrl.formValues.ApplicationType"
ng-if="!(!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.state.pullImageValidity)"
form-values="ctrl.formValues"
old-form-values="ctrl.savedFormValues"
></kubernetes-summary-view>
></application-summary-section>
</div>
<!-- #region ACTIONS -->
<div class="col-sm-12 form-section-title !mt-6" ng-if="ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.GIT" ng-hide="ctrl.stack.IsComposeFormat"> Actions </div>

View file

@ -10,17 +10,11 @@ import { getGlobalDeploymentOptions } from '@/react/portainer/settings/settings.
import {
KubernetesApplicationDataAccessPolicies,
KubernetesApplicationDeploymentTypes,
KubernetesApplicationPublishingTypes,
KubernetesApplicationQuotaDefaults,
KubernetesApplicationServiceTypes,
KubernetesApplicationTypes,
KubernetesDeploymentTypes,
} from 'Kubernetes/models/application/models';
import {
KubernetesApplicationEnvironmentVariableFormValue,
KubernetesApplicationFormValues,
KubernetesApplicationPersistedFolderFormValue,
KubernetesFormValidationReferences,
} from 'Kubernetes/models/application/formValues';
} from 'Kubernetes/models/application/models/appConstants';
import { KubernetesApplicationQuotaDefaults, KubernetesDeploymentTypes } from 'Kubernetes/models/application/models';
import { KubernetesApplicationEnvironmentVariableFormValue, KubernetesApplicationFormValues, KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
@ -76,7 +70,7 @@ class KubernetesCreateApplicationController {
this.ApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
this.ApplicationPublishingTypes = KubernetesApplicationPublishingTypes;
this.KubernetesApplicationServiceTypes = KubernetesApplicationServiceTypes;
this.ApplicationTypes = KubernetesApplicationTypes;
this.ServiceTypes = KubernetesServiceTypes;
this.KubernetesDeploymentTypes = KubernetesDeploymentTypes;
@ -151,6 +145,9 @@ class KubernetesCreateApplicationController {
this.onChangeReplicaCount = this.onChangeReplicaCount.bind(this);
this.onAutoScaleChange = this.onAutoScaleChange.bind(this);
this.onChangePlacements = this.onChangePlacements.bind(this);
this.updateApplicationType = this.updateApplicationType.bind(this);
this.getAppType = this.getAppType.bind(this);
this.showDataAccessPolicySection = this.showDataAccessPolicySection.bind(this);
}
/* #endregion */
@ -165,6 +162,24 @@ class KubernetesCreateApplicationController {
this.$scope.$evalAsync(() => {
this.formValues.DeploymentType = value;
});
this.updateApplicationType();
}
updateApplicationType() {
return this.$scope.$evalAsync(() => {
this.formValues.ApplicationType = this.getAppType();
this.validatePersistedFolders();
});
}
getAppType() {
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.Global) {
return this.ApplicationTypes.DaemonSet;
}
if (this.formValues.PersistedFolders && this.formValues.PersistedFolders.length) {
return this.ApplicationTypes.StatefulSet;
}
return this.ApplicationTypes.Deployment;
}
onChangeFileContent(value) {
@ -230,28 +245,23 @@ class KubernetesCreateApplicationController {
/* #region AUTO SCALER UI MANAGEMENT */
unselectAutoScaler() {
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.GLOBAL) {
this.formValues.AutoScaler.IsUsed = false;
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.Global) {
this.formValues.AutoScaler.isUsed = false;
}
}
onAutoScaleChange(values) {
return this.$async(async () => {
if (!this.formValues.AutoScaler.IsUsed && values.isUsed) {
if (!this.formValues.AutoScaler.isUsed && values.isUsed) {
this.formValues.AutoScaler = {
IsUsed: values.isUsed,
MinReplicas: 1,
MaxReplicas: 3,
TargetCPUUtilization: 50,
isUsed: values.isUsed,
minReplicas: 1,
maxReplicas: 3,
targetCpuUtilizationPercentage: 50,
};
return;
}
this.formValues.AutoScaler = {
IsUsed: values.isUsed,
MinReplicas: values.minReplicas,
MaxReplicas: values.maxReplicas,
TargetCPUUtilization: values.targetCpuUtilizationPercentage,
};
this.formValues.AutoScaler = values;
});
}
/* #endregion */
@ -319,22 +329,6 @@ class KubernetesCreateApplicationController {
/* #endregion */
/* #region PERSISTENT FOLDERS UI MANAGEMENT */
addPersistedFolder() {
let storageClass = {};
if (this.storageClasses.length > 0) {
storageClass = this.storageClasses[0];
}
const newPf = new KubernetesApplicationPersistedFolderFormValue(storageClass);
this.formValues.PersistedFolders.push(newPf);
this.resetDeploymentType();
}
restorePersistedFolder(index) {
this.formValues.PersistedFolders[index].needsDeletion = false;
this.validatePersistedFolders();
}
resetPersistedFolders() {
this.formValues.PersistedFolders = _.forEach(this.formValues.PersistedFolders, (persistedFolder) => {
persistedFolder.existingVolume = null;
@ -342,32 +336,6 @@ class KubernetesCreateApplicationController {
});
this.validatePersistedFolders();
}
removePersistedFolder(index) {
if (this.state.isEdit && this.formValues.PersistedFolders[index].persistentVolumeClaimName) {
this.formValues.PersistedFolders[index].needsDeletion = true;
} else {
this.formValues.PersistedFolders.splice(index, 1);
}
this.validatePersistedFolders();
}
useNewVolume(index) {
this.formValues.PersistedFolders[index].useNewVolume = true;
this.formValues.PersistedFolders[index].existingVolume = null;
this.state.persistedFoldersUseExistingVolumes = _.some(this.formValues.PersistedFolders, { useNewVolume: false });
this.validatePersistedFolders();
}
useExistingVolume(index) {
this.formValues.PersistedFolders[index].useNewVolume = false;
this.state.persistedFoldersUseExistingVolumes = _.some(this.formValues.PersistedFolders, { useNewVolume: false });
if (this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED) {
this.formValues.DataAccessPolicy = this.ApplicationDataAccessPolicies.SHARED;
this.resetDeploymentType();
}
this.validatePersistedFolders();
}
/* #endregion */
/* #region PERSISTENT FOLDERS ON CHANGE VALIDATION */
@ -389,7 +357,14 @@ class KubernetesCreateApplicationController {
}
onChangePersistedFolder(values) {
this.formValues.PersistedFolders = values;
this.$scope.$evalAsync(() => {
this.formValues.PersistedFolders = values;
if (values && values.length && !this.supportGlobalDeployment()) {
this.onChangeDeploymentType(this.ApplicationDeploymentTypes.Replicated);
return;
}
this.updateApplicationType();
});
}
onChangeExistingVolumeSelection() {
@ -445,13 +420,12 @@ class KubernetesCreateApplicationController {
}
resetDeploymentType() {
this.formValues.DeploymentType = this.ApplicationDeploymentTypes.REPLICATED;
this.formValues.DeploymentType = this.ApplicationDeploymentTypes.Replicated;
}
// The data access policy panel is not shown when:
// * There is not persisted folder specified
// // The data access policy panel is shown when a persisted folder is specified
showDataAccessPolicySection() {
return this.formValues.PersistedFolders.length !== 0;
return this.formValues.PersistedFolders.length > 0;
}
// A global deployment is not available when either:
@ -460,7 +434,7 @@ class KubernetesCreateApplicationController {
supportGlobalDeployment() {
const hasFolders = this.formValues.PersistedFolders.length !== 0;
const hasRWOOnly = KubernetesApplicationHelper.hasRWOOnly(this.formValues);
const isIsolated = this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED;
const isIsolated = this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.Isolated;
if (hasFolders && (hasRWOOnly || isIsolated)) {
return false;
@ -469,9 +443,9 @@ class KubernetesCreateApplicationController {
return true;
}
// A StatefulSet is defined by DataAccessPolicy === ISOLATED
// A StatefulSet is defined by DataAccessPolicy === 'Isolated'
isEditAndStatefulSet() {
return this.state.isEdit && this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED;
return this.state.isEdit && this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.Isolated;
}
// A scalable deployment is available when either:
@ -482,7 +456,7 @@ class KubernetesCreateApplicationController {
supportScalableReplicaDeployment() {
const hasFolders = this.formValues.PersistedFolders.length !== 0;
const hasRWOOnly = KubernetesApplicationHelper.hasRWOOnly(this.formValues);
const isIsolated = this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED;
const isIsolated = this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.Isolated;
if (!hasFolders || isIsolated || (hasFolders && !hasRWOOnly)) {
return true;
@ -540,7 +514,7 @@ class KubernetesCreateApplicationController {
}
effectiveInstances() {
return this.formValues.DeploymentType === this.ApplicationDeploymentTypes.GLOBAL ? this.nodeNumber : this.formValues.ReplicaCount;
return this.formValues.DeploymentType === this.ApplicationDeploymentTypes.Global ? this.nodeNumber : this.formValues.ReplicaCount;
}
hasPortErrors() {
@ -564,11 +538,11 @@ class KubernetesCreateApplicationController {
return true;
}
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.REPLICATED) {
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.Replicated) {
return this.nodesLimits.overflowForReplica(cpu, memory, instances);
}
// DeploymentType == GLOBAL
// DeploymentType == 'Global'
return this.nodesLimits.overflowForGlobal(cpu, memory);
}
@ -613,7 +587,7 @@ class KubernetesCreateApplicationController {
}
isExistingVolumeButtonDisabled() {
return !this.hasAvailableVolumes() || (this.isEdit && this.application.ApplicationType === this.ApplicationTypes.STATEFULSET);
return !this.hasAvailableVolumes() || (this.isEdit && this.application.ApplicationType === this.ApplicationTypes.StatefulSet);
}
/* #endregion */
@ -630,7 +604,7 @@ class KubernetesCreateApplicationController {
const scalable = this.supportScalableReplicaDeployment();
const global = this.supportGlobalDeployment();
const replica = this.formValues.ReplicaCount > 1;
const replicated = this.formValues.DeploymentType === this.ApplicationDeploymentTypes.REPLICATED;
const replicated = this.formValues.DeploymentType === this.ApplicationDeploymentTypes.Replicated;
const res = (replicated && !scalable && replica) || (!replicated && !global);
return res;
}
@ -784,7 +758,7 @@ class KubernetesCreateApplicationController {
if (this.savedFormValues) {
this.formValues.PublishingType = this.savedFormValues.PublishingType;
} else {
this.formValues.PublishingType = this.ApplicationPublishingTypes.CLUSTER_IP;
this.formValues.PublishingType = this.KubernetesApplicationServiceTypes.ClusterIP;
}
}
this.formValues.OriginalIngresses = this.ingresses;
@ -1070,9 +1044,8 @@ class KubernetesCreateApplicationController {
this.savedFormValues = angular.copy(this.formValues);
this.updateNamespaceLimits(this.namespaceWithQuota);
this.updateSliders(this.namespaceWithQuota);
delete this.formValues.ApplicationType;
if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) {
if (this.application.ApplicationType !== KubernetesApplicationTypes.StatefulSet) {
_.forEach(this.formValues.PersistedFolders, (persistedFolder) => {
const volume = _.find(this.availableVolumes, ['PersistentVolumeClaim.Name', persistedFolder.persistentVolumeClaimName]);
if (volume) {

View file

@ -1,12 +1,9 @@
import _ from 'lodash-es';
import { KubernetesResourceTypes, KubernetesResourceActions } from 'Kubernetes/models/resource-types/models';
import { KubernetesApplicationFormValues } from 'Kubernetes/models/application/formValues';
import { KubernetesDeployment } from 'Kubernetes/models/deployment/models';
import { KubernetesStatefulSet } from 'Kubernetes/models/stateful-set/models';
import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-set/models';
import { KubernetesService, KubernetesServiceTypes } from 'Kubernetes/models/service/models';
import { KubernetesApplication, KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
import { KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models/appConstants';
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
import KubernetesServiceConverter from 'Kubernetes/converters/service';
@ -33,7 +30,7 @@ export function getApplicationResources(formValues, oldFormValues = {}) {
* Get summary of Kubernetes resources to be created
* @param {KubernetesApplicationFormValues} formValues
*/
function getCreatedApplicationResources(formValues) {
export function getCreatedApplicationResources(formValues) {
const resources = [];
let [app, headlessService, services, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
@ -65,14 +62,14 @@ function getCreatedApplicationResources(formValues) {
}
// Horizontal pod autoscalers
if (formValues.AutoScaler.IsUsed && formValues.DeploymentType !== KubernetesApplicationDeploymentTypes.GLOBAL) {
const kind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(app);
if (formValues.AutoScaler.IsUsed && formValues.DeploymentType !== KubernetesApplicationDeploymentTypes.Global) {
const kind = app.ApplicationType;
const autoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(formValues, kind);
resources.push({ action: CREATE, kind: KubernetesResourceTypes.HORIZONTAL_POD_AUTOSCALER, name: autoScaler.Name });
}
// Deployment
const appResourceType = getApplicationResourceType(app);
const appResourceType = app.ApplicationType;
if (appResourceType !== null) {
resources.push({ action: CREATE, kind: appResourceType, name: app.Name });
}
@ -85,13 +82,13 @@ function getCreatedApplicationResources(formValues) {
* @param {KubernetesApplicationFormValues} oldFormValues
* @param {KubernetesApplicationFormValues} newFormValues
*/
function getUpdatedApplicationResources(oldFormValues, newFormValues) {
export function getUpdatedApplicationResources(oldFormValues, newFormValues) {
const resources = [];
const [oldApp, oldHeadlessService, oldServices, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
const [newApp, newHeadlessService, newServices, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
const oldAppResourceType = getApplicationResourceType(oldApp);
const newAppResourceType = getApplicationResourceType(newApp);
const oldAppResourceType = oldApp.ApplicationType;
const newAppResourceType = newApp.ApplicationType;
if (oldAppResourceType !== newAppResourceType) {
// Deployment
@ -152,7 +149,7 @@ function getUpdatedApplicationResources(oldFormValues, newFormValues) {
resources.push({ action: DELETE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP });
}
const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
const newKind = newApp.ApplicationType;
const newAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(newFormValues, newKind);
if (!oldFormValues.AutoScaler.IsUsed) {
if (newFormValues.AutoScaler.IsUsed) {
@ -161,7 +158,7 @@ function getUpdatedApplicationResources(oldFormValues, newFormValues) {
}
} else {
// Horizontal pod autoscalers
const oldKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(oldApp);
const oldKind = oldApp.ApplicationType;
const oldAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(oldFormValues, oldKind);
if (newFormValues.AutoScaler.IsUsed) {
const hpaUpdateSummary = getHorizontalPodAutoScalerUpdateResourceSummary(oldAutoScaler, newAutoScaler);
@ -176,17 +173,6 @@ function getUpdatedApplicationResources(oldFormValues, newFormValues) {
return resources;
}
function getApplicationResourceType(app) {
if (app instanceof KubernetesDeployment || (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.DEPLOYMENT)) {
return KubernetesResourceTypes.DEPLOYMENT;
} else if (app instanceof KubernetesDaemonSet || (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.DAEMONSET)) {
return KubernetesResourceTypes.DAEMONSET;
} else if (app instanceof KubernetesStatefulSet || (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET)) {
return KubernetesResourceTypes.STATEFULSET;
}
return null;
}
function getIngressUpdateSummary(oldIngresses, newIngresses) {
const ingressesSummaries = newIngresses
.map((newIng) => {