@rancher/shell 0.3.15 → 0.3.17

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 (155) hide show
  1. package/assets/images/wechat-qr-code.jpg +0 -0
  2. package/assets/translations/en-us.yaml +70 -15
  3. package/assets/translations/zh-hans.yaml +155 -33
  4. package/chart/__tests__/S3.test.ts +50 -0
  5. package/chart/rancher-backup/S3.vue +21 -0
  6. package/chart/rancher-backup/index.vue +4 -0
  7. package/cloud-credential/generic.vue +1 -1
  8. package/components/BannerGraphic.vue +1 -0
  9. package/components/CommunityLinks.vue +1 -0
  10. package/components/CruResource.vue +1 -1
  11. package/components/EmberPage.vue +1 -0
  12. package/components/FileDiff.vue +92 -85
  13. package/components/GrafanaDashboard.vue +7 -1
  14. package/components/ResourceDetail/index.vue +4 -12
  15. package/components/ResourceList/index.vue +1 -1
  16. package/components/ResourceTable.vue +50 -2
  17. package/components/SimpleBox.vue +1 -0
  18. package/components/SortableTable/index.vue +5 -1
  19. package/components/YamlEditor.vue +1 -0
  20. package/components/auth/RoleDetailEdit.vue +1 -0
  21. package/components/form/GitPicker.vue +1 -1
  22. package/components/form/NameNsDescription.vue +28 -12
  23. package/components/form/NodeAffinity.vue +2 -2
  24. package/components/form/PodAffinity.vue +8 -3
  25. package/components/form/ResourceTabs/index.vue +8 -2
  26. package/components/form/Select.vue +16 -0
  27. package/components/form/__tests__/NodeAffinity.test.ts +38 -0
  28. package/components/form/__tests__/PodAffinity.test.ts +46 -0
  29. package/components/formatter/ClusterLink.vue +8 -4
  30. package/components/formatter/ImageName.vue +23 -0
  31. package/components/formatter/PodImages.vue +7 -1
  32. package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
  33. package/components/nav/Header.vue +2 -2
  34. package/config/__test__/home-links.test.ts +62 -0
  35. package/config/home-links.js +15 -3
  36. package/config/labels-annotations.js +5 -1
  37. package/config/product/auth.js +1 -1
  38. package/config/router.js +0 -9
  39. package/config/settings.ts +4 -0
  40. package/config/table-headers.js +6 -5
  41. package/config/uiplugins.js +50 -5
  42. package/core/plugin-helpers.js +20 -12
  43. package/core/plugin.ts +9 -0
  44. package/core/plugins.js +1 -1
  45. package/core/types-provisioning.ts +253 -0
  46. package/core/types.ts +17 -3
  47. package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
  48. package/detail/catalog.cattle.io.clusterrepo.vue +8 -1
  49. package/detail/node.vue +6 -6
  50. package/detail/pod.vue +2 -6
  51. package/detail/provisioning.cattle.io.cluster.vue +46 -7
  52. package/detail/workload/index.vue +9 -9
  53. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
  54. package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +56 -0
  55. package/edit/auth/github.vue +1 -0
  56. package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
  57. package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
  58. package/edit/fleet.cattle.io.gitrepo.vue +18 -1
  59. package/edit/monitoring.coreos.com.prometheusrule/index.vue +8 -3
  60. package/edit/namespace.vue +9 -1
  61. package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
  62. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
  63. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
  64. package/edit/provisioning.cattle.io.cluster/index.vue +52 -0
  65. package/edit/provisioning.cattle.io.cluster/rke2.vue +330 -150
  66. package/edit/ui.cattle.io.navlink.vue +2 -1
  67. package/initialize/App.js +3 -13
  68. package/initialize/layouts.ts +26 -0
  69. package/list/provisioning.cattle.io.cluster.vue +8 -1
  70. package/middleware/authenticated.js +93 -5
  71. package/mixins/brand.js +39 -3
  72. package/mixins/child-hook.js +2 -2
  73. package/mixins/create-edit-view/impl.js +2 -2
  74. package/models/fleet.cattle.io.gitrepo.js +1 -0
  75. package/models/provisioning.cattle.io.cluster.js +9 -1
  76. package/package.json +3 -3
  77. package/pages/about.vue +8 -2
  78. package/pages/auth/login.vue +10 -0
  79. package/pages/auth/logout.vue +11 -3
  80. package/pages/auth/setup.vue +4 -0
  81. package/pages/c/_cluster/apps/charts/index.vue +5 -2
  82. package/pages/c/_cluster/apps/charts/install.vue +5 -0
  83. package/pages/c/_cluster/auth/roles/index.vue +1 -1
  84. package/pages/c/_cluster/explorer/index.vue +1 -10
  85. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
  86. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
  87. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
  88. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
  89. package/pages/c/_cluster/uiplugins/index.vue +155 -44
  90. package/pages/docs/_doc.vue +9 -3
  91. package/pages/home.vue +10 -5
  92. package/pages/support/index.vue +10 -4
  93. package/pkg/auto-import.js +1 -1
  94. package/plugins/clean-tooltip-directive.js +1 -1
  95. package/plugins/dashboard-store/resource-class.js +35 -2
  96. package/plugins/plugin.js +9 -1
  97. package/plugins/steve/actions.js +22 -0
  98. package/rancher-components/BadgeState/BadgeState.vue +5 -1
  99. package/rancher-components/Banner/Banner.test.ts +51 -1
  100. package/rancher-components/Banner/Banner.vue +134 -53
  101. package/rancher-components/Card/Card.vue +24 -7
  102. package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
  103. package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
  104. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
  105. package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
  106. package/rancher-components/Form/Radio/RadioButton.vue +30 -13
  107. package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
  108. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
  109. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
  110. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
  111. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
  112. package/rancher-components/StringList/StringList.test.ts +453 -49
  113. package/rancher-components/StringList/StringList.vue +92 -58
  114. package/scripts/extension/publish +2 -2
  115. package/scripts/typegen.sh +1 -0
  116. package/server/server-middleware.js +4 -12
  117. package/store/index.js +13 -0
  118. package/store/prefs.js +0 -3
  119. package/store/type-map.js +17 -29
  120. package/types/shell/index.d.ts +243 -90
  121. package/utils/kube.js +9 -0
  122. package/utils/object.js +27 -0
  123. package/utils/settings.ts +2 -2
  124. package/vue.config.js +3 -2
  125. package/pages/safeMode.vue +0 -17
  126. package/rancher-components/components/BadgeState/BadgeState.spec.ts +0 -12
  127. package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
  128. package/rancher-components/components/BadgeState/index.ts +0 -1
  129. package/rancher-components/components/Banner/Banner.test.ts +0 -63
  130. package/rancher-components/components/Banner/Banner.vue +0 -244
  131. package/rancher-components/components/Banner/index.ts +0 -1
  132. package/rancher-components/components/Card/Card.vue +0 -167
  133. package/rancher-components/components/Card/index.ts +0 -1
  134. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
  135. package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -420
  136. package/rancher-components/components/Form/Checkbox/index.ts +0 -1
  137. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -23
  138. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -355
  139. package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
  140. package/rancher-components/components/Form/Radio/RadioButton.vue +0 -287
  141. package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -254
  142. package/rancher-components/components/Form/Radio/index.ts +0 -2
  143. package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -170
  144. package/rancher-components/components/Form/TextArea/index.ts +0 -1
  145. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
  146. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -149
  147. package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
  148. package/rancher-components/components/Form/index.ts +0 -5
  149. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -151
  150. package/rancher-components/components/LabeledTooltip/index.ts +0 -1
  151. package/rancher-components/components/StringList/StringList.test.ts +0 -484
  152. package/rancher-components/components/StringList/StringList.vue +0 -611
  153. package/rancher-components/components/StringList/index.ts +0 -1
  154. /package/rancher-components/{components/Card → Card}/Card.test.ts +0 -0
  155. /package/rancher-components/{components/Form → Form}/Radio/RadioButton.test.ts +0 -0
@@ -154,6 +154,9 @@ export default {
154
154
  }
155
155
 
156
156
  return 'none';
157
+ },
158
+ updatePageValid(update) {
159
+ this.$emit('valid', update);
157
160
  }
158
161
  },
159
162
  get
@@ -189,6 +192,7 @@ export default {
189
192
  :value="value.s3"
190
193
  :secrets="secrets"
191
194
  :mode="mode"
195
+ @valid="updatePageValid($event)"
192
196
  />
193
197
  <template v-else>
194
198
  <div class="row">
@@ -33,7 +33,7 @@ export default {
33
33
  for ( const k of keyOptions ) {
34
34
  const sk = simplify(k);
35
35
 
36
- if ( normanSchema || likelyFields.includes(sk) || iffyFields.includes(sk) ) {
36
+ if ( normanSchema?.resourceFields || likelyFields.includes(sk) || iffyFields.includes(sk) ) {
37
37
  keys.push(k);
38
38
  }
39
39
  }
@@ -53,6 +53,7 @@ export default {
53
53
  <div
54
54
  v-if="pref"
55
55
  class="close-button"
56
+ data-testid="graphic-banner-close"
56
57
  @click="hide()"
57
58
  >
58
59
  <i class="icon icon-close" />
@@ -138,6 +138,7 @@ export default {
138
138
  >
139
139
  <div class="wechat-modal">
140
140
  <h1>{{ t('footer.wechat.modalText') }}</h1>
141
+ <h1>{{ t('footer.wechat.modalText2') }}</h1>
141
142
  <div class="qr-img" />
142
143
  <div>
143
144
  <button
@@ -288,7 +288,7 @@ export default {
288
288
  closeError(index) {
289
289
  const errors = this.errors.filter((_, i) => i !== index);
290
290
 
291
- this.$emit('error', errors);
291
+ this.$emit('error', errors, this.errors[index]);
292
292
  },
293
293
 
294
294
  emitOrRoute() {
@@ -252,6 +252,7 @@ export default {
252
252
  if (iframeEl === null) {
253
253
  iframeEl = document.createElement('iframe');
254
254
  iframeEl.setAttribute('id', EMBER_FRAME);
255
+ iframeEl.setAttribute('data-testid', EMBER_FRAME);
255
256
  iframeEl.classList.add(EMBER_FRAME_HIDE_CLASS);
256
257
 
257
258
  if (this.inline) {
@@ -1,5 +1,6 @@
1
1
  <script>
2
- import { Diff2Html } from 'diff2html';
2
+ import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui-slim.js';
3
+
3
4
  import { createPatch } from 'diff';
4
5
 
5
6
  export default {
@@ -38,36 +39,40 @@ export default {
38
39
  }
39
40
  },
40
41
 
41
- computed: {
42
- html() {
43
- const outputFormat = this.sideBySide ? 'side-by-side' : 'line-by-line';
44
- const showFiles = false;
45
- const matching = 'words';
42
+ mounted() {
43
+ this.draw();
44
+ },
45
+
46
+ watch: {
47
+ sideBySide() {
48
+ this.draw();
49
+ }
50
+ },
46
51
 
52
+ methods: {
53
+ draw() {
54
+ const targetElement = document.getElementById('diffElement');
47
55
  const patch = createPatch(
48
56
  this.filename,
49
57
  this.orig,
50
58
  this.neu
51
59
  );
60
+ const configuration = {
61
+ // UI
62
+ synchronisedScroll: true,
52
63
 
53
- const json = Diff2Html.getJsonFromDiff(patch, {
54
- inputFormat: 'diff',
55
- outputFormat,
56
- showFiles,
57
- matching
58
- });
59
-
60
- return Diff2Html.getPrettyHtml(json, {
61
- inputFormat: 'json',
62
- outputFormat,
63
- showFiles,
64
- matching,
65
- synchronizedScroll: true,
66
- });
67
- }
68
- },
64
+ // Base
65
+ outputFormat: this.sideBySide ? 'side-by-side' : 'line-by-line',
66
+ drawFileList: false,
67
+ matching: 'words',
68
+ };
69
+
70
+ const diff2htmlUi = new Diff2HtmlUI(targetElement, patch, configuration);
71
+
72
+ diff2htmlUi.draw();
73
+ this.fit();
74
+ },
69
75
 
70
- methods: {
71
76
  fit() {
72
77
  if ( !this.autoResize ) {
73
78
  return;
@@ -97,8 +102,8 @@ export default {
97
102
  <div>
98
103
  <resize-observer @notify="fit" />
99
104
  <div
105
+ id="diffElement"
100
106
  ref="root"
101
- v-clean-html="html"
102
107
  class="root"
103
108
  />
104
109
  </div>
@@ -112,66 +117,68 @@ export default {
112
117
  }
113
118
  </style>
114
119
 
115
- <style lang="scss">
116
- @import 'node_modules/diff2html/dist/diff2html.min.css';
117
-
118
- .d2h-file-header {
119
- display: none;
120
- }
121
-
122
- .d2h-file-wrapper {
123
- border-color: var(--diff-border);
124
- }
125
-
126
- .d2h-diff-table {
127
- font-family: Menlo,Consolas,monospace;
128
- font-size: 13px;
129
- }
130
-
131
- .d2h-emptyplaceholder, .d2h-code-side-emptyplaceholder {
132
- border-color: var(--diff-linenum-border);
133
- background-color: var(--diff-empty-placeholder);
134
- }
135
-
136
- .d2h-code-linenumber,
137
- .d2h-code-side-linenumber {
138
- background-color: var(--diff-linenum-bg);
139
- color: var(--diff-linenum);
140
- border-color: var(--diff-linenum-border);
141
- border-left: 0;
142
- }
143
-
144
- .d2h-code-line del,.d2h-code-side-line del {
145
- background-color: var(--diff-line-del-bg);
146
- }
147
-
148
- .d2h-code-line ins,.d2h-code-side-line ins {
149
- background-color: var(--diff-line-ins-bg);
150
- }
151
-
152
- .d2h-del {
153
- background-color: var(--diff-del-bg);
154
- border-color: var(--diff-del-border);
155
- color: var(--body-text);
156
- }
157
-
158
- .d2h-ins {
159
- background-color: var(--diff-ins-bg);
160
- border-color: var(--diff-ins-border);
161
- color: var(--body-text);
162
- }
163
-
164
- .d2h-info {
165
- background-color: var(--diff-header-bg);
166
- color: var(--diff-header);
167
- border-color: var(--diff-header-border);
168
- }
169
-
170
- .d2h-file-diff .d2h-del.d2h-change {
171
- background-color: var(--diff-chg-del);
172
- }
173
-
174
- .d2h-file-diff .d2h-ins.d2h-change {
175
- background-color: var(--diff-chg-ins);
120
+ <style scoped lang="scss">
121
+ @import 'node_modules/diff2html/bundles/css/diff2html.min.css';
122
+
123
+ ::v-deep .d2h-wrapper {
124
+ .d2h-file-header {
125
+ display: none;
126
+ }
127
+
128
+ .d2h-file-wrapper {
129
+ border-color: var(--diff-border);
130
+ }
131
+
132
+ .d2h-diff-table {
133
+ font-family: Menlo,Consolas,monospace;
134
+ font-size: 13px;
135
+ }
136
+
137
+ .d2h-emptyplaceholder, .d2h-code-side-emptyplaceholder {
138
+ border-color: var(--diff-linenum-border);
139
+ background-color: var(--diff-empty-placeholder);
140
+ }
141
+
142
+ .d2h-code-linenumber,
143
+ .d2h-code-side-linenumber {
144
+ background-color: var(--diff-linenum-bg);
145
+ color: var(--diff-linenum);
146
+ border-color: var(--diff-linenum-border);
147
+ border-left: 0;
148
+ }
149
+
150
+ .d2h-code-line del,.d2h-code-side-line del {
151
+ background-color: var(--diff-line-del-bg);
152
+ }
153
+
154
+ .d2h-code-line ins,.d2h-code-side-line ins {
155
+ background-color: var(--diff-line-ins-bg);
156
+ }
157
+
158
+ .d2h-del {
159
+ background-color: var(--diff-del-bg);
160
+ border-color: var(--diff-del-border);
161
+ color: var(--body-text);
162
+ }
163
+
164
+ .d2h-ins {
165
+ background-color: var(--diff-ins-bg);
166
+ border-color: var(--diff-ins-border);
167
+ color: var(--body-text);
168
+ }
169
+
170
+ .d2h-info {
171
+ background-color: var(--diff-header-bg);
172
+ color: var(--diff-header);
173
+ border-color: var(--diff-header-border);
174
+ }
175
+
176
+ .d2h-file-diff .d2h-del.d2h-change {
177
+ background-color: var(--diff-chg-del);
178
+ }
179
+
180
+ .d2h-file-diff .d2h-ins.d2h-change {
181
+ background-color: var(--diff-chg-ins);
182
+ }
176
183
  }
177
184
  </style>
@@ -259,10 +259,16 @@ export default {
259
259
  v-if="!loading && !error"
260
260
  class="external-link"
261
261
  >
262
+ <!-- https://github.com/harvester/harvester-installer/pull/512/files -->
263
+ <!-- It is necessary to include the parameter referer when accessing the Grafana page. -->
264
+ <!-- This parameter is required by the backend to identify the origin of the request from which cluster -->
265
+ <!-- The matching mechanism as follows: -->
266
+ <!-- ~.*/k8s/clusters/(c-m-.+)/.* -->
267
+ <!-- ~.*/dashboard/harvester/c/(c-m-.+)/.* -->
262
268
  <a
263
269
  :href="grafanaUrl"
264
270
  target="_blank"
265
- rel="noopener noreferrer nofollow"
271
+ rel="noopener nofollow"
266
272
  >{{ t('grafanaDashboard.grafana') }} <i class="icon icon-external-link" /></a>
267
273
  </div>
268
274
  </div>
@@ -193,6 +193,9 @@ export default {
193
193
  opt: { watch: true }
194
194
  });
195
195
  } catch (e) {
196
+ if (e.status === 404 || e.status === 403) {
197
+ store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceIdNotFound', { resource, fqid }, true)));
198
+ }
196
199
  liveModel = {};
197
200
  notFound = fqid;
198
201
  }
@@ -367,18 +370,7 @@ export default {
367
370
  </script>
368
371
 
369
372
  <template>
370
- <Loading v-if="$fetchState.pending" />
371
- <div v-else-if="notFound">
372
- <IconMessage icon="icon-warning">
373
- <template v-slot:message>
374
- {{ t('generic.notFound') }}
375
- <div>
376
- <div>{{ t('generic.type') }}: {{ resource }}</div>
377
- <div>{{ t('generic.id') }}: {{ notFound }}</div>
378
- </div>
379
- </template>
380
- </IconMessage>
381
- </div>
373
+ <Loading v-if="$fetchState.pending || notFound" />
382
374
  <div v-else>
383
375
  <Masthead
384
376
  v-if="showMasthead"
@@ -70,7 +70,7 @@ export default {
70
70
 
71
71
  if ( !this.hasFetch ) {
72
72
  if ( !schema ) {
73
- store.dispatch('loadingError', new Error(`Type ${ resource } not found, unable to display list`));
73
+ store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceListNotFound', { resource }, true)));
74
74
 
75
75
  return;
76
76
  }
@@ -4,8 +4,9 @@ import { get } from '@shell/utils/object';
4
4
  import { mapPref, GROUP_RESOURCES } from '@shell/store/prefs';
5
5
  import ButtonGroup from '@shell/components/ButtonGroup';
6
6
  import SortableTable from '@shell/components/SortableTable';
7
- import { NAMESPACE } from '@shell/config/table-headers';
7
+ import { NAMESPACE, AGE } from '@shell/config/table-headers';
8
8
  import { findBy } from '@shell/utils/array';
9
+ import { ExtensionPoint, TableColumnLocation } from '@shell/core/types';
9
10
 
10
11
  // Default group-by in the case the group stored in the preference does not apply
11
12
  const DEFAULT_GROUP = 'namespace';
@@ -57,6 +58,12 @@ export default {
57
58
  required: false
58
59
  },
59
60
 
61
+ keyField: {
62
+ // Field that is unique for each row.
63
+ type: String,
64
+ default: '_key',
65
+ },
66
+
60
67
  headers: {
61
68
  type: Array,
62
69
  default: null,
@@ -216,6 +223,7 @@ export default {
216
223
  _headers() {
217
224
  let headers;
218
225
  const showNamespace = this.showNamespaceColumn;
226
+ const type = this.schema?.id || this.$route?.params?.resource || undefined;
219
227
 
220
228
  if ( this.headers ) {
221
229
  headers = this.headers.slice();
@@ -223,6 +231,46 @@ export default {
223
231
  headers = this.$store.getters['type-map/headersFor'](this.schema);
224
232
  }
225
233
 
234
+ // add custom table columns provided by the extensions ExtensionPoint.TABLE_COL hook
235
+ // gate it so that we prevent errors on older versions of dashboard
236
+ if (this.$store.$plugin?.getUIConfig) {
237
+ const extensionCols = this.$store.$plugin.getUIConfig(ExtensionPoint.TABLE_COL, TableColumnLocation.RESOURCE);
238
+
239
+ // Try and insert the columns before the Age column
240
+ let insertPosition = headers.length;
241
+
242
+ if (headers.length > 0) {
243
+ const ageColIndex = headers.findIndex((h) => h.name === AGE.name);
244
+
245
+ if (ageColIndex >= 0) {
246
+ insertPosition = ageColIndex;
247
+ } else {
248
+ // we've found some labels with ' ', which isn't necessarily empty (explore action/button)
249
+ // if we are to add cols, let's push them before these so that the UI doesn't look weird
250
+ const lastViableColIndex = headers.findIndex((h) => (!h.label || !h.label?.trim()) && (!h.labelKey || !h.labelKey?.trim()));
251
+
252
+ if (lastViableColIndex >= 0) {
253
+ insertPosition = lastViableColIndex;
254
+ }
255
+ }
256
+ }
257
+
258
+ // adding extension defined cols to the correct header config
259
+ extensionCols.forEach((col) => {
260
+ if (col.locationConfig.resource) {
261
+ col.locationConfig.resource.forEach((resource) => {
262
+ if (resource && type === resource) {
263
+ // we need the 'value' prop to be populated in order for the rows to show the values
264
+ if (!col.value && col.getValue) {
265
+ col.value = col.getValue;
266
+ }
267
+ headers.splice(insertPosition, 0, col);
268
+ }
269
+ });
270
+ }
271
+ });
272
+ }
273
+
226
274
  // If only one namespace is selected, hide the namespace column
227
275
  if ( !showNamespace ) {
228
276
  const idx = headers.findIndex((header) => header.name === NAMESPACE.name);
@@ -447,7 +495,7 @@ export default {
447
495
  :has-advanced-filtering="hasAdvancedFiltering"
448
496
  :adv-filter-hide-labels-as-cols="advFilterHideLabelsAsCols"
449
497
  :adv-filter-prevent-filtering-labels="advFilterPreventFilteringLabels"
450
- key-field="_key"
498
+ :key-field="keyField"
451
499
  :sort-generation-fn="safeSortGenerationFn"
452
500
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
453
501
  :force-update-live-and-delayed="forceUpdateLiveAndDelayed"
@@ -31,6 +31,7 @@ export default {
31
31
  <div
32
32
  v-if="shown"
33
33
  class="simple-box"
34
+ data-testid="simple-box-container"
34
35
  v-on="$listeners"
35
36
  >
36
37
  <div
@@ -903,7 +903,10 @@ export default {
903
903
  </script>
904
904
 
905
905
  <template>
906
- <div ref="container">
906
+ <div
907
+ ref="container"
908
+ data-testid="cluster-list-container"
909
+ >
907
910
  <div
908
911
  :class="{'titled': $slots.title && $slots.title.length}"
909
912
  class="sortable-table-header"
@@ -1001,6 +1004,7 @@ export default {
1001
1004
  <div
1002
1005
  v-if="search || hasAdvancedFiltering || isTooManyItemsToAutoUpdate || ($slots['header-right'] && $slots['header-right'].length)"
1003
1006
  class="search row"
1007
+ data-testid="search-box-filter-row"
1004
1008
  >
1005
1009
  <ul
1006
1010
  v-if="hasAdvancedFiltering"
@@ -241,6 +241,7 @@ export default {
241
241
  :side-by-side="diffMode === 'split'"
242
242
  :orig="original"
243
243
  :neu="curValue"
244
+ :footer-space="80"
244
245
  />
245
246
  </div>
246
247
  </template>
@@ -668,6 +668,7 @@ export default {
668
668
  :value="getRule('resources', props.row.value)"
669
669
  :disabled="isBuiltin"
670
670
  :options="resourceOptions"
671
+ option-key="optionKey"
671
672
  :searchable="true"
672
673
  :taggable="true"
673
674
  :mode="mode"
@@ -357,7 +357,7 @@ export default Vue.extend<Data, any, any, any>({
357
357
  <div class="spacer">
358
358
  <LabeledInput
359
359
  v-model="selectedAccOrOrg"
360
- data-testid="epinio_app-source_git-username"
360
+ data-testid="git_picker-username-or-org"
361
361
  :tooltip="t(`gitPicker.${ type }.username.tooltip`)"
362
362
  :label="t(`gitPicker.${ type }.username.inputLabel`)"
363
363
  :required="true"
@@ -8,16 +8,7 @@ import { DESCRIPTION } from '@shell/config/labels-annotations';
8
8
  import { _VIEW, _EDIT, _CREATE } from '@shell/config/query-params';
9
9
  import { LabeledInput } from '@components/Form/LabeledInput';
10
10
  import LabeledSelect from '@shell/components/form/LabeledSelect';
11
-
12
- export function normalizeName(str) {
13
- return (str || '')
14
- .trim()
15
- .toLowerCase()
16
- .replace(/\s+/g, '-')
17
- .replace(/-+/g, '-')
18
- .replace(/^-+/, '')
19
- .replace(/-+$/, '');
20
- }
11
+ import { normalizeName } from '@shell/utils/kube';
21
12
 
22
13
  export default {
23
14
  name: 'NameNsDescription',
@@ -101,10 +92,20 @@ export default {
101
92
  type: Boolean,
102
93
  default: false
103
94
  },
95
+ /**
96
+ * Use these objects instead of namespaces
97
+ */
104
98
  namespacesOverride: {
105
99
  type: Array,
106
100
  default: null,
107
101
  },
102
+ /**
103
+ * User these namespaces instead of determining list within component
104
+ */
105
+ namespaceOptions: {
106
+ type: Array,
107
+ default: null,
108
+ },
108
109
  createNamespaceOverride: {
109
110
  type: Boolean,
110
111
  default: false,
@@ -228,8 +229,23 @@ export default {
228
229
  * Map namespaces from the store to options, adding divider and create button
229
230
  */
230
231
  options() {
231
- const namespaces = this.namespacesOverride ||
232
- (Object.keys(this.isCreate ? this.allowedNamespaces() : this.namespaces()));
232
+ let namespaces;
233
+
234
+ if (this.namespacesOverride) {
235
+ // Use the resources provided
236
+ namespaces = this.namespacesOverride;
237
+ } else {
238
+ if (this.namespaceOptions) {
239
+ // Use the namespaces provided
240
+ namespaces = (this.namespaceOptions.map((ns) => ns.name) || []).sort();
241
+ } else {
242
+ // Determine the namespaces
243
+ const namespaceObjs = this.isCreate ? this.allowedNamespaces() : this.namespaces();
244
+
245
+ namespaces = Object.keys(namespaceObjs);
246
+ }
247
+ }
248
+
233
249
  const options = namespaces
234
250
  .map((namespace) => ({ nameDisplay: namespace, id: namespace }))
235
251
  .map(this.namespaceMapper || ((obj) => ({
@@ -156,7 +156,7 @@ export default {
156
156
  },
157
157
 
158
158
  priorityDisplay(term) {
159
- return term.weight ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
159
+ return 'weight' in term ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
160
160
  },
161
161
 
162
162
  updateExpressions(row, expressions) {
@@ -212,7 +212,7 @@ export default {
212
212
  />
213
213
  </div>
214
214
  <div
215
- v-if="props.row.value.weight"
215
+ v-if="'weight' in props.row.value"
216
216
  class="col span-3"
217
217
  >
218
218
  <LabeledInput
@@ -286,7 +286,7 @@ export default {
286
286
  },
287
287
 
288
288
  priorityDisplay(term) {
289
- return term.weight ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
289
+ return 'weight' in term ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
290
290
  },
291
291
 
292
292
  changeNamespaceMode(val, term, idx) {
@@ -340,6 +340,11 @@ export default {
340
340
  this.queueUpdate();
341
341
  },
342
342
 
343
+ updateLabelSelector(e, props) {
344
+ this.set(props.row.value, 'labelSelector.matchExpressions', e);
345
+ this.queueUpdate();
346
+ },
347
+
343
348
  isEmpty,
344
349
  get,
345
350
  set
@@ -431,7 +436,7 @@ export default {
431
436
  :value="get(props.row.value, 'labelSelector.matchExpressions')"
432
437
  :show-remove="false"
433
438
  :data-testid="`pod-affinity-expressions-index${props.i}`"
434
- @input="e=>set(props.row.value, 'labelSelector.matchExpressions', e)"
439
+ @input="e=>updateLabelSelector(e, props)"
435
440
  />
436
441
  <div class="row mt-20">
437
442
  <div class="col span-9">
@@ -463,7 +468,7 @@ export default {
463
468
  />
464
469
  </div>
465
470
  <div
466
- v-if="props.row.value.weight"
471
+ v-if="'weight' in props.row.value"
467
472
  class="col span-3"
468
473
  >
469
474
  <LabeledInput
@@ -13,6 +13,7 @@ import { _VIEW } from '@shell/config/query-params';
13
13
  import RelatedResources from '@shell/components/RelatedResources';
14
14
  import { ExtensionPoint, TabLocation } from '@shell/core/types';
15
15
  import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
16
+ import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
16
17
 
17
18
  export default {
18
19
 
@@ -60,6 +61,11 @@ export default {
60
61
  needRelated: {
61
62
  type: Boolean,
62
63
  default: true
64
+ },
65
+
66
+ extensionParams: {
67
+ type: Object,
68
+ default: null
63
69
  }
64
70
  },
65
71
 
@@ -71,7 +77,7 @@ export default {
71
77
  allEvents: [],
72
78
  selectedTab: this.defaultTab,
73
79
  didLoadEvents: false,
74
- extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route),
80
+ extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route, this, this.extensionParams),
75
81
  };
76
82
  },
77
83
 
@@ -140,7 +146,7 @@ export default {
140
146
  },
141
147
  conditionsHaveIssues() {
142
148
  if (this.showConditions) {
143
- return this.value.status?.conditions?.some((cond) => cond.error);
149
+ return this.value.status?.conditions?.filter((cond) => !isConditionReadyAndWaiting(cond)).some((cond) => cond.error);
144
150
  }
145
151
 
146
152
  return false;
@@ -134,11 +134,27 @@ export default {
134
134
 
135
135
  return true;
136
136
  },
137
+ /**
138
+ * Get a unique value to represent the option
139
+ */
137
140
  getOptionKey(opt) {
141
+ // Use the property from a component level key
142
+ if (opt && this.optionKey) {
143
+ return get(opt, this.optionKey);
144
+ }
145
+
146
+ // Use the property from an option level key
147
+ // This doesn't seem right, think it was meant to represent the actual option key... rather than the key to find the option key
148
+ // This approach also doesn't appear in LabeledSelect
138
149
  if (opt?.optionKey) {
150
+ // opt.optionKey should in theory be optionKeyKey
139
151
  return get(opt, opt.optionKey);
140
152
  }
141
153
 
154
+ // There's no configuration to help us get a sensible key. Fall back on ..
155
+ // - the label
156
+ // - something random
157
+
142
158
  const label = this.getOptionLabel(opt);
143
159
 
144
160
  // label may be type of object