@rancher/shell 0.5.1 → 0.5.3

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 (70) hide show
  1. package/assets/translations/en-us.yaml +8 -4
  2. package/components/ClusterIconMenu.vue +24 -9
  3. package/components/CodeMirror.vue +79 -18
  4. package/components/FixedBanner.vue +1 -0
  5. package/components/ResourceDetail/index.vue +1 -4
  6. package/components/ResourceYaml.vue +29 -5
  7. package/components/SideNav.vue +42 -64
  8. package/components/SortableTable/index.vue +1 -1
  9. package/components/YamlEditor.vue +1 -0
  10. package/components/__tests__/CodeMirror.spec.ts +99 -0
  11. package/components/form/BannerSettings.vue +3 -0
  12. package/components/form/FileSelector.vue +1 -0
  13. package/components/form/KeyValue.vue +1 -0
  14. package/components/formatter/WorkloadDetailEndpoints.vue +12 -22
  15. package/components/formatter/__tests__/WorkloadDetailEndpoints.test.ts +81 -0
  16. package/components/nav/Header.vue +1 -0
  17. package/components/nav/Jump.vue +19 -9
  18. package/components/nav/TopLevelMenu.vue +37 -15
  19. package/components/nav/Type.vue +15 -4
  20. package/components/nav/__tests__/TopLevelMenu.test.ts +1 -1
  21. package/components/nav/__tests__/Type.test.ts +30 -0
  22. package/core/types-provisioning.ts +7 -0
  23. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +77 -0
  24. package/detail/fleet.cattle.io.bundle.vue +1 -1
  25. package/detail/provisioning.cattle.io.cluster.vue +19 -4
  26. package/edit/management.cattle.io.setting.vue +1 -0
  27. package/edit/monitoring.coreos.com.alertmanagerconfig/types/opsgenie.vue +1 -1
  28. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +1 -2
  29. package/edit/monitoring.coreos.com.alertmanagerconfig/types/slack.vue +1 -1
  30. package/edit/provisioning.cattle.io.cluster/index.vue +23 -10
  31. package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -50
  32. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +9 -11
  33. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +3 -1
  34. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +3 -0
  35. package/edit/token.vue +1 -0
  36. package/list/catalog.cattle.io.app.vue +1 -0
  37. package/list/management.cattle.io.setting.vue +1 -0
  38. package/machine-config/amazonec2.vue +1 -0
  39. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +151 -0
  40. package/models/__tests__/secret.test.ts +37 -0
  41. package/models/__tests__/storage.k8s.io.storageclass.test.ts +22 -0
  42. package/models/management.cattle.io.kontainerdriver.js +2 -1
  43. package/models/provisioning.cattle.io.cluster.js +36 -1
  44. package/models/secret.js +9 -0
  45. package/models/storage.k8s.io.storageclass.js +1 -1
  46. package/package.json +1 -1
  47. package/pages/c/_cluster/settings/DefaultLinksEditor.vue +1 -0
  48. package/pages/c/_cluster/settings/brand.vue +3 -0
  49. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +4 -4
  50. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +5 -2
  51. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +96 -0
  52. package/pages/c/_cluster/uiplugins/__tests__/SetupUIPlugins.test.ts +128 -0
  53. package/plugins/dashboard-store/__tests__/actions.test.ts +196 -111
  54. package/plugins/dashboard-store/actions.js +4 -6
  55. package/plugins/dashboard-store/getters.js +60 -2
  56. package/plugins/dashboard-store/resource-class.js +6 -2
  57. package/plugins/steve/__tests__/getters.spec.ts +10 -0
  58. package/plugins/steve/__tests__/resource-utils.test.ts +159 -0
  59. package/plugins/steve/actions.js +3 -37
  60. package/plugins/steve/getters.js +6 -0
  61. package/plugins/steve/resource-utils.ts +38 -0
  62. package/scripts/extension/parse-tag-name +3 -3
  63. package/store/__tests__/type-map.test.ts +1122 -0
  64. package/store/index.js +3 -2
  65. package/store/plugins.js +7 -6
  66. package/store/type-map.js +145 -75
  67. package/types/shell/index.d.ts +2 -0
  68. package/utils/__tests__/create-yaml.test.ts +10 -0
  69. package/utils/create-yaml.js +5 -1
  70. package/utils/object.js +10 -0
@@ -143,6 +143,7 @@ export default ({
143
143
  <RadioGroup
144
144
  v-model="value[bannerType].textAlignment"
145
145
  name="bannerAlignment"
146
+ :data-testid="`banner_alignment_radio_options${bannerType}`"
146
147
  :label="t('banner.bannerAlignment.label')"
147
148
  :options="radioOptions.options"
148
149
  :labels="radioOptions.labels"
@@ -160,6 +161,7 @@ export default ({
160
161
  <Checkbox
161
162
  v-model="value[bannerType][o.style]"
162
163
  name="bannerDecoration"
164
+ :data-testid="`banner_decoration_checkbox${bannerType}${o.label}`"
163
165
  class="banner-decoration-checkbox"
164
166
  :mode="mode"
165
167
  :label="o.label"
@@ -169,6 +171,7 @@ export default ({
169
171
  <div class="col span-2">
170
172
  <LabeledSelect
171
173
  v-model="value[bannerType].fontSize"
174
+ :data-testid="`banner_font_size_options${bannerType}`"
172
175
  :disabled="isUiDisabled"
173
176
  :label="t('banner.bannerFontSize.label')"
174
177
  :options="uiBannerFontSizeOptions"
@@ -148,6 +148,7 @@ export default {
148
148
  :disabled="disabled"
149
149
  type="button"
150
150
  class="file-selector btn"
151
+ data-testid="file-selector__uploader-button"
151
152
  @click="selectFile"
152
153
  >
153
154
  <span>{{ label }}</span>
@@ -777,6 +777,7 @@ export default {
777
777
  v-if="addAllowed"
778
778
  type="button"
779
779
  class="btn role-tertiary add"
780
+ data-testid="add_link_button"
780
781
  :disabled="loading || disabled || (keyOptions && filteredKeyOptions.length === 0)"
781
782
  @click="add()"
782
783
  >
@@ -27,6 +27,7 @@ export default {
27
27
 
28
28
  try {
29
29
  out = JSON.parse(this.value);
30
+
30
31
  out.forEach((endpoint) => {
31
32
  let protocol = 'http';
32
33
 
@@ -34,10 +35,19 @@ export default {
34
35
  protocol = 'https';
35
36
  }
36
37
 
37
- if (endpoint.addresses) {
38
+ const linkDefaultDisplay = endpoint.port ? `${ endpoint.port }/${ endpoint.protocol }` : endpoint.protocol;
39
+
40
+ // If there's an ingress and it has a hostname, we use the hostname address instead
41
+ // https://github.com/rancher/dashboard/issues/8087
42
+ if (endpoint.ingressName && endpoint.hostname) {
43
+ endpoint.link = `${ protocol }://${ endpoint.hostname }${ endpoint.path }`;
44
+ endpoint.linkDisplay = endpoint.link;
45
+ } else if (endpoint.addresses && endpoint.addresses.length) {
38
46
  endpoint.link = `${ protocol }://${ endpoint.addresses[0] }:${ endpoint.port }`;
47
+ endpoint.linkDisplay = linkDefaultDisplay;
39
48
  } else if (externalIp) {
40
49
  endpoint.link = `${ protocol }://${ externalIp }:${ endpoint.port }`;
50
+ endpoint.linkDisplay = linkDefaultDisplay;
41
51
  } else {
42
52
  endpoint.display = `[${ this.t('servicesPage.anyNode') }]:${ endpoint.port }`;
43
53
  }
@@ -49,26 +59,6 @@ export default {
49
59
  }
50
60
  }
51
61
 
52
- return null;
53
- },
54
-
55
- protocol() {
56
- const { parsed } = this;
57
-
58
- if ( parsed) {
59
- if (this.parsed[0].protocol) {
60
- return this.parsed[0].protocol;
61
- }
62
-
63
- const match = parsed.match(/^([^:]+):\/\//);
64
-
65
- if ( match ) {
66
- return match[1];
67
- } else {
68
- return 'link';
69
- }
70
- }
71
-
72
62
  return null;
73
63
  }
74
64
  },
@@ -90,7 +80,7 @@ export default {
90
80
  :href="endpoint.link"
91
81
  target="_blank"
92
82
  rel="nofollow noopener noreferrer"
93
- ><span v-if="endpoint.port">{{ endpoint.port }}/</span>{{ endpoint.protocol }}</a>
83
+ >{{ endpoint.linkDisplay }}</a>
94
84
  </template>
95
85
  </span>
96
86
  </template>
@@ -0,0 +1,81 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import WorkloadDetailEndpoints from '@shell/components/formatter/WorkloadDetailEndpoints.vue';
3
+ import Tag from '@shell/components/Tag';
4
+
5
+ describe('component: WorkloadDetailEndpoints', () => {
6
+ const withIngressAndHostname = [{
7
+ addresses: [
8
+ '172.18.0.3'
9
+ ],
10
+ port: 80,
11
+ protocol: 'HTTP',
12
+ serviceName: 'kube-public:tetris',
13
+ ingressName: 'kube-public:tetris',
14
+ hostname: 'tetris.kube-public.172.18.0.3.sslip.io',
15
+ path: '/',
16
+ allNodes: false,
17
+ }];
18
+
19
+ const withoutIngress = [
20
+ {
21
+ addresses: [
22
+ '172.18.0.3'
23
+ ],
24
+ port: 80,
25
+ protocol: 'TCP',
26
+ serviceName: 'kube-system:traefik',
27
+ allNodes: false
28
+ },
29
+ {
30
+ addresses: [
31
+ '172.18.0.3'
32
+ ],
33
+ port: 443,
34
+ protocol: 'TCP',
35
+ serviceName: 'kube-system:traefik',
36
+ allNodes: false
37
+ }
38
+ ];
39
+
40
+ const withoutAddresses = [
41
+ {
42
+ port: 443,
43
+ protocol: 'TCP',
44
+ serviceName: 'kube-system:traefik',
45
+ allNodes: false
46
+ }
47
+ ];
48
+
49
+ const basicNodesOutput = [
50
+ { externalIp: 'some-external-ip' }
51
+ ];
52
+
53
+ it.each([
54
+ [withIngressAndHostname, [], ['http://tetris.kube-public.172.18.0.3.sslip.io/']],
55
+ [withoutIngress, [], ['http://172.18.0.3:80', 'https://172.18.0.3:443']],
56
+ [withoutAddresses, basicNodesOutput, ['https://some-external-ip:443']],
57
+ ])('should display a link given the appropriate conditions', (value:any[], nodesOutput:any[], expectationArr:any[]) => {
58
+ const wrapper = mount(WorkloadDetailEndpoints, {
59
+ propsData: { value: JSON.stringify(value) },
60
+ mocks: { $store: { getters: { 'cluster/all': () => nodesOutput } } }
61
+ });
62
+
63
+ wrapper.vm.parsed.forEach((endpoint:{[key: string]: string}, i:number) => {
64
+ expect(endpoint.link).toBe(expectationArr[i]);
65
+ });
66
+ });
67
+
68
+ it.each([
69
+ [withoutAddresses, [], ['[some-translation]:443']],
70
+ ])('should render a Tag component with the appropriate content', (value:any[], nodesOutput:any[], expectationArr:any[]) => {
71
+ const wrapper = mount(WorkloadDetailEndpoints, {
72
+ propsData: { value: JSON.stringify(value) },
73
+ mocks: { $store: { getters: { 'cluster/all': () => nodesOutput, 'i18n/t': () => 'some-translation' } } }
74
+ });
75
+
76
+ wrapper.vm.parsed.forEach((endpoint:{[key: string]: string}, i:number) => {
77
+ expect(endpoint.display).toBe(expectationArr[i]);
78
+ expect(wrapper.findComponent(Tag).exists()).toBe(true);
79
+ });
80
+ });
81
+ });
@@ -432,6 +432,7 @@ export default {
432
432
  >
433
433
  <BrandImage
434
434
  class="side-menu-logo-img"
435
+ data-testid="header-side-menu__brand-img"
435
436
  file-name="rancher-logo.svg"
436
437
  />
437
438
  </div>
@@ -2,7 +2,8 @@
2
2
  import debounce from 'lodash/debounce';
3
3
  import Group from '@shell/components/nav/Group';
4
4
  import { isMac } from '@shell/utils/platform';
5
- import { BOTH, ALL } from '@shell/store/type-map';
5
+ import { BOTH, TYPE_MODES } from '@shell/store/type-map';
6
+ import { COUNT } from '@shell/config/types';
6
7
 
7
8
  export default {
8
9
  components: { Group },
@@ -31,17 +32,26 @@ export default {
31
32
  methods: {
32
33
  updateMatches() {
33
34
  const clusterId = this.$store.getters['clusterId'];
34
- const isAllNamespaces = this.$store.getters['isAllNamespaces'];
35
- const product = this.$store.getters['productId'];
35
+ const productId = this.$store.getters['productId'];
36
+ const product = this.$store.getters['currentProduct'];
36
37
 
37
- let namespaces = null;
38
+ const allTypesByMode = this.$store.getters['type-map/allTypes'](productId, [TYPE_MODES.ALL]) || {};
39
+ const allTypes = allTypesByMode[TYPE_MODES.ALL];
40
+ const out = this.$store.getters['type-map/getTree'](productId, TYPE_MODES.ALL, allTypes, clusterId, BOTH, null, this.value);
38
41
 
39
- if ( !isAllNamespaces ) {
40
- namespaces = Object.keys(this.$store.getters['activeNamespaceCache']);
41
- }
42
+ // Suplement the output with count info. Usualy the `Type` component would handle this individualy... but scales real bad so give it
43
+ // some help
44
+ const counts = this.$store.getters[`${ product.inStore }/all`](COUNT)?.[0]?.counts || {};
42
45
 
43
- const allTypes = this.$store.getters['type-map/allTypes'](product) || {};
44
- const out = this.$store.getters['type-map/getTree'](product, ALL, allTypes, clusterId, BOTH, namespaces, null, this.value);
46
+ out.forEach((o) => {
47
+ o.children?.forEach((t) => {
48
+ const count = counts[t.name];
49
+
50
+ t.count = count ? count.summary.count || 0 : null;
51
+ t.byNamespace = count ? count.namespaces : {};
52
+ t.revision = count ? count.revision : null;
53
+ });
54
+ });
45
55
 
46
56
  this.groups = out;
47
57
 
@@ -52,13 +52,11 @@ export default {
52
52
  ...mapGetters(['clusterId']),
53
53
  ...mapGetters(['clusterReady', 'isRancher', 'currentCluster', 'currentProduct', 'isRancherInHarvester']),
54
54
  ...mapGetters({ features: 'features/get' }),
55
-
56
55
  value: {
57
56
  get() {
58
57
  return this.$store.getters['productId'];
59
58
  },
60
59
  },
61
-
62
60
  sideMenuStyle() {
63
61
  return {
64
62
  marginBottom: this.globalBannerSettings?.footerFont,
@@ -171,7 +169,6 @@ export default {
171
169
 
172
170
  return `min-height: ${ height }px`;
173
171
  },
174
-
175
172
  clusterFilterCount() {
176
173
  return this.clusterFilter ? this.clustersFiltered.length : this.clusters.length;
177
174
  },
@@ -376,7 +373,10 @@ export default {
376
373
  /><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" /></svg>
377
374
  </div>
378
375
  <div class="side-menu-logo">
379
- <BrandImage file-name="rancher-logo.svg" />
376
+ <BrandImage
377
+ data-testid="side-menu__brand-img"
378
+ file-name="rancher-logo.svg"
379
+ />
380
380
  </div>
381
381
  </div>
382
382
 
@@ -425,6 +425,10 @@ export default {
425
425
  v-model="clusterFilter"
426
426
  :placeholder="t('nav.search.placeholder')"
427
427
  >
428
+ <i
429
+ class="magnifier icon icon-search"
430
+ :class="{ active: clusterFilter }"
431
+ />
428
432
  <i
429
433
  v-if="clusterFilter"
430
434
  class="icon icon-close"
@@ -789,6 +793,7 @@ export default {
789
793
 
790
794
  &.menu-open {
791
795
  width: 300px;
796
+ box-shadow: 3px 1px 3px var(--shadow);
792
797
  }
793
798
 
794
799
  .title {
@@ -796,7 +801,6 @@ export default {
796
801
  height: 55px;
797
802
  flex: 0 0 55px;
798
803
  width: 100%;
799
- border-bottom: 1px solid var(--nav-border);
800
804
  justify-content: flex-start;
801
805
  align-items: center;
802
806
 
@@ -949,13 +953,31 @@ export default {
949
953
  position: relative;
950
954
  > input {
951
955
  background-color: transparent;
952
- margin-bottom: 8px;
953
956
  padding-right: 35px;
957
+ padding-left: 25px;
958
+ height: 32px;
959
+ }
960
+ > .magnifier {
961
+ position: absolute;
962
+ top: 12px;
963
+ left: 8px;
964
+ width: 12px;
965
+ height: 12px;
966
+ font-size: 12px;
967
+ opacity: 0.4;
968
+
969
+ &.active {
970
+ opacity: 1;
971
+
972
+ &:hover {
973
+ color: var(--body-text);
974
+ }
975
+ }
954
976
  }
955
977
  > i {
956
978
  position: absolute;
957
- font-size: $clear-search-size;
958
- top: 11px;
979
+ font-size: 12px;
980
+ top: 12px;
959
981
  right: 8px;
960
982
  opacity: 0.7;
961
983
  cursor: pointer;
@@ -992,10 +1014,10 @@ export default {
992
1014
  height: 42px;
993
1015
 
994
1016
  .search {
995
- transition: all 0.5s ease-in-out;
1017
+ transition: all 0.25s ease-in-out;
996
1018
  transition-delay: 2s;
997
1019
  width: 72%;
998
- height: 42px;
1020
+ height: 36px;
999
1021
 
1000
1022
  input {
1001
1023
  height: 100%;
@@ -1051,7 +1073,7 @@ export default {
1051
1073
  hr {
1052
1074
  margin: 0;
1053
1075
  width: 94%;
1054
- transition: all 0.5s ease-in-out;
1076
+ transition: all 0.25s ease-in-out;
1055
1077
  max-width: 100%;
1056
1078
  }
1057
1079
  }
@@ -1078,7 +1100,7 @@ export default {
1078
1100
  text-transform: uppercase;
1079
1101
 
1080
1102
  span {
1081
- transition: all 0.5s ease-in-out;
1103
+ transition: all 0.25s ease-in-out;
1082
1104
  display: flex;
1083
1105
  max-height: 16px;
1084
1106
  }
@@ -1087,7 +1109,7 @@ export default {
1087
1109
  margin: 0;
1088
1110
  max-width: 50px;
1089
1111
  width: 0;
1090
- transition: all 0.5s ease-in-out;
1112
+ transition: all 0.25s ease-in-out;
1091
1113
  }
1092
1114
  }
1093
1115
 
@@ -1199,12 +1221,12 @@ export default {
1199
1221
  }
1200
1222
 
1201
1223
  .fade-enter-active, .fade-leave-active {
1202
- transition: all 0.2s;
1224
+ transition: all 0.25s;
1203
1225
  transition-timing-function: ease;
1204
1226
  }
1205
1227
 
1206
1228
  .fade-leave-active {
1207
- transition: all 0.4s;
1229
+ transition: all 0.25s;
1208
1230
  }
1209
1231
 
1210
1232
  .fade-leave-to {
@@ -1,8 +1,8 @@
1
1
  <script>
2
2
  import Favorite from '@shell/components/nav/Favorite';
3
- import { FAVORITE, USED } from '@shell/store/type-map';
3
+ import { TYPE_MODES } from '@shell/store/type-map';
4
4
 
5
- const showFavoritesFor = [FAVORITE, USED];
5
+ const showFavoritesFor = [TYPE_MODES.FAVORITE, TYPE_MODES.USED];
6
6
 
7
7
  export default {
8
8
 
@@ -90,12 +90,23 @@ export default {
90
90
  },
91
91
 
92
92
  showCount() {
93
- return typeof this.type.count !== 'undefined';
93
+ return this.count !== undefined;
94
94
  },
95
95
 
96
96
  namespaceIcon() {
97
97
  return this.type.namespaced;
98
98
  },
99
+
100
+ count() {
101
+ if (this.type.count !== undefined) {
102
+ return this.type.count;
103
+ }
104
+
105
+ const inStore = this.$store.getters['currentStore'](this.type.name);
106
+
107
+ return this.$store.getters[`${ inStore }/count`]({ name: this.type.name });
108
+ }
109
+
99
110
  },
100
111
 
101
112
  methods: {
@@ -162,7 +173,7 @@ export default {
162
173
  v-if="namespaceIcon"
163
174
  class="icon icon-namespace namespaced"
164
175
  />
165
- {{ type.count }}
176
+ {{ count }}
166
177
  </span>
167
178
  </a>
168
179
  </n-link>
@@ -1,6 +1,6 @@
1
- import { mount, Wrapper } from '@vue/test-utils';
2
1
  import TopLevelMenu from '@shell/components/nav/TopLevelMenu';
3
2
  import { SETTING } from '@shell/config/settings';
3
+ import { mount, Wrapper } from '@vue/test-utils';
4
4
 
5
5
  // DISCLAIMER: This should not be added here, although we have several store requests which are irrelevant
6
6
  const defaultStore = {
@@ -16,6 +16,12 @@ describe('component: Type', () => {
16
16
  mocks: {
17
17
  $route: { path: 'whatever' },
18
18
  $router: { resolve: () => ({ route: { path: 'whatever' } }) },
19
+ $store: {
20
+ getters: {
21
+ currentStore: () => 'cluster',
22
+ 'cluster/count': () => 1,
23
+ }
24
+ }
19
25
  },
20
26
  });
21
27
 
@@ -31,6 +37,12 @@ describe('component: Type', () => {
31
37
  mocks: {
32
38
  $route: { hash: 'whatever' },
33
39
  $router: { resolve: () => ({ route: { path: 'whatever' } }) },
40
+ $store: {
41
+ getters: {
42
+ currentStore: () => 'cluster',
43
+ 'cluster/count': () => 1,
44
+ }
45
+ }
34
46
  },
35
47
  });
36
48
 
@@ -67,6 +79,12 @@ describe('component: Type', () => {
67
79
  path: 'whatever',
68
80
  },
69
81
  $router: { resolve: () => ({ route: { path: 'many/parts' } }) },
82
+ $store: {
83
+ getters: {
84
+ currentStore: () => 'cluster',
85
+ 'cluster/count': () => 1,
86
+ }
87
+ }
70
88
  },
71
89
  });
72
90
 
@@ -101,6 +119,12 @@ describe('component: Type', () => {
101
119
  path: currentPath,
102
120
  },
103
121
  $router: { resolve: () => ({ route: { path: menuPath } }) },
122
+ $store: {
123
+ getters: {
124
+ currentStore: () => 'cluster',
125
+ 'cluster/count': () => 1,
126
+ }
127
+ }
104
128
  },
105
129
  });
106
130
 
@@ -126,6 +150,12 @@ describe('component: Type', () => {
126
150
  path: currentPath,
127
151
  },
128
152
  $router: { resolve: () => ({ route: { path: menuPath } }) },
153
+ $store: {
154
+ getters: {
155
+ currentStore: () => 'cluster',
156
+ 'cluster/count': () => 1,
157
+ }
158
+ }
129
159
  },
130
160
  });
131
161
 
@@ -60,6 +60,7 @@ export interface IClusterProvisioner {
60
60
 
61
61
  /**
62
62
  * Unique ID of the Cluster Provisioner
63
+ * If this overlaps with the name of an existing provisioner (seen in the type query param while creating a cluster) this provisioner will overwrite the built-in ui
63
64
  */
64
65
  id: string;
65
66
 
@@ -115,6 +116,12 @@ export interface IClusterProvisioner {
115
116
  */
116
117
  tag?: string;
117
118
 
119
+ /**
120
+ * Also show the provider card in the cluster importing flow
121
+ * If not set, the card will only be shown in the cluster creation page
122
+ */
123
+ showImport?: boolean
124
+
118
125
  /* --------------------------------------------------------------------------------------
119
126
  * Custer Details View
120
127
  * --------------------------------------------------------------------------------------- */
@@ -0,0 +1,77 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import ProvisioningCattleIoCluster from '@shell/detail/provisioning.cattle.io.cluster.vue';
3
+
4
+ jest.mock('@shell/utils/clipboard', () => {
5
+ return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
6
+ });
7
+
8
+ describe('view: provisioning.cattle.io.cluster', () => {
9
+ const mockStore = {
10
+ getters: {
11
+ 'management/canList': () => true,
12
+ 'management/schemaFor': jest.fn(),
13
+ 'i18n/t': (text: string) => text,
14
+ t: (text: string) => text,
15
+ currentStore: () => 'current_store',
16
+ 'current_store/schemaFor': jest.fn(),
17
+ 'current_store/all': jest.fn(),
18
+ workspace: jest.fn(),
19
+ },
20
+ };
21
+
22
+ const mocks = {
23
+ $store: mockStore,
24
+ $fetchState: { pending: false },
25
+ $route: {
26
+ query: { AS: '' },
27
+ name: {
28
+ endsWith: () => {
29
+ return false;
30
+ },
31
+ },
32
+ },
33
+ };
34
+
35
+ describe('registration tab visibility', () => {
36
+ it('a hosted Kubernetes Provider with a private endpoint (network config) and cluster not ready should SHOW the registration tab', async() => {
37
+ const value = {
38
+ isHostedKubernetesProvider: true,
39
+ isPrivateHostedProvider: true,
40
+ mgmt: {
41
+ hasLink: () => jest.fn(),
42
+ linkFor: () => '',
43
+ isReady: false
44
+ }
45
+ };
46
+
47
+ const wrapper = shallowMount(ProvisioningCattleIoCluster, {
48
+ mocks,
49
+ propsData: { value },
50
+ });
51
+
52
+ await wrapper.setData({ clusterToken: {} });
53
+
54
+ expect(wrapper.vm.showRegistration).toStrictEqual(true);
55
+ });
56
+
57
+ it('a hosted Kubernetes Provider WITHOUT a private endpoint (network config) and cluster not ready should NOT SHOW the registration tab', async() => {
58
+ const value = {
59
+ isHostedKubernetesProvider: true,
60
+ mgmt: {
61
+ hasLink: () => jest.fn(),
62
+ linkFor: () => '',
63
+ isReady: false
64
+ }
65
+ };
66
+
67
+ const wrapper = shallowMount(ProvisioningCattleIoCluster, {
68
+ mocks,
69
+ propsData: { value },
70
+ });
71
+
72
+ await wrapper.setData({ clusterToken: {} });
73
+
74
+ expect(wrapper.vm.showRegistration).toStrictEqual(false);
75
+ });
76
+ });
77
+ });
@@ -38,7 +38,7 @@ export default {
38
38
  if (this.hasRepoLabel) {
39
39
  const bundleResourceIds = this.bundleResourceIds;
40
40
 
41
- return this.repo?.status?.resources.filter((resource) => {
41
+ return this.repo?.status?.resources?.filter((resource) => {
42
42
  return bundleResourceIds.includes(resource.name);
43
43
  });
44
44
  } else if (this.value?.spec?.resources?.length) {
@@ -519,7 +519,10 @@ export default {
519
519
  return this.extDetailTabs.registration;
520
520
  }
521
521
 
522
- if ( this.value.isHostedKubernetesProvider && !this.isClusterReady ) {
522
+ // Hosted kubernetes providers with private endpoints need the registration tab
523
+ // https://github.com/rancher/dashboard/issues/6036
524
+ // https://github.com/rancher/dashboard/issues/4545
525
+ if ( this.value.isHostedKubernetesProvider && this.value.isPrivateHostedProvider && !this.isClusterReady ) {
523
526
  return this.extDetailTabs.registration;
524
527
  }
525
528
 
@@ -552,6 +555,18 @@ export default {
552
555
 
553
556
  snapshotsGroupBy() {
554
557
  return 'backupLocation';
558
+ },
559
+
560
+ extDetailTabsRelated() {
561
+ return this.extDetailTabs?.related;
562
+ },
563
+
564
+ extDetailTabsEvents() {
565
+ return this.extDetailTabs?.events;
566
+ },
567
+
568
+ extDetailTabsConditions() {
569
+ return this.extDetailTabs?.conditions;
555
570
  }
556
571
  },
557
572
 
@@ -725,9 +740,9 @@ export default {
725
740
  :default-tab="defaultTab"
726
741
  :need-related="hasLocalAccess"
727
742
  :extension-params="extCustomParams"
728
- :needRelated="extDetailTabs.related"
729
- :needEvents="extDetailTabs.events"
730
- :needConditions="extDetailTabs.conditions"
743
+ :needRelated="extDetailTabsRelated"
744
+ :needEvents="extDetailTabsEvents"
745
+ :needConditions="extDetailTabsConditions"
731
746
  >
732
747
  <Tab
733
748
  v-if="showMachines"
@@ -145,6 +145,7 @@ export default {
145
145
  <div class="edit-change mt-20">
146
146
  <h5 v-t="'advancedSettings.edit.changeSetting'" />
147
147
  <button
148
+ data-testid="advanced_settings_use_default"
148
149
  :disabled="!canReset"
149
150
  type="button"
150
151
  class="btn role-primary"
@@ -190,7 +190,7 @@ export default {
190
190
  <div class="row mb-20">
191
191
  <div class="col span-12">
192
192
  <LabeledInput
193
- v-model="value.httpConfig.proxyUrl"
193
+ v-model="value.httpConfig.proxyURL"
194
194
  :mode="mode"
195
195
  label="Proxy URL"
196
196
  placeholder="e.g. http://my-proxy/"