diff --git a/cypress/e2e/po/detail/fleet/fleet.cattle.io.application.po.ts b/cypress/e2e/po/detail/fleet/fleet.cattle.io.application.po.ts index 8291bdeeed..bc63ff2314 100644 --- a/cypress/e2e/po/detail/fleet/fleet.cattle.io.application.po.ts +++ b/cypress/e2e/po/detail/fleet/fleet.cattle.io.application.po.ts @@ -34,6 +34,6 @@ export default class FleetApplicationDetailsPo extends PagePo { } graph() { - return this.self().find('[data-testid="gitrepo_graph"]'); + return this.self().find('[data-testid="resource-graph"]'); } } diff --git a/cypress/e2e/po/pages/fleet/fleet.cattle.io.gitrepo.po.ts b/cypress/e2e/po/pages/fleet/fleet.cattle.io.gitrepo.po.ts index a88004ea47..19f1a0e53e 100644 --- a/cypress/e2e/po/pages/fleet/fleet.cattle.io.gitrepo.po.ts +++ b/cypress/e2e/po/pages/fleet/fleet.cattle.io.gitrepo.po.ts @@ -145,6 +145,6 @@ export class FleetGitRepoDetailsPo extends BaseDetailPagePo { } graph() { - return this.self().find('[data-testid="gitrepo_graph"]'); + return this.self().find('[data-testid="resource-graph"]'); } } diff --git a/shell/assets/translations/en-us.yaml b/shell/assets/translations/en-us.yaml index efbc889f46..a408651420 100644 --- a/shell/assets/translations/en-us.yaml +++ b/shell/assets/translations/en-us.yaml @@ -153,6 +153,11 @@ generic: opensInNewTab: Opens in a new tab +graph: + noPermissions: You do not have permission to Graph view + loading: Loading chart data... + rendering: Rendering chart... + locale: menu: Locale selector menu en-us: English @@ -2791,7 +2796,9 @@ fleet: title: Release label: Name placeholder: placeholder - registryOption: Choose a Rancher Registry + registry: + option: Choose a Rancher Registry + error: The {registry} has errors, please try to update the registry in Rancher repository. values: title: Values selectLabel: Values @@ -2806,6 +2813,7 @@ fleet: diff: Compare Changes values: selectLabel: YAML + loading: Loading from chart valuesFiles: selectLabel: Files ariaLabel: Enter path for value File @@ -2860,8 +2868,6 @@ fleet: other {Matches {matched, number} of {total, number} existing clusters, including "{sample}"} } fdc: - loadingChart: Loading chart data... - renderingChart: Rendering chart... id: ID type: Type state: State diff --git a/shell/assets/translations/zh-hans.yaml b/shell/assets/translations/zh-hans.yaml index 5aae1593c8..4e11faf82f 100644 --- a/shell/assets/translations/zh-hans.yaml +++ b/shell/assets/translations/zh-hans.yaml @@ -112,6 +112,10 @@ generic: other {匹配 {total, number} 中的 {matched, number} 个,包括 "{sample}"} } +graph: + loading: 正在加载 Chart 数据... + rendering: 正在渲染 Chart... + locale: en-us: English zh-hans: 简体中文 @@ -2221,8 +2225,6 @@ fleet: other {现有{total, number}个集群,与其中的{matched, number}个匹配,包括"{sample}"} } fdc: - loadingChart: 正在加载 Chart 数据... - renderingChart: 正在渲染 Chart... id: ID type: 类型 state: 状态 diff --git a/shell/components/fleet/ForceDirectedTreeChart/index.vue b/shell/components/ForceDirectedTreeChart.vue similarity index 90% rename from shell/components/fleet/ForceDirectedTreeChart/index.vue rename to shell/components/ForceDirectedTreeChart.vue index 1ddada685d..5de26af92e 100644 --- a/shell/components/fleet/ForceDirectedTreeChart/index.vue +++ b/shell/components/ForceDirectedTreeChart.vue @@ -2,7 +2,6 @@ import * as d3 from 'd3'; import { STATES } from '@shell/plugins/dashboard-store/resource-class'; import { BadgeState } from '@components/BadgeState'; -import { getChartIcon } from './chartIcons.js'; export default { name: 'ForceDirectedTreeChart', @@ -17,8 +16,24 @@ export default { required: true } }, + + async fetch() { + this.canViewChart = await this.fdcConfig.checkSchemaPermissions(this.$store); + + if (this.canViewChart) { + // set watcher for the chart data + this.dataWatcher = this.$watch(this.fdcConfig.watcherProp, (newValue) => { + this.watcherFunction(newValue); + }, { + deep: true, + immediate: true + }); + } + }, + data() { return { + canViewChart: null, dataWatcher: undefined, parsedInfo: undefined, root: undefined, @@ -37,7 +52,7 @@ export default { }, methods: { watcherFunction(newValue) { - if (newValue.length) { + if (newValue?.length) { if (!this.isChartFirstRendered) { this.parsedInfo = this.fdcConfig.parseData(this.data); @@ -158,16 +173,11 @@ export default { .attr('r', this.setNodeRadius); nodeEnter.append('circle') - .attr('r', (d) => { - return this.setNodeRadius(d) - 5; - }) + .attr('r', (d) => this.setNodeRadius(d) - 5) .attr('class', 'node-hover-layer'); - nodeEnter.append('svg').html((d) => { - const icon = this.fdcConfig.fetchNodeIcon(d); - - return getChartIcon(icon); - }) + nodeEnter.append('svg') + .html((d) => this.fdcConfig.fetchNodeIcon(d)) .attr('x', this.nodeImagePosition) .attr('y', this.nodeImagePosition) .attr('height', this.nodeImageSize) @@ -177,9 +187,7 @@ export default { this.simulation.nodes(this.allNodesData); this.simulation.force('link', d3.forceLink() - .id((d) => { - return d.id; - }) + .id((d) => d.id) .distance(100) .links(this.allLinks) ); @@ -188,10 +196,10 @@ export default { const lowerCaseStatus = d.data?.state ? d.data.state.toLowerCase() : 'unkown_status'; const defaultClassArray = ['node']; - if (STATES[lowerCaseStatus] && STATES[lowerCaseStatus].color) { - defaultClassArray.push(`node-${ STATES[lowerCaseStatus].color }`); - } else { + if (d?.data?.muteStatus) { defaultClassArray.push(`node-default-fill`); + } else if (STATES[lowerCaseStatus] && STATES[lowerCaseStatus].color) { + defaultClassArray.push(`node-${ STATES[lowerCaseStatus].color }`); } // node active (clicked) @@ -334,14 +342,6 @@ export default { this.svg = d3.select('#tree').append('svg') .attr('viewBox', `0 0 ${ this.fdcConfig.chartWidth } ${ this.fdcConfig.chartHeight }`) .attr('preserveAspectRatio', 'none'); - - // set watcher for the chart data - this.dataWatcher = this.$watch(this.fdcConfig.watcherProp, function(newValue) { - this.watcherFunction(newValue); - }, { - deep: true, - immediate: true - }); }, unmounted() { this.dataWatcher(); @@ -353,20 +353,26 @@ export default {
-

- {{ t('fleet.fdc.loadingChart') }} +

+ {{ t('graph.noPermissions') }}

-

- {{ t('fleet.fdc.renderingChart') }} +

+ {{ t('graph.loading') }}

- +

+ {{ t('graph.rendering') }} +

+
@@ -416,6 +422,9 @@ export default { >

{{ item.value }}

+ + {{ t(`typeLabel."${ item.valueKey }"`, { count: 1 }) }} + {{ item.value }} @@ -478,32 +487,29 @@ export default { } } - &.repo.active > circle { - transform: scale(1.2); - } - &.bundle.active > circle { transform: scale(1.35); } - &.bundle-deployment.active > circle { + &.bundledeployment.active > circle { transform: scale(1.6); } - &.node-default-fill > circle, - &.repo > circle { + &.node-default-fill > circle { + transform: scale(1.2); fill: var(--muted); } - &:not(.repo).node-success > circle { + + &.node-success > circle { fill: var(--success); } - &:not(.repo).node-info > circle { + &.node-info > circle { fill: var(--info); } - &:not(.repo).node-warning > circle { + &.node-warning > circle { fill: var(--warning); } - &:not(.repo).node-error > circle { + &.node-error > circle { fill: var(--error); } diff --git a/shell/components/ResourceDetail/index.vue b/shell/components/ResourceDetail/index.vue index d471b5f444..38f1837f86 100644 --- a/shell/components/ResourceDetail/index.vue +++ b/shell/components/ResourceDetail/index.vue @@ -6,14 +6,13 @@ import { _VIEW, _EDIT, _CLONE, _IMPORT, _STAGE, _CREATE, AS, _YAML, _DETAIL, _CONFIG, _GRAPH, PREVIEW, MODE, } from '@shell/config/query-params'; -import { FLEET, SCHEMA } from '@shell/config/types'; +import { SCHEMA } from '@shell/config/types'; import { createYaml } from '@shell/utils/create-yaml'; import Masthead from '@shell/components/ResourceDetail/Masthead'; import DetailTop from '@shell/components/DetailTop'; import { clone, diff } from '@shell/utils/object'; import IconMessage from '@shell/components/IconMessage'; -import ForceDirectedTreeChart from '@shell/components/fleet/ForceDirectedTreeChart'; -import { checkSchemasForFindAllHash } from '@shell/utils/auth'; +import ForceDirectedTreeChart from '@shell/components/ForceDirectedTreeChart'; import { stringify } from '@shell/utils/error'; import { Banner } from '@components/Banner'; @@ -172,28 +171,6 @@ export default { yaml = createYaml(schemas, resourceType, data); } } else { - if ( as === _GRAPH ) { - const graphSchema = await checkSchemasForFindAllHash({ - cluster: { - inStoreType: 'management', - type: FLEET.CLUSTER - }, - bundle: { - inStoreType: 'management', - type: FLEET.BUNDLE, - opt: { excludeFields: ['metadata.managedFields', 'spec.resources'] }, - }, - - bundleDeployment: { - inStoreType: 'management', - type: FLEET.BUNDLE_DEPLOYMENT - } - - }, this.$store); - - this.canViewChart = graphSchema.cluster && graphSchema.bundle && graphSchema.bundleDeployment; - } - let fqid = id; if ( schema.attributes?.namespaced && namespace ) { @@ -296,7 +273,6 @@ export default { value: null, model: null, notFound: null, - canViewChart: true, canViewYaml: null, errors: [] }; @@ -493,7 +469,7 @@ export default {
diff --git a/shell/components/fleet/ForceDirectedTreeChart/chartIcons.js b/shell/components/fleet/ForceDirectedTreeChart/chartIcons.js deleted file mode 100644 index 0db8a83246..0000000000 --- a/shell/components/fleet/ForceDirectedTreeChart/chartIcons.js +++ /dev/null @@ -1,17 +0,0 @@ -// This is to mitigate an issue where the SVG icons being imported from the project weren't being rendered on Firefox -// To know more about this technique, check this doc: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs -export const getChartIcon = (type) => ` - - - - - - - - - - - - - -`; diff --git a/shell/config/product/fleet.js b/shell/config/product/fleet.js index a915250bc7..6ab44d8a25 100644 --- a/shell/config/product/fleet.js +++ b/shell/config/product/fleet.js @@ -2,7 +2,7 @@ import { DSL } from '@shell/store/type-map'; import { FLEET } from '@shell/config/types'; import { STATE, NAME as NAME_COL, AGE, FLEET_APPLICATION_TYPE } from '@shell/config/table-headers'; import { FLEET as FLEET_FEATURE } from '@shell/store/features'; -import { gitRepoGraphConfig } from '@shell/pages/c/_cluster/fleet/GitRepoGraphConfig'; +import { graphConfig } from '@shell/pages/c/_cluster/fleet/graph/config'; import { BLANK_CLUSTER } from '@shell/store/store-types.js'; export const SOURCE_TYPE = { @@ -142,9 +142,11 @@ export function init(store) { ], 'resources'); configureType(FLEET.GIT_REPO, { - showListMasthead: false, hasGraph: true, graphConfig: gitRepoGraphConfig + showListMasthead: false, hasGraph: true, graphConfig + }); + configureType(FLEET.HELM_OP, { + showListMasthead: false, hasGraph: true, graphConfig }); - configureType(FLEET.HELM_OP, { showListMasthead: false, hasGraph: false }); weightType(FLEET.GIT_REPO, 110, true); weightType(FLEET.HELM_OP, 109, true); diff --git a/shell/pages/c/_cluster/fleet/GitRepoGraphConfig.js b/shell/pages/c/_cluster/fleet/GitRepoGraphConfig.js deleted file mode 100644 index c0b2a24e24..0000000000 --- a/shell/pages/c/_cluster/fleet/GitRepoGraphConfig.js +++ /dev/null @@ -1,249 +0,0 @@ -import { STATES } from '@shell/plugins/dashboard-store/resource-class'; -import { FLEET } from '@shell/config/types'; - -// some default values -const defaultNodeRadius = 20; -const defaultNodePadding = 15; -const chartWidth = 800; -const chartHeight = 500; -const fdcStrength = -300; -const fdcDistanceMax = 500; -const fdcForceCollide = 80; -const fdcAlphaDecay = 0.05; - -// setting up default sim params -// check documentation here: https://github.com/d3/d3-force#forceSimulation -const simulationParams = { - fdcStrength, - fdcDistanceMax, - fdcForceCollide, - fdcAlphaDecay -}; - -/** - * Represents a config object for FDC type - * @param {Function} parseData - Parses the specific data for each chart. Format must be compliant with d3 data format - * @example data format => { parent: {..., children: [ {..., children: []} ] } } - * @param {Function} extendNodeClass - Extends the classes for each node so that the styling is correctly applied - * @param {Function} nodeDimensions - Sets the radius of the nodes according each data type - * @param {Function} infoDetails - Prepares the data to be displayed in the info box on the right-side of the ForceDirectedTreeChart component - */ -export const gitRepoGraphConfig = { - chartWidth, - chartHeight, - simulationParams, - /** - * data prop that is used to trigger the watcher in the component. Should follow format "data.xxxxxx" - */ - watcherProp: 'data.bundles', - /** - * Mandatory params for a child object in parseData (for statuses to work) - * @param {String} state - * @param {String} stateDisplay - * @param {String} stateColor - * @param {String} matchingId (this can be different than the actual ID, depends on the usecase) - */ - parseData: (data) => { - const bundles = data.bundles.map((bundle, i) => { - const bundleLowercaseState = bundle.state ? bundle.state.toLowerCase() : 'unknown'; - const bundleStateColor = STATES[bundleLowercaseState].color; - - const repoChild = { - id: bundle.id, - matchingId: bundle.id, - type: bundle.type, - state: bundle.state, - stateLabel: bundle.stateDisplay, - stateColor: bundleStateColor, - isBundle: true, - errorMsg: bundle.stateDescription, - detailLocation: bundle.detailLocation, - children: [] - }; - - const bds = data.bundleDeployments.filter((bd) => bundle.id === `${ bd.metadata?.labels?.['fleet.cattle.io/bundle-namespace'] }/${ bd.metadata?.labels?.['fleet.cattle.io/bundle-name'] }`); - - bds.forEach((bd) => { - const bdLowercaseState = bd.state ? bd.state.toLowerCase() : 'unknown'; - const bdStateColor = STATES[bdLowercaseState]?.color; - - const cluster = data.clustersList.find((cluster) => { - const clusterString = `${ cluster.namespace }-${ cluster.name }`; - - return bd.id.includes(clusterString); - }); - - repoChild.children.push({ - id: bd.id, - matchingId: bd.id, - type: bd.type, - clusterLabel: cluster ? cluster.namespacedName : undefined, - clusterDetailLocation: cluster ? cluster.detailLocation : undefined, - state: bd.state, - stateLabel: bd.stateDisplay, - stateColor: bdStateColor, - isBundleDeployment: true, - errorMsg: bd.stateDescription, - detailLocation: bd.detailLocation, - }); - }); - - return repoChild; - }); - - const repoLowercaseState = data.state ? data.state.toLowerCase() : 'unknown'; - const repoStateColor = STATES[repoLowercaseState].color; - - const finalData = { - id: data.id, - matchingId: data.id, - type: data.type, - state: data.state, - stateLabel: data.stateDisplay, - stateColor: repoStateColor, - isRepo: true, - errorMsg: data.stateDescription, - detailLocation: data.detailLocation, - children: bundles - }; - - return finalData; - }, - /** - * Used to add relevant classes to each main node instance - */ - extendNodeClass: ({ data }) => { - const classArray = []; - - // node type - data?.isRepo ? classArray.push('repo') : data?.isBundle ? classArray.push('bundle') : classArray.push('bundle-deployment'); - - return classArray; - }, - /** - * Used to add the correct icon to each node - */ - fetchNodeIcon: ({ data }) => { - if (data?.isRepo) { - return 'git'; - } - - if ( data?.isBundle) { - if (data?.id.indexOf('helm') !== -1) { - return 'helm'; - } - - return 'bundle'; - } - - if (data?.isBundleDeployment) { - return 'node'; - } - }, - /** - * Used to set node dimensions - */ - nodeDimensions: ({ data }) => { - if (data?.isRepo) { - const radius = defaultNodeRadius * 3; - const padding = defaultNodePadding * 2.5; - - return { - radius, - size: (radius * 2) - padding, - position: -(((radius * 2) - padding) / 2) - }; - } - if (data?.isBundle) { - const radius = defaultNodeRadius * 2; - const padding = defaultNodePadding; - - if (data?.id.indexOf('helm') !== -1) { - return { - radius, - size: (radius * 1.5) - padding, - position: -(((radius * 1.5) - padding) / 2) - }; - } - - return { - radius, - size: (radius * 1.7) - padding, - position: -(((radius * 1.7) - padding) / 2) - }; - } - - return { - radius: defaultNodeRadius, - size: (defaultNodeRadius * 2) - defaultNodePadding, - position: -(((defaultNodeRadius * 2) - defaultNodePadding) / 2) - }; - }, - /** - * Use @param {Obj} valueObj for compound values (usually associated with a template of some sort on the actual component) - * or @param value for a simple straightforward value - */ - infoDetails: (data) => { - let dataType; - - switch (data.type) { - case FLEET.GIT_REPO: - dataType = 'GitRepo'; - break; - case FLEET.BUNDLE: - dataType = 'Bundle'; - break; - case FLEET.BUNDLE_DEPLOYMENT: - dataType = 'BundleDeployment'; - break; - default: - dataType = data.type; - break; - } - - const moreInfo = [ - { - labelKey: 'fleet.fdc.type', - value: dataType - }, - { - type: 'title-link', - labelKey: 'fleet.fdc.id', - valueObj: { - label: data.id, - detailLocation: data.detailLocation - } - } - ]; - - if (data.isBundleDeployment) { - moreInfo.push({ - type: 'title-link', - labelKey: 'fleet.fdc.cluster', - valueObj: { - label: data.clusterLabel, - detailLocation: data.clusterDetailLocation - } - }); - } - - moreInfo.push({ - type: 'state-badge', - labelKey: 'fleet.fdc.state', - valueObj: { - stateColor: data.stateColor, - stateLabel: data.stateLabel - } - }); - - if (data.errorMsg) { - moreInfo.push({ - type: 'single-error', - labelKey: 'fleet.fdc.error', - value: data.errorMsg - }); - } - - return moreInfo; - } -}; diff --git a/shell/pages/c/_cluster/fleet/graph/config.js b/shell/pages/c/_cluster/fleet/graph/config.js new file mode 100644 index 0000000000..f8061a3a0c --- /dev/null +++ b/shell/pages/c/_cluster/fleet/graph/config.js @@ -0,0 +1,277 @@ +import { STATES } from '@shell/plugins/dashboard-store/resource-class'; +import { FLEET } from '@shell/config/types'; +import { checkSchemasForFindAllHash } from '@shell/utils/auth'; + +// TODO use Rancher icons +const chartIcon = (type) => ` + + + + + + + + + + + + + +`; + +// some default values +const defaultNodeRadius = 20; +const defaultNodePadding = 15; +const chartWidth = 800; +const chartHeight = 500; +const fdcStrength = -300; +const fdcDistanceMax = 500; +const fdcForceCollide = 80; +const fdcAlphaDecay = 0.05; + +// setting up default sim params +// check documentation here: https://github.com/d3/d3-force#forceSimulation +const simulationParams = { + fdcStrength, + fdcDistanceMax, + fdcForceCollide, + fdcAlphaDecay +}; + +/** + * Represents a config object for FDC type + * @param {Function} parseData - Parses the specific data for each chart. Format must be compliant with d3 data format + * @example data format => { parent: {..., children: [ {..., children: []} ] } } + * @param {Function} extendNodeClass - Extends the classes for each node so that the styling is correctly applied + * @param {Function} nodeDimensions - Sets the radius of the nodes according each data type + * @param {Function} infoDetails - Prepares the data to be displayed in the info box on the right-side of the ForceDirectedTreeChart component + */ +export const graphConfig = { + chartWidth, + chartHeight, + simulationParams, + /** + * data prop that is used to trigger the watcher in the component. Should follow format "data.xxxxxx" + */ + watcherProp: 'data.bundles', + /** + * Mandatory params for a child object in parseData (for statuses to work) + * @param {String} state + * @param {String} stateDisplay + * @param {String} stateColor + * @param {String} matchingId (this can be different than the actual ID, depends on the usecase) + */ + parseData: (data) => { + const bundles = data.bundles.map((bundle) => { + const bundleLowercaseState = bundle.state ? bundle.state.toLowerCase() : 'unknown'; + const bundleStateColor = STATES[bundleLowercaseState].color; + + const appChild = { + id: bundle.id, + type: bundle.type, + matchingId: `${ bundle.type }-${ bundle.id }`, + state: bundle.state, + stateLabel: bundle.stateDisplay, + stateColor: bundleStateColor, + errorMsg: bundle.stateDescription, + detailLocation: bundle.detailLocation, + children: [] + }; + + const bds = data.bundleDeployments.filter((bd) => bundle.id === `${ bd.metadata?.labels?.['fleet.cattle.io/bundle-namespace'] }/${ bd.metadata?.labels?.['fleet.cattle.io/bundle-name'] }`); + + bds.forEach((bd) => { + const bdLowercaseState = bd.state ? bd.state.toLowerCase() : 'unknown'; + const bdStateColor = STATES[bdLowercaseState]?.color; + + const cluster = data.clustersList.find((cluster) => { + const clusterString = `${ cluster.namespace }-${ cluster.name }`; + + return bd.id.includes(clusterString); + }); + + appChild.children.push({ + id: bd.id, + type: bd.type, + matchingId: `${ bd.type }-${ bd.id }`, + clusterLabel: cluster ? cluster.namespacedName : undefined, + clusterDetailLocation: cluster ? cluster.detailLocation : undefined, + state: bd.state, + stateLabel: bd.stateDisplay, + stateColor: bdStateColor, + errorMsg: bd.stateDescription, + detailLocation: bd.detailLocation, + }); + }); + + return appChild; + }); + + const appLowercaseState = data.state ? data.state.toLowerCase() : 'unknown'; + const appStateColor = STATES[appLowercaseState].color; + + return { + id: data.id, + type: data.type, + matchingId: `${ data.type }-${ data.id }`, + state: data.state, + stateLabel: data.stateDisplay, + stateColor: appStateColor, + errorMsg: data.stateDescription, + detailLocation: data.detailLocation, + children: bundles, + muteStatus: true + }; + }, + /** + * Used to add relevant classes to each main node instance + */ + extendNodeClass: ({ data }) => { + const classArray = []; + + if (data?.type) { + const nodeType = data.type.replaceAll('fleet.cattle.io.', ''); + + classArray.push(nodeType); + } + + return classArray; + }, + /** + * Used to add the correct icon to each node + */ + fetchNodeIcon: ({ data }) => { + let type = ''; + + switch (data?.type) { + case FLEET.GIT_REPO: + type = 'git'; + break; + case FLEET.HELM_OP: + type = 'helm'; + break; + case FLEET.BUNDLE: + if (data?.id.indexOf('helm') !== -1) { + type = 'helm'; + } + + type = 'bundle'; + break; + case FLEET.BUNDLE_DEPLOYMENT: + type = 'node'; + break; + } + + return chartIcon(type); + }, + /** + * Used to set node dimensions + */ + nodeDimensions: ({ data }) => { + if (data?.type === FLEET.GIT_REPO || data?.type === FLEET.HELM_OP) { + const radius = defaultNodeRadius * 3; + const padding = defaultNodePadding * 2.5; + + return { + radius, + size: (radius * 2) - padding, + position: -(((radius * 2) - padding) / 2) + }; + } + if (data?.type === FLEET.BUNDLE) { + const radius = defaultNodeRadius * 2; + const padding = defaultNodePadding; + + if (data?.id.indexOf('helm') !== -1) { + return { + radius, + size: (radius * 1.5) - padding, + position: -(((radius * 1.5) - padding) / 2) + }; + } + + return { + radius, + size: (radius * 1.7) - padding, + position: -(((radius * 1.7) - padding) / 2) + }; + } + + return { + radius: defaultNodeRadius, + size: (defaultNodeRadius * 2) - defaultNodePadding, + position: -(((defaultNodeRadius * 2) - defaultNodePadding) / 2) + }; + }, + /** + * Use @param {Obj} valueObj for compound values (usually associated with a template of some sort on the actual component) + * or @param value for a simple straightforward value + */ + infoDetails: (data) => { + const moreInfo = [ + { + type: 'resource-type', + labelKey: 'fleet.fdc.type', + valueKey: data.type + }, + { + type: 'title-link', + labelKey: 'fleet.fdc.id', + valueObj: { + label: data.id, + detailLocation: data.detailLocation + } + } + ]; + + if (data?.type === FLEET.BUNDLE_DEPLOYMENT) { + moreInfo.push({ + type: 'title-link', + labelKey: 'fleet.fdc.cluster', + valueObj: { + label: data.clusterLabel, + detailLocation: data.clusterDetailLocation + } + }); + } + + moreInfo.push({ + type: 'state-badge', + labelKey: 'fleet.fdc.state', + valueObj: { + stateColor: data.stateColor, + stateLabel: data.stateLabel + } + }); + + if (data.errorMsg) { + moreInfo.push({ + type: 'single-error', + labelKey: 'fleet.fdc.error', + value: data.errorMsg + }); + } + + return moreInfo; + }, + + checkSchemaPermissions: async(store) => { + const schemas = await checkSchemasForFindAllHash({ + cluster: { + inStoreType: 'management', + type: FLEET.CLUSTER + }, + bundle: { + inStoreType: 'management', + type: FLEET.BUNDLE, + opt: { excludeFields: ['metadata.managedFields', 'spec.resources'] }, + }, + bundleDeployment: { + inStoreType: 'management', + type: FLEET.BUNDLE_DEPLOYMENT + } + }, store); + + return schemas.cluster && schemas.bundle && schemas.bundleDeployment; + } +}; diff --git a/shell/pages/c/_cluster/fleet/index.vue b/shell/pages/c/_cluster/fleet/index.vue index 332bd67423..0726913963 100644 --- a/shell/pages/c/_cluster/fleet/index.vue +++ b/shell/pages/c/_cluster/fleet/index.vue @@ -20,6 +20,11 @@ import FleetApplications from '@shell/components/fleet/FleetApplications.vue'; import FleetUtils from '@shell/utils/fleet'; import Preset from '@shell/mixins/preset'; +const VIEW_MODE = { + TABLE: 'flat', + CARDS: 'cards' +}; + export default { name: 'FleetDashboard', components: { @@ -101,25 +106,26 @@ export default { createRoute: { name: 'c-cluster-fleet-application-create' }, permissions: {}, FLEET, - [FLEET.REPO]: [], - [FLEET.HELM_OP]: [], - fleetWorkspaces: [], - viewModeOptions: [ + [FLEET.REPO]: [], + [FLEET.HELM_OP]: [], + fleetWorkspaces: [], + VIEW_MODE, + viewModeOptions: [ { tooltipKey: 'fleet.dashboard.viewMode.table', icon: 'icon-list-flat', - value: 'flat', + value: VIEW_MODE.TABLE, }, { tooltipKey: 'fleet.dashboard.viewMode.cards', icon: 'icon-apps', - value: 'cards', + value: VIEW_MODE.CARDS, }, ], CARDS_MIN: 50, CARDS_SIZE: 50, cardsCount: {}, - viewMode: 'cards', + viewMode: VIEW_MODE.CARDS, isWorkspaceCollapsed: {}, isStateCollapsed: {}, typeFilter: {}, @@ -671,7 +677,7 @@ export default {