@rancher/shell 3.0.5-rc.8 → 3.0.5

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 (199) hide show
  1. package/assets/styles/base/_color.scss +4 -1
  2. package/assets/styles/global/_tooltip.scss +7 -4
  3. package/assets/styles/themes/_dark.scss +11 -0
  4. package/assets/styles/themes/_light.scss +13 -1
  5. package/assets/styles/themes/_modern.scss +22 -0
  6. package/assets/translations/en-us.yaml +147 -19
  7. package/assets/translations/zh-hans.yaml +0 -1
  8. package/chart/monitoring/grafana/index.vue +8 -2
  9. package/components/ActionMenuShell.vue +3 -1
  10. package/components/Cron/CronExpressionEditor.vue +299 -0
  11. package/components/Cron/CronExpressionEditorModal.vue +247 -0
  12. package/components/Cron/CronTooltip.vue +87 -0
  13. package/components/Cron/types.ts +13 -0
  14. package/components/ForceDirectedTreeChart/composable.ts +11 -0
  15. package/components/PodSecurityAdmission.vue +2 -0
  16. package/components/PromptModal.vue +1 -1
  17. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
  18. package/components/Resource/Detail/CopyToClipboard.vue +78 -0
  19. package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
  20. package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
  21. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
  22. package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
  23. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
  24. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
  25. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +24 -38
  26. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
  27. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
  28. package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
  29. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
  30. package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
  31. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
  32. package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
  33. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +0 -3
  34. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
  35. package/components/Resource/Detail/Metadata/composables.ts +1 -4
  36. package/components/Resource/Detail/Metadata/index.vue +1 -0
  37. package/components/Resource/Detail/Preview/Content.vue +63 -0
  38. package/components/Resource/Detail/Preview/Preview.vue +128 -0
  39. package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
  40. package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
  41. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
  42. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
  43. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
  44. package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
  45. package/components/Resource/Detail/SpacedRow.vue +1 -0
  46. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +0 -5
  47. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
  48. package/components/Resource/Detail/TitleBar/composables.ts +1 -3
  49. package/components/Resource/Detail/TitleBar/index.vue +2 -29
  50. package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
  51. package/components/Resource/Detail/ViewOptions/index.vue +41 -0
  52. package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
  53. package/components/ResourceDetail/Masthead/legacy.vue +0 -19
  54. package/components/ResourceDetail/index.vue +1 -26
  55. package/components/ResourceTable.vue +24 -0
  56. package/components/SortableTable/index.vue +7 -1
  57. package/components/SortableTable/paging.js +3 -0
  58. package/components/Tabbed/Tab.vue +43 -1
  59. package/components/Tabbed/index.vue +3 -1
  60. package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
  61. package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
  62. package/components/auth/login/saml.vue +86 -0
  63. package/components/form/LabeledSelect.vue +8 -8
  64. package/components/form/ProjectMemberEditor.vue +2 -0
  65. package/components/form/ResourceTabs/composable.ts +54 -0
  66. package/components/form/ResourceTabs/index.vue +10 -7
  67. package/components/form/Select.vue +13 -10
  68. package/components/form/__tests__/LabeledSelect.test.ts +133 -0
  69. package/components/form/__tests__/Select.test.ts +134 -0
  70. package/components/nav/Header.vue +6 -5
  71. package/composables/useExtensionManager.ts +17 -0
  72. package/config/home-links.js +12 -0
  73. package/config/labels-annotations.js +0 -1
  74. package/config/page-actions.js +0 -1
  75. package/config/product/explorer.js +3 -1
  76. package/config/product/fleet.js +2 -7
  77. package/config/product/manager.js +0 -5
  78. package/config/query-params.js +1 -0
  79. package/config/router/navigation-guards/clusters.js +2 -1
  80. package/config/router/navigation-guards/products.js +1 -1
  81. package/config/store.js +2 -0
  82. package/core/extension-manager-impl.js +518 -0
  83. package/core/plugins.js +35 -468
  84. package/core/types.ts +8 -2
  85. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
  86. package/detail/catalog.cattle.io.app.vue +7 -4
  87. package/detail/fleet.cattle.io.bundle.vue +1 -5
  88. package/detail/fleet.cattle.io.cluster.vue +3 -2
  89. package/detail/fleet.cattle.io.gitrepo.vue +76 -49
  90. package/detail/fleet.cattle.io.helmop.vue +78 -49
  91. package/dialog/AddonConfigConfirmationDialog.vue +1 -1
  92. package/dialog/GenericPrompt.vue +1 -1
  93. package/dialog/ImportDialog.vue +9 -2
  94. package/dialog/InstallExtensionDialog.vue +18 -10
  95. package/dialog/SloDialog.vue +1 -1
  96. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
  97. package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
  98. package/edit/auth/oidc.vue +106 -6
  99. package/edit/auth/saml.vue +5 -5
  100. package/edit/cloudcredential.vue +31 -17
  101. package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
  102. package/edit/fleet.cattle.io.cluster.vue +19 -0
  103. package/edit/fleet.cattle.io.gitrepo.vue +23 -16
  104. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
  105. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
  106. package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
  107. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -3
  108. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
  109. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
  110. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  111. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
  112. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
  113. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
  114. package/edit/resources.cattle.io.restore.vue +5 -8
  115. package/initialize/install-plugins.js +1 -3
  116. package/list/__tests__/workload.test.ts +1 -0
  117. package/list/workload.vue +8 -1
  118. package/machine-config/components/GCEImage.vue +6 -5
  119. package/machine-config/google.vue +11 -6
  120. package/mixins/__tests__/auth-config.test.ts +4 -6
  121. package/mixins/__tests__/chart.test.ts +139 -1
  122. package/mixins/auth-config.js +33 -10
  123. package/mixins/chart.js +58 -18
  124. package/models/__tests__/namespace.test.ts +69 -0
  125. package/models/apps.statefulset.js +8 -10
  126. package/models/chart.js +5 -1
  127. package/models/fleet-application.js +16 -46
  128. package/models/fleet.cattle.io.bundle.js +1 -38
  129. package/models/fleet.cattle.io.gitrepo.js +4 -0
  130. package/models/fleet.cattle.io.helmop.js +4 -0
  131. package/models/management.cattle.io.cluster.js +1 -1
  132. package/models/management.cattle.io.project.js +12 -0
  133. package/models/namespace.js +30 -0
  134. package/models/workload.js +4 -1
  135. package/package.json +10 -10
  136. package/pages/auth/login.vue +8 -3
  137. package/pages/auth/logout.vue +6 -5
  138. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -11
  139. package/pages/c/_cluster/apps/charts/chart.vue +29 -20
  140. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  141. package/pages/c/_cluster/apps/charts/install.vue +6 -5
  142. package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
  143. package/pages/c/_cluster/explorer/tools/index.vue +145 -254
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +18 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
  146. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  147. package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
  148. package/pages/c/_cluster/uiplugins/index.vue +221 -363
  149. package/pages/home.vue +1 -9
  150. package/plugins/axios.js +3 -2
  151. package/plugins/dashboard-store/resource-class.js +49 -0
  152. package/plugins/ember-cookie.js +7 -3
  153. package/plugins/steve/subscribe.js +4 -2
  154. package/public/index.html +2 -1
  155. package/rancher-components/Card/Card.vue +1 -1
  156. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  157. package/rancher-components/Form/Radio/RadioButton.vue +1 -1
  158. package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
  159. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
  160. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
  161. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
  162. package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
  163. package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
  164. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +1 -1
  165. package/rancher-components/Pill/RcStatusBadge/index.ts +1 -1
  166. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -3
  167. package/rancher-components/Pill/RcStatusIndicator/types.ts +1 -1
  168. package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
  169. package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
  170. package/rancher-components/Pill/RcTag/index.ts +1 -0
  171. package/rancher-components/Pill/RcTag/types.ts +9 -0
  172. package/rancher-components/Pill/types.ts +1 -0
  173. package/rancher-components/RcItemCard/RcItemCard.vue +1 -0
  174. package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
  175. package/scripts/test-plugins-build.sh +0 -1
  176. package/store/__tests__/catalog.test.ts +63 -0
  177. package/store/__tests__/cookies.test.ts +72 -0
  178. package/store/auth.js +33 -10
  179. package/store/catalog.js +2 -2
  180. package/store/cookies.ts +30 -0
  181. package/store/prefs.js +10 -5
  182. package/store/type-map.js +3 -15
  183. package/types/extension-manager.ts +26 -0
  184. package/types/shell/index.d.ts +123 -27
  185. package/utils/__tests__/product.test.ts +129 -0
  186. package/utils/__tests__/resource.test.ts +87 -0
  187. package/utils/alertmanagerconfig.js +2 -2
  188. package/utils/auth.js +4 -77
  189. package/utils/product.ts +39 -0
  190. package/utils/resource.ts +35 -0
  191. package/utils/select.js +0 -24
  192. package/utils/validators/formRules/__tests__/index.test.ts +3 -0
  193. package/utils/validators/formRules/index.ts +2 -1
  194. package/vue.config.js +1 -1
  195. package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
  196. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
  197. package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
  198. package/utils/cookie-universal.js +0 -10
  199. /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
@@ -22,12 +22,69 @@ export default {
22
22
  window.location.href = idpRedirectUrl;
23
23
  },
24
24
  },
25
+
26
+ computed: {
27
+ // If any of the 3 params is specified, this is a CLI login
28
+ isCLILogin() {
29
+ const {
30
+ cli,
31
+ requestId,
32
+ publicKey,
33
+ responseType
34
+ } = this.$route.query;
35
+
36
+ return cli || publicKey || responseType || requestId;
37
+ },
38
+ // If this is a CLI login, we must have the correct respone type and the other params must not be empty
39
+ invalidCLILogin() {
40
+ const { requestId, publicKey, responseType } = this.$route.query;
41
+
42
+ if (this.isCLILogin) {
43
+ return responseType !== 'kubeconfig' || !requestId || !publicKey;
44
+ }
45
+
46
+ return false;
47
+ },
48
+ cliLoginCode() {
49
+ const { requestId } = this.$route.query;
50
+
51
+ return requestId;
52
+ },
53
+ warningMessageKey() {
54
+ const { cli } = this.$route.query;
55
+
56
+ return cli === 'true' ? 'login.cli.warning' : 'login.cli.warningLegacy';
57
+ }
58
+ }
25
59
  };
26
60
  </script>
27
61
 
28
62
  <template>
29
63
  <div class="text-center">
64
+ <div
65
+ v-if="isCLILogin"
66
+ class="cli-login"
67
+ >
68
+ <div class="cli-message">
69
+ {{ t('login.cli.welcome') }}
70
+ </div>
71
+ <div
72
+ v-if="invalidCLILogin"
73
+ class="cli-message cli-error"
74
+ >
75
+ {{ t('login.cli.invalidParams') }}
76
+ </div>
77
+ <template v-else>
78
+ <div class="cli-message">
79
+ {{ t(warningMessageKey, {}, true) }}
80
+ </div>
81
+ <div class="cli-login-code">
82
+ {{ cliLoginCode }}
83
+ </div>
84
+ </template>
85
+ </div>
30
86
  <button
87
+ v-if="!invalidCLILogin"
31
88
  ref="btn"
32
89
  class="btn bg-primary"
33
90
  style="font-size: 18px;"
@@ -37,3 +94,32 @@ export default {
37
94
  </button>
38
95
  </div>
39
96
  </template>
97
+ <style lang="scss" scoped>
98
+ .cli-login {
99
+ display: flex;
100
+ flex-direction: column;
101
+ align-items: center;
102
+
103
+ > div {
104
+ margin-bottom: 8px
105
+ }
106
+
107
+ .cli-message {
108
+ font-size: 16px;
109
+
110
+ &.cli-error {
111
+ color: var(--error);
112
+ }
113
+ }
114
+
115
+ .cli-login-code {
116
+ font-family: 'Courier New', Courier, monospace;
117
+ border: 1px solid var(--border);
118
+ border-radius: var(--border-radius);
119
+ padding: 4px 8px;
120
+ margin: 8px 0 16px 0;
121
+ letter-spacing: 1px;
122
+ font-size: 16px;
123
+ }
124
+ }
125
+ </style>
@@ -4,7 +4,7 @@ import LabeledFormElement from '@shell/mixins/labeled-form-element';
4
4
  import { get } from '@shell/utils/object';
5
5
  import { LabeledTooltip } from '@components/LabeledTooltip';
6
6
  import VueSelectOverrides from '@shell/mixins/vue-select-overrides';
7
- import { onClickOption, calculatePosition } from '@shell/utils/select';
7
+ import { calculatePosition } from '@shell/utils/select';
8
8
  import { generateRandomAlphaString } from '@shell/utils/string';
9
9
  import LabeledSelectPagination from '@shell/components/form/labeled-select-utils/labeled-select-pagination';
10
10
  import { LABEL_SELECT_NOT_OPTION_KINDS } from '@shell/types/components/labeledSelect';
@@ -169,14 +169,19 @@ export default {
169
169
  },
170
170
 
171
171
  methods: {
172
- // Ensure we only focus on open, otherwise we re-open on close
173
- clickSelect() {
172
+ clickSelect(event) {
174
173
  if (this.mode === _VIEW || this.loading === true || this.disabled === true) {
175
174
  return;
176
175
  }
177
176
 
177
+ // Ensure we don't toggle when clicking the clear button on multi-select
178
+ if (this.$attrs.multiple && event?.target.className === 'vs__deselect') {
179
+ return;
180
+ }
181
+
178
182
  this.isOpen = !this.isOpen;
179
183
 
184
+ // Ensure we only focus on open, otherwise we re-open on close
180
185
  if (this.isOpen) {
181
186
  this.focusSearch();
182
187
  }
@@ -262,10 +267,6 @@ export default {
262
267
 
263
268
  get,
264
269
 
265
- onClickOption(option, event) {
266
- onClickOption.call(this, option, event);
267
- },
268
-
269
270
  dropdownShouldOpen(instance, forceOpen = false) {
270
271
  if (!this.isOpen) {
271
272
  return false;
@@ -428,7 +429,6 @@ export default {
428
429
  v-else
429
430
  class="vs__option-kind"
430
431
  :class="{ 'has-icon' : hasGroupIcon}"
431
- @mousedown="(e) => onClickOption(option, e)"
432
432
  >
433
433
  {{ getOptionLabel(option) }}
434
434
  <i
@@ -286,6 +286,7 @@ export default {
286
286
  <template v-slot:body>
287
287
  <RadioGroup
288
288
  v-model:value="value.permissionGroup"
289
+ :mode="mode"
289
290
  data-testid="permission-options"
290
291
  :options="options"
291
292
  name="permission-group"
@@ -301,6 +302,7 @@ export default {
301
302
  >
302
303
  <Checkbox
303
304
  v-model:value="permission.value"
305
+ :mode="mode"
304
306
  :data-testid="`custom-permission-${i}`"
305
307
  :disabled="permission.locked"
306
308
  class="mb-5"
@@ -0,0 +1,54 @@
1
+ import { randomStr } from '@shell/utils/string';
2
+ import { sum } from 'lodash';
3
+ import { computed, inject, provide, ref } from 'vue';
4
+
5
+ const UPDATE_COUNT_PROVIDER_KEY = 'update-count';
6
+ const USE_COUNTS_KEY = 'is-inside-resource-tabs';
7
+
8
+ type UpdateCountFn = (key: string, count: number | undefined) => void;
9
+
10
+ export const useIndicateUseCounts = () => {
11
+ provide(USE_COUNTS_KEY, true);
12
+ };
13
+
14
+ export const useTabCountWatcher = () => {
15
+ if (!inject<boolean>(USE_COUNTS_KEY, false)) {
16
+ return { isCountVisible: ref<boolean>(false) };
17
+ }
18
+
19
+ const countLedger = ref<{ [key: string]: number | undefined }>({});
20
+
21
+ const isCountVisible = computed(() => {
22
+ // Some tables are destroyed and recreated depending on visibility so we count keys
23
+ // to check if a table has been present in the tab even if the count has been cleared
24
+ return Object.keys(countLedger.value).length > 0;
25
+ });
26
+
27
+ const count = computed(() => {
28
+ return sum(Object.values(countLedger.value).map((count) => count || 0));
29
+ });
30
+
31
+ const updateCount = (key: string, count: number | undefined) => {
32
+ countLedger.value[key] = count;
33
+ };
34
+
35
+ provide(UPDATE_COUNT_PROVIDER_KEY, updateCount);
36
+
37
+ return { isCountVisible, count };
38
+ };
39
+
40
+ export const useTabCountUpdater = () => {
41
+ const tabKey = randomStr();
42
+ const updateCount = inject<UpdateCountFn>(UPDATE_COUNT_PROVIDER_KEY);
43
+
44
+ const updateTabCount = (count: number | undefined) => {
45
+ updateCount?.(tabKey, count);
46
+ };
47
+
48
+ const clearTabCount = () => updateTabCount(undefined);
49
+
50
+ return {
51
+ updateTabCount,
52
+ clearTabCount
53
+ };
54
+ };
@@ -16,6 +16,7 @@ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
16
16
  import { MESSAGE, REASON } from '@shell/config/table-headers';
17
17
  import { STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
18
18
  import { headerFromSchemaColString } from '@shell/store/type-map.utils';
19
+ import { useIndicateUseCounts } from '@shell/components/form/ResourceTabs/composable';
19
20
 
20
21
  export default {
21
22
 
@@ -75,6 +76,12 @@ export default {
75
76
  }
76
77
  },
77
78
 
79
+ setup(props) {
80
+ if (props.mode === _VIEW) {
81
+ useIndicateUseCounts();
82
+ }
83
+ },
84
+
78
85
  data() {
79
86
  const inStore = this.$store.getters['currentStore'](EVENT);
80
87
  const eventSchema = this.$store.getters[`${ inStore }/schemaFor`](EVENT); // @TODO be smarter about which resources actually ever have events
@@ -155,15 +162,13 @@ export default {
155
162
  }
156
163
 
157
164
  return false;
165
+ },
166
+ children() {
167
+ return this.$slots?.default?.() || [];
158
168
  }
159
169
  },
160
170
 
161
171
  methods: {
162
- // Ensures we only fetch events and show the table when the events tab has been activated
163
- tabChange(neu) {
164
- this.selectedTab = neu?.selectedName;
165
- },
166
-
167
172
  /**
168
173
  * Conditions come from a resource's `status`. They are used by both core resources like workloads as well as those from CRDs
169
174
  * - Workloads
@@ -243,7 +248,6 @@ export default {
243
248
  @changed="tabChange"
244
249
  >
245
250
  <slot />
246
-
247
251
  <Tab
248
252
  v-if="showConditions"
249
253
  label-key="resourceTabs.conditions.tab"
@@ -262,7 +266,6 @@ export default {
262
266
  >
263
267
  <!-- namespaced: false given we don't want the default handling of namespaced resource (apply header filter) -->
264
268
  <PaginatedResourceTable
265
- v-if="selectedTab === 'events'"
266
269
  :schema="eventSchema"
267
270
  :local-filter="filterEventsLocal"
268
271
  :api-filter="filterEventsApi"
@@ -4,7 +4,7 @@ import LabeledFormElement from '@shell/mixins/labeled-form-element';
4
4
  import VueSelectOverrides from '@shell/mixins/vue-select-overrides';
5
5
  import { generateRandomAlphaString } from '@shell/utils/string';
6
6
  import { LabeledTooltip } from '@components/LabeledTooltip';
7
- import { onClickOption, calculatePosition } from '@shell/utils/select';
7
+ import { calculatePosition } from '@shell/utils/select';
8
8
  import { _VIEW } from '@shell/config/query-params';
9
9
  import { useClickOutside } from '@shell/composables/useClickOutside';
10
10
  import { ref } from 'vue';
@@ -94,7 +94,11 @@ export default {
94
94
  isLangSelect: {
95
95
  type: Boolean,
96
96
  default: false
97
- }
97
+ },
98
+ loading: {
99
+ default: false,
100
+ type: Boolean
101
+ },
98
102
  },
99
103
  setup() {
100
104
  const select = ref(null);
@@ -134,14 +138,19 @@ export default {
134
138
  calculatePosition(dropdownList, component, width, this.placement);
135
139
  },
136
140
 
137
- // Ensure we only focus on open, otherwise we re-open on close
138
141
  clickSelect(ev) {
139
142
  if (this.mode === _VIEW || this.loading === true || this.disabled === true) {
140
143
  return;
141
144
  }
142
145
 
146
+ // Ensure we don't toggle when clicking the clear button on multi-select
147
+ if (this.$attrs.multiple && ev?.target.className === 'vs__deselect') {
148
+ return;
149
+ }
150
+
143
151
  this.isOpen = !this.isOpen;
144
152
 
153
+ // Ensure we only focus on open, otherwise we re-open on close
145
154
  if (this.isOpen) {
146
155
  this.focusSearch(ev);
147
156
  }
@@ -163,9 +172,6 @@ export default {
163
172
 
164
173
  get,
165
174
 
166
- onClickOption(option, event) {
167
- onClickOption.call(this, option, event);
168
- },
169
175
  selectable(opt) {
170
176
  // Lets you disable options that are used
171
177
  // for headings on groups of options.
@@ -352,10 +358,7 @@ export default {
352
358
  <template
353
359
  #option="option"
354
360
  >
355
- <div
356
- :lang="isLangSelect ? option.value : undefined"
357
- @mousedown="(e) => onClickOption(option, e)"
358
- >
361
+ <div :lang="isLangSelect ? option.value : undefined">
359
362
  {{ getOptionLabel(option.label) }}
360
363
  </div>
361
364
  </template>
@@ -291,4 +291,137 @@ describe('component: LabeledSelect', () => {
291
291
  expect(spyFocus).toHaveBeenCalled();
292
292
  expect(spyPreventDefault).not.toHaveBeenCalled();
293
293
  });
294
+
295
+ describe('function: clickSelect', () => {
296
+ it('should open dropdown when clickSelect is called and not disabled', async() => {
297
+ const label = 'Foo';
298
+ const value = 'foo';
299
+ const wrapper = mount(LabeledSelect, {
300
+ props: {
301
+ value,
302
+ options: [{ label, value }],
303
+ disabled: false,
304
+ loading: false,
305
+ mode: _EDIT
306
+ }
307
+ });
308
+
309
+ expect(wrapper.vm.isOpen).toBe(false);
310
+
311
+ wrapper.vm.clickSelect();
312
+ await wrapper.vm.$nextTick();
313
+
314
+ expect(wrapper.vm.isOpen).toBe(true);
315
+ });
316
+
317
+ it('should not open dropdown when clickSelect is called and disabled', async() => {
318
+ const label = 'Foo';
319
+ const value = 'foo';
320
+ const wrapper = mount(LabeledSelect, {
321
+ props: {
322
+ value,
323
+ options: [{ label, value }],
324
+ disabled: true,
325
+ loading: false,
326
+ mode: _EDIT
327
+ }
328
+ });
329
+
330
+ expect(wrapper.vm.isOpen).toBe(false);
331
+
332
+ wrapper.vm.clickSelect();
333
+ await wrapper.vm.$nextTick();
334
+
335
+ expect(wrapper.vm.isOpen).toBe(false);
336
+ });
337
+
338
+ it('should not open dropdown when loading is true', async() => {
339
+ const label = 'Foo';
340
+ const value = 'foo';
341
+ const wrapper = mount(LabeledSelect, {
342
+ props: {
343
+ value,
344
+ options: [{ label, value }],
345
+ disabled: false,
346
+ loading: true,
347
+ mode: _EDIT
348
+ }
349
+ });
350
+
351
+ expect(wrapper.vm.isOpen).toBe(false);
352
+
353
+ wrapper.vm.clickSelect();
354
+ await wrapper.vm.$nextTick();
355
+
356
+ expect(wrapper.vm.isOpen).toBe(false);
357
+ });
358
+
359
+ it('should not open dropdown when mode is _VIEW', async() => {
360
+ const label = 'Foo';
361
+ const value = 'foo';
362
+ const wrapper = mount(LabeledSelect, {
363
+ props: {
364
+ value,
365
+ options: [{ label, value }],
366
+ disabled: false,
367
+ loading: false,
368
+ mode: _VIEW
369
+ }
370
+ });
371
+
372
+ expect(wrapper.vm.isOpen).toBe(false);
373
+
374
+ wrapper.vm.clickSelect();
375
+ await wrapper.vm.$nextTick();
376
+
377
+ expect(wrapper.vm.isOpen).toBe(false);
378
+ });
379
+
380
+ it('should not clear value if disabled', async() => {
381
+ const label = 'Foo';
382
+ const value = 'foo';
383
+ const wrapper = mount(LabeledSelect, {
384
+ props: {
385
+ value,
386
+ options: [{ label, value }],
387
+ multiple: true,
388
+ disabled: true,
389
+ mode: _EDIT
390
+ }
391
+ });
392
+
393
+ const clearBtn = wrapper.find('.vs__deselect');
394
+
395
+ expect(clearBtn.exists()).toBe(true);
396
+
397
+ await clearBtn.trigger('mousedown');
398
+ await wrapper.vm.$nextTick();
399
+
400
+ expect(wrapper.emitted('update:value')).toBeUndefined();
401
+ expect(wrapper.vm.isOpen).toBe(false);
402
+ });
403
+
404
+ it('should not open dropdown when remove button is clicked', async() => {
405
+ const label = 'Foo';
406
+ const value = 'foo';
407
+ const wrapper = mount(LabeledSelect, {
408
+ props: {
409
+ value,
410
+ options: [{ label, value }],
411
+ multiple: true,
412
+ mode: _EDIT
413
+ }
414
+ });
415
+
416
+ expect(wrapper.vm.isOpen).toBe(false);
417
+
418
+ const clearBtn = wrapper.find('.vs__deselect');
419
+
420
+ await clearBtn.trigger('mousedown');
421
+ await wrapper.vm.$nextTick();
422
+
423
+ expect(wrapper.emitted('update:value')).toBeUndefined();
424
+ expect(wrapper.vm.isOpen).toBe(false);
425
+ });
426
+ });
294
427
  });
@@ -1,6 +1,7 @@
1
1
  import { shallowMount, mount } from '@vue/test-utils';
2
2
  import { defineComponent } from 'vue';
3
3
  import Select from '@shell/components/form/Select.vue';
4
+ import { _EDIT, _VIEW } from '@shell/config/query-params';
4
5
 
5
6
  const SelectComponent = Select as ReturnType<typeof defineComponent>;
6
7
 
@@ -100,4 +101,137 @@ describe('select.vue', () => {
100
101
  expect(spyFocus).toHaveBeenCalled();
101
102
  expect(spyPreventDefault).not.toHaveBeenCalled();
102
103
  });
104
+
105
+ describe('function: clickSelect', () => {
106
+ it('should open dropdown when clickSelect is called and not disabled', async() => {
107
+ const label = 'Foo';
108
+ const value = 'foo';
109
+ const wrapper = mount(Select, {
110
+ props: {
111
+ value,
112
+ options: [{ label, value }],
113
+ disabled: false,
114
+ loading: false,
115
+ mode: _EDIT
116
+ }
117
+ });
118
+
119
+ expect(wrapper.vm.isOpen).toBe(false);
120
+
121
+ wrapper.vm.clickSelect();
122
+ await wrapper.vm.$nextTick();
123
+
124
+ expect(wrapper.vm.isOpen).toBe(true);
125
+ });
126
+
127
+ it('should not open dropdown when clickSelect is called and disabled', async() => {
128
+ const label = 'Foo';
129
+ const value = 'foo';
130
+ const wrapper = mount(Select, {
131
+ props: {
132
+ value,
133
+ options: [{ label, value }],
134
+ disabled: true,
135
+ loading: false,
136
+ mode: _EDIT
137
+ }
138
+ });
139
+
140
+ expect(wrapper.vm.isOpen).toBe(false);
141
+
142
+ wrapper.vm.clickSelect();
143
+ await wrapper.vm.$nextTick();
144
+
145
+ expect(wrapper.vm.isOpen).toBe(false);
146
+ });
147
+
148
+ it('should not open dropdown when loading is true', async() => {
149
+ const label = 'Foo';
150
+ const value = 'foo';
151
+ const wrapper = mount(Select, {
152
+ props: {
153
+ value,
154
+ options: [{ label, value }],
155
+ disabled: false,
156
+ loading: true,
157
+ mode: _EDIT
158
+ }
159
+ });
160
+
161
+ expect(wrapper.vm.isOpen).toBe(false);
162
+
163
+ wrapper.vm.clickSelect();
164
+ await wrapper.vm.$nextTick();
165
+
166
+ expect(wrapper.vm.isOpen).toBe(false);
167
+ });
168
+
169
+ it('should not open dropdown when mode is _VIEW', async() => {
170
+ const label = 'Foo';
171
+ const value = 'foo';
172
+ const wrapper = mount(Select, {
173
+ props: {
174
+ value,
175
+ options: [{ label, value }],
176
+ disabled: false,
177
+ loading: false,
178
+ mode: _VIEW
179
+ }
180
+ });
181
+
182
+ expect(wrapper.vm.isOpen).toBe(false);
183
+
184
+ wrapper.vm.clickSelect();
185
+ await wrapper.vm.$nextTick();
186
+
187
+ expect(wrapper.vm.isOpen).toBe(false);
188
+ });
189
+
190
+ it('should not clear value if disabled', async() => {
191
+ const label = 'Foo';
192
+ const value = 'foo';
193
+ const wrapper = mount(Select, {
194
+ props: {
195
+ value,
196
+ options: [{ label, value }],
197
+ multiple: true,
198
+ disabled: true,
199
+ mode: _EDIT
200
+ }
201
+ });
202
+
203
+ const clearBtn = wrapper.find('.vs__deselect');
204
+
205
+ expect(clearBtn.exists()).toBe(true);
206
+
207
+ await clearBtn.trigger('mousedown');
208
+ await wrapper.vm.$nextTick();
209
+
210
+ expect(wrapper.emitted('update:value')).toBeUndefined();
211
+ expect(wrapper.vm.isOpen).toBe(false);
212
+ });
213
+
214
+ it('should not open dropdown when remove button is clicked', async() => {
215
+ const label = 'Foo';
216
+ const value = 'foo';
217
+ const wrapper = mount(Select, {
218
+ props: {
219
+ value,
220
+ options: [{ label, value }],
221
+ multiple: true,
222
+ mode: _EDIT
223
+ }
224
+ });
225
+
226
+ expect(wrapper.vm.isOpen).toBe(false);
227
+
228
+ const clearBtn = wrapper.find('.vs__deselect');
229
+
230
+ await clearBtn.trigger('mousedown');
231
+ await wrapper.vm.$nextTick();
232
+
233
+ expect(wrapper.emitted('update:value')).toBeUndefined();
234
+ expect(wrapper.vm.isOpen).toBe(false);
235
+ });
236
+ });
103
237
  });
@@ -29,6 +29,7 @@ import {
29
29
  RcDropdownSeparator,
30
30
  RcDropdownTrigger
31
31
  } from '@components/RcDropdown';
32
+ import { SLO_AUTH_PROVIDERS } from '@shell/store/auth';
32
33
 
33
34
  export default {
34
35
 
@@ -99,10 +100,10 @@ export default {
99
100
  'showWorkspaceSwitcher'
100
101
  ]),
101
102
 
102
- samlAuthProviderEnabled() {
103
+ sloAuthProviderEnabled() {
103
104
  const publicAuthProviders = this.$store.getters['rancher/all']('authProvider');
104
105
 
105
- return publicAuthProviders.find((authProvider) => configType[authProvider.id] === 'saml') || {};
106
+ return publicAuthProviders.find((authProvider) => SLO_AUTH_PROVIDERS.includes(configType[authProvider?.id])) || {};
106
107
  },
107
108
 
108
109
  shouldShowSloLogoutModal() {
@@ -111,7 +112,7 @@ export default {
111
112
  return false;
112
113
  }
113
114
 
114
- const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.samlAuthProviderEnabled;
115
+ const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.sloAuthProviderEnabled;
115
116
 
116
117
  return logoutAllSupported && logoutAllEnabled && !logoutAllForced;
117
118
  },
@@ -276,8 +277,8 @@ export default {
276
277
  showSloModal() {
277
278
  this.$store.dispatch('management/promptModal', {
278
279
  component: 'SloDialog',
279
- componentProps: { authProvider: this.samlAuthProviderEnabled },
280
- modalWidth: '500px'
280
+ componentProps: { authProvider: this.sloAuthProviderEnabled },
281
+ modalWidth: '600px'
281
282
  });
282
283
  },
283
284
  // Sizes the product area of the header such that it shrinks to ensure the whole header bar can be shown
@@ -0,0 +1,17 @@
1
+ import { ExtensionManager } from '@shell/types/extension-manager';
2
+ import { getExtensionManager } from '@shell/core/extension-manager-impl';
3
+
4
+ /**
5
+ * Provides access to the registered extension manager instance. Used within Vue
6
+ * components or other composables that require extension functionality.
7
+ * @returns The extension manager instance
8
+ */
9
+ export const useExtensionManager = (): ExtensionManager => {
10
+ const extension = getExtensionManager();
11
+
12
+ if (!extension) {
13
+ throw new Error('useExtensionManager must be called after the extensionManager has been initialized');
14
+ }
15
+
16
+ return extension;
17
+ };