@rancher/shell 0.3.16 → 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 (144) hide show
  1. package/assets/images/wechat-qr-code.jpg +0 -0
  2. package/assets/translations/en-us.yaml +69 -14
  3. package/assets/translations/zh-hans.yaml +87 -7
  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/components/CommunityLinks.vue +1 -0
  8. package/components/FileDiff.vue +92 -85
  9. package/components/ResourceDetail/index.vue +4 -12
  10. package/components/ResourceList/index.vue +1 -1
  11. package/components/ResourceTable.vue +50 -2
  12. package/components/YamlEditor.vue +1 -0
  13. package/components/auth/RoleDetailEdit.vue +1 -0
  14. package/components/form/NameNsDescription.vue +28 -12
  15. package/components/form/NodeAffinity.vue +2 -2
  16. package/components/form/PodAffinity.vue +2 -2
  17. package/components/form/ResourceTabs/index.vue +8 -2
  18. package/components/form/Select.vue +16 -0
  19. package/components/form/__tests__/NodeAffinity.test.ts +38 -0
  20. package/components/form/__tests__/PodAffinity.test.ts +46 -0
  21. package/components/formatter/ClusterLink.vue +8 -4
  22. package/components/formatter/ImageName.vue +23 -0
  23. package/components/formatter/PodImages.vue +7 -1
  24. package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
  25. package/components/nav/Header.vue +2 -2
  26. package/config/__test__/home-links.test.ts +62 -0
  27. package/config/home-links.js +15 -3
  28. package/config/labels-annotations.js +5 -1
  29. package/config/router.js +0 -4
  30. package/config/settings.ts +4 -0
  31. package/config/table-headers.js +6 -5
  32. package/config/uiplugins.js +50 -5
  33. package/core/plugin-helpers.js +20 -12
  34. package/core/plugin.ts +9 -0
  35. package/core/plugins.js +1 -1
  36. package/core/types-provisioning.ts +253 -0
  37. package/core/types.ts +17 -3
  38. package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
  39. package/detail/node.vue +6 -6
  40. package/detail/pod.vue +2 -6
  41. package/detail/provisioning.cattle.io.cluster.vue +46 -7
  42. package/detail/workload/index.vue +9 -9
  43. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
  44. package/edit/auth/github.vue +1 -0
  45. package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
  46. package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
  47. package/edit/fleet.cattle.io.gitrepo.vue +18 -1
  48. package/edit/namespace.vue +9 -1
  49. package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
  50. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
  51. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
  52. package/edit/provisioning.cattle.io.cluster/index.vue +52 -0
  53. package/edit/provisioning.cattle.io.cluster/rke2.vue +330 -150
  54. package/edit/ui.cattle.io.navlink.vue +2 -1
  55. package/initialize/App.js +3 -13
  56. package/initialize/layouts.ts +26 -0
  57. package/list/provisioning.cattle.io.cluster.vue +8 -1
  58. package/middleware/authenticated.js +93 -5
  59. package/mixins/brand.js +39 -3
  60. package/mixins/child-hook.js +2 -2
  61. package/mixins/create-edit-view/impl.js +2 -2
  62. package/models/fleet.cattle.io.gitrepo.js +1 -0
  63. package/models/provisioning.cattle.io.cluster.js +9 -1
  64. package/package.json +2 -2
  65. package/pages/about.vue +8 -2
  66. package/pages/auth/login.vue +1 -1
  67. package/pages/auth/logout.vue +11 -3
  68. package/pages/c/_cluster/apps/charts/index.vue +5 -2
  69. package/pages/c/_cluster/apps/charts/install.vue +5 -0
  70. package/pages/c/_cluster/explorer/index.vue +1 -10
  71. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
  72. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
  73. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
  74. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
  75. package/pages/c/_cluster/uiplugins/index.vue +155 -44
  76. package/pages/docs/_doc.vue +9 -3
  77. package/pages/home.vue +5 -5
  78. package/pages/support/index.vue +10 -4
  79. package/pkg/auto-import.js +1 -1
  80. package/plugins/clean-tooltip-directive.js +1 -1
  81. package/plugins/dashboard-store/resource-class.js +35 -2
  82. package/plugins/plugin.js +9 -1
  83. package/rancher-components/BadgeState/BadgeState.vue +5 -1
  84. package/rancher-components/Banner/Banner.test.ts +51 -1
  85. package/rancher-components/Banner/Banner.vue +134 -53
  86. package/rancher-components/Card/Card.vue +24 -7
  87. package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
  88. package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
  89. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
  90. package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
  91. package/rancher-components/Form/Radio/RadioButton.vue +30 -13
  92. package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
  93. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
  94. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
  95. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
  96. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
  97. package/rancher-components/StringList/StringList.test.ts +453 -49
  98. package/rancher-components/StringList/StringList.vue +44 -26
  99. package/scripts/extension/publish +2 -2
  100. package/scripts/typegen.sh +1 -0
  101. package/server/server-middleware.js +4 -12
  102. package/store/index.js +13 -0
  103. package/store/prefs.js +0 -3
  104. package/store/type-map.js +17 -29
  105. package/types/shell/index.d.ts +236 -83
  106. package/utils/kube.js +9 -0
  107. package/utils/object.js +27 -0
  108. package/utils/settings.ts +2 -2
  109. package/vue.config.js +3 -2
  110. package/components/.DS_Store +0 -0
  111. package/components/__tests__/.DS_Store +0 -0
  112. package/creators/pkg/package-lock.json +0 -37
  113. package/pages/safeMode.vue +0 -17
  114. package/rancher-components/components/BadgeState/BadgeState.spec.ts +0 -12
  115. package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
  116. package/rancher-components/components/BadgeState/index.ts +0 -1
  117. package/rancher-components/components/Banner/Banner.test.ts +0 -63
  118. package/rancher-components/components/Banner/Banner.vue +0 -244
  119. package/rancher-components/components/Banner/index.ts +0 -1
  120. package/rancher-components/components/Card/Card.vue +0 -167
  121. package/rancher-components/components/Card/index.ts +0 -1
  122. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
  123. package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -420
  124. package/rancher-components/components/Form/Checkbox/index.ts +0 -1
  125. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -23
  126. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -355
  127. package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
  128. package/rancher-components/components/Form/Radio/RadioButton.vue +0 -287
  129. package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -254
  130. package/rancher-components/components/Form/Radio/index.ts +0 -2
  131. package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -170
  132. package/rancher-components/components/Form/TextArea/index.ts +0 -1
  133. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
  134. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -149
  135. package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
  136. package/rancher-components/components/Form/index.ts +0 -5
  137. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -151
  138. package/rancher-components/components/LabeledTooltip/index.ts +0 -1
  139. package/rancher-components/components/StringList/StringList.test.ts +0 -484
  140. package/rancher-components/components/StringList/StringList.vue +0 -611
  141. package/rancher-components/components/StringList/index.ts +0 -1
  142. package/yarn-error.log +0 -196
  143. /package/rancher-components/{components/Card → Card}/Card.test.ts +0 -0
  144. /package/rancher-components/{components/Form → Form}/Radio/RadioButton.test.ts +0 -0
@@ -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>
@@ -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"
@@ -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"
@@ -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) {
@@ -468,7 +468,7 @@ export default {
468
468
  />
469
469
  </div>
470
470
  <div
471
- v-if="props.row.value.weight"
471
+ v-if="'weight' in props.row.value"
472
472
  class="col span-3"
473
473
  >
474
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
@@ -0,0 +1,38 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import NodeAffinity from '@shell/components/form/NodeAffinity.vue';
3
+ import { _CREATE } from '@shell/config/query-params';
4
+
5
+ describe('component: NodeAffinity', () => {
6
+ it('should display the weight input when the priority is preferred', () => {
7
+ const nodeAffinity = {
8
+ preferredDuringSchedulingIgnoredDuringExecution: [{
9
+ preference: { matchExpressions: [] },
10
+ weight: 1
11
+ }],
12
+ requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [{ matchExpressions: [] }] }
13
+ };
14
+ const wrapper = mount(NodeAffinity, { propsData: { mode: _CREATE, value: nodeAffinity } });
15
+
16
+ expect(wrapper.find('[data-testid="node-affinity-weight-index0"]').exists()).toBeTruthy();
17
+ expect(wrapper.find('[data-testid="node-affinity-weight-index1"]').exists()).toBeFalsy();
18
+ });
19
+
20
+ it('should display the weight input when the value is cleared', async() => {
21
+ const nodeAffinity = {
22
+ preferredDuringSchedulingIgnoredDuringExecution: [{
23
+ preference: { matchExpressions: [] },
24
+ weight: 1
25
+ }],
26
+ };
27
+
28
+ const wrapper = mount(NodeAffinity, { propsData: { mode: _CREATE, value: nodeAffinity } });
29
+
30
+ const weightInput = wrapper.find('[data-testid="node-affinity-weight-index0"]');
31
+
32
+ weightInput.setValue('');
33
+
34
+ await wrapper.vm.$nextTick();
35
+
36
+ expect(wrapper.find('[data-testid="node-affinity-weight-index0"]').exists()).toBeTruthy();
37
+ });
38
+ });
@@ -0,0 +1,46 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import PodAffinity from '@shell/components/form/PodAffinity.vue';
3
+ import { _CREATE } from '@shell/config/query-params';
4
+
5
+ describe('component: PodAffinity', () => {
6
+ it('should display the weight input when the priority is preferred', () => {
7
+ const podAffinity = {
8
+ preferredDuringSchedulingIgnoredDuringExecution: [{
9
+ podAffinityTerm: { topologyKey: 'test topology key 1' },
10
+ weight: 1
11
+ }],
12
+ requiredDuringSchedulingIgnoredDuringExecution: [{ topologyKey: 'test topology key 2' }]
13
+ };
14
+ const wrapper = mount(PodAffinity, {
15
+ propsData: {
16
+ mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
17
+ }
18
+ });
19
+
20
+ expect(wrapper.find('[data-testid="pod-affinity-weight-index0"]').exists()).toBeTruthy();
21
+ expect(wrapper.find('[data-testid="pod-affinity-weight-index1"]').exists()).toBeFalsy();
22
+ });
23
+
24
+ it('should display the weight input when the value is cleared', async() => {
25
+ const podAffinity = {
26
+ preferredDuringSchedulingIgnoredDuringExecution: [{
27
+ podAffinityTerm: { topologyKey: 'test topology key 1' },
28
+ weight: 1
29
+ }],
30
+ };
31
+
32
+ const wrapper = mount(PodAffinity, {
33
+ propsData: {
34
+ mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
35
+ }
36
+ });
37
+
38
+ const weightInput = wrapper.find('[data-testid="pod-affinity-weight-index0"]');
39
+
40
+ weightInput.setValue('');
41
+
42
+ await wrapper.vm.$nextTick();
43
+
44
+ expect(wrapper.find('[data-testid="pod-affinity-weight-index0"]').exists()).toBeTruthy();
45
+ });
46
+ });
@@ -1,5 +1,6 @@
1
1
  <script>
2
2
  import { get } from '@shell/utils/object';
3
+ import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
3
4
 
4
5
  export default {
5
6
  props: {
@@ -27,10 +28,10 @@ export default {
27
28
 
28
29
  statusErrorConditions() {
29
30
  if (this.row.hasError) {
30
- return this.row?.status.conditions.filter((condition) => condition.error === true);
31
+ return this.row?.status.conditions.filter((cond) => cond.error === true && !isConditionReadyAndWaiting(cond));
31
32
  }
32
33
 
33
- return false;
34
+ return [];
34
35
  },
35
36
 
36
37
  formattedConditions() {
@@ -46,7 +47,7 @@ export default {
46
47
  return formattedTooltip.toString().replaceAll(',', '');
47
48
  }
48
49
 
49
- return false;
50
+ return '';
50
51
  },
51
52
  },
52
53
 
@@ -66,16 +67,19 @@ export default {
66
67
  v-if="row.unavailableMachines"
67
68
  v-clean-tooltip="row.unavailableMachines"
68
69
  class="conditions-alert-icon icon-alert icon"
70
+ data-testid="unavailable-machines-alert-icon"
69
71
  />
70
72
  <i
71
73
  v-if="row.rkeTemplateUpgrade"
72
74
  v-clean-tooltip="t('cluster.rkeTemplateUpgrade', { name: row.rkeTemplateUpgrade })"
73
75
  class="template-upgrade-icon icon-alert icon"
76
+ data-testid="rke-template-upgrade-alert-icon"
74
77
  />
75
78
  <i
76
- v-if="row.hasError"
79
+ v-if="row.hasError && statusErrorConditions.length > 0"
77
80
  v-clean-tooltip="{ content: `<div>${formattedConditions}</div>`, html: true }"
78
81
  class="conditions-alert-icon icon-error icon-lg"
82
+ data-testid="conditions-has-error-icon"
79
83
  />
80
84
  </span>
81
85
  </template>