@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.
- package/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/intf/shell-api/slide-in.ts +46 -17
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/apis/shell/__tests__/slide-in.test.ts +83 -2
- package/apis/shell/slide-in.ts +20 -0
- package/assets/images/providers/traefik.png +0 -0
- package/assets/translations/en-us.yaml +10 -4
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/ExplorerProjectsNamespaces.vue +2 -2
- package/components/GrafanaDashboard.vue +2 -8
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- 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/components/form/Security.vue +1 -1
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/config/labels-annotations.js +1 -0
- package/config/table-headers.js +5 -2
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/plugins-loader.js +2 -0
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +0 -2
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/namespace.test.ts +133 -8
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +57 -1
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/namespace.js +34 -2
- package/models/pod.js +12 -10
- package/models/provisioning.cattle.io.cluster.js +20 -13
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
- package/pages/c/_cluster/apps/charts/install.vue +29 -0
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- 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/pages/c/_cluster/monitoring/index.vue +6 -1
- package/pkg/vue.config.js +4 -3
- package/plugins/codemirror.js +2 -0
- package/plugins/dashboard-store/resource-class.js +13 -9
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/store/__tests__/action-menu.test.ts +622 -0
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +143 -43
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/__tests__/wm.test.ts +503 -0
- package/store/auth.js +11 -1
- package/store/aws.js +19 -4
- package/store/features.js +1 -0
- package/store/plugins.js +6 -0
- package/store/slideInPanel.ts +15 -3
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +84 -2
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/fleet-appco.test.ts +23 -0
- package/utils/async.ts +2 -0
- package/utils/fleet-appco.ts +6 -1
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/utils/sort.js +1 -1
- 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
|
-
|
|
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/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) => {
|
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
|
};
|
|
@@ -13,13 +13,13 @@ export const STEVE_RESPONSE_CODE = {
|
|
|
13
13
|
/**
|
|
14
14
|
* Steve API JSON response for GET requests
|
|
15
15
|
*/
|
|
16
|
-
export
|
|
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
|