@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.
- package/apis/intf/shell-api/slide-in.ts +46 -17
- package/apis/shell/__tests__/slide-in.test.ts +83 -2
- package/apis/shell/slide-in.ts +20 -0
- package/components/ExplorerProjectsNamespaces.vue +2 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +9 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -1
- package/components/SlideInPanelManager.vue +103 -25
- package/components/__tests__/SlideInPanelManager.spec.ts +153 -10
- package/components/auth/AuthBanner.vue +1 -1
- package/core/plugins-loader.js +2 -0
- package/models/__tests__/namespace.test.ts +133 -8
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +57 -1
- package/models/namespace.js +34 -2
- package/models/provisioning.cattle.io.cluster.js +20 -13
- package/package.json +1 -1
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +136 -1
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +69 -1
- package/pages/c/_cluster/fleet/index.vue +5 -9
- package/pkg/vue.config.js +4 -3
- package/plugins/codemirror.js +2 -0
- package/plugins/dashboard-store/resource-class.js +3 -6
- package/store/__tests__/action-menu.test.ts +622 -0
- package/store/__tests__/slideInPanel.test.ts +143 -43
- package/store/__tests__/wm.test.ts +503 -0
- package/store/aws.js +19 -4
- package/store/slideInPanel.ts +15 -3
- package/types/shell/index.d.ts +26 -2
- package/utils/__tests__/fleet-appco.test.ts +23 -0
- package/utils/fleet-appco.ts +6 -1
- 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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
189
|
+
useDualstackEndpoint: !isChinaRegion(region),
|
|
175
190
|
});
|
|
176
191
|
|
|
177
192
|
return client;
|
package/store/slideInPanel.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
};
|
package/types/shell/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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);
|