fix(ui): namespace caching issue EE-5273 (#8709)

* fix namespace caching issue

* fix(apps): add loading state [EE-5273]

* rm endpoint provider

* fix(namespace): remove caching [EE-5273]

* variable typo

---------

Co-authored-by: testa113 <testa113>
pull/8729/head
Prabhat Khera 2023-03-31 13:24:57 +13:00 committed by GitHub
parent d64e7eacfc
commit fc1aec3bb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 25 additions and 48 deletions

View File

@ -244,6 +244,7 @@
</thead> </thead>
<tbody> <tbody>
<tr <tr
ng-show="!$ctrl.isAppsLoading"
ng-click="$ctrl.expandItem(item, !$ctrl.isItemExpanded(item))" ng-click="$ctrl.expandItem(item, !$ctrl.isItemExpanded(item))"
dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.applyFilters | filter:$ctrl.state.textFilter | filter:$ctrl.isDisplayed | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))" dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.applyFilters | filter:$ctrl.state.textFilter | filter:$ctrl.isDisplayed | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))"
ng-class="{ active: item.Checked, interactive: $ctrl.isExpandable(item), 'secondary-body': !$ctrl.isPrimary }" ng-class="{ active: item.Checked, interactive: $ctrl.isExpandable(item), 'secondary-body': !$ctrl.isPrimary }"
@ -335,16 +336,16 @@
</span> </span>
</td> </td>
</tr> </tr>
<tr ng-if="!$ctrl.dataset"> <tr ng-if="$ctrl.isAppsLoading">
<td colspan="8" class="text-muted text-center">Loading...</td> <td colspan="8" class="text-muted text-center">Loading...</td>
</tr> </tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0"> <tr ng-if="$ctrl.state.filteredDataSet.length === 0 && !$ctrl.isAppsLoading">
<td colspan="8" class="text-muted text-center">No application available.</td> <td colspan="8" class="text-muted text-center">No application available.</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div ng-if="$ctrl.isPrimary" class="footer pl-5" ng-if="$ctrl.dataset"> <div class="footer pl-5" ng-if="$ctrl.isPrimary && $ctrl.dataset">
<div class="infoBar !ml-0" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div> <div class="infoBar !ml-0" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls"> <div class="paginationControls">
<form class="form-inline"> <form class="form-inline">

View File

@ -18,6 +18,7 @@ angular.module('portainer.kubernetes').component('kubernetesApplicationsDatatabl
namespaces: '<', namespaces: '<',
namespace: '<', namespace: '<',
onChangeNamespaceDropdown: '<', onChangeNamespaceDropdown: '<',
isAppsLoading: '<',
isSystemResources: '<', isSystemResources: '<',
setSystemResources: '<', setSystemResources: '<',
}, },

View File

@ -168,6 +168,7 @@
</thead> </thead>
<tbody> <tbody>
<tr <tr
ng-show="!$ctrl.isAppsLoading"
dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | filter: $ctrl.isDisplayed | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))" dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | filter: $ctrl.isDisplayed | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))"
ng-class="{ active: item.Checked, 'datatable-highlighted': item.Highlighted }" ng-class="{ active: item.Checked, 'datatable-highlighted': item.Highlighted }"
ng-click="$ctrl.expandItem(item, !item.Expanded)" ng-click="$ctrl.expandItem(item, !item.Expanded)"
@ -220,10 +221,10 @@
> >
</td> </td>
</tr> </tr>
<tr ng-if="!$ctrl.dataset"> <tr ng-if="$ctrl.isAppsLoading">
<td colspan="5" class="text-muted text-center">Loading...</td> <td colspan="5" class="text-muted text-center">Loading...</td>
</tr> </tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0"> <tr ng-if="$ctrl.state.filteredDataSet.length === 0 && !$ctrl.isAppsLoading">
<td colspan="5" class="text-muted text-center">No stack available.</td> <td colspan="5" class="text-muted text-center">No stack available.</td>
</tr> </tr>
</tbody> </tbody>

View File

@ -13,6 +13,7 @@ angular.module('portainer.kubernetes').component('kubernetesApplicationsStacksDa
namespaces: '<', namespaces: '<',
namespace: '<', namespace: '<',
onChangeNamespaceDropdown: '<', onChangeNamespaceDropdown: '<',
isAppsLoading: '<',
isSystemResources: '<', isSystemResources: '<',
setSystemResources: '<', setSystemResources: '<',
}, },

View File

@ -18,7 +18,6 @@ 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);
} }
/** /**
@ -81,20 +80,13 @@ class KubernetesNamespaceService {
} }
} }
async get(name, refreshCache = false) { async get(name) {
if (name) { if (name) {
return this.$async(this.getAsync, name); return this.$async(this.getAsync, name);
} }
const cachedAllowedNamespaces = this.LocalStorage.getAllowedNamespaces(); const allowedNamespaces = await this.getAllAsync();
if (!cachedAllowedNamespaces || refreshCache) { updateNamespaces(allowedNamespaces);
const allowedNamespaces = await this.getAllAsync(); return allowedNamespaces;
this.LocalStorage.storeAllowedNamespaces(allowedNamespaces);
updateNamespaces(allowedNamespaces);
return allowedNamespaces;
} else {
updateNamespaces(cachedAllowedNamespaces);
return cachedAllowedNamespaces;
}
} }
/** /**
@ -105,7 +97,6 @@ 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);
@ -116,14 +107,6 @@ 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

@ -36,8 +36,8 @@ export function KubernetesResourcePoolService(
} }
// getting the quota for all namespaces is costly by default, so disable getting it by default // getting the quota for all namespaces is costly by default, so disable getting it by default
async function getAll({ getQuota = false, refreshCache = false }) { async function getAll({ getQuota = false }) {
const namespaces = await KubernetesNamespaceService.get('', refreshCache); const namespaces = await KubernetesNamespaceService.get();
const pools = await Promise.all( const pools = await Promise.all(
_.map(namespaces, async (namespace) => { _.map(namespaces, async (namespace) => {
const name = namespace.Name; const name = namespace.Name;

View File

@ -22,6 +22,7 @@
namespaces="ctrl.state.namespaces" namespaces="ctrl.state.namespaces"
namespace="ctrl.state.namespaceName" namespace="ctrl.state.namespaceName"
on-change-namespace-dropdown="(ctrl.onChangeNamespaceDropdown)" on-change-namespace-dropdown="(ctrl.onChangeNamespaceDropdown)"
is-apps-loading="ctrl.state.isAppsLoading"
is-system-resources="ctrl.state.isSystemResources" is-system-resources="ctrl.state.isSystemResources"
set-system-resources="(ctrl.setSystemResources)" set-system-resources="(ctrl.setSystemResources)"
> >
@ -38,6 +39,7 @@
namespaces="ctrl.state.namespaces" namespaces="ctrl.state.namespaces"
namespace="ctrl.state.namespaceName" namespace="ctrl.state.namespaceName"
on-change-namespace-dropdown="(ctrl.onChangeNamespaceDropdown)" on-change-namespace-dropdown="(ctrl.onChangeNamespaceDropdown)"
is-apps-loading="ctrl.state.isAppsLoading"
is-system-resources="ctrl.state.isSystemResources" is-system-resources="ctrl.state.isSystemResources"
set-system-resources="(ctrl.setSystemResources)" set-system-resources="(ctrl.setSystemResources)"
> >

View File

@ -143,15 +143,14 @@ class KubernetesApplicationsController {
}); });
} }
onChangeNamespaceDropdown(namespaceName) { onChangeNamespaceDropdown(namespace) {
this.state.namespaceName = namespaceName; this.state.namespaceName = namespace;
// save the selected namespaceName in local storage with the key 'kubernetes_namespace_filter_${environmentId}_${userID}' return this.$async(this.getApplicationsAsync);
this.LocalStorage.storeNamespaceFilter(this.endpoint.Id, this.user.ID, namespaceName);
this.getApplicationsAsync();
} }
async getApplicationsAsync() { async getApplicationsAsync() {
try { try {
this.state.isAppsLoading = true;
const [applications, configurations] = await Promise.all([ const [applications, configurations] = await Promise.all([
this.KubernetesApplicationService.get(this.state.namespaceName), this.KubernetesApplicationService.get(this.state.namespaceName),
this.KubernetesConfigurationService.get(this.state.namespaceName), this.KubernetesConfigurationService.get(this.state.namespaceName),
@ -166,6 +165,8 @@ class KubernetesApplicationsController {
this.$scope.$apply(); 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');
} finally {
this.state.isAppsLoading = false;
} }
} }

View File

@ -290,7 +290,6 @@
<!-- table --> <!-- table -->
<kubernetes-application-services-table <kubernetes-application-services-table
services="ctrl.application.Services" services="ctrl.application.Services"
namespaces="ctrl.allNamespaces"
application="ctrl.application" application="ctrl.application"
public-url="ctrl.state.publicUrl" public-url="ctrl.state.publicUrl"
></kubernetes-application-services-table> ></kubernetes-application-services-table>

View File

@ -221,7 +221,6 @@ 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

@ -21,7 +21,6 @@ class KubernetesResourcePoolsController {
} }
async onReload() { async onReload() {
await this.KubernetesNamespaceService.refreshCacheAsync();
this.$state.reload(this.$state.current); this.$state.reload(this.$state.current);
} }
@ -50,7 +49,6 @@ class KubernetesResourcePoolsController {
} finally { } finally {
--actionCount; --actionCount;
if (actionCount === 0) { if (actionCount === 0) {
await this.KubernetesNamespaceService.refreshCacheAsync();
this.$state.reload(this.$state.current); this.$state.reload(this.$state.current);
} }
} }
@ -77,7 +75,7 @@ class KubernetesResourcePoolsController {
async getResourcePoolsAsync() { async getResourcePoolsAsync() {
try { try {
this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true, refreshCache: true }); this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
} catch (err) { } catch (err) {
this.Notifications.error('Failure', err, 'Unable to retreive namespaces'); this.Notifications.error('Failure', err, 'Unable to retreive namespaces');
} }

View File

@ -127,15 +127,6 @@ 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');
},
}; };
}, },
]); ]);

View File

@ -16,7 +16,7 @@ export function useNamespaces(environmentId: EnvironmentId) {
async () => { async () => {
const namespaces = await getNamespaces(environmentId); const namespaces = await getNamespaces(environmentId);
const namespaceNames = Object.keys(namespaces); const namespaceNames = Object.keys(namespaces);
// use seflsubjectaccess reviews to avoid forbidden requests // use selfsubjectaccess reviews to avoid forbidden requests
const allNamespaceAccessReviews = await Promise.all( const allNamespaceAccessReviews = await Promise.all(
namespaceNames.map((namespaceName) => namespaceNames.map((namespaceName) =>
getSelfSubjectAccessReview(environmentId, namespaceName) getSelfSubjectAccessReview(environmentId, namespaceName)