@rancher/shell 3.0.12-rc.5 → 3.0.12-rc.7

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 (116) hide show
  1. package/apis/intf/resources-api/resource-base.ts +42 -2
  2. package/apis/intf/resources-api/resource-instance.ts +101 -0
  3. package/apis/intf/resources-api/resources-api.ts +177 -23
  4. package/apis/intf/resources.ts +8 -6
  5. package/apis/intf/shell-api/slide-in.ts +46 -17
  6. package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
  7. package/apis/resources/resources-api-class.ts +232 -43
  8. package/apis/shell/__tests__/slide-in.test.ts +83 -2
  9. package/apis/shell/slide-in.ts +20 -0
  10. package/assets/images/providers/traefik.png +0 -0
  11. package/assets/translations/en-us.yaml +10 -4
  12. package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
  13. package/chart/rancher-monitoring-dashboards/index.vue +155 -0
  14. package/cloud-credential/__tests__/generic.test.ts +132 -0
  15. package/cloud-credential/generic.vue +33 -3
  16. package/components/EtcdInfoBanner.vue +2 -12
  17. package/components/ExplorerProjectsNamespaces.vue +2 -2
  18. package/components/GrafanaDashboard.vue +2 -8
  19. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
  20. package/components/Resource/Detail/Masthead/index.vue +8 -3
  21. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +9 -0
  22. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -1
  23. package/components/SlideInPanelManager.vue +103 -25
  24. package/components/__tests__/SlideInPanelManager.spec.ts +153 -10
  25. package/components/auth/AuthBanner.vue +1 -1
  26. package/components/form/Security.vue +1 -1
  27. package/components/formatter/Translate.vue +22 -1
  28. package/components/nav/Group.vue +5 -10
  29. package/components/nav/TopLevelMenu.vue +1 -30
  30. package/components/nav/__tests__/Group.test.ts +61 -0
  31. package/config/labels-annotations.js +1 -0
  32. package/config/table-headers.js +5 -2
  33. package/core/__tests__/plugin-products-apply.test.ts +787 -0
  34. package/core/__tests__/plugin-products-extend.test.ts +189 -0
  35. package/core/__tests__/plugin-products-helpers.test.ts +81 -78
  36. package/core/__tests__/plugin-products-new.test.ts +389 -0
  37. package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
  38. package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
  39. package/core/plugin-products-base.ts +201 -93
  40. package/core/plugin-products-extending.ts +1 -1
  41. package/core/plugin-products-external.ts +380 -0
  42. package/core/plugin-products-helpers.ts +28 -24
  43. package/core/plugin-products-internal.ts +207 -0
  44. package/core/plugin-products-top-level.ts +10 -5
  45. package/core/plugin-products-type-guards.ts +17 -6
  46. package/core/plugin-products.ts +5 -7
  47. package/core/plugin-types.ts +1 -389
  48. package/core/plugin.ts +10 -11
  49. package/core/plugins-loader.js +2 -0
  50. package/core/types.ts +6 -202
  51. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
  52. package/detail/pod.vue +1 -1
  53. package/detail/provisioning.cattle.io.cluster.vue +0 -2
  54. package/edit/auth/AuthProviderWarningBanners.vue +12 -0
  55. package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
  56. package/edit/auth/__tests__/azuread.test.ts +1 -0
  57. package/edit/auth/__tests__/github.test.ts +1 -0
  58. package/edit/auth/__tests__/oidc.test.ts +1 -0
  59. package/edit/auth/__tests__/saml.test.ts +1 -0
  60. package/edit/auth/ldap/__tests__/index.test.ts +1 -0
  61. package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
  62. package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
  63. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
  64. package/list/management.cattle.io.user.vue +24 -5
  65. package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
  66. package/machine-config/__tests__/generic.test.ts +163 -0
  67. package/machine-config/components/EC2Networking.vue +31 -7
  68. package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
  69. package/machine-config/generic.vue +32 -5
  70. package/mixins/resource-fetch.js +1 -1
  71. package/models/__tests__/namespace.test.ts +133 -8
  72. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +57 -1
  73. package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
  74. package/models/namespace.js +34 -2
  75. package/models/pod.js +12 -10
  76. package/models/provisioning.cattle.io.cluster.js +20 -13
  77. package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
  78. package/package.json +1 -1
  79. package/pages/auth/login.vue +1 -1
  80. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
  81. package/pages/c/_cluster/apps/charts/install.vue +29 -0
  82. package/pages/c/_cluster/auth/config/index.vue +11 -4
  83. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +136 -1
  84. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +69 -1
  85. package/pages/c/_cluster/fleet/index.vue +5 -9
  86. package/pages/c/_cluster/monitoring/index.vue +6 -1
  87. package/pkg/vue.config.js +4 -3
  88. package/plugins/codemirror.js +2 -0
  89. package/plugins/dashboard-store/resource-class.js +13 -9
  90. package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
  91. package/plugins/steve/steve-class.js +43 -0
  92. package/plugins/steve/subscribe.js +11 -0
  93. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
  94. package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
  95. package/store/__tests__/action-menu.test.ts +622 -0
  96. package/store/__tests__/auth.test.ts +18 -0
  97. package/store/__tests__/notifications.test.ts +466 -1
  98. package/store/__tests__/slideInPanel.test.ts +143 -43
  99. package/store/__tests__/ui-context.test.ts +255 -0
  100. package/store/__tests__/wm.test.ts +503 -0
  101. package/store/auth.js +11 -1
  102. package/store/aws.js +19 -4
  103. package/store/features.js +1 -0
  104. package/store/plugins.js +6 -0
  105. package/store/slideInPanel.ts +15 -3
  106. package/types/rancher/steve.api.ts +2 -2
  107. package/types/shell/index.d.ts +84 -2
  108. package/types/store/dashboard-store.types.ts +2 -2
  109. package/types/store/type-map.ts +262 -1
  110. package/utils/__tests__/fleet-appco.test.ts +23 -0
  111. package/utils/async.ts +2 -0
  112. package/utils/fleet-appco.ts +6 -1
  113. package/utils/grafana.js +2 -17
  114. package/utils/monitoring.js +38 -1
  115. package/utils/sort.js +1 -1
  116. package/core/__tests__/plugin-products.test.ts +0 -4694
@@ -0,0 +1,503 @@
1
+ import {
2
+ state, getters, mutations, actions, State
3
+ } from '@shell/store/wm';
4
+ import { BOTTOM, LEFT, RIGHT } from '@shell/utils/position';
5
+ import { Tab } from '@shell/types/window-manager';
6
+ import { STORAGE_KEY } from '@shell/components/nav/WindowManager/constants';
7
+
8
+ function makeTab(overrides: Partial<Tab> = {}): Tab {
9
+ return {
10
+ id: 'tab1',
11
+ icon: 'icon',
12
+ label: 'Tab 1',
13
+ position: BOTTOM as Tab['position'],
14
+ layouts: ['default'] as Tab['layouts'],
15
+ showHeader: true,
16
+ containerHeight: null,
17
+ containerWidth: null,
18
+ ...overrides,
19
+ } as Tab;
20
+ }
21
+
22
+ describe('wm store', () => {
23
+ let s: State;
24
+
25
+ beforeEach(() => {
26
+ localStorage.clear();
27
+ s = state();
28
+ });
29
+
30
+ afterEach(() => {
31
+ localStorage.clear();
32
+ });
33
+
34
+ describe('state', () => {
35
+ it('returns initial state with empty collections and null userPin', () => {
36
+ expect(s.tabs).toStrictEqual([]);
37
+ expect(s.active).toStrictEqual({});
38
+ expect(s.open).toStrictEqual({});
39
+ expect(s.userPin).toBeNull();
40
+ expect(s.lockedPositions).toStrictEqual([]);
41
+ });
42
+
43
+ it('reads panelHeight for BOTTOM from localStorage on initialization', () => {
44
+ localStorage.setItem(STORAGE_KEY[BOTTOM], '300');
45
+ const fresh = state();
46
+
47
+ expect(fresh.panelHeight[BOTTOM]).toStrictEqual('300');
48
+ });
49
+
50
+ it('reads panelWidth for LEFT and RIGHT from localStorage on initialization', () => {
51
+ localStorage.setItem(STORAGE_KEY[LEFT], '250');
52
+ localStorage.setItem(STORAGE_KEY[RIGHT], '350');
53
+ const fresh = state();
54
+
55
+ expect(fresh.panelWidth[LEFT]).toStrictEqual('250');
56
+ expect(fresh.panelWidth[RIGHT]).toStrictEqual('350');
57
+ });
58
+
59
+ it('panelHeight and panelWidth are null when localStorage is empty', () => {
60
+ expect(s.panelHeight[BOTTOM]).toBeNull();
61
+ expect(s.panelWidth[LEFT]).toBeNull();
62
+ expect(s.panelWidth[RIGHT]).toBeNull();
63
+ });
64
+ });
65
+
66
+ describe('getters', () => {
67
+ describe('byId', () => {
68
+ it('returns the tab when id matches', () => {
69
+ const tab = makeTab({ id: 'abc' });
70
+
71
+ s.tabs = [tab];
72
+
73
+ expect(getters.byId(s)('abc')).toStrictEqual(tab);
74
+ });
75
+
76
+ it('returns undefined when no tab matches the id', () => {
77
+ s.tabs = [makeTab({ id: 'abc' })];
78
+
79
+ expect(getters.byId(s)('xyz')).toBeUndefined();
80
+ });
81
+ });
82
+
83
+ describe('tabs', () => {
84
+ it('returns all tabs from state', () => {
85
+ const tabs = [makeTab({ id: 'a' }), makeTab({ id: 'b' })];
86
+
87
+ s.tabs = tabs;
88
+
89
+ expect(getters.tabs(s)).toStrictEqual(tabs);
90
+ });
91
+ });
92
+
93
+ describe('isOpen', () => {
94
+ it('returns true when the position is open', () => {
95
+ s.open = { [BOTTOM]: true };
96
+
97
+ expect(getters.isOpen(s)(BOTTOM)).toBe(true);
98
+ });
99
+
100
+ it('returns false when the position is explicitly closed', () => {
101
+ s.open = { [BOTTOM]: false };
102
+
103
+ expect(getters.isOpen(s)(BOTTOM)).toBe(false);
104
+ });
105
+
106
+ it('returns undefined when the position is not in the open map', () => {
107
+ expect(getters.isOpen(s)(BOTTOM)).toBeUndefined();
108
+ });
109
+ });
110
+
111
+ describe('panelWidth', () => {
112
+ it('returns the panelWidth map', () => {
113
+ s.panelWidth = { [LEFT]: 250, [RIGHT]: 350 };
114
+
115
+ expect(getters.panelWidth(s)).toStrictEqual({ [LEFT]: 250, [RIGHT]: 350 });
116
+ });
117
+ });
118
+
119
+ describe('panelHeight', () => {
120
+ it('returns the panelHeight map', () => {
121
+ s.panelHeight = { [BOTTOM]: 300 };
122
+
123
+ expect(getters.panelHeight(s)).toStrictEqual({ [BOTTOM]: 300 });
124
+ });
125
+ });
126
+
127
+ describe('userPin', () => {
128
+ it('returns the userPin from state', () => {
129
+ s.userPin = LEFT;
130
+
131
+ expect(getters.userPin(s)).toStrictEqual(LEFT);
132
+ });
133
+ });
134
+
135
+ describe('lockedPositions', () => {
136
+ it('returns the lockedPositions array', () => {
137
+ s.lockedPositions = [BOTTOM as Tab['position']];
138
+
139
+ expect(getters.lockedPositions(s)).toStrictEqual([BOTTOM]);
140
+ });
141
+ });
142
+ });
143
+
144
+ describe('mutations', () => {
145
+ describe('addTab', () => {
146
+ it('adds a new tab to the tabs array', () => {
147
+ mutations.addTab(s, makeTab({ id: 'new-tab' }));
148
+
149
+ expect(s.tabs).toHaveLength(1);
150
+ expect(s.tabs[0].id).toStrictEqual('new-tab');
151
+ });
152
+
153
+ it('does not add a duplicate when a tab with the same id already exists', () => {
154
+ s.tabs = [makeTab({ id: 'dup' })];
155
+ mutations.addTab(s, makeTab({ id: 'dup' }));
156
+
157
+ expect(s.tabs).toHaveLength(1);
158
+ });
159
+
160
+ it('sets position from localStorage pin when position is undefined', () => {
161
+ localStorage.setItem(STORAGE_KEY['pin'], LEFT);
162
+ const tab = makeTab({ id: 't1', position: undefined as any });
163
+
164
+ mutations.addTab(s, tab);
165
+
166
+ expect(s.tabs[0].position).toStrictEqual(LEFT);
167
+ });
168
+
169
+ it('sets position from localStorage pin when position is the string "undefined"', () => {
170
+ localStorage.setItem(STORAGE_KEY['pin'], RIGHT);
171
+ const tab = makeTab({ id: 't1', position: 'undefined' as any });
172
+
173
+ mutations.addTab(s, tab);
174
+
175
+ expect(s.tabs[0].position).toStrictEqual(RIGHT);
176
+ });
177
+
178
+ it('defaults position to BOTTOM when localStorage pin is empty and position is undefined', () => {
179
+ const tab = makeTab({ id: 't1', position: undefined as any });
180
+
181
+ mutations.addTab(s, tab);
182
+
183
+ expect(s.tabs[0].position).toStrictEqual(BOTTOM);
184
+ });
185
+
186
+ it('defaults layouts to ["default"] when layouts is undefined', () => {
187
+ const tab = makeTab({ id: 't1', layouts: undefined as any });
188
+
189
+ mutations.addTab(s, tab);
190
+
191
+ expect(s.tabs[0].layouts).toStrictEqual(['default']);
192
+ });
193
+
194
+ it('preserves existing layouts when provided', () => {
195
+ const tab = makeTab({ id: 't1', layouts: ['home'] as Tab['layouts'] });
196
+
197
+ mutations.addTab(s, tab);
198
+
199
+ expect(s.tabs[0].layouts).toStrictEqual(['home']);
200
+ });
201
+
202
+ it('defaults showHeader to true when showHeader is undefined', () => {
203
+ const tab = makeTab({ id: 't1', showHeader: undefined as any });
204
+
205
+ mutations.addTab(s, tab);
206
+
207
+ expect(s.tabs[0].showHeader).toBe(true);
208
+ });
209
+
210
+ it('preserves showHeader when explicitly set to false', () => {
211
+ const tab = makeTab({ id: 't1', showHeader: false });
212
+
213
+ mutations.addTab(s, tab);
214
+
215
+ expect(s.tabs[0].showHeader).toBe(false);
216
+ });
217
+
218
+ it('sets active[position] to the tab id', () => {
219
+ mutations.addTab(s, makeTab({ id: 't1', position: BOTTOM as Tab['position'] }));
220
+
221
+ expect(s.active[BOTTOM]).toStrictEqual('t1');
222
+ });
223
+
224
+ it('sets open[position] to true', () => {
225
+ mutations.addTab(s, makeTab({ id: 't1', position: BOTTOM as Tab['position'] }));
226
+
227
+ expect(s.open[BOTTOM]).toBe(true);
228
+ });
229
+
230
+ it('sets userPin to the tab position', () => {
231
+ mutations.addTab(s, makeTab({ id: 't1', position: LEFT as Tab['position'] }));
232
+
233
+ expect(s.userPin).toStrictEqual(LEFT);
234
+ });
235
+
236
+ it('saves the pin position to localStorage', () => {
237
+ mutations.addTab(s, makeTab({ id: 't1', position: RIGHT as Tab['position'] }));
238
+
239
+ expect(localStorage.getItem(STORAGE_KEY['pin'])).toStrictEqual(RIGHT);
240
+ });
241
+
242
+ it('forces position to BOTTOM when BOTTOM is in lockedPositions', () => {
243
+ s.lockedPositions = [BOTTOM as Tab['position']];
244
+ const tab = makeTab({ id: 't1', position: LEFT as Tab['position'] });
245
+
246
+ mutations.addTab(s, tab);
247
+
248
+ expect(s.tabs[0].position).toStrictEqual(BOTTOM);
249
+ });
250
+ });
251
+
252
+ describe('switchTab', () => {
253
+ it('moves the tab to the target position', () => {
254
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
255
+ mutations.switchTab(s, { tabId: 't1', targetPosition: LEFT as Tab['position'] });
256
+
257
+ expect(s.tabs[0].position).toStrictEqual(LEFT);
258
+ });
259
+
260
+ it('sets active[targetPosition] to the tab id', () => {
261
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
262
+ mutations.switchTab(s, { tabId: 't1', targetPosition: LEFT as Tab['position'] });
263
+
264
+ expect(s.active[LEFT]).toStrictEqual('t1');
265
+ });
266
+
267
+ it('sets open[targetPosition] to true', () => {
268
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
269
+ mutations.switchTab(s, { tabId: 't1', targetPosition: LEFT as Tab['position'] });
270
+
271
+ expect(s.open[LEFT]).toBe(true);
272
+ });
273
+
274
+ it('updates active[oldPosition] to the first remaining tab when moving to a different position', () => {
275
+ s.tabs = [
276
+ makeTab({ id: 't1', position: BOTTOM as Tab['position'] }),
277
+ makeTab({ id: 't2', position: BOTTOM as Tab['position'] }),
278
+ ];
279
+ s.active = { [BOTTOM]: 't1' };
280
+ mutations.switchTab(s, { tabId: 't1', targetPosition: LEFT as Tab['position'] });
281
+
282
+ expect(s.active[BOTTOM]).toStrictEqual('t2');
283
+ });
284
+
285
+ it('sets open[oldPosition] to false when no tabs remain after switch', () => {
286
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
287
+ s.open = { [BOTTOM]: true };
288
+ mutations.switchTab(s, { tabId: 't1', targetPosition: LEFT as Tab['position'] });
289
+
290
+ expect(s.open[BOTTOM]).toBe(false);
291
+ });
292
+
293
+ it('sets userPin to targetPosition', () => {
294
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
295
+ mutations.switchTab(s, { tabId: 't1', targetPosition: RIGHT as Tab['position'] });
296
+
297
+ expect(s.userPin).toStrictEqual(RIGHT);
298
+ });
299
+
300
+ it('saves targetPosition to localStorage', () => {
301
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
302
+ mutations.switchTab(s, { tabId: 't1', targetPosition: LEFT as Tab['position'] });
303
+
304
+ expect(localStorage.getItem(STORAGE_KEY['pin'])).toStrictEqual(LEFT);
305
+ });
306
+ });
307
+
308
+ describe('closeTab', () => {
309
+ it('removes the tab from the tabs array', () => {
310
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
311
+ mutations.closeTab(s, { id: 't1' });
312
+
313
+ expect(s.tabs).toHaveLength(0);
314
+ });
315
+
316
+ it('does nothing when the tab id is not found', () => {
317
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
318
+ mutations.closeTab(s, { id: 'unknown' });
319
+
320
+ expect(s.tabs).toHaveLength(1);
321
+ });
322
+
323
+ it('sets open[position] to false when the last tab at that position is closed', () => {
324
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
325
+ s.open = { [BOTTOM]: true };
326
+ mutations.closeTab(s, { id: 't1' });
327
+
328
+ expect(s.open[BOTTOM]).toBe(false);
329
+ });
330
+
331
+ it('updates active[position] to the first remaining tab after close', () => {
332
+ s.tabs = [
333
+ makeTab({ id: 't1', position: BOTTOM as Tab['position'] }),
334
+ makeTab({ id: 't2', position: BOTTOM as Tab['position'] }),
335
+ ];
336
+ s.active = { [BOTTOM]: 't2' };
337
+ mutations.closeTab(s, { id: 't2' });
338
+
339
+ expect(s.active[BOTTOM]).toStrictEqual('t1');
340
+ });
341
+
342
+ it('clears active[position] to empty string when the last tab is closed', () => {
343
+ s.tabs = [makeTab({ id: 't1', position: BOTTOM as Tab['position'] })];
344
+ s.active = { [BOTTOM]: 't1' };
345
+ mutations.closeTab(s, { id: 't1' });
346
+
347
+ expect(s.active[BOTTOM]).toStrictEqual('');
348
+ });
349
+ });
350
+
351
+ describe('removeTab', () => {
352
+ it('removes the exact tab reference from the tabs array', () => {
353
+ const tab = makeTab({ id: 't1' });
354
+
355
+ s.tabs = [tab];
356
+ mutations.removeTab(s, tab);
357
+
358
+ expect(s.tabs).toHaveLength(0);
359
+ });
360
+ });
361
+
362
+ describe('setOpen', () => {
363
+ it('sets open[position] to true', () => {
364
+ mutations.setOpen(s, { position: BOTTOM, open: true });
365
+
366
+ expect(s.open[BOTTOM]).toBe(true);
367
+ });
368
+
369
+ it('sets open[position] to false', () => {
370
+ s.open = { [BOTTOM]: true };
371
+ mutations.setOpen(s, { position: BOTTOM, open: false });
372
+
373
+ expect(s.open[BOTTOM]).toBe(false);
374
+ });
375
+ });
376
+
377
+ describe('setActive', () => {
378
+ it('sets active[position] to the given id', () => {
379
+ mutations.setActive(s, { position: BOTTOM, id: 'tab-id' });
380
+
381
+ expect(s.active[BOTTOM]).toStrictEqual('tab-id');
382
+ });
383
+ });
384
+
385
+ describe('setPanelHeight', () => {
386
+ it('sets panelHeight for the position', () => {
387
+ mutations.setPanelHeight(s, { position: BOTTOM, height: 300 });
388
+
389
+ expect(s.panelHeight[BOTTOM]).toStrictEqual(300);
390
+ });
391
+
392
+ it('saves height to localStorage', () => {
393
+ mutations.setPanelHeight(s, { position: BOTTOM, height: 400 });
394
+
395
+ expect(localStorage.getItem(STORAGE_KEY[BOTTOM])).toStrictEqual('400');
396
+ });
397
+
398
+ it('updates containerHeight only for tabs at that position', () => {
399
+ s.tabs = [
400
+ makeTab({
401
+ id: 't1', position: BOTTOM as Tab['position'], containerHeight: null
402
+ }),
403
+ makeTab({
404
+ id: 't2', position: LEFT as Tab['position'], containerHeight: null
405
+ }),
406
+ ];
407
+ mutations.setPanelHeight(s, { position: BOTTOM, height: 300 });
408
+
409
+ expect(s.tabs[0].containerHeight).toStrictEqual(300);
410
+ expect(s.tabs[1].containerHeight).toBeNull();
411
+ });
412
+ });
413
+
414
+ describe('setPanelWidth', () => {
415
+ it('sets panelWidth for the position', () => {
416
+ mutations.setPanelWidth(s, { position: LEFT as Tab['position'], width: 250 });
417
+
418
+ expect(s.panelWidth[LEFT]).toStrictEqual(250);
419
+ });
420
+
421
+ it('saves width to localStorage', () => {
422
+ mutations.setPanelWidth(s, { position: RIGHT as Tab['position'], width: 300 });
423
+
424
+ expect(localStorage.getItem(STORAGE_KEY[RIGHT])).toStrictEqual('300');
425
+ });
426
+
427
+ it('updates containerWidth only for tabs at that position', () => {
428
+ s.tabs = [
429
+ makeTab({
430
+ id: 't1', position: LEFT as Tab['position'], containerWidth: null
431
+ }),
432
+ makeTab({
433
+ id: 't2', position: RIGHT as Tab['position'], containerWidth: null
434
+ }),
435
+ ];
436
+ mutations.setPanelWidth(s, { position: LEFT as Tab['position'], width: 250 });
437
+
438
+ expect(s.tabs[0].containerWidth).toStrictEqual(250);
439
+ expect(s.tabs[1].containerWidth).toBeNull();
440
+ });
441
+ });
442
+
443
+ describe('setUserPin', () => {
444
+ it('sets userPin to the given value', () => {
445
+ mutations.setUserPin(s, LEFT);
446
+
447
+ expect(s.userPin).toStrictEqual(LEFT);
448
+ });
449
+
450
+ it('saves the pin to localStorage', () => {
451
+ mutations.setUserPin(s, RIGHT);
452
+
453
+ expect(localStorage.getItem(STORAGE_KEY['pin'])).toStrictEqual(RIGHT);
454
+ });
455
+ });
456
+
457
+ describe('setLockedPositions', () => {
458
+ it('replaces lockedPositions with the given array', () => {
459
+ mutations.setLockedPositions(s, [BOTTOM as Tab['position'], LEFT as Tab['position']]);
460
+
461
+ expect(s.lockedPositions).toStrictEqual([BOTTOM, LEFT]);
462
+ });
463
+ });
464
+ });
465
+
466
+ describe('actions', () => {
467
+ let commit: jest.Mock;
468
+
469
+ beforeEach(() => {
470
+ commit = jest.fn();
471
+ });
472
+
473
+ describe('close', () => {
474
+ it('throws when id is not provided', () => {
475
+ expect(() => actions.close({
476
+ commit, state: s, getters: {}
477
+ }, '')).toThrow('[wm] id is not provided');
478
+ });
479
+
480
+ it('commits closeTab with the given id', () => {
481
+ actions.close({
482
+ commit, state: s, getters: {}
483
+ }, 'tab-id');
484
+
485
+ expect(commit).toHaveBeenCalledWith('closeTab', { id: 'tab-id' });
486
+ });
487
+ });
488
+
489
+ describe('open', () => {
490
+ it('throws when tab has no id', () => {
491
+ expect(() => actions.open({ commit }, makeTab({ id: '' }))).toThrow('[wm] id is not provided');
492
+ });
493
+
494
+ it('commits addTab with the given tab', () => {
495
+ const tab = makeTab({ id: 'new-tab' });
496
+
497
+ actions.open({ commit }, tab);
498
+
499
+ expect(commit).toHaveBeenCalledWith('addTab', tab);
500
+ });
501
+ });
502
+ });
503
+ });
package/store/auth.js CHANGED
@@ -76,7 +76,7 @@ export const getters = {
76
76
 
77
77
  selfUser(state) {
78
78
  return state.selfUser;
79
- }
79
+ },
80
80
  };
81
81
 
82
82
  export const mutations = {
@@ -208,6 +208,16 @@ export const actions = {
208
208
  return findBy(authConfigs, 'id', id);
209
209
  },
210
210
 
211
+ /**
212
+ * Is the local auth provider enabled?
213
+ */
214
+ async getLocalProviderEnabled({ dispatch }) {
215
+ // If it exists as an option to log in --> enabled
216
+ const localProvider = await dispatch('getAuthProvider', 'local');
217
+
218
+ return !!localProvider;
219
+ },
220
+
211
221
  /**
212
222
  * Create the basic json object used for the nonce (this includes the random nonce/state)
213
223
  */
package/store/aws.js CHANGED
@@ -4,6 +4,20 @@ import { FetchHttpHandler } from '@smithy/fetch-http-handler';
4
4
  import { isArray, addObjects } from '@shell/utils/array';
5
5
  import { formatAWSError } from '@shell/utils/error';
6
6
 
7
+ /**
8
+ * Check if the given region is an AWS China region.
9
+ * China regions don't support dual-stack endpoints.
10
+ * @param {string} region - AWS region identifier (e.g., 'cn-north-1', 'us-west-2')
11
+ * @returns {boolean} - True if the region starts with 'cn-'
12
+ */
13
+ function isChinaRegion(region) {
14
+ if (!region) {
15
+ return false;
16
+ }
17
+
18
+ return region.startsWith('cn-');
19
+ }
20
+
7
21
  export const state = () => {
8
22
  return {
9
23
  instanceTypes: [],
@@ -126,7 +140,8 @@ export const actions = {
126
140
  region,
127
141
  credentialDefaultProvider: credentialDefaultProvider(accessKey, secretKey),
128
142
  requestHandler: new Handler(cloudCredentialId, undefined, this.$shell.proxy),
129
- useDualstackEndpoint: true,
143
+ // China regions (cn-*) don't support dual-stack endpoints
144
+ useDualstackEndpoint: !isChinaRegion(region),
130
145
  });
131
146
 
132
147
  return client;
@@ -141,7 +156,7 @@ export const actions = {
141
156
  region,
142
157
  credentialDefaultProvider: credentialDefaultProvider(accessKey, secretKey),
143
158
  requestHandler: new Handler(cloudCredentialId, undefined, this.$shell.proxy),
144
- useDualstackEndpoint: true,
159
+ useDualstackEndpoint: !isChinaRegion(region),
145
160
  });
146
161
 
147
162
  return client;
@@ -156,7 +171,7 @@ export const actions = {
156
171
  region,
157
172
  credentialDefaultProvider: credentialDefaultProvider(accessKey, secretKey),
158
173
  requestHandler: new Handler(cloudCredentialId, undefined, this.$shell.proxy),
159
- useDualstackEndpoint: true,
174
+ useDualstackEndpoint: !isChinaRegion(region),
160
175
  });
161
176
 
162
177
  return client;
@@ -171,7 +186,7 @@ export const actions = {
171
186
  region,
172
187
  credentialDefaultProvider: credentialDefaultProvider(accessKey, secretKey),
173
188
  requestHandler: new Handler(cloudCredentialId, undefined, this.$shell.proxy),
174
- useDualstackEndpoint: true,
189
+ useDualstackEndpoint: !isChinaRegion(region),
175
190
  });
176
191
 
177
192
  return client;
package/store/features.js CHANGED
@@ -43,6 +43,7 @@ export const AUTOSCALER = create('cluster-autoscaling', false);
43
43
  export const CLUSTER_SHELL = create('cluster-shell', true);
44
44
  export const NODE_SHELL = create('node-shell', true);
45
45
  export const POD_SHELL = create('pod-shell', true);
46
+ export const HIDE_LOCAL_AUTH_PROVIDER = create('hide-local-auth-provider', false);
46
47
 
47
48
  // Not currently used.. no point defining ones we don't use
48
49
  // export const EMBEDDED_CLUSTER_API = create('embedded-cluster-api', true);
package/store/plugins.js CHANGED
@@ -129,6 +129,12 @@ export const state = function() {
129
129
  return {};
130
130
  };
131
131
 
132
+ export const actions = {
133
+ mapDriver(ctx, { name, to }) {
134
+ mapDriver(name, to);
135
+ },
136
+ };
137
+
132
138
  export const getters = {
133
139
  credentialOptions() {
134
140
  return (name) => {
@@ -8,6 +8,8 @@ export interface SlideInPanelState {
8
8
  componentProps: Record<string, any>;
9
9
  }
10
10
 
11
+ let closeTimer: ReturnType<typeof setTimeout> | null = null;
12
+
11
13
  const state = (): SlideInPanelState => ({
12
14
  isOpen: false,
13
15
  isClosing: false,
@@ -24,20 +26,30 @@ const getters: GetterTree<SlideInPanelState, any> = {
24
26
 
25
27
  const mutations: MutationTree<SlideInPanelState> = {
26
28
  open(state, payload: { component: Component; componentProps?: Record<string, any> }) {
29
+ if (closeTimer) {
30
+ clearTimeout(closeTimer);
31
+ closeTimer = null;
32
+ }
33
+
34
+ state.isClosing = false;
27
35
  state.isOpen = true;
28
36
  state.component = markRaw(payload.component);
29
37
  state.componentProps = payload.componentProps || {};
30
38
  },
31
39
  close(state) {
40
+ if (closeTimer) {
41
+ clearTimeout(closeTimer);
42
+ closeTimer = null;
43
+ }
44
+
32
45
  state.isClosing = true;
33
46
  state.isOpen = false;
34
47
 
35
- // Delay clearing component/props for 500ms (same as transition duration)
36
- setTimeout(() => {
48
+ closeTimer = setTimeout(() => {
37
49
  state.component = null;
38
50
  state.componentProps = {};
39
-
40
51
  state.isClosing = false;
52
+ closeTimer = null;
41
53
  }, 500);
42
54
  }
43
55
  };
@@ -13,13 +13,13 @@ export const STEVE_RESPONSE_CODE = {
13
13
  /**
14
14
  * Steve API JSON response for GET requests
15
15
  */
16
- export interface SteveGetResponse extends KubeGetResponse {
16
+ export type SteveGetResponse<T = Record<string, any>> = T & KubeGetResponse & {
17
17
  // Rancher specific properties (there are more)
18
18
  id: string,
19
19
 
20
20
  // Bucket for everything else (hopefully to remove once above populated)
21
21
  [key: string]: any
22
- }
22
+ };
23
23
 
24
24
  /**
25
25
  * Steve API JSON response for LIST requests