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

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 (81) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/translations/en-us.yaml +39 -10
  3. package/components/ActionDropdownShell.vue +5 -3
  4. package/components/ButtonGroup.vue +26 -1
  5. package/components/CruResource.vue +51 -2
  6. package/components/PromptRestore.vue +93 -32
  7. package/components/Questions/index.vue +1 -0
  8. package/components/ResourceTable.vue +1 -0
  9. package/components/SortableTable/index.vue +4 -3
  10. package/components/Wizard.vue +14 -1
  11. package/components/__tests__/ButtonGroup.test.ts +56 -0
  12. package/components/__tests__/PromptRestore.test.ts +169 -19
  13. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  14. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  15. package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
  16. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  17. package/components/form/FileSelector.vue +39 -1
  18. package/components/form/PrivateRegistry.constants.ts +7 -0
  19. package/components/form/PrivateRegistry.vue +253 -18
  20. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  21. package/components/form/__tests__/FileSelector.test.ts +23 -0
  22. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  23. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  24. package/components/formatter/EtcdSnapshotName.vue +73 -0
  25. package/components/nav/Header.vue +8 -1
  26. package/components/templates/default.vue +7 -0
  27. package/config/features.js +1 -0
  28. package/config/labels-annotations.js +2 -0
  29. package/config/product/manager.js +6 -0
  30. package/config/secret.ts +10 -0
  31. package/config/settings.ts +6 -2
  32. package/config/types.js +7 -0
  33. package/detail/provisioning.cattle.io.cluster.vue +79 -3
  34. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  35. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  36. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  37. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
  38. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  39. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  40. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  41. package/edit/fleet.cattle.io.helmop.vue +51 -5
  42. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  43. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  44. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  45. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  46. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  47. package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
  48. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  49. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  50. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
  51. package/models/__tests__/secret.test.ts +68 -1
  52. package/models/management.cattle.io.cluster.js +21 -3
  53. package/models/pod.js +13 -2
  54. package/models/provisioning.cattle.io.cluster.js +59 -9
  55. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  56. package/models/secret.js +19 -0
  57. package/models/workload.js +12 -7
  58. package/package.json +1 -1
  59. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  60. package/pages/c/_cluster/apps/charts/install.vue +114 -28
  61. package/pkg/require-asset.lib.js +25 -0
  62. package/pkg/vue.config.js +7 -0
  63. package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
  64. package/plugins/dashboard-store/getters.js +0 -1
  65. package/plugins/dashboard-store/resource-class.js +52 -12
  66. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  67. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  68. package/rancher-components/RcButton/index.ts +1 -1
  69. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  70. package/store/__tests__/features.test.ts +131 -0
  71. package/store/__tests__/growl.test.ts +374 -0
  72. package/store/__tests__/modal.test.ts +131 -0
  73. package/store/__tests__/slideInPanel.test.ts +88 -0
  74. package/store/__tests__/type-map.utils.test.ts +433 -0
  75. package/store/features.js +4 -0
  76. package/types/shell/index.d.ts +62 -0
  77. package/utils/__tests__/operation-cr.test.ts +34 -0
  78. package/utils/operation-cr.js +19 -0
  79. package/utils/require-asset.ts +7 -0
  80. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  81. package/utils/validators/private-registry.ts +15 -4
@@ -0,0 +1,88 @@
1
+ import slideInPanelStore from '../slideInPanel';
2
+
3
+ describe('slideInPanel store', () => {
4
+ let s: ReturnType<typeof slideInPanelStore.state>;
5
+ const fakeComponent = { name: 'FakePanel' } as any;
6
+
7
+ beforeEach(() => {
8
+ s = slideInPanelStore.state();
9
+ jest.useFakeTimers();
10
+ });
11
+
12
+ afterEach(() => {
13
+ jest.useRealTimers();
14
+ });
15
+
16
+ describe('state', () => {
17
+ it('returns initial default state', () => {
18
+ expect(s.isOpen).toBe(false);
19
+ expect(s.isClosing).toBe(false);
20
+ expect(s.component).toBeNull();
21
+ expect(s.componentProps).toStrictEqual({});
22
+ });
23
+ });
24
+
25
+ describe('mutations', () => {
26
+ describe('open', () => {
27
+ it('sets isOpen to true', () => {
28
+ slideInPanelStore.mutations.open(s, { component: fakeComponent });
29
+
30
+ expect(s.isOpen).toBe(true);
31
+ });
32
+
33
+ it('stores the component reference', () => {
34
+ slideInPanelStore.mutations.open(s, { component: fakeComponent });
35
+
36
+ expect(s.component).toBe(fakeComponent);
37
+ });
38
+
39
+ it('sets componentProps from payload', () => {
40
+ slideInPanelStore.mutations.open(s, { component: fakeComponent, componentProps: { title: 'Test' } });
41
+
42
+ expect(s.componentProps).toStrictEqual({ title: 'Test' });
43
+ });
44
+
45
+ it('defaults componentProps to empty object when not provided', () => {
46
+ slideInPanelStore.mutations.open(s, { component: fakeComponent });
47
+
48
+ expect(s.componentProps).toStrictEqual({});
49
+ });
50
+ });
51
+
52
+ describe('close', () => {
53
+ beforeEach(() => {
54
+ slideInPanelStore.mutations.open(s, { component: fakeComponent, componentProps: { title: 'Test' } });
55
+ });
56
+
57
+ it('sets isClosing to true immediately', () => {
58
+ slideInPanelStore.mutations.close(s);
59
+
60
+ expect(s.isClosing).toBe(true);
61
+ });
62
+
63
+ it('sets isOpen to false immediately', () => {
64
+ slideInPanelStore.mutations.close(s);
65
+
66
+ expect(s.isOpen).toBe(false);
67
+ });
68
+
69
+ it('retains component and componentProps before the 500ms delay elapses', () => {
70
+ slideInPanelStore.mutations.close(s);
71
+ jest.advanceTimersByTime(499);
72
+
73
+ expect(s.component).toBe(fakeComponent);
74
+ expect(s.componentProps).toStrictEqual({ title: 'Test' });
75
+ expect(s.isClosing).toBe(true);
76
+ });
77
+
78
+ it('clears component, componentProps and isClosing after 500ms', () => {
79
+ slideInPanelStore.mutations.close(s);
80
+ jest.advanceTimersByTime(500);
81
+
82
+ expect(s.component).toBeNull();
83
+ expect(s.componentProps).toStrictEqual({});
84
+ expect(s.isClosing).toBe(false);
85
+ });
86
+ });
87
+ });
88
+ });
@@ -0,0 +1,433 @@
1
+ import {
2
+ conditionalDepaginate,
3
+ configureConditionalDepaginate,
4
+ headerFromSchemaCol,
5
+ rowValueGetter,
6
+ } from '../type-map.utils';
7
+
8
+ const makeGetters = (overrides: any = {}) => ({
9
+ 'i18n/exists': jest.fn(() => false),
10
+ 'i18n/t': jest.fn((key: string) => key),
11
+ currentStore: jest.fn(() => 'cluster'),
12
+ 'cluster/all': jest.fn(() => []),
13
+ ...overrides,
14
+ });
15
+
16
+ const makeCol = (overrides: any = {}) => ({
17
+ description: '',
18
+ field: '$.metadata.name',
19
+ format: '',
20
+ name: 'Name',
21
+ priority: 0,
22
+ type: 'string',
23
+ ...overrides,
24
+ });
25
+
26
+ const makeAgeColumn = () => ({
27
+ name: 'age',
28
+ label: 'Age',
29
+ value: 'metadata.creationTimestamp',
30
+ sort: ['metadata.creationTimestamp'],
31
+ });
32
+
33
+ describe('rowValueGetter', () => {
34
+ describe('fields matching $.metadata.fields[N] pattern', () => {
35
+ it.each([
36
+ {
37
+ desc: 'returns function accessing index 0',
38
+ field: '$.metadata.fields[0]',
39
+ asFn: true as const,
40
+ expectedValue: { metadata: { fields: ['first', 'second'] } },
41
+ expectedIdx: 0,
42
+ },
43
+ {
44
+ desc: 'returns function accessing index 2',
45
+ field: '$.metadata.fields[2]',
46
+ asFn: true as const,
47
+ expectedValue: { metadata: { fields: ['a', 'b', 'c'] } },
48
+ expectedIdx: 2,
49
+ },
50
+ ])('$desc', ({
51
+ field, asFn, expectedValue, expectedIdx,
52
+ }) => {
53
+ const getter = rowValueGetter(makeCol({ field }), asFn) as (row: any) => any;
54
+
55
+ expect(typeof getter).toStrictEqual('function');
56
+ expect(getter(expectedValue)).toStrictEqual(expectedValue.metadata.fields[expectedIdx]);
57
+ });
58
+
59
+ it.each([
60
+ {
61
+ desc: 'returns string path for index 0',
62
+ field: '$.metadata.fields[0]',
63
+ asFn: false as const,
64
+ expected: 'metadata.fields.0',
65
+ },
66
+ {
67
+ desc: 'returns string path for index 3',
68
+ field: '$.metadata.fields[3]',
69
+ asFn: false as const,
70
+ expected: 'metadata.fields.3',
71
+ },
72
+ ])('$desc', ({ field, asFn, expected }) => {
73
+ expect(rowValueGetter(makeCol({ field }), asFn)).toStrictEqual(expected);
74
+ });
75
+
76
+ it('returns undefined when row has no fields array', () => {
77
+ const getter = rowValueGetter(makeCol({ field: '$.metadata.fields[0]' }), true) as (row: any) => any;
78
+
79
+ expect(getter({})).toBeUndefined();
80
+ });
81
+
82
+ it('returns undefined when metadata is missing', () => {
83
+ const getter = rowValueGetter(makeCol({ field: '$.metadata.fields[1]' }), true) as (row: any) => any;
84
+
85
+ expect(getter({ other: 'data' })).toBeUndefined();
86
+ });
87
+ });
88
+
89
+ describe('fields starting with . (dot-prefixed)', () => {
90
+ it('prepends $ to dot-prefixed fields', () => {
91
+ const result = rowValueGetter(makeCol({ field: '.metadata.name' }), false);
92
+
93
+ expect(result).toStrictEqual('$.metadata.name');
94
+ });
95
+
96
+ it('does not modify fields already starting with $', () => {
97
+ const result = rowValueGetter(makeCol({ field: '$.metadata.name' }), false);
98
+
99
+ expect(result).toStrictEqual('$.metadata.name');
100
+ });
101
+ });
102
+
103
+ describe('fields with escaped dots (rewriteJsonPath)', () => {
104
+ it.each([
105
+ {
106
+ desc: 'rewrites single escaped dot in label key',
107
+ field: '$.metadata.labels.topology\\.kubernetes\\.io/zone',
108
+ expected: '$.metadata.labels.["topology.kubernetes.io/zone"]',
109
+ },
110
+ {
111
+ desc: 'rewrites escaped dot in annotation key',
112
+ field: '$.metadata.annotations.cattle\\.io/hash',
113
+ expected: '$.metadata.annotations.["cattle.io/hash"]',
114
+ },
115
+ ])('$desc', ({ field, expected }) => {
116
+ expect(rowValueGetter(makeCol({ field }), false)).toStrictEqual(expected);
117
+ });
118
+
119
+ it('returns path unchanged when no escaped dots present', () => {
120
+ const field = '$.metadata.labels.app';
121
+
122
+ expect(rowValueGetter(makeCol({ field }), false)).toStrictEqual(field);
123
+ });
124
+ });
125
+ });
126
+
127
+ describe('conditionalDepaginate', () => {
128
+ it.each([
129
+ {
130
+ desc: 'returns true when depaginate is boolean true',
131
+ depaginate: true,
132
+ args: undefined,
133
+ expected: true,
134
+ },
135
+ {
136
+ desc: 'returns false when depaginate is boolean false',
137
+ depaginate: false,
138
+ args: undefined,
139
+ expected: false,
140
+ },
141
+ {
142
+ desc: 'returns undefined when depaginate is undefined',
143
+ depaginate: undefined,
144
+ args: undefined,
145
+ expected: undefined,
146
+ },
147
+ ])('$desc', ({ depaginate, args, expected }) => {
148
+ expect(conditionalDepaginate(depaginate as any, args)).toStrictEqual(expected);
149
+ });
150
+
151
+ it('calls function with args and returns its result when args are provided', () => {
152
+ const fn = jest.fn(() => true);
153
+ const args = { ctx: { rootGetters: {} as any }, args: { type: 'pod', opt: {} } };
154
+
155
+ const result = conditionalDepaginate(fn, args);
156
+
157
+ expect(fn).toHaveBeenCalledWith(args);
158
+ expect(result).toStrictEqual(true);
159
+ });
160
+
161
+ it('returns false when depaginate is a function but no args are provided', () => {
162
+ const fn = jest.fn(() => true);
163
+
164
+ expect(conditionalDepaginate(fn, undefined)).toStrictEqual(false);
165
+ expect(fn).not.toHaveBeenCalled();
166
+ });
167
+ });
168
+
169
+ describe('configureConditionalDepaginate', () => {
170
+ const makeRootGetters = (resourceCount: number | undefined, type: string, store = 'cluster') => {
171
+ const counts: any = {};
172
+
173
+ if (resourceCount !== undefined) {
174
+ counts[type] = { summary: { count: resourceCount } };
175
+ }
176
+
177
+ return {
178
+ currentStore: jest.fn(() => store),
179
+ [`${ store }/all`]: jest.fn(() => [{ counts }]),
180
+ };
181
+ };
182
+
183
+ it('returns true when resource count is below maxResourceCount', () => {
184
+ const type = 'pod';
185
+ const rootGetters = makeRootGetters(50, type) as any;
186
+ const fn = configureConditionalDepaginate({ maxResourceCount: 100, isNorman: false });
187
+ const result = fn({
188
+ ctx: { rootGetters },
189
+ args: { type, opt: {} },
190
+ });
191
+
192
+ expect(result).toStrictEqual(true);
193
+ });
194
+
195
+ it('returns false when resource count equals maxResourceCount', () => {
196
+ const type = 'pod';
197
+ const rootGetters = makeRootGetters(100, type) as any;
198
+ const fn = configureConditionalDepaginate({ maxResourceCount: 100, isNorman: false });
199
+ const result = fn({
200
+ ctx: { rootGetters },
201
+ args: { type, opt: {} },
202
+ });
203
+
204
+ expect(result).toStrictEqual(false);
205
+ });
206
+
207
+ it('returns false when resource count exceeds maxResourceCount', () => {
208
+ const type = 'pod';
209
+ const rootGetters = makeRootGetters(200, type) as any;
210
+ const fn = configureConditionalDepaginate({ maxResourceCount: 100, isNorman: false });
211
+ const result = fn({
212
+ ctx: { rootGetters },
213
+ args: { type, opt: {} },
214
+ });
215
+
216
+ expect(result).toStrictEqual(false);
217
+ });
218
+
219
+ it('returns false when resource count is undefined', () => {
220
+ const type = 'pod';
221
+ const rootGetters = makeRootGetters(undefined, type) as any;
222
+ const fn = configureConditionalDepaginate({ maxResourceCount: 100, isNorman: false });
223
+ const result = fn({
224
+ ctx: { rootGetters },
225
+ args: { type, opt: {} },
226
+ });
227
+
228
+ expect(result).toStrictEqual(false);
229
+ });
230
+
231
+ it('uses management.cattle.io. prefix for Norman types', () => {
232
+ const type = 'node';
233
+ const normanType = `management.cattle.io.${ type }`;
234
+ const rootGetters = makeRootGetters(5, normanType) as any;
235
+ const fn = configureConditionalDepaginate({ maxResourceCount: 100, isNorman: true });
236
+ const result = fn({
237
+ ctx: { rootGetters },
238
+ args: { type, opt: {} },
239
+ });
240
+
241
+ expect(result).toStrictEqual(true);
242
+ });
243
+ });
244
+
245
+ describe('headerFromSchemaCol', () => {
246
+ describe('age column shortcut', () => {
247
+ it.each([
248
+ {
249
+ desc: 'returns ageColumn when format is empty and name is age',
250
+ col: makeCol({
251
+ name: 'age',
252
+ format: '',
253
+ }),
254
+ },
255
+ {
256
+ desc: 'returns ageColumn when format is date and name is age',
257
+ col: makeCol({
258
+ name: 'age',
259
+ format: 'date',
260
+ }),
261
+ },
262
+ {
263
+ desc: 'returns ageColumn when type is date and name is age',
264
+ col: makeCol({
265
+ name: 'age',
266
+ type: 'date',
267
+ format: '',
268
+ }),
269
+ },
270
+ {
271
+ desc: 'returns ageColumn when name is Age (case insensitive)',
272
+ col: makeCol({
273
+ name: 'Age',
274
+ format: '',
275
+ }),
276
+ },
277
+ ])('$desc', ({ col }) => {
278
+ const ageColumn = makeAgeColumn() as any;
279
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, ageColumn);
280
+
281
+ expect(result).toStrictEqual(ageColumn);
282
+ });
283
+
284
+ it('does not return ageColumn when ageColumn is not provided', () => {
285
+ const col = makeCol({ name: 'age', format: '' });
286
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
287
+
288
+ expect(result).not.toStrictEqual(null);
289
+ expect(result.name).toStrictEqual('age');
290
+ });
291
+ });
292
+
293
+ describe('formatter assignment', () => {
294
+ it('sets formatter to Date and width 120 for date format', () => {
295
+ const col = makeCol({
296
+ name: 'created',
297
+ format: 'date',
298
+ });
299
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
300
+
301
+ expect(result.formatter).toStrictEqual('Date');
302
+ expect(result.width).toStrictEqual(120);
303
+ expect(result.formatterOpts).toStrictEqual({ multiline: true });
304
+ });
305
+
306
+ it('sets formatter to Date and width 120 when type is date', () => {
307
+ const col = makeCol({
308
+ name: 'modified',
309
+ format: '',
310
+ type: 'date',
311
+ });
312
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
313
+
314
+ expect(result.formatter).toStrictEqual('Date');
315
+ expect(result.width).toStrictEqual(120);
316
+ });
317
+
318
+ it('sets formatter to Number when type is number', () => {
319
+ const col = makeCol({
320
+ name: 'count',
321
+ type: 'number',
322
+ });
323
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
324
+
325
+ expect(result.formatter).toStrictEqual('Number');
326
+ });
327
+
328
+ it('sets formatter to Number when type is int', () => {
329
+ const col = makeCol({
330
+ name: 'replicas',
331
+ type: 'int',
332
+ });
333
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
334
+
335
+ expect(result.formatter).toStrictEqual('Number');
336
+ });
337
+
338
+ it('does not set formatter for plain text column', () => {
339
+ const col = makeCol({ name: 'status', type: 'string' });
340
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
341
+
342
+ expect(result.formatter).toBeUndefined();
343
+ });
344
+ });
345
+
346
+ describe('label resolution', () => {
347
+ it('uses i18n translation when key exists', () => {
348
+ const getters = makeGetters({
349
+ 'i18n/exists': jest.fn(() => true),
350
+ 'i18n/t': jest.fn((key: string) => `translated:${ key }`),
351
+ });
352
+ const col = makeCol({ name: 'status' });
353
+ const result = headerFromSchemaCol(col as any, getters as any, false, null as any);
354
+
355
+ expect(result.label).toStrictEqual('translated:tableHeaders.status');
356
+ });
357
+
358
+ it('uses column name when i18n key does not exist', () => {
359
+ const getters = makeGetters({ 'i18n/exists': jest.fn(() => false) });
360
+ const col = makeCol({ name: 'MyColumn' });
361
+ const result = headerFromSchemaCol(col as any, getters as any, false, null as any);
362
+
363
+ expect(result.label).toStrictEqual('MyColumn');
364
+ });
365
+
366
+ it('camelCases the i18n key when col name has spaces', () => {
367
+ const getters = makeGetters({
368
+ 'i18n/exists': jest.fn((key: string) => key === 'tableHeaders.myLabel'),
369
+ 'i18n/t': jest.fn((key: string) => `t:${ key }`),
370
+ });
371
+ const col = makeCol({ name: 'my label' });
372
+ const result = headerFromSchemaCol(col as any, getters as any, false, null as any);
373
+
374
+ expect(result.label).toStrictEqual('t:tableHeaders.myLabel');
375
+ });
376
+ });
377
+
378
+ describe('tooltip from description', () => {
379
+ it('uses description as tooltip when not ending with dot', () => {
380
+ const col = makeCol({ name: 'status', description: 'Current status' });
381
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
382
+
383
+ expect(result.tooltip).toStrictEqual('Current status');
384
+ });
385
+
386
+ it('strips trailing dot from description for tooltip', () => {
387
+ const col = makeCol({ name: 'status', description: 'Current status.' });
388
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
389
+
390
+ expect(result.tooltip).toStrictEqual('Current status');
391
+ });
392
+
393
+ it('uses empty string as tooltip when description is empty', () => {
394
+ const col = makeCol({ name: 'status', description: '' });
395
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
396
+
397
+ expect(result.tooltip).toStrictEqual('');
398
+ });
399
+ });
400
+
401
+ describe('pagination mode', () => {
402
+ it('uses string path as value when pagination is true', () => {
403
+ const col = makeCol({ name: 'status', field: '$.metadata.name' });
404
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, true, null as any);
405
+
406
+ expect(typeof result.value).toStrictEqual('string');
407
+ });
408
+
409
+ it('uses function as value when pagination is false', () => {
410
+ const col = makeCol({ name: 'status', field: '$.metadata.fields[0]' });
411
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
412
+
413
+ expect(typeof result.value).toStrictEqual('function');
414
+ });
415
+ });
416
+
417
+ describe('output shape', () => {
418
+ it('sets name to lowercase version of col.name', () => {
419
+ const col = makeCol({ name: 'Status' });
420
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, false, null as any);
421
+
422
+ expect(result.name).toStrictEqual('status');
423
+ });
424
+
425
+ it('sets sort and search from string path', () => {
426
+ const col = makeCol({ name: 'name', field: '$.metadata.name' });
427
+ const result = headerFromSchemaCol(col as any, makeGetters() as any, true, null as any);
428
+
429
+ expect(result.sort).toStrictEqual(['$.metadata.name']);
430
+ expect(result.search).toStrictEqual('$.metadata.name');
431
+ });
432
+ });
433
+ });
package/store/features.js CHANGED
@@ -39,6 +39,10 @@ export const PROVISIONING_PRE_BOOTSTRAP = create('provisioningprebootstrap', fal
39
39
  export const SCHEDULING_CUSTOMIZATION = create(SCHEDULING_CUSTOMIZATION_FEATURE, false);
40
40
  export const SCC = create('rancher-scc-registration-extension', true);
41
41
  export const AUTOSCALER = create('cluster-autoscaling', false);
42
+ // Feature flags for disabling shell access to clusters, nodes, pods
43
+ export const CLUSTER_SHELL = create('cluster-shell', true);
44
+ export const NODE_SHELL = create('node-shell', true);
45
+ export const POD_SHELL = create('pod-shell', true);
42
46
 
43
47
  // Not currently used.. no point defining ones we don't use
44
48
  // export const EMBEDDED_CLUSTER_API = create('embedded-cluster-api', true);
@@ -141,6 +141,9 @@ export namespace SNAPSHOT {
141
141
  let CLUSTER_NAME_1: string;
142
142
  export { CLUSTER_NAME_1 as CLUSTER_NAME };
143
143
  }
144
+ export namespace OPERATION_ANNOTATIONS {
145
+ let ENABLED: string;
146
+ }
144
147
  export namespace ISTIO {
145
148
  let AUTO_INJECTION: string;
146
149
  }
@@ -309,6 +312,9 @@ export namespace SNAPSHOT {
309
312
  let CLUSTER_NAME_1: string;
310
313
  export { CLUSTER_NAME_1 as CLUSTER_NAME };
311
314
  }
315
+ export namespace OPERATION_ANNOTATIONS {
316
+ let ENABLED: string;
317
+ }
312
318
  export namespace ISTIO {
313
319
  let AUTO_INJECTION: string;
314
320
  }
@@ -4511,6 +4517,11 @@ export const LONGHORN_DRIVER: "driver.longhorn.io";
4511
4517
  export const LONGHORN_VERSION_V1: "LonghornV1";
4512
4518
  export const LONGHORN_VERSION_V2: "LonghornV2";
4513
4519
  export const SNAPSHOT: "rke.cattle.io.etcdsnapshot";
4520
+ export namespace OPERATION {
4521
+ let ETCD_SNAPSHOT: string;
4522
+ let ETCD_SNAPSHOT_RESTORE: string;
4523
+ let ENCRYPTION_KEY_ROTATE: string;
4524
+ }
4514
4525
  export namespace MANAGEMENT {
4515
4526
  let AUTH_CONFIG_1: string;
4516
4527
  export { AUTH_CONFIG_1 as AUTH_CONFIG };
@@ -4688,6 +4699,7 @@ export namespace AUTH_TYPE {
4688
4699
  let _S3: string;
4689
4700
  let _RKE: string;
4690
4701
  let _IMAGE_PULL_SECRET: string;
4702
+ let _GITHUB_APP: string;
4691
4703
  }
4692
4704
  export const LOCAL_CLUSTER: "local";
4693
4705
  export namespace CLUSTER_REPO_TYPES {
@@ -4896,6 +4908,11 @@ export const LONGHORN_DRIVER: "driver.longhorn.io";
4896
4908
  export const LONGHORN_VERSION_V1: "LonghornV1";
4897
4909
  export const LONGHORN_VERSION_V2: "LonghornV2";
4898
4910
  export const SNAPSHOT: "rke.cattle.io.etcdsnapshot";
4911
+ export namespace OPERATION {
4912
+ let ETCD_SNAPSHOT: string;
4913
+ let ETCD_SNAPSHOT_RESTORE: string;
4914
+ let ENCRYPTION_KEY_ROTATE: string;
4915
+ }
4899
4916
  export namespace MANAGEMENT {
4900
4917
  let AUTH_CONFIG_1: string;
4901
4918
  export { AUTH_CONFIG_1 as AUTH_CONFIG };
@@ -5073,6 +5090,7 @@ export namespace AUTH_TYPE {
5073
5090
  let _S3: string;
5074
5091
  let _RKE: string;
5075
5092
  let _IMAGE_PULL_SECRET: string;
5093
+ let _GITHUB_APP: string;
5076
5094
  }
5077
5095
  export const LOCAL_CLUSTER: "local";
5078
5096
  export namespace CLUSTER_REPO_TYPES {
@@ -6624,6 +6642,7 @@ export namespace STATES_ENUM {
6624
6642
  let BUILDING: string;
6625
6643
  let COMPLETED: string;
6626
6644
  let CORDONED: string;
6645
+ let CANCELLED: string;
6627
6646
  let COUNT: string;
6628
6647
  let CREATED: string;
6629
6648
  let CREATING: string;
@@ -6862,6 +6881,8 @@ export default class Resource {
6862
6881
  doActionGrowl(actionName: any, body: any, opt?: {}): Promise<any>;
6863
6882
  patch(data: any, opt?: {}, merge?: boolean, alertOnError?: boolean): any;
6864
6883
  save(...args: any[]): Promise<this>;
6884
+ _collectionUrl(): any;
6885
+ dryRunCreate(data: any): Promise<any>;
6865
6886
  /**
6866
6887
  * Remove any unwanted properties from the object that will be saved
6867
6888
  */
@@ -7202,6 +7223,7 @@ export namespace STATES_ENUM {
7202
7223
  let BUILDING: string;
7203
7224
  let COMPLETED: string;
7204
7225
  let CORDONED: string;
7226
+ let CANCELLED: string;
7205
7227
  let COUNT: string;
7206
7228
  let CREATED: string;
7207
7229
  let CREATING: string;
@@ -7440,6 +7462,8 @@ export default class Resource {
7440
7462
  doActionGrowl(actionName: any, body: any, opt?: {}): Promise<any>;
7441
7463
  patch(data: any, opt?: {}, merge?: boolean, alertOnError?: boolean): any;
7442
7464
  save(...args: any[]): Promise<this>;
7465
+ _collectionUrl(): any;
7466
+ dryRunCreate(data: any): Promise<any>;
7443
7467
  /**
7444
7468
  * Remove any unwanted properties from the object that will be saved
7445
7469
  */
@@ -7901,6 +7925,9 @@ export const PROVISIONING_PRE_BOOTSTRAP: any;
7901
7925
  export const SCHEDULING_CUSTOMIZATION: any;
7902
7926
  export const SCC: any;
7903
7927
  export const AUTOSCALER: any;
7928
+ export const CLUSTER_SHELL: any;
7929
+ export const NODE_SHELL: any;
7930
+ export const POD_SHELL: any;
7904
7931
  export namespace getters {
7905
7932
  function get(state: any, getters: any, rootState: any, rootGetters: any): (name: any) => any;
7906
7933
  }
@@ -7934,6 +7961,9 @@ export const PROVISIONING_PRE_BOOTSTRAP: any;
7934
7961
  export const SCHEDULING_CUSTOMIZATION: any;
7935
7962
  export const SCC: any;
7936
7963
  export const AUTOSCALER: any;
7964
+ export const CLUSTER_SHELL: any;
7965
+ export const NODE_SHELL: any;
7966
+ export const POD_SHELL: any;
7937
7967
  export namespace getters {
7938
7968
  function get(state: any, getters: any, rootState: any, rootGetters: any): (name: any) => any;
7939
7969
  }
@@ -9765,6 +9795,38 @@ export function convertStringToKV(input: string): {};
9765
9795
  declare function isEqualBasic(from: any, to: any): boolean;
9766
9796
  }
9767
9797
 
9798
+ // @shell/utils/operation-cr
9799
+
9800
+ declare module '@shell/utils/operation-cr' {
9801
+ /**
9802
+ * Create a day 2 operation CR for imported clusters.
9803
+ *
9804
+ * @param {Function} dispatch - The Vuex dispatch function
9805
+ * @param {string} type - The operation CRD type
9806
+ * @param {object} spec - The operation spec
9807
+ * @param {string} namespace - The namespace for the operation CR
9808
+ * @param {string} namePrefix - The name prefix for the generated name
9809
+ * @returns {Promise} The saved resource
9810
+ */
9811
+ export function createOperationCR(dispatch: Function, type: string, spec: object, namespace: string, namePrefix: string): Promise<any>;
9812
+ }
9813
+
9814
+ // @shell/utils/operation-cr.js
9815
+
9816
+ declare module '@shell/utils/operation-cr.js' {
9817
+ /**
9818
+ * Create a day 2 operation CR for imported clusters.
9819
+ *
9820
+ * @param {Function} dispatch - The Vuex dispatch function
9821
+ * @param {string} type - The operation CRD type
9822
+ * @param {object} spec - The operation spec
9823
+ * @param {string} namespace - The namespace for the operation CR
9824
+ * @param {string} namePrefix - The name prefix for the generated name
9825
+ * @returns {Promise} The saved resource
9826
+ */
9827
+ export function createOperationCR(dispatch: Function, type: string, spec: object, namespace: string, namePrefix: string): Promise<any>;
9828
+ }
9829
+
9768
9830
  // @shell/utils/parse-externalid
9769
9831
 
9770
9832
  declare module '@shell/utils/parse-externalid' {