mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-11-15 21:36:16 +08:00
fix: show resource counts for all side navigation items (#1196)
* fix: show resource counts for all side navigation items
The side navigation looks up the count of a nav item by its name, but the
counts are keyed by the K8s resource type. Many Harvester nav items are
named differently than the resource they list (e.g. harvesterhci.io.volume
lists persistentvolumeclaim), so no count was shown for them. Some list
views also show only a subset of their resource type, so the plain count
would not match the list.
Override the count getter of the harvester store:
- Resolve the nav item to the resource type it lists, using the same
fallback to the configureType resource as schemaFor. This fixes Hosts,
Alertmanager Configs, Cluster Flows, Cluster Outputs, Flows, Outputs
and Secrets.
- Count only the subset shown by the list for Volumes, VM Networks,
VM Backups, VM Snapshots, Volume Snapshots, Cloud Configuration
Templates and Storage. The matches are counted per namespace once per
store list using sharedComputed.
- Like the list views, do not count resources in system namespaces when
"All Namespaces" is selected.
Steve's counts endpoint only returns the total per resource type, so the
filtered counts are computed from the store list and are only available
once that list is loaded. Until then these nav items show no count, unless
there are no resources of that type at all (then 0). The dashboard loads
PVCs and networks, the VM list PVCs and backups, the other resources are
loaded when their list is opened.
Related to: https://github.com/harvester/harvester/issues/11984
Signed-off-by: Volker Theile <vtheile@suse.com>
* refactor: get the side navigation counts from the server
Stop loading full resource lists and filtering them in the browser to
get the side navigation counts.
- Nav items listing all resources of a type use the counts endpoint,
through the resource type of the nav item.
- VM backups, VM snapshots, volume snapshots, storage classes, cloud
config templates and VM networks request their subset from
/v1/harvester with a page size of 1. These requests run again when the
counts endpoint reports other numbers for the type, and when the
namespace filter changes.
- Namespaces count the already loaded namespaces that are neither system
nor Fleet managed.
- Volumes have no count: the list hides volumes that are only marked by
annotations, which the API cannot filter on.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
* refactor: keep the side navigation count helpers in the store getters
Move them out of their own file, next to the count getter that uses them.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
* fix: make the side navigation count requests more robust
- Ignore a late response of the previous cluster. Resetting the request
ids when loading a cluster let it overwrite the count of the new one.
- Keep the watchers when a cluster left while it was loading finishes
loading after the new one.
- Show no count instead of 0 on Harvester v1.3, which ignores the query
and sends all resources without a count.
- Hide the storage network by its generated name. Networks created
before Harvester v1.4 do not have the hash label, and an upgrade does
not add it.
- Stop refreshing after leaving the product, cancel pending refreshes
when the watchers stop, and cope with the store module being
unregistered when logging out.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
* fix: count only the settings the Settings page shows
The nav item showed the total of all Setting resources, but the page only
shows the allowed ones that exist in the cluster and are supported by its
version.
Move these rules into a visibleSettings getter that both the page and the
count use, so that they always agree. SettingList no longer needs its
feature flag check.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
* test: remove a Settings page test that was committed twice
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
* fix: show the number of volumes in the side navigation
The Volumes list hides golden image volumes and the temporary volumes CDI
uses to populate a volume, so the total of the counts endpoint would be
wrong.
Golden image volumes are only marked by an annotation, which Harvester's
API cannot filter on, but each one belongs to a DataVolume owned by its VM
image. So request the volumes that are not owned by another volume, and
the DataVolumes owned by a VM image, and show the difference.
Every nav item with a list now has a number, so remove NO_COUNT.
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
---------
Signed-off-by: Volker Theile <vtheile@suse.com>
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
Co-authored-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
(cherry picked from commit 784c22e27e)
This commit is contained in:
@@ -5,7 +5,12 @@ import { COUNT, NAMESPACE, MANAGEMENT } from '@shell/config/types';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { DEV, NAMESPACE_FILTERS } from '@shell/store/prefs';
|
||||
import { createNamespaceFilterKeyWithId } from '@shell/utils/namespace-filter';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { HCI } from '../../types';
|
||||
import { PRODUCT_NAME } from '../../config/harvester';
|
||||
import {
|
||||
SERVER_FILTERED_COUNTS, countedNamespaces, filteredCountRequest, isFilteredByNamespace
|
||||
} from './getters';
|
||||
|
||||
export default {
|
||||
async loadCluster({
|
||||
@@ -131,6 +136,8 @@ export default {
|
||||
getters
|
||||
}, { root: true });
|
||||
|
||||
dispatch('watchFilteredCounts', { clusterId: id });
|
||||
|
||||
// Solve compatibility with Rancher v2.6.x, fell remove these codes after not support v2.6.x
|
||||
const definition = {
|
||||
def: false,
|
||||
@@ -166,4 +173,95 @@ export default {
|
||||
}, { root: true });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests the side navigation counts of the nav items in SERVER_FILTERED_COUNTS
|
||||
* (all of them by default), see getters.ts. Responses of an outdated request
|
||||
* of a nav item (e.g. before a namespace filter change, or of the previous
|
||||
* cluster) are ignored.
|
||||
*/
|
||||
async fetchFilteredCounts({
|
||||
state, commit, dispatch, getters, rootGetters
|
||||
}: any, names: string[] = Object.keys(SERVER_FILTERED_COUNTS)) {
|
||||
await Promise.all(names.map(async(name: string) => {
|
||||
commit('filteredCountRequested', name);
|
||||
|
||||
const requestId = state.filteredCountRequests[name];
|
||||
const request = filteredCountRequest(getters, rootGetters, name);
|
||||
let count = request?.count ?? null;
|
||||
|
||||
if (request?.url) {
|
||||
try {
|
||||
const res = await dispatch('request', { url: request.url });
|
||||
|
||||
// Steve leaves out `count` when nothing matches. Harvester v1.3 ignores the query and
|
||||
// sends all resources without a `count`, which says nothing about the subset.
|
||||
count = res?.count ?? (res?.data?.length ? null : 0);
|
||||
} catch (e) {
|
||||
count = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.filteredCountRequests[name] === requestId) {
|
||||
commit('setFilteredCount', { name, count });
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests the counts of SERVER_FILTERED_COUNTS, and requests them again when
|
||||
* they may have changed: when the counts endpoint reports other numbers for
|
||||
* their resource type (it is kept up to date through the websocket), and when
|
||||
* the namespace filter changes. A change that leaves these numbers as they are
|
||||
* (e.g. adding the cloud-init template label to an existing ConfigMap) shows up
|
||||
* with the next change of that resource type.
|
||||
*/
|
||||
watchFilteredCounts({
|
||||
state, commit, dispatch, getters, rootGetters
|
||||
}: any, { clusterId }: any) {
|
||||
// The `loadCluster` of a cluster left while it was loading can finish after the one of the new cluster
|
||||
if (rootGetters.clusterId !== clusterId) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.filteredCountWatchers.forEach((unwatch: () => void) => unwatch());
|
||||
commit('resetFilteredCounts');
|
||||
|
||||
// The watchers stay registered after leaving the cluster or the product, until the next `loadCluster`
|
||||
const refresh = (names: string[]) => {
|
||||
if (rootGetters.clusterId === clusterId && rootGetters.currentProduct?.inStore === PRODUCT_NAME) {
|
||||
dispatch('fetchFilteredCounts', names);
|
||||
}
|
||||
};
|
||||
const names = Object.keys(SERVER_FILTERED_COUNTS);
|
||||
const namesByType: Record<string, string[]> = {};
|
||||
|
||||
names.forEach((name) => {
|
||||
const type = getters.schemaFor(name)?.id;
|
||||
|
||||
if (type) {
|
||||
namesByType[type] = [...(namesByType[type] || []), name];
|
||||
}
|
||||
});
|
||||
|
||||
const unwatchers = Object.entries(namesByType).map(([type, namesOfType]) => {
|
||||
const refreshType = debounce(() => refresh(namesOfType), 1000);
|
||||
// `all` is gone once the store module is unregistered when logging out
|
||||
const unwatch = this.watch(() => JSON.stringify(getters.all?.(COUNT)?.[0]?.counts?.[type] || null), refreshType);
|
||||
|
||||
return () => {
|
||||
unwatch();
|
||||
refreshType.cancel();
|
||||
};
|
||||
});
|
||||
|
||||
unwatchers.push(this.watch(
|
||||
() => JSON.stringify(countedNamespaces(rootGetters)),
|
||||
() => refresh(names.filter((name) => isFilteredByNamespace(name, getters.schemaFor(name))))
|
||||
));
|
||||
|
||||
commit('setFilteredCountWatchers', unwatchers);
|
||||
|
||||
return dispatch('fetchFilteredCounts');
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user