@rancher/shell 3.0.5-rc.2 → 3.0.5-rc.3

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 (113) hide show
  1. package/assets/data/aws-regions.json +2 -0
  2. package/assets/styles/global/_layout.scss +0 -1
  3. package/assets/translations/en-us.yaml +61 -19
  4. package/assets/translations/zh-hans.yaml +0 -10
  5. package/chart/monitoring/index.vue +1 -1
  6. package/components/AsyncButton.vue +2 -0
  7. package/components/CodeMirror.vue +3 -3
  8. package/components/CruResource.vue +103 -15
  9. package/components/ExplorerProjectsNamespaces.vue +7 -2
  10. package/components/FixedBanner.vue +19 -5
  11. package/components/PaginatedResourceTable.vue +7 -0
  12. package/components/ResourceDetail/Masthead.vue +0 -1
  13. package/components/ResourceList/index.vue +2 -1
  14. package/components/SlideInPanelManager.vue +1 -2
  15. package/components/SortableTable/selection.js +1 -1
  16. package/components/Tabbed/index.vue +6 -0
  17. package/components/__tests__/AsyncButton.test.ts +39 -0
  18. package/components/__tests__/CruResource.test.ts +63 -0
  19. package/components/__tests__/PromptModal.test.ts +0 -2
  20. package/components/form/ArrayList.vue +134 -118
  21. package/components/form/BannerSettings.vue +145 -96
  22. package/components/form/KeyValue.vue +10 -7
  23. package/components/form/LabeledSelect.vue +9 -2
  24. package/components/form/MatchExpressions.vue +5 -1
  25. package/components/form/NameNsDescription.vue +1 -1
  26. package/components/form/ResourceSelector.vue +26 -23
  27. package/components/form/ResourceTabs/index.vue +2 -1
  28. package/components/form/Select.vue +9 -2
  29. package/components/form/UnitInput.vue +13 -0
  30. package/components/form/__tests__/ArrayList.test.ts +32 -0
  31. package/components/form/__tests__/KeyValue.test.ts +36 -0
  32. package/components/form/__tests__/LabeledSelect.test.ts +33 -0
  33. package/components/form/__tests__/Select.test.ts +34 -1
  34. package/components/form/__tests__/UnitInput.test.ts +23 -1
  35. package/components/formatter/ClusterLink.vue +5 -8
  36. package/components/formatter/Description.vue +30 -0
  37. package/components/formatter/__tests__/ClusterLink.test.ts +2 -32
  38. package/components/nav/NamespaceFilter.vue +1 -1
  39. package/components/nav/WindowManager/index.vue +1 -0
  40. package/config/product/explorer.js +16 -13
  41. package/config/product/manager.js +1 -28
  42. package/config/settings.ts +11 -13
  43. package/config/table-headers.js +7 -5
  44. package/detail/catalog.cattle.io.app.vue +0 -1
  45. package/detail/provisioning.cattle.io.cluster.vue +13 -3
  46. package/detail/service.vue +0 -1
  47. package/detail/workload/index.vue +21 -34
  48. package/dialog/ExtensionCatalogUninstallDialog.vue +14 -8
  49. package/edit/__tests__/service.test.ts +2 -1
  50. package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +3 -14
  51. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +57 -62
  52. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +3 -14
  53. package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.test.ts +72 -41
  54. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +17 -1
  55. package/edit/networking.k8s.io.networkpolicy/index.vue +18 -30
  56. package/edit/provisioning.cattle.io.cluster/index.vue +21 -73
  57. package/edit/service.vue +13 -28
  58. package/list/workload.vue +6 -1
  59. package/mixins/resource-fetch-api-pagination.js +55 -43
  60. package/mixins/resource-fetch.js +14 -5
  61. package/models/__tests__/workload.test.ts +1 -0
  62. package/models/cluster/node.js +1 -0
  63. package/models/cluster.js +32 -2
  64. package/models/management.cattle.io.cluster.js +0 -20
  65. package/models/management.cattle.io.node.js +7 -22
  66. package/models/management.cattle.io.nodepool.js +12 -0
  67. package/models/namespace.js +5 -0
  68. package/models/provisioning.cattle.io.cluster.js +18 -64
  69. package/models/service.js +24 -9
  70. package/models/workload.js +70 -31
  71. package/package.json +1 -1
  72. package/pages/c/_cluster/apps/charts/install.vue +0 -1
  73. package/pages/c/_cluster/explorer/index.vue +11 -0
  74. package/pages/c/_cluster/longhorn/index.vue +2 -2
  75. package/pages/c/_cluster/settings/banners.vue +56 -2
  76. package/pages/c/_cluster/settings/performance.vue +7 -26
  77. package/pages/home.vue +11 -52
  78. package/plugins/clean-html.js +2 -0
  79. package/plugins/dashboard-store/__tests__/actions.test.ts +4 -1
  80. package/plugins/dashboard-store/actions.js +122 -21
  81. package/plugins/dashboard-store/getters.js +74 -3
  82. package/plugins/dashboard-store/mutations.js +10 -5
  83. package/plugins/dashboard-store/resource-class.js +23 -3
  84. package/plugins/steve/__tests__/getters.test.ts +18 -11
  85. package/plugins/steve/__tests__/steve-class.test.ts +1 -0
  86. package/plugins/steve/actions.js +34 -12
  87. package/plugins/steve/getters.js +39 -10
  88. package/plugins/steve/steve-class.js +5 -0
  89. package/plugins/steve/steve-pagination-utils.ts +199 -37
  90. package/plugins/steve/worker/web-worker.advanced.js +3 -1
  91. package/rancher-components/Banner/Banner.test.ts +51 -3
  92. package/rancher-components/Banner/Banner.vue +28 -6
  93. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +5 -1
  94. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +21 -1
  95. package/store/features.js +0 -1
  96. package/store/type-map.utils.ts +45 -2
  97. package/types/fleet.d.ts +1 -1
  98. package/types/kube/kube-api.ts +22 -0
  99. package/types/resources/settings.d.ts +0 -4
  100. package/types/shell/index.d.ts +346 -289
  101. package/types/store/dashboard-store.types.ts +24 -1
  102. package/types/store/pagination.types.ts +19 -2
  103. package/utils/cluster.js +24 -20
  104. package/utils/grafana.js +1 -0
  105. package/utils/object.js +0 -12
  106. package/utils/pagination-utils.ts +6 -2
  107. package/utils/perf-setting.utils.ts +28 -0
  108. package/utils/selector-typed.ts +205 -0
  109. package/utils/selector.js +29 -6
  110. package/utils/uiplugins.ts +10 -6
  111. package/utils/v-sphere.ts +5 -1
  112. package/components/formatter/RKETemplateName.vue +0 -37
  113. package/dialog/SaveAsRKETemplateDialog.vue +0 -139
@@ -11,6 +11,7 @@
11
11
  "ap-southeast-3",
12
12
  "ap-southeast-4",
13
13
  "ap-southeast-5",
14
+ "ap-southeast-7",
14
15
  "ca-central-1",
15
16
  "ca-west-1",
16
17
  "cn-north-1",
@@ -26,6 +27,7 @@
26
27
  "il-central-1",
27
28
  "me-central-1",
28
29
  "me-south-1",
30
+ "mx-central-1",
29
31
  "sa-east-1",
30
32
  "us-east-1",
31
33
  "us-east-2",
@@ -69,7 +69,6 @@ HEADER {
69
69
  display: grid;
70
70
  position: relative;
71
71
  flex: 1 1 auto;
72
- overflow-y: auto;
73
72
  min-height: 0px;
74
73
 
75
74
  &.dashboard-padding-left {
@@ -7,6 +7,9 @@ generic:
7
7
  userMenu: user menu
8
8
  actionMenu: action menu
9
9
  namespaceFilter: namespace filter menu
10
+ banners:
11
+ altCloseBanner: Close banner icon
12
+ bannerIcon: Banner icon
10
13
  filterNamespaces: filter namespaces
11
14
  clearSearch: clear search
12
15
  clearFilters: clear filters
@@ -129,8 +132,16 @@ generic:
129
132
  }
130
133
  basic: Basic
131
134
  ariaLabel:
132
- key: Key {index}
133
- value: Value {index}
135
+ keyValue: Key-Value input
136
+ key: Key for row {index}
137
+ value: Value for row {index}
138
+ remove: remove row {index}
139
+ addKeyValue: Add a new Key-Value row
140
+ readKeyValue: read Key-Values from file
141
+ genericAddRow: Add a new row
142
+ arrayList: Array list input
143
+ genericRow: row {index}
144
+
134
145
 
135
146
  locale:
136
147
  menu: Locale selector menu
@@ -181,7 +192,6 @@ nav:
181
192
  restoreSnapshot: Restore Snapshot
182
193
  rotateCertificates: Rotate Certificates
183
194
  rotateEncryptionKeys: Rotate Encryption Keys
184
- saveAsRKETemplate: Save as RKE Template
185
195
  takeSnapshot: Take Snapshot
186
196
  seeAllClusters: See all clusters
187
197
  seeAllClustersCollapsed: See all
@@ -717,6 +727,8 @@ assignTo:
717
727
  workspace: Workspace
718
728
 
719
729
  asyncButton:
730
+ alt:
731
+ iconAlt: Async button icon
720
732
  apply:
721
733
  action: Apply
722
734
  success: Applied
@@ -1936,9 +1948,8 @@ cluster:
1936
1948
  =1 { {pool_name}: The provided value for {fields} was not found in the list of expected values. This can happen with clusters provisioned outside of Rancher or when options for the provider have changed. }
1937
1949
  other { {pool_name}: The provided values for {fields} were not found in the list of expected values. This can happen with clusters provisioned outside of Rancher or when options for the provider have changed. }
1938
1950
  }
1939
- rke1DeprecationMessage: 'Rancher Kubernetes Engine (RKE / RKE1) will reach end of life on July 31, 2025. Rancher 2.12.0 and later will no longer support provisioning or managing downstream RKE1 clusters. We recommend replatforming RKE1 clusters to RKE2 to ensure continued support and security updates. Learn more about the transition <a href="https://www.suse.com/support/kb/doc/?id=000021518" target="_blank" rel="noopener noreferrer nofollow">here</a>.'
1940
- rke1DeprecationShortMessage: Rancher Kubernetes Engine (RKE / RKE1) will reach end of life on July 31, 2025. Rancher 2.12.0 and later will no longer support provisioning or managing downstream RKE1 clusters.
1941
- rkeTemplateUpgrade: Template revision {name} available for upgrade
1951
+ rke1DeprecationMessage: 'Rancher Kubernetes Engine (RKE / RKE1) has reached end of life and these clusters are no longer supported. We recommend replatforming RKE1 clusters to RKE2 to ensure continued support and security updates. Learn more about the transition <a href="https://www.suse.com/support/kb/doc/?id=000021518" target="_blank" rel="noopener noreferrer nofollow">here</a>.'
1952
+ rke1Unsupported: RKE1 Clusters are no longer supported
1942
1953
  cloudCredentials:
1943
1954
  renew: Renew Cloud Credentials
1944
1955
  expired: Cloud Credential expired, please Renew Cloud Credentials
@@ -3209,8 +3220,6 @@ landing:
3209
3220
  title: Commercial Support
3210
3221
  body: Learn about commercial support
3211
3222
  landingPrefs:
3212
- title: You can change what you see when you login via preferences
3213
- userPrefs: Preferences
3214
3223
  body: "You can change where you land when you login"
3215
3224
  ariaLabelTakeMeToCluster: Select which cluster to take me to after login
3216
3225
  options:
@@ -3908,6 +3917,7 @@ nameNsDescription:
3908
3917
  placeholder: Any text you want that better describes this resource
3909
3918
 
3910
3919
  namespace:
3920
+ cancelCreateAriaLabel: Cancel create namespace
3911
3921
  containerResourceLimit: Container Resource Limit
3912
3922
  resourceQuotas: Resource Quotas
3913
3923
  project:
@@ -4981,12 +4991,6 @@ promptRollback:
4981
4991
  multipleWorkloadError: "Only one workload can be rolled back at a time."
4982
4992
  singleRevisionBanner: There are no revisions to roll back to.
4983
4993
 
4984
- promptSaveAsRKETemplate:
4985
- title: Convert {cluster} to new RKE Template
4986
- name: Cluster Template Name
4987
- description: Create a new RKE cluster template and initial revision from the current cluster configuration.
4988
- warning: This will modify the cluster, setting it up to use the newly created cluster template and revision. This can not be undone.
4989
-
4990
4994
  promptRotateEncryptionKey:
4991
4995
  title: Rotate Encryption Keys
4992
4996
  description: The last backup {name} was performed on {date}
@@ -7826,15 +7830,12 @@ performance:
7826
7830
  authUserTTL: This timeout cannot be higher than the user session timeout auth-user-session-ttl-minutes, which is currently {current} minutes.
7827
7831
  serverPagination:
7828
7832
  label: Server-side Pagination
7829
- description: By default Lists will fetch all resources and paginate in the browser (create the page given sorting and filtering settings). Change this setting to enable pagination server-side. This should help the responsiveness of the UI in systems with a lot of resources.
7830
- checkboxLabel: Enable Server-side Pagination
7833
+ description: By default Lists will fetch all resources and paginate (create the page given sort and filter settings) locally in the browser. Server-Side Pagination moves this out from the browser to the server. This improves performance of the UI, especially in systems with lots of resources.
7831
7834
  applicable: "Server-side pagination applies to Resource Types"
7832
- incompatibleDescription: "Server-side Pagination is incompatible with Manual Refresh and Incremental Loading. Enabling this will disable them."
7833
- featureFlag: The&nbsp;<a href="{ffUrl}">Feature Flag</a>&nbsp;`ui-sql-cache` must be enabled to use this feature
7835
+ featureFlag: Enabling/Disabling <i class="mr-5">"Server-side Pagination"</i> is now solely done so via the <i class="mr-5">ui-sql-cache</i>&nbsp;<a href="{ffUrl}">Feature Flag</a>
7834
7836
  resources:
7835
7837
  generic: most resources in the cluster's 'More Resources' section
7836
7838
  all: All Resources
7837
- experimental: This setting is experimental and may be removed or updated in future versions. We do not recommend enabling this setting in production environments.
7838
7839
  banner:
7839
7840
  label: Fixed Banners
7840
7841
  settingName: Banners
@@ -7867,6 +7868,12 @@ banner:
7867
7868
  consent: Consent Banner
7868
7869
  consentFootnote: "Tip: Use \\n character for line break"
7869
7870
  individualSetting: 'This banner is managed by the setting "{name}" and can not be modified in the UI'
7871
+ type:
7872
+ html: HTML
7873
+ text: Text
7874
+ htmlWarning: 'Not all HTML elements or attriutes are permitted and the HTML entered here will be sanitized before display. For HTML anchor elements, it is recommended to use the target attribute to have links open in a blank tab.'
7875
+ htmlContent: HTML Content
7876
+ toggleTextHtml: Toggle between text and html
7870
7877
 
7871
7878
  branding:
7872
7879
  label: Branding
@@ -8167,3 +8174,38 @@ gitPicker:
8167
8174
  networkAttachmentDefinition:
8168
8175
  tabs:
8169
8176
  config: Config
8177
+
8178
+ errors:
8179
+ actionNotAvailable: This action is not currently available
8180
+ missingRequired: is required
8181
+ notUnique: is not unique
8182
+ notNullable: must be set
8183
+ invalidOption: is not a valid option
8184
+ invalidCharacters: contains invalid characters
8185
+ minLengthExceeded: is not long enough
8186
+ maxLengthExceeded: is too long
8187
+ minLimitExceeded: is too small
8188
+ maxLimitExceded: is too big
8189
+ messageAndDetail: '{message} ({detail})'
8190
+ messageOrDetail: '{val}'
8191
+ failedInApi:
8192
+ withName:
8193
+ withCodeExplanation:
8194
+ withMessageDetail: 'Validation failed in API: {name} {codeExplanation}: {messageDetail}'
8195
+ withoutMessageDetail: 'Validation failed in API: {name} {codeExplanation}'
8196
+ withMessageDetail: 'Validation failed in API: {name} {messageDetail}'
8197
+ withoutAnythingElse: 'Validation failed in API: {name} '
8198
+ withoutName:
8199
+ withMessageDetail:
8200
+ withCodeExplanation: 'Validation failed in API: {codeExplanation}: {messageDetail}'
8201
+ withoutCodeExplanation: 'Validation failed in API: {messageDetail}'
8202
+ withCode:
8203
+ withCodeExplanation: 'Validation failed in API: {code} {codeExplanation}'
8204
+ withoutCodeExplanation: 'Validation failed in API: {code}'
8205
+ withoutAnything: Validation failed in API.
8206
+ notFound:
8207
+ withUrl: '{msg}: {url}'
8208
+ withoutUrl: '{msg}'
8209
+
8210
+
8211
+
@@ -137,7 +137,6 @@ nav:
137
137
  restoreSnapshot: 还原快照
138
138
  rotateCertificates: 轮换证书
139
139
  rotateEncryptionKeys: 轮换加密密钥
140
- saveAsRKETemplate: 保存为 RKE 模板
141
140
  takeSnapshot: 拍摄快照
142
141
  group:
143
142
  cluster: 集群
@@ -1669,7 +1668,6 @@ cluster:
1669
1668
  rke2-k3-reprovisioning: '更改集群配置可能导致节点重新配置。详情请参见 <a target="blank" href="{docsBase}/how-to-guides/new-user-guides/launch-kubernetes-with-rancher/rke1-vs-rke2-differences#cluster-api" target="_blank" rel="noopener nofollow">文档</a>。'
1670
1669
  desiredNodeGroupWarning: 没有可用于运行 Cluster Agent 的节点。要让集群变为 Active 状态,至少需要有 1 个可用的节点。
1671
1670
  haveArgInfo: 所选 Kubernetes 版本的配置信息不可用。此屏幕中可用的选项将受到限制,你可能需要使用 YAML 编辑器。
1672
- rkeTemplateUpgrade: 模板修订版 {name} 可用于升级
1673
1671
 
1674
1672
  availabilityWarnings:
1675
1673
  node: 节点 {name} 处于非活动状态
@@ -2731,8 +2729,6 @@ landing:
2731
2729
  title: 商业支持
2732
2730
  body: 了解商业支持
2733
2731
  landingPrefs:
2734
- title: 更改登录后显示的页面:
2735
- userPrefs: 偏好设置
2736
2732
  body: "登录后显示:"
2737
2733
  options:
2738
2734
  homePage: 主页
@@ -4447,12 +4443,6 @@ promptRollback:
4447
4443
  multipleWorkloadError: "一次只能回滚一个工作负载。"
4448
4444
  singleRevisionBanner: 没有可用于回滚的修订版本。
4449
4445
 
4450
- promptSaveAsRKETemplate:
4451
- title: 将 {cluster} 转换为新的 RKE 模板
4452
- name: 集群模板名称
4453
- description: 创建新的集群模板,并使用当前集群配置发起修改。
4454
- warning: 此操作将对集群进行修改,即把新创建的集群模板和修改应用到集群中。此操作不能撤销。
4455
-
4456
4446
  promptRotateEncryptionKey:
4457
4447
  title: 轮换加密密钥
4458
4448
  description: 上次备份 {name} 的备份时间为 {date}
@@ -55,7 +55,7 @@ export default {
55
55
  async fetch() {
56
56
  const { $store } = this;
57
57
 
58
- // Fetch all the resources required for all the tabs asyncronously up front
58
+ // Fetch all the resources required for all the tabs asynchronously up front
59
59
  const hashPromises = {
60
60
  namespaces: $store.getters['namespaces'](),
61
61
  pvcs: $store.dispatch('cluster/findAll', { type: PVC }),
@@ -314,11 +314,13 @@ export default defineComponent({
314
314
  v-if="displayIcon"
315
315
  v-clean-tooltip="tooltip"
316
316
  :class="{icon: true, 'icon-lg': true, [displayIcon]: true, 'mr-0': isManualRefresh}"
317
+ :alt="t('asyncButton.alt.iconAlt')"
317
318
  />
318
319
  <span
319
320
  v-if="labelAs === 'text' && displayLabel"
320
321
  v-clean-tooltip="tooltip"
321
322
  v-clean-html="displayLabel"
323
+ data-testid="async-btn-display-label"
322
324
  />
323
325
  </button>
324
326
  </template>
@@ -88,7 +88,7 @@ export default {
88
88
 
89
89
  // fixes https://github.com/rancher/dashboard/issues/13653
90
90
  // we can't use the inert HTML prop on the parent because it disables all interaction
91
- out.readOnly = this.isDisabled ? 'nocursor' : false;
91
+ out.readOnly = !!this.isDisabled;
92
92
 
93
93
  return out;
94
94
  },
@@ -227,8 +227,8 @@ export default {
227
227
  },
228
228
 
229
229
  onFocus() {
230
- this.isCodeMirrorFocused = true;
231
- this.$emit('onFocus', true);
230
+ this.isCodeMirrorFocused = !this.isDisabled;
231
+ this.$emit('onFocus', this.isCodeMirrorFocused);
232
232
  },
233
233
 
234
234
  onBlur() {
@@ -265,7 +265,7 @@ export default {
265
265
  return !this.errors ? {} : this.errorsMap || this.errors.reduce((acc, error) => ({
266
266
  ...acc,
267
267
  [error]: {
268
- message: error,
268
+ message: this.formatError(error),
269
269
  icon: null
270
270
  }
271
271
  }), {});
@@ -371,19 +371,23 @@ export default {
371
371
  },
372
372
 
373
373
  async clickSave(buttonDone) {
374
- try {
375
- await this.createNamespaceIfNeeded();
376
-
377
- // If the attempt to create the new namespace
378
- // is successful, save the resource.
379
- this.$emit('finish', buttonDone);
380
- } catch (err) {
374
+ if (this.createNamespace) {
375
+ try {
376
+ await this.createNamespaceIfNeeded();
377
+ } catch (err) {
381
378
  // After the attempt to create the namespace,
382
379
  // show any applicable errors if the namespace is
383
380
  // invalid.
384
- this.$emit('error', exceptionToErrorsArray(err.message));
385
- buttonDone(false);
381
+ this.$emit('error', exceptionToErrorsArray(err.message));
382
+ buttonDone(false);
383
+
384
+ return;
385
+ }
386
386
  }
387
+
388
+ // If the attempt to create the new namespace
389
+ // was successful or no ns needs to be created, save the resource.
390
+ this.$emit('finish', buttonDone);
387
391
  },
388
392
 
389
393
  save() {
@@ -395,17 +399,13 @@ export default {
395
399
  const newNamespaceName = get(this.resource, this.namespaceKey);
396
400
  let namespaceAlreadyExists = false;
397
401
 
398
- if (!this.createNamespace) {
399
- return;
400
- }
401
-
402
402
  try {
403
403
  // This is in a try-catch block because the call to fetch
404
404
  // a namespace throws an error if the namespace is not found.
405
405
  namespaceAlreadyExists = !!(await this.$store.dispatch(`${ inStore }/find`, { type: NAMESPACE, id: newNamespaceName }));
406
406
  } catch {}
407
407
 
408
- if (this.createNamespace && !namespaceAlreadyExists) {
408
+ if (!namespaceAlreadyExists) {
409
409
  try {
410
410
  const newNamespace = await this.$store.dispatch(`${ inStore }/createNamespace`, { name: newNamespaceName }, { root: true });
411
411
 
@@ -427,6 +427,94 @@ export default {
427
427
 
428
428
  shouldProvideSlot(slot) {
429
429
  return slot !== 'default' && typeof this.$slots[slot] === 'function';
430
+ },
431
+
432
+ formatError(err) {
433
+ if ( typeof err === 'string') {
434
+ return err;
435
+ }
436
+
437
+ if ( err?.code === 'ActionNotAvailable' ) {
438
+ return this.t('errors.actionNotAvailable');
439
+ }
440
+ const msg = !!err?.message ? err.message : '';
441
+ let messageDetail = '';
442
+
443
+ if (!!err?.message && !!err.detail) {
444
+ messageDetail = this.t('errors.messageAndDetail', { message: err.message, detail: err.detail });
445
+ } else if (!!err?.message || !!err.detail) {
446
+ const val = err.message ? err.message : err.detail;
447
+
448
+ messageDetail = this.t('errors.messageOrDetail', { val });
449
+ }
450
+
451
+ if ( err?.status === 422 ) {
452
+ const name = err?.fieldName;
453
+ const code = err?.code;
454
+ let codeExplanation = '';
455
+
456
+ switch ( err?.code ) {
457
+ case 'MissingRequired':
458
+ codeExplanation = this.t('errors.missingRequired'); break;
459
+ case 'NotUnique':
460
+ codeExplanation = this.t('errors.notUnique'); break;
461
+ case 'NotNullable':
462
+ codeExplanation = this.t('errors.notNullable'); break;
463
+ case 'InvalidOption':
464
+ codeExplanation = this.t('errors.invalidOption'); break;
465
+ case 'InvalidCharacters':
466
+ codeExplanation = this.t('errors.invalidCharacters'); break;
467
+ case 'MinLengthExceeded':
468
+ codeExplanation = this.t('errors.minLengthExceeded'); break;
469
+ case 'MaxLengthExceeded':
470
+ codeExplanation = this.t('errors.maxLengthExceeded'); break;
471
+ case 'MinLimitExceeded':
472
+ codeExplanation = this.t('errors.minLimitExceeded'); break;
473
+ case 'MaxLimitExceded':
474
+ codeExplanation = this.t('errors.maxLimitExceded'); break;
475
+ }
476
+
477
+ if (!!name) {
478
+ if (!!codeExplanation) {
479
+ if (!!messageDetail) {
480
+ return this.t('errors.failedInApi.withName.withCodeExplanation.withMessageDetail', {
481
+ name, codeExplanation, messageDetail
482
+ });
483
+ }
484
+
485
+ return this.t('errors.failedInApi.withName.withCodeExplanation.withoutMessageDetail', { name, codeExplanation });
486
+ }
487
+ if (!!messageDetail) {
488
+ return this.t('errors.failedInApi.withName.withMessageDetail', { name, messageDetail });
489
+ }
490
+
491
+ return this.t('errors.failedInApi.withName.withoutAnythingElse', { name });
492
+ } else {
493
+ if (!!messageDetail) {
494
+ if (!!codeExplanation) {
495
+ return this.t('errors.failedInApi.withoutName.withMessageDetail.withCodeExplanation', { codeExplanation, messageDetail });
496
+ }
497
+
498
+ return this.t('errors.failedInApi.withoutName.withMessageDetail.withoutCodeExplanation', { messageDetail });
499
+ } else if (!!code) {
500
+ if (!!codeExplanation) {
501
+ return this.t('errors.failedInApi.withoutName.withCode.withCodeExplanation', { code, codeExplanation });
502
+ }
503
+
504
+ return this.t('errors.failedInApi.withoutName.withCode.withoutCodeExplanation', { code });
505
+ }
506
+
507
+ return this.t('errors.failedInApi.withoutAnything');
508
+ }
509
+ } else if ( err?.status === 404 ) {
510
+ if (!!err?.opt?.url) {
511
+ return this.t('errors.notFound.withUrl', { msg, url: err.opt.url });
512
+ }
513
+
514
+ return this.t('errors.notFound.withoutUrl', { msg });
515
+ }
516
+
517
+ return messageDetail.length > 0 ? messageDetail : err;
430
518
  }
431
519
  },
432
520
 
@@ -1,7 +1,9 @@
1
1
  <script>
2
2
  import { mapGetters, useStore } from 'vuex';
3
3
  import ResourceTable, { defaultTableSortGenerationFn } from '@shell/components/ResourceTable';
4
- import { STATE, AGE, NAME, NS_SNAPSHOT_QUOTA } from '@shell/config/table-headers';
4
+ import {
5
+ STATE, AGE, NAME, NS_SNAPSHOT_QUOTA, DESCRIPTION
6
+ } from '@shell/config/table-headers';
5
7
  import { uniq } from '@shell/utils/array';
6
8
  import { MANAGEMENT, NAMESPACE, VIRTUAL_TYPES, HCI } from '@shell/config/types';
7
9
  import { PROJECT_ID, FLAT_VIEW } from '@shell/config/query-params';
@@ -15,6 +17,7 @@ import { NAMESPACE_FILTER_ALL_ORPHANS } from '@shell/utils/namespace-filter';
15
17
  import ResourceFetch from '@shell/mixins/resource-fetch';
16
18
  import DOMPurify from 'dompurify';
17
19
  import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
20
+ import perfSettingsUtils from '@shell/utils/perf-setting.utils';
18
21
  import ActionMenu from '@shell/components/ActionMenuShell.vue';
19
22
  import { useRuntimeFlag } from '@shell/composables/useRuntimeFlag';
20
23
 
@@ -110,7 +113,7 @@ export default {
110
113
  return !this.currentCluster || this.namespaces.length ? false : this.$fetchState.pending;
111
114
  },
112
115
  showIncrementalLoadingIndicator() {
113
- return this.perfConfig?.incrementalLoading?.enabled;
116
+ return perfSettingsUtils.incrementalLoadingUtils.isEnabled(this.calcCanPaginate(), this.perfConfig);
114
117
  },
115
118
  isNamespaceCreatable() {
116
119
  return (this.schema?.collectionMethods || []).includes('POST');
@@ -122,6 +125,7 @@ export default {
122
125
  const headers = [
123
126
  STATE,
124
127
  NAME,
128
+ DESCRIPTION
125
129
  ];
126
130
 
127
131
  if (this.groupPreference === 'none') {
@@ -502,6 +506,7 @@ export default {
502
506
  <ActionMenu
503
507
  v-if="showProjectActionButton(group.group)"
504
508
  :resource="getProjectActions(group.group)"
509
+ data-testid="action-button"
505
510
  :button-aria-label="t('projectNamespaces.tableActionsLabel', { resource: projectResource(group.group) })"
506
511
  />
507
512
  <div
@@ -164,7 +164,7 @@ export default {
164
164
  class="banner"
165
165
  data-testid="fixed__banner"
166
166
  :style="bannerStyle"
167
- :class="{'banner-consent': consent}"
167
+ :class="{'banner-consent': consent, 'banner-text': !!banner.text}"
168
168
  >
169
169
  <!-- text as array to support line breaks programmatically rather than just exposing HTML -->
170
170
  <div v-if="isTextAnArray">
@@ -177,11 +177,16 @@ export default {
177
177
  </div>
178
178
  </div>
179
179
  <div
180
- v-else
180
+ v-else-if="banner.text"
181
181
  class="single-row"
182
182
  >
183
183
  {{ banner.text }}
184
184
  </div>
185
+ <div
186
+ v-else-if="banner.html"
187
+ v-clean-html="banner.html"
188
+ class="single-row"
189
+ />
185
190
  </div>
186
191
  <div v-else-if="showDialog">
187
192
  <div class="banner-dialog-glass" />
@@ -205,13 +210,19 @@ export default {
205
210
  </div>
206
211
  </div>
207
212
  <div
208
- v-else
213
+ v-else-if="banner.text"
209
214
  class="single-row"
210
215
  >
211
216
  {{ banner.text }}
212
217
  </div>
218
+ <div
219
+ v-else-if="banner.html"
220
+ v-clean-html="banner.html"
221
+ class="single-row"
222
+ />
213
223
  </div>
214
224
  <button
225
+ data-testid="login-confirmation-accept-button"
215
226
  class="btn role-primary"
216
227
  @click="hideDialog()"
217
228
  >
@@ -225,10 +236,13 @@ export default {
225
236
 
226
237
  <style lang="scss" scoped>
227
238
  .banner {
228
- text-align: center;
229
239
  line-height: 2em;
230
240
  width: 100%;
231
- padding: 0 20px;
241
+
242
+ &.banner-text {
243
+ padding: 0 20px;
244
+ text-align: center;
245
+ }
232
246
 
233
247
  &.banner-consent {
234
248
  height: unset;
@@ -95,6 +95,12 @@ export default defineComponent({
95
95
 
96
96
  return customHeaders || this.$store.getters['type-map/headersFor'](this.schema, this.canPaginate);
97
97
  }
98
+ },
99
+
100
+ methods: {
101
+ clearSelection() {
102
+ this.$refs.table.clearSelection();
103
+ },
98
104
  }
99
105
  });
100
106
 
@@ -103,6 +109,7 @@ export default defineComponent({
103
109
  <template>
104
110
  <div>
105
111
  <ResourceTable
112
+ ref="table"
106
113
  v-bind="$attrs"
107
114
  :schema="schema"
108
115
  :rows="rows"
@@ -545,7 +545,6 @@ export default {
545
545
  {{ value.createdBy.displayName }}
546
546
  </span>
547
547
  </span>
548
- <span v-if="value.showPodRestarts">{{ t("resourceDetail.masthead.restartCount") }}:<span class="live-data"> {{ value.restartCount }}</span></span>
549
548
  </div>
550
549
  </div>
551
550
  <slot name="right">
@@ -9,6 +9,7 @@ import { ResourceListComponentName } from './resource-list.config';
9
9
  import { PanelLocation, ExtensionPoint } from '@shell/core/types';
10
10
  import ExtensionPanel from '@shell/components/ExtensionPanel';
11
11
  import { sameContents } from '@shell/utils/array';
12
+ import perfSettingsUtils from '@shell/utils/perf-setting.utils';
12
13
 
13
14
  export default {
14
15
  name: ResourceListComponentName,
@@ -138,7 +139,7 @@ export default {
138
139
  },
139
140
 
140
141
  showIncrementalLoadingIndicator() {
141
- return this.perfConfig?.incrementalLoading?.enabled;
142
+ return perfSettingsUtils.incrementalLoadingUtils.isEnabled(this.calcCanPaginate(), this.perfConfig);
142
143
  },
143
144
 
144
145
  },
@@ -53,8 +53,7 @@ function closePanel() {
53
53
  <i
54
54
  class="icon icon-close"
55
55
  data-testid="slide-in-close"
56
- :trigger-focus-trap="true"
57
- tabindex="0"
56
+ :tabindex="isOpen ? 0 : -1"
58
57
  @click="closePanel"
59
58
  />
60
59
  </div>
@@ -531,7 +531,7 @@ export default {
531
531
  },
532
532
 
533
533
  clearSelection() {
534
- this.update([], this.selectedRows);
534
+ this.update([], [...this.selectedRows]);
535
535
  },
536
536
 
537
537
  }
@@ -61,6 +61,11 @@ export default {
61
61
  tabsOnly: {
62
62
  type: Boolean,
63
63
  default: false,
64
+ },
65
+
66
+ resource: {
67
+ type: Object,
68
+ default: () => {}
64
69
  }
65
70
  },
66
71
 
@@ -355,6 +360,7 @@ export default {
355
360
  >
356
361
  <component
357
362
  :is="tab.component"
363
+ :resource="resource"
358
364
  />
359
365
  </Tab>
360
366
  </div>
@@ -145,4 +145,43 @@ describe('component: AsyncButton', () => {
145
145
  expect(spyDone).toHaveBeenCalledWith('cancelled');
146
146
  expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.ACTION);
147
147
  });
148
+
149
+ it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', () => {
150
+ const mockExists = jest.fn().mockReturnValue(true);
151
+ const mockT = jest.fn().mockReturnValue('some-string');
152
+ const ariaLabel = 'some-aria-label';
153
+ const ariaLabelledBy = 'some-aria-labelledby';
154
+
155
+ const wrapper: VueWrapper<InstanceType<typeof AsyncButton>> = mount(AsyncButton, {
156
+ props: { icon: 'some-icon', disabled: true },
157
+ attrs: { 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy },
158
+ global: {
159
+ mocks: {
160
+ $store: {
161
+ getters: {
162
+ 'i18n/exists': mockExists,
163
+ 'i18n/t': mockT
164
+ }
165
+ },
166
+ }
167
+ },
168
+ });
169
+
170
+ const item = wrapper.find('button');
171
+
172
+ const itemRole = item.attributes('role');
173
+ const itemAriaLabel = item.attributes('aria-label');
174
+ const itemAriaLabelledBy = item.attributes('aria-labelledby');
175
+ const itemAriaDisabled = item.attributes('aria-disabled');
176
+
177
+ // let's check some attributes passing...
178
+ expect(itemAriaLabel).toBe(ariaLabel);
179
+ expect(itemAriaLabelledBy).toBe(ariaLabelledBy);
180
+
181
+ // rest of the checks
182
+ expect(itemRole).toBe('button');
183
+ expect(itemAriaDisabled).toBe('true');
184
+ expect(item.find('span[data-testid="async-btn-display-label"]').attributes('id')).toBe(wrapper.vm.describedbyId);
185
+ expect(item.find('i').attributes('alt')).toBeDefined();
186
+ });
148
187
  });