@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
@@ -68,8 +68,9 @@ export default {
68
68
  },
69
69
 
70
70
  isActive() {
71
- const typeFullPath = this.$router.resolve(this.typeRoute)?.fullPath.toLowerCase();
72
- const pageFullPath = this.$route.fullPath?.toLowerCase().split('#')[0]; // Ignore the shebang when comparing routes
71
+ // Use .path instead of .fullPath to ignore query parameters and hashes when comparing routes
72
+ const typePath = this.$router.resolve(this.typeRoute)?.path.toLowerCase();
73
+ const pagePath = this.$route.path?.toLowerCase();
73
74
  const routeMetaNav = this.$route.meta?.nav;
74
75
 
75
76
  // If the route explicitly declares the nav path that should be highlighted, then use that
@@ -80,14 +81,14 @@ export default {
80
81
  .replace(':cluster', cluster)
81
82
  .replace(':product', product);
82
83
 
83
- if (navPath === typeFullPath) {
84
+ if (navPath === typePath) {
84
85
  return true;
85
86
  }
86
87
  }
87
88
 
88
89
  if ( !this.type.exact) {
89
- const typeSplit = typeFullPath.split('/');
90
- const pageSplit = pageFullPath.split('/');
90
+ const typeSplit = typePath.split('/');
91
+ const pageSplit = pagePath.split('/');
91
92
 
92
93
  for (let index = 0; index < typeSplit.length; ++index) {
93
94
  if ( index >= pageSplit.length || typeSplit[index] !== pageSplit[index] ) {
@@ -98,7 +99,7 @@ export default {
98
99
  return true;
99
100
  }
100
101
 
101
- return typeFullPath === pageFullPath;
102
+ return typePath === pagePath;
102
103
  },
103
104
 
104
105
  typeRoute() {
@@ -131,7 +132,7 @@ export default {
131
132
  <router-link
132
133
  v-if="type.route"
133
134
  :key="type.name"
134
- v-slot="{ href, navigate,isExactActive }"
135
+ v-slot="{ href, navigate, isExactActive }"
135
136
  custom
136
137
  :to="typeRoute"
137
138
  >
@@ -36,7 +36,7 @@ const props = defineProps({
36
36
 
37
37
  const { loadComponent } = useComponentsMount();
38
38
 
39
- const { isPanelEnabled } = usePanelsHandler({ layout: props.layout, positions: props.positions });
39
+ const { isPanelEnabled } = usePanelsHandler(props);
40
40
  const { tabs } = useTabsHandler();
41
41
  </script>
42
42
 
@@ -66,6 +66,7 @@ const { tabs } = useTabsHandler();
66
66
  :active="true"
67
67
  :height="tab.containerHeight"
68
68
  :width="tab.containerWidth"
69
+ :layout="layout"
69
70
  v-bind="tab.attrs"
70
71
  />
71
72
  </keep-alive>
@@ -8,6 +8,13 @@ export default {
8
8
  name: 'WorkspaceSwitcher',
9
9
  components: { Select },
10
10
 
11
+ props: {
12
+ disabled: {
13
+ type: Boolean,
14
+ default: false,
15
+ },
16
+ },
17
+
11
18
  computed: {
12
19
  ...mapState(['allWorkspaces', 'workspace', 'allNamespaces', 'defaultNamespace', 'getActiveNamespaces']),
13
20
 
@@ -94,6 +101,7 @@ export default {
94
101
  label="label"
95
102
  :options="options"
96
103
  :clearable="false"
104
+ :disabled="disabled"
97
105
  :reduce="(opt) => opt.value"
98
106
  />
99
107
  <!--button v-shortkey.once="['w']" class="hide" @shortkey="focus()" /-->
@@ -187,4 +195,9 @@ export default {
187
195
  .filter :deep() .unlabeled-select INPUT[type='search'] {
188
196
  padding: 7px;
189
197
  }
198
+
199
+ .filter :deep() .unlabeled-select.disabled {
200
+ opacity: 0.5;
201
+ cursor: not-allowed;
202
+ }
190
203
  </style>
@@ -0,0 +1,67 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import Group from '@shell/components/nav/Group.vue';
3
+
4
+ describe('component: Group', () => {
5
+ it('isOverview ignores query parameters and hash strings when checking active state', () => {
6
+ const group = {
7
+ name: 'test',
8
+ children: [
9
+ {
10
+ route: { name: 'overview-route' },
11
+ overview: true
12
+ }
13
+ ]
14
+ };
15
+
16
+ const wrapper = shallowMount(Group as any, {
17
+ props: {
18
+ group, canCollapse: true, idPrefix: ''
19
+ },
20
+ global: {
21
+ mocks: {
22
+ $route: { path: '/test/route', fullPath: '/test/route?query=val#hash' },
23
+ $router: {
24
+ resolve: jest.fn().mockReturnValue({ path: '/test/route', fullPath: '/test/route' }),
25
+ getRoutes: jest.fn().mockReturnValue([])
26
+ },
27
+ t: (key: string) => key
28
+ }
29
+ }
30
+ });
31
+
32
+ expect((wrapper.vm as any).isOverview).toBe(true);
33
+ });
34
+
35
+ it('hasActiveRoute ignores query parameters when checking item paths', () => {
36
+ const group = {
37
+ name: 'test',
38
+ children: [
39
+ { route: { name: 'child-route', params: {} } }
40
+ ]
41
+ };
42
+
43
+ const wrapper = shallowMount(Group as any, {
44
+ props: {
45
+ group, canCollapse: true, idPrefix: ''
46
+ },
47
+ global: {
48
+ mocks: {
49
+ $route: {
50
+ params: {},
51
+ hash: '#hash',
52
+ path: '/child/route',
53
+ fullPath: '/child/route?query=val#hash',
54
+ matched: []
55
+ },
56
+ $router: {
57
+ resolve: jest.fn().mockReturnValue({ path: '/child/route', fullPath: '/child/route' }),
58
+ getRoutes: jest.fn().mockReturnValue([])
59
+ },
60
+ t: (key: string) => key
61
+ }
62
+ }
63
+ });
64
+
65
+ expect((wrapper.vm as any).hasActiveRoute()).toBe(true);
66
+ });
67
+ });
@@ -0,0 +1,235 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import Header from '@shell/components/nav/Header.vue';
3
+
4
+ describe('component: Header', () => {
5
+ const defaultStoreMock = {
6
+ getters: {
7
+ clusterReady: false,
8
+ isExplorer: false,
9
+ isRancher: false,
10
+ currentCluster: null,
11
+ currentProduct: null,
12
+ rootProduct: { name: 'fleet' },
13
+ backToRancherLink: '',
14
+ backToRancherGlobalLink: '',
15
+ pageActions: [],
16
+ isSingleProduct: false,
17
+ isRancherInHarvester: false,
18
+ showTopLevelMenu: false,
19
+ showWorkspaceSwitcher: true,
20
+ 'management/schemaFor': () => null,
21
+ 'management/all': () => [],
22
+ 'rancher/schemaFor': () => null,
23
+ 'rancher/byId': () => null,
24
+ 'rancher/all': () => [],
25
+ 'auth/principalId': 'test',
26
+ 'auth/enabled': false,
27
+ 'i18n/withFallback': () => '',
28
+ },
29
+ dispatch: jest.fn(),
30
+ commit: jest.fn(),
31
+ };
32
+
33
+ const defaultRouteMock = {
34
+ name: 'c-cluster-fleet-application-resource',
35
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo',
36
+ params: { resource: 'fleet.cattle.io.gitrepo' },
37
+ };
38
+
39
+ const defaultConfigMock = { rancherEnv: 'web' };
40
+
41
+ function createWrapper(routeOverride = {}, storeOverride = {}) {
42
+ const routeMock = {
43
+ ...defaultRouteMock,
44
+ ...routeOverride,
45
+ };
46
+
47
+ const storeMock = {
48
+ ...defaultStoreMock,
49
+ getters: {
50
+ ...defaultStoreMock.getters,
51
+ ...storeOverride,
52
+ },
53
+ dispatch: jest.fn(),
54
+ commit: jest.fn(),
55
+ };
56
+
57
+ return shallowMount(Header as any, {
58
+ global: {
59
+ mocks: {
60
+ $store: storeMock,
61
+ $route: routeMock,
62
+ $config: defaultConfigMock,
63
+ $extension: { getDynamic: jest.fn() },
64
+ },
65
+ stubs: {
66
+ 'router-link': { template: '<a><slot /></a>' },
67
+ BrandImage: { template: '<span />' },
68
+ ClusterProviderIcon: { template: '<span />' },
69
+ ClusterBadge: { template: '<span />' },
70
+ TopLevelMenu: { template: '<div />' },
71
+ NamespaceFilter: { template: '<div />' },
72
+ WorkspaceSwitcher: { template: '<div />' },
73
+ IconOrSvg: { template: '<span />' },
74
+ AppModal: { template: '<div />' },
75
+ NotificationCenter: { template: '<div />' },
76
+ HeaderPageActionMenu: { template: '<div />' },
77
+ RcDropdown: { template: '<div><slot /><slot name="dropdownCollection" /></div>' },
78
+ RcDropdownItem: { template: '<div><slot /></div>' },
79
+ RcDropdownSeparator: { template: '<hr />' },
80
+ RcDropdownTrigger: { template: '<button><slot /></button>' },
81
+ },
82
+ },
83
+ });
84
+ }
85
+
86
+ describe('disableWorkspaceSwitcher', () => {
87
+ it('should return false on a list page', () => {
88
+ const wrapper = createWrapper({
89
+ name: 'c-cluster-fleet-application-resource',
90
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo',
91
+ params: { resource: 'fleet.cattle.io.gitrepo' },
92
+ });
93
+
94
+ expect((wrapper.vm as any).disableWorkspaceSwitcher).toBe(false);
95
+ });
96
+
97
+ it('should return true on a detail page (route has an id param)', () => {
98
+ const wrapper = createWrapper({
99
+ name: 'c-cluster-fleet-application-resource-namespace-id',
100
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo/fleet-default/my-repo',
101
+ params: {
102
+ resource: 'fleet.cattle.io.gitrepo', namespace: 'fleet-default', id: 'my-repo'
103
+ },
104
+ });
105
+
106
+ expect((wrapper.vm as any).disableWorkspaceSwitcher).toBe(true);
107
+ });
108
+
109
+ it('should return true on a create page (route name ends with -create)', () => {
110
+ const wrapper = createWrapper({
111
+ name: 'c-cluster-fleet-application-resource-create',
112
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo/create',
113
+ params: { resource: 'fleet.cattle.io.gitrepo' },
114
+ });
115
+
116
+ expect((wrapper.vm as any).disableWorkspaceSwitcher).toBe(true);
117
+ });
118
+
119
+ it('should return true on the application create page', () => {
120
+ const wrapper = createWrapper({
121
+ name: 'c-cluster-fleet-application-create',
122
+ path: '/c/local/fleet/application/create',
123
+ params: {},
124
+ });
125
+
126
+ expect((wrapper.vm as any).disableWorkspaceSwitcher).toBe(true);
127
+ });
128
+
129
+ it('should return false on the Workspaces list page', () => {
130
+ const wrapper = createWrapper({
131
+ name: 'c-cluster-fleet-application-resource',
132
+ path: '/c/local/fleet/application/management.cattle.io.fleetworkspace',
133
+ params: { resource: 'management.cattle.io.fleetworkspace' },
134
+ });
135
+
136
+ expect((wrapper.vm as any).disableWorkspaceSwitcher).toBe(false);
137
+ });
138
+
139
+ it('should return false on a non-workspace resource list page', () => {
140
+ const wrapper = createWrapper({
141
+ name: 'c-cluster-fleet-application-resource',
142
+ path: '/c/local/fleet/application/fleet.cattle.io.cluster',
143
+ params: { resource: 'fleet.cattle.io.cluster' },
144
+ });
145
+
146
+ expect((wrapper.vm as any).disableWorkspaceSwitcher).toBe(false);
147
+ });
148
+
149
+ it('should return true on an edit page (route has an id param)', () => {
150
+ const wrapper = createWrapper({
151
+ name: 'c-cluster-fleet-application-resource-namespace-id',
152
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo/fleet-default/my-repo?mode=edit',
153
+ params: {
154
+ resource: 'fleet.cattle.io.gitrepo', namespace: 'fleet-default', id: 'my-repo'
155
+ },
156
+ });
157
+
158
+ expect((wrapper.vm as any).disableWorkspaceSwitcher).toBe(true);
159
+ });
160
+ });
161
+
162
+ describe('showFilter', () => {
163
+ it('should return true on a list page when showWorkspaceSwitcher is enabled', () => {
164
+ const wrapper = createWrapper(
165
+ {
166
+ name: 'c-cluster-fleet-application-resource',
167
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo',
168
+ params: { resource: 'fleet.cattle.io.gitrepo' },
169
+ },
170
+ { currentProduct: { showWorkspaceSwitcher: true } },
171
+ );
172
+
173
+ expect((wrapper.vm as any).showFilter).toBe(true);
174
+ });
175
+
176
+ it('should return true when showWorkspaceSwitcher is enabled on a detail page (switcher visible but disabled)', () => {
177
+ const wrapper = createWrapper(
178
+ {
179
+ name: 'c-cluster-fleet-application-resource-namespace-id',
180
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo/fleet-default/my-repo',
181
+ params: {
182
+ resource: 'fleet.cattle.io.gitrepo', namespace: 'fleet-default', id: 'my-repo'
183
+ },
184
+ },
185
+ { currentProduct: { showWorkspaceSwitcher: true } },
186
+ );
187
+
188
+ expect((wrapper.vm as any).showFilter).toBe(true);
189
+ });
190
+
191
+ it('should return true when showWorkspaceSwitcher is enabled on a create page (switcher visible but disabled)', () => {
192
+ const wrapper = createWrapper(
193
+ {
194
+ name: 'c-cluster-fleet-application-resource-create',
195
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo/create',
196
+ params: { resource: 'fleet.cattle.io.gitrepo' },
197
+ },
198
+ { currentProduct: { showWorkspaceSwitcher: true } },
199
+ );
200
+
201
+ expect((wrapper.vm as any).showFilter).toBe(true);
202
+ });
203
+
204
+ it('should return false when showWorkspaceSwitcher is false in the store (e.g. Workspaces page)', () => {
205
+ const wrapper = createWrapper(
206
+ {
207
+ name: 'c-cluster-fleet-application-resource',
208
+ path: '/c/local/fleet/application/management.cattle.io.fleetworkspace',
209
+ params: { resource: 'management.cattle.io.fleetworkspace' },
210
+ },
211
+ {
212
+ currentProduct: { showWorkspaceSwitcher: true },
213
+ showWorkspaceSwitcher: false,
214
+ },
215
+ );
216
+
217
+ expect((wrapper.vm as any).showFilter).toBe(false);
218
+ });
219
+
220
+ it('should return true when showNamespaceFilter is enabled regardless of route', () => {
221
+ const wrapper = createWrapper(
222
+ {
223
+ name: 'c-cluster-fleet-application-resource-namespace-id',
224
+ path: '/c/local/fleet/application/fleet.cattle.io.gitrepo/fleet-default/my-repo',
225
+ params: {
226
+ resource: 'fleet.cattle.io.gitrepo', namespace: 'fleet-default', id: 'my-repo'
227
+ },
228
+ },
229
+ { currentCluster: { id: 'local' }, currentProduct: { showNamespaceFilter: true } },
230
+ );
231
+
232
+ expect((wrapper.vm as any).showFilter).toBe(true);
233
+ });
234
+ });
235
+ });
@@ -28,10 +28,10 @@ describe('component: Type', () => {
28
28
  };
29
29
  const routerMock = {
30
30
  resolve: jest.fn((route) => {
31
- return { fullPath: route };
31
+ return { fullPath: route, path: route };
32
32
  })
33
33
  };
34
- const routeMock = { fullPath: 'route' };
34
+ const routeMock = { fullPath: 'route', path: 'route' };
35
35
 
36
36
  describe('should pass props correctly', () => {
37
37
  it('should forward Type props to router-link', () => {
@@ -104,7 +104,7 @@ describe('component: Type', () => {
104
104
  directives: { cleanHtml: (identity) => identity },
105
105
 
106
106
  mocks: {
107
- $store: storeMock, $router: routerMock, $route: { fullPath: 'bad' }
107
+ $store: storeMock, $router: routerMock, $route: { fullPath: 'bad', path: 'bad' }
108
108
  },
109
109
  stubs: { routerLink: createChildRenderingRouterLinkStub() },
110
110
  },
@@ -152,6 +152,23 @@ describe('component: Type', () => {
152
152
 
153
153
  expect(elementWithSelector.exists()).toBe(false);
154
154
  });
155
+ it('should use active and exact active classes when route matches but includes query and hash', () => {
156
+ const wrapper = shallowMount(Type as any, {
157
+ props: { type: defaultRouteTypeProp, highlightRoute: true },
158
+
159
+ global: {
160
+ directives: { cleanHtml: (identity) => identity },
161
+
162
+ mocks: {
163
+ $store: storeMock, $router: routerMock, $route: { fullPath: 'route?repo=test#myhash', path: 'route' }
164
+ },
165
+ stubs: { routerLink: createChildRenderingRouterLinkStub({ isExactActive: true }) },
166
+ },
167
+ });
168
+
169
+ expect(wrapper.find(`.${ activeClass }`).exists()).toBe(true);
170
+ expect(wrapper.find(`.${ exactActiveClass }`).exists()).toBe(true);
171
+ });
155
172
  });
156
173
 
157
174
  describe('should use classes if preconditions are met', () => {
@@ -9,7 +9,6 @@ import ActionMenu from '@shell/components/ActionMenu';
9
9
  import GrowlManager from '@shell/components/GrowlManager';
10
10
  import ModalManager from '@shell/components/ModalManager';
11
11
  import SlideInPanelManager from '@shell/components/SlideInPanelManager';
12
- import WindowManager from '@shell/components/nav/WindowManager';
13
12
  import PromptRemove from '@shell/components/PromptRemove';
14
13
  import PromptRestore from '@shell/components/PromptRestore';
15
14
  import PromptModal from '@shell/components/PromptModal';
@@ -39,7 +38,6 @@ export default {
39
38
  GrowlManager,
40
39
  ModalManager,
41
40
  SlideInPanelManager,
42
- WindowManager,
43
41
  FixedBanner,
44
42
  AwsComplianceBanner,
45
43
  Inactivity,
@@ -48,10 +46,11 @@ export default {
48
46
 
49
47
  mixins: [PageHeaderActions, Brand, BrowserTabVisibility],
50
48
 
49
+ inject: ['notifyWmContainerReady'],
50
+
51
51
  // Note - This will not run on route change
52
52
  data() {
53
53
  return {
54
- layout: Layout.default,
55
54
  noLocaleShortcut: process.env.dev || false,
56
55
  wantNavSync: false,
57
56
  };
@@ -104,6 +103,10 @@ export default {
104
103
  },
105
104
  },
106
105
 
106
+ mounted() {
107
+ this.notifyWmContainerReady(Layout.default);
108
+ },
109
+
107
110
  methods: {
108
111
 
109
112
  handlePageAction(action) {
@@ -159,6 +162,10 @@ export default {
159
162
 
160
163
  <template>
161
164
  <div class="dashboard-root">
165
+ <a
166
+ href="#main-content"
167
+ class="skip-to-content btn role-primary"
168
+ >{{ t('nav.skipToContent') }}</a>
162
169
  <FixedBanner :header="true" />
163
170
  <AwsComplianceBanner v-if="managementReady" />
164
171
  <div
@@ -173,8 +180,10 @@ export default {
173
180
  />
174
181
  <main
175
182
  v-if="clusterAndRouteReady"
183
+ id="main-content"
176
184
  class="main-layout"
177
185
  :aria-label="t('layouts.default')"
186
+ tabindex="-1"
178
187
  >
179
188
  <router-view
180
189
  :key="$route.path"
@@ -211,15 +220,22 @@ export default {
211
220
  <!-- Ensure there's an outlet to show the error (404) page -->
212
221
  <main
213
222
  v-else-if="unmatchedRoute"
223
+ id="main-content"
214
224
  class="main-layout"
215
225
  :aria-label="t('layouts.default')"
226
+ tabindex="-1"
216
227
  >
217
228
  <router-view
218
229
  :key="$route.path"
219
230
  class="outlet"
220
231
  />
221
232
  </main>
222
- <WindowManager :layout="layout" />
233
+ <!-- Teleport target for WindowManager (unique per layout) -->
234
+ <!-- display: contents makes child panels become grid items of the parent grid -->
235
+ <div
236
+ id="wm-container-default"
237
+ style="display: contents;"
238
+ />
223
239
  </div>
224
240
  <FixedBanner :footer="true" />
225
241
  <GrowlManager />
@@ -227,3 +243,17 @@ export default {
227
243
  <Inactivity />
228
244
  </div>
229
245
  </template>
246
+
247
+ <style lang="scss" scoped>
248
+ .skip-to-content {
249
+ position: fixed;
250
+ top: 0;
251
+ left: 0;
252
+ z-index: 9999;
253
+ transform: translateY(-100%);
254
+
255
+ &:focus {
256
+ transform: translate(1rem, 1rem);
257
+ }
258
+ }
259
+ </style>
@@ -11,7 +11,6 @@ import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
11
11
  import Inactivity from '@shell/components/Inactivity';
12
12
  import { mapState, mapGetters } from 'vuex';
13
13
  import PromptModal from '@shell/components/PromptModal';
14
- import WindowManager from '@shell/components/nav/WindowManager';
15
14
  import { Layout } from '@shell/types/window-manager';
16
15
 
17
16
  export default {
@@ -25,14 +24,14 @@ export default {
25
24
  AwsComplianceBanner,
26
25
  Inactivity,
27
26
  PromptModal,
28
- WindowManager
29
27
  },
30
28
 
31
29
  mixins: [Brand, BrowserTabVisibility],
32
30
 
31
+ inject: ['notifyWmContainerReady'],
32
+
33
33
  data() {
34
34
  return {
35
- layout: Layout.home,
36
35
  // Assume home pages have routes where the name is the key to use for string lookup
37
36
  name: this.$route.name,
38
37
  noLocaleShortcut: process.env.dev || false,
@@ -45,6 +44,10 @@ export default {
45
44
  ...mapGetters(['showTopLevelMenu']),
46
45
  },
47
46
 
47
+ mounted() {
48
+ this.notifyWmContainerReady(Layout.home);
49
+ },
50
+
48
51
  methods: {
49
52
  toggleTheme() {
50
53
  this.$store.dispatch('prefs/toggleTheme');
@@ -82,7 +85,12 @@ export default {
82
85
  class="outlet"
83
86
  />
84
87
  </main>
85
- <WindowManager :layout="layout" />
88
+ <!-- Teleport target for WindowManager (unique per layout) -->
89
+ <!-- display: contents makes child panels become grid items of the parent grid -->
90
+ <div
91
+ id="wm-container-home"
92
+ style="display: contents;"
93
+ />
86
94
  </div>
87
95
  <FixedBanner :footer="true" />
88
96
  <GrowlManager />
@@ -125,27 +133,6 @@ export default {
125
133
  }
126
134
  }
127
135
 
128
- .wm {
129
- grid-area: wm;
130
- overflow-y: hidden;
131
- z-index: z-index('windowsManager');
132
- position: relative;
133
- }
134
-
135
- .wm-vr {
136
- grid-area: wm-vr;
137
- overflow-y: hidden;
138
- z-index: z-index('windowsManager');
139
- position: relative;
140
- }
141
-
142
- .wm-vl {
143
- grid-area: wm-vl;
144
- overflow-y: hidden;
145
- z-index: z-index('windowsManager');
146
- position: relative;
147
- }
148
-
149
136
  MAIN {
150
137
  grid-area: main;
151
138
  overflow: auto;