@rancher/shell 3.0.12-rc.1 → 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 (134) 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/translations/en-us.yaml +19 -17
  6. package/assets/translations/zh-hans.yaml +4 -8
  7. package/chart/__tests__/S3.test.ts +10 -3
  8. package/components/CountBox.vue +20 -0
  9. package/components/CreateDriver.vue +0 -12
  10. package/components/DetailText.vue +12 -3
  11. package/components/SelectIconGrid.vue +5 -0
  12. package/components/__tests__/CountBox.test.ts +72 -0
  13. package/components/__tests__/DetailText.test.ts +113 -0
  14. package/components/fleet/FleetClusterTargets/index.vue +18 -1
  15. package/components/form/InputWithSelect.vue +18 -10
  16. package/components/form/KeyValue.vue +17 -1
  17. package/components/form/LabeledSelect.vue +82 -24
  18. package/components/form/Select.vue +73 -56
  19. package/components/form/ServiceNameSelect.vue +13 -11
  20. package/components/form/__tests__/KeyValue.test.ts +66 -0
  21. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  22. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  23. package/components/nav/Group.vue +7 -6
  24. package/components/nav/Header.vue +24 -3
  25. package/components/nav/NotificationCenter/Notification.vue +4 -1
  26. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  27. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  28. package/components/nav/Type.vue +8 -7
  29. package/components/nav/WindowManager/index.vue +2 -1
  30. package/components/nav/WorkspaceSwitcher.vue +13 -0
  31. package/components/nav/__tests__/Group.test.ts +67 -0
  32. package/components/nav/__tests__/Header.test.ts +235 -0
  33. package/components/nav/__tests__/Type.test.ts +20 -3
  34. package/components/templates/default.vue +34 -4
  35. package/components/templates/home.vue +12 -25
  36. package/components/templates/plain.vue +13 -26
  37. package/composables/useLabeledFormElement.ts +10 -2
  38. package/composables/useLabeledSelect.ts +60 -0
  39. package/composables/useUserRetentionValidation.ts +1 -49
  40. package/config/cookies.js +0 -1
  41. package/config/labels-annotations.js +1 -0
  42. package/config/query-params.js +1 -0
  43. package/config/router/routes.js +0 -8
  44. package/core/__tests__/plugin-products.test.ts +616 -25
  45. package/core/plugin-products-base.ts +31 -14
  46. package/core/plugin-products-helpers.ts +5 -4
  47. package/core/plugin-types.ts +18 -3
  48. package/core/types.ts +3 -1
  49. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  50. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  51. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  52. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  53. package/edit/__tests__/nodeDriver.test.ts +5 -11
  54. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  55. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  56. package/edit/auth/__tests__/oidc.test.ts +54 -0
  57. package/edit/auth/azuread.vue +1 -1
  58. package/edit/auth/oidc.vue +8 -0
  59. package/edit/kontainerDriver.vue +1 -2
  60. package/edit/nodeDriver.vue +0 -2
  61. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  62. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  63. package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
  64. package/initialize/App.vue +29 -2
  65. package/initialize/install-plugins.js +0 -2
  66. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  67. package/list/catalog.cattle.io.app.vue +25 -5
  68. package/list/management.cattle.io.feature.vue +1 -1
  69. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  70. package/machine-config/amazonec2.vue +1 -0
  71. package/mixins/chart.js +40 -9
  72. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  73. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  74. package/models/__tests__/chart.test.ts +99 -6
  75. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  76. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  77. package/models/catalog.cattle.io.app.js +21 -17
  78. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  79. package/models/chart.js +33 -19
  80. package/models/fleet-application.js +1 -1
  81. package/models/fleet.cattle.io.bundle.js +1 -1
  82. package/models/kontainerdriver.js +11 -0
  83. package/models/management.cattle.io.authconfig.js +5 -1
  84. package/models/management.cattle.io.cluster.js +0 -53
  85. package/models/management.cattle.io.feature.js +3 -3
  86. package/models/management.cattle.io.kontainerdriver.js +1 -26
  87. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  88. package/models/nodedriver.js +7 -0
  89. package/package.json +13 -12
  90. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
  91. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  92. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  93. package/pages/c/_cluster/apps/charts/chart.vue +217 -33
  94. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  95. package/pages/c/_cluster/apps/charts/install.vue +8 -3
  96. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  97. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  98. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
  99. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  100. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +15 -10
  101. package/pages/c/_cluster/uiplugins/index.vue +23 -25
  102. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
  103. package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
  104. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  105. package/scripts/test-plugins-build.sh +5 -2
  106. package/server/server-middleware.js +2 -2
  107. package/static/humans.txt +1 -0
  108. package/static/robots.txt +34 -0
  109. package/static/welcome-cow.svg +18 -0
  110. package/store/__tests__/catalog.test.ts +161 -11
  111. package/store/auth.js +0 -3
  112. package/store/catalog.js +60 -8
  113. package/types/shell/index.d.ts +26 -22
  114. package/utils/__tests__/git.test.ts +270 -0
  115. package/utils/__tests__/inactivity.test.ts +316 -0
  116. package/utils/__tests__/object.test.ts +77 -0
  117. package/utils/__tests__/time.test.ts +14 -1
  118. package/utils/__tests__/url.test.ts +246 -0
  119. package/utils/object.js +33 -2
  120. package/utils/time.ts +5 -0
  121. package/vue.config.js +0 -9
  122. package/assets/images/providers/azuread-black.svg +0 -22
  123. package/assets/images/providers/azuread.svg +0 -25
  124. package/assets/images/vendor/azuread.svg +0 -18
  125. package/assets/styles/fonts/_dots.scss +0 -18
  126. package/components/EmberPage.vue +0 -622
  127. package/components/EmberPageView.vue +0 -39
  128. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  129. package/mixins/labeled-form-element.ts +0 -225
  130. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  131. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  132. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  133. package/plugins/ember-cookie.js +0 -17
  134. package/utils/ember-page.js +0 -30
@@ -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;
@@ -14,7 +14,6 @@ import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
14
14
  import Inactivity from '@shell/components/Inactivity';
15
15
  import { mapGetters } from 'vuex';
16
16
  import PromptModal from '@shell/components/PromptModal';
17
- import WindowManager from '@shell/components/nav/WindowManager';
18
17
  import { Layout } from '@shell/types/window-manager';
19
18
 
20
19
  export default {
@@ -31,17 +30,17 @@ export default {
31
30
  SlideInPanelManager,
32
31
  AwsComplianceBanner,
33
32
  Inactivity,
34
- WindowManager
35
33
  },
36
34
 
37
35
  mixins: [Brand, BrowserTabVisibility],
38
36
 
37
+ inject: ['notifyWmContainerReady'],
38
+
39
39
  data() {
40
40
  return {
41
41
  // Assume home pages have routes where the name is the key to use for string lookup
42
42
  name: this.$route.name,
43
- noLocaleShortcut: process.env.dev || false,
44
- layout: Layout.plain,
43
+ noLocaleShortcut: process.env.dev || false
45
44
  };
46
45
  },
47
46
 
@@ -50,6 +49,10 @@ export default {
50
49
  ...mapGetters(['showTopLevelMenu']),
51
50
  },
52
51
 
52
+ mounted() {
53
+ this.notifyWmContainerReady(Layout.plain);
54
+ },
55
+
53
56
  methods: {
54
57
  toggleTheme() {
55
58
  this.$store.dispatch('prefs/toggleTheme');
@@ -98,7 +101,12 @@ export default {
98
101
  @shortkey="toggleNoneLocale()"
99
102
  />
100
103
  </main>
101
- <WindowManager :layout="layout" />
104
+ <!-- Teleport target for WindowManager (unique per layout) -->
105
+ <!-- display: contents makes child panels become grid items of the parent grid -->
106
+ <div
107
+ id="wm-container-plain"
108
+ style="display: contents;"
109
+ />
102
110
  </div>
103
111
 
104
112
  <FixedBanner :footer="true" />
@@ -130,27 +138,6 @@ export default {
130
138
  }
131
139
  }
132
140
 
133
- .wm {
134
- grid-area: wm;
135
- overflow-y: hidden;
136
- z-index: z-index('windowsManager');
137
- position: relative;
138
- }
139
-
140
- .wm-vr {
141
- grid-area: wm-vr;
142
- overflow-y: hidden;
143
- z-index: z-index('windowsManager');
144
- position: relative;
145
- }
146
-
147
- .wm-vl {
148
- grid-area: wm-vl;
149
- overflow-y: hidden;
150
- z-index: z-index('windowsManager');
151
- position: relative;
152
- }
153
-
154
141
  MAIN {
155
142
  grid-area: main;
156
143
  overflow: auto;
@@ -6,7 +6,7 @@ import { _VIEW, _EDIT } from '@shell/config/query-params';
6
6
 
7
7
  interface LabeledFormElementProps {
8
8
  mode: string;
9
- value: string | number | Record<string, any>
9
+ value: string | number | Record<string, any> | null
10
10
  required: boolean;
11
11
  disabled: boolean;
12
12
  rules: Array<any>;
@@ -18,6 +18,8 @@ interface UseLabeledFormElement {
18
18
  focused: Ref<boolean>;
19
19
  blurred: Ref<number | null>;
20
20
  requiredField: ComputedRef<any>;
21
+ empty: ComputedRef<boolean>;
22
+ isView: ComputedRef<boolean>;
21
23
  isDisabled: ComputedRef<any>;
22
24
  validationMessage: ComputedRef<any>;
23
25
  onFocusLabeled: () => void;
@@ -46,7 +48,7 @@ export const labeledFormElementProps = {
46
48
  default: null
47
49
  },
48
50
  value: {
49
- type: [String, Number, Object],
51
+ type: [String, Number, Object, null],
50
52
  default: ''
51
53
  },
52
54
  mode: {
@@ -82,6 +84,10 @@ export const useLabeledFormElement = (props: LabeledFormElementProps, emit: Emit
82
84
  return props.required || props.rules?.some((rule: any) => rule?.name === 'required');
83
85
  });
84
86
 
87
+ const empty = computed(() => {
88
+ return !!`${ props.value }`;
89
+ });
90
+
85
91
  const isView = computed(() => {
86
92
  return props.mode === _VIEW;
87
93
  });
@@ -143,6 +149,8 @@ export const useLabeledFormElement = (props: LabeledFormElementProps, emit: Emit
143
149
  raised,
144
150
  focused,
145
151
  blurred,
152
+ empty,
153
+ isView,
146
154
  onFocusLabeled,
147
155
  onBlurLabeled,
148
156
  isDisabled,
@@ -0,0 +1,60 @@
1
+ import { computed, ComputedRef, Ref, nextTick } from 'vue';
2
+ import { getWidth, setWidth } from '@shell/utils/width';
3
+
4
+ interface LabeledSelectProps {
5
+ options?: Array<any>;
6
+ searchable?: boolean;
7
+ filterable?: boolean;
8
+ }
9
+
10
+ interface UseLabeledSelect {
11
+ isSearchable: ComputedRef<boolean>;
12
+ isFilterable: ComputedRef<boolean>;
13
+ resizeHandler: (selectRef: Ref<HTMLElement | null>) => void;
14
+ }
15
+
16
+ export const useLabeledSelect = (props: LabeledSelectProps, canPaginate?: ComputedRef<boolean>): UseLabeledSelect => {
17
+ const isSearchable = computed(() => {
18
+ if (canPaginate?.value) {
19
+ return true;
20
+ }
21
+ const options = props.options || [];
22
+
23
+ if (props.searchable || options.length >= 10) {
24
+ return true;
25
+ }
26
+
27
+ return false;
28
+ });
29
+
30
+ const isFilterable = computed(() => {
31
+ if (canPaginate?.value) {
32
+ return false;
33
+ }
34
+
35
+ return props.filterable ?? true;
36
+ });
37
+
38
+ const resizeHandler = (selectRef: Ref<HTMLElement | null>) => {
39
+ // since the DD is positioned there is no way to 'inherit' the size of the input, this calcs the size of the parent and set the dd width if it is smaller. If not let it grow with the regular styles
40
+ nextTick(() => {
41
+ if (!selectRef.value) {
42
+ return;
43
+ }
44
+
45
+ const DD = selectRef.value.querySelector('ul.vs__dropdown-menu');
46
+ const selectWidth = getWidth(selectRef.value) || 0;
47
+ const dropWidth = getWidth(DD as Element) || 0;
48
+
49
+ if (dropWidth < selectWidth) {
50
+ setWidth(DD as Element, selectWidth);
51
+ }
52
+ });
53
+ };
54
+
55
+ return {
56
+ isSearchable,
57
+ isFilterable,
58
+ resizeHandler
59
+ };
60
+ };