feat(applications): application page performance improvements EE-4956 (#8569)

pull/8617/head
Prabhat Khera 2023-03-08 10:27:42 +13:00 committed by GitHub
parent 01ea9afe33
commit bdde278139
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 239 additions and 9 deletions

View File

@ -8,6 +8,22 @@
</div> </div>
Applications Applications
</div> </div>
<div class="form-group namespaces !mb-0 !mr-0 min-w-[280px]">
<div class="input-group">
<span class="input-group-addon">
<pr-icon icon="'filter'"></pr-icon>
Namespace
</span>
<select
class="form-control"
ng-model="$ctrl.state.namespace"
ng-change="$ctrl.onChangeNamespace()"
data-cy="component-namespaceSelect"
ng-options="o.Value as (o.Name + (o.IsSystem ? ' - system' : '')) for o in $ctrl.state.namespaces"
>
</select>
</div>
</div>
<div class="searchBar vertical-center !mr-0 min-w-[280px]"> <div class="searchBar vertical-center !mr-0 min-w-[280px]">
<pr-icon icon="'search'" class-name="'searchIcon'"></pr-icon> <pr-icon icon="'search'" class-name="'searchIcon'"></pr-icon>
<input <input

View File

@ -15,5 +15,10 @@ angular.module('portainer.kubernetes').component('kubernetesApplicationsDatatabl
refreshCallback: '<', refreshCallback: '<',
onPublishingModeClick: '<', onPublishingModeClick: '<',
isPrimary: '<', isPrimary: '<',
namespaces: '<',
namespace: '<',
onChangeNamespaceDropdown: '<',
isSystemResources: '<',
setSystemResources: '<',
}, },
}); });

View File

@ -21,6 +21,8 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
this.state = Object.assign(this.state, { this.state = Object.assign(this.state, {
expandAll: false, expandAll: false,
expandedItems: [], expandedItems: [],
namespace: '',
namespaces: [],
}); });
this.filters = { this.filters = {
@ -70,6 +72,8 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
}; };
this.onSettingsShowSystemChange = function () { this.onSettingsShowSystemChange = function () {
this.updateNamespace();
this.setSystemResources(this.settings.showSystem);
DatatableService.setDataTableSettings(this.tableKey, this.settings); DatatableService.setDataTableSettings(this.tableKey, this.settings);
}; };
@ -135,6 +139,43 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
this.filters.state.values = _.uniqBy(availableTypeFilters, 'type'); this.filters.state.values = _.uniqBy(availableTypeFilters, 'type');
}; };
this.onChangeNamespace = function () {
this.onChangeNamespaceDropdown(this.state.namespace);
};
this.updateNamespace = function () {
if (this.namespaces) {
const namespaces = [{ Name: 'All namespaces', Value: '', IsSystem: false }];
this.namespaces.find((ns) => {
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.$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.$onInit = function () {
this.isAdmin = Authentication.isAdmin(); this.isAdmin = Authentication.isAdmin();
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes; this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
@ -172,7 +213,16 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
if (storedSettings !== null) { if (storedSettings !== null) {
this.settings = storedSettings; this.settings = storedSettings;
this.settings.open = false; 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(); this.onSettingsRepeaterChange();
}; };
}, },

View File

@ -10,6 +10,22 @@
Stacks Stacks
</div> </div>
<!-- actions --> <!-- actions -->
<div class="form-group namespaces !mb-0 !mr-0 min-w-[280px]">
<div class="input-group">
<span class="input-group-addon">
<pr-icon icon="'filter'"></pr-icon>
Namespace
</span>
<select
class="form-control"
ng-model="$ctrl.state.namespace"
ng-change="$ctrl.onChangeNamespace()"
data-cy="component-namespaceSelect"
ng-options="o.Value as (o.Name + (o.IsSystem ? ' - system' : '')) for o in $ctrl.state.namespaces"
>
</select>
</div>
</div>
<div class="searchBar vertical-center"> <div class="searchBar vertical-center">
<pr-icon icon="'search'" class-name="'!h-3'"></pr-icon> <pr-icon icon="'search'" class-name="'!h-3'"></pr-icon>
<input <input

View File

@ -10,5 +10,10 @@ angular.module('portainer.kubernetes').component('kubernetesApplicationsStacksDa
reverseOrder: '<', reverseOrder: '<',
refreshCallback: '<', refreshCallback: '<',
removeAction: '<', removeAction: '<',
namespaces: '<',
namespace: '<',
onChangeNamespaceDropdown: '<',
isSystemResources: '<',
setSystemResources: '<',
}, },
}); });

View File

@ -13,6 +13,8 @@ angular.module('portainer.kubernetes').controller('KubernetesApplicationsStacksD
this.state = Object.assign(this.state, { this.state = Object.assign(this.state, {
expandedItems: [], expandedItems: [],
expandAll: false, expandAll: false,
namespace: '',
namespaces: [],
}); });
var ctrl = this; var ctrl = this;
@ -22,6 +24,8 @@ angular.module('portainer.kubernetes').controller('KubernetesApplicationsStacksD
}); });
this.onSettingsShowSystemChange = function () { this.onSettingsShowSystemChange = function () {
this.updateNamespace();
this.setSystemResources(this.settings.showSystem);
DatatableService.setDataTableSettings(this.tableKey, this.settings); DatatableService.setDataTableSettings(this.tableKey, this.settings);
}; };
@ -76,6 +80,44 @@ angular.module('portainer.kubernetes').controller('KubernetesApplicationsStacksD
}); });
}; };
this.onChangeNamespace = function () {
this.onChangeNamespaceDropdown(this.state.namespace);
};
this.updateNamespace = function () {
if (this.namespaces) {
const namespaces = [{ Name: 'All namespaces', Value: '', IsSystem: false }];
this.namespaces.find((ns) => {
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.$onInit = function () {
this.isAdmin = Authentication.isAdmin(); this.isAdmin = Authentication.isAdmin();
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes; this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
@ -103,11 +145,20 @@ angular.module('portainer.kubernetes').controller('KubernetesApplicationsStacksD
this.filters.state.open = false; this.filters.state.open = false;
} }
var storedSettings = DatatableService.getDataTableSettings(this.tableKey); var storedSettings = DatatableService.getDataTableSettings(this.settingsKey);
if (storedSettings !== null) { if (storedSettings !== null) {
this.settings = storedSettings; this.settings = storedSettings;
this.settings.open = false; 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(); this.onSettingsRepeaterChange();
}; };
}, },

View File

@ -7,9 +7,10 @@ import $allSettled from 'Portainer/services/allSettled';
class KubernetesNamespaceService { class KubernetesNamespaceService {
/* @ngInject */ /* @ngInject */
constructor($async, KubernetesNamespaces) { constructor($async, KubernetesNamespaces, LocalStorage) {
this.$async = $async; this.$async = $async;
this.KubernetesNamespaces = KubernetesNamespaces; this.KubernetesNamespaces = KubernetesNamespaces;
this.LocalStorage = LocalStorage;
this.getAsync = this.getAsync.bind(this); this.getAsync = this.getAsync.bind(this);
this.getAllAsync = this.getAllAsync.bind(this); this.getAllAsync = this.getAllAsync.bind(this);
@ -17,6 +18,7 @@ class KubernetesNamespaceService {
this.deleteAsync = this.deleteAsync.bind(this); this.deleteAsync = this.deleteAsync.bind(this);
this.getJSONAsync = this.getJSONAsync.bind(this); this.getJSONAsync = this.getJSONAsync.bind(this);
this.updateFinalizeAsync = this.updateFinalizeAsync.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) { if (name) {
return this.$async(this.getAsync, 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 payload = KubernetesNamespaceConverter.createPayload(namespace);
const params = {}; const params = {};
const data = await this.KubernetesNamespaces().create(params, payload).$promise; const data = await this.KubernetesNamespaces().create(params, payload).$promise;
await this.refreshCacheAsync();
return data; return data;
} catch (err) { } catch (err) {
throw new PortainerError('Unable to create namespace', err); throw new PortainerError('Unable to create namespace', err);
@ -104,6 +115,14 @@ class KubernetesNamespaceService {
return this.$async(this.createAsync, namespace); 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 * DELETE
*/ */

View File

@ -19,6 +19,11 @@
refresh-callback="ctrl.getApplications" refresh-callback="ctrl.getApplications"
on-publishing-mode-click="(ctrl.onPublishingModeClick)" on-publishing-mode-click="(ctrl.onPublishingModeClick)"
is-primary="true" 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)"
> >
</kubernetes-applications-datatable> </kubernetes-applications-datatable>
</uib-tab> </uib-tab>
@ -30,6 +35,11 @@
order-by="Name" order-by="Name"
refresh-callback="ctrl.getApplications" refresh-callback="ctrl.getApplications"
remove-action="ctrl.removeStacksAction" 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)"
> >
</kubernetes-applications-stacks-datatable> </kubernetes-applications-stacks-datatable>
</uib-tab> </uib-tab>

View File

@ -9,9 +9,22 @@ import { confirmDelete } from '@@/modals/confirm';
class KubernetesApplicationsController { class KubernetesApplicationsController {
/* @ngInject */ /* @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.$async = $async;
this.$state = $state; this.$state = $state;
this.$scope = $scope;
this.Notifications = Notifications; this.Notifications = Notifications;
this.KubernetesApplicationService = KubernetesApplicationService; this.KubernetesApplicationService = KubernetesApplicationService;
this.HelmService = HelmService; this.HelmService = HelmService;
@ -19,6 +32,7 @@ class KubernetesApplicationsController {
this.Authentication = Authentication; this.Authentication = Authentication;
this.LocalStorage = LocalStorage; this.LocalStorage = LocalStorage;
this.StackService = StackService; this.StackService = StackService;
this.KubernetesNamespaceService = KubernetesNamespaceService;
this.onInit = this.onInit.bind(this); this.onInit = this.onInit.bind(this);
this.getApplications = this.getApplications.bind(this); this.getApplications = this.getApplications.bind(this);
@ -28,6 +42,8 @@ class KubernetesApplicationsController {
this.removeStacksAction = this.removeStacksAction.bind(this); this.removeStacksAction = this.removeStacksAction.bind(this);
this.removeStacksActionAsync = this.removeStacksActionAsync.bind(this); this.removeStacksActionAsync = this.removeStacksActionAsync.bind(this);
this.onPublishingModeClick = this.onPublishingModeClick.bind(this); this.onPublishingModeClick = this.onPublishingModeClick.bind(this);
this.onChangeNamespaceDropdown = this.onChangeNamespaceDropdown.bind(this);
this.setSystemResources = this.setSystemResources.bind(this);
} }
selectTab(index) { selectTab(index) {
@ -126,20 +142,34 @@ class KubernetesApplicationsController {
}); });
} }
onChangeNamespaceDropdown(namespace) {
this.state.namespace = namespace;
this.getApplicationsAsync();
}
async getApplicationsAsync() { async getApplicationsAsync() {
try { 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 configuredApplications = KubernetesConfigurationHelper.getApplicationConfigurations(applications, configurations);
const { helmApplications, nonHelmApplications } = KubernetesApplicationHelper.getNestedApplications(configuredApplications); const { helmApplications, nonHelmApplications } = KubernetesApplicationHelper.getNestedApplications(configuredApplications);
this.state.applications = [...helmApplications, ...nonHelmApplications]; this.state.applications = [...helmApplications, ...nonHelmApplications];
this.state.stacks = KubernetesStackHelper.stacksFromApplications(applications); this.state.stacks = KubernetesStackHelper.stacksFromApplications(applications);
this.state.ports = KubernetesApplicationHelper.portMappingsFromApplications(applications); this.state.ports = KubernetesApplicationHelper.portMappingsFromApplications(applications);
this.$scope.$apply();
} catch (err) { } catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve applications'); this.Notifications.error('Failure', err, 'Unable to retrieve applications');
} }
} }
setSystemResources(flag) {
this.state.isSystemResources = flag;
}
getApplications() { getApplications() {
return this.$async(this.getApplicationsAsync); return this.$async(this.getApplicationsAsync);
} }
@ -153,7 +183,16 @@ class KubernetesApplicationsController {
applications: [], applications: [],
stacks: [], stacks: [],
ports: [], 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(); await this.getApplications();
this.state.viewReady = true; this.state.viewReady = true;

View File

@ -35,7 +35,8 @@ class KubernetesResourcePoolController {
KubernetesPodService, KubernetesPodService,
KubernetesApplicationService, KubernetesApplicationService,
KubernetesIngressService, KubernetesIngressService,
KubernetesVolumeService KubernetesVolumeService,
KubernetesNamespaceService
) { ) {
Object.assign(this, { Object.assign(this, {
$async, $async,
@ -54,6 +55,7 @@ class KubernetesResourcePoolController {
KubernetesApplicationService, KubernetesApplicationService,
KubernetesIngressService, KubernetesIngressService,
KubernetesVolumeService, KubernetesVolumeService,
KubernetesNamespaceService,
}); });
this.IngressClassTypes = KubernetesIngressClassTypes; this.IngressClassTypes = KubernetesIngressClassTypes;
@ -219,6 +221,7 @@ class KubernetesResourcePoolController {
return; return;
} }
await this.KubernetesResourcePoolService.toggleSystem(this.endpoint.Id, namespaceName, !this.isSystem); await this.KubernetesResourcePoolService.toggleSystem(this.endpoint.Id, namespaceName, !this.isSystem);
await this.KubernetesNamespaceService.refreshCacheAsync();
this.Notifications.success('Namespace successfully updated', namespaceName); this.Notifications.success('Namespace successfully updated', namespaceName);
this.$state.reload(this.$state.current); this.$state.reload(this.$state.current);

View File

@ -1,4 +1,4 @@
<page-header ng-if="ctrl.state.viewReady" title="'Namespace list'" breadcrumbs="['Namespaces']" reload="true"></page-header> <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> <kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>

View File

@ -17,6 +17,12 @@ class KubernetesResourcePoolsController {
this.getResourcePoolsAsync = this.getResourcePoolsAsync.bind(this); this.getResourcePoolsAsync = this.getResourcePoolsAsync.bind(this);
this.removeAction = this.removeAction.bind(this); this.removeAction = this.removeAction.bind(this);
this.removeActionAsync = this.removeActionAsync.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) { async removeActionAsync(selectedItems) {
@ -48,6 +54,7 @@ class KubernetesResourcePoolsController {
} }
} }
} }
await this.KubernetesNamespaceService.refreshCacheAsync();
} }
removeAction(selectedItems) { removeAction(selectedItems) {

View File

@ -112,7 +112,7 @@ angular.module('portainer.app').factory('LocalStorage', [
localStorageService.clearAll(); localStorageService.clearAll();
}, },
cleanAuthData() { cleanAuthData() {
localStorageService.remove('JWT', 'APPLICATION_STATE', 'LOGIN_STATE_UUID'); localStorageService.remove('JWT', 'APPLICATION_STATE', 'LOGIN_STATE_UUID', 'ALLOWED_NAMESPACES');
}, },
storeKubernetesSummaryToggle(value) { storeKubernetesSummaryToggle(value) {
localStorageService.set('kubernetes_summary_expanded', value); localStorageService.set('kubernetes_summary_expanded', value);
@ -120,6 +120,15 @@ angular.module('portainer.app').factory('LocalStorage', [
getKubernetesSummaryToggle() { getKubernetesSummaryToggle() {
return localStorageService.get('kubernetes_summary_expanded'); 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');
},
}; };
}, },
]); ]);