From bdde27813911d2c8fb1bb8ecd709f221b7f4eab0 Mon Sep 17 00:00:00 2001
From: Prabhat Khera <91852476+prabhat-org@users.noreply.github.com>
Date: Wed, 8 Mar 2023 10:27:42 +1300
Subject: [PATCH] feat(applications): application page performance improvements
EE-4956 (#8569)
---
.../applicationsDatatable.html | 16 ++++++
.../applicationsDatatable.js | 5 ++
.../applicationsDatatableController.js | 50 +++++++++++++++++
.../applicationsStacksDatatable.html | 16 ++++++
.../applicationsStacksDatatable.js | 5 ++
.../applicationsStacksDatatableController.js | 53 ++++++++++++++++++-
app/kubernetes/services/namespaceService.js | 25 +++++++--
.../views/applications/applications.html | 10 ++++
.../applications/applicationsController.js | 43 ++++++++++++++-
.../edit/resourcePoolController.js | 5 +-
.../views/resource-pools/resourcePools.html | 2 +-
.../resource-pools/resourcePoolsController.js | 7 +++
app/portainer/services/localStorage.js | 11 +++-
13 files changed, 239 insertions(+), 9 deletions(-)
diff --git a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
index 7e75312f8..25fe51b06 100644
--- a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
+++ b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
@@ -8,6 +8,22 @@
Applications
+
{
+ if (!this.settings.showSystem && ns.IsSystem) {
+ return false;
+ }
+ namespaces.push({ Name: ns.Name, Value: ns.Name, IsSystem: ns.IsSystem });
+ });
+ this.state.namespaces = namespaces;
+
+ if (this.state.namespace && !this.state.namespaces.find((ns) => ns.Name === this.state.namespace)) {
+ if (this.state.namespaces.length > 1) {
+ let defaultNS = this.state.namespaces.find((ns) => ns.Name === 'default');
+ defaultNS = defaultNS || this.state.namespaces[1];
+ this.state.namespace = defaultNS.Value;
+ } else {
+ this.state.namespace = this.state.namespaces[0].Value;
+ }
+ this.onChangeNamespaceDropdown(this.state.namespace);
+ }
+ }
+ };
+
+ this.$onChanges = function () {
+ if (typeof this.isSystemResources !== 'undefined') {
+ this.settings.showSystem = this.isSystemResources;
+ DatatableService.setDataTableSettings(this.settingsKey, this.settings);
+ }
+ this.state.namespace = this.namespace;
+ this.updateNamespace();
+ this.prepareTableFromDataset();
+ };
+
this.$onInit = function () {
this.isAdmin = Authentication.isAdmin();
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
@@ -103,11 +145,20 @@ angular.module('portainer.kubernetes').controller('KubernetesApplicationsStacksD
this.filters.state.open = false;
}
- var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
+ var storedSettings = DatatableService.getDataTableSettings(this.settingsKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
+
+ this.setSystemResources && this.setSystemResources(this.settings.showSystem);
}
+
+ // Set the default selected namespace
+ if (!this.state.namespace) {
+ this.state.namespace = this.namespace;
+ }
+
+ this.updateNamespace();
this.onSettingsRepeaterChange();
};
},
diff --git a/app/kubernetes/services/namespaceService.js b/app/kubernetes/services/namespaceService.js
index 3e25ae636..fe54c1800 100644
--- a/app/kubernetes/services/namespaceService.js
+++ b/app/kubernetes/services/namespaceService.js
@@ -7,9 +7,10 @@ import $allSettled from 'Portainer/services/allSettled';
class KubernetesNamespaceService {
/* @ngInject */
- constructor($async, KubernetesNamespaces) {
+ constructor($async, KubernetesNamespaces, LocalStorage) {
this.$async = $async;
this.KubernetesNamespaces = KubernetesNamespaces;
+ this.LocalStorage = LocalStorage;
this.getAsync = this.getAsync.bind(this);
this.getAllAsync = this.getAllAsync.bind(this);
@@ -17,6 +18,7 @@ class KubernetesNamespaceService {
this.deleteAsync = this.deleteAsync.bind(this);
this.getJSONAsync = this.getJSONAsync.bind(this);
this.updateFinalizeAsync = this.updateFinalizeAsync.bind(this);
+ this.refreshCacheAsync = this.refreshCacheAsync.bind(this);
}
/**
@@ -79,11 +81,19 @@ class KubernetesNamespaceService {
}
}
- get(name) {
+ async get(name) {
if (name) {
return this.$async(this.getAsync, name);
}
- return this.$async(this.getAllAsync);
+ const cachedAllowedNamespaces = this.LocalStorage.getAllowedNamespaces();
+ if (cachedAllowedNamespaces) {
+ updateNamespaces(cachedAllowedNamespaces);
+ return cachedAllowedNamespaces;
+ } else {
+ const allowedNamespaces = await this.getAllAsync();
+ this.LocalStorage.storeAllowedNamespaces(allowedNamespaces);
+ return allowedNamespaces;
+ }
}
/**
@@ -94,6 +104,7 @@ class KubernetesNamespaceService {
const payload = KubernetesNamespaceConverter.createPayload(namespace);
const params = {};
const data = await this.KubernetesNamespaces().create(params, payload).$promise;
+ await this.refreshCacheAsync();
return data;
} catch (err) {
throw new PortainerError('Unable to create namespace', err);
@@ -104,6 +115,14 @@ class KubernetesNamespaceService {
return this.$async(this.createAsync, namespace);
}
+ async refreshCacheAsync() {
+ this.LocalStorage.deleteAllowedNamespaces();
+ const allowedNamespaces = await this.getAllAsync();
+ this.LocalStorage.storeAllowedNamespaces(allowedNamespaces);
+ updateNamespaces(allowedNamespaces);
+ return allowedNamespaces;
+ }
+
/**
* DELETE
*/
diff --git a/app/kubernetes/views/applications/applications.html b/app/kubernetes/views/applications/applications.html
index f36252acf..a7fecea89 100644
--- a/app/kubernetes/views/applications/applications.html
+++ b/app/kubernetes/views/applications/applications.html
@@ -19,6 +19,11 @@
refresh-callback="ctrl.getApplications"
on-publishing-mode-click="(ctrl.onPublishingModeClick)"
is-primary="true"
+ namespaces="ctrl.state.namespaces"
+ namespace="ctrl.state.namespace"
+ on-change-namespace-dropdown="(ctrl.onChangeNamespaceDropdown)"
+ is-system-resources="ctrl.state.isSystemResources"
+ set-system-resources="(ctrl.setSystemResources)"
>
@@ -30,6 +35,11 @@
order-by="Name"
refresh-callback="ctrl.getApplications"
remove-action="ctrl.removeStacksAction"
+ namespaces="ctrl.state.namespaces"
+ namespace="ctrl.state.namespace"
+ on-change-namespace-dropdown="(ctrl.onChangeNamespaceDropdown)"
+ is-system-resources="ctrl.state.isSystemResources"
+ set-system-resources="(ctrl.setSystemResources)"
>
diff --git a/app/kubernetes/views/applications/applicationsController.js b/app/kubernetes/views/applications/applicationsController.js
index a339ee2fd..028a7d927 100644
--- a/app/kubernetes/views/applications/applicationsController.js
+++ b/app/kubernetes/views/applications/applicationsController.js
@@ -9,9 +9,22 @@ import { confirmDelete } from '@@/modals/confirm';
class KubernetesApplicationsController {
/* @ngInject */
- constructor($async, $state, Notifications, KubernetesApplicationService, HelmService, KubernetesConfigurationService, Authentication, LocalStorage, StackService) {
+ constructor(
+ $async,
+ $state,
+ $scope,
+ Notifications,
+ KubernetesApplicationService,
+ HelmService,
+ KubernetesConfigurationService,
+ Authentication,
+ LocalStorage,
+ StackService,
+ KubernetesNamespaceService
+ ) {
this.$async = $async;
this.$state = $state;
+ this.$scope = $scope;
this.Notifications = Notifications;
this.KubernetesApplicationService = KubernetesApplicationService;
this.HelmService = HelmService;
@@ -19,6 +32,7 @@ class KubernetesApplicationsController {
this.Authentication = Authentication;
this.LocalStorage = LocalStorage;
this.StackService = StackService;
+ this.KubernetesNamespaceService = KubernetesNamespaceService;
this.onInit = this.onInit.bind(this);
this.getApplications = this.getApplications.bind(this);
@@ -28,6 +42,8 @@ class KubernetesApplicationsController {
this.removeStacksAction = this.removeStacksAction.bind(this);
this.removeStacksActionAsync = this.removeStacksActionAsync.bind(this);
this.onPublishingModeClick = this.onPublishingModeClick.bind(this);
+ this.onChangeNamespaceDropdown = this.onChangeNamespaceDropdown.bind(this);
+ this.setSystemResources = this.setSystemResources.bind(this);
}
selectTab(index) {
@@ -126,20 +142,34 @@ class KubernetesApplicationsController {
});
}
+ onChangeNamespaceDropdown(namespace) {
+ this.state.namespace = namespace;
+ this.getApplicationsAsync();
+ }
+
async getApplicationsAsync() {
try {
- const [applications, configurations] = await Promise.all([this.KubernetesApplicationService.get(), this.KubernetesConfigurationService.get()]);
+ const [applications, configurations] = await Promise.all([
+ this.KubernetesApplicationService.get(this.state.namespace),
+ this.KubernetesConfigurationService.get(this.state.namespace),
+ ]);
const configuredApplications = KubernetesConfigurationHelper.getApplicationConfigurations(applications, configurations);
const { helmApplications, nonHelmApplications } = KubernetesApplicationHelper.getNestedApplications(configuredApplications);
this.state.applications = [...helmApplications, ...nonHelmApplications];
this.state.stacks = KubernetesStackHelper.stacksFromApplications(applications);
this.state.ports = KubernetesApplicationHelper.portMappingsFromApplications(applications);
+
+ this.$scope.$apply();
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve applications');
}
}
+ setSystemResources(flag) {
+ this.state.isSystemResources = flag;
+ }
+
getApplications() {
return this.$async(this.getApplicationsAsync);
}
@@ -153,7 +183,16 @@ class KubernetesApplicationsController {
applications: [],
stacks: [],
ports: [],
+ namespaces: [],
+ namespace: '',
+ isSystemResources: undefined,
};
+
+ this.state.namespaces = await this.KubernetesNamespaceService.get();
+ this.state.namespaces = this.state.namespaces.filter((n) => n.Status !== 'Terminating');
+ this.state.namespaces = _.sortBy(this.state.namespaces, 'Name');
+ this.state.namespace = this.state.namespaces.length ? (this.state.namespaces.find((n) => n.Name === 'default') ? 'default' : this.state.namespaces[0].Name) : '';
+
await this.getApplications();
this.state.viewReady = true;
diff --git a/app/kubernetes/views/resource-pools/edit/resourcePoolController.js b/app/kubernetes/views/resource-pools/edit/resourcePoolController.js
index 583e055e7..fd2224730 100644
--- a/app/kubernetes/views/resource-pools/edit/resourcePoolController.js
+++ b/app/kubernetes/views/resource-pools/edit/resourcePoolController.js
@@ -35,7 +35,8 @@ class KubernetesResourcePoolController {
KubernetesPodService,
KubernetesApplicationService,
KubernetesIngressService,
- KubernetesVolumeService
+ KubernetesVolumeService,
+ KubernetesNamespaceService
) {
Object.assign(this, {
$async,
@@ -54,6 +55,7 @@ class KubernetesResourcePoolController {
KubernetesApplicationService,
KubernetesIngressService,
KubernetesVolumeService,
+ KubernetesNamespaceService,
});
this.IngressClassTypes = KubernetesIngressClassTypes;
@@ -219,6 +221,7 @@ class KubernetesResourcePoolController {
return;
}
await this.KubernetesResourcePoolService.toggleSystem(this.endpoint.Id, namespaceName, !this.isSystem);
+ await this.KubernetesNamespaceService.refreshCacheAsync();
this.Notifications.success('Namespace successfully updated', namespaceName);
this.$state.reload(this.$state.current);
diff --git a/app/kubernetes/views/resource-pools/resourcePools.html b/app/kubernetes/views/resource-pools/resourcePools.html
index c63736563..9b8b56230 100644
--- a/app/kubernetes/views/resource-pools/resourcePools.html
+++ b/app/kubernetes/views/resource-pools/resourcePools.html
@@ -1,4 +1,4 @@
-
+
diff --git a/app/kubernetes/views/resource-pools/resourcePoolsController.js b/app/kubernetes/views/resource-pools/resourcePoolsController.js
index 3aacad7f1..55fd2a3ed 100644
--- a/app/kubernetes/views/resource-pools/resourcePoolsController.js
+++ b/app/kubernetes/views/resource-pools/resourcePoolsController.js
@@ -17,6 +17,12 @@ class KubernetesResourcePoolsController {
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() {
+ await this.KubernetesNamespaceService.refreshCacheAsync();
+ this.$state.reload(this.$state.current);
}
async removeActionAsync(selectedItems) {
@@ -48,6 +54,7 @@ class KubernetesResourcePoolsController {
}
}
}
+ await this.KubernetesNamespaceService.refreshCacheAsync();
}
removeAction(selectedItems) {
diff --git a/app/portainer/services/localStorage.js b/app/portainer/services/localStorage.js
index 632dfff18..3256d523b 100644
--- a/app/portainer/services/localStorage.js
+++ b/app/portainer/services/localStorage.js
@@ -112,7 +112,7 @@ angular.module('portainer.app').factory('LocalStorage', [
localStorageService.clearAll();
},
cleanAuthData() {
- localStorageService.remove('JWT', 'APPLICATION_STATE', 'LOGIN_STATE_UUID');
+ localStorageService.remove('JWT', 'APPLICATION_STATE', 'LOGIN_STATE_UUID', 'ALLOWED_NAMESPACES');
},
storeKubernetesSummaryToggle(value) {
localStorageService.set('kubernetes_summary_expanded', value);
@@ -120,6 +120,15 @@ angular.module('portainer.app').factory('LocalStorage', [
getKubernetesSummaryToggle() {
return localStorageService.get('kubernetes_summary_expanded');
},
+ storeAllowedNamespaces: function (namespaces) {
+ localStorageService.set('ALLOWED_NAMESPACES', namespaces);
+ },
+ getAllowedNamespaces: function () {
+ return localStorageService.get('ALLOWED_NAMESPACES');
+ },
+ deleteAllowedNamespaces: function () {
+ localStorageService.remove('ALLOWED_NAMESPACES');
+ },
};
},
]);