@rancher/shell 3.0.1-rc.3 → 3.0.1-rc.4
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/translations/en-us.yaml +13 -11
- package/assets/translations/zh-hans.yaml +0 -6
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +66 -11
- package/components/ResourceYaml.vue +0 -53
- package/components/form/UnitInput.vue +1 -1
- package/components/form/__tests__/UnitInput.test.ts +1 -1
- package/components/nav/TopLevelMenu.vue +1 -1
- package/components/nav/WindowManager/ContainerShell.vue +13 -4
- package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +20 -18
- package/composables/useLabeledFormElement.ts +6 -2
- package/config/router/navigation-guards/index.js +1 -2
- package/config/settings.ts +2 -7
- package/edit/workload/index.vue +1 -1
- package/edit/workload/storage/csi/index.vue +29 -1
- package/edit/workload/storage/index.vue +1 -0
- package/initialize/App.vue +3 -10
- package/initialize/install-plugins.js +1 -2
- package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +6 -2
- package/models/nodedriver.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +1 -1
- package/package.json +2 -3
- package/pages/auth/setup.vue +7 -28
- package/rancher-components/Banner/Banner.vue +1 -0
- package/rancher-components/Form/Checkbox/Checkbox.vue +2 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
- package/rancher-components/Form/Radio/RadioButton.vue +2 -0
- package/rancher-components/Form/Radio/RadioGroup.vue +2 -0
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +2 -0
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -0
- package/rancher-components/StringList/StringList.test.ts +15 -15
- package/rancher-components/StringList/StringList.vue +3 -0
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
- package/scripts/test-plugins-build.sh +3 -2
- package/utils/uiplugins.ts +293 -0
- package/components/templates/error.vue +0 -131
- package/config/router/navigation-guards/history.js +0 -13
- package/plugins/back-button.js +0 -3
|
@@ -1480,16 +1480,16 @@ cluster:
|
|
|
1480
1480
|
defaultLabel: Use default data directory configuration
|
|
1481
1481
|
commonLabel: Use a common base directory for data directory configuration (sub-directories will be used for the system-agent, provisioning and distro paths)
|
|
1482
1482
|
customLabel: Use custom data directories
|
|
1483
|
-
common:
|
|
1483
|
+
common:
|
|
1484
1484
|
label: Data directory base path
|
|
1485
1485
|
tooltip: Data directory base path. We will append the sub-directories appropriate for each directory (/agent, /provisioning and either /rke2 or /k3s)
|
|
1486
|
-
systemAgent:
|
|
1486
|
+
systemAgent:
|
|
1487
1487
|
label: System-agent directory path
|
|
1488
1488
|
tooltip: Data directory for the system-agent connection info and plans
|
|
1489
|
-
provisioning:
|
|
1489
|
+
provisioning:
|
|
1490
1490
|
label: Provisioning directory path
|
|
1491
1491
|
tooltip: Data directory for provisioning related files
|
|
1492
|
-
k8sDistro:
|
|
1492
|
+
k8sDistro:
|
|
1493
1493
|
label: K8s Distro directory path
|
|
1494
1494
|
tooltip: Data directory for the k8s distro
|
|
1495
1495
|
machineConfig:
|
|
@@ -5300,7 +5300,6 @@ setup:
|
|
|
5300
5300
|
skip: Skip
|
|
5301
5301
|
tip: What URL should be used for this {vendor} installation? All the nodes in your clusters will need to be able to reach this.
|
|
5302
5302
|
setPassword: The first order of business is to set a strong password for the default <code>{username}</code> user. We suggest using this random one generated just for you, but enter your own if you like.
|
|
5303
|
-
telemetry: Allow collection of <a href="{docsBase}/faq/telemetry" target="_blank" rel="noopener noreferrer nofollow">anonymous statistics</a> to help us improve {name}
|
|
5304
5303
|
useManual: Set a specific password to use
|
|
5305
5304
|
useRandom: Use a randomly generated password
|
|
5306
5305
|
welcome: Welcome to {vendor}!
|
|
@@ -6139,6 +6138,14 @@ wm:
|
|
|
6139
6138
|
clear: Clear
|
|
6140
6139
|
containerName: "Container: {label}"
|
|
6141
6140
|
failed: "Unable to open a shell to the container (none of the shell commmands succeeded)\n\r"
|
|
6141
|
+
logLevel:
|
|
6142
|
+
info: INFO
|
|
6143
|
+
error: ERROR
|
|
6144
|
+
warning: WARN
|
|
6145
|
+
debug: DEBUG
|
|
6146
|
+
logMessage:
|
|
6147
|
+
containerError: "{ logLevel }: Container missing shell executable (/bin/sh)"
|
|
6148
|
+
|
|
6142
6149
|
kubectlShell:
|
|
6143
6150
|
title: "Kubectl: {name}"
|
|
6144
6151
|
|
|
@@ -6544,7 +6551,7 @@ workload:
|
|
|
6544
6551
|
managed: Managed
|
|
6545
6552
|
shared: Shared
|
|
6546
6553
|
drivers:
|
|
6547
|
-
driver
|
|
6554
|
+
driver-longhorn-io: Longhorn
|
|
6548
6555
|
fsType: Filesystem Type
|
|
6549
6556
|
shareName: Share Name
|
|
6550
6557
|
secretName: Secret Name
|
|
@@ -7415,7 +7422,6 @@ advancedSettings:
|
|
|
7415
7422
|
'ui-dashboard-index': 'HTML index location for the {appName} UI.'
|
|
7416
7423
|
'ui-offline-preferred': 'Controls whether UI assets are served locally by the server container or from the remote URL defined in the ui-index and ui-dashboard-index settings. The `Dynamic` option will use local assets in production builds of {appName}.'
|
|
7417
7424
|
'ui-pl': 'Private-Label company name.'
|
|
7418
|
-
'telemetry-opt': 'Telemetry reporting opt-in.'
|
|
7419
7425
|
'auth-user-info-max-age-seconds': 'The maximum age of a users auth tokens before an auth provider group membership sync will be performed.'
|
|
7420
7426
|
'auth-user-info-resync-cron': 'Default cron schedule for resyncing auth provider group memberships.'
|
|
7421
7427
|
'cluster-template-enforcement': 'Non-admins will be restricted to launching clusters via preapproved RKE Templates only.'
|
|
@@ -7440,10 +7446,6 @@ advancedSettings:
|
|
|
7440
7446
|
'ui-default-landing':
|
|
7441
7447
|
ember: Cluster Manager
|
|
7442
7448
|
vue: Cluster Explorer
|
|
7443
|
-
'telemetry-opt':
|
|
7444
|
-
prompt: Prompt
|
|
7445
|
-
in: Opt-in to Telemetry
|
|
7446
|
-
out: Opt-out of Telemetry
|
|
7447
7449
|
'ui-offline-preferred':
|
|
7448
7450
|
dynamic: Dynamic
|
|
7449
7451
|
true: Local
|
|
@@ -4964,7 +4964,6 @@ setup:
|
|
|
4964
4964
|
skip: 跳过
|
|
4965
4965
|
tip: 此 {vendor} 安装应使用什么 URL?集群中的所有节点都需要能访问该 URL。
|
|
4966
4966
|
setPassword: 请为默认用户 <code>{username}</code>设置强密码。建议使用生成的随机密码。你也可以自行设置。
|
|
4967
|
-
telemetry: 允许收集<a href="{docsBase}/faq/telemetry" target="_blank" rel="noopener noreferrer nofollow">匿名统计数据</a>,以帮我们改进 {name}。
|
|
4968
4967
|
useManual: 设置密码
|
|
4969
4968
|
useRandom: 使用随机生成的密码
|
|
4970
4969
|
welcome: 欢迎使用 {vendor}!
|
|
@@ -7011,7 +7010,6 @@ advancedSettings:
|
|
|
7011
7010
|
'ui-dashboard-index': '{appName} UI 的 HTML 索引位置。'
|
|
7012
7011
|
'ui-offline-preferred': '控制 UI 资产是由服务器容器在本地提供,还是从 ui-index 和 ui-dashboard-index 设置中定义的远程 URL 提供。`动态` 选项将在 {appName} 的生产版本中使用本地资产。'
|
|
7013
7012
|
'ui-pl': '自有品牌公司名称。'
|
|
7014
|
-
'telemetry-opt': '遥测报告加入。'
|
|
7015
7013
|
'auth-user-info-max-age-seconds': '在同步验证提供程序组成员之前,用户验证 Token 的最长存活时间。'
|
|
7016
7014
|
'auth-user-info-resync-cron': '重新同步验证提供程序组成员的默认 cron 调度。'
|
|
7017
7015
|
'cluster-template-enforcement': '非管理员只能通过预先批准的 RKE 模板启动集群。'
|
|
@@ -7032,10 +7030,6 @@ advancedSettings:
|
|
|
7032
7030
|
'ui-default-landing':
|
|
7033
7031
|
ember: Cluster Manager
|
|
7034
7032
|
vue: 集群浏览器
|
|
7035
|
-
'telemetry-opt':
|
|
7036
|
-
prompt: 提示
|
|
7037
|
-
in: 加入遥测
|
|
7038
|
-
out: 退出遥测
|
|
7039
7033
|
'ui-offline-preferred':
|
|
7040
7034
|
dynamic: 动态
|
|
7041
7035
|
true: 本地
|
|
@@ -447,7 +447,7 @@ export default {
|
|
|
447
447
|
{{ parent.displayName }}:
|
|
448
448
|
</router-link>
|
|
449
449
|
<span v-else>{{ parent.displayName }}:</span>
|
|
450
|
-
<span v-if="value
|
|
450
|
+
<span v-if="value?.detailPageHeaderActionOverride && value?.detailPageHeaderActionOverride(realMode)">{{ value?.detailPageHeaderActionOverride(realMode) }}</span>
|
|
451
451
|
<t
|
|
452
452
|
v-else
|
|
453
453
|
class="masthead-resource-title"
|
|
@@ -14,6 +14,8 @@ import { clone, diff } from '@shell/utils/object';
|
|
|
14
14
|
import IconMessage from '@shell/components/IconMessage';
|
|
15
15
|
import ForceDirectedTreeChart from '@shell/components/fleet/ForceDirectedTreeChart';
|
|
16
16
|
import { checkSchemasForFindAllHash } from '@shell/utils/auth';
|
|
17
|
+
import { stringify } from '@shell/utils/error';
|
|
18
|
+
import { Banner } from '@components/Banner';
|
|
17
19
|
|
|
18
20
|
function modeFor(route) {
|
|
19
21
|
if ( route.query?.mode === _IMPORT ) {
|
|
@@ -48,6 +50,7 @@ export default {
|
|
|
48
50
|
ResourceYaml,
|
|
49
51
|
Masthead,
|
|
50
52
|
IconMessage,
|
|
53
|
+
Banner
|
|
51
54
|
},
|
|
52
55
|
|
|
53
56
|
mixins: [CreateEditView],
|
|
@@ -75,7 +78,11 @@ export default {
|
|
|
75
78
|
componentTestid: {
|
|
76
79
|
type: String,
|
|
77
80
|
default: 'resource-details'
|
|
78
|
-
}
|
|
81
|
+
},
|
|
82
|
+
errorsMap: {
|
|
83
|
+
type: Object,
|
|
84
|
+
default: null
|
|
85
|
+
},
|
|
79
86
|
},
|
|
80
87
|
|
|
81
88
|
async fetch() {
|
|
@@ -202,16 +209,26 @@ export default {
|
|
|
202
209
|
notFound = fqid;
|
|
203
210
|
}
|
|
204
211
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
try {
|
|
213
|
+
if (realMode === _VIEW) {
|
|
214
|
+
model = liveModel;
|
|
215
|
+
} else {
|
|
216
|
+
model = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
|
|
217
|
+
}
|
|
218
|
+
initialModel = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
|
|
212
219
|
|
|
220
|
+
if ( as === _YAML ) {
|
|
221
|
+
yaml = await getYaml(this.$store, liveModel);
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
this.errors.push(e);
|
|
225
|
+
}
|
|
213
226
|
if ( as === _YAML ) {
|
|
214
|
-
|
|
227
|
+
try {
|
|
228
|
+
yaml = await getYaml(this.$store, liveModel);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
this.errors.push(e);
|
|
231
|
+
}
|
|
215
232
|
}
|
|
216
233
|
|
|
217
234
|
if ( as === _GRAPH ) {
|
|
@@ -225,7 +242,11 @@ export default {
|
|
|
225
242
|
}
|
|
226
243
|
|
|
227
244
|
// Ensure common properties exists
|
|
228
|
-
|
|
245
|
+
try {
|
|
246
|
+
model = await store.dispatch(`${ inStore }/cleanForDetail`, model);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
this.errors.push(e);
|
|
249
|
+
}
|
|
229
250
|
|
|
230
251
|
const out = {
|
|
231
252
|
hasGraph,
|
|
@@ -272,6 +293,7 @@ export default {
|
|
|
272
293
|
notFound: null,
|
|
273
294
|
canViewChart: true,
|
|
274
295
|
canViewYaml: null,
|
|
296
|
+
errors: []
|
|
275
297
|
};
|
|
276
298
|
},
|
|
277
299
|
|
|
@@ -311,6 +333,18 @@ export default {
|
|
|
311
333
|
|
|
312
334
|
return null;
|
|
313
335
|
},
|
|
336
|
+
hasErrors() {
|
|
337
|
+
return this.errors?.length && Array.isArray(this.errors);
|
|
338
|
+
},
|
|
339
|
+
mappedErrors() {
|
|
340
|
+
return !this.errors ? {} : this.errorsMap || this.errors.reduce((acc, error) => ({
|
|
341
|
+
...acc,
|
|
342
|
+
[error]: {
|
|
343
|
+
message: error?.data?.message || error,
|
|
344
|
+
icon: null
|
|
345
|
+
}
|
|
346
|
+
}), {});
|
|
347
|
+
},
|
|
314
348
|
},
|
|
315
349
|
|
|
316
350
|
watch: {
|
|
@@ -360,6 +394,7 @@ export default {
|
|
|
360
394
|
},
|
|
361
395
|
|
|
362
396
|
methods: {
|
|
397
|
+
stringify,
|
|
363
398
|
setSubtype(subtype) {
|
|
364
399
|
this.resourceSubtype = subtype;
|
|
365
400
|
},
|
|
@@ -371,6 +406,9 @@ export default {
|
|
|
371
406
|
m[act]();
|
|
372
407
|
}
|
|
373
408
|
},
|
|
409
|
+
closeError(index) {
|
|
410
|
+
this.errors = this.errors.filter((_, i) => i !== index);
|
|
411
|
+
},
|
|
374
412
|
}
|
|
375
413
|
};
|
|
376
414
|
</script>
|
|
@@ -398,6 +436,22 @@ export default {
|
|
|
398
436
|
:value="liveModel"
|
|
399
437
|
/>
|
|
400
438
|
</Masthead>
|
|
439
|
+
<div
|
|
440
|
+
v-if="hasErrors"
|
|
441
|
+
id="cru-errors"
|
|
442
|
+
class="cru__errors"
|
|
443
|
+
>
|
|
444
|
+
<Banner
|
|
445
|
+
v-for="(err, i) in errors"
|
|
446
|
+
:key="i"
|
|
447
|
+
color="error"
|
|
448
|
+
:data-testid="`error-banner${i}`"
|
|
449
|
+
:label="stringify(mappedErrors[err].message)"
|
|
450
|
+
:icon="mappedErrors[err].icon"
|
|
451
|
+
:closable="true"
|
|
452
|
+
@close="closeError(i)"
|
|
453
|
+
/>
|
|
454
|
+
</div>
|
|
401
455
|
|
|
402
456
|
<ForceDirectedTreeChart
|
|
403
457
|
v-if="isGraph && canViewChart"
|
|
@@ -413,8 +467,9 @@ export default {
|
|
|
413
467
|
:yaml="yaml"
|
|
414
468
|
:offer-preview="offerPreview"
|
|
415
469
|
:done-route="doneRoute"
|
|
416
|
-
:done-override="value.doneOverride"
|
|
470
|
+
:done-override="value ? value.doneOverride : null"
|
|
417
471
|
@update:value="$emit('input', $event)"
|
|
472
|
+
@error="e=>errors.push(e)"
|
|
418
473
|
/>
|
|
419
474
|
|
|
420
475
|
<component
|
|
@@ -205,58 +205,6 @@ export default {
|
|
|
205
205
|
cm.getMode().fold = saved;
|
|
206
206
|
},
|
|
207
207
|
|
|
208
|
-
onChanges(cm, changes) {
|
|
209
|
-
if ( changes.length !== 1 ) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const change = changes[0];
|
|
214
|
-
|
|
215
|
-
if ( change.from.line !== change.to.line ) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let line = change.from.line;
|
|
220
|
-
let str = cm.getLine(line);
|
|
221
|
-
let maxIndent = indentChars(str);
|
|
222
|
-
|
|
223
|
-
if ( maxIndent === null ) {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
cm.replaceRange('', { line, ch: 0 }, { line, ch: 1 }, '+input');
|
|
228
|
-
|
|
229
|
-
while ( line > 0 ) {
|
|
230
|
-
line--;
|
|
231
|
-
str = cm.getLine(line);
|
|
232
|
-
const indent = indentChars(str);
|
|
233
|
-
|
|
234
|
-
if ( indent === null ) {
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if ( indent < maxIndent ) {
|
|
239
|
-
cm.replaceRange('', { line, ch: 0 }, { line, ch: 1 }, '+input');
|
|
240
|
-
|
|
241
|
-
if ( indent === 0 ) {
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
maxIndent = indent;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function indentChars(str) {
|
|
250
|
-
const match = str.match(/^#(\s+)/);
|
|
251
|
-
|
|
252
|
-
if ( match ) {
|
|
253
|
-
return match[1].length;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
|
|
260
208
|
updateValue(value) {
|
|
261
209
|
this.$refs.yamleditor.updateValue(value);
|
|
262
210
|
},
|
|
@@ -354,7 +302,6 @@ export default {
|
|
|
354
302
|
class="yaml-editor flex-content"
|
|
355
303
|
:editor-mode="editorMode"
|
|
356
304
|
@onReady="onReady"
|
|
357
|
-
@onChanges="onChanges"
|
|
358
305
|
/>
|
|
359
306
|
<slot
|
|
360
307
|
name="yamlFooter"
|
|
@@ -184,7 +184,7 @@ describe('component: UnitInput', () => {
|
|
|
184
184
|
input.trigger('blur');
|
|
185
185
|
|
|
186
186
|
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
187
|
-
expect(wrapper.emitted('update:value')[
|
|
187
|
+
expect(wrapper.emitted('update:value')[3][0]).toBe(value);
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
describe.each([
|
|
@@ -14,6 +14,7 @@ import Socket, {
|
|
|
14
14
|
EVENT_CONNECT_ERROR,
|
|
15
15
|
} from '@shell/utils/socket';
|
|
16
16
|
import Window from './Window';
|
|
17
|
+
import dayjs from 'dayjs';
|
|
17
18
|
|
|
18
19
|
const commands = {
|
|
19
20
|
linux: [
|
|
@@ -287,7 +288,7 @@ export default {
|
|
|
287
288
|
|
|
288
289
|
// If we had an error message, try connecting with the next command
|
|
289
290
|
if (this.errorMsg) {
|
|
290
|
-
this.terminal.
|
|
291
|
+
this.terminal.writeln(this.errorMsg);
|
|
291
292
|
if (this.backupShells.length && this.retries < 2) {
|
|
292
293
|
this.retries++;
|
|
293
294
|
// we're not really counting on this being a reactive change so there's no need to fire the whole action
|
|
@@ -299,7 +300,9 @@ export default {
|
|
|
299
300
|
this.connect();
|
|
300
301
|
} else {
|
|
301
302
|
// Output an message to let he user know none of the shell commands worked
|
|
302
|
-
|
|
303
|
+
const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
304
|
+
|
|
305
|
+
this.terminal.writeln(`[${ timestamp }] ${ this.t('wm.containerShell.logLevel.info') }: ${ this.t('wm.containerShell.failed') }`);
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
308
|
});
|
|
@@ -317,10 +320,16 @@ export default {
|
|
|
317
320
|
}
|
|
318
321
|
this.terminal.write(msg);
|
|
319
322
|
} else {
|
|
320
|
-
|
|
323
|
+
const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
324
|
+
let customError = `[${ timestamp }] ${ this.t('wm.containerShell.logLevel.error') }: ${ this.container }: ${ msg }`;
|
|
325
|
+
|
|
326
|
+
if (msg.includes('stat /bin/sh: no such file or directory')) {
|
|
327
|
+
customError = `[${ timestamp }] ${ this.t('wm.containerShell.logMessage.containerError', { logLevel: this.t('wm.containerShell.logLevel.error') }) }: ${ msg }`;
|
|
328
|
+
}
|
|
329
|
+
console.error(customError); // eslint-disable-line no-console
|
|
321
330
|
|
|
322
331
|
if (`${ type }` === '3') {
|
|
323
|
-
this.errorMsg =
|
|
332
|
+
this.errorMsg = customError;
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
});
|
|
@@ -29,6 +29,7 @@ describe('component: ContainerShell', () => {
|
|
|
29
29
|
return { rows: 1 };
|
|
30
30
|
});
|
|
31
31
|
const write = jest.fn();
|
|
32
|
+
const writeln = jest.fn();
|
|
32
33
|
const reset = jest.fn();
|
|
33
34
|
|
|
34
35
|
jest.mock(/* webpackChunkName: "xterm" */ 'xterm', () => {
|
|
@@ -39,6 +40,7 @@ describe('component: ContainerShell', () => {
|
|
|
39
40
|
open = open;
|
|
40
41
|
focus = focus;
|
|
41
42
|
write = write;
|
|
43
|
+
writeln = writeln;
|
|
42
44
|
reset = reset
|
|
43
45
|
}
|
|
44
46
|
};
|
|
@@ -277,10 +279,10 @@ describe('component: ContainerShell', () => {
|
|
|
277
279
|
eventConnected();
|
|
278
280
|
eventMessage({ detail: { data: `3${ errorMessage }` } });
|
|
279
281
|
|
|
280
|
-
expect(consoleError.mock.calls[0][0]).
|
|
282
|
+
expect(consoleError.mock.calls[0][0]).toContain(errorMessage);
|
|
281
283
|
expect(wrapper.vm.isOpen).toBe(true);
|
|
282
284
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
283
|
-
expect(wrapper.vm.errorMsg).
|
|
285
|
+
expect(wrapper.vm.errorMsg).toContain(errorMessage);
|
|
284
286
|
expect(wrapper.vm.os).toBe('linux');
|
|
285
287
|
});
|
|
286
288
|
|
|
@@ -325,10 +327,10 @@ describe('component: ContainerShell', () => {
|
|
|
325
327
|
|
|
326
328
|
eventDisconnected();
|
|
327
329
|
|
|
328
|
-
expect(consoleError.mock.calls[0][0]).
|
|
330
|
+
expect(consoleError.mock.calls[0][0]).toContain(errorMessage);
|
|
329
331
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
330
332
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
331
|
-
expect(wrapper.vm.errorMsg).
|
|
333
|
+
expect(wrapper.vm.errorMsg).toContain('eventMessageError');
|
|
332
334
|
// the backup shell that was leftover was windows so it became the new os in dataprops
|
|
333
335
|
expect(wrapper.vm.os).toBeUndefined();
|
|
334
336
|
// but we still didn't write it to the pod itself since we don't know if it worked
|
|
@@ -370,11 +372,11 @@ describe('component: ContainerShell', () => {
|
|
|
370
372
|
eventMessage({ detail: { data: `3${ windowsErrorMessage }` } });
|
|
371
373
|
eventDisconnected();
|
|
372
374
|
|
|
373
|
-
expect(consoleError.mock.calls[0][0]).
|
|
374
|
-
expect(consoleError.mock.calls[1][0]).
|
|
375
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
376
|
+
expect(consoleError.mock.calls[1][0]).toContain(windowsErrorMessage);
|
|
375
377
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
376
378
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
377
|
-
expect(wrapper.vm.errorMsg).
|
|
379
|
+
expect(wrapper.vm.errorMsg).toContain(windowsErrorMessage);
|
|
378
380
|
expect(wrapper.vm.os).toBeUndefined();
|
|
379
381
|
// we never found a shell that worked so we're going to leave the pod os as undefined
|
|
380
382
|
expect(defaultContainerShellParams.propsData.pod.os).toBeUndefined();
|
|
@@ -426,11 +428,11 @@ describe('component: ContainerShell', () => {
|
|
|
426
428
|
eventMessage({ detail: { data: `3${ windowsErrorMessage }` } });
|
|
427
429
|
eventDisconnected();
|
|
428
430
|
|
|
429
|
-
expect(consoleError.mock.calls[0][0]).
|
|
430
|
-
expect(consoleError.mock.calls[1][0]).
|
|
431
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
432
|
+
expect(consoleError.mock.calls[1][0]).toContain(windowsErrorMessage);
|
|
431
433
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
432
434
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
433
|
-
expect(wrapper.vm.errorMsg).
|
|
435
|
+
expect(wrapper.vm.errorMsg).toContain(windowsErrorMessage);
|
|
434
436
|
expect(wrapper.vm.os).toBeUndefined();
|
|
435
437
|
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
436
438
|
expect(connect.mock.calls).toHaveLength(3);
|
|
@@ -472,13 +474,13 @@ describe('component: ContainerShell', () => {
|
|
|
472
474
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
473
475
|
expect(wrapper.vm.os).toBeUndefined();
|
|
474
476
|
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
475
|
-
expect(wrapper.vm.errorMsg).
|
|
477
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
476
478
|
|
|
477
479
|
eventConnecting();
|
|
478
480
|
eventConnected();
|
|
479
481
|
eventMessage({ detail: { data: `1${ windowsShellMessage }` } });
|
|
480
482
|
|
|
481
|
-
expect(consoleError.mock.calls[0][0]).
|
|
483
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
482
484
|
expect(consoleError.mock.calls[1]).toBeUndefined();
|
|
483
485
|
expect(wrapper.vm.isOpen).toBe(true);
|
|
484
486
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
@@ -532,7 +534,7 @@ describe('component: ContainerShell', () => {
|
|
|
532
534
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
533
535
|
expect(wrapper.vm.os).toBe('linux');
|
|
534
536
|
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
535
|
-
expect(wrapper.vm.errorMsg).
|
|
537
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
536
538
|
|
|
537
539
|
eventConnecting();
|
|
538
540
|
eventConnected();
|
|
@@ -542,7 +544,7 @@ describe('component: ContainerShell', () => {
|
|
|
542
544
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
543
545
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
544
546
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
545
|
-
expect(wrapper.vm.errorMsg).
|
|
547
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
546
548
|
expect(wrapper.vm.os).toBe('linux');
|
|
547
549
|
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
548
550
|
expect(connect.mock.calls).toHaveLength(3);
|
|
@@ -552,13 +554,13 @@ describe('component: ContainerShell', () => {
|
|
|
552
554
|
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
553
555
|
eventDisconnected();
|
|
554
556
|
|
|
555
|
-
expect(consoleError.mock.calls[0][0]).
|
|
556
|
-
expect(consoleError.mock.calls[1][0]).
|
|
557
|
-
expect(consoleError.mock.calls[2][0]).
|
|
557
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
558
|
+
expect(consoleError.mock.calls[1][0]).toContain(linuxErrorMessage);
|
|
559
|
+
expect(consoleError.mock.calls[2][0]).toContain(linuxErrorMessage);
|
|
558
560
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
559
561
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
560
562
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
561
|
-
expect(wrapper.vm.errorMsg).
|
|
563
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
562
564
|
expect(wrapper.vm.os).toBe('linux');
|
|
563
565
|
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
564
566
|
// 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
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ref, computed, ComputedRef, Ref, defineEmits
|
|
3
|
+
} from 'vue';
|
|
2
4
|
import { _VIEW, _EDIT } from '@shell/config/query-params';
|
|
3
5
|
|
|
4
6
|
interface LabeledFormElementProps {
|
|
@@ -70,7 +72,9 @@ export const labeledFormElementProps = {
|
|
|
70
72
|
}
|
|
71
73
|
};
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
const labeledFormElementEmits = defineEmits(['update:validation']);
|
|
76
|
+
|
|
77
|
+
export const useLabeledFormElement = (props: LabeledFormElementProps, emit: typeof labeledFormElementEmits): UseLabeledFormElement => {
|
|
74
78
|
const raised = ref(props.mode === _VIEW || !!`${ props.value }`);
|
|
75
79
|
const focused = ref(false);
|
|
76
80
|
const blurred = ref<number | null>(null);
|
|
@@ -4,7 +4,6 @@ import { install as installAuthentication } from '@shell/config/router/navigatio
|
|
|
4
4
|
import { install as installRuntimeExtensionRoute } from '@shell/config/router/navigation-guards/runtime-extension-route';
|
|
5
5
|
import { install as installI18N } from '@shell/config/router/navigation-guards/i18n';
|
|
6
6
|
import { install as installProducts } from '@shell/config/router/navigation-guards/products';
|
|
7
|
-
import { install as installHistory } from '@shell/config/router/navigation-guards/history';
|
|
8
7
|
import { install as installClusters } from '@shell/config/router/navigation-guards/clusters';
|
|
9
8
|
import { install as installHandleInstallRedirect } from '@shell/config/router/navigation-guards/install-redirect';
|
|
10
9
|
import { install as installPageTitle } from '@shell/config/router/navigation-guards/page-title';
|
|
@@ -17,7 +16,7 @@ export function installNavigationGuards(router, context) {
|
|
|
17
16
|
// NOTE: the order of the installation matters.
|
|
18
17
|
// Be intentional when adding, removing or modifying the guards that are installed.
|
|
19
18
|
|
|
20
|
-
const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication, installProducts,
|
|
19
|
+
const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication, installProducts, installClusters, installRuntimeExtensionRoute, installI18N, installHandleInstallRedirect, installPageTitle, installServerUpgradeGrowl];
|
|
21
20
|
|
|
22
21
|
navigationGuardInstallers.forEach((installer) => installer(router, context));
|
|
23
22
|
}
|
package/config/settings.ts
CHANGED
|
@@ -51,7 +51,6 @@ export const SETTING = {
|
|
|
51
51
|
INGRESS_IP_DOMAIN: 'ingress-ip-domain',
|
|
52
52
|
SERVER_URL: 'server-url',
|
|
53
53
|
RKE_METADATA_CONFIG: 'rke-metadata-config',
|
|
54
|
-
TELEMETRY: 'telemetry-opt',
|
|
55
54
|
EULA_AGREED: 'eula-agreed',
|
|
56
55
|
AUTH_USER_INFO_MAX_AGE_SECONDS: 'auth-user-info-max-age-seconds',
|
|
57
56
|
AUTH_USER_SESSION_TTL_MINUTES: 'auth-user-session-ttl-minutes',
|
|
@@ -156,12 +155,8 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
|
|
|
156
155
|
},
|
|
157
156
|
[SETTING.BRAND]: { canReset: true },
|
|
158
157
|
[SETTING.CLUSTER_TEMPLATE_ENFORCEMENT]: { kind: 'boolean' },
|
|
159
|
-
[SETTING.
|
|
160
|
-
|
|
161
|
-
options: ['prompt', 'in', 'out']
|
|
162
|
-
},
|
|
163
|
-
[SETTING.HIDE_LOCAL_CLUSTER]: { kind: 'boolean' },
|
|
164
|
-
[SETTING.AGENT_TLS_MODE]: {
|
|
158
|
+
[SETTING.HIDE_LOCAL_CLUSTER]: { kind: 'boolean' },
|
|
159
|
+
[SETTING.AGENT_TLS_MODE]: {
|
|
165
160
|
kind: 'enum',
|
|
166
161
|
options: ['strict', 'system-store'],
|
|
167
162
|
warning: 'agent-tls-mode'
|
package/edit/workload/index.vue
CHANGED
|
@@ -44,6 +44,33 @@ export default {
|
|
|
44
44
|
|
|
45
45
|
...mapGetters({ t: 'i18n/t' })
|
|
46
46
|
},
|
|
47
|
+
|
|
48
|
+
methods: {
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves the label for a given option
|
|
51
|
+
* @param option The option for which to retrieve the label. option can be
|
|
52
|
+
* either a string or an object. If it is an object, is should have a `label`
|
|
53
|
+
* property associated with it.
|
|
54
|
+
*/
|
|
55
|
+
getOptionLabel(option) {
|
|
56
|
+
if (typeof option === 'string') {
|
|
57
|
+
return this.getOptionLabelString(option);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { label } = option;
|
|
61
|
+
|
|
62
|
+
return this.getOptionLabelString(label);
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Translates a given key into a localized string.
|
|
66
|
+
* @param key The key to be translated.
|
|
67
|
+
*/
|
|
68
|
+
getOptionLabelString(key) {
|
|
69
|
+
// Periods are replaced with `-` to prevent conflict with the default key
|
|
70
|
+
// separator.
|
|
71
|
+
return this.t(`workload.storage.csi.drivers.'${ key.replaceAll('.', '-') }'`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
47
74
|
};
|
|
48
75
|
</script>
|
|
49
76
|
|
|
@@ -71,10 +98,11 @@ export default {
|
|
|
71
98
|
<div class="col span-6">
|
|
72
99
|
<LabeledSelect
|
|
73
100
|
v-model:value="value.csi.driver"
|
|
101
|
+
data-testid="workload-storage-driver"
|
|
74
102
|
:mode="mode"
|
|
75
103
|
:label="t('workload.storage.driver')"
|
|
76
104
|
:options="driverOpts"
|
|
77
|
-
:get-option-label="
|
|
105
|
+
:get-option-label="getOptionLabel"
|
|
78
106
|
:required="true"
|
|
79
107
|
/>
|
|
80
108
|
</div>
|