@rancher/shell 3.0.8-rc.10 → 3.0.8-rc.13

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 (96) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +90 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +98 -0
  6. package/apis/intf/system.ts +34 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +54 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +33 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/components/CruResource.vue +8 -1
  18. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  19. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  20. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
  21. package/components/ModalManager.vue +11 -1
  22. package/components/ResourceDetail/index.vue +3 -0
  23. package/components/ResourceTable.vue +54 -21
  24. package/components/SlideInPanelManager.vue +16 -11
  25. package/components/SortableTable/index.vue +20 -2
  26. package/components/Tabbed/index.vue +37 -2
  27. package/components/auth/login/ldap.vue +3 -3
  28. package/components/form/NodeScheduling.vue +2 -2
  29. package/components/form/ResourceTabs/composable.ts +2 -2
  30. package/components/nav/Group.vue +9 -2
  31. package/components/nav/Header.vue +1 -1
  32. package/components/nav/Type.vue +8 -3
  33. package/components/nav/__tests__/Type.test.ts +59 -0
  34. package/composables/cruResource.ts +27 -0
  35. package/composables/focusTrap.ts +3 -1
  36. package/composables/resourceDetail.ts +15 -0
  37. package/config/router/navigation-guards/clusters.js +3 -3
  38. package/config/router/navigation-guards/products.js +1 -1
  39. package/core/__tests__/extension-manager-impl.test.js +437 -0
  40. package/core/extension-manager-impl.js +6 -27
  41. package/core/plugin-helpers.ts +2 -2
  42. package/core/plugin.ts +9 -1
  43. package/core/plugins-loader.js +2 -2
  44. package/core/types-provisioning.ts +4 -0
  45. package/core/types.ts +35 -0
  46. package/detail/provisioning.cattle.io.cluster.vue +8 -6
  47. package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
  48. package/dialog/SearchDialog.vue +1 -0
  49. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  50. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  51. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
  52. package/edit/workload/index.vue +1 -1
  53. package/initialize/install-plugins.js +4 -5
  54. package/models/management.cattle.io.cluster.js +1 -1
  55. package/models/provisioning.cattle.io.cluster.js +1 -1
  56. package/package.json +3 -3
  57. package/pages/auth/login.vue +3 -3
  58. package/pages/auth/setup.vue +1 -1
  59. package/pages/auth/verify.vue +3 -3
  60. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  61. package/pages/c/_cluster/fleet/index.vue +4 -7
  62. package/pkg/auto-import.js +3 -3
  63. package/pkg/dynamic-importer.lib.js +1 -1
  64. package/pkg/import.js +1 -1
  65. package/plugins/dashboard-store/getters.js +1 -1
  66. package/plugins/dashboard-store/model-loader.js +1 -1
  67. package/plugins/dashboard-store/resource-class.js +6 -2
  68. package/plugins/plugin.js +2 -2
  69. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
  70. package/plugins/steve/steve-class.js +1 -1
  71. package/plugins/steve/steve-pagination-utils.ts +108 -43
  72. package/scripts/publish-shell.sh +25 -0
  73. package/store/__tests__/catalog.test.ts +1 -1
  74. package/store/__tests__/type-map.test.ts +164 -2
  75. package/store/auth.js +1 -1
  76. package/store/i18n.js +3 -3
  77. package/store/index.js +5 -3
  78. package/store/notifications.ts +2 -0
  79. package/store/type-map.js +14 -5
  80. package/types/internal-api/shell/modal.d.ts +6 -6
  81. package/types/notifications/index.ts +126 -15
  82. package/types/rancher/index.d.ts +9 -0
  83. package/types/shell/index.d.ts +1 -0
  84. package/types/vue-shim.d.ts +5 -4
  85. package/utils/pagination-utils.ts +2 -2
  86. package/utils/pagination-wrapper.ts +1 -1
  87. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  88. package/vue.config.js +3 -3
  89. package/composables/useExtensionManager.ts +0 -17
  90. package/core/__test__/extension-manager-impl.test.js +0 -236
  91. package/core/plugins.js +0 -38
  92. package/plugins/internal-api/index.ts +0 -37
  93. package/plugins/internal-api/shared/base-api.ts +0 -13
  94. package/plugins/internal-api/shell/shell.api.ts +0 -108
  95. package/types/internal-api/shell/growl.d.ts +0 -25
  96. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -7,6 +7,10 @@ import findIndex from 'lodash/findIndex';
7
7
  import { ExtensionPoint, TabLocation } from '@shell/core/types';
8
8
  import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
9
9
  import Tab from '@shell/components/Tabbed/Tab';
10
+ import { ref } from 'vue';
11
+ import { useIsInResourceDetailDrawer } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
12
+ import { useIsInResourceDetailPage } from '@shell/composables/resourceDetail';
13
+ import { useIsInResourceCreatePage, useIsInResourceEditPage } from '@shell/composables/cruResource';
10
14
 
11
15
  export default {
12
16
  name: 'Tabbed',
@@ -105,7 +109,14 @@ export default {
105
109
  },
106
110
 
107
111
  data() {
108
- const extensionTabs = this.showExtensionTabs ? getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route, this, this.extensionParams) || [] : [];
112
+ const location = this.getInitialTabLocation();
113
+ let extensionTabs = this.showExtensionTabs ? getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, location, this.$route, this, this.extensionParams) || [] : [];
114
+ const legacyExtensionTabs = this.showExtensionTabs ? getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route, this, this.extensionParams) || [] : [];
115
+
116
+ if (!extensionTabs.length) {
117
+ // Support legacy tabs for RESOURCE_DETAIL location
118
+ extensionTabs = legacyExtensionTabs;
119
+ }
109
120
 
110
121
  const parsedExtTabs = extensionTabs.map((item) => {
111
122
  return {
@@ -130,7 +141,18 @@ export default {
130
141
  // hide tabs based on tab count IF flag is active
131
142
  hideTabs() {
132
143
  return this.hideSingleTab && this.sortedTabs.length === 1;
133
- }
144
+ },
145
+ },
146
+
147
+ setup() {
148
+ const isInResourceDetailDrawer = ref(useIsInResourceDetailDrawer());
149
+ const isInResourceDetailPage = ref(useIsInResourceDetailPage());
150
+ const isInResourceEditPage = ref(useIsInResourceEditPage());
151
+ const isInResourceCreatePage = ref(useIsInResourceCreatePage());
152
+
153
+ return {
154
+ isInResourceDetailDrawer, isInResourceDetailPage, isInResourceEditPage, isInResourceCreatePage
155
+ };
134
156
  },
135
157
 
136
158
  watch: {
@@ -166,6 +188,19 @@ export default {
166
188
  },
167
189
 
168
190
  methods: {
191
+ getInitialTabLocation() {
192
+ if (this.isInResourceEditPage) {
193
+ return TabLocation.RESOURCE_EDIT_PAGE;
194
+ } else if (this.isInResourceDetailDrawer) {
195
+ return TabLocation.RESOURCE_SHOW_CONFIGURATION;
196
+ } else if (this.isInResourceDetailPage) {
197
+ return TabLocation.RESOURCE_DETAIL_PAGE;
198
+ } else if (this.isInResourceCreatePage) {
199
+ return TabLocation.RESOURCE_CREATE_PAGE;
200
+ } else {
201
+ return TabLocation.ALL;
202
+ }
203
+ },
169
204
  hasIcon(tab) {
170
205
  return tab.displayAlertIcon || (tab.error && !tab.active);
171
206
  },
@@ -34,9 +34,9 @@ export default {
34
34
  });
35
35
 
36
36
  await loadPlugins({
37
- app: this.$store.app,
38
- store: this.$store,
39
- $plugin: this.$store.$plugin
37
+ app: this.$store.app,
38
+ store: this.$store,
39
+ $extension: this.$store.$extension,
40
40
  });
41
41
 
42
42
  buttonCb(true);
@@ -181,7 +181,7 @@ export default {
181
181
  :options="selectNodeOptions"
182
182
  :mode="mode"
183
183
  :data-testid="'node-scheduling-selectNode'"
184
- @input="update"
184
+ @update:value="update"
185
185
  />
186
186
  </div>
187
187
  <template v-if="selectNode === 'nodeSelector'">
@@ -205,7 +205,7 @@ export default {
205
205
  v-model:value="nodeAffinity"
206
206
  :mode="mode"
207
207
  :data-testid="'node-scheduling-nodeAffinity'"
208
- @input="update"
208
+ @update:value="update"
209
209
  />
210
210
  </template>
211
211
  </div>
@@ -39,10 +39,10 @@ export const useTabCountWatcher = () => {
39
39
 
40
40
  export const useTabCountUpdater = () => {
41
41
  const tabKey = randomStr();
42
- const updateCount = inject<UpdateCountFn>(UPDATE_COUNT_PROVIDER_KEY);
42
+ const updateCount = inject<UpdateCountFn>(UPDATE_COUNT_PROVIDER_KEY, () => { });
43
43
 
44
44
  const updateTabCount = (count: number | undefined) => {
45
- updateCount?.(tabKey, count);
45
+ updateCount(tabKey, count);
46
46
  };
47
47
 
48
48
  const clearTabCount = () => updateTabCount(undefined);
@@ -41,6 +41,11 @@ export default {
41
41
  fixedOpen: {
42
42
  type: Boolean,
43
43
  default: false,
44
+ },
45
+
46
+ highlightRoute: {
47
+ type: Boolean,
48
+ default: true,
44
49
  }
45
50
  },
46
51
 
@@ -233,7 +238,7 @@ export default {
233
238
  <template>
234
239
  <div
235
240
  class="accordion"
236
- :class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren, 'group-highlight': isGroupActive }"
241
+ :class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren, 'group-highlight': highlightRoute && isGroupActive }"
237
242
  >
238
243
  <div
239
244
  v-if="showHeader || (!onlyHasOverview && canCollapse)"
@@ -242,7 +247,7 @@ export default {
242
247
  <div
243
248
  v-if="showHeader"
244
249
  class="header"
245
- :class="{'active': isOverview, 'noHover': !canCollapse || fixedOpen}"
250
+ :class="{'active': highlightRoute && isOverview, 'noHover': !canCollapse || fixedOpen}"
246
251
  role="button"
247
252
  :tabindex="fixedOpen ? -1 : 0"
248
253
  :aria-label="group.labelDisplay || group.label || ''"
@@ -311,6 +316,7 @@ export default {
311
316
  :can-collapse="canCollapse"
312
317
  :group="child"
313
318
  :fixed-open="fixedOpen"
319
+ :highlight-route="highlightRoute"
314
320
  @selected="groupSelected($event)"
315
321
  @expand="expandGroup($event)"
316
322
  @close="close($event)"
@@ -322,6 +328,7 @@ export default {
322
328
  :is-root="depth == 0 && !showHeader"
323
329
  :type="child"
324
330
  :depth="depth"
331
+ :highlight-route="highlightRoute"
325
332
  @selected="selectType($event)"
326
333
  />
327
334
  </template>
@@ -254,7 +254,7 @@ export default {
254
254
  this.extensionHeaderActions = getApplicableExtensionEnhancements(this, ExtensionPoint.ACTION, ActionLocation.HEADER, neu);
255
255
  this.updateExtensionActionsEnabled();
256
256
 
257
- this.navHeaderRight = this.$plugin?.getDynamic('component', 'NavHeaderRight');
257
+ this.navHeaderRight = this.$extension?.getDynamic('component', 'NavHeaderRight');
258
258
  }
259
259
  },
260
260
  immediate: true,
@@ -27,6 +27,11 @@ export default {
27
27
  type: Number,
28
28
  default: 0,
29
29
  },
30
+
31
+ highlightRoute: {
32
+ type: Boolean,
33
+ default: true,
34
+ },
30
35
  },
31
36
 
32
37
  data() {
@@ -121,12 +126,12 @@ export default {
121
126
  >
122
127
  <li
123
128
  class="child nav-type"
124
- :class="{'root': isRoot, [`depth-${depth}`]: true, 'router-link-active': isActive, 'router-link-exact-active': isExactActive}"
129
+ :class="{'root': isRoot, [`depth-${depth}`]: true, 'router-link-active': highlightRoute && isActive, 'router-link-exact-active': highlightRoute && isExactActive}"
125
130
  @click="navigate"
126
131
  @keypress.enter="navigate"
127
132
  >
128
133
  <TabTitle
129
- v-if="isExactActive"
134
+ v-if="highlightRoute && isExactActive"
130
135
  :show-child="false"
131
136
  >
132
137
  {{ type.labelKey ? t(type.labelKey) : (type.labelDisplay || type.label) }}
@@ -136,7 +141,7 @@ export default {
136
141
  :aria-label="type.labelKey ? t(type.labelKey) : (type.labelDisplay || type.label)"
137
142
  :href="href"
138
143
  class="type-link"
139
- :aria-current="isActive ? 'page' : undefined"
144
+ :aria-current="highlightRoute && isActive ? 'page' : undefined"
140
145
  @click="selectType(); navigate($event);"
141
146
  @mouseenter="setNear(true)"
142
147
  @mouseleave="setNear(false)"
@@ -250,6 +250,65 @@ describe('component: Type', () => {
250
250
  });
251
251
  });
252
252
 
253
+ describe('should respect highlightRoute prop', () => {
254
+ it('should not use active class when highlightRoute is false even if route is active', () => {
255
+ const wrapper = shallowMount(Type as any, {
256
+ props: { type: defaultRouteTypeProp, highlightRoute: false },
257
+
258
+ global: {
259
+ directives: { cleanHtml: (identity) => identity },
260
+
261
+ mocks: {
262
+ $store: storeMock, $router: routerMock, $route: routeMock
263
+ },
264
+ stubs: { routerLink: createChildRenderingRouterLinkStub() },
265
+ },
266
+ });
267
+
268
+ const elementWithSelector = wrapper.find(`.${ activeClass }`);
269
+
270
+ expect(elementWithSelector.exists()).toBe(false);
271
+ });
272
+
273
+ it('should not use exact active class when highlightRoute is false even if route is exact active', () => {
274
+ const wrapper = shallowMount(Type as any, {
275
+ props: { type: defaultRouteTypeProp, highlightRoute: false },
276
+
277
+ global: {
278
+ directives: { cleanHtml: (identity) => identity },
279
+
280
+ mocks: {
281
+ $store: storeMock, $router: routerMock, $route: routeMock
282
+ },
283
+ stubs: { routerLink: createChildRenderingRouterLinkStub({ isExactActive: true }) },
284
+ },
285
+ });
286
+
287
+ const elementWithSelector = wrapper.find(`.${ exactActiveClass }`);
288
+
289
+ expect(elementWithSelector.exists()).toBe(false);
290
+ });
291
+
292
+ it('should use active class when highlightRoute is true (default) and route is active', () => {
293
+ const wrapper = shallowMount(Type as any, {
294
+ props: { type: defaultRouteTypeProp, highlightRoute: true },
295
+
296
+ global: {
297
+ directives: { cleanHtml: (identity) => identity },
298
+
299
+ mocks: {
300
+ $store: storeMock, $router: routerMock, $route: routeMock
301
+ },
302
+ stubs: { routerLink: createChildRenderingRouterLinkStub() },
303
+ },
304
+ });
305
+
306
+ const elementWithSelector = wrapper.find(`.${ activeClass }`);
307
+
308
+ expect(elementWithSelector.exists()).toBe(true);
309
+ });
310
+ });
311
+
253
312
  describe('should handle the favorite icon appropriately', () => {
254
313
  it('should show favorite icon if mouse is over and type is favorite', async() => {
255
314
  const wrapper = shallowMount(Type as any, {
@@ -0,0 +1,27 @@
1
+ import { inject, provide } from 'vue';
2
+ const IS_IN_RESOURCE_EDIT_PAGE_KEY = 'isInResourceEditKey';
3
+ const IS_IN_RESOURCE_CREATE_PAGE_KEY = 'isInResourceCreateKey';
4
+
5
+ /**
6
+ * Used to determine if the current component was instantiated as an ancestor of a CruResource EDIT page.
7
+ * @returns true if the component is an ancestor of CruResource EDIT page, otherwise false
8
+ */
9
+ export function useIsInResourceEditPage() {
10
+ return inject(IS_IN_RESOURCE_EDIT_PAGE_KEY, false);
11
+ }
12
+
13
+ /**
14
+ * Used to determine if the current component was instantiated as an ancestor of a CruResource CREATE page.
15
+ * @returns true if the component is an ancestor of CruResource CREATE page, otherwise false
16
+ */
17
+ export function useIsInResourceCreatePage() {
18
+ return inject(IS_IN_RESOURCE_CREATE_PAGE_KEY, false);
19
+ }
20
+
21
+ export function useResourceEditPageProvider() {
22
+ provide(IS_IN_RESOURCE_EDIT_PAGE_KEY, true);
23
+ }
24
+
25
+ export function useResourceCreatePageProvider() {
26
+ provide(IS_IN_RESOURCE_CREATE_PAGE_KEY, true);
27
+ }
@@ -57,8 +57,10 @@ export function useWatcherBasedSetupFocusTrapWithDestroyIncluded(watchVar:any, f
57
57
 
58
58
  focusTrapInstance = createFocusTrap(focusEl, opts);
59
59
 
60
+ const activate = () => focusTrapInstance.activate();
61
+
60
62
  nextTick(() => {
61
- focusTrapInstance.activate();
63
+ setTimeout(activate, 0);
62
64
  });
63
65
  });
64
66
  } else if (!neu && focusTrapInstance && Object.keys(focusTrapInstance).length && !useUnmountHook) {
@@ -0,0 +1,15 @@
1
+ import { inject, provide } from 'vue';
2
+
3
+ const IS_IN_RESOURCE_DETAIL_PAGE_KEY = 'isInResourceDetailKey';
4
+
5
+ /**
6
+ * Used to determine if the current component was instantiated as an ancestor of a ResourceDetail.
7
+ * @returns true if the component is an ancestor of ResourceDetail, otherwise false
8
+ */
9
+ export function useIsInResourceDetailPage() {
10
+ return inject(IS_IN_RESOURCE_DETAIL_PAGE_KEY, false);
11
+ }
12
+
13
+ export function useResourceDetailPageProvider() {
14
+ provide(IS_IN_RESOURCE_DETAIL_PAGE_KEY, true);
15
+ }
@@ -30,12 +30,12 @@ export async function loadClusters(to, from, next, { store }) {
30
30
  const oldPkg = getPackageFromRoute(from);
31
31
  const oldProduct = getProductFromRoute(from);
32
32
 
33
- // TODO: Replace all references to store.$plugin.
33
+ // TODO: Replace all references to store.$extension.
34
34
  // Unfortunately the initialization code has circular dependencies between creating
35
35
  // the router and creating the store that will need to be untangled before this can be tackled.
36
36
 
37
37
  // Leave an old pkg where we weren't before?
38
- const oldPkgPlugin = oldPkg ? Object.values(store.$plugin.getPlugins()).find((p) => p.name === oldPkg) : null;
38
+ const oldPkgPlugin = oldPkg ? Object.values(store.$extension.getPlugins()).find((p) => p.name === oldPkg) : null;
39
39
 
40
40
  if (oldPkg && oldPkg !== pkg ) {
41
41
  // Execute anything optional the plugin wants to. For example resetting it's store to remove data
@@ -53,7 +53,7 @@ export async function loadClusters(to, from, next, { store }) {
53
53
  ];
54
54
 
55
55
  // Entering a new package where we weren't before?
56
- const newPkgPlugin = pkg ? Object.values(store.$plugin.getPlugins()).find((p) => p.name === pkg) : null;
56
+ const newPkgPlugin = pkg ? Object.values(store.$extension.getPlugins()).find((p) => p.name === pkg) : null;
57
57
 
58
58
  // Note - We can't block on oldPkg !== newPkg because on a fresh load the `from` route equals the `to` route
59
59
  if (pkg && (oldPkg !== pkg || from.fullPath === to.fullPath)) {
@@ -9,7 +9,7 @@ export async function loadProducts(to, from, next, { store }) {
9
9
  // GC should be notified of route change before any find/get request is made that might be used for that page
10
10
  store.dispatch('gcRouteChanged', to);
11
11
 
12
- await applyProducts(store, store.$plugin);
12
+ await applyProducts(store, store.$extension);
13
13
  setProduct(store, to);
14
14
  next();
15
15
  }