@rancher/shell 3.0.1-rc.2 → 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.
Files changed (56) hide show
  1. package/assets/styles/app.scss +0 -1
  2. package/assets/translations/en-us.yaml +14 -12
  3. package/assets/translations/zh-hans.yaml +0 -9
  4. package/components/GlobalRoleBindings.vue +0 -7
  5. package/components/ResourceDetail/Masthead.vue +1 -1
  6. package/components/ResourceDetail/index.vue +66 -11
  7. package/components/ResourceYaml.vue +0 -53
  8. package/components/SortableTable/THead.vue +1 -1
  9. package/components/auth/RoleDetailEdit.vue +0 -16
  10. package/components/form/UnitInput.vue +1 -1
  11. package/components/form/__tests__/UnitInput.test.ts +1 -1
  12. package/components/nav/TopLevelMenu.vue +1 -1
  13. package/components/nav/WindowManager/ContainerShell.vue +13 -4
  14. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +20 -18
  15. package/composables/useLabeledFormElement.ts +6 -2
  16. package/config/router/navigation-guards/index.js +1 -2
  17. package/config/settings.ts +2 -7
  18. package/edit/catalog.cattle.io.clusterrepo.vue +0 -9
  19. package/edit/provisioning.cattle.io.cluster/rke2.vue +3 -4
  20. package/edit/workload/index.vue +1 -1
  21. package/edit/workload/storage/csi/index.vue +29 -1
  22. package/edit/workload/storage/index.vue +1 -0
  23. package/initialize/App.vue +3 -10
  24. package/initialize/install-plugins.js +1 -2
  25. package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +6 -2
  26. package/models/__tests__/management.cattle.io.cluster.test.ts +0 -42
  27. package/models/management.cattle.io.cluster.js +0 -24
  28. package/models/management.cattle.io.globalrole.js +1 -0
  29. package/models/nodedriver.js +2 -2
  30. package/models/provisioning.cattle.io.cluster.js +24 -2
  31. package/package.json +2 -3
  32. package/pages/auth/setup.vue +7 -28
  33. package/pages/c/_cluster/auth/roles/index.vue +1 -11
  34. package/pages/c/_cluster/explorer/__tests__/index.test.ts +71 -1
  35. package/pages/c/_cluster/explorer/index.vue +6 -2
  36. package/rancher-components/Banner/Banner.vue +1 -0
  37. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -0
  38. package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
  39. package/rancher-components/Form/Radio/RadioButton.vue +2 -0
  40. package/rancher-components/Form/Radio/RadioGroup.vue +2 -0
  41. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +3 -1
  42. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -0
  43. package/rancher-components/StringList/StringList.test.ts +15 -15
  44. package/rancher-components/StringList/StringList.vue +3 -0
  45. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
  46. package/scripts/test-plugins-build.sh +3 -2
  47. package/types/shell/index.d.ts +20 -0
  48. package/utils/__tests__/object.test.ts +30 -1
  49. package/utils/object.js +28 -0
  50. package/utils/uiplugins.ts +293 -0
  51. package/vue.config.js +2 -1
  52. package/components/templates/error.vue +0 -131
  53. package/config/router/navigation-guards/history.js +0 -13
  54. package/machine-config/__tests__/vmwarevsphere-pool-config-merge.test.ts +0 -30
  55. package/machine-config/vmwarevsphere-pool-config-merge.ts +0 -25
  56. package/plugins/back-button.js +0 -3
@@ -32,4 +32,3 @@
32
32
 
33
33
  @import "./vendor/vue-select";
34
34
  @import "./vendor/code-mirror";
35
- @import 'node_modules/xterm/css/xterm.css';
@@ -1065,7 +1065,7 @@ catalog:
1065
1065
  target:
1066
1066
  git: Git repository containing Helm chart or cluster template definitions
1067
1067
  http: http(s) URL to an index generated by Helm
1068
- oci: OCI Repository <span class="oci-experimental-badge text-bold ml-5">Experimental</span>
1068
+ oci: OCI Repository
1069
1069
  label: Target
1070
1070
  url:
1071
1071
  label: Index URL
@@ -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.longhorn.io: Longhorn
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
@@ -4556,9 +4556,6 @@ rbac:
4556
4556
  admin:
4557
4557
  label: 管理员
4558
4558
  description: 管理员可以完全控制整个安装以及所有集群中的所有资源。
4559
- restricted-admin:
4560
- label: 受限管理员
4561
- description: 受限管理员可以完全控制所有下游集群的所有资源,但不能访问本地集群。
4562
4559
  user:
4563
4560
  label: 普通用户
4564
4561
  description: 普通用户可以创建集群,并管理其授权访问的集群和项目。
@@ -4967,7 +4964,6 @@ setup:
4967
4964
  skip: 跳过
4968
4965
  tip: 此 {vendor} 安装应使用什么 URL?集群中的所有节点都需要能访问该 URL。
4969
4966
  setPassword: 请为默认用户 <code>{username}</code>设置强密码。建议使用生成的随机密码。你也可以自行设置。
4970
- telemetry: 允许收集<a href="{docsBase}/faq/telemetry" target="_blank" rel="noopener noreferrer nofollow">匿名统计数据</a>,以帮我们改进 {name}。
4971
4967
  useManual: 设置密码
4972
4968
  useRandom: 使用随机生成的密码
4973
4969
  welcome: 欢迎使用 {vendor}!
@@ -7014,7 +7010,6 @@ advancedSettings:
7014
7010
  'ui-dashboard-index': '{appName} UI 的 HTML 索引位置。'
7015
7011
  'ui-offline-preferred': '控制 UI 资产是由服务器容器在本地提供,还是从 ui-index 和 ui-dashboard-index 设置中定义的远程 URL 提供。`动态` 选项将在 {appName} 的生产版本中使用本地资产。'
7016
7012
  'ui-pl': '自有品牌公司名称。'
7017
- 'telemetry-opt': '遥测报告加入。'
7018
7013
  'auth-user-info-max-age-seconds': '在同步验证提供程序组成员之前,用户验证 Token 的最长存活时间。'
7019
7014
  'auth-user-info-resync-cron': '重新同步验证提供程序组成员的默认 cron 调度。'
7020
7015
  'cluster-template-enforcement': '非管理员只能通过预先批准的 RKE 模板启动集群。'
@@ -7035,10 +7030,6 @@ advancedSettings:
7035
7030
  'ui-default-landing':
7036
7031
  ember: Cluster Manager
7037
7032
  vue: 集群浏览器
7038
- 'telemetry-opt':
7039
- prompt: 提示
7040
- in: 加入遥测
7041
- out: 退出遥测
7042
7033
  'ui-offline-preferred':
7043
7034
  dynamic: 动态
7044
7035
  true: 本地
@@ -108,7 +108,6 @@ export default {
108
108
  // This not only identifies global roles but the order here is the order we want to display them in the UI
109
109
  globalPermissions: [
110
110
  'admin',
111
- 'restricted-admin',
112
111
  'user',
113
112
  'user-base',
114
113
  ],
@@ -121,7 +120,6 @@ export default {
121
120
  };
122
121
  },
123
122
  computed: {
124
- ...mapGetters(['releaseNotesUrl']),
125
123
  ...mapGetters({ t: 'i18n/t' }),
126
124
 
127
125
  isCreate() {
@@ -375,11 +373,6 @@ export default {
375
373
  </div>
376
374
  </template>
377
375
  </Checkbox>
378
- <p
379
- v-if="role.id === 'restricted-admin'"
380
- v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)"
381
- class="deprecation-notice"
382
- />
383
376
  </div>
384
377
  </div>
385
378
  </template>
@@ -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.detailPageHeaderActionOverride && value.detailPageHeaderActionOverride(realMode)">{{ value.detailPageHeaderActionOverride(realMode) }}</span>
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
- if (realMode === _VIEW) {
206
- model = liveModel;
207
- } else {
208
- model = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
209
- }
210
-
211
- initialModel = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
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
- yaml = await getYaml(this.$store, liveModel);
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
- model = await store.dispatch(`${ inStore }/cleanForDetail`, model);
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"
@@ -429,7 +429,7 @@ export default {
429
429
  color: var(--body-text);
430
430
 
431
431
  .table-header-container {
432
- display: flex;
432
+ display: inline-flex;
433
433
 
434
434
  .content {
435
435
  display: flex;
@@ -1,5 +1,4 @@
1
1
  <script>
2
- import { mapGetters } from 'vuex';
3
2
  import { MANAGEMENT, RBAC } from '@shell/config/types';
4
3
  import CruResource from '@shell/components/CruResource';
5
4
  import CreateEditView from '@shell/mixins/create-edit-view';
@@ -15,7 +14,6 @@ import { ucFirst } from '@shell/utils/string';
15
14
  import SortableTable from '@shell/components/SortableTable';
16
15
  import { _CLONE, _DETAIL } from '@shell/config/query-params';
17
16
  import { SCOPED_RESOURCES, SCOPED_RESOURCE_GROUPS } from '@shell/config/roles';
18
- import { Banner } from '@components/Banner';
19
17
  import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
20
18
 
21
19
  import { SUBTYPE_MAPPING, VERBS } from '@shell/models/management.cattle.io.roletemplate';
@@ -66,7 +64,6 @@ export default {
66
64
  SortableTable,
67
65
  Loading,
68
66
  Error,
69
- Banner,
70
67
  LabeledInput
71
68
  },
72
69
 
@@ -163,12 +160,6 @@ export default {
163
160
  },
164
161
 
165
162
  computed: {
166
- ...mapGetters(['releaseNotesUrl']),
167
-
168
- showRestrictedAdminDeprecationBanner() {
169
- return this.value.subtype === GLOBAL && this.value.id === 'restricted-admin';
170
- },
171
-
172
163
  label() {
173
164
  return this.t(`rbac.roletemplate.subtypes.${ this.value.subtype }.label`);
174
165
  },
@@ -559,13 +550,6 @@ export default {
559
550
  @finish="save"
560
551
  @cancel="cancel"
561
552
  >
562
- <Banner
563
- v-if="showRestrictedAdminDeprecationBanner"
564
- color="warning"
565
- class="mb-20"
566
- >
567
- <span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
568
- </Banner>
569
553
  <template v-if="isDetail">
570
554
  <SortableTable
571
555
  key-field="index"
@@ -6,7 +6,7 @@ import { _EDIT } from '@shell/config/query-params';
6
6
  export default {
7
7
  components: { LabeledInput },
8
8
 
9
- emits: ['update:value'],
9
+ emits: ['update:value', 'update:validation', 'change', 'blur'],
10
10
 
11
11
  props: {
12
12
  /**
@@ -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')[4][0]).toBe(value);
187
+ expect(wrapper.emitted('update:value')[3][0]).toBe(value);
188
188
  });
189
189
 
190
190
  describe.each([
@@ -1119,7 +1119,7 @@ export default {
1119
1119
  display: block;
1120
1120
  font-size: $icon-size;
1121
1121
  margin-right: 14px;
1122
- &.group-icon {
1122
+ &:not(.pin){
1123
1123
  width: 42px;
1124
1124
  }
1125
1125
  }
@@ -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.write(this.errorMsg);
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
- this.terminal.write(this.t('wm.containerShell.failed'));
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
- console.error(msg); // eslint-disable-line no-console
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 = msg;
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]).toBe(errorMessage);
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).toBe(errorMessage);
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]).toBe(errorMessage);
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).toBe('eventMessageError');
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]).toBe(linuxErrorMessage);
374
- expect(consoleError.mock.calls[1][0]).toBe(windowsErrorMessage);
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).toBe(windowsErrorMessage);
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]).toBe(linuxErrorMessage);
430
- expect(consoleError.mock.calls[1][0]).toBe(windowsErrorMessage);
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).toBe(windowsErrorMessage);
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).toBe(linuxErrorMessage);
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]).toBe(linuxErrorMessage);
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).toBe(linuxErrorMessage);
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).toBe(linuxErrorMessage);
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]).toBe(linuxErrorMessage);
556
- expect(consoleError.mock.calls[1][0]).toBe(linuxErrorMessage);
557
- expect(consoleError.mock.calls[2][0]).toBe(linuxErrorMessage);
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).toBe(linuxErrorMessage);
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 { ref, computed, ComputedRef, Ref } from 'vue';
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
- export const useLabeledFormElement = (props: LabeledFormElementProps, emit: (event: string, ...args: any[]) => void): UseLabeledFormElement => {
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);