@rancher/shell 0.3.16 → 0.3.18
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/images/wechat-qr-code.jpg +0 -0
- package/assets/translations/en-us.yaml +75 -16
- package/assets/translations/zh-hans.yaml +151 -15
- package/chart/__tests__/S3.test.ts +50 -0
- package/chart/rancher-backup/S3.vue +21 -0
- package/chart/rancher-backup/index.vue +4 -0
- package/components/AsyncButton.vue +1 -1
- package/components/CommunityLinks.vue +1 -0
- package/components/FileDiff.vue +92 -85
- package/components/Inactivity.vue +10 -0
- package/components/LazyImage.vue +2 -2
- package/components/PromptRestore.vue +7 -5
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +8 -14
- package/components/ResourceList/index.vue +1 -1
- package/components/ResourceTable.vue +50 -2
- package/components/YamlEditor.vue +1 -0
- package/components/__tests__/PromptRestore.test.ts +72 -0
- package/components/auth/AzureWarning.vue +1 -1
- package/components/auth/RoleDetailEdit.vue +1 -0
- package/components/fleet/FleetResources.vue +3 -64
- package/components/form/FileImageSelector.vue +9 -0
- package/components/form/FileSelector.vue +2 -1
- package/components/form/MatchExpressions.vue +1 -3
- package/components/form/NameNsDescription.vue +28 -12
- package/components/form/NodeAffinity.vue +2 -2
- package/components/form/PodAffinity.vue +2 -2
- package/components/form/ResourceTabs/index.vue +8 -2
- package/components/form/Select.vue +16 -0
- package/components/form/__tests__/FileImageSelector.test.ts +42 -0
- package/components/form/__tests__/FileSelector.test.ts +76 -0
- package/components/form/__tests__/NodeAffinity.test.ts +38 -0
- package/components/form/__tests__/PodAffinity.test.ts +46 -0
- package/components/formatter/ClusterLink.vue +8 -4
- package/components/formatter/ClusterProvider.vue +3 -1
- package/components/formatter/ImageName.vue +23 -0
- package/components/formatter/PodImages.vue +7 -1
- package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
- package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
- package/components/nav/Header.vue +2 -2
- package/components/nav/WindowManager/ContainerShell.vue +60 -36
- package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
- package/config/__test__/home-links.test.ts +62 -0
- package/config/home-links.js +15 -3
- package/config/labels-annotations.js +7 -2
- package/config/persistentVolume.ts +108 -0
- package/config/product/manager.js +5 -1
- package/config/router.js +0 -4
- package/config/settings.ts +4 -0
- package/config/table-headers.js +6 -5
- package/config/types.js +2 -0
- package/config/uiplugins.js +50 -5
- package/core/plugin-helpers.js +39 -15
- package/core/plugin.ts +9 -0
- package/core/plugins.js +1 -1
- package/core/types-provisioning.ts +253 -0
- package/core/types.ts +21 -3
- package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
- package/detail/fleet.cattle.io.gitrepo.vue +10 -2
- package/detail/node.vue +6 -6
- package/detail/pod.vue +38 -9
- package/detail/provisioning.cattle.io.cluster.vue +46 -7
- package/detail/workload/index.vue +49 -18
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
- package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
- package/edit/auth/github.vue +1 -0
- package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
- package/edit/fleet.cattle.io.clustergroup.vue +14 -3
- package/edit/fleet.cattle.io.gitrepo.vue +18 -1
- package/edit/namespace.vue +9 -1
- package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
- package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
- package/edit/persistentvolume/index.vue +2 -1
- package/edit/persistentvolume/plugins/csi.vue +3 -1
- package/edit/persistentvolume/plugins/longhorn.vue +12 -12
- package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
- package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +53 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +335 -151
- package/edit/storage.k8s.io.storageclass/index.vue +1 -2
- package/edit/ui.cattle.io.navlink.vue +213 -186
- package/initialize/App.js +3 -13
- package/initialize/layouts.ts +26 -0
- package/layouts/default.vue +1 -1
- package/list/group.principal.vue +1 -1
- package/list/provisioning.cattle.io.cluster.vue +8 -1
- package/middleware/authenticated.js +101 -5
- package/mixins/brand.js +39 -3
- package/mixins/child-hook.js +2 -2
- package/mixins/create-edit-view/impl.js +4 -4
- package/models/chart.js +1 -1
- package/models/fleet.cattle.io.cluster.js +33 -4
- package/models/fleet.cattle.io.gitrepo.js +113 -38
- package/models/management.cattle.io.kontainerdriver.js +14 -0
- package/models/persistentvolume.js +2 -111
- package/models/pod.js +30 -0
- package/models/provisioning.cattle.io.cluster.js +9 -1
- package/models/rke.cattle.io.etcdsnapshot.js +10 -7
- package/package.json +2 -2
- package/pages/about.vue +8 -2
- package/pages/auth/login.vue +1 -1
- package/pages/auth/logout.vue +11 -3
- package/pages/c/_cluster/apps/charts/index.vue +5 -2
- package/pages/c/_cluster/apps/charts/install.vue +5 -0
- package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +2 -11
- package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
- package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
- package/pages/c/_cluster/settings/brand.vue +11 -8
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
- package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
- package/pages/c/_cluster/uiplugins/index.vue +160 -44
- package/pages/docs/_doc.vue +9 -3
- package/pages/home.vue +6 -6
- package/pages/support/index.vue +10 -4
- package/pkg/auto-import.js +1 -1
- package/plugins/clean-tooltip-directive.js +1 -1
- package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
- package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
- package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/resource-class.js +39 -2
- package/plugins/plugin.js +9 -1
- package/plugins/steve/__tests__/getters.spec.ts +93 -0
- package/plugins/steve/getters.js +21 -1
- package/plugins/steve/subscribe.js +1 -3
- package/rancher-components/BadgeState/BadgeState.vue +5 -1
- package/rancher-components/Banner/Banner.test.ts +51 -1
- package/rancher-components/Banner/Banner.vue +134 -53
- package/rancher-components/Card/Card.test.ts +37 -0
- package/rancher-components/Card/Card.vue +24 -7
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
- package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
- package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
- package/rancher-components/Form/Radio/RadioButton.vue +30 -13
- package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
- package/rancher-components/StringList/StringList.test.ts +453 -49
- package/rancher-components/StringList/StringList.vue +44 -26
- package/scripts/extension/publish +2 -2
- package/scripts/typegen.sh +11 -2
- package/server/server-middleware.js +4 -12
- package/store/index.js +14 -3
- package/store/prefs.js +0 -3
- package/store/store-types.js +2 -0
- package/store/type-map.js +17 -29
- package/types/api.d.ts +1 -0
- package/types/fleet.d.ts +1 -0
- package/types/shell/index.d.ts +931 -85
- package/types/userPreferences.d.ts +1 -1
- package/utils/__mocks__/socket.js +21 -0
- package/utils/grafana.js +23 -11
- package/utils/kube.js +9 -0
- package/utils/object.js +27 -0
- package/utils/selector.js +2 -1
- package/utils/settings.ts +2 -2
- package/utils/validators/formRules/index.ts +3 -3
- package/vue.config.js +3 -2
- package/components/.DS_Store +0 -0
- package/components/__tests__/.DS_Store +0 -0
- package/creators/pkg/package-lock.json +0 -37
- package/pages/safeMode.vue +0 -17
- package/plugins/steve/urloptions.js +0 -47
- package/yarn-error.log +0 -196
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { mount, Wrapper } from '@vue/test-utils';
|
|
2
|
+
import ContainerShell from '@shell/components/nav/WindowManager/ContainerShell.vue';
|
|
3
|
+
import Socket, {
|
|
4
|
+
addEventListener, EVENT_CONNECTED, EVENT_CONNECTING, EVENT_DISCONNECTED, EVENT_MESSAGE, EVENT_CONNECT_ERROR
|
|
5
|
+
} from '@shell/utils/socket';
|
|
6
|
+
|
|
7
|
+
jest.mock('@shell/utils/socket');
|
|
8
|
+
jest.mock('@shell/utils/crypto', () => {
|
|
9
|
+
const originalModule = jest.requireActual('@shell/utils/crypto');
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
__esModule: true,
|
|
13
|
+
...originalModule,
|
|
14
|
+
base64Decode: jest.fn().mockImplementation((str:String) => str)
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('component: ContainerShell', () => {
|
|
19
|
+
const action = jest.fn();
|
|
20
|
+
const translate = jest.fn();
|
|
21
|
+
const schemaFor = jest.fn();
|
|
22
|
+
const onData = jest.fn();
|
|
23
|
+
const loadAddon = jest.fn();
|
|
24
|
+
const open = jest.fn();
|
|
25
|
+
const focus = jest.fn();
|
|
26
|
+
const fit = jest.fn();
|
|
27
|
+
const proposeDimensions = jest.fn().mockImplementation(() => {
|
|
28
|
+
return { rows: 1 };
|
|
29
|
+
});
|
|
30
|
+
const write = jest.fn();
|
|
31
|
+
const reset = jest.fn();
|
|
32
|
+
|
|
33
|
+
jest.mock(/* webpackChunkName: "xterm" */ 'xterm', () => {
|
|
34
|
+
return {
|
|
35
|
+
Terminal: class {
|
|
36
|
+
onData = onData;
|
|
37
|
+
loadAddon = loadAddon;
|
|
38
|
+
open = open;
|
|
39
|
+
focus = focus;
|
|
40
|
+
write = write;
|
|
41
|
+
reset = reset
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
jest.mock(/* webpackChunkName: "xterm" */ 'xterm-addon-fit', () => {
|
|
46
|
+
return {
|
|
47
|
+
FitAddon: class {
|
|
48
|
+
fit = fit
|
|
49
|
+
proposeDimensions = proposeDimensions
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const defaultContainerShellParams = {
|
|
55
|
+
propsData: {
|
|
56
|
+
tab: {},
|
|
57
|
+
active: true,
|
|
58
|
+
height: 1000,
|
|
59
|
+
pod: {
|
|
60
|
+
spec: { nodeName: 'nodeId' },
|
|
61
|
+
links: { view: 'url' },
|
|
62
|
+
os: 'linux'
|
|
63
|
+
},
|
|
64
|
+
initialContainer: 'containerId'
|
|
65
|
+
},
|
|
66
|
+
stubs: ['resize-observer'],
|
|
67
|
+
mocks: {
|
|
68
|
+
$store: {
|
|
69
|
+
dispatch: action,
|
|
70
|
+
getters: {
|
|
71
|
+
'i18n/t': translate,
|
|
72
|
+
'cluster/schemaFor': schemaFor
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const resetMocks = () => {
|
|
79
|
+
// Clear all instances and calls to constructor and all methods:
|
|
80
|
+
jest.clearAllMocks();
|
|
81
|
+
defaultContainerShellParams.propsData.pod.os = 'linux';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const wrapperPostMounted = async(params: Object) => {
|
|
85
|
+
const wrapper = await mount(ContainerShell, params);
|
|
86
|
+
|
|
87
|
+
// these awaits are all associated with the various async dyamic imports on xterm
|
|
88
|
+
await wrapper.vm.$nextTick();
|
|
89
|
+
await wrapper.vm.$nextTick();
|
|
90
|
+
await wrapper.vm.$nextTick();
|
|
91
|
+
await wrapper.vm.$nextTick();
|
|
92
|
+
|
|
93
|
+
return wrapper;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
it.todo('test that we are calling the xterm terminal and fitAddon class method mocks correctly');
|
|
97
|
+
|
|
98
|
+
it('creates a window on the page', async() => {
|
|
99
|
+
resetMocks();
|
|
100
|
+
const wrapper: Wrapper<InstanceType<typeof ContainerShell> & { [key: string]: any }> = await wrapperPostMounted(defaultContainerShellParams);
|
|
101
|
+
const windowElement = wrapper.find('div.window');
|
|
102
|
+
|
|
103
|
+
expect(windowElement.exists()).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('the find action for the node is called if schemaFor finds a schema for NODE', async() => {
|
|
107
|
+
resetMocks();
|
|
108
|
+
const testSchemaFindsSchemaParams = {
|
|
109
|
+
...defaultContainerShellParams,
|
|
110
|
+
mocks: {
|
|
111
|
+
...defaultContainerShellParams.mocks,
|
|
112
|
+
$store: {
|
|
113
|
+
...defaultContainerShellParams.mocks.$store,
|
|
114
|
+
getters: {
|
|
115
|
+
...defaultContainerShellParams.mocks.$store.getters,
|
|
116
|
+
'cluster/schemaFor': jest.fn().mockImplementation(() => true)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
await wrapperPostMounted(testSchemaFindsSchemaParams);
|
|
123
|
+
|
|
124
|
+
const actionParams = action.mock.calls[0];
|
|
125
|
+
|
|
126
|
+
expect(action.mock.calls).toHaveLength(1);
|
|
127
|
+
expect(actionParams[0]).toBe('cluster/find');
|
|
128
|
+
expect(actionParams[1]).toStrictEqual({
|
|
129
|
+
id: 'nodeId',
|
|
130
|
+
type: 'node'
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('the translate getter for the ...', async() => {
|
|
135
|
+
resetMocks();
|
|
136
|
+
await wrapperPostMounted(defaultContainerShellParams);
|
|
137
|
+
const firstTranslate = translate.mock.calls[0];
|
|
138
|
+
const secondTranslate = translate.mock.calls[1];
|
|
139
|
+
|
|
140
|
+
expect(translate.mock.calls).toHaveLength(2);
|
|
141
|
+
expect(firstTranslate[0]).toBe('wm.containerShell.clear');
|
|
142
|
+
expect(firstTranslate[1]).toStrictEqual({});
|
|
143
|
+
expect(secondTranslate[0]).toBe('wm.connection.disconnected');
|
|
144
|
+
expect(secondTranslate[1]).toStrictEqual({});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('the socket is instantiated', async() => {
|
|
148
|
+
resetMocks();
|
|
149
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
150
|
+
|
|
151
|
+
const socketParams = Socket.mock.calls[0][0]
|
|
152
|
+
.split('?')[1]
|
|
153
|
+
.split('&')
|
|
154
|
+
.reduce((paramMap: Object, param: [String, String]) => {
|
|
155
|
+
const [key, value] = param.split('=');
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
...paramMap,
|
|
159
|
+
[key]: decodeURIComponent(value)
|
|
160
|
+
};
|
|
161
|
+
}, {});
|
|
162
|
+
|
|
163
|
+
expect(Socket.mock.calls).toHaveLength(1);
|
|
164
|
+
expect(socketParams.command).toBe('TERM=xterm-256color; export TERM; [ -x /bin/bash ] && ([ -x /usr/bin/script ] && /usr/bin/script -q -c "/bin/bash" /dev/null || exec /bin/bash) || exec /bin/sh');
|
|
165
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('the sockets events are bound', async() => {
|
|
169
|
+
resetMocks();
|
|
170
|
+
await wrapperPostMounted(defaultContainerShellParams);
|
|
171
|
+
|
|
172
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
173
|
+
|
|
174
|
+
expect(addEventListenerCalls).toHaveLength(5);
|
|
175
|
+
expect(addEventListenerCalls[0][0]).toBe(EVENT_CONNECTING);
|
|
176
|
+
expect(addEventListenerCalls[1][0]).toBe(EVENT_CONNECT_ERROR);
|
|
177
|
+
expect(addEventListenerCalls[2][0]).toBe(EVENT_CONNECTED);
|
|
178
|
+
expect(addEventListenerCalls[3][0]).toBe(EVENT_DISCONNECTED);
|
|
179
|
+
expect(addEventListenerCalls[4][0]).toBe(EVENT_MESSAGE);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('the socket connecting event sets data props correctly', async() => {
|
|
183
|
+
resetMocks();
|
|
184
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
185
|
+
|
|
186
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
187
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
188
|
+
|
|
189
|
+
eventConnecting();
|
|
190
|
+
|
|
191
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
192
|
+
expect(wrapper.vm.isOpening).toBe(true);
|
|
193
|
+
expect(wrapper.vm.errorMsg).toBe('');
|
|
194
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('the socket connect error event sets data props correctly and calls the console', async() => {
|
|
198
|
+
resetMocks();
|
|
199
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
200
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
201
|
+
const errorMessage = 'eventConnectError';
|
|
202
|
+
|
|
203
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
204
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
205
|
+
const eventConnectError = addEventListenerCalls[1][1];
|
|
206
|
+
|
|
207
|
+
eventConnecting();
|
|
208
|
+
eventConnectError(errorMessage);
|
|
209
|
+
|
|
210
|
+
expect(consoleError.mock.calls[0][0]).toBe('Connect Error');
|
|
211
|
+
expect(consoleError.mock.calls[0][1]).toBe(errorMessage);
|
|
212
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
213
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
214
|
+
expect(wrapper.vm.errorMsg).toBe('');
|
|
215
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('the socket connected event sets data props correctly', async() => {
|
|
219
|
+
resetMocks();
|
|
220
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
221
|
+
|
|
222
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
223
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
224
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
225
|
+
|
|
226
|
+
eventConnecting();
|
|
227
|
+
eventConnected();
|
|
228
|
+
|
|
229
|
+
expect(wrapper.vm.isOpen).toBe(true);
|
|
230
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
231
|
+
expect(wrapper.vm.errorMsg).toBe('');
|
|
232
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it.todo('test that fit and flush are operating properly');
|
|
236
|
+
it.todo('test that we are properly feeding the terminal the commandOnFirstConnect prop correctly on connected');
|
|
237
|
+
|
|
238
|
+
it('the socket message event sets data props correctly', async() => {
|
|
239
|
+
resetMocks();
|
|
240
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
241
|
+
|
|
242
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
243
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
244
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
245
|
+
const eventMessage = addEventListenerCalls[4][1];
|
|
246
|
+
|
|
247
|
+
eventConnecting();
|
|
248
|
+
eventConnected();
|
|
249
|
+
eventMessage({ detail: { data: '1noError' } });
|
|
250
|
+
|
|
251
|
+
expect(wrapper.vm.isOpen).toBe(true);
|
|
252
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
253
|
+
expect(wrapper.vm.errorMsg).toBe('');
|
|
254
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('the socket message event sets data props correctly and call the console on error', async() => {
|
|
258
|
+
resetMocks();
|
|
259
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
260
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
261
|
+
const errorMessage = 'eventMessageError';
|
|
262
|
+
|
|
263
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
264
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
265
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
266
|
+
const eventMessage = addEventListenerCalls[4][1];
|
|
267
|
+
|
|
268
|
+
eventConnecting();
|
|
269
|
+
eventConnected();
|
|
270
|
+
eventMessage({ detail: { data: `3${ errorMessage }` } });
|
|
271
|
+
|
|
272
|
+
expect(consoleError.mock.calls[0][0]).toBe(errorMessage);
|
|
273
|
+
expect(wrapper.vm.isOpen).toBe(true);
|
|
274
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
275
|
+
expect(wrapper.vm.errorMsg).toBe(errorMessage);
|
|
276
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('the socket disconnect event without an error sets data props correctly', async() => {
|
|
280
|
+
resetMocks();
|
|
281
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
282
|
+
|
|
283
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
284
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
285
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
286
|
+
const eventDisconnected = addEventListenerCalls[3][1];
|
|
287
|
+
|
|
288
|
+
eventConnecting();
|
|
289
|
+
eventConnected();
|
|
290
|
+
eventDisconnected();
|
|
291
|
+
|
|
292
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
293
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
294
|
+
expect(wrapper.vm.errorMsg).toBe('');
|
|
295
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('the socket disconnect event with an error sets data props correctly and attempts a second connect', async() => {
|
|
299
|
+
resetMocks();
|
|
300
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
301
|
+
const connect = jest.spyOn(ContainerShell.methods, 'connect');
|
|
302
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
303
|
+
const errorMessage = 'eventMessageError';
|
|
304
|
+
|
|
305
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
306
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
307
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
308
|
+
const eventMessage = addEventListenerCalls[4][1];
|
|
309
|
+
const eventDisconnected = addEventListenerCalls[3][1];
|
|
310
|
+
|
|
311
|
+
eventConnecting();
|
|
312
|
+
eventConnected(); // for whatever reason, when this is called the mock on addEventListener doesn't clear it's calls...
|
|
313
|
+
eventMessage({ detail: { data: `3${ errorMessage }` } });
|
|
314
|
+
|
|
315
|
+
// we start with 2 backup shells but remove whichever one we already used
|
|
316
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
317
|
+
|
|
318
|
+
eventDisconnected();
|
|
319
|
+
|
|
320
|
+
expect(consoleError.mock.calls[0][0]).toBe(errorMessage);
|
|
321
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
322
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
323
|
+
expect(wrapper.vm.errorMsg).toBe('eventMessageError');
|
|
324
|
+
// the backup shell that was leftover was windows so it became the new os in dataprops
|
|
325
|
+
expect(wrapper.vm.os).toBe('windows');
|
|
326
|
+
// but we still didn't write it to the pod itself since we don't know if it worked
|
|
327
|
+
expect(defaultContainerShellParams.propsData.pod.os).toBeUndefined();
|
|
328
|
+
// we can see here that we removed that last backup shell because we're attempting to use it now
|
|
329
|
+
expect(wrapper.vm.backupShells).toHaveLength(0);
|
|
330
|
+
expect(connect.mock.calls).toHaveLength(2);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('the socket disconnect event fires twice, sets data props correctly, and only attempts two connects if the pod os is linux', async() => {
|
|
334
|
+
resetMocks();
|
|
335
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
336
|
+
const connect = jest.spyOn(ContainerShell.methods, 'connect');
|
|
337
|
+
const wrapper = await wrapperPostMounted(defaultContainerShellParams);
|
|
338
|
+
const linuxErrorMessage = 'eventLinuxMessageError';
|
|
339
|
+
const windowsErrorMessage = 'eventWindowsMessageError';
|
|
340
|
+
|
|
341
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
342
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
343
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
344
|
+
const eventMessage = addEventListenerCalls[4][1];
|
|
345
|
+
const eventDisconnected = addEventListenerCalls[3][1];
|
|
346
|
+
|
|
347
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
348
|
+
|
|
349
|
+
eventConnecting();
|
|
350
|
+
eventConnected();
|
|
351
|
+
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
352
|
+
eventDisconnected();
|
|
353
|
+
|
|
354
|
+
expect(wrapper.vm.backupShells).toHaveLength(0);
|
|
355
|
+
expect(wrapper.vm.os).toBe('windows');
|
|
356
|
+
// the pod os was 'linux' but we cleared it out since that didn't work
|
|
357
|
+
expect(defaultContainerShellParams.propsData.pod.os).toBeUndefined();
|
|
358
|
+
expect(connect.mock.calls).toHaveLength(2);
|
|
359
|
+
|
|
360
|
+
eventConnecting();
|
|
361
|
+
eventConnected();
|
|
362
|
+
eventMessage({ detail: { data: `3${ windowsErrorMessage }` } });
|
|
363
|
+
eventDisconnected();
|
|
364
|
+
|
|
365
|
+
expect(consoleError.mock.calls[0][0]).toBe(linuxErrorMessage);
|
|
366
|
+
expect(consoleError.mock.calls[1][0]).toBe(windowsErrorMessage);
|
|
367
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
368
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
369
|
+
expect(wrapper.vm.errorMsg).toBe(windowsErrorMessage);
|
|
370
|
+
expect(wrapper.vm.os).toBe('windows');
|
|
371
|
+
// we never found a shell that worked so we're going to leave the pod os as undefined
|
|
372
|
+
expect(defaultContainerShellParams.propsData.pod.os).toBeUndefined();
|
|
373
|
+
// we're out of backupShells now so we're not going to retry after that second disconnect
|
|
374
|
+
expect(connect.mock.calls).toHaveLength(2);
|
|
375
|
+
|
|
376
|
+
resetMocks();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('the socket disconnect event fires twice, sets data props correctly, and only attempts two connects if the pod os is undefined', async() => {
|
|
380
|
+
resetMocks();
|
|
381
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
382
|
+
const connect = jest.spyOn(ContainerShell.methods, 'connect');
|
|
383
|
+
const testUndefinedOsParams = {
|
|
384
|
+
...defaultContainerShellParams,
|
|
385
|
+
propsData: {
|
|
386
|
+
...defaultContainerShellParams.propsData,
|
|
387
|
+
pod: {
|
|
388
|
+
...defaultContainerShellParams.propsData.pod,
|
|
389
|
+
os: undefined
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const wrapper = await wrapperPostMounted(testUndefinedOsParams);
|
|
394
|
+
const linuxErrorMessage = 'eventLinuxMessageError';
|
|
395
|
+
const windowsErrorMessage = 'eventWindowsMessageError';
|
|
396
|
+
|
|
397
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
398
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
399
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
400
|
+
const eventMessage = addEventListenerCalls[4][1];
|
|
401
|
+
const eventDisconnected = addEventListenerCalls[3][1];
|
|
402
|
+
|
|
403
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
404
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
405
|
+
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
406
|
+
|
|
407
|
+
eventConnecting();
|
|
408
|
+
eventConnected();
|
|
409
|
+
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
410
|
+
eventDisconnected();
|
|
411
|
+
|
|
412
|
+
expect(wrapper.vm.backupShells).toHaveLength(0);
|
|
413
|
+
expect(wrapper.vm.os).toBe('windows');
|
|
414
|
+
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
415
|
+
|
|
416
|
+
eventConnecting();
|
|
417
|
+
eventConnected();
|
|
418
|
+
eventMessage({ detail: { data: `3${ windowsErrorMessage }` } });
|
|
419
|
+
eventDisconnected();
|
|
420
|
+
|
|
421
|
+
expect(consoleError.mock.calls[0][0]).toBe(linuxErrorMessage);
|
|
422
|
+
expect(consoleError.mock.calls[1][0]).toBe(windowsErrorMessage);
|
|
423
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
424
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
425
|
+
expect(wrapper.vm.errorMsg).toBe(windowsErrorMessage);
|
|
426
|
+
expect(wrapper.vm.os).toBe('windows');
|
|
427
|
+
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
428
|
+
expect(connect.mock.calls).toHaveLength(2);
|
|
429
|
+
|
|
430
|
+
resetMocks();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('the socket disconnect event fires twice, sets data props correctly, and only attempts two connects, and sets the pod os if the pod os is initially undefined and connects on the second attempt', async() => {
|
|
434
|
+
resetMocks();
|
|
435
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
436
|
+
const connect = jest.spyOn(ContainerShell.methods, 'connect');
|
|
437
|
+
const testUndefinedOsParams = {
|
|
438
|
+
...defaultContainerShellParams,
|
|
439
|
+
propsData: {
|
|
440
|
+
...defaultContainerShellParams.propsData,
|
|
441
|
+
pod: {
|
|
442
|
+
...defaultContainerShellParams.propsData.pod,
|
|
443
|
+
os: undefined
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const wrapper = await wrapperPostMounted(testUndefinedOsParams);
|
|
448
|
+
const linuxErrorMessage = 'eventLinuxMessageError';
|
|
449
|
+
const windowsShellMessage = 'eventWindowsMessageShell';
|
|
450
|
+
|
|
451
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
452
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
453
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
454
|
+
const eventMessage = addEventListenerCalls[4][1];
|
|
455
|
+
const eventDisconnected = addEventListenerCalls[3][1];
|
|
456
|
+
|
|
457
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
458
|
+
|
|
459
|
+
eventConnecting();
|
|
460
|
+
eventConnected();
|
|
461
|
+
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
462
|
+
eventDisconnected();
|
|
463
|
+
|
|
464
|
+
expect(wrapper.vm.backupShells).toHaveLength(0);
|
|
465
|
+
expect(wrapper.vm.os).toBe('windows');
|
|
466
|
+
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
467
|
+
expect(wrapper.vm.errorMsg).toBe(linuxErrorMessage);
|
|
468
|
+
|
|
469
|
+
eventConnecting();
|
|
470
|
+
eventConnected();
|
|
471
|
+
eventMessage({ detail: { data: `1${ windowsShellMessage }` } });
|
|
472
|
+
|
|
473
|
+
expect(consoleError.mock.calls[0][0]).toBe(linuxErrorMessage);
|
|
474
|
+
expect(consoleError.mock.calls[1]).toBeUndefined();
|
|
475
|
+
expect(wrapper.vm.isOpen).toBe(true);
|
|
476
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
477
|
+
expect(wrapper.vm.errorMsg).toBe('');
|
|
478
|
+
expect(wrapper.vm.os).toBe('windows');
|
|
479
|
+
// the second shell worked so we're going to set it on the pod itself so if we need to connect again we'll just use the correct shell on the first attempt
|
|
480
|
+
expect(testUndefinedOsParams.propsData.pod.os).toBe('windows');
|
|
481
|
+
expect(connect.mock.calls).toHaveLength(2);
|
|
482
|
+
|
|
483
|
+
resetMocks();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('the socket disconnect event fires 3 times, sets data props correctly, and only attempts 3 connects if the pod os is defined at the pods parent node', async() => {
|
|
487
|
+
resetMocks();
|
|
488
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
489
|
+
const connect = jest.spyOn(ContainerShell.methods, 'connect');
|
|
490
|
+
const testNodeDefinedOsParams = {
|
|
491
|
+
...defaultContainerShellParams,
|
|
492
|
+
propsData: {
|
|
493
|
+
...defaultContainerShellParams.propsData,
|
|
494
|
+
pod: {
|
|
495
|
+
...defaultContainerShellParams.propsData.pod,
|
|
496
|
+
_os: 'linux',
|
|
497
|
+
get os(): string {
|
|
498
|
+
return 'linux';
|
|
499
|
+
},
|
|
500
|
+
set os(os: string) {
|
|
501
|
+
this._os = os;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const wrapper = await wrapperPostMounted(testNodeDefinedOsParams);
|
|
508
|
+
const linuxErrorMessage = 'eventLinuxMessageError';
|
|
509
|
+
|
|
510
|
+
const addEventListenerCalls = addEventListener.mock.calls;
|
|
511
|
+
const eventConnecting = addEventListenerCalls[0][1];
|
|
512
|
+
const eventConnected = addEventListenerCalls[2][1];
|
|
513
|
+
const eventMessage = addEventListenerCalls[4][1];
|
|
514
|
+
const eventDisconnected = addEventListenerCalls[3][1];
|
|
515
|
+
|
|
516
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
517
|
+
|
|
518
|
+
eventConnecting();
|
|
519
|
+
eventConnected();
|
|
520
|
+
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
521
|
+
eventDisconnected();
|
|
522
|
+
|
|
523
|
+
// the parent node's os overrides the _os field in the pod so it didn't change on the previous failure and we know it is correct, thus we're not burning down our backup shells and just retrying the same shell
|
|
524
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
525
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
526
|
+
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
527
|
+
expect(wrapper.vm.errorMsg).toBe(linuxErrorMessage);
|
|
528
|
+
|
|
529
|
+
eventConnecting();
|
|
530
|
+
eventConnected();
|
|
531
|
+
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
532
|
+
eventDisconnected();
|
|
533
|
+
|
|
534
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
535
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
536
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
537
|
+
expect(wrapper.vm.errorMsg).toBe(linuxErrorMessage);
|
|
538
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
539
|
+
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
540
|
+
expect(connect.mock.calls).toHaveLength(3);
|
|
541
|
+
|
|
542
|
+
eventConnecting();
|
|
543
|
+
eventConnected();
|
|
544
|
+
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
545
|
+
eventDisconnected();
|
|
546
|
+
|
|
547
|
+
expect(consoleError.mock.calls[0][0]).toBe(linuxErrorMessage);
|
|
548
|
+
expect(consoleError.mock.calls[1][0]).toBe(linuxErrorMessage);
|
|
549
|
+
expect(consoleError.mock.calls[2][0]).toBe(linuxErrorMessage);
|
|
550
|
+
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
551
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
552
|
+
expect(wrapper.vm.isOpening).toBe(false);
|
|
553
|
+
expect(wrapper.vm.errorMsg).toBe(linuxErrorMessage);
|
|
554
|
+
expect(wrapper.vm.os).toBe('linux');
|
|
555
|
+
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
556
|
+
// at some point we have to stop retying and if we're not burning through backup shells, there's a retry limit of 2 for a total of 3 attempts
|
|
557
|
+
expect(connect.mock.calls).toHaveLength(3);
|
|
558
|
+
|
|
559
|
+
resetMocks();
|
|
560
|
+
});
|
|
561
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Vuex from 'vuex';
|
|
2
|
+
import { createLocalVue } from '@vue/test-utils';
|
|
3
|
+
import { ensureSupportLink } from '@shell/config/home-links.js';
|
|
4
|
+
import { getters, state, mutations } from '@shell/store/i18n.js';
|
|
5
|
+
|
|
6
|
+
jest.mock('@shell/assets/translations/en-us.yaml', () => ({
|
|
7
|
+
locale: {
|
|
8
|
+
'en-us': 'English',
|
|
9
|
+
'zh-hans': '简体中文',
|
|
10
|
+
none: '(None)',
|
|
11
|
+
}
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('fx: ensureSupportLink', () => {
|
|
15
|
+
const localVue = createLocalVue();
|
|
16
|
+
|
|
17
|
+
localVue.use(Vuex);
|
|
18
|
+
|
|
19
|
+
const store = new Vuex.Store({
|
|
20
|
+
state,
|
|
21
|
+
getters: {
|
|
22
|
+
'i18n/selectedLocaleLabel': getters.selectedLocaleLabel,
|
|
23
|
+
'i18n/t': getters.t,
|
|
24
|
+
},
|
|
25
|
+
mutations,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
store.commit('loadTranslations', {
|
|
29
|
+
locale: 'zh-zhans',
|
|
30
|
+
translations: {
|
|
31
|
+
locale: {
|
|
32
|
+
'en-us': 'English',
|
|
33
|
+
'zh-hans': '简体中文',
|
|
34
|
+
none: '(None)',
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const testCases = [
|
|
40
|
+
['en-us', false],
|
|
41
|
+
['zh-hans', true],
|
|
42
|
+
['none', false],
|
|
43
|
+
[null, false],
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
it.each(testCases)('should return cn forum link if the language is zh-hans', (language:String, value) => {
|
|
47
|
+
store.commit('setSelected', language);
|
|
48
|
+
|
|
49
|
+
const links = { defaults: [], custom: [] };
|
|
50
|
+
const hasSupport = true;
|
|
51
|
+
const isSupportPage = true;
|
|
52
|
+
|
|
53
|
+
const localThis = {
|
|
54
|
+
$store: store,
|
|
55
|
+
t: store.getters['i18n/t'],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const result = ensureSupportLink(links, hasSupport, isSupportPage, localThis.t, store);
|
|
59
|
+
|
|
60
|
+
expect(!!result.defaults.find((link) => link.key === 'cnforums')).toBe(value);
|
|
61
|
+
});
|
|
62
|
+
});
|
package/config/home-links.js
CHANGED
|
@@ -38,6 +38,12 @@ const SUPPORT_LINK = {
|
|
|
38
38
|
readonly: true
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
const CN_FORUMS_LINK = {
|
|
42
|
+
key: 'cnforums',
|
|
43
|
+
value: 'https://forums.rancher.cn/',
|
|
44
|
+
enabled: true,
|
|
45
|
+
};
|
|
46
|
+
|
|
41
47
|
// We add a version attribute to the setting so we know what has been migrated and which version of the setting we have
|
|
42
48
|
export const CUSTOM_LINKS_VERSION = 'v1';
|
|
43
49
|
|
|
@@ -72,7 +78,7 @@ export async function fetchLinks(store, hasSupport, isSupportPage, t) {
|
|
|
72
78
|
uiLinks.defaults = defaults;
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
return ensureSupportLink(uiLinks, hasSupport, isSupportPage, t);
|
|
81
|
+
return ensureSupportLink(uiLinks, hasSupport, isSupportPage, t, store);
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
// No new setting, so return the required structure
|
|
@@ -117,11 +123,11 @@ export async function fetchLinks(store, hasSupport, isSupportPage, t) {
|
|
|
117
123
|
console.warn('Could not parse legacy link settings', e); // eslint-disable-line no-console
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
return ensureSupportLink(links, hasSupport, isSupportPage, t);
|
|
126
|
+
return ensureSupportLink(links, hasSupport, isSupportPage, t, store);
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
// Ensure the support link is added if needed
|
|
124
|
-
function ensureSupportLink(links, hasSupport, isSupportPage, t) {
|
|
130
|
+
export function ensureSupportLink(links, hasSupport, isSupportPage, t, store) {
|
|
125
131
|
if (!hasSupport && !isSupportPage) {
|
|
126
132
|
const supportLink = links.defaults?.find((link) => link.key === 'commercialSupport');
|
|
127
133
|
|
|
@@ -130,6 +136,12 @@ function ensureSupportLink(links, hasSupport, isSupportPage, t) {
|
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
|
|
139
|
+
const selectedLocaleLabel = store.getters['i18n/selectedLocaleLabel'];
|
|
140
|
+
|
|
141
|
+
if (selectedLocaleLabel === t('locale.zh-hans')) {
|
|
142
|
+
links.defaults.push(CN_FORUMS_LINK);
|
|
143
|
+
}
|
|
144
|
+
|
|
133
145
|
// Localise the default links
|
|
134
146
|
links.defaults = links.defaults.map((link) => {
|
|
135
147
|
return {
|
|
@@ -54,7 +54,11 @@ export const CAPI = {
|
|
|
54
54
|
DELETE_MACHINE: 'cluster.x-k8s.io/delete-machine',
|
|
55
55
|
PROVIDER: 'provider.cattle.io',
|
|
56
56
|
SECRET_AUTH: 'v2prov-secret-authorized-for-cluster',
|
|
57
|
-
SECRET_WILL_DELETE: 'v2prov-authorized-secret-deletes-on-cluster-removal'
|
|
57
|
+
SECRET_WILL_DELETE: 'v2prov-authorized-secret-deletes-on-cluster-removal',
|
|
58
|
+
/**
|
|
59
|
+
* Annotation for overriding the cluster provider,
|
|
60
|
+
*/
|
|
61
|
+
UI_CUSTOM_PROVIDER: 'ui.rancher/provider'
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
export const CATALOG = {
|
|
@@ -105,7 +109,8 @@ export const FLEET = {
|
|
|
105
109
|
CLUSTER_DISPLAY_NAME: 'management.cattle.io/cluster-display-name',
|
|
106
110
|
CLUSTER_NAME: 'management.cattle.io/cluster-name',
|
|
107
111
|
BUNDLE_ID: 'fleet.cattle.io/bundle-id',
|
|
108
|
-
MANAGED: 'fleet.cattle.io/managed'
|
|
112
|
+
MANAGED: 'fleet.cattle.io/managed',
|
|
113
|
+
CLUSTER: 'fleet.cattle.io/cluster'
|
|
109
114
|
};
|
|
110
115
|
|
|
111
116
|
export const RBAC = { PRODUCT: 'management.cattle.io/ui-product' };
|