@rancher/shell 3.0.12-rc.6 → 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 (30) hide show
  1. package/apis/intf/shell-api/slide-in.ts +46 -17
  2. package/apis/shell/__tests__/slide-in.test.ts +83 -2
  3. package/apis/shell/slide-in.ts +20 -0
  4. package/components/ExplorerProjectsNamespaces.vue +2 -2
  5. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +9 -0
  6. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -1
  7. package/components/SlideInPanelManager.vue +103 -25
  8. package/components/__tests__/SlideInPanelManager.spec.ts +153 -10
  9. package/components/auth/AuthBanner.vue +1 -1
  10. package/core/plugins-loader.js +2 -0
  11. package/models/__tests__/namespace.test.ts +133 -8
  12. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +57 -1
  13. package/models/namespace.js +34 -2
  14. package/models/provisioning.cattle.io.cluster.js +20 -13
  15. package/package.json +1 -1
  16. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +136 -1
  17. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +69 -1
  18. package/pages/c/_cluster/fleet/index.vue +5 -9
  19. package/pkg/vue.config.js +4 -3
  20. package/plugins/codemirror.js +2 -0
  21. package/plugins/dashboard-store/resource-class.js +3 -6
  22. package/store/__tests__/action-menu.test.ts +622 -0
  23. package/store/__tests__/slideInPanel.test.ts +143 -43
  24. package/store/__tests__/wm.test.ts +503 -0
  25. package/store/aws.js +19 -4
  26. package/store/slideInPanel.ts +15 -3
  27. package/types/shell/index.d.ts +26 -2
  28. package/utils/__tests__/fleet-appco.test.ts +23 -0
  29. package/utils/fleet-appco.ts +6 -1
  30. package/utils/sort.js +1 -1
@@ -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/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;
@@ -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
  };
@@ -5983,13 +5983,25 @@ export default class Namespace {
5983
5983
  get confirmRemove(): boolean;
5984
5984
  get listLocation(): {
5985
5985
  name: string;
5986
+ params: {
5987
+ cluster: any;
5988
+ product: any;
5989
+ };
5986
5990
  };
5987
5991
  get _detailLocation(): any;
5988
5992
  get parentLocationOverride(): {
5989
5993
  name: string;
5994
+ params: {
5995
+ cluster: any;
5996
+ product: any;
5997
+ };
5990
5998
  };
5991
5999
  get doneOverride(): {
5992
6000
  name: string;
6001
+ params: {
6002
+ cluster: any;
6003
+ product: any;
6004
+ };
5993
6005
  };
5994
6006
  set resourceQuota(value: any);
5995
6007
  get resourceQuota(): any;
@@ -6047,13 +6059,25 @@ export default class Namespace {
6047
6059
  get confirmRemove(): boolean;
6048
6060
  get listLocation(): {
6049
6061
  name: string;
6062
+ params: {
6063
+ cluster: any;
6064
+ product: any;
6065
+ };
6050
6066
  };
6051
6067
  get _detailLocation(): any;
6052
6068
  get parentLocationOverride(): {
6053
6069
  name: string;
6070
+ params: {
6071
+ cluster: any;
6072
+ product: any;
6073
+ };
6054
6074
  };
6055
6075
  get doneOverride(): {
6056
6076
  name: string;
6077
+ params: {
6078
+ cluster: any;
6079
+ product: any;
6080
+ };
6057
6081
  };
6058
6082
  set resourceQuota(value: any);
6059
6083
  get resourceQuota(): any;
@@ -10569,7 +10593,7 @@ export function parseField(str: any): {
10569
10593
  field: any;
10570
10594
  reverse: boolean;
10571
10595
  };
10572
- export function sortBy(ary: any, keys: any, desc: any): any;
10596
+ export function sortBy(ary: any, keys: any, desc?: boolean): any;
10573
10597
  export function sortableNumericSuffix(str: any): any;
10574
10598
  export function isNumeric(num: any): boolean;
10575
10599
  }
@@ -10634,7 +10658,7 @@ export function parseField(str: any): {
10634
10658
  field: any;
10635
10659
  reverse: boolean;
10636
10660
  };
10637
- export function sortBy(ary: any, keys: any, desc: any): any;
10661
+ export function sortBy(ary: any, keys: any, desc?: boolean): any;
10638
10662
  export function sortableNumericSuffix(str: any): any;
10639
10663
  export function isNumeric(num: any): boolean;
10640
10664
  }
@@ -100,6 +100,29 @@ describe('fleet-appco utils', () => {
100
100
  expect(onStateChange).toHaveBeenCalledWith(expect.objectContaining({ repoName: 'my-repo', error: false }));
101
101
  });
102
102
 
103
+ it('should wait for the repo using a 10 minute timeout and 3s interval (#14627)', async() => {
104
+ // A cold OCI pull can take minutes; the wait must not give up early.
105
+ const waitForTestFn = jest.fn((fn: () => boolean) => {
106
+ fn();
107
+
108
+ return Promise.resolve();
109
+ });
110
+ const repo = buildRepo({
111
+ status: { conditions: [{ type: 'OCIDownloaded', status: 'True' }] },
112
+ waitForTestFn,
113
+ });
114
+ const store = buildStore(repo);
115
+
116
+ await fetchAppCoCharts(store as any, 'my-repo');
117
+
118
+ expect(waitForTestFn).toHaveBeenCalledWith(
119
+ expect.any(Function),
120
+ expect.stringContaining('my-repo'),
121
+ 600000,
122
+ 3000,
123
+ );
124
+ });
125
+
103
126
  it('should return no entries and an error state when the repo reports an error', async() => {
104
127
  const repo = buildRepo({ metadata: { state: { error: true, message: 'boom' } } });
105
128
  const store = buildStore(repo);