@rancher/shell 3.0.9-rc.5 → 3.0.9

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 (172) hide show
  1. package/assets/images/providers/oci-open-containers.svg +22 -0
  2. package/assets/images/providers/traefik.png +0 -0
  3. package/assets/styles/themes/_dark.scss +2 -0
  4. package/assets/styles/themes/_light.scss +2 -0
  5. package/assets/styles/themes/_modern.scss +6 -0
  6. package/assets/translations/en-us.yaml +129 -25
  7. package/components/CruResource.vue +3 -1
  8. package/components/ExplorerProjectsNamespaces.vue +12 -12
  9. package/components/IconOrSvg.vue +61 -42
  10. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  11. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  12. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  13. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  14. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  15. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  16. package/components/Resource/Detail/ResourceRow.vue +2 -2
  17. package/components/ResourceList/index.vue +7 -4
  18. package/components/SortableTable/index.vue +2 -2
  19. package/components/Window/ContainerLogs.vue +48 -37
  20. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  21. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  22. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  23. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  24. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  25. package/components/fleet/GitRepoTargetTab.vue +77 -0
  26. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  27. package/components/fleet/HelmOpChartTab.vue +158 -0
  28. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  29. package/components/fleet/HelmOpTargetTab.vue +84 -0
  30. package/components/fleet/HelmOpValuesTab.vue +147 -0
  31. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  32. package/components/form/BannerSettings.vue +2 -2
  33. package/components/form/NodeScheduling.vue +81 -7
  34. package/components/form/NotificationSettings.vue +2 -2
  35. package/components/form/PodAffinity.vue +1 -36
  36. package/components/form/ResourceLabeledSelect.vue +8 -4
  37. package/components/form/ResourceQuota/Namespace.vue +30 -9
  38. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  39. package/components/form/ResourceQuota/Project.vue +140 -82
  40. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  41. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  42. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  43. package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
  44. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  45. package/components/form/SchedulingCustomization.vue +14 -6
  46. package/components/form/SelectOrCreateAuthSecret.vue +107 -18
  47. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  48. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  49. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  50. package/components/formatter/ClusterLink.vue +8 -0
  51. package/components/formatter/SecretOrigin.vue +79 -0
  52. package/config/labels-annotations.js +7 -6
  53. package/config/pagination-table-headers.js +6 -4
  54. package/config/product/explorer.js +1 -11
  55. package/config/product/manager.js +0 -1
  56. package/config/query-params.js +3 -0
  57. package/config/settings.ts +15 -2
  58. package/config/table-headers.js +21 -17
  59. package/config/types.js +23 -8
  60. package/detail/fleet.cattle.io.cluster.vue +1 -1
  61. package/detail/workload/index.vue +11 -16
  62. package/dialog/DeactivateDriverDialog.vue +1 -1
  63. package/dialog/FeatureFlagListDialog.vue +1 -1
  64. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  65. package/dialog/ScalePoolDownDialog.vue +2 -2
  66. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  67. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  68. package/edit/__tests__/management.cattle.io.project.test.js +56 -128
  69. package/edit/auth/oidc.vue +1 -1
  70. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  71. package/edit/fleet.cattle.io.gitrepo.vue +153 -283
  72. package/edit/fleet.cattle.io.helmop.vue +190 -332
  73. package/edit/management.cattle.io.project.vue +5 -42
  74. package/edit/management.cattle.io.setting.vue +6 -0
  75. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
  76. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
  77. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
  78. package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
  79. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
  80. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
  81. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
  82. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
  83. package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
  84. package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
  85. package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
  86. package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
  87. package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
  88. package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
  89. package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
  90. package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
  91. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  92. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  93. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  94. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  95. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
  96. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +114 -0
  97. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  98. package/edit/provisioning.cattle.io.cluster/rke2.vue +167 -69
  99. package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
  100. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  101. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +70 -7
  102. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +343 -0
  103. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  104. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  105. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  106. package/edit/secret/index.vue +1 -1
  107. package/edit/token.vue +68 -29
  108. package/edit/workload/__tests__/index.test.ts +2 -37
  109. package/edit/workload/index.vue +6 -2
  110. package/edit/workload/mixins/workload.js +0 -32
  111. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  112. package/list/management.cattle.io.setting.vue +13 -0
  113. package/list/provisioning.cattle.io.cluster.vue +50 -1
  114. package/list/secret.vue +4 -9
  115. package/list/service.vue +6 -8
  116. package/machine-config/amazonec2.vue +11 -4
  117. package/machine-config/components/EC2Networking.vue +46 -30
  118. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  119. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  120. package/machine-config/digitalocean.vue +3 -3
  121. package/models/__tests__/chart.test.ts +2 -2
  122. package/models/__tests__/namespace.test.ts +11 -0
  123. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  124. package/models/__tests__/workload.test.ts +42 -1
  125. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  126. package/models/chart.js +3 -3
  127. package/models/ext.cattle.io.token.js +48 -0
  128. package/models/kontainerdriver.js +2 -2
  129. package/models/namespace.js +7 -1
  130. package/models/nodedriver.js +2 -2
  131. package/models/provisioning.cattle.io.cluster.js +28 -7
  132. package/models/secret.js +0 -17
  133. package/models/service.js +44 -1
  134. package/models/token.js +4 -0
  135. package/models/workload.js +12 -6
  136. package/package.json +1 -1
  137. package/pages/account/index.vue +96 -67
  138. package/pages/auth/setup.vue +5 -14
  139. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +45 -18
  140. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  141. package/pages/c/_cluster/apps/charts/index.vue +82 -3
  142. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  143. package/pages/c/_cluster/explorer/tools/index.vue +1 -1
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  146. package/pages/c/_cluster/settings/index.vue +3 -1
  147. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  148. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  149. package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
  150. package/plugins/dashboard-store/actions.js +3 -8
  151. package/plugins/dashboard-store/getters.js +7 -5
  152. package/plugins/dashboard-store/mutations.js +4 -1
  153. package/plugins/dashboard-store/resource-class.js +3 -3
  154. package/plugins/steve/__tests__/steve-class.test.ts +102 -141
  155. package/plugins/steve/steve-class.js +12 -3
  156. package/plugins/steve/steve-pagination-utils.ts +6 -2
  157. package/rancher-components/RcIcon/types.ts +2 -0
  158. package/rancher-components/RcItemCard/RcItemCard.vue +72 -20
  159. package/store/prefs.js +3 -0
  160. package/types/aws-sdk.d.ts +121 -0
  161. package/types/resources/node.ts +15 -0
  162. package/types/shell/index.d.ts +537 -506
  163. package/types/store/pagination.types.ts +5 -5
  164. package/utils/__tests__/array.test.ts +1 -29
  165. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  166. package/utils/array.ts +0 -11
  167. package/utils/aws.ts +21 -0
  168. package/utils/cluster.js +22 -2
  169. package/utils/selector-typed.ts +1 -1
  170. package/utils/svg-filter.js +4 -3
  171. package/components/__tests__/ProjectRow.test.ts +0 -206
  172. package/components/form/ResourceQuota/ProjectRow.vue +0 -277
package/edit/token.vue CHANGED
@@ -2,7 +2,7 @@
2
2
  import { mapGetters } from 'vuex';
3
3
  import day from 'dayjs';
4
4
  import sortBy from 'lodash/sortBy';
5
- import { MANAGEMENT, NORMAN } from '@shell/config/types';
5
+ import { MANAGEMENT, EXT } from '@shell/config/types';
6
6
  import { Banner } from '@components/Banner';
7
7
  import DetailText from '@shell/components/DetailText';
8
8
  import Footer from '@shell/components/form/Footer';
@@ -14,6 +14,7 @@ import CreateEditView from '@shell/mixins/create-edit-view';
14
14
  import { diffFrom } from '@shell/utils/time';
15
15
  import { filterHiddenLocalCluster, filterOnlyKubernetesClusters } from '@shell/utils/cluster';
16
16
  import { SETTING } from '@shell/config/settings';
17
+ import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
17
18
 
18
19
  export default {
19
20
  components: {
@@ -24,6 +25,7 @@ export default {
24
25
  LabeledSelect,
25
26
  RadioGroup,
26
27
  Select,
28
+ Checkbox,
27
29
  },
28
30
 
29
31
  mixins: [CreateEditView],
@@ -41,7 +43,11 @@ export default {
41
43
 
42
44
  return {
43
45
  errors: null,
46
+ user: null,
44
47
  form: {
48
+ enabled: true,
49
+ description: '',
50
+ clusterName: '',
45
51
  expiryType: 'never',
46
52
  customExpiry: 0,
47
53
  customExpiryUnits: 'minute',
@@ -51,11 +57,13 @@ export default {
51
57
  accessKey: '',
52
58
  secretKey: '',
53
59
  maxTTL,
60
+ ttl: ''
54
61
  };
55
62
  },
56
63
 
57
64
  computed: {
58
65
  ...mapGetters({ t: 'i18n/t' }),
66
+
59
67
  scopes() {
60
68
  const all = this.$store.getters['management/all'](MANAGEMENT.CLUSTER);
61
69
  const kubeClusters = filterHiddenLocalCluster(filterOnlyKubernetesClusters(all, this.$store), this.$store);
@@ -71,16 +79,20 @@ export default {
71
79
  const options = ['never', 'day', 'month', 'year', 'custom'];
72
80
  let opts = options.map((opt) => ({ value: opt, label: this.t(`accountAndKeys.apiKeys.add.expiry.options.${ opt }`) }));
73
81
 
74
- // When the TTL is anything other than 0, present only two options
82
+ // When the TTL is greater than 0, present only two options
75
83
  // (1) The maximum allowed
76
84
  // (2) Custom
77
- if (this.maxTTL !== 0 ) {
85
+ if (this.maxTTL > 0 ) {
78
86
  const now = day();
79
87
  const expiry = now.add(this.maxTTL, 'minute');
80
88
  const max = diffFrom(expiry, now, this.t);
81
89
 
82
90
  opts = opts.filter((opt) => opt.value === 'custom');
83
91
  opts.unshift({ value: 'max', label: this.t('accountAndKeys.apiKeys.add.expiry.options.maximum', { value: max.string }) });
92
+ } else {
93
+ // maxTTL <= 0 means there is no maximum, so we can show the 'never' option which results in an infinite TTL
94
+ // OR if we set a positive TTL, then it assumes that value
95
+ opts = opts.filter((opt) => opt.value === 'never' || opt.value === 'custom');
84
96
  }
85
97
 
86
98
  return opts;
@@ -91,6 +103,9 @@ export default {
91
103
 
92
104
  return filtered.map((opt) => ({ value: opt, label: this.t(`accountAndKeys.apiKeys.add.customExpiry.options.${ opt }`) }));
93
105
  },
106
+ hasNeverOption() {
107
+ return this.expiryOptions?.filter((opt) => opt.value === 'never')?.length === 1;
108
+ }
94
109
  },
95
110
 
96
111
  mounted() {
@@ -115,31 +130,33 @@ export default {
115
130
  });
116
131
  },
117
132
 
118
- async actuallySave(url) {
133
+ async actuallySave() {
134
+ // update expiration value before save
119
135
  this.updateExpiry();
120
- if ( this.isCreate ) {
121
- // Description is a bit weird, so need to clone and set this
122
- // rather than use this.value - need to find a way to set this if we ever
123
- // want to allow edit (which I don't think we do)
124
- const res = await this.value.save();
125
136
 
126
- this.created = res;
127
- this.ttlLimited = res.ttl !== this.value.ttl;
128
- const token = this.created.token.split(':');
137
+ if ( this.isCreate ) {
138
+ const steveToken = await this.$store.dispatch('management/create', {
139
+ type: EXT.TOKEN,
140
+ spec: {
141
+ description: this.form.description,
142
+ kind: '',
143
+ userPrincipal: null, // will be set by the backend to the current user
144
+ clusterName: this.form.clusterName,
145
+ enabled: this.form.enabled,
146
+ ttl: this.ttl
147
+ // userID: not needed as it will be set by the backend to the current user
148
+ }
149
+ });
150
+
151
+ const steveTokenSaved = await steveToken.save();
152
+
153
+ this.created = steveTokenSaved;
154
+ this.ttlLimited = this.created?.spec?.ttl !== this.ttl;
155
+ const token = this.created?.status?.bearerToken?.split(':');
129
156
 
130
157
  this.accessKey = token[0];
131
158
  this.secretKey = (token.length > 1) ? token[1] : '';
132
- this.token = this.created.token;
133
-
134
- // Force a refresh of the token so we get the expiry date correctly
135
- await this.$store.dispatch('rancher/find', {
136
- type: NORMAN.TOKEN,
137
- id: res.id,
138
- opt: { force: true }
139
- }, { root: true });
140
- } else {
141
- // Note: update of existing key not supported currently
142
- await this.value.save();
159
+ this.token = this.created?.status?.bearerToken;
143
160
  }
144
161
  },
145
162
 
@@ -159,7 +176,9 @@ export default {
159
176
  const units = (v === 'custom') ? this.form.customExpiryUnits : v;
160
177
  let ttl = 0;
161
178
 
162
- if (units === 'max') {
179
+ if (v === 'never') {
180
+ ttl = -1;
181
+ } else if (units === 'max') {
163
182
  ttl = this.maxTTL * 60 * 1000;
164
183
  } else if ( units !== 'never' ) {
165
184
  const now = day();
@@ -167,7 +186,8 @@ export default {
167
186
 
168
187
  ttl = expiry.diff(now);
169
188
  }
170
- this.value.ttl = ttl;
189
+
190
+ this.ttl = ttl;
171
191
  }
172
192
  }
173
193
  };
@@ -178,7 +198,7 @@ export default {
178
198
  <div class="pl-10 pr-10">
179
199
  <LabeledInput
180
200
  key="description"
181
- v-model:value="value.description"
201
+ v-model:value="form.description"
182
202
  :placeholder="t('accountAndKeys.apiKeys.add.description.placeholder')"
183
203
  label-key="accountAndKeys.apiKeys.add.description.label"
184
204
  mode="edit"
@@ -186,13 +206,30 @@ export default {
186
206
  />
187
207
 
188
208
  <LabeledSelect
189
- v-model:value="value.clusterId"
209
+ v-model:value="form.clusterName"
190
210
  class="mt-20 scope-select"
191
211
  label-key="accountAndKeys.apiKeys.add.scope"
192
212
  :options="scopes"
193
213
  />
194
214
 
195
- <h5 class="pt-20">
215
+ <Checkbox
216
+ v-model:value="form.enabled"
217
+ class="mt-20 mb-20"
218
+ :mode="mode"
219
+ label-key="accountAndKeys.apiKeys.add.enabled"
220
+ />
221
+
222
+ <Banner
223
+ v-if="hasNeverOption"
224
+ color="warning"
225
+ class="mt-20"
226
+ >
227
+ <div>
228
+ {{ t('accountAndKeys.apiKeys.info.expiryOptionsWithNever') }}
229
+ </div>
230
+ </Banner>
231
+
232
+ <h5 class="mb-20">
196
233
  {{ t('accountAndKeys.apiKeys.add.expiry.label') }}
197
234
  </h5>
198
235
 
@@ -204,7 +241,9 @@ export default {
204
241
  class="mr-20"
205
242
  name="expiryGroup"
206
243
  />
207
- <div class="ml-20 mt-10 expiry">
244
+ <div
245
+ class="ml-20 mt-10 expiry"
246
+ >
208
247
  <input
209
248
  v-model="form.customExpiry"
210
249
  :disabled="form.expiryType !== 'custom'"
@@ -68,6 +68,8 @@ describe('component: Workload', () => {
68
68
  $store: {
69
69
  getters: {
70
70
  'cluster/schemaFor': jest.fn(),
71
+ 'cluster/canList': jest.fn(),
72
+ currentStore: () => 'cluster',
71
73
  'type-map/labelFor': jest.fn(),
72
74
  'i18n/t': (text: string, v: {[key:string]: string}) => {
73
75
  return `${ text }, ${ Object.values(v || {}) }`;
@@ -99,42 +101,5 @@ describe('component: Workload', () => {
99
101
 
100
102
  expect(result).toStrictEqual(newMessage);
101
103
  });
102
-
103
- describe('secondaryResourceDataConfig', () => {
104
- it('should filter out nodes with control-plane or etcd taints from workerNodes parsingFunc', () => {
105
- const allNodeObjects = [
106
- {
107
- id: 'node-1',
108
- spec: { taints: [{ key: 'node-role.kubernetes.io/control-plane', effect: 'NoSchedule' }] }
109
- },
110
- {
111
- id: 'node-2',
112
- spec: { taints: [{ key: 'node-role.kubernetes.io/etcd', effect: 'NoSchedule' }] }
113
- },
114
- {
115
- id: 'node-3',
116
- spec: { taints: [{ key: 'node-role.kubernetes.io/worker', effect: 'NoSchedule' }] }
117
- },
118
- {
119
- id: 'node-4',
120
- spec: { taints: [] }
121
- },
122
- {
123
- id: 'node-5',
124
- spec: {}
125
- },
126
- {
127
- id: 'node-6',
128
- spec: null
129
- }
130
- ];
131
-
132
- const { data } = (Workload.mixins[2] as any).methods.secondaryResourceDataConfig.apply({ value: { metadata: { namespace: 'test' } } });
133
- const workerNodesParsingFunc = data.node.applyTo.find((r: any) => r.var === 'workerNodes').parsingFunc;
134
- const result = workerNodesParsingFunc(allNodeObjects);
135
-
136
- expect(result).toStrictEqual(['node-3', 'node-4', 'node-5', 'node-6']);
137
- });
138
- });
139
104
  });
140
105
  });
@@ -4,6 +4,7 @@ import FormValidation from '@shell/mixins/form-validation';
4
4
  import WorkLoadMixin from '@shell/edit/workload/mixins/workload';
5
5
  import { mapGetters } from 'vuex';
6
6
  import { FORM_TYPES } from '@shell/components/form/Security';
7
+ import { NODE } from '@shell/config/types';
7
8
 
8
9
  export default {
9
10
  name: 'Workload',
@@ -21,9 +22,13 @@ export default {
21
22
  },
22
23
  },
23
24
  data() {
25
+ const inStore = this.$store.getters['currentStore'](NODE);
26
+ const canNode = this.$store.getters[`${ inStore }/canList`](NODE);
27
+
24
28
  return {
25
29
  selectedName: null,
26
30
  errors: [],
31
+ canNode,
27
32
  FORM_TYPES
28
33
  };
29
34
  },
@@ -506,11 +511,11 @@ export default {
506
511
  <PodAffinity
507
512
  :mode="mode"
508
513
  :value="podTemplateSpec"
509
- :nodes="allNodeObjects"
510
514
  :loading="isLoadingSecondaryResources"
511
515
  />
512
516
  </Tab>
513
517
  <Tab
518
+ v-if="canNode"
514
519
  :label="t('workload.container.titles.nodeScheduling')"
515
520
  name="nodeScheduling-pod"
516
521
  :weight="tabWeightMap['nodeScheduling']"
@@ -518,7 +523,6 @@ export default {
518
523
  <NodeScheduling
519
524
  :mode="mode"
520
525
  :value="podTemplateSpec"
521
- :nodes="workerNodes"
522
526
  :loading="isLoadingSecondaryResources"
523
527
  />
524
528
  </Tab>
@@ -5,7 +5,6 @@ import {
5
5
  CONFIG_MAP,
6
6
  SECRET,
7
7
  WORKLOAD_TYPES,
8
- NODE,
9
8
  SERVICE,
10
9
  PVC,
11
10
  SERVICE_ACCOUNT,
@@ -263,9 +262,6 @@ export default {
263
262
  return {
264
263
  secondaryResourceData: this.secondaryResourceDataConfig(),
265
264
  namespacedConfigMaps: [],
266
- allNodes: null,
267
- workerNodes: null,
268
- allNodeObjects: [],
269
265
  namespacedSecrets: [],
270
266
  imagePullNamespacedSecrets: [],
271
267
  allServices: [],
@@ -690,34 +686,6 @@ export default {
690
686
  }
691
687
  ]
692
688
  },
693
- [NODE]: {
694
- applyTo: [
695
- { var: 'allNodeObjects' },
696
- {
697
- var: 'allNodes',
698
- parsingFunc: (data) => {
699
- return data.map((node) => node.id);
700
- }
701
- },
702
- {
703
- var: 'workerNodes',
704
- parsingFunc: (data) => {
705
- const keys = [
706
- `node-role.kubernetes.io/control-plane`,
707
- `node-role.kubernetes.io/etcd`
708
- ];
709
-
710
- return data
711
- .filter((node) => {
712
- const taints = node?.spec?.taints || [];
713
-
714
- return taints.every((taint) => !keys.includes(taint.key));
715
- })
716
- .map((node) => node.id);
717
- }
718
- },
719
- ]
720
- },
721
689
  [SERVICE]: {
722
690
  applyTo: [
723
691
  { var: 'allServices' },
@@ -0,0 +1,198 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { nextTick } from 'vue';
3
+ import ManagementSetting from '@shell/list/management.cattle.io.setting.vue';
4
+ import { SETTING } from '@shell/config/settings';
5
+
6
+ const mockStore = {
7
+ getters: {
8
+ 'prefs/get': () => false,
9
+ 'i18n/t': (key: string) => key,
10
+ },
11
+ dispatch: jest.fn()
12
+ };
13
+
14
+ const mockRoute = {
15
+ hash: '',
16
+ params: {}
17
+ };
18
+
19
+ const mockRouter = {
20
+ push: jest.fn(),
21
+ replace: jest.fn()
22
+ };
23
+
24
+ // Mock scrollIntoView
25
+ const mockScrollIntoView = jest.fn();
26
+
27
+ Object.defineProperty(Element.prototype, 'scrollIntoView', {
28
+ value: mockScrollIntoView,
29
+ writable: true
30
+ });
31
+
32
+ // Mock querySelector
33
+ const mockQuerySelector = jest.fn();
34
+
35
+ Object.defineProperty(document, 'querySelector', {
36
+ value: mockQuerySelector,
37
+ writable: true
38
+ });
39
+
40
+ describe('managementSetting - Scroll Behavior', () => {
41
+ let wrapper: any;
42
+
43
+ const createWrapper = (routeHash = '') => {
44
+ const route = { ...mockRoute, hash: routeHash };
45
+
46
+ return mount(ManagementSetting, {
47
+ global: {
48
+ mocks: {
49
+ $store: mockStore,
50
+ $route: route,
51
+ $router: mockRouter,
52
+ $fetchState: { pending: false }
53
+ },
54
+ stubs: {
55
+ Loading: true,
56
+ Banner: true,
57
+ Setting: true
58
+ }
59
+ }
60
+ });
61
+ };
62
+
63
+ beforeEach(() => {
64
+ jest.clearAllMocks();
65
+ mockScrollIntoView.mockClear();
66
+ mockQuerySelector.mockClear();
67
+
68
+ // Mock successful API response with cluster agent setting
69
+ mockStore.dispatch.mockResolvedValue([
70
+ {
71
+ id: SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS,
72
+ value: '{"enabled": true}',
73
+ default: '{"enabled": false}',
74
+ availableActions: ['edit']
75
+ },
76
+ {
77
+ id: SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS,
78
+ value: '{"enabled": true}',
79
+ default: '{"enabled": false}',
80
+ availableActions: ['edit']
81
+ }
82
+ ]);
83
+ });
84
+
85
+ afterEach(() => {
86
+ if (wrapper) {
87
+ wrapper.unmount();
88
+ }
89
+ });
90
+
91
+ it('should scroll to hash anchor after data is loaded', async() => {
92
+ const mockElement = document.createElement('div');
93
+
94
+ mockQuerySelector.mockReturnValue(mockElement);
95
+
96
+ wrapper = createWrapper('#cluster-agent-default-priority-class');
97
+
98
+ // Trigger fetch manually since we can't easily test the async fetch hook
99
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
100
+ await nextTick();
101
+
102
+ expect(mockQuerySelector).toHaveBeenCalledWith('#cluster-agent-default-priority-class');
103
+ expect(mockScrollIntoView).toHaveBeenCalledWith({
104
+ behavior: 'smooth',
105
+ block: 'start'
106
+ });
107
+ });
108
+
109
+ it('should not scroll if hash is empty', async() => {
110
+ wrapper = createWrapper('');
111
+
112
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
113
+ await nextTick();
114
+
115
+ expect(mockQuerySelector).not.toHaveBeenCalled();
116
+ expect(mockScrollIntoView).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it('should not scroll if element is not found', async() => {
120
+ mockQuerySelector.mockReturnValue(null);
121
+
122
+ wrapper = createWrapper('#non-existent-element');
123
+
124
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
125
+ await nextTick();
126
+
127
+ expect(mockQuerySelector).toHaveBeenCalledWith('#non-existent-element');
128
+ expect(mockScrollIntoView).not.toHaveBeenCalled();
129
+ });
130
+
131
+ it('should scroll to fleet agent setting hash', async() => {
132
+ const mockElement = document.createElement('div');
133
+
134
+ mockQuerySelector.mockReturnValue(mockElement);
135
+
136
+ wrapper = createWrapper('#fleet-agent-default-priority-class');
137
+
138
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
139
+ await nextTick();
140
+
141
+ expect(mockQuerySelector).toHaveBeenCalledWith('#fleet-agent-default-priority-class');
142
+ expect(mockScrollIntoView).toHaveBeenCalledWith({
143
+ behavior: 'smooth',
144
+ block: 'start'
145
+ });
146
+ });
147
+
148
+ it('should handle hash with special characters', async() => {
149
+ const mockElement = document.createElement('div');
150
+
151
+ mockQuerySelector.mockReturnValue(mockElement);
152
+
153
+ wrapper = createWrapper('#setting-with-special-chars_123');
154
+
155
+ await wrapper.vm.$options.fetch.call(wrapper.vm);
156
+ await nextTick();
157
+
158
+ expect(mockQuerySelector).toHaveBeenCalledWith('#setting-with-special-chars_123');
159
+ expect(mockScrollIntoView).toHaveBeenCalledWith({
160
+ behavior: 'smooth',
161
+ block: 'start'
162
+ });
163
+ });
164
+
165
+ it('should render settings with correct IDs for anchors', async() => {
166
+ wrapper = createWrapper();
167
+
168
+ // Manually set the data to simulate successful fetch
169
+ wrapper.vm.settings = [
170
+ {
171
+ id: SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS,
172
+ description: 'test-description',
173
+ data: { value: 'test' },
174
+ customized: false,
175
+ fromEnv: false,
176
+ hasActions: true
177
+ }
178
+ ];
179
+ wrapper.vm.provisioningSettings = [
180
+ {
181
+ id: SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS,
182
+ description: 'test-description',
183
+ data: { value: 'test' },
184
+ customized: false,
185
+ fromEnv: false,
186
+ hasActions: true
187
+ }
188
+ ];
189
+
190
+ await nextTick();
191
+
192
+ const settingDivs = wrapper.findAll('div[id]');
193
+ const ids = settingDivs.map((div: any) => div.attributes('id'));
194
+
195
+ expect(ids).toContain(SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS);
196
+ expect(ids).toContain(SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS);
197
+ });
198
+ });
@@ -66,6 +66,17 @@ export default {
66
66
 
67
67
  this.settings = settings;
68
68
  this.provisioningSettings = provisioningSettings;
69
+
70
+ this.$nextTick(() => {
71
+ // Handle scrolling to hash anchor after data is loaded
72
+ if (this.$route.hash) {
73
+ const element = document.querySelector(this.$route.hash);
74
+
75
+ if (element) {
76
+ element.scrollIntoView({ behavior: 'smooth', block: 'start' });
77
+ }
78
+ }
79
+ });
69
80
  },
70
81
 
71
82
  data() {
@@ -89,6 +100,7 @@ export default {
89
100
  </Banner>
90
101
  <div
91
102
  v-for="(setting) in settings"
103
+ :id="setting.id"
92
104
  :key="setting.id"
93
105
  >
94
106
  <Setting
@@ -101,6 +113,7 @@ export default {
101
113
  </h2>
102
114
  <div
103
115
  v-for="(setting) in provisioningSettings"
116
+ :id="setting.id"
104
117
  :key="setting.id"
105
118
  >
106
119
  <Setting
@@ -167,6 +167,12 @@ export default {
167
167
 
168
168
  },
169
169
 
170
+ methods: {
171
+ getCustomDetailLink(cluster) {
172
+ return cluster.isCapiHybrid ? null : cluster.detailLocation;
173
+ }
174
+ },
175
+
170
176
  $loadingResources() {
171
177
  // results are filtered so we wouldn't get the correct count on indicator...
172
178
  return { loadIndeterminate: true };
@@ -211,7 +217,6 @@ export default {
211
217
  </router-link>
212
218
  </template>
213
219
  </Masthead>
214
-
215
220
  <ResourceTable
216
221
  :headers="headers"
217
222
  :table-actions="true"
@@ -221,6 +226,8 @@ export default {
221
226
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
222
227
  :data-testid="'cluster-list'"
223
228
  :force-update-live-and-delayed="forceUpdateLiveAndDelayed"
229
+ :get-custom-detail-link="getCustomDetailLink"
230
+ :sub-rows="true"
224
231
  >
225
232
  <!-- Why are state column and subrow overwritten here? -->
226
233
  <!-- for rke1 clusters, where they try to use the mgmt cluster stateObj instead of prov cluster stateObj, -->
@@ -266,6 +273,48 @@ export default {
266
273
  {{ t('cluster.explore') }}
267
274
  </button>
268
275
  </template>
276
+ <template #additional-sub-row="{row, fullColspan, tableActions}">
277
+ <tr
278
+ class="capi-unsupported"
279
+ :class="{'has-description': !!row.stateDescription}"
280
+ >
281
+ <td
282
+ v-if="row.isCapiHybrid"
283
+ class="row-check"
284
+ />
285
+ <td
286
+ v-if="row.isCapiHybrid"
287
+ :data-testid="`capi-unsupported-warning-${row?.metadata?.name}`"
288
+ :colspan="fullColspan - (tableActions ? 1: 0)"
289
+ >
290
+ <div
291
+ class="text-warning"
292
+ :class="{'mt-5': !!row.stateDescription.trim()}"
293
+ >
294
+ <i class="icon icon-warning" />{{ t('cluster.capi.notSupported') }}
295
+ </div>
296
+ </td>
297
+ </tr>
298
+ </template>
269
299
  </ResourceTable>
270
300
  </div>
271
301
  </template>
302
+
303
+ <style scoped lang="scss">
304
+ .capi-unsupported {
305
+ &.has-description {
306
+ border-bottom: none;
307
+ padding: 0px;
308
+ td {
309
+ padding-top: 0px;
310
+ }
311
+ }
312
+
313
+ & div {
314
+ & i {
315
+ margin-right: 0.1em;
316
+ }
317
+ display: flex;
318
+ }
319
+ }
320
+ </style>
package/list/secret.vue CHANGED
@@ -7,8 +7,8 @@ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable';
7
7
  import { TableColumn } from '@shell/types/store/type-map';
8
8
  import ResourceFetch from '@shell/mixins/resource-fetch';
9
9
  import { mapGetters } from 'vuex';
10
- import { SECRET_CLONE, SECRET_PROJECT_SCOPED } from '@shell/config/table-headers';
11
- import { STEVE_SECRET_CLONE } from '@shell/config/pagination-table-headers';
10
+ import { SECRET_ORIGIN } from '@shell/config/table-headers';
11
+ import { STEVE_SECRET_ORIGIN } from '@shell/config/pagination-table-headers';
12
12
 
13
13
  export default {
14
14
  name: 'ListSecret',
@@ -61,13 +61,8 @@ export default {
61
61
 
62
62
  if (this.canViewProjects) {
63
63
  // if the user can see projects, add a column to let them know if it's a secret from a project scoped secret
64
- headers.push(SECRET_CLONE);
65
- headersSSP.push(STEVE_SECRET_CLONE);
66
- if (this.currentCluster.isLocal) {
67
- // if the user is on the local cluster, add a column to let them know if it's a project scoped secret (from another cluster)
68
- headers.push(SECRET_PROJECT_SCOPED);
69
- headersSSP.push(SECRET_PROJECT_SCOPED);
70
- }
64
+ headers.push(SECRET_ORIGIN);
65
+ headersSSP.push(STEVE_SECRET_ORIGIN);
71
66
  }
72
67
 
73
68
  headers.push(this.namespacedHeaders[this.namespacedHeaders.length - 1]);