@rancher/shell 0.3.26 → 0.3.27

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.
Files changed (43) hide show
  1. package/.DS_Store +0 -0
  2. package/assets/translations/en-us.yaml +4 -3
  3. package/assets/translations/zh-hans.yaml +2 -3
  4. package/components/AlertTable.vue +8 -6
  5. package/components/EmberPage.vue +2 -2
  6. package/components/EtcdInfoBanner.vue +12 -2
  7. package/components/GlobalRoleBindings.vue +10 -0
  8. package/components/GrafanaDashboard.vue +8 -3
  9. package/components/Wizard.vue +17 -1
  10. package/components/auth/RoleDetailEdit.vue +17 -1
  11. package/components/form/ArrayList.vue +20 -11
  12. package/components/form/__tests__/ArrayList.test.ts +44 -0
  13. package/components/nav/Header.vue +5 -4
  14. package/components/nav/TopLevelMenu.vue +38 -15
  15. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -0
  16. package/components/nav/__tests__/Type.test.ts +139 -0
  17. package/config/private-label.js +1 -1
  18. package/config/settings.ts +0 -2
  19. package/core/types.ts +11 -4
  20. package/edit/provisioning.cattle.io.cluster/Basics.vue +13 -0
  21. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +1 -1
  22. package/edit/workload/mixins/workload.js +14 -4
  23. package/models/fleet.cattle.io.cluster.js +11 -1
  24. package/package.json +1 -1
  25. package/pages/c/_cluster/auth/roles/index.vue +11 -1
  26. package/pages/c/_cluster/explorer/index.vue +7 -2
  27. package/pages/c/_cluster/monitoring/index.vue +26 -39
  28. package/pages/support/index.vue +1 -8
  29. package/promptRemove/management.cattle.io.project.vue +6 -9
  30. package/rancher-components/components/Form/Radio/RadioGroup.test.ts +30 -0
  31. package/rancher-components/components/Form/Radio/RadioGroup.vue +4 -0
  32. package/store/features.js +1 -0
  33. package/types/shell/index.d.ts +4 -1
  34. package/utils/__tests__/object.test.ts +67 -1
  35. package/utils/__tests__/version.test.ts +13 -23
  36. package/utils/cluster.js +1 -1
  37. package/utils/grafana.js +1 -2
  38. package/utils/monitoring.js +25 -1
  39. package/utils/object.js +4 -3
  40. package/utils/sort.js +1 -1
  41. package/utils/validators/formRules/index.ts +1 -1
  42. package/utils/validators/role-template.js +1 -1
  43. package/utils/version.js +0 -13
@@ -0,0 +1,120 @@
1
+ import { mount, Wrapper } from '@vue/test-utils';
2
+ import TopLevelMenu from '@shell/components/nav/TopLevelMenu';
3
+
4
+ // DISCLAIMER: This should not be added here, although we have several store requests which are irrelevant
5
+ const defaultStore = {
6
+ 'management/byId': jest.fn(),
7
+ 'management/schemaFor': jest.fn(),
8
+ 'i18n/t': jest.fn(),
9
+ 'features/get': jest.fn(),
10
+ 'prefs/theme': jest.fn(),
11
+ defaultClusterId: jest.fn(),
12
+ clusterId: jest.fn(),
13
+ 'type-map/activeProducts': [],
14
+ };
15
+
16
+ describe('topLevelMenu', () => {
17
+ it('should display clusters', () => {
18
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
19
+ mocks: {
20
+ $store: {
21
+ getters: {
22
+ 'management/all': () => [{ name: 'whatever' }],
23
+ ...defaultStore
24
+ },
25
+ },
26
+ },
27
+ stubs: ['BrandImage', 'nuxt-link']
28
+ });
29
+
30
+ const cluster = wrapper.find('[data-testid="top-level-menu-cluster-0"]');
31
+
32
+ expect(cluster.exists()).toBe(true);
33
+ });
34
+
35
+ describe('searching a term', () => {
36
+ describe('should displays a no results message if have clusters but', () => {
37
+ it('given no matching clusters', () => {
38
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
39
+ data: () => ({ clusterFilter: 'whatever' }),
40
+ mocks: {
41
+ $store: {
42
+ getters: {
43
+ 'management/all': () => [{ nameDisplay: 'something else' }],
44
+ ...defaultStore
45
+ },
46
+ },
47
+ },
48
+ stubs: ['BrandImage', 'nuxt-link']
49
+ });
50
+
51
+ const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
52
+
53
+ expect(noResults.exists()).toBe(true);
54
+ });
55
+
56
+ it('given no matched pinned clusters', () => {
57
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
58
+ data: () => ({ clusterFilter: 'whatever' }),
59
+ mocks: {
60
+ $store: {
61
+ getters: {
62
+ 'management/all': () => [{ nameDisplay: 'something else', pinned: true }],
63
+ ...defaultStore
64
+ },
65
+ },
66
+ },
67
+ stubs: ['BrandImage', 'nuxt-link']
68
+ });
69
+
70
+ const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
71
+
72
+ expect(noResults.exists()).toBe(true);
73
+ });
74
+ });
75
+
76
+ describe('should not displays a no results message', () => {
77
+ it('given matching clusters', () => {
78
+ const search = 'you found me';
79
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
80
+ data: () => ({ clusterFilter: search }),
81
+ mocks: {
82
+ $store: {
83
+ getters: {
84
+ 'management/all': () => [{ nameDisplay: search }],
85
+ ...defaultStore
86
+ },
87
+ },
88
+ },
89
+ stubs: ['BrandImage', 'nuxt-link']
90
+ });
91
+
92
+ const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
93
+
94
+ expect(wrapper.vm.clustersFiltered).toHaveLength(1);
95
+ expect(noResults.exists()).toBe(false);
96
+ });
97
+
98
+ it('given clusters with status pinned', () => {
99
+ const search = 'you found me';
100
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
101
+ data: () => ({ clusterFilter: search }),
102
+ mocks: {
103
+ $store: {
104
+ getters: {
105
+ 'management/all': () => [{ nameDisplay: search, pinned: true }],
106
+ ...defaultStore
107
+ },
108
+ },
109
+ },
110
+ stubs: ['BrandImage', 'nuxt-link']
111
+ });
112
+
113
+ const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
114
+
115
+ expect(wrapper.vm.pinFiltered).toHaveLength(1);
116
+ expect(noResults.exists()).toBe(false);
117
+ });
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,139 @@
1
+ import { mount, RouterLinkStub } from '@vue/test-utils';
2
+ import Type from '@shell/components/nav/Type.vue';
3
+
4
+ // Mandatory to mock vue-router in this test
5
+ jest.mock('vue-router');
6
+
7
+ // Configuration text
8
+ const className = 'nuxt-link-active';
9
+
10
+ describe('component: Type', () => {
11
+ describe('should not use highlight class', () => {
12
+ it('given no hash', () => {
13
+ const wrapper = mount(Type, {
14
+ propsData: { type: { route: 'something else' } },
15
+ stubs: { nLink: RouterLinkStub },
16
+ mocks: {
17
+ $route: { path: 'whatever' },
18
+ $router: { resolve: () => ({ route: { path: 'whatever' } }) },
19
+ },
20
+ });
21
+
22
+ const highlight = wrapper.find(`.${ className }`);
23
+
24
+ expect(highlight.exists()).toBe(false);
25
+ });
26
+
27
+ it('given no path', () => {
28
+ const wrapper = mount(Type, {
29
+ propsData: { type: { route: 'something else' } },
30
+ stubs: { nLink: RouterLinkStub },
31
+ mocks: {
32
+ $route: { hash: 'whatever' },
33
+ $router: { resolve: () => ({ route: { path: 'whatever' } }) },
34
+ },
35
+ });
36
+
37
+ const highlight = wrapper.find(`.${ className }`);
38
+
39
+ expect(highlight.exists()).toBe(false);
40
+ });
41
+
42
+ it('given no matching values', () => {
43
+ const wrapper = mount(Type, {
44
+ propsData: { type: {} },
45
+ stubs: { nLink: RouterLinkStub },
46
+ mocks: {
47
+ $route: {
48
+ hash: 'hash',
49
+ path: 'path',
50
+ },
51
+ $router: { resolve: () => ({ route: { path: 'whatever' } }) },
52
+ },
53
+ });
54
+
55
+ const highlight = wrapper.find(`.${ className }`);
56
+
57
+ expect(highlight.exists()).toBe(false);
58
+ });
59
+
60
+ it('given navigation path is bigger than current page route path', () => {
61
+ const wrapper = mount(Type, {
62
+ propsData: { type: { route: 'not empty' } },
63
+ stubs: { nLink: RouterLinkStub },
64
+ mocks: {
65
+ $route: {
66
+ hash: 'not empty',
67
+ path: 'whatever',
68
+ },
69
+ $router: { resolve: () => ({ route: { path: 'many/parts' } }) },
70
+ },
71
+ });
72
+
73
+ const highlight = wrapper.find(`.${ className }`);
74
+
75
+ expect(highlight.exists()).toBe(false);
76
+ });
77
+
78
+ it.each([
79
+ // URL with fragments like anchors
80
+ [
81
+ '/c/c-m-hzqf4tqt/explorer/members#project-membership',
82
+ '/c/c-m-hzqf4tqt/explorer/members'
83
+ ],
84
+ // Similar paths
85
+ [
86
+ '/c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundlenamespacemapping',
87
+ '/c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundle'
88
+ ],
89
+ // paths with same parts, e.g. parents
90
+ [
91
+ '/c/c-m-hzqf4tqt/fleet',
92
+ '/c/c-m-hzqf4tqt/fleet/management.cattle.io.fleetworkspace'
93
+ ],
94
+ ])('given different current path %p and menu path %p', (currentPath, menuPath) => {
95
+ const wrapper = mount(Type, {
96
+ propsData: { type: { route: 'not empty' } },
97
+ stubs: { nLink: RouterLinkStub },
98
+ mocks: {
99
+ $route: {
100
+ hash: 'not empty',
101
+ path: currentPath,
102
+ },
103
+ $router: { resolve: () => ({ route: { path: menuPath } }) },
104
+ },
105
+ });
106
+
107
+ const highlight = wrapper.find(`.${ className }`);
108
+
109
+ expect(highlight.exists()).toBe(false);
110
+ });
111
+ });
112
+
113
+ describe('should use highlight class', () => {
114
+ it.each([
115
+ [
116
+ 'same',
117
+ 'same'
118
+ ],
119
+ ])('given same current path %p and menu path %p (on first load)', (currentPath, menuPath) => {
120
+ const wrapper = mount(Type, {
121
+ propsData: { type: { route: 'not empty' } },
122
+ stubs: { nLink: RouterLinkStub },
123
+ mocks: {
124
+ $route: {
125
+ hash: 'not empty',
126
+ path: currentPath,
127
+ },
128
+ $router: { resolve: () => ({ route: { path: menuPath } }) },
129
+ },
130
+ });
131
+
132
+ const highlight = wrapper.find(`.${ className }`);
133
+
134
+ expect(highlight.exists()).toBe(true);
135
+ });
136
+ });
137
+ });
138
+
139
+ jest.restoreAllMocks();
@@ -3,7 +3,7 @@ import { SETTING } from './settings';
3
3
  export const ANY = 0;
4
4
  export const STANDARD = 1;
5
5
  export const CUSTOM = 2;
6
- export const DOCS_BASE = 'https://rancher.com/docs/rancher/v2.7/en';
6
+ export const DOCS_BASE = 'https://ranchermanager.docs.rancher.com/v2.8';
7
7
 
8
8
  const STANDARD_VENDOR = 'Rancher';
9
9
  const STANDARD_PRODUCT = 'Explorer';
@@ -42,7 +42,6 @@ export const SETTING = {
42
42
  HIDE_LOCAL_CLUSTER: 'hide-local-cluster',
43
43
  AUTH_TOKEN_MAX_TTL_MINUTES: 'auth-token-max-ttl-minutes',
44
44
  KUBECONFIG_GENERATE_TOKEN: 'kubeconfig-generate-token',
45
- KUBECONFIG_TOKEN_TTL_MINUTES: 'kubeconfig-token-ttl-minutes',
46
45
  KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES: 'kubeconfig-default-token-ttl-minutes',
47
46
  ENGINE_URL: 'engine-install-url',
48
47
  ENGINE_ISO_URL: 'engine-iso-url',
@@ -127,7 +126,6 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
127
126
  [SETTING.AUTH_USER_SESSION_TTL_MINUTES]: {},
128
127
  [SETTING.AUTH_TOKEN_MAX_TTL_MINUTES]: {},
129
128
  [SETTING.KUBECONFIG_GENERATE_TOKEN]: { kind: 'boolean' },
130
- [SETTING.KUBECONFIG_TOKEN_TTL_MINUTES]: {},
131
129
  [SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { kind: 'integer' },
132
130
  [SETTING.AUTH_USER_INFO_RESYNC_CRON]: {},
133
131
  [SETTING.SERVER_URL]: { kind: 'url', canReset: true },
package/core/types.ts CHANGED
@@ -323,6 +323,11 @@ export interface ConfigureTypeOptions {
323
323
  */
324
324
  isRemovable?: boolean;
325
325
 
326
+ /**
327
+ * Resources of this type can be edited
328
+ */
329
+ isEditable?: boolean;
330
+
326
331
  /**
327
332
  * This type should be grouped by namespaces when displayed in a table
328
333
  */
@@ -343,16 +348,18 @@ export interface ConfigureTypeOptions {
343
348
  */
344
349
  showState?: boolean;
345
350
 
351
+ /**
352
+ * Define where this type/page should navigate to (menu entry routing)
353
+ */
354
+ customRoute?: Object;
355
+
346
356
  /**
347
357
  * Leaving these here for completeness but I don't think these should be advertised as useable to plugin creators.
348
358
  */
349
359
  // alias
350
- // customRoute
351
- // customRoute
352
360
  // depaginate
353
361
  // graphConfig
354
362
  // hasGraph
355
- // isEditable
356
363
  // limit
357
364
  // listGroups
358
365
  // localOnly
@@ -379,7 +386,7 @@ export interface ConfigureVirtualTypeOptions extends ConfigureTypeOptions {
379
386
  /**
380
387
  * The route that this type should correspond to {@link PluginRouteConfig} {@link RouteConfig}
381
388
  */
382
- route: PluginRouteConfig | RouteConfig;
389
+ route: PluginRouteConfig | RouteConfig | Object;
383
390
  }
384
391
 
385
392
  export interface DSLReturnType {
@@ -377,6 +377,13 @@ export default {
377
377
  const canNotEdit = this.clusterIsAlreadyCreated && !this.unsupportedCloudProvider;
378
378
 
379
379
  return canNotEdit;
380
+ },
381
+
382
+ /**
383
+ * Display warning about additional configuration needed for cloud provider Amazon if kube >= 1.27
384
+ */
385
+ showCloudProviderAmazonAdditionalConfigWarning() {
386
+ return !!semver.gte(this.value.spec.kubernetesVersion, 'v1.27.0') && this.agentConfig['cloud-provider-name'] === 'aws';
380
387
  }
381
388
  },
382
389
 
@@ -413,6 +420,12 @@ export default {
413
420
  v-clean-html="t('cluster.harvester.warning.cloudProvider.incompatible', null, true)"
414
421
  />
415
422
  </Banner>
423
+ <Banner
424
+ v-if="showCloudProviderAmazonAdditionalConfigWarning"
425
+ color="warning"
426
+ >
427
+ <span v-clean-html="t('cluster.banner.cloudProviderAddConfig', {}, true)" />
428
+ </Banner>
416
429
  <div class="row mb-10">
417
430
  <div class="col span-6">
418
431
  <LabeledSelect
@@ -55,7 +55,7 @@ export default {
55
55
  this.controlPlane && out.push('--controlplane');
56
56
  this.worker && out.push('--worker');
57
57
  this.address && out.push(`--address ${ sanitizeIP(this.address) }`);
58
- this.internalAddress && out.push(`--internal-address ${ sanitizeValue(this.internalAddress) }`);
58
+ this.internalAddress && out.push(`--internal-address ${ sanitizeIP(this.internalAddress) }`);
59
59
  this.nodeName && out.push(`--node-name ${ sanitizeValue(this.nodeName) }`);
60
60
 
61
61
  for ( const key in this.labels ) {
@@ -146,10 +146,20 @@ export default {
146
146
 
147
147
  async fetch() {
148
148
  // TODO Should remove these lines
149
- await allHash({
150
- rancherClusters: this.$store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER }),
151
- harvesterConfigs: this.$store.dispatch('management/findAll', { type: HCI.HARVESTER_CONFIG }),
152
- });
149
+ // ? The results aren't stored, so don't know why we fetch?
150
+
151
+ // User might not have access to these resources - so check before trying to fetch
152
+ const fetches = {};
153
+
154
+ if (this.$store.getters[`management/canList`](CAPI.RANCHER_CLUSTER)) {
155
+ fetches.rancherClusters = this.$store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER });
156
+ }
157
+
158
+ if (this.$store.getters[`management/canList`](HCI.HARVESTER_CONFIG)) {
159
+ fetches.harvesterConfigs = this.$store.dispatch('management/findAll', { type: HCI.HARVESTER_CONFIG });
160
+ }
161
+
162
+ await allHash(fetches);
153
163
 
154
164
  // don't block UI for these resources
155
165
  this.resourceManagerFetchSecondaryResources(this.secondaryResourceData);
@@ -5,6 +5,7 @@ import SteveModel from '@shell/plugins/steve/steve-class';
5
5
  import { escapeHtml } from '@shell/utils/string';
6
6
  import { insertAt } from '@shell/utils/array';
7
7
  import jsyaml from 'js-yaml';
8
+ import { FLEET_WORKSPACE_BACK } from '@shell/store/features';
8
9
 
9
10
  export default class FleetCluster extends SteveModel {
10
11
  get _availableActions() {
@@ -80,7 +81,16 @@ export default class FleetCluster extends SteveModel {
80
81
  }
81
82
 
82
83
  get canChangeWorkspace() {
83
- return !this.isRke2 && !this.isLocal;
84
+ // https://github.com/rancher/dashboard/issues/7745
85
+ if (this.isLocal) {
86
+ return false;
87
+ }
88
+ // https://github.com/rancher/dashboard/issues/9730
89
+ if (this.isRke2) {
90
+ return this.$rootGetters['features/get'](FLEET_WORKSPACE_BACK);
91
+ }
92
+
93
+ return true;
84
94
  }
85
95
 
86
96
  get isLocal() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.26",
3
+ "version": "0.3.27",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -1,4 +1,5 @@
1
1
  <script>
2
+ import { mapGetters } from 'vuex';
2
3
  import Tab from '@shell/components/Tabbed/Tab';
3
4
  import Tabbed from '@shell/components/Tabbed';
4
5
  import { MANAGEMENT } from '@shell/config/types';
@@ -7,6 +8,7 @@ import Loading from '@shell/components/Loading';
7
8
  import { SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/management.cattle.io.roletemplate';
8
9
  import { NAME } from '@shell/config/product/auth';
9
10
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
11
+ import { Banner } from '@components/Banner';
10
12
 
11
13
  const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
12
14
  const CLUSTER = SUBTYPE_MAPPING.CLUSTER.key;
@@ -31,7 +33,7 @@ export default {
31
33
  name: 'Roles',
32
34
 
33
35
  components: {
34
- Tab, Tabbed, ResourceTable, Loading
36
+ Tab, Tabbed, ResourceTable, Loading, Banner
35
37
  },
36
38
 
37
39
  async asyncData({ store }) {
@@ -100,6 +102,8 @@ export default {
100
102
  },
101
103
 
102
104
  computed: {
105
+ ...mapGetters(['releaseNotesUrl']),
106
+
103
107
  globalResources() {
104
108
  return this.globalRoles;
105
109
  },
@@ -181,6 +185,12 @@ export default {
181
185
  :weight="tabs[GLOBAL].weight"
182
186
  :label-key="tabs[GLOBAL].labelKey"
183
187
  >
188
+ <Banner
189
+ color="warning"
190
+ class="mb-20"
191
+ >
192
+ <span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
193
+ </Banner>
184
194
  <ResourceTable
185
195
  :schema="tabs[GLOBAL].schema"
186
196
  :rows="globalResources"
@@ -28,7 +28,7 @@ import {
28
28
  } from '@shell/config/table-headers';
29
29
 
30
30
  import { mapPref, PSP_DEPRECATION_BANNER } from '@shell/store/prefs';
31
- import { haveV1Monitoring, monitoringStatus } from '@shell/utils/monitoring';
31
+ import { haveV1Monitoring, monitoringStatus, canViewGrafanaLink } from '@shell/utils/monitoring';
32
32
  import Tabbed from '@shell/components/Tabbed';
33
33
  import Tab from '@shell/components/Tabbed/Tab';
34
34
  import { allDashboardsExist } from '@shell/utils/grafana';
@@ -103,6 +103,10 @@ export default {
103
103
  `Determine etcd metrics`
104
104
  );
105
105
 
106
+ // It's not enough to check that the grafana links are working for the current user; embedded cluster-level dashboards should only be shown if the user can view the grafana endpoint
107
+ // https://github.com/rancher/dashboard/issues/9792
108
+ setPromiseResult(canViewGrafanaLink(this.$store), this, 'canViewMetrics', 'Determine Grafana Permission');
109
+
106
110
  if (this.currentCluster.isLocal && this.$store.getters['management/schemaFor'](MANAGEMENT.NODE)) {
107
111
  this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE });
108
112
  }
@@ -125,6 +129,7 @@ export default {
125
129
  showClusterMetrics: false,
126
130
  showK8sMetrics: false,
127
131
  showEtcdMetrics: false,
132
+ canViewMetrics: false,
128
133
  CLUSTER_METRICS_DETAIL_URL,
129
134
  CLUSTER_METRICS_SUMMARY_URL,
130
135
  K8S_METRICS_DETAIL_URL,
@@ -346,7 +351,7 @@ export default {
346
351
  },
347
352
 
348
353
  hasMetricsTabs() {
349
- return this.showClusterMetrics || this.showK8sMetrics || this.showEtcdMetrics;
354
+ return this.canViewMetrics && ( this.showClusterMetrics || this.showK8sMetrics || this.showEtcdMetrics);
350
355
  },
351
356
 
352
357
  hasBadge() {
@@ -4,16 +4,14 @@ import isEmpty from 'lodash/isEmpty';
4
4
  import InstallRedirect from '@shell/utils/install-redirect';
5
5
  import AlertTable from '@shell/components/AlertTable';
6
6
  import { NAME, CHART_NAME } from '@shell/config/product/monitoring';
7
- import { CATALOG, ENDPOINTS, MONITORING } from '@shell/config/types';
7
+ import { CATALOG, MONITORING } from '@shell/config/types';
8
8
  import { allHash } from '@shell/utils/promise';
9
9
  import { findBy } from '@shell/utils/array';
10
10
  import { getClusterPrefix } from '@shell/utils/grafana';
11
11
  import { Banner } from '@components/Banner';
12
12
  import LazyImage from '@shell/components/LazyImage';
13
13
  import SimpleBox from '@shell/components/SimpleBox';
14
- import { haveV1MonitoringWorkloads } from '@shell/utils/monitoring';
15
-
16
- const CATTLE_MONITORING_NAMESPACE = 'cattle-monitoring-system';
14
+ import { haveV1MonitoringWorkloads, canViewAlertManagerLink, canViewGrafanaLink, canViewPrometheusLink } from '@shell/utils/monitoring';
17
15
 
18
16
  export default {
19
17
  components: {
@@ -96,52 +94,41 @@ export default {
96
94
  const { $store, externalLinks } = this;
97
95
 
98
96
  this.v1Installed = await haveV1MonitoringWorkloads($store);
99
- const hash = await allHash({
100
- apps: $store.dispatch('cluster/findAll', { type: CATALOG.APP }),
101
- endpoints: $store.dispatch('cluster/findAll', { type: ENDPOINTS }),
102
- });
97
+ const hash = {};
98
+
99
+ if ($store.getters['cluster/canList'](CATALOG.APP)) {
100
+ hash.apps = $store.dispatch('cluster/findAll', { type: CATALOG.APP });
101
+ }
102
+ const res = await allHash(hash);
103
+
104
+ const canViewAlertManager = await canViewAlertManagerLink(this.$store);
105
+ const canViewGrafana = await canViewGrafanaLink(this.$store);
106
+ const canViewPrometheus = await canViewPrometheusLink(this.$store);
103
107
 
104
- if (!isEmpty(hash.endpoints)) {
108
+ if (canViewAlertManager) {
105
109
  const amMatch = findBy(externalLinks, 'group', 'alertmanager');
106
- const grafanaMatch = findBy(externalLinks, 'group', 'grafana');
107
- const promeMatch = externalLinks.filter(
108
- (el) => el.group === 'prometheus'
109
- );
110
110
 
111
+ amMatch.enabled = true;
112
+ }
113
+ if (canViewGrafana) {
114
+ const grafanaMatch = findBy(externalLinks, 'group', 'grafana');
111
115
  // Generate Grafana link
112
116
  const currentCluster = this.$store.getters['currentCluster'];
113
- const rancherMonitoring = !isEmpty(hash.apps) ? findBy(hash.apps, 'id', 'cattle-monitoring-system/rancher-monitoring') : '';
117
+ const rancherMonitoring = !isEmpty(res.apps) ? findBy(res.apps, 'id', 'cattle-monitoring-system/rancher-monitoring') : '';
114
118
  const clusterPrefix = getClusterPrefix(rancherMonitoring?.currentVersion || '', currentCluster.id);
115
119
 
116
120
  grafanaMatch.link = `${ clusterPrefix }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/`;
121
+ grafanaMatch.enabled = true;
122
+ }
117
123
 
118
- const alertmanager = findBy(
119
- hash.endpoints,
120
- 'id',
121
- `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-alertmanager`
122
- );
123
- const grafana = findBy(
124
- hash.endpoints,
125
- 'id',
126
- `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-grafana`
127
- );
128
- const prometheus = findBy(
129
- hash.endpoints,
130
- 'id',
131
- `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-prometheus`
124
+ if (canViewPrometheus) {
125
+ const promeMatch = externalLinks.filter(
126
+ (el) => el.group === 'prometheus'
132
127
  );
133
128
 
134
- if (!isEmpty(alertmanager) && !isEmpty(alertmanager.subsets)) {
135
- amMatch.enabled = true;
136
- }
137
- if (!isEmpty(grafana) && !isEmpty(grafana.subsets)) {
138
- grafanaMatch.enabled = true;
139
- }
140
- if (!isEmpty(prometheus) && !isEmpty(prometheus.subsets)) {
141
- promeMatch.forEach((match) => {
142
- match.enabled = true;
143
- });
144
- }
129
+ promeMatch.forEach((match) => {
130
+ match.enabled = true;
131
+ });
145
132
  }
146
133
  },
147
134
  },
@@ -8,7 +8,6 @@ import { SETTING } from '@shell/config/settings';
8
8
  import { addParam } from '@shell/utils/url';
9
9
  import { isRancherPrime } from '@shell/config/version';
10
10
  import { hasCspAdapter } from 'mixins/brand';
11
- import { generateSupportLink } from '@shell/utils/version';
12
11
 
13
12
  export default {
14
13
  layout: 'home',
@@ -112,12 +111,6 @@ export default {
112
111
 
113
112
  sccLink() {
114
113
  return this.hasAWSSupport ? addParam('https://scc.suse.com', 'from_marketplace', '1') : 'https://scc.suse.com';
115
- },
116
-
117
- supportLink() {
118
- const version = this.settings?.find((s) => s.id === SETTING.VERSION_RANCHER)?.value;
119
-
120
- return generateSupportLink(version);
121
114
  }
122
115
  },
123
116
 
@@ -139,7 +132,7 @@ export default {
139
132
  <div class="support-link">
140
133
  <a
141
134
  class="support-link"
142
- :href="supportLink"
135
+ href="https://www.rancher.com/support"
143
136
  target="_blank"
144
137
  rel="noopener noreferrer nofollow"
145
138
  >{{ t('support.community.learnMore') }}</a>