@rancher/shell 3.0.12-rc.5 → 3.0.12-rc.7
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/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/intf/shell-api/slide-in.ts +46 -17
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/apis/shell/__tests__/slide-in.test.ts +83 -2
- package/apis/shell/slide-in.ts +20 -0
- package/assets/images/providers/traefik.png +0 -0
- package/assets/translations/en-us.yaml +10 -4
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/ExplorerProjectsNamespaces.vue +2 -2
- package/components/GrafanaDashboard.vue +2 -8
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +9 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -1
- package/components/SlideInPanelManager.vue +103 -25
- package/components/__tests__/SlideInPanelManager.spec.ts +153 -10
- package/components/auth/AuthBanner.vue +1 -1
- package/components/form/Security.vue +1 -1
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/config/labels-annotations.js +1 -0
- package/config/table-headers.js +5 -2
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/plugins-loader.js +2 -0
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +0 -2
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/namespace.test.ts +133 -8
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +57 -1
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/namespace.js +34 -2
- package/models/pod.js +12 -10
- package/models/provisioning.cattle.io.cluster.js +20 -13
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
- package/pages/c/_cluster/apps/charts/install.vue +29 -0
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +136 -1
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +69 -1
- package/pages/c/_cluster/fleet/index.vue +5 -9
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/pkg/vue.config.js +4 -3
- package/plugins/codemirror.js +2 -0
- package/plugins/dashboard-store/resource-class.js +13 -9
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/store/__tests__/action-menu.test.ts +622 -0
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +143 -43
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/__tests__/wm.test.ts +503 -0
- package/store/auth.js +11 -1
- package/store/aws.js +19 -4
- package/store/features.js +1 -0
- package/store/plugins.js +6 -0
- package/store/slideInPanel.ts +15 -3
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +84 -2
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/fleet-appco.test.ts +23 -0
- package/utils/async.ts +2 -0
- package/utils/fleet-appco.ts +6 -1
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/utils/sort.js +1 -1
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import VmwarevsphereMachineTemplate from '@shell/models/rke-machine.cattle.io.vmwarevspheremachinetemplate';
|
|
2
|
+
|
|
3
|
+
describe('class VmwarevsphereMachineTemplate', () => {
|
|
4
|
+
describe('providerLocation', () => {
|
|
5
|
+
it.each([
|
|
6
|
+
['/DC1/host/ClusterA/Resources', 'ClusterA'],
|
|
7
|
+
['/DC1/host/ClusterA/Resources/PoolX', 'ClusterA'],
|
|
8
|
+
['/DC1/host/ClusterA/Resources/', 'ClusterA'],
|
|
9
|
+
['/DC1/host/esxi01/Resources', 'esxi01'],
|
|
10
|
+
['/DC1/vm/FolderA', null],
|
|
11
|
+
['/DC1/host/foo/ResourcesPool/bar', null],
|
|
12
|
+
['/DC1/host//Resources/PoolX', null],
|
|
13
|
+
['', null],
|
|
14
|
+
[null, null],
|
|
15
|
+
[undefined, null],
|
|
16
|
+
])('returns %p for pool %p', (pool: string | null | undefined, expected: string | null) => {
|
|
17
|
+
const machineTemplate = new VmwarevsphereMachineTemplate({ spec: { template: { spec: { pool } } } });
|
|
18
|
+
|
|
19
|
+
expect(machineTemplate.providerLocation).toStrictEqual(expected);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
package/models/namespace.js
CHANGED
|
@@ -2,12 +2,15 @@ import SYSTEM_NAMESPACES from '@shell/config/system-namespaces';
|
|
|
2
2
|
import {
|
|
3
3
|
PROJECT, SYSTEM_NAMESPACE, ISTIO as ISTIO_LABELS, FLEET, RESOURCE_QUOTA
|
|
4
4
|
} from '@shell/config/labels-annotations';
|
|
5
|
-
import { ISTIO, MANAGEMENT } from '@shell/config/types';
|
|
5
|
+
import { ISTIO, MANAGEMENT, LOCAL_CLUSTER } from '@shell/config/types';
|
|
6
6
|
|
|
7
7
|
import { get, set } from '@shell/utils/object';
|
|
8
8
|
import { insertAt, isArray } from '@shell/utils/array';
|
|
9
9
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
10
10
|
import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
|
|
11
|
+
import { NAME as MANAGER } from '@shell/config/product/manager';
|
|
12
|
+
import { NAME as EXPLORER } from '@shell/config/product/explorer';
|
|
13
|
+
import sideNavService from '@shell/components/nav/TopLevelMenu.helper';
|
|
11
14
|
import { hasPSALabels, getPSATooltipsDescription, getPSALabels } from '@shell/utils/pod-security-admission';
|
|
12
15
|
import { PSAIconsDisplay, PSALabelsNamespaceVersion } from '@shell/config/pod-security-admission';
|
|
13
16
|
|
|
@@ -188,7 +191,14 @@ export default class Namespace extends SteveModel {
|
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
get listLocation() {
|
|
191
|
-
const
|
|
194
|
+
const productId = this.$rootGetters['productId'];
|
|
195
|
+
const isManagerProduct = productId === MANAGER;
|
|
196
|
+
const cluster = isManagerProduct ? LOCAL_CLUSTER : this.$rootGetters['clusterId'];
|
|
197
|
+
|
|
198
|
+
const listLocation = {
|
|
199
|
+
name: this.$rootGetters['isRancher'] ? 'c-cluster-product-projectsnamespaces' : 'c-cluster-product-resource',
|
|
200
|
+
params: { cluster, product: EXPLORER },
|
|
201
|
+
};
|
|
192
202
|
|
|
193
203
|
// Harvester uses these resource directly... but has different routes. listLocation covers routes leading back to route
|
|
194
204
|
if (this.$rootGetters['currentProduct'].inStore === HARVESTER) {
|
|
@@ -202,6 +212,12 @@ export default class Namespace extends SteveModel {
|
|
|
202
212
|
get _detailLocation() {
|
|
203
213
|
const _detailLocation = super._detailLocation;
|
|
204
214
|
|
|
215
|
+
// Namespace detail pages belong to the local cluster's explorer, not Cluster Management
|
|
216
|
+
if (this.$rootGetters['productId'] === MANAGER) {
|
|
217
|
+
_detailLocation.params.cluster = LOCAL_CLUSTER;
|
|
218
|
+
_detailLocation.params.product = EXPLORER;
|
|
219
|
+
}
|
|
220
|
+
|
|
205
221
|
return _detailLocation;
|
|
206
222
|
}
|
|
207
223
|
|
|
@@ -285,6 +301,22 @@ export default class Namespace extends SteveModel {
|
|
|
285
301
|
glance.splice(namespaceIndex, 1, this.projectGlance);
|
|
286
302
|
}
|
|
287
303
|
|
|
304
|
+
// In Cluster Management, the type link points to the local cluster's explorer.
|
|
305
|
+
// If the user cannot access the local cluster, show type as plain text.
|
|
306
|
+
const productId = this.$rootGetters['productId'];
|
|
307
|
+
|
|
308
|
+
if (productId === MANAGER) {
|
|
309
|
+
const hasLocalCluster = sideNavService.helper.clustersPinned.some((c) => c.id === LOCAL_CLUSTER) ||
|
|
310
|
+
sideNavService.helper.clustersOthers.some((c) => c.id === LOCAL_CLUSTER);
|
|
311
|
+
const typeIndex = glance.findIndex((item) => item.name === 'type');
|
|
312
|
+
|
|
313
|
+
if (!hasLocalCluster && typeIndex > -1) {
|
|
314
|
+
glance[typeIndex] = {
|
|
315
|
+
...glance[typeIndex], formatter: undefined, formatterOpts: undefined
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
288
320
|
// projectGlance could be undefined
|
|
289
321
|
return glance.filter(Boolean);
|
|
290
322
|
}
|
package/models/pod.js
CHANGED
|
@@ -309,19 +309,21 @@ export default class Pod extends WorkloadService {
|
|
|
309
309
|
save() {
|
|
310
310
|
const prev = { ...this };
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
if (this.spec?.template) {
|
|
313
|
+
const { metadata, spec } = this.spec.template;
|
|
313
314
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
this.spec = {
|
|
316
|
+
...this.spec,
|
|
317
|
+
...spec
|
|
318
|
+
};
|
|
318
319
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
this.metadata = {
|
|
321
|
+
...this.metadata,
|
|
322
|
+
...metadata
|
|
323
|
+
};
|
|
323
324
|
|
|
324
|
-
|
|
325
|
+
delete this.spec.template;
|
|
326
|
+
}
|
|
325
327
|
|
|
326
328
|
// IF there is an error POD world model get overwritten
|
|
327
329
|
// For the workloads this need be reset back
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
CONFIG_MAP, AUTOSCALER_CONFIG_MAP_ID,
|
|
4
4
|
EVENT, OPERATION
|
|
5
5
|
} from '@shell/config/types';
|
|
6
|
+
import { NAME as EXPLORER } from '@shell/config/product/explorer';
|
|
7
|
+
import sideNavService from '@shell/components/nav/TopLevelMenu.helper';
|
|
6
8
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
7
9
|
import { findBy } from '@shell/utils/array';
|
|
8
10
|
import { get, set } from '@shell/utils/object';
|
|
@@ -788,21 +790,26 @@ export default class ProvCluster extends SteveModel {
|
|
|
788
790
|
}
|
|
789
791
|
|
|
790
792
|
get namespaceLocation() {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
id: this.namespace
|
|
801
|
-
}
|
|
802
|
-
};
|
|
793
|
+
// Check side nav for local cluster access — `management/byId` only reflects what's
|
|
794
|
+
// currently cached, which on a downstream detail page is just the downstream cluster
|
|
795
|
+
// (so admins would falsely look like they have no local access). The side nav reflects
|
|
796
|
+
// permission, not cache state, so it's accurate for both admins and downstream-only users.
|
|
797
|
+
const hasLocalCluster = sideNavService.helper.clustersPinned.some((c) => c.id === LOCAL_CLUSTER) ||
|
|
798
|
+
sideNavService.helper.clustersOthers.some((c) => c.id === LOCAL_CLUSTER);
|
|
799
|
+
|
|
800
|
+
if (!hasLocalCluster) {
|
|
801
|
+
return null;
|
|
803
802
|
}
|
|
804
803
|
|
|
805
|
-
return
|
|
804
|
+
return {
|
|
805
|
+
name: 'c-cluster-product-resource-id',
|
|
806
|
+
params: {
|
|
807
|
+
cluster: LOCAL_CLUSTER,
|
|
808
|
+
product: EXPLORER,
|
|
809
|
+
resource: NAMESPACE,
|
|
810
|
+
id: this.namespace
|
|
811
|
+
}
|
|
812
|
+
};
|
|
806
813
|
}
|
|
807
814
|
|
|
808
815
|
/**
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { formatSi } from '@shell/utils/units';
|
|
2
2
|
import MachineTemplate from './rke-machine.cattle.io.machinetemplate';
|
|
3
3
|
|
|
4
|
+
const POOL_LOCATION_MATCHER = /\/host\/([^/]+)\/Resources(?:\/|$)/;
|
|
5
|
+
|
|
4
6
|
export default class VmwarevsphereMachineTemplate extends MachineTemplate {
|
|
5
7
|
get provider() {
|
|
6
8
|
return 'vmwarevsphere';
|
|
7
9
|
}
|
|
8
10
|
|
|
11
|
+
get providerLocation() {
|
|
12
|
+
const pool = this.spec?.template?.spec?.pool;
|
|
13
|
+
const match = pool?.match(POOL_LOCATION_MATCHER);
|
|
14
|
+
|
|
15
|
+
return match?.[1] || null;
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
get providerSize() {
|
|
10
19
|
const size = formatSi(this.spec.template.spec.memorySize * 1048576, 1024, 'iB');
|
|
11
20
|
|
package/package.json
CHANGED
package/pages/auth/login.vue
CHANGED
|
@@ -177,7 +177,7 @@ export default {
|
|
|
177
177
|
this.vendor = getVendor();
|
|
178
178
|
this.providers = providers;
|
|
179
179
|
this.hasLocal = hasLocal;
|
|
180
|
-
this.showLocal = !hasOthers || (this.$route.query[LOCAL] === _FLAGGED);
|
|
180
|
+
this.showLocal = hasLocal && (!hasOthers || (this.$route.query[LOCAL] === _FLAGGED));
|
|
181
181
|
this.customLoginError = JSON.parse(value).loginError;
|
|
182
182
|
this.firstLogin = firstLoginSetting?.value === 'true';
|
|
183
183
|
this.username = this.firstLogin ? 'admin' : this.username;
|
|
@@ -522,4 +522,114 @@ describe('page: Install', () => {
|
|
|
522
522
|
expect(wrapper.vm.registryPullSecret).toBeNull();
|
|
523
523
|
});
|
|
524
524
|
});
|
|
525
|
+
|
|
526
|
+
describe('computed properties: monitoring banners', () => {
|
|
527
|
+
const setupComponent = (existing: any, releaseName: string, componentName: string, chartName: string, installedApps: any[] = [], certified = 'rancher') => {
|
|
528
|
+
const mockStore = {
|
|
529
|
+
getters: {
|
|
530
|
+
'i18n/withFallback': () => '',
|
|
531
|
+
'catalog/inStore': 'cluster',
|
|
532
|
+
'features/get': () => false,
|
|
533
|
+
'type-map/hasCustomChart': () => false,
|
|
534
|
+
'wm/byId': () => null,
|
|
535
|
+
'i18n/t': (k: string) => k,
|
|
536
|
+
'prefs/get': () => {},
|
|
537
|
+
'management/all': () => [],
|
|
538
|
+
'cluster/all': () => [],
|
|
539
|
+
'cluster/byId': (type: string, id: string) => {
|
|
540
|
+
if (type === 'catalog.cattle.io.app') {
|
|
541
|
+
return installedApps.find((app) => app.id === id);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return null;
|
|
545
|
+
},
|
|
546
|
+
'catalog/charts': [],
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
return mount(Install as any, {
|
|
551
|
+
global: {
|
|
552
|
+
mocks: {
|
|
553
|
+
$store: mockStore,
|
|
554
|
+
$route: { query: {} },
|
|
555
|
+
$fetchState: { pending: false },
|
|
556
|
+
t: (k: string) => k,
|
|
557
|
+
},
|
|
558
|
+
stubs: {
|
|
559
|
+
Loading: true,
|
|
560
|
+
Wizard: true,
|
|
561
|
+
Banner: true,
|
|
562
|
+
Checkbox: true,
|
|
563
|
+
LabeledInput: true,
|
|
564
|
+
LabeledSelect: true,
|
|
565
|
+
NameNsDescription: true,
|
|
566
|
+
Tabbed: true,
|
|
567
|
+
Questions: true,
|
|
568
|
+
YamlEditor: true,
|
|
569
|
+
ResourceCancelModal: true,
|
|
570
|
+
UnitInput: true,
|
|
571
|
+
TypeDescription: true,
|
|
572
|
+
LazyImage: true,
|
|
573
|
+
ChartReadme: true,
|
|
574
|
+
ButtonGroup: true,
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
data() {
|
|
578
|
+
return {
|
|
579
|
+
existing,
|
|
580
|
+
version: {
|
|
581
|
+
annotations: {
|
|
582
|
+
[CATALOG_ANNOTATIONS.RELEASE_NAME]: releaseName,
|
|
583
|
+
[CATALOG_ANNOTATIONS.COMPONENT]: componentName,
|
|
584
|
+
[CATALOG_ANNOTATIONS.CERTIFIED]: certified,
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
chart: {
|
|
588
|
+
chartName,
|
|
589
|
+
versions: []
|
|
590
|
+
},
|
|
591
|
+
query: { versionName: '1.0.0' }
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
it('showMonitoringBanner should return translation key if releaseName matches rancher-monitoring (install or edit)', () => {
|
|
598
|
+
const wrapper1 = setupComponent(true, 'rancher-monitoring', '', '');
|
|
599
|
+
|
|
600
|
+
expect((wrapper1.vm as any).showMonitoringBanner).toBe('catalog.install.steps.basics.oldMonitoringChartWarning');
|
|
601
|
+
|
|
602
|
+
const wrapper2 = setupComponent(true, '', 'rancher-monitoring', 'rancher-monitoring');
|
|
603
|
+
|
|
604
|
+
expect((wrapper2.vm as any).showMonitoringBanner).toBeNull();
|
|
605
|
+
|
|
606
|
+
const wrapper3 = setupComponent(false, 'rancher-monitoring', '', '');
|
|
607
|
+
|
|
608
|
+
expect((wrapper3.vm as any).showMonitoringBanner).toBe('catalog.install.steps.basics.oldMonitoringChartWarning');
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('showMonitoringBanner should return translation key if existing is false and releaseName matches rancher-monitoring-dashboards', () => {
|
|
612
|
+
const wrapper1 = setupComponent(false, 'rancher-monitoring-dashboards', '', '');
|
|
613
|
+
|
|
614
|
+
expect((wrapper1.vm as any).showMonitoringBanner).toBe('catalog.install.steps.basics.newMonitoringChartWarning');
|
|
615
|
+
|
|
616
|
+
const wrapper2 = setupComponent(false, '', 'rancher-monitoring-dashboards', 'rancher-monitoring-dashboards');
|
|
617
|
+
|
|
618
|
+
expect((wrapper2.vm as any).showMonitoringBanner).toBeNull();
|
|
619
|
+
|
|
620
|
+
const wrapper3 = setupComponent(true, 'rancher-monitoring-dashboards', '', '');
|
|
621
|
+
|
|
622
|
+
expect((wrapper3.vm as any).showMonitoringBanner).toBeNull();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('showMonitoringBanner should return null when the chart is not Rancher-certified, even if the release name matches', () => {
|
|
626
|
+
const oldChartThirdParty = setupComponent(false, 'rancher-monitoring', '', '', [], 'partner');
|
|
627
|
+
|
|
628
|
+
expect((oldChartThirdParty.vm as any).showMonitoringBanner).toBeNull();
|
|
629
|
+
|
|
630
|
+
const newChartThirdParty = setupComponent(false, 'rancher-monitoring-dashboards', '', '', [], '');
|
|
631
|
+
|
|
632
|
+
expect((newChartThirdParty.vm as any).showMonitoringBanner).toBeNull();
|
|
633
|
+
});
|
|
634
|
+
});
|
|
525
635
|
});
|
|
@@ -515,6 +515,29 @@ export default {
|
|
|
515
515
|
...mapGetters({ inStore: 'catalog/inStore', features: 'features/get' }),
|
|
516
516
|
mcm: mapFeature(MULTI_CLUSTER),
|
|
517
517
|
|
|
518
|
+
showMonitoringBanner() {
|
|
519
|
+
const annotations = this.version?.annotations || {};
|
|
520
|
+
const releaseName = annotations[CATALOG_ANNOTATIONS.RELEASE_NAME];
|
|
521
|
+
const certified = annotations[CATALOG_ANNOTATIONS.CERTIFIED];
|
|
522
|
+
|
|
523
|
+
// Only show monitoring banners for the Rancher-certified charts —
|
|
524
|
+
// a third-party chart that happens to reuse the release name should not
|
|
525
|
+
// trigger our migration prompts.
|
|
526
|
+
if (certified !== 'rancher') {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (releaseName === 'rancher-monitoring') {
|
|
531
|
+
return this.t('catalog.install.steps.basics.oldMonitoringChartWarning');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (!this.existing && releaseName === 'rancher-monitoring-dashboards') {
|
|
535
|
+
return this.t('catalog.install.steps.basics.newMonitoringChartWarning');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return null;
|
|
539
|
+
},
|
|
540
|
+
|
|
518
541
|
/**
|
|
519
542
|
* Return list of variables to filter chart questions
|
|
520
543
|
*/
|
|
@@ -1678,6 +1701,12 @@ export default {
|
|
|
1678
1701
|
</template>
|
|
1679
1702
|
<template #basics>
|
|
1680
1703
|
<div class="step__basic">
|
|
1704
|
+
<Banner
|
|
1705
|
+
v-if="showMonitoringBanner"
|
|
1706
|
+
color="warning"
|
|
1707
|
+
>
|
|
1708
|
+
{{ showMonitoringBanner }}
|
|
1709
|
+
</Banner>
|
|
1681
1710
|
<Banner
|
|
1682
1711
|
v-if="step1Description"
|
|
1683
1712
|
color="info"
|
|
@@ -6,6 +6,7 @@ import { MODE, _EDIT } from '@shell/config/query-params';
|
|
|
6
6
|
import { authProvidersInfo } from '@shell/utils/auth';
|
|
7
7
|
import { Banner } from '@components/Banner';
|
|
8
8
|
import Loading from '@shell/components/Loading';
|
|
9
|
+
import { HIDE_LOCAL_AUTH_PROVIDER } from '@shell/store/features';
|
|
9
10
|
|
|
10
11
|
const resource = MANAGEMENT.AUTH_CONFIG;
|
|
11
12
|
|
|
@@ -28,8 +29,9 @@ export default {
|
|
|
28
29
|
data() {
|
|
29
30
|
return {
|
|
30
31
|
// Provided by fetch later
|
|
31
|
-
enabled:
|
|
32
|
-
nonLocal:
|
|
32
|
+
enabled: false,
|
|
33
|
+
nonLocal: null,
|
|
34
|
+
disableLocalAuth: this.$store.getters['features/get'](HIDE_LOCAL_AUTH_PROVIDER)
|
|
33
35
|
};
|
|
34
36
|
},
|
|
35
37
|
|
|
@@ -100,8 +102,13 @@ export default {
|
|
|
100
102
|
<router-link :to="localUsersRoute">
|
|
101
103
|
{{ t('authConfig.manageLocal') }}
|
|
102
104
|
</router-link>
|
|
103
|
-
<br>
|
|
104
|
-
|
|
105
|
+
<br><br>
|
|
106
|
+
<template v-if="disableLocalAuth">
|
|
107
|
+
{{ t('authConfig.bannerEnableAuthProvider', null, true) }}
|
|
108
|
+
</template>
|
|
109
|
+
<template v-else>
|
|
110
|
+
{{ t('authConfig.noneEnabled') }}
|
|
111
|
+
</template>
|
|
105
112
|
</div>
|
|
106
113
|
</Banner>
|
|
107
114
|
<SelectIconGrid
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { useWorkloadDashboard } from '@shell/pages/c/_cluster/explorer/workload-dashboard/composable';
|
|
2
2
|
import { WORKLOAD_RESOURCE_TYPES } from '@shell/pages/c/_cluster/explorer/workload-dashboard/types';
|
|
3
|
+
import { WORKLOAD_TYPES } from '@shell/config/types';
|
|
3
4
|
import { defineComponent, h } from 'vue';
|
|
4
5
|
import { shallowMount, flushPromises } from '@vue/test-utils';
|
|
5
6
|
|
|
7
|
+
const WORKLOAD_DASHBOARD_ROUTE = 'c-cluster-explorer-workload-dashboard';
|
|
8
|
+
|
|
6
9
|
const mockGetters: Record<string, any> = {};
|
|
7
10
|
const mockDispatch = jest.fn();
|
|
8
11
|
const mockRouterPush = jest.fn();
|
|
12
|
+
const mockCurrentRoute = { value: { name: WORKLOAD_DASHBOARD_ROUTE } };
|
|
9
13
|
|
|
10
14
|
jest.mock('vuex', () => ({
|
|
11
15
|
useStore: () => ({
|
|
@@ -18,7 +22,7 @@ jest.mock('vuex', () => ({
|
|
|
18
22
|
}),
|
|
19
23
|
}));
|
|
20
24
|
|
|
21
|
-
jest.mock('vue-router', () => ({ useRouter: () => ({ push: mockRouterPush }) }));
|
|
25
|
+
jest.mock('vue-router', () => ({ useRouter: () => ({ push: mockRouterPush, currentRoute: mockCurrentRoute }) }));
|
|
22
26
|
|
|
23
27
|
jest.mock('@shell/composables/useI18n', () => ({ useI18n: () => ({ t: (key: string, args?: Record<string, any>) => `%${ key }%${ args ? JSON.stringify(args) : '' }` }) }));
|
|
24
28
|
|
|
@@ -105,6 +109,7 @@ describe('composable: useWorkloadDashboard', () => {
|
|
|
105
109
|
beforeEach(() => {
|
|
106
110
|
jest.clearAllMocks();
|
|
107
111
|
setupGetters();
|
|
112
|
+
mockCurrentRoute.value.name = WORKLOAD_DASHBOARD_ROUTE;
|
|
108
113
|
});
|
|
109
114
|
|
|
110
115
|
describe('namespaceSubtitle', () => {
|
|
@@ -558,4 +563,134 @@ describe('composable: useWorkloadDashboard', () => {
|
|
|
558
563
|
wrapper.unmount();
|
|
559
564
|
});
|
|
560
565
|
});
|
|
566
|
+
|
|
567
|
+
describe('response validation', () => {
|
|
568
|
+
// The composable caches malformed clusters in a module-level singleton, so each
|
|
569
|
+
// invalid-data test uses a unique cluster id to stay independent of the others.
|
|
570
|
+
function deploymentRouteFor(cluster: string) {
|
|
571
|
+
return {
|
|
572
|
+
name: 'c-cluster-product-resource',
|
|
573
|
+
params: {
|
|
574
|
+
cluster,
|
|
575
|
+
product: 'explorer',
|
|
576
|
+
resource: WORKLOAD_TYPES.DEPLOYMENT,
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const malformedSummaryResponse = {
|
|
582
|
+
summary: [{
|
|
583
|
+
property: 'metadata.state.name',
|
|
584
|
+
counts: { running: { namespace: { default: 5 } } },
|
|
585
|
+
}],
|
|
586
|
+
data: [],
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
it('should redirect to the deployment page when a count entry is missing total', async() => {
|
|
590
|
+
const { wrapper } = mountComposable({ clusterId: 'c-missing-total' }, malformedSummaryResponse);
|
|
591
|
+
|
|
592
|
+
await flushPromises();
|
|
593
|
+
|
|
594
|
+
expect(mockRouterPush).toHaveBeenCalledWith(deploymentRouteFor('c-missing-total'));
|
|
595
|
+
wrapper.unmount();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should redirect when the summary is empty but data is present', async() => {
|
|
599
|
+
const dataWithoutSummaryResponse = { summary: [], data: [{ id: 'pod-1' }] };
|
|
600
|
+
|
|
601
|
+
const { wrapper } = mountComposable({ clusterId: 'c-empty-summary-with-data' }, dataWithoutSummaryResponse);
|
|
602
|
+
|
|
603
|
+
await flushPromises();
|
|
604
|
+
|
|
605
|
+
expect(mockRouterPush).toHaveBeenCalledWith(deploymentRouteFor('c-empty-summary-with-data'));
|
|
606
|
+
wrapper.unmount();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('should redirect when there is no summary but data is present', async() => {
|
|
610
|
+
const dataWithoutSummaryResponse = { data: [{ id: 'pod-1' }] };
|
|
611
|
+
|
|
612
|
+
const { wrapper } = mountComposable({ clusterId: 'c-no-summary-with-data' }, dataWithoutSummaryResponse);
|
|
613
|
+
|
|
614
|
+
await flushPromises();
|
|
615
|
+
|
|
616
|
+
expect(mockRouterPush).toHaveBeenCalledWith(deploymentRouteFor('c-no-summary-with-data'));
|
|
617
|
+
wrapper.unmount();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('should not redirect when every count entry has a total', async() => {
|
|
621
|
+
const { wrapper } = mountComposable();
|
|
622
|
+
|
|
623
|
+
await flushPromises();
|
|
624
|
+
|
|
625
|
+
expect(mockRouterPush).not.toHaveBeenCalled();
|
|
626
|
+
wrapper.unmount();
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('should not redirect when the summary is empty and there is no data', async() => {
|
|
630
|
+
const emptyResponse = { summary: [], data: [] };
|
|
631
|
+
|
|
632
|
+
const { wrapper } = mountComposable({}, emptyResponse);
|
|
633
|
+
|
|
634
|
+
await flushPromises();
|
|
635
|
+
|
|
636
|
+
expect(mockRouterPush).not.toHaveBeenCalled();
|
|
637
|
+
wrapper.unmount();
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it('should not redirect when there is no summary and no data', async() => {
|
|
641
|
+
const noSummaryResponse = { data: [] };
|
|
642
|
+
|
|
643
|
+
const { wrapper } = mountComposable({}, noSummaryResponse);
|
|
644
|
+
|
|
645
|
+
await flushPromises();
|
|
646
|
+
|
|
647
|
+
expect(mockRouterPush).not.toHaveBeenCalled();
|
|
648
|
+
wrapper.unmount();
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('should not redirect for entries returned with an error', async() => {
|
|
652
|
+
const errorResponse = { summary: null, error: 'No access' };
|
|
653
|
+
|
|
654
|
+
const { wrapper } = mountComposable({}, errorResponse);
|
|
655
|
+
|
|
656
|
+
await flushPromises();
|
|
657
|
+
|
|
658
|
+
expect(mockRouterPush).not.toHaveBeenCalled();
|
|
659
|
+
wrapper.unmount();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('should not redirect when the user has already navigated away from the dashboard', async() => {
|
|
663
|
+
// The fetch resolves after the user left the workload dashboard.
|
|
664
|
+
mockCurrentRoute.value.name = 'c-cluster-product-resource';
|
|
665
|
+
|
|
666
|
+
const { wrapper } = mountComposable({ clusterId: 'c-navigated-away' }, malformedSummaryResponse);
|
|
667
|
+
|
|
668
|
+
await flushPromises();
|
|
669
|
+
|
|
670
|
+
expect(mockRouterPush).not.toHaveBeenCalled();
|
|
671
|
+
wrapper.unmount();
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('should skip fetching and redirect immediately for a cluster already known to be invalid', async() => {
|
|
675
|
+
const cluster = 'c-cached-invalid';
|
|
676
|
+
|
|
677
|
+
// First mount detects the malformed response and caches the cluster.
|
|
678
|
+
const first = mountComposable({ clusterId: cluster }, malformedSummaryResponse);
|
|
679
|
+
|
|
680
|
+
await flushPromises();
|
|
681
|
+
first.wrapper.unmount();
|
|
682
|
+
|
|
683
|
+
jest.clearAllMocks();
|
|
684
|
+
|
|
685
|
+
// Second mount returns a valid response, but the cache should short-circuit the
|
|
686
|
+
// fetch entirely and redirect without ever requesting the summaries again.
|
|
687
|
+
const second = mountComposable({ clusterId: cluster }, summaryResponse);
|
|
688
|
+
|
|
689
|
+
await flushPromises();
|
|
690
|
+
|
|
691
|
+
expect(mockDispatch).not.toHaveBeenCalledWith('cluster/request', expect.anything());
|
|
692
|
+
expect(mockRouterPush).toHaveBeenCalledWith(deploymentRouteFor(cluster));
|
|
693
|
+
second.wrapper.unmount();
|
|
694
|
+
});
|
|
695
|
+
});
|
|
561
696
|
});
|