@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.
- package/assets/styles/global/_button.scss +1 -1
- package/assets/translations/en-us.yaml +39 -10
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +51 -2
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/ResourceTable.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/Wizard.vue +14 -1
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/form/FileSelector.vue +39 -1
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/nav/Header.vue +8 -1
- package/components/templates/default.vue +7 -0
- package/config/features.js +1 -0
- package/config/labels-annotations.js +2 -0
- package/config/product/manager.js +6 -0
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -2
- package/config/types.js +7 -0
- package/detail/provisioning.cattle.io.cluster.vue +79 -3
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +51 -5
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/pod.js +13 -2
- package/models/provisioning.cattle.io.cluster.js +59 -9
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +12 -7
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
- package/pages/c/_cluster/apps/charts/install.vue +114 -28
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +52 -12
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/features.js +4 -0
- package/types/shell/index.d.ts +62 -0
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/operation-cr.js +19 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- 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);
|
package/types/shell/index.d.ts
CHANGED
|
@@ -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' {
|