diff --git a/shell/components/fleet/FleetRepos.vue b/shell/components/fleet/FleetRepos.vue index 4fed19ad5f8..8ab7dd209df 100644 --- a/shell/components/fleet/FleetRepos.vue +++ b/shell/components/fleet/FleetRepos.vue @@ -6,17 +6,15 @@ import FleetIntro from '@shell/components/fleet/FleetIntro'; import { AGE, - STATE, - NAME, - FLEET_SUMMARY, FLEET_REPO, - FLEET_REPO_TARGET, - FLEET_REPO_CLUSTERS_READY, FLEET_REPO_CLUSTER_SUMMARY, - FLEET_REPO_PER_CLUSTER_STATE - + FLEET_REPO_CLUSTERS_READY, + FLEET_REPO_PER_CLUSTER_STATE, + FLEET_REPO_TARGET, + FLEET_SUMMARY, + NAME, + STATE, } from '@shell/config/table-headers'; -import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class'; // i18n-ignore repoDisplay export default { @@ -77,31 +75,18 @@ export default { headers() { // Cluster summary is only shown in the cluster view - const fleetClusterSummary = { + const summary = this.isClusterView ? [{ ...FLEET_REPO_CLUSTER_SUMMARY, - formatterOpts: { - // Fleet uses labels to identify clusters - clusterLabel: this.clusterId - }, - }; + formatterOpts: { clusterId: this.clusterId }, + }] : [FLEET_REPO_CLUSTERS_READY, FLEET_SUMMARY]; // if hasPerClusterState then use the repo state - const fleetPerClusterState = { + const state = this.isClusterView ? { ...FLEET_REPO_PER_CLUSTER_STATE, - value: (row) => { - const statePerCluster = row.clusterResourceStatus?.find((c) => { - return c.clusterLabel === this.clusterId; - }); - - return statePerCluster ? statePerCluster?.status?.displayStatus : STATES_ENUM.ACTIVE; - }, - }; + value: (repo) => repo.clusterState(this.clusterId), + } : STATE; - const summary = this.isClusterView ? [fleetClusterSummary] : [FLEET_REPO_CLUSTERS_READY, FLEET_SUMMARY]; - - const state = this.isClusterView ? fleetPerClusterState : STATE; - - const out = [ + return [ state, NAME, FLEET_REPO, @@ -109,8 +94,6 @@ export default { ...summary, AGE ]; - - return out; }, }, methods: { diff --git a/shell/components/formatter/FleetClusterSummaryGraph.vue b/shell/components/formatter/FleetClusterSummaryGraph.vue index 7fc03cbbd2c..ba5ea1a654f 100644 --- a/shell/components/formatter/FleetClusterSummaryGraph.vue +++ b/shell/components/formatter/FleetClusterSummaryGraph.vue @@ -11,7 +11,7 @@ export default { required: true }, - clusterLabel: { + clusterId: { type: String, required: true } @@ -22,6 +22,6 @@ export default { diff --git a/shell/components/formatter/FleetSummaryGraph.vue b/shell/components/formatter/FleetSummaryGraph.vue index 58d3b571d2b..f6dd595b935 100644 --- a/shell/components/formatter/FleetSummaryGraph.vue +++ b/shell/components/formatter/FleetSummaryGraph.vue @@ -14,7 +14,7 @@ export default { required: true }, - clusterLabel: { + clusterId: { type: String, required: false, default: null, @@ -23,10 +23,8 @@ export default { computed: { summary() { - if (this.clusterLabel) { - return this.row.clusterResourceStatus.find((x) => { - return x.clusterLabel === this.clusterLabel; - })?.status.resourceCounts || {}; + if (this.clusterId) { + return this.row.statusResourceCountsForCluster(this.clusterId); } return this.row.status?.resourceCounts || {}; @@ -37,7 +35,8 @@ export default { }, stateParts() { - const keys = Object.keys(this.summary).filter((x) => !x.startsWith('desired')); + const summary = this.summary; + const keys = Object.keys(summary).filter((x) => !x.startsWith('desired')); const out = keys.map((key) => { const textColor = colorForState(key); @@ -46,7 +45,7 @@ export default { label: ucFirst(key), color: textColor.replace(/text-/, 'bg-'), textColor, - value: this.summary[key], + value: summary[key], sort: stateSort(textColor, key), }; }).filter((x) => x.value > 0); diff --git a/shell/detail/fleet.cattle.io.cluster.vue b/shell/detail/fleet.cattle.io.cluster.vue index 46693240622..cca17db8ac2 100644 --- a/shell/detail/fleet.cattle.io.cluster.vue +++ b/shell/detail/fleet.cattle.io.cluster.vue @@ -29,11 +29,11 @@ export default { }, async fetch() { - const clusterId = this.value?.metadata?.labels[FLEET_LABELS.CLUSTER_NAME]; + const managementClusterId = this.value?.metadata?.labels[FLEET_LABELS.CLUSTER_NAME]; const hash = await allHash({ rancherCluster: this.$store.dispatch('management/find', { type: MANAGEMENT.CLUSTER, - id: clusterId + id: managementClusterId }), repos: this.$store.dispatch('management/findAll', { type: FLEET.GIT_REPO }), workspaces: this.$store.dispatch('management/findAll', { type: FLEET.WORKSPACE }), @@ -53,7 +53,7 @@ export default { return this.value.bundleDeployments; }, clusterId() { - return this.value?.metadata?.labels[FLEET_LABELS.CLUSTER_NAME]; + return this.value.id; }, repos() { diff --git a/shell/models/fleet.cattle.io.bundle.js b/shell/models/fleet.cattle.io.bundle.js index 9e3443e67f9..f2609e73740 100644 --- a/shell/models/fleet.cattle.io.bundle.js +++ b/shell/models/fleet.cattle.io.bundle.js @@ -6,17 +6,6 @@ import { FLEET } from '@shell/config/types'; import { convertSelectorObj, matching } from '@shell/utils/selector'; export default class FleetBundle extends SteveModel { - get deploymentInfo() { - const ready = this.status?.summary?.ready || 0; - const total = this.status?.summary?.desiredReady || 0; - - return { - ready, - unready: total - ready, - total - }; - } - get lastUpdateTime() { return this.status?.conditions?.[0].lastUpdateTime; } diff --git a/shell/models/fleet.cattle.io.gitrepo.js b/shell/models/fleet.cattle.io.gitrepo.js index 247f36293af..02b34e3b6ba 100644 --- a/shell/models/fleet.cattle.io.gitrepo.js +++ b/shell/models/fleet.cattle.io.gitrepo.js @@ -7,7 +7,7 @@ import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array'; import { set } from '@shell/utils/object'; import SteveModel from '@shell/plugins/steve/steve-class'; import { - colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort + colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, STATES_ENUM, stateSort, } from '@shell/plugins/dashboard-store/resource-class'; import { NAME } from '@shell/config/product/explorer'; import FleetUtils from '@shell/utils/fleet'; @@ -20,6 +20,21 @@ function quacksLikeAHash(str) { return false; } +function normalizeStateCounts(data) { + if (!data || data === {}) { + return { + total: 0, + states: {}, + }; + } + const { desiredReady, ...rest } = data ; + + return { + total: desiredReady, + states: rest, + }; +} + export default class GitRepo extends SteveModel { applyDefaults() { const spec = this.spec || {}; @@ -305,18 +320,7 @@ export default class GitRepo extends SteveModel { } get bundles() { - const all = this.$getters['all'](FLEET.BUNDLE); - - return all.filter((bundle) => bundle.repoName === this.name && - bundle.namespace === this.namespace && - bundle.namespacedName.startsWith(`${ this.namespace }:${ this.name }`)); - } - - /** - * Bundles with state of active - */ - get bundlesReady() { - return this.bundles?.filter((bundle) => bundle.state === 'active'); + return this.$getters['matching'](FLEET.BUNDLE, { 'fleet.cattle.io/repo-name': this.name }, this.namespace); } get bundleDeployments() { @@ -325,6 +329,37 @@ export default class GitRepo extends SteveModel { return bds.filter((bd) => bd.metadata?.labels?.['fleet.cattle.io/repo-name'] === this.name); } + get allBundlesStatuses() { + const { nonReadyResources, ...bundlesSummary } = this.status?.summary || {}; + + return normalizeStateCounts(bundlesSummary); + } + + get allResourceStatuses() { + return normalizeStateCounts(this.status?.resourceCounts || {}); + } + + statusResourceCountsForCluster(clusterId) { + if (!this.targetClusters.some((c) => c.id === clusterId)) { + return {}; + } + + return this.bundleDeployments + .filter((bd) => FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels) === clusterId) + .map((bd) => FleetUtils.resourcesFromBundleDeploymentStatus(bd.status)) + .flat() + .map((r) => r.state) + .reduce((prev, state) => { + if (!prev[state]) { + prev[state] = 0; + } + prev[state]++; + prev.desiredReady++; + + return prev; + }, { desiredReady: 0 }); + } + get resourcesStatuses() { const bundleDeployments = this.bundleDeployments || []; const clusters = (this.targetClusters || []).reduce((res, c) => { @@ -357,7 +392,7 @@ export default class GitRepo extends SteveModel { name: `c-cluster-product-resource${ r.namespace ? '-namespace' : '' }-id`, params: { product: NAME, - cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME], + cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME], // explorer uses the "management" Cluster name, which differs from the Fleet Cluster name resource: type, namespace: r.namespace, id: r.name, @@ -385,7 +420,6 @@ export default class GitRepo extends SteveModel { creationTimestamp: r.createdAt, // other properties - clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME], stateBackground: color, stateDisplay: display, stateSort: stateSort(color, display), @@ -408,42 +442,10 @@ export default class GitRepo extends SteveModel { }; } - get clusterResourceStatus() { - const clusterStatuses = this.resourcesStatuses.reduce((prev, curr) => { - const { clusterId, clusterLabel, state } = curr; - - if (!prev[clusterId]) { - prev[clusterId] = { - clusterLabel, - resourceCounts: { [state]: 0, desiredReady: 0 } - - }; - } - - if (!prev[clusterId].resourceCounts[state]) { - prev[clusterId].resourceCounts[state] = 0; - } - - prev[clusterId].resourceCounts[state] += 1; - prev[clusterId].resourceCounts.desiredReady += 1; - - return prev; - }, {}); - - const values = Object.keys(clusterStatuses).map((key) => { - const { clusterLabel, resourceCounts } = clusterStatuses[key]; - - return { - clusterId: key, - clusterLabel, // FLEET LABEL - status: { - displayStatus: primaryDisplayStatusFromCount(resourceCounts), - resourceCounts: { ...resourceCounts } - } - }; - }); + clusterState(clusterId) { + const resourceCounts = this.statusResourceCountsForCluster(clusterId); - return values; + return primaryDisplayStatusFromCount(resourceCounts) || STATES_ENUM.ACTIVE; } get clustersList() { diff --git a/shell/pages/c/_cluster/fleet/index.vue b/shell/pages/c/_cluster/fleet/index.vue index 87b26971672..2d176c6beaa 100644 --- a/shell/pages/c/_cluster/fleet/index.vue +++ b/shell/pages/c/_cluster/fleet/index.vue @@ -2,7 +2,12 @@ import { mapState } from 'vuex'; import { FLEET } from '@shell/config/types'; import { WORKSPACE } from '@shell/store/prefs'; -import { STATES_ENUM, STATES, getStateLabel } from '@shell/plugins/dashboard-store/resource-class'; +import { + getStateLabel, + primaryDisplayStatusFromCount, + STATES, + STATES_ENUM, +} from '@shell/plugins/dashboard-store/resource-class'; import Loading from '@shell/components/Loading'; import CollapsibleCard from '@shell/components/CollapsibleCard.vue'; import ResourceTable from '@shell/components/ResourceTable'; @@ -41,6 +46,7 @@ export default { inStoreType: 'management', type: FLEET.BUNDLE, opt: { excludeFields: ['metadata.managedFields', 'spec.resources'] }, + skipWait: true, }, gitRepos: { inStoreType: 'management', @@ -98,7 +104,6 @@ export default { } ], schema: {}, - allBundles: [], gitRepos: [], fleetWorkspacesData: [], isCollapsed: {}, @@ -138,14 +143,24 @@ export default { }); }, workspacesData() { - return this.fleetWorkspaces.filter((ws) => ws.repos && ws.repos.length); + return this.fleetWorkspaces.filter((ws) => ws.counts.gitRepos > 0); }, emptyWorkspaces() { - return this.fleetWorkspaces.filter((ws) => !ws.repos || !ws.repos.length); + return this.fleetWorkspaces.filter((ws) => ws.counts.gitRepos === 0); }, areAllCardsExpanded() { return Object.keys(this.isCollapsed).every((key) => !this.isCollapsed[key]); - } + }, + gitReposCounts() { + return this.gitRepos.reduce((prev, gitRepo) => { + prev[gitRepo.id] = { + bundles: gitRepo.allBundlesStatuses, + resources: gitRepo.allResourceStatuses, + }; + + return prev; + }, {}); + }, }, methods: { setWorkspaceFilterAndLinkToGitRepo(value) { @@ -170,96 +185,59 @@ export default { return this.getBadgeClassAndIcon(area, row) || defaultStatusInfo; }, getBadgeClassAndIcon(area, row) { - let group; - if (!this.admissableAreas.includes(area)) { return false; } + let group; + if (area === 'clusters') { - if (row.clusterInfo?.ready === row.clusterInfo?.total && row.clusterInfo?.ready) { - return { - badgeClass: STATES[STATES_ENUM.ACTIVE].color, - icon: STATES[STATES_ENUM.ACTIVE].compoundIcon - }; - } - } else if (area === 'bundles') { - group = row.bundles; - } else if (area === 'resources') { - group = row.status?.resources; - } + const clusterInfo = row.clusterInfo; + const state = clusterInfo.ready === clusterInfo.total ? STATES_ENUM.ACTIVE : STATES_ENUM.NOT_READY; - if (group?.length && group?.every((item) => item.state?.toLowerCase() === STATES_ENUM.ACTIVE)) { return { - badgeClass: STATES[STATES_ENUM.ACTIVE].color ? STATES[STATES_ENUM.ACTIVE].color : `${ STATES[STATES_ENUM.UNKNOWN].color } bg-unmapped-state`, - icon: STATES[STATES_ENUM.ACTIVE].compoundIcon ? STATES[STATES_ENUM.ACTIVE].compoundIcon : `${ STATES[STATES_ENUM.UNKNOWN].compoundIcon } unmapped-icon` - }; - } - if (group?.length && group?.some((item) => item.state?.toLowerCase() === STATES_ENUM.ERR_APPLIED)) { - return { - badgeClass: STATES[STATES_ENUM.ERR_APPLIED].color ? STATES[STATES_ENUM.ERR_APPLIED].color : `${ STATES[STATES_ENUM.UNKNOWN].color } bg-unmapped-state`, - icon: STATES[STATES_ENUM.ERR_APPLIED].compoundIcon ? STATES[STATES_ENUM.ERR_APPLIED].compoundIcon : `${ STATES[STATES_ENUM.UNKNOWN].compoundIcon } unmapped-icon` + badgeClass: `${ STATES[state].color } badge-class-area-${ area }`, + icon: STATES[state].compoundIcon }; + } else if (area === 'clusters' || area === 'bundles') { + group = row.allBundlesStatuses; + } else if (area === 'resources') { + group = row.allResourceStatuses; + } else { + // unreachable + return false; } - if (group?.length && group?.some((item) => item.state?.toLowerCase() === STATES_ENUM.NOT_READY)) { + + if (group.total === group.states.ready) { return { - badgeClass: STATES[STATES_ENUM.NOT_READY].color ? STATES[STATES_ENUM.NOT_READY].color : `${ STATES[STATES_ENUM.UNKNOWN].color } bg-unmapped-state`, - icon: STATES[STATES_ENUM.NOT_READY].compoundIcon ? STATES[STATES_ENUM.NOT_READY].compoundIcon : `${ STATES[STATES_ENUM.UNKNOWN].compoundIcon } unmapped-icon` + badgeClass: STATES[STATES_ENUM.ACTIVE].color, + icon: STATES[STATES_ENUM.ACTIVE].compoundIcon, }; } - - if (area === 'resources') { - if (row.status?.resourceCounts?.desiredReady === row.status?.resourceCounts?.ready && row.status?.resourceCounts?.desiredReady) { - return { - badgeClass: STATES[STATES_ENUM.ACTIVE].color, - icon: STATES[STATES_ENUM.ACTIVE].compoundIcon - }; - } - } + const state = primaryDisplayStatusFromCount(group.states); return { - badgeClass: `${ STATES[STATES_ENUM.NOT_READY].color } badge-class-area-${ area }`, - icon: STATES[STATES_ENUM.NOT_READY].compoundIcon + badgeClass: STATES[state].color ? STATES[state].color : `${ STATES[STATES_ENUM.UNKNOWN].color } bg-unmapped-state`, + icon: STATES[state].compoundIcon ? STATES[state].compoundIcon : `${ STATES[STATES_ENUM.UNKNOWN].compoundIcon } unmapped-icon` }; }, - getTooltipInfo(area, row) { - let group; - + getTooltipInfo(area, row, rowCounts) { if (!this.admissableAreas.includes(area)) { return {}; } - if (area === 'clusters') { - group = ''; - } else if (area === 'bundles') { - group = row.bundles; + if (area === 'bundles') { + return this.generateTooltipData(rowCounts[row.id].bundles.states); } else if (area === 'resources') { - group = row.status?.resources; - } - - if (group?.length) { - return this.generateTooltipData(group); + return this.generateTooltipData(rowCounts[row.id].resources.states); } return ''; }, - generateTooltipData(data) { - const infoObj = {}; - let tooltipData = ''; - - data.forEach((item) => { - if (!infoObj[item.state]) { - infoObj[item.state] = 0; - } - - infoObj[item.state]++; - }); - - Object.keys(infoObj).forEach((key) => { - tooltipData += `${ getStateLabel(key) }: ${ infoObj[key] }
`; - }); - - return tooltipData; + generateTooltipData(infoObj) { + return Object.keys(infoObj) + .filter((key) => infoObj[key] > 0) // filter zero values + .map((key) => `${ getStateLabel(key) }: ${ infoObj[key] }
`).join(''); }, getBadgeValue(area, row) { let value; @@ -269,11 +247,15 @@ export default { } if (area === 'clusters') { - return `${ row.clusterInfo.ready }/${ row.clusterInfo.total }`; + value = `${ row.clusterInfo.ready }/${ row.clusterInfo.total }`; } else if (area === 'bundles') { - value = xOfy(row.bundlesReady?.length, row.bundles?.length); + const bundles = row.allBundlesStatuses; + + value = xOfy(bundles.states.ready || 0, bundles.total); } else if (area === 'resources') { - value = xOfy(row.status?.resourceCounts?.ready, row.status?.resourceCounts?.desiredReady); + const resources = row.allResourceStatuses; + + value = xOfy(resources.states.ready || 0, resources.total); } return value; @@ -413,7 +395,7 @@ export default { { const validSchema = value.schemaValidator ? value.schemaValidator(schema) : !!schema; if (validSchema) { - hash[key] = store.dispatch(`${ value.inStoreType }/findAll`, { type: value.type, opt: value.opt } ); + const res = store.dispatch(`${ value.inStoreType }/findAll`, { type: value.type, opt: value.opt } ); + + if (!value.skipWait) { + hash[key] = res; + } } }