@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +144 -41
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/CruResource.vue +161 -14
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +10 -6
- package/components/GrowlManager.vue +4 -0
- package/components/MgmtNodeList.vue +184 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
- package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +7 -1
- package/components/ResourceList/index.vue +82 -1
- package/components/RichTranslation.vue +5 -2
- package/components/Setting.vue +1 -0
- package/components/SubtleLink.vue +31 -6
- package/components/Tabbed/Tab.vue +29 -3
- package/components/Tabbed/index.vue +25 -3
- package/components/TableOfContents/TableOfContents.vue +109 -0
- package/components/TableOfContents/composables.ts +258 -0
- package/components/Window/ContainerShell.vue +21 -11
- package/components/Window/__tests__/ContainerShell.test.ts +107 -37
- package/components/Wizard.vue +9 -4
- package/components/fleet/AppCoChartGrid.vue +401 -0
- package/components/fleet/AppCoEmptyState.vue +127 -0
- package/components/fleet/AppCoPageHeader.vue +119 -0
- package/components/fleet/AppCoVersionSelect.vue +70 -0
- package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +189 -146
- package/components/fleet/FleetIntro.vue +7 -3
- package/components/fleet/FleetNoWorkspaces.vue +7 -3
- package/components/fleet/FleetSecretSelector.vue +5 -3
- package/components/fleet/FleetValuesFrom.vue +8 -2
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpResourcesSection.vue +82 -0
- package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
- package/components/fleet/HelmOpTargetTab.vue +64 -60
- package/components/fleet/HelmOpValuesTab.vue +129 -105
- package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
- package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
- package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
- package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
- package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
- package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
- package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
- package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
- package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
- package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
- package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
- package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
- package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
- package/components/fleet/dashboard/Empty.vue +8 -4
- package/components/fleet/dashboard/ResourceCard.vue +28 -0
- package/components/fleet/dashboard/ResourceDetails.vue +28 -0
- package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
- package/components/form/ArrayList.vue +61 -4
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +39 -1
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +4 -0
- package/components/nav/TopLevelMenu.vue +7 -2
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
- package/components/templates/default.vue +9 -4
- package/components/templates/home.vue +9 -4
- package/components/templates/plain.vue +9 -4
- package/composables/useHelmOpResources.test.ts +56 -0
- package/composables/useHelmOpResources.ts +32 -0
- package/composables/useStateColor.test.ts +325 -0
- package/composables/useStateColor.ts +128 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +1 -0
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +2 -0
- package/config/router/index.js +16 -0
- package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
- package/config/router/navigation-guards/authentication.js +10 -4
- package/config/router/routes.js +20 -6
- package/config/settings.ts +0 -2
- package/config/table-headers.js +3 -4
- package/config/types.js +9 -0
- package/core/plugin-products-base.ts +3 -3
- package/core/plugin-types.ts +83 -30
- package/core/plugin.ts +3 -0
- package/core/types-provisioning.ts +34 -1
- package/core/types.ts +15 -2
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
- package/detail/__tests__/workload.test.ts +3 -152
- package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +30 -4
- package/detail/workload/index.vue +12 -55
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
- package/edit/auth/__tests__/azuread.test.ts +34 -9
- package/edit/auth/__tests__/github.test.ts +234 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -6
- package/edit/auth/__tests__/saml.test.ts +196 -0
- package/edit/auth/azuread.vue +128 -95
- package/edit/auth/github.vue +72 -13
- package/edit/auth/ldap/__tests__/index.test.ts +206 -0
- package/edit/auth/ldap/config.vue +8 -0
- package/edit/auth/ldap/index.vue +75 -1
- package/edit/auth/oidc.vue +119 -73
- package/edit/auth/saml.vue +76 -12
- package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
- package/edit/fleet.cattle.io.helmop.vue +491 -136
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/list/group.principal.vue +5 -4
- package/list/harvesterhci.io.management.cluster.vue +8 -9
- package/list/management.cattle.io.user.vue +12 -9
- package/list/provisioning.cattle.io.cluster.vue +16 -10
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +94 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/chart.js +11 -2
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +21 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
- package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
- package/models/__tests__/fleet-application.test.ts +175 -0
- package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
- package/models/__tests__/management.cattle.io.node.ts +22 -0
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
- package/models/__tests__/workload.test.ts +401 -26
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/compliance.cattle.io.clusterscan.js +39 -4
- package/models/fleet-application.js +4 -0
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +18 -2
- package/models/management.cattle.io.node.js +44 -3
- package/models/namespace.js +1 -1
- package/models/pod.js +33 -1
- package/models/provisioning.cattle.io.cluster.js +5 -5
- package/models/workload.js +108 -13
- package/models/workload.service.js +5 -0
- package/package.json +14 -13
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +11 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/chart.vue +2 -1
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +122 -116
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
- package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
- package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
- package/pages/c/_cluster/fleet/application/create.vue +187 -136
- package/pages/c/_cluster/fleet/application/index.vue +5 -3
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
- package/pages/c/_cluster/fleet/index.vue +2 -2
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/index.vue +15 -0
- package/pages/fail-whale.vue +16 -11
- package/pages/home.vue +16 -46
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
- package/plugins/dashboard-store/resource-class.js +62 -7
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +103 -0
- package/rancher-components/RcButton/RcButton.vue +94 -15
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
- package/rancher-components/RcSection/RcSection.vue +28 -3
- package/scripts/extension/helm/package/Dockerfile +1 -1
- package/scripts/test-plugins-build.sh +2 -1
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/catalog.js +57 -0
- package/store/plugins.js +7 -4
- package/types/components/buttonGroup.ts +5 -0
- package/types/shell/index.d.ts +104 -70
- package/utils/__tests__/auth.test.ts +273 -0
- package/utils/__tests__/computed.test.ts +193 -0
- package/utils/__tests__/cspAdaptor.test.ts +163 -0
- package/utils/__tests__/dom.test.ts +81 -0
- package/utils/__tests__/duration.test.ts +37 -1
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/position.test.ts +237 -0
- package/utils/__tests__/provider.test.ts +51 -1
- package/utils/__tests__/queue.test.ts +232 -0
- package/utils/__tests__/release-notes.test.ts +221 -0
- package/utils/__tests__/router.test.js +254 -1
- package/utils/__tests__/select.test.ts +208 -0
- package/utils/__tests__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +126 -6
- package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
- package/utils/crypto/__tests__/index.test.ts +144 -0
- package/utils/duration.ts +104 -0
- package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
- package/utils/dynamic-content/info.ts +2 -1
- package/utils/error.js +13 -0
- package/utils/fleet-appco.ts +323 -0
- package/utils/object.js +22 -2
- package/utils/provider.ts +12 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/xccdf.ts +39 -42
- package/vue.config.js +1 -1
- package/pages/support/index.vue +0 -264
- package/utils/duration.js +0 -43
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parseStateFilter } from '@shell/mixins/resource-fetch-api-pagination';
|
|
2
|
+
import { PaginationFilterEquality } from '@shell/types/store/pagination.types';
|
|
3
|
+
|
|
4
|
+
describe('parseStateFilter', () => {
|
|
5
|
+
it('should return null for null input', () => {
|
|
6
|
+
expect(parseStateFilter(null)).toBeNull();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return null for undefined input', () => {
|
|
10
|
+
expect(parseStateFilter(undefined)).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should return null for empty string', () => {
|
|
14
|
+
expect(parseStateFilter('')).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should parse a single state', () => {
|
|
18
|
+
const result = parseStateFilter('running');
|
|
19
|
+
|
|
20
|
+
expect(result).toHaveLength(1);
|
|
21
|
+
expect(result?.[0].field).toStrictEqual('metadata.state.name');
|
|
22
|
+
expect(result?.[0].value).toStrictEqual('running');
|
|
23
|
+
expect(result?.[0].equality).toStrictEqual(PaginationFilterEquality.IN);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should parse multiple comma-separated states', () => {
|
|
27
|
+
const result = parseStateFilter('running,active');
|
|
28
|
+
|
|
29
|
+
expect(result).toHaveLength(1);
|
|
30
|
+
expect(result?.[0].field).toStrictEqual('metadata.state.name');
|
|
31
|
+
expect(result?.[0].value).toStrictEqual('running,active');
|
|
32
|
+
expect(result?.[0].equality).toStrictEqual(PaginationFilterEquality.IN);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should parse three comma-separated states', () => {
|
|
36
|
+
const result = parseStateFilter('running,active,waiting');
|
|
37
|
+
|
|
38
|
+
expect(result).toHaveLength(1);
|
|
39
|
+
expect(result?.[0].value).toStrictEqual('running,active,waiting');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should ignore empty segments from trailing commas', () => {
|
|
43
|
+
const result = parseStateFilter('running,,active,');
|
|
44
|
+
|
|
45
|
+
expect(result).toHaveLength(1);
|
|
46
|
+
expect(result?.[0].value).toStrictEqual('running,active');
|
|
47
|
+
});
|
|
48
|
+
});
|
package/mixins/auth-config.js
CHANGED
|
@@ -201,6 +201,9 @@ export default {
|
|
|
201
201
|
if (!this.model.accessMode) {
|
|
202
202
|
this.model.accessMode = 'unrestricted';
|
|
203
203
|
}
|
|
204
|
+
if (this.model.id === 'github' || this.model.id === 'githubapp') {
|
|
205
|
+
this.model.accessMode = 'restricted';
|
|
206
|
+
}
|
|
204
207
|
await this.model.doAction('testAndApply', obj, { redirectUnauthorized: false });
|
|
205
208
|
}
|
|
206
209
|
|
|
@@ -241,6 +244,10 @@ export default {
|
|
|
241
244
|
} else {
|
|
242
245
|
console.warn(`Unable to find principal marked as 'me'`); // eslint-disable-line no-console
|
|
243
246
|
}
|
|
247
|
+
|
|
248
|
+
if (!wasEnabled) {
|
|
249
|
+
this.model.accessMode = 'required';
|
|
250
|
+
}
|
|
244
251
|
}
|
|
245
252
|
if (wasEnabled && configType === 'oauth') {
|
|
246
253
|
await this.model.save({ ignoreFields: ['oauthCredential', 'serviceAccountCredential'] } );
|
package/mixins/chart.js
CHANGED
|
@@ -71,6 +71,8 @@ export default {
|
|
|
71
71
|
|
|
72
72
|
const selectedVersion = this.targetVersion;
|
|
73
73
|
const OSs = this.currentCluster?.workerOSs;
|
|
74
|
+
const isRancher = isRancherRepo(this.repo, this.chart);
|
|
75
|
+
const permittedSystemsByVersion = new Map();
|
|
74
76
|
const out = [];
|
|
75
77
|
|
|
76
78
|
versions.forEach((version) => {
|
|
@@ -84,9 +86,10 @@ export default {
|
|
|
84
86
|
keywords: version.keywords
|
|
85
87
|
};
|
|
86
88
|
|
|
87
|
-
const isRancher = isRancherRepo(this.repo, this.chart);
|
|
88
89
|
const permittedSystems = getPermittedOSs(version?.annotations, isRancher);
|
|
89
90
|
|
|
91
|
+
permittedSystemsByVersion.set(version.version, permittedSystems);
|
|
92
|
+
|
|
90
93
|
if (permittedSystems.length > 0 && difference(OSs, permittedSystems).length > 0) {
|
|
91
94
|
nue.disabled = true;
|
|
92
95
|
}
|
|
@@ -118,7 +121,13 @@ export default {
|
|
|
118
121
|
const currentVersion = out.find((v) => v.originalVersion === this.currentVersion);
|
|
119
122
|
|
|
120
123
|
if (currentVersion) {
|
|
121
|
-
|
|
124
|
+
const permittedSystems = permittedSystemsByVersion.get(currentVersion.originalVersion) || [];
|
|
125
|
+
|
|
126
|
+
if (permittedSystems.length === 1) {
|
|
127
|
+
currentVersion.label = this.t(`catalog.install.versions.current_${ permittedSystems[0] }`, { ver: this.currentVersion });
|
|
128
|
+
} else {
|
|
129
|
+
currentVersion.label = this.t('catalog.install.versions.current', { ver: this.currentVersion });
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
return out;
|
package/mixins/child-hook.js
CHANGED
|
@@ -8,20 +8,26 @@ export const AFTER_SAVE_HOOKS = '_afterSaveHooks';
|
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
10
|
methods: {
|
|
11
|
+
registerHook(key, boundFn, name, priority = 99, boundFnContext) {
|
|
12
|
+
this._registerHook(key, boundFn, name, priority, boundFnContext);
|
|
13
|
+
},
|
|
14
|
+
|
|
11
15
|
registerBeforeHook(boundFn, name, priority = 99, boundFnContext) {
|
|
12
|
-
this.
|
|
16
|
+
this.registerHook(BEFORE_SAVE_HOOKS, boundFn, name, priority, boundFnContext);
|
|
13
17
|
},
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
this[
|
|
17
|
-
// BEFORE_SAVE_HOOKS is an array of objects with keys
|
|
18
|
-
// fn, name and priority.
|
|
19
|
+
unregisterHook(key, name) {
|
|
20
|
+
this[key] = (this[key] || []).filter((hook) => {
|
|
19
21
|
return hook.name !== name;
|
|
20
22
|
});
|
|
21
23
|
},
|
|
22
24
|
|
|
25
|
+
unregisterBeforeSaveHook(name) {
|
|
26
|
+
this.unregisterHook(BEFORE_SAVE_HOOKS, name);
|
|
27
|
+
},
|
|
28
|
+
|
|
23
29
|
registerAfterHook(boundFn, name, priority = 99, boundFnContext) {
|
|
24
|
-
this.
|
|
30
|
+
this.registerHook(AFTER_SAVE_HOOKS, boundFn, name, priority, boundFnContext);
|
|
25
31
|
},
|
|
26
32
|
|
|
27
33
|
async applyHooks(key, ...args) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
|
|
2
2
|
import { LAST_NAMESPACE } from '@shell/store/prefs';
|
|
3
|
-
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
3
|
+
import { exceptionToErrorsArray, isDoNotLogError } from '@shell/utils/error';
|
|
4
4
|
import ChildHook, { BEFORE_SAVE_HOOKS, AFTER_SAVE_HOOKS } from '@shell/mixins/child-hook';
|
|
5
5
|
import { clear } from '@shell/utils/array';
|
|
6
6
|
import { DEFAULT_WORKSPACE } from '@shell/config/types';
|
|
@@ -184,8 +184,10 @@ export default {
|
|
|
184
184
|
} else {
|
|
185
185
|
this.errors = exceptionToErrorsArray(err);
|
|
186
186
|
}
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
if (!isDoNotLogError(err)) {
|
|
188
|
+
// Provide a stack trace for easier debugging of save errors
|
|
189
|
+
console.error('CreateEditView mixin failed to save: ', err); // eslint-disable-line no-console
|
|
190
|
+
}
|
|
189
191
|
buttonDone && buttonDone(false);
|
|
190
192
|
}
|
|
191
193
|
},
|
|
@@ -5,10 +5,28 @@ import { mapGetters } from 'vuex';
|
|
|
5
5
|
import { ResourceListComponentName } from '../components/ResourceList/resource-list.config';
|
|
6
6
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
7
7
|
import debounce from 'lodash/debounce';
|
|
8
|
-
import { PaginationParamFilter, PaginationFilterField, PaginationArgs } from '@shell/types/store/pagination.types';
|
|
8
|
+
import { PaginationParamFilter, PaginationFilterField, PaginationFilterEquality, PaginationArgs } from '@shell/types/store/pagination.types';
|
|
9
9
|
import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
|
|
10
10
|
import { STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
|
|
11
11
|
|
|
12
|
+
export function parseStateFilter(stateFilter) {
|
|
13
|
+
if (!stateFilter) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const states = stateFilter.split(',').filter(Boolean);
|
|
18
|
+
|
|
19
|
+
if (!states.length) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return [new PaginationFilterField({
|
|
24
|
+
field: 'metadata.state.name',
|
|
25
|
+
value: states.join(','),
|
|
26
|
+
equality: PaginationFilterEquality.IN,
|
|
27
|
+
})];
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
/**
|
|
13
31
|
* Companion mixin used with `resource-fetch` for `ResourceList` to determine if the user needs to filter the list by a single namespace
|
|
14
32
|
*/
|
|
@@ -75,6 +93,7 @@ export default {
|
|
|
75
93
|
const {
|
|
76
94
|
page, perPage, filter, sort, descending
|
|
77
95
|
} = event;
|
|
96
|
+
const stateFilters = parseStateFilter(this.$route?.query?.stateFilter) || [];
|
|
78
97
|
const searchFilters = filter.searchQuery ? filter.searchFields.map((field) => new PaginationFilterField({
|
|
79
98
|
field,
|
|
80
99
|
value: filter.searchQuery,
|
|
@@ -91,6 +110,7 @@ export default {
|
|
|
91
110
|
projectsOrNamespaces: this.requestFilters.projectsOrNamespaces,
|
|
92
111
|
filters: [
|
|
93
112
|
new PaginationParamFilter({ fields: searchFilters }),
|
|
113
|
+
new PaginationParamFilter({ fields: stateFilters }),
|
|
94
114
|
...this.requestFilters.filters, // Apply the additional filters. these aren't from the user but from ns filtering
|
|
95
115
|
]
|
|
96
116
|
});
|
|
@@ -17,6 +17,7 @@ describe('clusterRepo', () => {
|
|
|
17
17
|
jest.spyOn(model, 'save').mockImplementation().mockResolvedValue(true);
|
|
18
18
|
jest.spyOn(model, 'waitForState').mockImplementation().mockResolvedValue(true);
|
|
19
19
|
jest.spyOn(model, '$dispatch', 'get').mockReturnValue(jest.fn());
|
|
20
|
+
jest.spyOn(model, 't', 'get').mockReturnValue((key: string) => key);
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
describe('refresh', () => {
|
|
@@ -59,6 +60,62 @@ describe('clusterRepo', () => {
|
|
|
59
60
|
});
|
|
60
61
|
});
|
|
61
62
|
|
|
63
|
+
describe('defaultRefreshIntervalHours', () => {
|
|
64
|
+
it('returns 24 for OCI repos', () => {
|
|
65
|
+
model.spec.url = 'oci://example.com/chart';
|
|
66
|
+
model.spec.insecurePlainHttp = false;
|
|
67
|
+
expect(model.defaultRefreshIntervalHours).toStrictEqual(24);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('returns 1 for git repos', () => {
|
|
71
|
+
model.spec = { gitRepo: 'https://github.com/example/charts' };
|
|
72
|
+
expect(model.defaultRefreshIntervalHours).toStrictEqual(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns 1 for helm repos', () => {
|
|
76
|
+
model.spec = { url: 'https://charts.example.com' };
|
|
77
|
+
expect(model.defaultRefreshIntervalHours).toStrictEqual(1);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('defaultRefreshInterval', () => {
|
|
82
|
+
it('returns seconds for OCI repos (24 hours)', () => {
|
|
83
|
+
model.spec.url = 'oci://example.com/chart';
|
|
84
|
+
model.spec.insecurePlainHttp = false;
|
|
85
|
+
expect(model.defaultRefreshInterval).toStrictEqual(86400);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns seconds for non-OCI repos (1 hour)', () => {
|
|
89
|
+
model.spec = { gitRepo: 'https://github.com/example/charts' };
|
|
90
|
+
expect(model.defaultRefreshInterval).toStrictEqual(3600);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('refreshIntervalDisplay', () => {
|
|
95
|
+
it.each([
|
|
96
|
+
-1,
|
|
97
|
+
-100,
|
|
98
|
+
])('returns "Disabled" when value is %p', (val) => {
|
|
99
|
+
model.spec.refreshInterval = val;
|
|
100
|
+
expect(model.refreshIntervalDisplay).toStrictEqual('generic.disabled');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('returns formatted duration for positive values', () => {
|
|
104
|
+
model.spec.refreshInterval = 3661;
|
|
105
|
+
expect(model.refreshIntervalDisplay).toStrictEqual('1h 1m 1s');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('returns default formatted duration when not set', () => {
|
|
109
|
+
model.spec = { url: 'https://charts.example.com' };
|
|
110
|
+
expect(model.refreshIntervalDisplay).toStrictEqual('1h');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('returns default formatted duration for OCI when not set', () => {
|
|
114
|
+
model.spec = { url: 'oci://example.com/chart', insecurePlainHttp: false };
|
|
115
|
+
expect(model.refreshIntervalDisplay).toStrictEqual('1d');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
62
119
|
describe('refreshBulk', () => {
|
|
63
120
|
it('calls refresh(false) on all items and then dispatches a single catalog/load with all repoKeys', async() => {
|
|
64
121
|
const mockItem1 = {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import ClusterScan from '@shell/models/compliance.cattle.io.clusterscan';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build a ClusterScan model wired to a mocked store `dispatch`.
|
|
5
|
+
* `$dispatch` resolves to `this.$ctx.dispatch`, so injecting it via the
|
|
6
|
+
* constructor context is enough to exercise `_resolveExportMetadata`.
|
|
7
|
+
*/
|
|
8
|
+
const makeScan = (dispatch: jest.Mock) => new ClusterScan({}, {
|
|
9
|
+
getters: {},
|
|
10
|
+
rootGetters: {},
|
|
11
|
+
dispatch,
|
|
12
|
+
} as any) as any;
|
|
13
|
+
|
|
14
|
+
const benchmarkWithConfigMap = {
|
|
15
|
+
spec: {
|
|
16
|
+
customBenchmarkConfigMapName: 'metadata-cm',
|
|
17
|
+
customBenchmarkConfigMapNamespace: 'compliance',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('class: ClusterScan', () => {
|
|
22
|
+
describe('method: _resolveExportMetadata', () => {
|
|
23
|
+
it('returns empty metadata and decorations when the benchmark is undefined', async() => {
|
|
24
|
+
const dispatch = jest.fn();
|
|
25
|
+
const scan = makeScan(dispatch);
|
|
26
|
+
|
|
27
|
+
const result = await scan._resolveExportMetadata(undefined);
|
|
28
|
+
|
|
29
|
+
expect(result).toStrictEqual({ metadata: {}, decorations: {} });
|
|
30
|
+
expect(dispatch).not.toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('returns empty when the ConfigMap name is missing', async() => {
|
|
34
|
+
const dispatch = jest.fn();
|
|
35
|
+
const scan = makeScan(dispatch);
|
|
36
|
+
|
|
37
|
+
const result = await scan._resolveExportMetadata({ spec: { customBenchmarkConfigMapNamespace: 'compliance' } });
|
|
38
|
+
|
|
39
|
+
expect(result).toStrictEqual({ metadata: {}, decorations: {} });
|
|
40
|
+
expect(dispatch).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns empty when the ConfigMap namespace is missing', async() => {
|
|
44
|
+
const dispatch = jest.fn();
|
|
45
|
+
const scan = makeScan(dispatch);
|
|
46
|
+
|
|
47
|
+
const result = await scan._resolveExportMetadata({ spec: { customBenchmarkConfigMapName: 'metadata-cm' } });
|
|
48
|
+
|
|
49
|
+
expect(result).toStrictEqual({ metadata: {}, decorations: {} });
|
|
50
|
+
expect(dispatch).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('fetches the ConfigMap by namespace/name from the configmap type', async() => {
|
|
54
|
+
const dispatch = jest.fn().mockResolvedValue({ data: {} });
|
|
55
|
+
const scan = makeScan(dispatch);
|
|
56
|
+
|
|
57
|
+
await scan._resolveExportMetadata(benchmarkWithConfigMap);
|
|
58
|
+
|
|
59
|
+
expect(dispatch).toHaveBeenCalledWith('find', { type: 'configmap', id: 'compliance/metadata-cm' });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('returns empty when the ConfigMap has no metadata.yaml data key', async() => {
|
|
63
|
+
const dispatch = jest.fn().mockResolvedValue({ data: { 'other.yaml': 'title: x' } });
|
|
64
|
+
const scan = makeScan(dispatch);
|
|
65
|
+
|
|
66
|
+
const result = await scan._resolveExportMetadata(benchmarkWithConfigMap);
|
|
67
|
+
|
|
68
|
+
expect(result).toStrictEqual({ metadata: {}, decorations: {} });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns empty when the ConfigMap itself is not found', async() => {
|
|
72
|
+
const dispatch = jest.fn().mockResolvedValue(undefined);
|
|
73
|
+
const scan = makeScan(dispatch);
|
|
74
|
+
|
|
75
|
+
const result = await scan._resolveExportMetadata(benchmarkWithConfigMap);
|
|
76
|
+
|
|
77
|
+
expect(result).toStrictEqual({ metadata: {}, decorations: {} });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('splits top-level fields into metadata and the checks map into decorations', async() => {
|
|
81
|
+
const yaml = [
|
|
82
|
+
'title: CIS Kubernetes Benchmark',
|
|
83
|
+
'description: A CIS profile',
|
|
84
|
+
'referenceType: cis',
|
|
85
|
+
'checks:',
|
|
86
|
+
' "5.1.1":',
|
|
87
|
+
' ruleId: CIS-5.1.1-rule',
|
|
88
|
+
' severity: high',
|
|
89
|
+
' idents:',
|
|
90
|
+
' - system: https://www.cisecurity.org/controls/',
|
|
91
|
+
' value: CIS-CSC-3',
|
|
92
|
+
' "5.1.2":',
|
|
93
|
+
' ruleId: CIS-5.1.2-rule',
|
|
94
|
+
].join('\n');
|
|
95
|
+
const dispatch = jest.fn().mockResolvedValue({ data: { 'metadata.yaml': yaml } });
|
|
96
|
+
const scan = makeScan(dispatch);
|
|
97
|
+
|
|
98
|
+
const result = await scan._resolveExportMetadata(benchmarkWithConfigMap);
|
|
99
|
+
|
|
100
|
+
expect(result.metadata).toStrictEqual({
|
|
101
|
+
title: 'CIS Kubernetes Benchmark',
|
|
102
|
+
description: 'A CIS profile',
|
|
103
|
+
referenceType: 'cis',
|
|
104
|
+
});
|
|
105
|
+
expect(result.decorations).toStrictEqual({
|
|
106
|
+
'5.1.1': {
|
|
107
|
+
ruleId: 'CIS-5.1.1-rule',
|
|
108
|
+
severity: 'high',
|
|
109
|
+
idents: [{ system: 'https://www.cisecurity.org/controls/', value: 'CIS-CSC-3' }],
|
|
110
|
+
},
|
|
111
|
+
'5.1.2': { ruleId: 'CIS-5.1.2-rule' },
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('returns empty decorations when metadata.yaml has no checks map', async() => {
|
|
116
|
+
const yaml = ['title: STIG', 'source: disa'].join('\n');
|
|
117
|
+
const dispatch = jest.fn().mockResolvedValue({ data: { 'metadata.yaml': yaml } });
|
|
118
|
+
const scan = makeScan(dispatch);
|
|
119
|
+
|
|
120
|
+
const result = await scan._resolveExportMetadata(benchmarkWithConfigMap);
|
|
121
|
+
|
|
122
|
+
expect(result.metadata).toStrictEqual({ title: 'STIG', source: 'disa' });
|
|
123
|
+
expect(result.decorations).toStrictEqual({});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('returns empty when the ConfigMap lookup rejects', async() => {
|
|
127
|
+
const dispatch = jest.fn().mockRejectedValue(new Error('not found'));
|
|
128
|
+
const scan = makeScan(dispatch);
|
|
129
|
+
|
|
130
|
+
const result = await scan._resolveExportMetadata(benchmarkWithConfigMap);
|
|
131
|
+
|
|
132
|
+
expect(result).toStrictEqual({ metadata: {}, decorations: {} });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns empty when metadata.yaml is malformed YAML', async() => {
|
|
136
|
+
const dispatch = jest.fn().mockResolvedValue({ data: { 'metadata.yaml': 'title: "unterminated' } });
|
|
137
|
+
const scan = makeScan(dispatch);
|
|
138
|
+
|
|
139
|
+
const result = await scan._resolveExportMetadata(benchmarkWithConfigMap);
|
|
140
|
+
|
|
141
|
+
expect(result).toStrictEqual({ metadata: {}, decorations: {} });
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import FleetApplication from '@shell/models/fleet-application.js';
|
|
2
|
+
|
|
3
|
+
describe('class FleetApplication', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
jest.restoreAllMocks();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe('applicationType', () => {
|
|
9
|
+
it('should return the kind property', () => {
|
|
10
|
+
const instance = new FleetApplication({ kind: 'GitRepo' });
|
|
11
|
+
|
|
12
|
+
expect(instance.applicationType).toStrictEqual('GitRepo');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('targetClusters', () => {
|
|
17
|
+
function createFleetApplication(targets: any[] | undefined, clusters: any[], workspaceId = 'fleet-default', groups: any[] = []) {
|
|
18
|
+
const workspace = {
|
|
19
|
+
id: workspaceId,
|
|
20
|
+
clusters,
|
|
21
|
+
clusterGroups: groups,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
jest.spyOn(FleetApplication.prototype, '$getters', 'get').mockReturnValue({ byId: () => workspace });
|
|
25
|
+
|
|
26
|
+
return new FleetApplication({
|
|
27
|
+
metadata: { namespace: workspaceId },
|
|
28
|
+
spec: { targets },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
it.each([
|
|
33
|
+
[
|
|
34
|
+
'metadata.name',
|
|
35
|
+
[{ clusterName: 'c-m-abc123' }],
|
|
36
|
+
[{
|
|
37
|
+
id: 'fleet-default/c-m-abc123', metadata: { name: 'c-m-abc123' }, nameDisplay: 'my-cluster'
|
|
38
|
+
}],
|
|
39
|
+
[{
|
|
40
|
+
id: 'fleet-default/c-m-abc123', metadata: { name: 'c-m-abc123' }, nameDisplay: 'my-cluster'
|
|
41
|
+
}],
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
'nameDisplay when metadata.name does not match',
|
|
45
|
+
[{ clusterName: 'my-display-name' }],
|
|
46
|
+
[{
|
|
47
|
+
id: 'fleet-default/c-m-abc123', metadata: { name: 'c-m-abc123' }, nameDisplay: 'my-display-name'
|
|
48
|
+
}],
|
|
49
|
+
[{
|
|
50
|
+
id: 'fleet-default/c-m-abc123', metadata: { name: 'c-m-abc123' }, nameDisplay: 'my-display-name'
|
|
51
|
+
}],
|
|
52
|
+
],
|
|
53
|
+
])('should find cluster by %s', (_label, targets, clusters, expected) => {
|
|
54
|
+
const app = createFleetApplication(targets, clusters);
|
|
55
|
+
|
|
56
|
+
expect(app.targetClusters).toStrictEqual(expected);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should prefer metadata.name match over nameDisplay match', () => {
|
|
60
|
+
const clusters = [
|
|
61
|
+
{
|
|
62
|
+
id: 'fleet-default/exact-match',
|
|
63
|
+
metadata: { name: 'exact-match' },
|
|
64
|
+
nameDisplay: 'display-a',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'fleet-default/c-m-other',
|
|
68
|
+
metadata: { name: 'c-m-other' },
|
|
69
|
+
nameDisplay: 'exact-match',
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const app = createFleetApplication([{ clusterName: 'exact-match' }], clusters);
|
|
74
|
+
|
|
75
|
+
expect(app.targetClusters).toStrictEqual([clusters[0]]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should return empty array when no cluster matches by name or nameDisplay', () => {
|
|
79
|
+
const clusters = [
|
|
80
|
+
{
|
|
81
|
+
id: 'fleet-default/c-m-abc123',
|
|
82
|
+
metadata: { name: 'c-m-abc123' },
|
|
83
|
+
nameDisplay: 'my-cluster',
|
|
84
|
+
}
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const app = createFleetApplication([{ clusterName: 'non-existent' }], clusters);
|
|
88
|
+
|
|
89
|
+
expect(app.targetClusters).toStrictEqual([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should handle multiple targets with mixed name and nameDisplay matches', () => {
|
|
93
|
+
const clusters = [
|
|
94
|
+
{
|
|
95
|
+
id: 'fleet-default/c-m-abc123',
|
|
96
|
+
metadata: { name: 'c-m-abc123' },
|
|
97
|
+
nameDisplay: 'cluster-alpha',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'fleet-default/c-m-def456',
|
|
101
|
+
metadata: { name: 'c-m-def456' },
|
|
102
|
+
nameDisplay: 'cluster-beta',
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
const targets = [
|
|
107
|
+
{ clusterName: 'c-m-abc123' },
|
|
108
|
+
{ clusterName: 'cluster-beta' },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const app = createFleetApplication(targets, clusters);
|
|
112
|
+
|
|
113
|
+
expect(app.targetClusters).toStrictEqual([clusters[0], clusters[1]]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return empty array when targets is empty', () => {
|
|
117
|
+
const app = createFleetApplication([], []);
|
|
118
|
+
|
|
119
|
+
expect(app.targetClusters).toStrictEqual([]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should return empty array when targets is undefined', () => {
|
|
123
|
+
const app = createFleetApplication(undefined, []);
|
|
124
|
+
|
|
125
|
+
expect(app.targetClusters).toStrictEqual([]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should return empty array when workspace has no clusters', () => {
|
|
129
|
+
const app = createFleetApplication([{ clusterName: 'any-name' }], []);
|
|
130
|
+
|
|
131
|
+
expect(app.targetClusters).toStrictEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle cluster with undefined nameDisplay gracefully', () => {
|
|
135
|
+
const clusters = [
|
|
136
|
+
{
|
|
137
|
+
id: 'fleet-default/c-m-abc123',
|
|
138
|
+
metadata: { name: 'c-m-abc123' },
|
|
139
|
+
nameDisplay: undefined,
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const app = createFleetApplication([{ clusterName: 'c-m-abc123' }], clusters);
|
|
144
|
+
|
|
145
|
+
expect(app.targetClusters).toStrictEqual([clusters[0]]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return local cluster targets when workspace is fleet-local', () => {
|
|
149
|
+
const localTargetClusters = [
|
|
150
|
+
{
|
|
151
|
+
id: 'fleet-local/local',
|
|
152
|
+
metadata: { name: 'local' },
|
|
153
|
+
nameDisplay: 'local',
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const groups = [
|
|
158
|
+
{
|
|
159
|
+
id: 'fleet-local/default',
|
|
160
|
+
targetClusters: localTargetClusters,
|
|
161
|
+
}
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const app = createFleetApplication([], [], 'fleet-local', groups);
|
|
165
|
+
|
|
166
|
+
expect(app.targetClusters).toStrictEqual(localTargetClusters);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should return empty array when workspace is fleet-local and default group is missing', () => {
|
|
170
|
+
const app = createFleetApplication([], [], 'fleet-local', []);
|
|
171
|
+
|
|
172
|
+
expect(app.targetClusters).toStrictEqual([]);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|