@rancher/shell 3.0.8 → 3.0.9-rc.1
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/modal.ts +38 -0
- package/apis/intf/slide-in.ts +3 -1
- package/apis/shell/__tests__/slide-in.test.ts +36 -0
- package/apis/shell/slide-in.ts +5 -1
- package/detail/catalog.cattle.io.app.vue +1 -0
- package/edit/workload/__tests__/index.test.ts +123 -85
- package/edit/workload/index.vue +1 -1
- package/edit/workload/mixins/workload.js +19 -1
- package/mixins/__tests__/brand.spec.ts +18 -13
- package/package.json +1 -1
- package/pages/c/_cluster/fleet/index.vue +5 -5
- package/plugins/steve/mutations.js +9 -0
- package/plugins/steve/subscribe.js +23 -2
- package/types/store/dashboard-store.types.ts +29 -7
- package/utils/cspAdaptor.ts +32 -14
- package/utils/pagination-wrapper.ts +3 -3
package/apis/intf/modal.ts
CHANGED
|
@@ -11,6 +11,44 @@ export interface ModalConfig {
|
|
|
11
11
|
* ```ts
|
|
12
12
|
* props: { title: 'Hello Modal', isVisible: true }
|
|
13
13
|
* ```
|
|
14
|
+
*
|
|
15
|
+
* Props can include callback functions to be invoked when confirming a modal.
|
|
16
|
+
*
|
|
17
|
+
* Example with a callback function:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import MyCustomModal from './MyCustomModal.vue';
|
|
21
|
+
*
|
|
22
|
+
* export default {
|
|
23
|
+
* methods: {
|
|
24
|
+
* myAction() {
|
|
25
|
+
* console.log('Performed an action');
|
|
26
|
+
* },
|
|
27
|
+
* showModal() {
|
|
28
|
+
* this.$shell.modal.open(MyCustomModal, {
|
|
29
|
+
* props: { onConfirm: this.myAction }
|
|
30
|
+
* });
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* export default {
|
|
38
|
+
* props: {
|
|
39
|
+
* onConfirm: {
|
|
40
|
+
* type: Function,
|
|
41
|
+
* required: true
|
|
42
|
+
* }
|
|
43
|
+
* },
|
|
44
|
+
* methods:.
|
|
45
|
+
* confirm() {
|
|
46
|
+
* this.onConfirm();
|
|
47
|
+
* this.$emit('close');
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
14
52
|
*/
|
|
15
53
|
props?: Record<string, any>;
|
|
16
54
|
|
package/apis/intf/slide-in.ts
CHANGED
|
@@ -51,4 +51,40 @@ describe('slideInApiImpl', () => {
|
|
|
51
51
|
componentProps: { ...config }, // The implementation spreads the config
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
|
+
|
|
55
|
+
it('should open a slide-in panel with an undefined config gracefully', () => {
|
|
56
|
+
// 3. Act
|
|
57
|
+
slideInApi.open(MockComponent, undefined);
|
|
58
|
+
|
|
59
|
+
// 4. Assert
|
|
60
|
+
expect(mockCommit).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(mockCommit).toHaveBeenCalledWith('slideInPanel/open', {
|
|
62
|
+
component: MockComponent,
|
|
63
|
+
componentProps: {},
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle config with props property', () => {
|
|
68
|
+
const config = {
|
|
69
|
+
title: 'Test Panel',
|
|
70
|
+
props: {
|
|
71
|
+
foo: 'bar',
|
|
72
|
+
baz: 123
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 3. Act
|
|
77
|
+
slideInApi.open(MockComponent, config);
|
|
78
|
+
|
|
79
|
+
// 4. Assert
|
|
80
|
+
expect(mockCommit).toHaveBeenCalledTimes(1);
|
|
81
|
+
expect(mockCommit).toHaveBeenCalledWith('slideInPanel/open', {
|
|
82
|
+
component: MockComponent,
|
|
83
|
+
componentProps: {
|
|
84
|
+
title: 'Test Panel',
|
|
85
|
+
foo: 'bar',
|
|
86
|
+
baz: 123
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
});
|
|
54
90
|
});
|
package/apis/shell/slide-in.ts
CHANGED
|
@@ -25,9 +25,13 @@ export class SlideInApiImpl implements SlideInApi {
|
|
|
25
25
|
* @param config - Slide-In configuration object
|
|
26
26
|
*/
|
|
27
27
|
public open(component: Component, config?: SlideInConfig): void {
|
|
28
|
+
const props = config?.props || {};
|
|
29
|
+
|
|
30
|
+
delete config?.props;
|
|
31
|
+
|
|
28
32
|
this.store.commit('slideInPanel/open', {
|
|
29
33
|
component,
|
|
30
|
-
componentProps: { ...config || {} }
|
|
34
|
+
componentProps: { ...(config || {}), ...props }
|
|
31
35
|
});
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -4,100 +4,138 @@ import Workload from '@shell/edit/workload/index.vue';
|
|
|
4
4
|
jest.mock('@shell/models/secret', () => ({ onmessage: jest.fn() }));
|
|
5
5
|
|
|
6
6
|
describe('component: Workload', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
isJob: jest.fn(),
|
|
48
|
-
podFsGroup: jest.fn(),
|
|
49
|
-
namespacedSecrets: jest.fn(),
|
|
50
|
-
registerBeforeHook: jest.fn(),
|
|
51
|
-
pvcs: jest.fn(),
|
|
52
|
-
// tabWeightMap: jest.fn(),
|
|
53
|
-
}
|
|
54
|
-
};
|
|
7
|
+
const baseMockedValidationMixin = {
|
|
8
|
+
methods: {
|
|
9
|
+
fvFormIsValid: jest.fn(),
|
|
10
|
+
type: jest.fn(),
|
|
11
|
+
fvGetAndReportPathRules: jest.fn(),
|
|
12
|
+
},
|
|
13
|
+
computed: { fvUnreportedValidationErrors: jest.fn().mockReturnValue([]) }
|
|
14
|
+
};
|
|
15
|
+
const baseMockedCREMixin = {};
|
|
16
|
+
const baseMockedWorkloadMixin = {
|
|
17
|
+
methods: {
|
|
18
|
+
doneRoute: jest.fn(),
|
|
19
|
+
workloadSubTypes: jest.fn(),
|
|
20
|
+
applyHooks: jest.fn(),
|
|
21
|
+
save: jest.fn(),
|
|
22
|
+
selectType: jest.fn(),
|
|
23
|
+
isCronJob: jest.fn(),
|
|
24
|
+
spec: jest.fn(),
|
|
25
|
+
isReplicable: jest.fn(),
|
|
26
|
+
isStatefulSet: jest.fn(),
|
|
27
|
+
headlessServices: jest.fn(),
|
|
28
|
+
defaultTab: jest.fn(),
|
|
29
|
+
allContainers: jest.fn(),
|
|
30
|
+
isPod: jest.fn(),
|
|
31
|
+
tabWeightMap: jest.fn(),
|
|
32
|
+
podLabels: jest.fn(),
|
|
33
|
+
podTemplateSpec: jest.fn(),
|
|
34
|
+
isLoadingSecondaryResources: jest.fn(),
|
|
35
|
+
allNodes: jest.fn(),
|
|
36
|
+
clearPvcFormState: jest.fn(),
|
|
37
|
+
savePvcHookName: jest.fn(),
|
|
38
|
+
namespacedConfigMaps: jest.fn(),
|
|
39
|
+
podAnnotations: jest.fn(),
|
|
40
|
+
isJob: jest.fn(),
|
|
41
|
+
podFsGroup: jest.fn(),
|
|
42
|
+
namespacedSecrets: jest.fn(),
|
|
43
|
+
registerBeforeHook: jest.fn(),
|
|
44
|
+
pvcs: jest.fn(),
|
|
45
|
+
}
|
|
46
|
+
};
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
describe('component: Workload', () => {
|
|
49
|
+
it.each([
|
|
50
|
+
[
|
|
51
|
+
`pods \"test\" is forbidden: violates PodSecurity \"restricted:latest\": allowPrivilegeEscalation != false (container \"container-0\" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container \"container-0\" must set securityContext.capabilities.drop=[\"ALL\"]), runAsNonRoot != true (container \"container-0\" must not set securityContext.runAsNonRoot=false), seccompProfile (pod or container \"container-0\" must set securityContext.seccompProfile.type to \"RuntimeDefault\" or \"Localhost\")`,
|
|
52
|
+
`workload.error, \"test\",\"restricted:latest\"`
|
|
53
|
+
]
|
|
54
|
+
])('should map error message into object', (oldMessage, newMessage) => {
|
|
55
|
+
// For this test, allNodeObjects is just a jest.fn() in the base mixin
|
|
56
|
+
const MockedWorkload = { ...Workload, mixins: [baseMockedValidationMixin, baseMockedCREMixin, { ...baseMockedWorkloadMixin, computed: { allNodeObjects: jest.fn() } }] };
|
|
57
|
+
const wrapper = shallowMount(MockedWorkload, {
|
|
58
|
+
props: {
|
|
59
|
+
value: { metadata: {}, spec: { template: {} } },
|
|
60
|
+
params: {},
|
|
61
|
+
fvFormIsValid: {}
|
|
62
|
+
},
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
global: {
|
|
65
|
+
mocks: {
|
|
66
|
+
$route: { params: {}, query: {} },
|
|
67
|
+
$router: { applyQuery: jest.fn() },
|
|
68
|
+
$fetchState: { pending: false },
|
|
69
|
+
$store: {
|
|
70
|
+
getters: {
|
|
71
|
+
'cluster/schemaFor': jest.fn(),
|
|
72
|
+
'type-map/labelFor': jest.fn(),
|
|
73
|
+
'i18n/t': (text: string, v: {[key:string]: string}) => {
|
|
74
|
+
return `${ text }, ${ Object.values(v || {}) }`;
|
|
75
|
+
},
|
|
75
76
|
},
|
|
76
77
|
},
|
|
77
78
|
},
|
|
78
|
-
},
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
stubs: {
|
|
81
|
+
Tab: true,
|
|
82
|
+
LabeledInput: true,
|
|
83
|
+
VolumeClaimTemplate: true,
|
|
84
|
+
Networking: true,
|
|
85
|
+
Job: true,
|
|
86
|
+
NodeScheduling: true,
|
|
87
|
+
PodAffinity: true,
|
|
88
|
+
Tolerations: true,
|
|
89
|
+
Storage: true,
|
|
90
|
+
Tabbed: true,
|
|
91
|
+
LabeledSelect: true,
|
|
92
|
+
NameNsDescription: true,
|
|
93
|
+
CruResource: true,
|
|
94
|
+
KeyValue: true
|
|
95
|
+
},
|
|
95
96
|
},
|
|
96
|
-
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = (wrapper.vm as any).mapError(oldMessage).message;
|
|
100
|
+
|
|
101
|
+
expect(result).toStrictEqual(newMessage);
|
|
97
102
|
});
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
describe('secondaryResourceDataConfig', () => {
|
|
105
|
+
it('should filter out nodes with control-plane or etcd taints from workerNodes parsingFunc', () => {
|
|
106
|
+
const allNodeObjects = [
|
|
107
|
+
{
|
|
108
|
+
id: 'node-1',
|
|
109
|
+
spec: { taints: [{ key: 'node-role.kubernetes.io/control-plane', effect: 'NoSchedule' }] }
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'node-2',
|
|
113
|
+
spec: { taints: [{ key: 'node-role.kubernetes.io/etcd', effect: 'NoSchedule' }] }
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'node-3',
|
|
117
|
+
spec: { taints: [{ key: 'node-role.kubernetes.io/worker', effect: 'NoSchedule' }] }
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'node-4',
|
|
121
|
+
spec: { taints: [] }
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'node-5',
|
|
125
|
+
spec: {}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'node-6',
|
|
129
|
+
spec: null
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const { data } = (Workload.mixins[2] as any).methods.secondaryResourceDataConfig.apply({ value: { metadata: { namespace: 'test' } } });
|
|
134
|
+
const workerNodesParsingFunc = data.node.applyTo.find((r: any) => r.var === 'workerNodes').parsingFunc;
|
|
135
|
+
const result = workerNodesParsingFunc(allNodeObjects);
|
|
100
136
|
|
|
101
|
-
|
|
137
|
+
expect(result).toStrictEqual(['node-3', 'node-4', 'node-5', 'node-6']);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
102
140
|
});
|
|
103
141
|
});
|
package/edit/workload/index.vue
CHANGED
|
@@ -258,6 +258,7 @@ export default {
|
|
|
258
258
|
secondaryResourceData: this.secondaryResourceDataConfig(),
|
|
259
259
|
namespacedConfigMaps: [],
|
|
260
260
|
allNodes: null,
|
|
261
|
+
workerNodes: null,
|
|
261
262
|
allNodeObjects: [],
|
|
262
263
|
namespacedSecrets: [],
|
|
263
264
|
imagePullNamespacedSecrets: [],
|
|
@@ -647,7 +648,24 @@ export default {
|
|
|
647
648
|
parsingFunc: (data) => {
|
|
648
649
|
return data.map((node) => node.id);
|
|
649
650
|
}
|
|
650
|
-
}
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
var: 'workerNodes',
|
|
654
|
+
parsingFunc: (data) => {
|
|
655
|
+
const keys = [
|
|
656
|
+
`node-role.kubernetes.io/control-plane`,
|
|
657
|
+
`node-role.kubernetes.io/etcd`
|
|
658
|
+
];
|
|
659
|
+
|
|
660
|
+
return data
|
|
661
|
+
.filter((node) => {
|
|
662
|
+
const taints = node?.spec?.taints || [];
|
|
663
|
+
|
|
664
|
+
return taints.every((taint) => !keys.includes(taint.key));
|
|
665
|
+
})
|
|
666
|
+
.map((node) => node.id);
|
|
667
|
+
}
|
|
668
|
+
},
|
|
651
669
|
]
|
|
652
670
|
},
|
|
653
671
|
[SERVICE]: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { CATALOG, MANAGEMENT } from '@shell/config/types';
|
|
3
3
|
import Brand from '@shell/mixins/brand';
|
|
4
|
+
import CspAdapterUtils from '@shell/utils/cspAdaptor';
|
|
4
5
|
|
|
5
6
|
describe('brandMixin', () => {
|
|
6
7
|
const createWrapper = (vaiOn = false) => {
|
|
@@ -54,45 +55,47 @@ describe('brandMixin', () => {
|
|
|
54
55
|
data: () => data,
|
|
55
56
|
global: { mocks: { $store: store } }
|
|
56
57
|
});
|
|
57
|
-
const
|
|
58
|
+
const spyManagementDispatch = jest.spyOn(store, 'dispatch');
|
|
59
|
+
|
|
60
|
+
CspAdapterUtils.resetState();
|
|
58
61
|
|
|
59
62
|
return {
|
|
60
63
|
wrapper,
|
|
61
64
|
store,
|
|
62
|
-
|
|
65
|
+
spyManagementDispatch,
|
|
63
66
|
};
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
describe('should make correct requests', () => {
|
|
67
70
|
it('vai off', async() => {
|
|
68
|
-
const { wrapper,
|
|
71
|
+
const { wrapper, spyManagementDispatch } = createWrapper(false);
|
|
69
72
|
|
|
70
73
|
// NOTE - wrapper.vm.$options.fetch() doesn't work
|
|
71
74
|
await wrapper.vm.$options.fetch.apply(wrapper.vm);
|
|
72
75
|
|
|
73
76
|
// wrapper.vm.$nextTick();
|
|
74
|
-
expect(
|
|
77
|
+
expect(spyManagementDispatch).toHaveBeenNthCalledWith(1, 'management/findAll', {
|
|
75
78
|
type: MANAGEMENT.SETTING,
|
|
76
79
|
opt: {
|
|
77
80
|
load: 'multi', redirectUnauthorized: false, url: `/v1/${ MANAGEMENT.SETTING }s`
|
|
78
81
|
}
|
|
79
82
|
});
|
|
80
|
-
expect(
|
|
83
|
+
expect(spyManagementDispatch).toHaveBeenNthCalledWith(2, 'management/findAll', { type: CATALOG.APP });
|
|
81
84
|
});
|
|
82
85
|
|
|
83
86
|
it('vai on', async() => {
|
|
84
|
-
const { wrapper,
|
|
87
|
+
const { wrapper, spyManagementDispatch } = createWrapper(true);
|
|
85
88
|
|
|
86
89
|
// NOTE - wrapper.vm.$options.fetch() doesn't work
|
|
87
90
|
await wrapper.vm.$options.fetch.apply(wrapper.vm);
|
|
88
91
|
|
|
89
|
-
expect(
|
|
92
|
+
expect(spyManagementDispatch).toHaveBeenNthCalledWith(1, 'management/findAll', {
|
|
90
93
|
type: MANAGEMENT.SETTING,
|
|
91
94
|
opt: {
|
|
92
95
|
load: 'multi', url: `/v1/${ MANAGEMENT.SETTING }s`, redirectUnauthorized: false
|
|
93
96
|
}
|
|
94
97
|
});
|
|
95
|
-
expect(
|
|
98
|
+
expect(spyManagementDispatch).toHaveBeenNthCalledWith(2, 'management/findPage', {
|
|
96
99
|
type: CATALOG.APP,
|
|
97
100
|
opt: {
|
|
98
101
|
pagination: {
|
|
@@ -110,7 +113,9 @@ describe('brandMixin', () => {
|
|
|
110
113
|
pageSize: null,
|
|
111
114
|
projectsOrNamespaces: [],
|
|
112
115
|
sort: []
|
|
113
|
-
}
|
|
116
|
+
},
|
|
117
|
+
transient: true,
|
|
118
|
+
watch: false
|
|
114
119
|
}
|
|
115
120
|
});
|
|
116
121
|
});
|
|
@@ -120,7 +125,7 @@ describe('brandMixin', () => {
|
|
|
120
125
|
it('should have correct csp values (off)', async() => {
|
|
121
126
|
const { wrapper, store } = createWrapper();
|
|
122
127
|
|
|
123
|
-
const
|
|
128
|
+
const spyManagementDispatch = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
|
|
124
129
|
const { type } = options as any;
|
|
125
130
|
|
|
126
131
|
if (type === MANAGEMENT.SETTING) {
|
|
@@ -136,7 +141,7 @@ describe('brandMixin', () => {
|
|
|
136
141
|
// NOTE - wrapper.vm.$options.fetch() doesn't work
|
|
137
142
|
await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
|
|
138
143
|
|
|
139
|
-
expect(
|
|
144
|
+
expect(spyManagementDispatch).toHaveBeenCalledTimes(2);
|
|
140
145
|
|
|
141
146
|
expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
|
|
142
147
|
expect(wrapper.vm.cspAdapter).toBeFalsy();
|
|
@@ -145,7 +150,7 @@ describe('brandMixin', () => {
|
|
|
145
150
|
it.each(['rancher-csp-adapter', 'rancher-csp-billing-adapter'])('should have correct csp values (on - %p )', async(chartName) => {
|
|
146
151
|
const { wrapper, store } = createWrapper();
|
|
147
152
|
|
|
148
|
-
const
|
|
153
|
+
const spyManagementDispatch = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
|
|
149
154
|
const { type } = options as any;
|
|
150
155
|
|
|
151
156
|
if (type === MANAGEMENT.SETTING) {
|
|
@@ -161,7 +166,7 @@ describe('brandMixin', () => {
|
|
|
161
166
|
// NOTE - wrapper.vm.$options.fetch() doesn't work
|
|
162
167
|
await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
|
|
163
168
|
|
|
164
|
-
expect(
|
|
169
|
+
expect(spyManagementDispatch).toHaveBeenCalledTimes(2);
|
|
165
170
|
|
|
166
171
|
expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
|
|
167
172
|
expect(wrapper.vm.cspAdapter).toBeTruthy();
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import debounce from 'lodash/debounce';
|
|
3
3
|
import { getVersionData } from '@shell/config/version';
|
|
4
4
|
import { mapState, mapGetters } from 'vuex';
|
|
@@ -322,13 +322,13 @@ export default {
|
|
|
322
322
|
this.selectedCard = selected;
|
|
323
323
|
|
|
324
324
|
this.$shell.slideIn.open(ResourceDetails, {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
showHeader: false,
|
|
326
|
+
width: window.innerWidth / 3 > 530 ? `${ window.innerWidth / 3 }px` : '530px',
|
|
327
|
+
props: {
|
|
328
328
|
value,
|
|
329
329
|
statePanel,
|
|
330
330
|
workspace
|
|
331
|
-
}
|
|
331
|
+
},
|
|
332
332
|
});
|
|
333
333
|
},
|
|
334
334
|
|
|
@@ -180,6 +180,15 @@ export default {
|
|
|
180
180
|
* Load multiple different types of resources
|
|
181
181
|
*/
|
|
182
182
|
loadMulti(state, { data, ctx }) {
|
|
183
|
+
const type = data[0]?.type;
|
|
184
|
+
const cache = state.types[type];
|
|
185
|
+
|
|
186
|
+
if (cache?.havePage) {
|
|
187
|
+
console.warn(`Prevented \`loadMulti\` mutation from polluting the cache for type "${ type }" (currently represents a page).`); // eslint-disable-line no-console
|
|
188
|
+
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
183
192
|
for (const entry of data) {
|
|
184
193
|
const resource = load(state, { data: entry, ctx });
|
|
185
194
|
|
|
@@ -1200,6 +1200,18 @@ const defaultActions = {
|
|
|
1200
1200
|
},
|
|
1201
1201
|
|
|
1202
1202
|
'ws.resource.create'(ctx, msg) {
|
|
1203
|
+
const data = msg.data;
|
|
1204
|
+
const type = data?.type;
|
|
1205
|
+
|
|
1206
|
+
const havePage = ctx.getters['havePage'](type);
|
|
1207
|
+
|
|
1208
|
+
if (havePage) {
|
|
1209
|
+
console.warn(`Prevented watch \`resource.create\` data from polluting the cache for type "${ type }" (currently represents a page). To prevent any further issues the watch has been stopped.`, msg); // eslint-disable-line no-console
|
|
1210
|
+
ctx.dispatch('unwatch', { ...msg, type });
|
|
1211
|
+
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1203
1215
|
ctx.state.debugSocket && console.info(`Resource Create [${ ctx.getters.storeName }]`, msg.resourceType, msg); // eslint-disable-line no-console
|
|
1204
1216
|
queueChange(ctx, msg, true, 'Create');
|
|
1205
1217
|
},
|
|
@@ -1230,8 +1242,8 @@ const defaultActions = {
|
|
|
1230
1242
|
const havePage = ctx.getters['havePage'](type);
|
|
1231
1243
|
|
|
1232
1244
|
if (havePage) {
|
|
1233
|
-
console.warn(`Prevented watch \`resource.change\` data from polluting the cache for type "${ type }" (currently represents a page). To prevent any further issues the watch has been stopped.`,
|
|
1234
|
-
ctx.dispatch('unwatch',
|
|
1245
|
+
console.warn(`Prevented watch \`resource.change\` data from polluting the cache for type "${ type }" (currently represents a page). To prevent any further issues the watch has been stopped.`, msg); // eslint-disable-line no-console
|
|
1246
|
+
ctx.dispatch('unwatch', { ...msg, type });
|
|
1235
1247
|
|
|
1236
1248
|
return;
|
|
1237
1249
|
}
|
|
@@ -1277,6 +1289,15 @@ const defaultActions = {
|
|
|
1277
1289
|
}
|
|
1278
1290
|
}
|
|
1279
1291
|
|
|
1292
|
+
const havePage = ctx.getters['havePage'](type);
|
|
1293
|
+
|
|
1294
|
+
if (havePage) {
|
|
1295
|
+
console.warn(`Prevented watch \`resource.remove\` data from polluting the cache for type "${ type }" (currently represents a page). To prevent any further issues the watch has been stopped.`, msg); // eslint-disable-line no-console
|
|
1296
|
+
ctx.dispatch('unwatch', { ...msg, type });
|
|
1297
|
+
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1280
1301
|
queueChange(ctx, msg, false, 'Remove');
|
|
1281
1302
|
|
|
1282
1303
|
const typeOption = ctx.rootGetters['type-map/optionsFor'](type);
|
|
@@ -48,11 +48,6 @@ export interface ActionFindAllArgs extends ActionCoreFindArgs {
|
|
|
48
48
|
depaginate?: boolean,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export interface ActionFindPageTransientResult<T> {
|
|
52
|
-
pagination: StorePagination,
|
|
53
|
-
data: T[],
|
|
54
|
-
}
|
|
55
|
-
|
|
56
51
|
/**
|
|
57
52
|
* Args used for findPage action
|
|
58
53
|
*/
|
|
@@ -81,19 +76,46 @@ export interface ActionFindPageArgs extends ActionCoreFindArgs {
|
|
|
81
76
|
* If true don't persist the http response to the store, just pass it back
|
|
82
77
|
*/
|
|
83
78
|
transient?: boolean,
|
|
79
|
+
/**
|
|
80
|
+
* The target minimum revision for the resource.
|
|
81
|
+
*
|
|
82
|
+
* If this is higher than the latest revision known to rancher then an error will be returned
|
|
83
|
+
*/
|
|
84
|
+
revision?: string
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Response to a transient (not stored in cache) findPage action
|
|
89
|
+
*/
|
|
90
|
+
export type ActionFindPageTransientResponse<T = any> = {
|
|
87
91
|
data: T[],
|
|
88
92
|
pagination?: StorePagination
|
|
89
|
-
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Response to the findPage action
|
|
97
|
+
*
|
|
98
|
+
* If the request was transient (not stored in cache) this will be an object contain all the details of the request
|
|
99
|
+
*
|
|
100
|
+
* If the request was not transient this will just be the array of resources
|
|
101
|
+
*/
|
|
102
|
+
export type ActionFindPageResponse<T = any> = ActionFindPageTransientResponse | T[];
|
|
90
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Args used for findPage action
|
|
106
|
+
*/
|
|
91
107
|
export interface ActionFindMatchingArgs extends ActionCoreFindArgs {
|
|
92
108
|
labelSelector: KubeLabelSelector,
|
|
93
109
|
namespaced?: string,
|
|
94
110
|
depaginate?: boolean
|
|
95
111
|
}
|
|
96
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Response to the findMatching action
|
|
115
|
+
*/
|
|
97
116
|
export type ActionFindMatchingResponse<T = any> = ActionFindPageResponse<T>
|
|
98
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Args used for findLabelSelector action
|
|
120
|
+
*/
|
|
99
121
|
export type ActionFindLabelSelectorArgs = ActionFindPageArgs | ActionFindMatchingArgs;
|
package/utils/cspAdaptor.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// For testing these could be changed to something like...
|
|
2
2
|
|
|
3
3
|
import { CATALOG } from '@shell/config/types';
|
|
4
|
+
import { ActionFindPageArgs, ActionFindPageTransientResponse } from '@shell/types/store/dashboard-store.types';
|
|
4
5
|
import { FilterArgs, PaginationFilterField, PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
5
6
|
import { VuexStore } from '@shell/types/store/vuex';
|
|
6
7
|
|
|
7
8
|
const CSP_ADAPTER_APPS = ['rancher-csp-adapter', 'rancher-csp-billing-adapter'];
|
|
8
9
|
// For testing above line could be replaced with below line...
|
|
9
|
-
// const
|
|
10
|
+
// const CSP_ADAPTER_APPS = ['rancher-webhooka', 'rancher-webhook'];
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Helpers in order to
|
|
@@ -16,27 +17,44 @@ class CspAdapterUtils {
|
|
|
16
17
|
return $store.getters[`management/paginationEnabled`]({ id: CATALOG.APP, context: 'branding' });
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
static
|
|
20
|
+
private static apps?: any[] = undefined;
|
|
21
|
+
public static resetState() {
|
|
22
|
+
this.apps = undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async fetchCspAdaptorApp($store: VuexStore): Promise<any> {
|
|
26
|
+
if (this.apps) {
|
|
27
|
+
return this.apps;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
// For the login page, the schemas won't be loaded - we don't need the apps in this case
|
|
21
31
|
if ($store.getters['management/canList'](CATALOG.APP)) {
|
|
22
32
|
if (CspAdapterUtils.canPagination($store)) {
|
|
23
33
|
// Restrict the amount of apps we need to fetch
|
|
24
|
-
|
|
34
|
+
const opt: ActionFindPageArgs = {
|
|
35
|
+
pagination: new FilterArgs({
|
|
36
|
+
filters: PaginationParamFilter.createMultipleFields(CSP_ADAPTER_APPS.map(
|
|
37
|
+
(t) => new PaginationFilterField({
|
|
38
|
+
field: 'metadata.name',
|
|
39
|
+
value: t,
|
|
40
|
+
})
|
|
41
|
+
)),
|
|
42
|
+
}),
|
|
43
|
+
watch: false,
|
|
44
|
+
transient: true
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const resp: ActionFindPageTransientResponse = await $store.dispatch('management/findPage', {
|
|
25
48
|
type: CATALOG.APP,
|
|
26
|
-
opt
|
|
27
|
-
pagination: new FilterArgs({
|
|
28
|
-
filters: PaginationParamFilter.createMultipleFields(CSP_ADAPTER_APPS.map(
|
|
29
|
-
(t) => new PaginationFilterField({
|
|
30
|
-
field: 'metadata.name',
|
|
31
|
-
value: t,
|
|
32
|
-
})
|
|
33
|
-
)),
|
|
34
|
-
})
|
|
35
|
-
}
|
|
49
|
+
opt
|
|
36
50
|
});
|
|
51
|
+
|
|
52
|
+
this.apps = resp.data;
|
|
53
|
+
} else {
|
|
54
|
+
this.apps = await $store.dispatch('management/findAll', { type: CATALOG.APP });
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
return
|
|
57
|
+
return this.apps;
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
return Promise.resolve([]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
2
2
|
import { PaginationArgs, PaginationResourceContext } from '@shell/types/store/pagination.types';
|
|
3
3
|
import { VuexStore } from '@shell/types/store/vuex';
|
|
4
|
-
import { ActionFindPageArgs,
|
|
4
|
+
import { ActionFindPageArgs, ActionFindPageTransientResponse } from '@shell/types/store/dashboard-store.types';
|
|
5
5
|
import { STEVE_WATCH_EVENT_TYPES, STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
|
|
6
6
|
import { Reactive, reactive } from 'vue';
|
|
7
7
|
import { STEVE_UNWATCH_EVENT_PARAMS, STEVE_WATCH_EVENT_LISTENER_CALLBACK, STEVE_WATCH_EVENT_PARAMS, STEVE_WATCH_EVENT_PARAMS_COMMON } from '@shell/types/store/subscribe-events.types';
|
|
@@ -30,7 +30,7 @@ interface Args {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
interface Result<T> extends Omit<
|
|
33
|
+
interface Result<T> extends Omit<ActionFindPageTransientResponse<T>, 'data'> {
|
|
34
34
|
data: Reactive<T[]>
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -86,7 +86,7 @@ class PaginationWrapper<T extends object> {
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
// Fetch
|
|
89
|
-
const out:
|
|
89
|
+
const out: ActionFindPageTransientResponse<T> = await this.$store.dispatch(`${ this.enabledFor.store }/findPage`, { opt, type: this.enabledFor.resource?.id });
|
|
90
90
|
|
|
91
91
|
// Watch
|
|
92
92
|
const firstTime = !this.steveWatchParams;
|