diff --git a/app/kubernetes/models/resource-pool/formValues.js b/app/kubernetes/models/resource-pool/formValues.js
index bb3f598c0..55efbfbba 100644
--- a/app/kubernetes/models/resource-pool/formValues.js
+++ b/app/kubernetes/models/resource-pool/formValues.js
@@ -1,11 +1,9 @@
export function KubernetesResourcePoolFormValues(defaults) {
- return {
- Name: '',
- MemoryLimit: defaults.MemoryLimit,
- CpuLimit: defaults.CpuLimit,
- HasQuota: false,
- IngressClasses: [], // KubernetesResourcePoolIngressClassFormValue
- };
+ this.Name = '';
+ this.MemoryLimit = defaults.MemoryLimit;
+ this.CpuLimit = defaults.CpuLimit;
+ this.HasQuota = false;
+ this.IngressClasses = []; // KubernetesResourcePoolIngressClassFormValue
}
/**
diff --git a/app/kubernetes/models/resource-types/models.js b/app/kubernetes/models/resource-types/models.js
new file mode 100644
index 000000000..4e739642e
--- /dev/null
+++ b/app/kubernetes/models/resource-types/models.js
@@ -0,0 +1,19 @@
+export const KubernetesResourceTypes = Object.freeze({
+ NAMESPACE: 'Namespace',
+ RESOURCEQUOTA: 'ResourceQuota',
+ CONFIGMAP: 'ConfigMap',
+ SECRET: 'Secret',
+ DEPLOYMENT: 'Deployment',
+ STATEFULSET: 'StatefulSet',
+ DAEMONSET: 'Daemonset',
+ PERSISTENT_VOLUME_CLAIM: 'PersistentVolumeClaim',
+ SERVICE: 'Service',
+ INGRESS: 'Ingress',
+ HORIZONTAL_POD_AUTOSCALER: 'HorizontalPodAutoscaler',
+});
+
+export const KubernetesResourceActions = Object.freeze({
+ CREATE: 'Create',
+ UPDATE: 'Update',
+ DELETE: 'Delete',
+});
diff --git a/app/kubernetes/services/applicationService.js b/app/kubernetes/services/applicationService.js
index 0452d829d..0f13b5dd2 100644
--- a/app/kubernetes/services/applicationService.js
+++ b/app/kubernetes/services/applicationService.js
@@ -220,6 +220,11 @@ class KubernetesApplicationService {
// resource creation flow
// should we keep formValues > Resource_1 || Resource_2
// or should we switch to formValues > Composite > Resource_1 || Resource_2
+ /**
+ * NOTE: Keep this method flow in sync with `getCreatedApplicationResources` method in the `applicationService` file
+ * To synchronise with kubernetes resource creation summary output, any new resources created in this method should
+ * also be displayed in the summary output (getCreatedApplicationResources)
+ */
async createAsync(formValues) {
try {
let [app, headlessService, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
@@ -266,6 +271,11 @@ class KubernetesApplicationService {
/* #region PATCH */
// this function accepts KubernetesApplicationFormValues as parameters
+ /**
+ * NOTE: Keep this method flow in sync with `getUpdatedApplicationResources` method in the `applicationService` file
+ * To synchronise with kubernetes resource creation, update and delete summary output, any new resources created
+ * in this method should also be displayed in the summary output (getUpdatedApplicationResources)
+ */
async patchAsync(oldFormValues, newFormValues) {
try {
const [oldApp, oldHeadlessService, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html
index 1e0b85111..58537b1e4 100644
--- a/app/kubernetes/views/applications/create/createApplication.html
+++ b/app/kubernetes/views/applications/create/createApplication.html
@@ -1543,6 +1543,13 @@
+
+
+
Actions
diff --git a/app/kubernetes/views/summary/resources/applicationResources.js b/app/kubernetes/views/summary/resources/applicationResources.js
new file mode 100644
index 000000000..4ea6cbf9b
--- /dev/null
+++ b/app/kubernetes/views/summary/resources/applicationResources.js
@@ -0,0 +1,242 @@
+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 { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
+import {
+ KubernetesApplication,
+ KubernetesApplicationDeploymentTypes,
+ KubernetesApplicationPublishingTypes,
+ KubernetesApplicationTypes,
+} from 'Kubernetes/models/application/models';
+import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
+import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
+import KubernetesApplicationConverter from 'Kubernetes/converters/application';
+import KubernetesServiceConverter from 'Kubernetes/converters/service';
+import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
+import KubernetesPersistentVolumeClaimConverter from 'Kubernetes/converters/persistentVolumeClaim';
+
+const { CREATE, UPDATE, DELETE } = KubernetesResourceActions;
+
+/**
+ * Get summary of Kubernetes resources to be created, updated or deleted
+ * @param {KubernetesApplicationFormValues} formValues
+ */
+export default function (formValues, oldFormValues = {}) {
+ if (oldFormValues instanceof KubernetesApplicationFormValues) {
+ const resourceSummary = getUpdatedApplicationResources(oldFormValues, formValues);
+ return resourceSummary;
+ }
+ const resourceSummary = getCreatedApplicationResources(formValues);
+ return resourceSummary;
+}
+
+/**
+ * Get summary of Kubernetes resources to be created
+ * @param {KubernetesApplicationFormValues} formValues
+ */
+function getCreatedApplicationResources(formValues) {
+ const resources = [];
+
+ let [app, headlessService, service, claims] = KubernetesApplicationConverter.applicationFormValuesToApplication(formValues);
+
+ if (service) {
+ // Service
+ resources.push({ action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: service.Name, type: service.Type || KubernetesServiceTypes.CLUSTER_IP });
+ if (formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ // Ingress
+ const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(formValues, service.Name);
+ resources.push(...getIngressUpdateSummary(formValues.OriginalIngresses, ingresses));
+ }
+ }
+
+ if (app instanceof KubernetesStatefulSet) {
+ // Service
+ resources.push({ action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: headlessService.Name, type: headlessService.Type || KubernetesServiceTypes.CLUSTER_IP });
+ } else {
+ // Persistent volume claims
+ const persistentVolumeClaimResources = claims
+ .filter((pvc) => !pvc.PreviousName && !pvc.Id)
+ .map((pvc) => ({ action: CREATE, kind: KubernetesResourceTypes.PERSISTENT_VOLUME_CLAIM, name: pvc.Name }));
+ resources.push(...persistentVolumeClaimResources);
+ }
+
+ // Horizontal pod autoscalers
+ if (formValues.AutoScaler.IsUsed && formValues.DeploymentType !== KubernetesApplicationDeploymentTypes.GLOBAL) {
+ const kind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(app);
+ const autoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(formValues, kind);
+ resources.push({ action: CREATE, kind: KubernetesResourceTypes.HORIZONTAL_POD_AUTOSCALER, name: autoScaler.Name });
+ }
+
+ // Deployment
+ const appResourceType = getApplicationResourceType(app);
+ if (appResourceType !== null) {
+ resources.push({ action: CREATE, kind: appResourceType, name: app.Name });
+ }
+
+ return resources;
+}
+
+/**
+ * Get summary of Kubernetes resources to be created, updated and/or deleted
+ * @param {KubernetesApplicationFormValues} oldFormValues
+ * @param {KubernetesApplicationFormValues} newFormValues
+ */
+function getUpdatedApplicationResources(oldFormValues, newFormValues) {
+ const resources = [];
+
+ const [oldApp, oldHeadlessService, oldService, oldClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(oldFormValues);
+ const [newApp, newHeadlessService, newService, newClaims] = KubernetesApplicationConverter.applicationFormValuesToApplication(newFormValues);
+
+ const oldAppResourceType = getApplicationResourceType(oldApp);
+ const newAppResourceType = getApplicationResourceType(newApp);
+
+ if (oldAppResourceType !== newAppResourceType) {
+ // Deployment
+ resources.push({ action: DELETE, kind: oldAppResourceType, name: oldApp.Name });
+ if (oldService) {
+ // Service
+ resources.push({ action: DELETE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP });
+ }
+ // re-creation of resources
+ const createdApplicationResourceSummary = getCreatedApplicationResources(newFormValues);
+ resources.push(...createdApplicationResourceSummary);
+ return resources;
+ }
+
+ if (newApp instanceof KubernetesStatefulSet) {
+ const headlessServiceUpdateResourceSummary = getServiceUpdateResourceSummary(oldHeadlessService, newHeadlessService);
+ if (headlessServiceUpdateResourceSummary) {
+ resources.push(headlessServiceUpdateResourceSummary);
+ }
+ } else {
+ // Persistent volume claims
+ const claimSummaries = newClaims
+ .map((pvc) => {
+ if (!pvc.PreviousName && !pvc.Id) {
+ return { action: CREATE, kind: KubernetesResourceTypes.PERSISTENT_VOLUME_CLAIM, name: pvc.Name };
+ } else if (!pvc.Id) {
+ const oldClaim = _.find(oldClaims, { Name: pvc.PreviousName });
+ return getVolumeClaimUpdateResourceSummary(oldClaim, pvc);
+ }
+ })
+ .filter((pvc) => pvc); // remove nulls
+ resources.push(...claimSummaries);
+ }
+
+ // Deployment
+ resources.push({ action: UPDATE, kind: oldAppResourceType, name: oldApp.Name });
+
+ if (oldService && newService) {
+ // Service
+ const serviceUpdateResourceSummary = getServiceUpdateResourceSummary(oldService, newService);
+ if (serviceUpdateResourceSummary) {
+ resources.push(serviceUpdateResourceSummary);
+ }
+
+ if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS || oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ // Ingress
+ const oldIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(oldFormValues, oldService.Name);
+ const newIngresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
+ resources.push(...getIngressUpdateSummary(oldIngresses, newIngresses));
+ }
+ } else if (!oldService && newService) {
+ // Service
+ resources.push({ action: CREATE, kind: KubernetesResourceTypes.SERVICE, name: newService.Name, type: newService.Type || KubernetesServiceTypes.CLUSTER_IP });
+ if (newFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ // Ingress
+ const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, newService.Name);
+ resources.push(...getIngressUpdateSummary(newFormValues.OriginalIngresses, ingresses));
+ }
+ } else if (oldService && !newService) {
+ // Service
+ resources.push({ action: DELETE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP });
+ if (oldFormValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
+ // Ingress
+ const ingresses = KubernetesIngressConverter.applicationFormValuesToIngresses(newFormValues, oldService.Name);
+ resources.push(...getIngressUpdateSummary(oldFormValues.OriginalIngresses, ingresses));
+ }
+ }
+
+ const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
+ const newAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(newFormValues, newKind);
+ if (!oldFormValues.AutoScaler.IsUsed) {
+ if (newFormValues.AutoScaler.IsUsed) {
+ // Horizontal pod autoscalers
+ resources.push({ action: CREATE, kind: KubernetesResourceTypes.HORIZONTAL_POD_AUTOSCALER, name: newAutoScaler.Name });
+ }
+ } else {
+ // Horizontal pod autoscalers
+ const oldKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(oldApp);
+ const oldAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(oldFormValues, oldKind);
+ if (newFormValues.AutoScaler.IsUsed) {
+ const hpaUpdateSummary = getHorizontalPodAutoScalerUpdateResourceSummary(oldAutoScaler, newAutoScaler);
+ if (hpaUpdateSummary) {
+ resources.push(hpaUpdateSummary);
+ }
+ } else {
+ resources.push({ action: DELETE, kind: KubernetesResourceTypes.HORIZONTAL_POD_AUTOSCALER, name: oldAutoScaler.Name });
+ }
+ }
+
+ 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) => {
+ const oldIng = _.find(oldIngresses, { Name: newIng.Name });
+ return getIngressUpdateResourceSummary(oldIng, newIng);
+ })
+ .filter((s) => s); // remove nulls
+ return ingressesSummaries;
+}
+
+// getIngressUpdateResourceSummary replicates KubernetesIngressService.patch
+function getIngressUpdateResourceSummary(oldIngress, newIngress) {
+ const payload = KubernetesIngressConverter.patchPayload(oldIngress, newIngress);
+ if (payload.length) {
+ return { action: UPDATE, kind: KubernetesResourceTypes.INGRESS, name: oldIngress.Name };
+ }
+ return null;
+}
+
+// getVolumeClaimUpdateResourceSummary replicates KubernetesPersistentVolumeClaimService.patch
+function getVolumeClaimUpdateResourceSummary(oldPVC, newPVC) {
+ const payload = KubernetesPersistentVolumeClaimConverter.patchPayload(oldPVC, newPVC);
+ if (payload.length) {
+ return { action: UPDATE, kind: KubernetesResourceTypes.PERSISTENT_VOLUME_CLAIM, name: oldPVC.Name };
+ }
+ return null;
+}
+
+// getServiceUpdateResourceSummary replicates KubernetesServiceService.patch
+function getServiceUpdateResourceSummary(oldService, newService) {
+ const payload = KubernetesServiceConverter.patchPayload(oldService, newService);
+ if (payload.length) {
+ return { action: UPDATE, kind: KubernetesResourceTypes.SERVICE, name: oldService.Name, type: oldService.Type || KubernetesServiceTypes.CLUSTER_IP };
+ }
+ return null;
+}
+
+// getHorizontalPodAutoScalerUpdateResourceSummary replicates KubernetesHorizontalPodAutoScalerService.patch
+function getHorizontalPodAutoScalerUpdateResourceSummary(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
+ const payload = KubernetesHorizontalPodAutoScalerConverter.patchPayload(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
+ if (payload.length) {
+ return { action: UPDATE, kind: KubernetesResourceTypes.HORIZONTAL_POD_AUTOSCALER, name: oldHorizontalPodAutoScaler.Name };
+ }
+ return null;
+}
diff --git a/app/kubernetes/views/summary/resources/configurationResources.js b/app/kubernetes/views/summary/resources/configurationResources.js
new file mode 100644
index 000000000..a2e04e1b0
--- /dev/null
+++ b/app/kubernetes/views/summary/resources/configurationResources.js
@@ -0,0 +1,13 @@
+import { KubernetesResourceTypes, KubernetesResourceActions } from 'Kubernetes/models/resource-types/models';
+import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
+
+const { CREATE, UPDATE } = KubernetesResourceActions;
+
+export default function (formValues) {
+ const action = formValues.Id ? UPDATE : CREATE;
+ if (formValues.Type === KubernetesConfigurationTypes.CONFIGMAP) {
+ return [{ action, kind: KubernetesResourceTypes.CONFIGMAP, name: formValues.Name }];
+ } else if (formValues.Type === KubernetesConfigurationTypes.SECRET) {
+ return [{ action, kind: KubernetesResourceTypes.SECRET, name: formValues.Name }];
+ }
+}
diff --git a/app/kubernetes/views/summary/resources/helpers.js b/app/kubernetes/views/summary/resources/helpers.js
new file mode 100644
index 000000000..53d32cb8b
--- /dev/null
+++ b/app/kubernetes/views/summary/resources/helpers.js
@@ -0,0 +1,54 @@
+import _ from 'lodash-es';
+import * as JsonPatch from 'fast-json-patch';
+import { KubernetesResourceActions } from 'Kubernetes/models/resource-types/models';
+
+function findCreateResources(newResources, oldResources) {
+ return _.differenceBy(newResources, oldResources, 'Name');
+}
+
+function findDeleteResources(newResources, oldResources) {
+ return _.differenceBy(oldResources, newResources, 'Name');
+}
+
+function findUpdateResources(newResources, oldResources) {
+ const updateResources = _.intersectionWith(newResources, oldResources, (newResource, oldResource) => {
+ // find out resources with same name but content changed
+ if (newResource.Name != oldResource.Name) {
+ return false;
+ }
+ return !isEqual(newResource, oldResource);
+ });
+
+ return updateResources;
+}
+
+function isEqual(newResource, oldResource) {
+ let patches = JsonPatch.compare(newResource, oldResource);
+ patches = _.filter(patches, (change) => {
+ return !_.includes(change.path, '$$hashKey') && !_.includes(change.path, 'Duplicate');
+ });
+
+ return !patches.length;
+}
+
+function doGetResourcesSummary(newResources, oldResources, kind, action, actionFilter) {
+ const filteredResources = actionFilter(newResources, oldResources);
+ const summary = filteredResources.map((resource) => ({ name: resource.Name, action, kind }));
+
+ return summary;
+}
+
+export function getResourcesSummary(newResources, oldResources, kind) {
+ if (!Array.isArray(newResources)) {
+ newResources = newResources ? [newResources] : [];
+ oldResources = oldResources ? [oldResources] : [];
+ }
+
+ const summary = [
+ ...doGetResourcesSummary(newResources, oldResources, kind, KubernetesResourceActions.CREATE, findCreateResources),
+ ...doGetResourcesSummary(newResources, oldResources, kind, KubernetesResourceActions.UPDATE, findUpdateResources),
+ ...doGetResourcesSummary(newResources, oldResources, kind, KubernetesResourceActions.DELETE, findDeleteResources),
+ ];
+
+ return summary;
+}
diff --git a/app/kubernetes/views/summary/resources/namespaceResources.js b/app/kubernetes/views/summary/resources/namespaceResources.js
new file mode 100644
index 000000000..1b9ec1bd2
--- /dev/null
+++ b/app/kubernetes/views/summary/resources/namespaceResources.js
@@ -0,0 +1,23 @@
+import KubernetesResourcePoolConverter from 'Kubernetes/converters/resourcePool';
+import { KubernetesResourcePoolFormValues } from 'Kubernetes/models/resource-pool/formValues';
+import { KubernetesResourceQuotaDefaults } from 'Kubernetes/models/resource-quota/models';
+import { KubernetesResourceTypes } from 'Kubernetes/models/resource-types/models';
+import { getResourcesSummary } from 'Kubernetes/views/summary/resources/helpers';
+
+export default function (newFormValues, oldFormValues) {
+ const [newNamespace, newQuota, newIngresses] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
+
+ if (!(oldFormValues instanceof KubernetesResourcePoolFormValues)) {
+ oldFormValues = new KubernetesResourcePoolFormValues(KubernetesResourceQuotaDefaults);
+ }
+
+ const [oldNamespace, oldQuota, oldIngresses] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
+
+ const resources = [
+ ...getResourcesSummary(newNamespace, oldNamespace, KubernetesResourceTypes.NAMESPACE),
+ ...getResourcesSummary(newQuota, oldQuota, KubernetesResourceTypes.RESOURCEQUOTA),
+ ...getResourcesSummary(newIngresses, oldIngresses, KubernetesResourceTypes.INGRESS),
+ ];
+
+ return resources;
+}
diff --git a/app/kubernetes/views/summary/summary.html b/app/kubernetes/views/summary/summary.html
new file mode 100644
index 000000000..8ab3e4f16
--- /dev/null
+++ b/app/kubernetes/views/summary/summary.html
@@ -0,0 +1,38 @@
+
+
+
diff --git a/app/kubernetes/views/summary/summary.js b/app/kubernetes/views/summary/summary.js
new file mode 100644
index 000000000..841a9f46a
--- /dev/null
+++ b/app/kubernetes/views/summary/summary.js
@@ -0,0 +1,9 @@
+angular.module('portainer.kubernetes').component('kubernetesSummaryView', {
+ templateUrl: './summary.html',
+ controller: 'KubernetesSummaryController',
+ controllerAs: '$ctrl',
+ bindings: {
+ formValues: '<',
+ oldFormValues: '<',
+ },
+});
diff --git a/app/kubernetes/views/summary/summaryController.js b/app/kubernetes/views/summary/summaryController.js
new file mode 100644
index 000000000..0579569e3
--- /dev/null
+++ b/app/kubernetes/views/summary/summaryController.js
@@ -0,0 +1,89 @@
+import angular from 'angular';
+import { KubernetesConfigurationFormValues } from 'Kubernetes/models/configuration/formvalues';
+import { KubernetesResourcePoolFormValues } from 'Kubernetes/models/resource-pool/formValues';
+import { KubernetesApplicationFormValues } from 'Kubernetes/models/application/formValues';
+import { KubernetesResourceActions, KubernetesResourceTypes } from 'Kubernetes/models/resource-types/models';
+import getApplicationResources from './resources/applicationResources';
+import getNamespaceResources from './resources/namespaceResources';
+import getConfigurationResources from './resources/configurationResources';
+
+class KubernetesSummaryController {
+ /* @ngInject */
+ constructor($scope, LocalStorage, KubernetesResourcePoolService) {
+ this.LocalStorage = LocalStorage;
+ this.KubernetesResourcePoolService = KubernetesResourcePoolService;
+
+ this.toggleSummary = this.toggleSummary.bind(this);
+ this.generateResourceSummaryList = this.generateResourceSummaryList.bind(this);
+
+ // Deep-watch changes on formValues property
+ $scope.$watch(
+ '$ctrl.formValues',
+ (formValues) => {
+ this.state.resources = this.generateResourceSummaryList(formValues);
+ },
+ true
+ );
+ }
+
+ getArticle(resourceType, resourceAction) {
+ let article = 'a';
+ if (resourceAction === KubernetesResourceActions.CREATE) {
+ if (resourceType === KubernetesResourceTypes.INGRESS) {
+ article = 'an';
+ }
+ } else {
+ article = 'the';
+ }
+
+ return article;
+ }
+
+ /**
+ * toggleSummary toggles the summary panel state and persists it to browser localstorage
+ */
+ toggleSummary() {
+ this.state.expandedTemplate = !this.state.expandedTemplate;
+ this.LocalStorage.storeKubernetesSummaryToggle(this.state.expandedTemplate);
+ }
+
+ /**
+ * generateResourceSummaryList maps formValues to custom object
+ * @param {object} formValues
+ * @returns {object} => { action: "string", kind: "string", name: "string" }
+ */
+ generateResourceSummaryList(formValues) {
+ const oldFormValues = this.oldFormValues;
+
+ if (formValues instanceof KubernetesConfigurationFormValues) {
+ // Configuration
+ return getConfigurationResources(formValues);
+ } else if (formValues instanceof KubernetesResourcePoolFormValues) {
+ // Namespaces
+ return getNamespaceResources(formValues, oldFormValues);
+ } else if (formValues instanceof KubernetesApplicationFormValues) {
+ // Applications
+
+ // extract cpu and memory requests & limits for pod
+ this.state.limits = { cpu: formValues.CpuLimit, memory: formValues.MemoryLimit };
+
+ return getApplicationResources(formValues, oldFormValues);
+ }
+
+ return [];
+ }
+
+ $onInit() {
+ const toggleValue = this.LocalStorage.getKubernetesSummaryToggle();
+ const expanded = typeof toggleValue === 'boolean' ? toggleValue : true;
+
+ this.state = {
+ expandedTemplate: expanded,
+ resources: [],
+ limits: { cpu: null, memory: null },
+ };
+ }
+}
+
+export default KubernetesSummaryController;
+angular.module('portainer.kubernetes').controller('KubernetesSummaryController', KubernetesSummaryController);
diff --git a/app/portainer/services/localStorage.js b/app/portainer/services/localStorage.js
index 5026331bc..558201942 100644
--- a/app/portainer/services/localStorage.js
+++ b/app/portainer/services/localStorage.js
@@ -144,6 +144,12 @@ angular.module('portainer.app').factory('LocalStorage', [
cleanEndpointData() {
localStorageService.remove('ENDPOINT_ID', 'ENDPOINT_PUBLIC_URL', 'ENDPOINT_OFFLINE_MODE', 'ENDPOINTS_DATA', 'ENDPOINT_STATE');
},
+ storeKubernetesSummaryToggle(value) {
+ localStorageService.set('kubernetes_summary_expanded', value);
+ },
+ getKubernetesSummaryToggle() {
+ return localStorageService.get('kubernetes_summary_expanded');
+ },
};
},
]);