@rancher/shell 3.0.11 → 3.0.12-rc.2

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 (219) hide show
  1. package/assets/images/providers/entraid-black.svg +4 -0
  2. package/assets/images/providers/entraid.svg +9 -0
  3. package/assets/images/vendor/entraid.svg +9 -0
  4. package/assets/styles/app.scss +0 -1
  5. package/assets/styles/base/_mixins.scss +31 -0
  6. package/assets/styles/base/_variables.scss +2 -0
  7. package/assets/styles/themes/_modern.scss +6 -5
  8. package/assets/translations/en-us.yaml +24 -21
  9. package/assets/translations/zh-hans.yaml +4 -11
  10. package/chart/__tests__/S3.test.ts +10 -3
  11. package/components/CountBox.vue +20 -0
  12. package/components/CreateDriver.vue +0 -12
  13. package/components/DetailText.vue +12 -3
  14. package/components/EmptyProductPage.vue +76 -0
  15. package/components/Resource/Detail/CopyToClipboard.vue +1 -2
  16. package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
  17. package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
  18. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
  19. package/components/Resource/Detail/TitleBar/index.vue +1 -1
  20. package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
  21. package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
  22. package/components/Resource/Detail/ViewOptions/index.vue +2 -1
  23. package/components/ResourceList/Masthead.vue +25 -2
  24. package/components/SelectIconGrid.vue +5 -0
  25. package/components/SideNav.vue +13 -0
  26. package/components/__tests__/CountBox.test.ts +72 -0
  27. package/components/__tests__/DetailText.test.ts +113 -0
  28. package/components/__tests__/PromptModal.test.ts +2 -0
  29. package/components/fleet/FleetClusterTargets/index.vue +18 -1
  30. package/components/fleet/FleetClusters.vue +1 -0
  31. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  32. package/components/form/InputWithSelect.vue +18 -10
  33. package/components/form/KeyValue.vue +17 -1
  34. package/components/form/LabeledSelect.vue +82 -24
  35. package/components/form/NodeScheduling.vue +17 -3
  36. package/components/form/PrivateRegistry.vue +69 -0
  37. package/components/form/Select.vue +73 -56
  38. package/components/form/ServiceNameSelect.vue +13 -11
  39. package/components/form/__tests__/KeyValue.test.ts +66 -0
  40. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  41. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  42. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  43. package/components/formatter/WorkloadHealthScale.vue +3 -1
  44. package/components/nav/Group.vue +33 -9
  45. package/components/nav/Header.vue +56 -10
  46. package/components/nav/NotificationCenter/Notification.vue +4 -1
  47. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  48. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  49. package/components/nav/TopLevelMenu.vue +15 -1
  50. package/components/nav/Type.vue +8 -7
  51. package/components/nav/WindowManager/index.vue +2 -1
  52. package/components/nav/WorkspaceSwitcher.vue +13 -0
  53. package/components/nav/__tests__/Group.test.ts +67 -0
  54. package/components/nav/__tests__/Header.test.ts +235 -0
  55. package/components/nav/__tests__/Type.test.ts +20 -3
  56. package/components/templates/default.vue +34 -4
  57. package/components/templates/home.vue +12 -25
  58. package/components/templates/plain.vue +13 -26
  59. package/composables/useLabeledFormElement.ts +10 -2
  60. package/composables/useLabeledSelect.ts +60 -0
  61. package/composables/useUserRetentionValidation.ts +1 -49
  62. package/config/cookies.js +0 -1
  63. package/config/labels-annotations.js +1 -0
  64. package/config/pagination-table-headers.js +8 -1
  65. package/config/product/apps.js +2 -1
  66. package/config/product/auth.js +1 -0
  67. package/config/product/backup.js +1 -0
  68. package/config/product/compliance.js +1 -1
  69. package/config/product/explorer.js +25 -6
  70. package/config/product/fleet.js +1 -0
  71. package/config/product/gatekeeper.js +1 -0
  72. package/config/product/istio.js +1 -0
  73. package/config/product/logging.js +1 -0
  74. package/config/product/longhorn.js +2 -1
  75. package/config/product/manager.js +1 -0
  76. package/config/product/monitoring.js +1 -0
  77. package/config/product/navlinks.js +1 -0
  78. package/config/product/neuvector.js +2 -1
  79. package/config/product/settings.js +1 -0
  80. package/config/product/uiplugins.js +1 -0
  81. package/config/query-params.js +1 -0
  82. package/config/router/routes.js +0 -8
  83. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  84. package/core/__tests__/plugin-products.test.ts +3810 -0
  85. package/core/extension-manager-impl.js +30 -1
  86. package/core/plugin-products-base.ts +392 -0
  87. package/core/plugin-products-extending.ts +44 -0
  88. package/core/plugin-products-helpers.ts +263 -0
  89. package/core/plugin-products-top-level.ts +66 -0
  90. package/core/plugin-products-type-guards.ts +33 -0
  91. package/core/plugin-products.ts +50 -0
  92. package/core/plugin-types.ts +237 -0
  93. package/core/plugin.ts +45 -10
  94. package/core/productDebugger.js +48 -0
  95. package/core/types.ts +97 -11
  96. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  97. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  98. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  99. package/detail/fleet.cattle.io.bundle.vue +21 -34
  100. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  101. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  102. package/dialog/InstallExtensionDialog.vue +6 -27
  103. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  104. package/dialog/UninstallExtensionDialog.vue +4 -26
  105. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  106. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  107. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  108. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  109. package/edit/__tests__/nodeDriver.test.ts +5 -11
  110. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  111. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  112. package/edit/auth/__tests__/oidc.test.ts +54 -0
  113. package/edit/auth/azuread.vue +1 -1
  114. package/edit/auth/oidc.vue +8 -0
  115. package/edit/kontainerDriver.vue +1 -2
  116. package/edit/nodeDriver.vue +0 -2
  117. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  119. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  120. package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  122. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  124. package/initialize/App.vue +29 -2
  125. package/initialize/install-plugins.js +0 -2
  126. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  127. package/list/catalog.cattle.io.app.vue +25 -5
  128. package/list/management.cattle.io.feature.vue +1 -1
  129. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  130. package/list/provisioning.cattle.io.cluster.vue +0 -1
  131. package/list/workload.vue +11 -4
  132. package/machine-config/amazonec2.vue +1 -0
  133. package/mixins/chart.js +40 -9
  134. package/mixins/resource-fetch.js +12 -3
  135. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  136. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  137. package/models/__tests__/chart.test.ts +99 -6
  138. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  139. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  140. package/models/catalog.cattle.io.app.js +21 -17
  141. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  142. package/models/chart.js +33 -19
  143. package/models/fleet-application.js +1 -1
  144. package/models/fleet.cattle.io.bundle.js +1 -1
  145. package/models/kontainerdriver.js +11 -0
  146. package/models/management.cattle.io.authconfig.js +5 -1
  147. package/models/management.cattle.io.cluster.js +0 -53
  148. package/models/management.cattle.io.feature.js +3 -3
  149. package/models/management.cattle.io.kontainerdriver.js +1 -26
  150. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  151. package/models/nodedriver.js +7 -0
  152. package/models/pod.js +18 -0
  153. package/models/workload.js +20 -2
  154. package/package.json +13 -13
  155. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  156. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
  157. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  158. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  159. package/pages/c/_cluster/apps/charts/chart.vue +217 -33
  160. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  161. package/pages/c/_cluster/apps/charts/install.vue +8 -3
  162. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  163. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  164. package/pages/c/_cluster/settings/brand.vue +4 -4
  165. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
  166. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  167. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
  168. package/pages/c/_cluster/uiplugins/index.vue +166 -62
  169. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  170. package/plugins/dashboard-store/actions.js +3 -2
  171. package/plugins/dashboard-store/resource-class.js +62 -6
  172. package/plugins/plugin.js +16 -0
  173. package/plugins/steve/steve-pagination-utils.ts +7 -0
  174. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
  175. package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
  176. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  177. package/scripts/test-plugins-build.sh +5 -2
  178. package/scripts/typegen.sh +13 -1
  179. package/server/server-middleware.js +2 -2
  180. package/static/humans.txt +1 -0
  181. package/static/robots.txt +34 -0
  182. package/static/welcome-cow.svg +18 -0
  183. package/store/__tests__/catalog.test.ts +161 -11
  184. package/store/__tests__/type-map.test.ts +84 -24
  185. package/store/auth.js +0 -3
  186. package/store/catalog.js +60 -8
  187. package/store/type-map.js +42 -3
  188. package/tsconfig.paths.json +1 -0
  189. package/types/resources/pod.ts +18 -0
  190. package/types/shell/index.d.ts +8539 -2938
  191. package/types/store/dashboard-store.types.ts +5 -0
  192. package/types/store/pagination.types.ts +6 -0
  193. package/utils/__tests__/git.test.ts +270 -0
  194. package/utils/__tests__/inactivity.test.ts +316 -0
  195. package/utils/__tests__/object.test.ts +77 -0
  196. package/utils/__tests__/time.test.ts +14 -1
  197. package/utils/__tests__/url.test.ts +246 -0
  198. package/utils/axios.js +1 -4
  199. package/utils/dynamic-importer.js +3 -2
  200. package/utils/object.js +33 -2
  201. package/utils/pagination-utils.ts +1 -1
  202. package/utils/time.ts +5 -0
  203. package/utils/uiplugins.ts +12 -16
  204. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  205. package/utils/validators/private-registry.ts +28 -0
  206. package/vue.config.js +0 -9
  207. package/assets/images/providers/azuread-black.svg +0 -22
  208. package/assets/images/providers/azuread.svg +0 -25
  209. package/assets/images/vendor/azuread.svg +0 -18
  210. package/assets/styles/fonts/_dots.scss +0 -18
  211. package/components/EmberPage.vue +0 -622
  212. package/components/EmberPageView.vue +0 -39
  213. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  214. package/mixins/labeled-form-element.ts +0 -225
  215. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  216. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  217. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  218. package/plugins/ember-cookie.js +0 -17
  219. package/utils/ember-page.js +0 -30
@@ -1,16 +1,17 @@
1
1
  <script>
2
2
  import CompactInput from '@shell/mixins/compact-input';
3
- import LabeledFormElement from '@shell/mixins/labeled-form-element';
4
3
  import { get } from '@shell/utils/object';
5
4
  import { LabeledTooltip } from '@components/LabeledTooltip';
6
5
  import VueSelectOverrides from '@shell/mixins/vue-select-overrides';
7
6
  import { calculatePosition } from '@shell/utils/select';
8
7
  import { generateRandomAlphaString } from '@shell/utils/string';
9
- import LabeledSelectPagination from '@shell/components/form/labeled-select-utils/labeled-select-pagination';
8
+ import { useLabeledSelectPagination, labeledSelectPaginationProps } from '@shell/components/form/labeled-select-utils/useLabeledSelectPagination';
10
9
  import { LABEL_SELECT_NOT_OPTION_KINDS } from '@shell/types/components/labeledSelect';
11
10
  import { mapGetters } from 'vuex';
12
11
  import { _VIEW } from '@shell/config/query-params';
13
12
  import { useClickOutside } from '@shell/composables/useClickOutside';
13
+ import { useLabeledFormElement, labeledFormElementProps } from '@shell/composables/useLabeledFormElement';
14
+ import { useLabeledSelect } from '@shell/composables/useLabeledSelect';
14
15
  import { ref } from 'vue';
15
16
 
16
17
  export default {
@@ -21,14 +22,18 @@ export default {
21
22
  components: { LabeledTooltip },
22
23
  mixins: [
23
24
  CompactInput,
24
- LabeledFormElement,
25
25
  VueSelectOverrides,
26
- LabeledSelectPagination
27
26
  ],
28
27
 
29
- emits: ['on-open', 'on-close', 'selecting', 'deselecting', 'search', 'update:validation', 'update:value'],
28
+ emits: ['on-open', 'on-close', 'on-focus', 'on-blur', 'selecting', 'deselecting', 'search', 'update:validation', 'update:value'],
30
29
 
31
30
  props: {
31
+ ...labeledFormElementProps,
32
+ ...labeledSelectPaginationProps,
33
+ value: {
34
+ default: null,
35
+ type: [String, Object, Number, Array, Boolean]
36
+ },
32
37
  appendToBody: {
33
38
  default: true,
34
39
  type: Boolean,
@@ -37,14 +42,6 @@ export default {
37
42
  default: false,
38
43
  type: Boolean
39
44
  },
40
- disabled: {
41
- default: false,
42
- type: Boolean
43
- },
44
- required: {
45
- default: false,
46
- type: Boolean
47
- },
48
45
  hoverTooltip: {
49
46
  default: true,
50
47
  type: Boolean
@@ -99,14 +96,18 @@ export default {
99
96
  default: null,
100
97
  type: [String, Object]
101
98
  },
102
- value: {
103
- default: null,
104
- type: [String, Object, Number, Array, Boolean]
105
- },
106
99
  options: {
107
100
  type: Array,
108
101
  default: () => ([])
109
102
  },
103
+ searchable: {
104
+ default: false,
105
+ type: Boolean
106
+ },
107
+ filterable: {
108
+ default: true,
109
+ type: Boolean
110
+ },
110
111
  closeOnSelect: {
111
112
  type: Boolean,
112
113
  default: true
@@ -117,7 +118,7 @@ export default {
117
118
  }
118
119
  },
119
120
 
120
- setup() {
121
+ setup(props, { emit }) {
121
122
  const select = ref(null);
122
123
  const isOpen = ref(false);
123
124
 
@@ -125,7 +126,67 @@ export default {
125
126
  isOpen.value = false;
126
127
  });
127
128
 
128
- return { isOpen, select };
129
+ const {
130
+ raised,
131
+ focused,
132
+ blurred,
133
+ empty,
134
+ isView,
135
+ onFocusLabeled,
136
+ onBlurLabeled,
137
+ isDisabled,
138
+ validationMessage,
139
+ requiredField
140
+ } = useLabeledFormElement(props, emit);
141
+
142
+ const {
143
+ canPaginate,
144
+ canLoadMore,
145
+ optionCounts,
146
+ _options,
147
+ pages,
148
+ totalResults,
149
+ paginating,
150
+ loadMore,
151
+ setPaginationFilter,
152
+ } = useLabeledSelectPagination(props);
153
+
154
+ const {
155
+ isSearchable,
156
+ isFilterable,
157
+ resizeHandler: resizeHandlerFn
158
+ } = useLabeledSelect(props, canPaginate);
159
+
160
+ const resizeHandler = () => {
161
+ resizeHandlerFn(select);
162
+ };
163
+
164
+ return {
165
+ isOpen,
166
+ select,
167
+ raised,
168
+ focused,
169
+ blurred,
170
+ empty,
171
+ isView,
172
+ onFocusLabeled,
173
+ onBlurLabeled,
174
+ isDisabled,
175
+ validationMessage,
176
+ requiredField,
177
+ isSearchable,
178
+ isFilterable,
179
+ resizeHandler,
180
+ canPaginate,
181
+ canLoadMore,
182
+ optionCounts,
183
+ _options,
184
+ pages,
185
+ totalResults,
186
+ paginating,
187
+ loadMore,
188
+ setPaginationFilter,
189
+ };
129
190
  },
130
191
 
131
192
  data() {
@@ -148,11 +209,6 @@ export default {
148
209
  return this.canPaginate ? !!this._options.find((o) => o.kind === 'group' && !!o.icon) : false;
149
210
  },
150
211
 
151
- _options() {
152
- // If we're paginated show the page as provided by `paginate`. See label-select-pagination mixin
153
- return this.canPaginate ? this.page : this.options;
154
- },
155
-
156
212
  filteredAttrs() {
157
213
  const {
158
214
  class: _class,
@@ -207,11 +263,13 @@ export default {
207
263
  },
208
264
 
209
265
  onFocus() {
266
+ this.$emit('on-focus');
210
267
  this.selectedVisibility = 'hidden';
211
268
  this.onFocusLabeled();
212
269
  },
213
270
 
214
271
  onBlur() {
272
+ this.$emit('on-blur');
215
273
  this.selectedVisibility = 'visible';
216
274
  this.onBlurLabeled();
217
275
  },
@@ -2,6 +2,7 @@
2
2
  import { mapGetters } from 'vuex';
3
3
  import { RadioGroup } from '@components/Form/Radio';
4
4
  import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
5
+ import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
5
6
  import NodeAffinity from '@shell/components/form/NodeAffinity.vue';
6
7
  import { HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
7
8
  import { _VIEW } from '@shell/config/query-params';
@@ -18,6 +19,7 @@ const parseNode = (node: string | KubeNode) => typeof node === 'string' ? node :
18
19
  export default {
19
20
  components: {
20
21
  RadioGroup,
22
+ LabeledSelect,
21
23
  ResourceLabeledSelect,
22
24
  NodeAffinity,
23
25
  },
@@ -35,7 +37,7 @@ export default {
35
37
  */
36
38
  nodes: {
37
39
  type: Array,
38
- default: () => []
40
+ default: () => null
39
41
  },
40
42
 
41
43
  mode: {
@@ -218,14 +220,14 @@ export default {
218
220
  handler(nodeSelector) {
219
221
  // Harvester specific code should not live in rancher/dashboard components
220
222
  // This was brought into harvester/dashboard via https://github.com/harvester/dashboard/pull/342
221
- // rancher/dashboard via https://github.com/rancher/dashboard/pull/6310
223
+ // and then rancher/dashboard via https://github.com/rancher/dashboard/pull/6310
222
224
  if (this.isHarvester && nodeSelector?.[HOSTNAME]) {
223
225
  this.selectNode = 'nodeSelector';
224
226
  const nodeName = nodeSelector[HOSTNAME];
225
227
 
226
228
  this.nodeName = nodeName;
227
229
 
228
- const array = this.nodes.map((n) => n.value);
230
+ const array = this.nodes?.map((n) => n.value) || [];
229
231
 
230
232
  if (nodeName && !array.includes(nodeName)) {
231
233
  this.$store.dispatch('growl/error', {
@@ -259,7 +261,19 @@ export default {
259
261
  <template v-if="selectNode === 'nodeSelector'">
260
262
  <div class="row">
261
263
  <div class="col span-6">
264
+ <LabeledSelect
265
+ v-if="nodes"
266
+ v-model:value="nodeName"
267
+ :label="t('workload.scheduling.affinity.nodeName')"
268
+ :options="nodes || []"
269
+ :mode="mode"
270
+ :multiple="false"
271
+ :loading="loading"
272
+ :data-testid="'node-scheduling-nodeSelector'"
273
+ @update:value="update"
274
+ />
262
275
  <ResourceLabeledSelect
276
+ v-else
263
277
  v-model:value="nodeName"
264
278
  :label="t('workload.scheduling.affinity.nodeName')"
265
279
  :resource-type="NODE"
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from 'vue';
3
+ import Banner from '@components/Banner/Banner.vue';
4
+ import { Checkbox } from '@components/Form/Checkbox';
5
+ import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
6
+
7
+ const props = defineProps<{
8
+ value?: string | null;
9
+ enabled?: boolean;
10
+ mode?: string;
11
+ rules?: Function[];
12
+ checkboxTestId?: string;
13
+ inputTestId?: string;
14
+ }>();
15
+
16
+ const emit = defineEmits<{
17
+ 'update:value': [val: string | null];
18
+ 'update:enabled': [val: boolean];
19
+ }>();
20
+
21
+ const showInput = ref(!!props.value);
22
+
23
+ watch(() => props.enabled, (neu) => {
24
+ if (typeof neu === 'boolean' && neu !== showInput.value) {
25
+ showInput.value = neu;
26
+ }
27
+ });
28
+
29
+ watch(showInput, (neu, old) => {
30
+ if (neu !== props.enabled) {
31
+ emit('update:enabled', neu);
32
+ }
33
+ if (!neu && old && props.value) {
34
+ emit('update:value', null);
35
+ }
36
+ });
37
+
38
+ watch(() => props.value, (neu) => {
39
+ if (!!neu && !showInput.value) {
40
+ showInput.value = true;
41
+ }
42
+ });
43
+ </script>
44
+
45
+ <template>
46
+ <Banner
47
+ color="info"
48
+ class="mt-0"
49
+ label-key="cluster.privateRegistry.importedDescription"
50
+ />
51
+ <Checkbox
52
+ v-model:value="showInput"
53
+ class="mb-20"
54
+ :mode="mode"
55
+ :label="t('cluster.privateRegistry.label')"
56
+ :data-testid="checkboxTestId"
57
+ />
58
+ <LabeledInput
59
+ v-if="showInput"
60
+ :value="value as string"
61
+ :mode="mode"
62
+ :rules="rules"
63
+ :required="true"
64
+ label-key="catalog.chart.registry.custom.inputLabel"
65
+ :data-testid="inputTestId"
66
+ :placeholder="t('catalog.chart.registry.custom.placeholder')"
67
+ @update:value="(val) => emit('update:value', val)"
68
+ />
69
+ </template>
@@ -1,39 +1,38 @@
1
1
  <script>
2
2
  import { get } from '@shell/utils/object';
3
- import LabeledFormElement from '@shell/mixins/labeled-form-element';
4
3
  import VueSelectOverrides from '@shell/mixins/vue-select-overrides';
5
4
  import { generateRandomAlphaString } from '@shell/utils/string';
6
5
  import { LabeledTooltip } from '@components/LabeledTooltip';
7
6
  import { calculatePosition } from '@shell/utils/select';
8
7
  import { _VIEW } from '@shell/config/query-params';
9
8
  import { useClickOutside } from '@shell/composables/useClickOutside';
9
+ import { useLabeledFormElement, labeledFormElementProps } from '@shell/composables/useLabeledFormElement';
10
+ import { useLabeledSelect } from '@shell/composables/useLabeledSelect';
10
11
  import { ref } from 'vue';
11
12
 
12
13
  export default {
13
- emits: ['update:value', 'createdListItem', 'on-open', 'on-close'],
14
+ inheritAttrs: false,
15
+
16
+ emits: ['update:value', 'createdListItem', 'on-open', 'on-close', 'on-focus', 'on-blur', 'update:validation'],
14
17
 
15
18
  components: { LabeledTooltip },
16
19
  mixins: [
17
- LabeledFormElement,
18
20
  VueSelectOverrides,
19
21
  ],
20
22
  props: {
23
+ ...labeledFormElementProps,
24
+ value: {
25
+ default: null,
26
+ type: [String, Object, Number, Array, Boolean],
27
+ },
21
28
  appendToBody: {
22
29
  default: true,
23
30
  type: Boolean,
24
31
  },
25
- disabled: {
26
- default: false,
27
- type: Boolean,
28
- },
29
32
  getKeyForOption: {
30
33
  default: null,
31
34
  type: Function
32
35
  },
33
- mode: {
34
- default: 'edit',
35
- type: String,
36
- },
37
36
  optionKey: {
38
37
  default: null,
39
38
  type: String,
@@ -46,10 +45,6 @@ export default {
46
45
  default: null,
47
46
  type: String,
48
47
  },
49
- placeholder: {
50
- type: String,
51
- default: '',
52
- },
53
48
  popperOverride: {
54
49
  type: Function,
55
50
  default: null,
@@ -78,10 +73,6 @@ export default {
78
73
  type: String,
79
74
  default: null,
80
75
  },
81
- value: {
82
- default: null,
83
- type: [String, Object, Number, Array, Boolean],
84
- },
85
76
  closeOnSelect: {
86
77
  type: Boolean,
87
78
  default: true,
@@ -99,8 +90,20 @@ export default {
99
90
  default: false,
100
91
  type: Boolean
101
92
  },
93
+ options: {
94
+ type: Array,
95
+ default: () => ([])
96
+ },
97
+ searchable: {
98
+ default: false,
99
+ type: Boolean
100
+ },
101
+ filterable: {
102
+ default: true,
103
+ type: Boolean
104
+ },
102
105
  },
103
- setup() {
106
+ setup(props, { emit }) {
104
107
  const select = ref(null);
105
108
  const isOpen = ref(false);
106
109
 
@@ -108,13 +111,61 @@ export default {
108
111
  isOpen.value = false;
109
112
  });
110
113
 
111
- return { isOpen, select };
114
+ const {
115
+ raised,
116
+ focused,
117
+ blurred,
118
+ empty,
119
+ isView,
120
+ onFocusLabeled,
121
+ onBlurLabeled,
122
+ isDisabled,
123
+ validationMessage,
124
+ requiredField
125
+ } = useLabeledFormElement(props, emit);
126
+
127
+ const onFocus = () => {
128
+ emit('on-focus');
129
+ onFocusLabeled();
130
+ };
131
+
132
+ const onBlur = () => {
133
+ emit('on-blur');
134
+ onBlurLabeled();
135
+ };
136
+
137
+ const {
138
+ isSearchable,
139
+ isFilterable,
140
+ resizeHandler: resizeHandlerFn
141
+ } = useLabeledSelect(props);
142
+
143
+ const resizeHandler = () => {
144
+ resizeHandlerFn(select);
145
+ };
146
+
147
+ return {
148
+ isOpen,
149
+ select,
150
+ raised,
151
+ focused,
152
+ blurred,
153
+ empty,
154
+ isView,
155
+ onFocus,
156
+ onBlur,
157
+ isDisabled,
158
+ validationMessage,
159
+ requiredField,
160
+ isSearchable,
161
+ isFilterable,
162
+ resizeHandler
163
+ };
112
164
  },
113
165
  data() {
114
166
  return { generatedUid: `s-uid-${ generateRandomAlphaString(12) }` };
115
167
  },
116
168
  methods: {
117
- // resizeHandler = in mixin
118
169
  getOptionLabel(option) {
119
170
  if (this.$attrs['get-option-label']) {
120
171
  return this.$attrs['get-option-label'](option);
@@ -252,40 +303,6 @@ export default {
252
303
  },
253
304
  },
254
305
  computed: {
255
- requiredField() {
256
- // using "any" for a type on "rule" here is dirty but the use of the optional chaining operator makes it safe for what we're doing here.
257
- return (this.required || this.rules.some((rule) => rule?.name === 'required'));
258
- },
259
- validationMessage() {
260
- // we want to grab the required rule passed in if we can but if it's not there then we can just grab it from the formRulesGenerator
261
- const requiredRule = this.rules.find((rule) => rule?.name === 'required');
262
- const ruleMessages = [];
263
- const value = this?.value;
264
-
265
- if (requiredRule && this.blurred && !this.focused) {
266
- const message = requiredRule(value);
267
-
268
- if (!!message) {
269
- return message;
270
- }
271
- }
272
-
273
- for (const rule of this.rules) {
274
- const message = rule(value);
275
-
276
- if (!!message && rule.name !== 'required') { // we're catching 'required' above so we can ignore it here
277
- ruleMessages.push(message);
278
- }
279
- }
280
- if (ruleMessages.length > 0 && (this.blurred || this.focused)) {
281
- return ruleMessages.join(', ');
282
- } else {
283
- return undefined;
284
- }
285
- },
286
- canPaginate() {
287
- return false;
288
- },
289
306
  deClassedAttrs() {
290
307
  const { class: _, ...rest } = this.$attrs;
291
308
 
@@ -1,16 +1,16 @@
1
1
  <script>
2
- import labeledFormElement from '@shell/mixins/labeled-form-element';
3
2
  import LabeledSelect from '@shell/components/form/LabeledSelect';
4
3
  import { Banner } from '@components/Banner';
5
4
  import { _VIEW } from '@shell/config/query-params';
5
+ import { computed } from 'vue';
6
6
 
7
7
  export default {
8
+ inheritAttrs: false,
9
+
8
10
  emits: ['update:value'],
9
11
 
10
12
  components: { LabeledSelect, Banner },
11
13
 
12
- mixins: [labeledFormElement],
13
-
14
14
  props: {
15
15
  disabled: {
16
16
  type: Boolean,
@@ -66,6 +66,16 @@ export default {
66
66
  type: Boolean,
67
67
  default: false,
68
68
  },
69
+ value: {
70
+ type: String,
71
+ default: null
72
+ }
73
+ },
74
+
75
+ setup(props) {
76
+ const isView = computed(() => props.mode === _VIEW);
77
+
78
+ return { isView };
69
79
  },
70
80
 
71
81
  data() {
@@ -73,10 +83,6 @@ export default {
73
83
  },
74
84
 
75
85
  computed: {
76
- isView() {
77
- return this.mode === _VIEW;
78
- },
79
-
80
86
  serviceNameNew() {
81
87
  if (!this.selected) {
82
88
  return false;
@@ -88,10 +94,6 @@ export default {
88
94
  serviceName() {
89
95
  return this.reduce(this.selected);
90
96
  },
91
-
92
- canPaginate() {
93
- return false;
94
- },
95
97
  },
96
98
 
97
99
  methods: {
@@ -177,6 +177,72 @@ describe('component: KeyValue', () => {
177
177
  expect(firstValueInput.element.value).toBe('testvalue1');
178
178
  });
179
179
 
180
+ describe('valueConcealed', () => {
181
+ it('should not render actual secret values in the DOM when valueConcealed is true', () => {
182
+ const secretValue = 'super-secret-api-key-12345';
183
+ const wrapper = mount(KeyValue, {
184
+ props: {
185
+ value: { mySecret: secretValue },
186
+ mode: 'view',
187
+ valueConcealed: true,
188
+ },
189
+
190
+ global: {
191
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
192
+ stubs: { CodeMirror: true },
193
+ },
194
+ });
195
+
196
+ const concealedEl = wrapper.find('[data-testid="concealed-value"]');
197
+
198
+ expect(concealedEl.exists()).toBe(true);
199
+ expect(wrapper.html()).not.toContain(secretValue);
200
+ });
201
+
202
+ it('should render a TextAreaAutoGrow with the real value when valueConcealed is false', () => {
203
+ const secretValue = 'visible-value';
204
+ const wrapper = mount(KeyValue, {
205
+ props: {
206
+ value: { myKey: secretValue },
207
+ mode: 'view',
208
+ valueConcealed: false,
209
+ },
210
+
211
+ global: {
212
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
213
+ stubs: { CodeMirror: true },
214
+ },
215
+ });
216
+
217
+ const concealedEl = wrapper.find('[data-testid="concealed-value"]');
218
+
219
+ expect(concealedEl.exists()).toBe(false);
220
+
221
+ const multilineEl = wrapper.find('[data-testid="value-multiline"]');
222
+
223
+ expect(multilineEl.exists()).toBe(true);
224
+ });
225
+
226
+ it('should have user-select none on the concealed placeholder to prevent text selection', () => {
227
+ const wrapper = mount(KeyValue, {
228
+ props: {
229
+ value: { mySecret: 'secret' },
230
+ mode: 'view',
231
+ valueConcealed: true,
232
+ },
233
+
234
+ global: {
235
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
236
+ stubs: { CodeMirror: true },
237
+ },
238
+ });
239
+
240
+ const concealedEl = wrapper.find('[data-testid="concealed-value"]');
241
+
242
+ expect(concealedEl.classes()).toContain('concealed-value');
243
+ });
244
+ });
245
+
180
246
  it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
181
247
  const value = [{ key: 'testkey1', value: 'testvalue1' }];
182
248
 
@@ -1,9 +1,18 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import NodeScheduling from '@shell/components/form/NodeScheduling.vue';
3
3
  import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
4
+ import { createStore } from 'vuex';
4
5
 
5
6
  const requiredSetup = () => {
6
7
  return {
8
+ provide: {
9
+ store: createStore({
10
+ getters: {
11
+ currentStore: () => 'cluster',
12
+ 'cluster/paginationEnabled': () => () => false,
13
+ },
14
+ }),
15
+ },
7
16
  global: {
8
17
  mocks: {
9
18
  $fetchState: { pending: false },