@rancher/shell 3.0.1-rc.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/assets/data/aws-regions.json +1 -0
  2. package/assets/styles/base/_basic.scss +5 -0
  3. package/assets/styles/base/_mixins.scss +8 -0
  4. package/assets/styles/global/_button.scss +5 -0
  5. package/assets/styles/themes/_dark.scss +2 -0
  6. package/assets/styles/themes/_light.scss +2 -0
  7. package/assets/translations/en-us.yaml +40 -22
  8. package/assets/translations/zh-hans.yaml +1 -7
  9. package/chart/monitoring/StorageClassSelector.vue +1 -1
  10. package/components/AssignTo.vue +1 -0
  11. package/components/AsyncButton.vue +1 -0
  12. package/components/BackLink.vue +8 -2
  13. package/components/PaginatedResourceTable.vue +135 -0
  14. package/components/ResourceDetail/Masthead.vue +1 -1
  15. package/components/ResourceDetail/index.vue +66 -11
  16. package/components/ResourceList/index.vue +0 -1
  17. package/components/ResourceTable.vue +6 -1
  18. package/components/ResourceYaml.vue +0 -53
  19. package/components/SortableTable/index.vue +8 -6
  20. package/components/Tabbed/index.vue +35 -2
  21. package/components/form/ResourceLabeledSelect.vue +2 -2
  22. package/components/form/ResourceTabs/index.vue +0 -23
  23. package/components/form/Taints.vue +1 -1
  24. package/components/form/UnitInput.vue +1 -1
  25. package/components/form/__tests__/UnitInput.test.ts +1 -1
  26. package/components/nav/TopLevelMenu.helper.ts +546 -0
  27. package/components/nav/TopLevelMenu.vue +125 -160
  28. package/components/nav/WindowManager/ContainerShell.vue +13 -4
  29. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +20 -18
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
  31. package/composables/useLabeledFormElement.ts +6 -2
  32. package/config/pagination-table-headers.js +4 -4
  33. package/config/product/explorer.js +2 -0
  34. package/config/router/navigation-guards/index.js +1 -2
  35. package/config/router/routes.js +1 -1
  36. package/config/settings.ts +15 -8
  37. package/core/plugin.ts +8 -1
  38. package/core/types-provisioning.ts +5 -0
  39. package/core/types.ts +26 -1
  40. package/dialog/DrainNode.vue +6 -6
  41. package/edit/catalog.cattle.io.clusterrepo.vue +95 -52
  42. package/edit/provisioning.cattle.io.cluster/index.vue +8 -3
  43. package/edit/workload/index.vue +1 -1
  44. package/edit/workload/storage/csi/index.vue +29 -1
  45. package/edit/workload/storage/index.vue +1 -0
  46. package/initialize/App.vue +3 -10
  47. package/initialize/install-plugins.js +1 -2
  48. package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +6 -2
  49. package/list/node.vue +8 -5
  50. package/mixins/resource-fetch-api-pagination.js +40 -5
  51. package/mixins/resource-fetch.js +48 -5
  52. package/models/management.cattle.io.nodepool.js +5 -4
  53. package/models/nodedriver.js +2 -2
  54. package/models/provisioning.cattle.io.cluster.js +3 -11
  55. package/package.json +7 -8
  56. package/pages/about.vue +22 -0
  57. package/pages/auth/setup.vue +7 -28
  58. package/pages/c/_cluster/explorer/__tests__/index.test.ts +36 -24
  59. package/pages/c/_cluster/explorer/index.vue +100 -59
  60. package/pages/home.vue +308 -123
  61. package/plugins/dashboard-store/__tests__/mutations.test.ts +2 -0
  62. package/plugins/dashboard-store/actions.js +29 -19
  63. package/plugins/dashboard-store/getters.js +5 -2
  64. package/plugins/dashboard-store/mutations.js +4 -2
  65. package/plugins/steve/__tests__/mutations.test.ts +2 -1
  66. package/plugins/steve/steve-pagination-utils.ts +25 -2
  67. package/plugins/steve/subscribe.js +22 -8
  68. package/rancher-components/Banner/Banner.vue +1 -0
  69. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -0
  70. package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
  71. package/rancher-components/Form/Radio/RadioButton.vue +2 -0
  72. package/rancher-components/Form/Radio/RadioGroup.vue +2 -0
  73. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +2 -0
  74. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -0
  75. package/rancher-components/StringList/StringList.test.ts +15 -15
  76. package/rancher-components/StringList/StringList.vue +3 -0
  77. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
  78. package/scripts/extension/parse-tag-name +2 -0
  79. package/scripts/test-plugins-build.sh +4 -2
  80. package/store/index.js +31 -9
  81. package/tsconfig.json +7 -1
  82. package/types/resources/settings.d.ts +1 -1
  83. package/types/shell/index.d.ts +1107 -1276
  84. package/types/store/dashboard-store.types.ts +4 -0
  85. package/types/store/pagination.types.ts +13 -0
  86. package/types/store/vuex.d.ts +8 -0
  87. package/types/vue-shim.d.ts +6 -31
  88. package/utils/cluster.js +92 -1
  89. package/utils/pagination-utils.ts +17 -8
  90. package/utils/pagination-wrapper.ts +70 -0
  91. package/utils/uiplugins.ts +307 -0
  92. package/components/templates/error.vue +0 -131
  93. package/config/router/navigation-guards/history.js +0 -13
  94. package/plugins/back-button.js +0 -3
@@ -10,6 +10,7 @@
10
10
  "ap-southeast-2",
11
11
  "ap-southeast-3",
12
12
  "ap-southeast-4",
13
+ "ap-southeast-5",
13
14
  "ca-central-1",
14
15
  "ca-west-1",
15
16
  "cn-north-1",
@@ -89,6 +89,11 @@ A {
89
89
  text-decoration: underline;
90
90
  color: var(--body-text);
91
91
  }
92
+
93
+ &:focus-visible {
94
+ @include focus-outline;
95
+ outline-offset: 2px;
96
+ }
92
97
  }
93
98
 
94
99
  HR {
@@ -147,8 +147,16 @@
147
147
  }
148
148
  }
149
149
 
150
+ // -------------------------------------------------------------------------------------------------
151
+ // Focus styles
152
+
150
153
  @mixin form-focus {
151
154
  // Focus for form like elements (not to be confused with basic :focus style)
152
155
  outline: none;
153
156
  border-color: var(--outline);
157
+ }
158
+
159
+ @mixin focus-outline {
160
+ // Focus for form like elements (not to be confused with basic :focus style)
161
+ outline: 2px solid var(--primary-keyboard-focus);
154
162
  }
@@ -77,6 +77,11 @@ button,
77
77
  background-color: var(--primary-hover-bg);
78
78
  color: var(--primary-text);
79
79
  }
80
+
81
+ &:focus-visible {
82
+ @include focus-outline;
83
+ outline-offset: 2px;
84
+ }
80
85
  }
81
86
 
82
87
  .role-secondary {
@@ -21,6 +21,7 @@
21
21
 
22
22
  // dark main text
23
23
  $lightest: #ffffff;
24
+ $keyboard-focus: #00ddff;
24
25
 
25
26
  // menu cluster description active+hover
26
27
  $desc-light: #EEEFF4;
@@ -45,6 +46,7 @@
45
46
  --default-light-bg : #{rgba($dark, 0.05)};
46
47
  --slider-light-bg : #{rgba($darker, 1)};
47
48
  --slider-light-bg-right : #{rgba($darker, 0)};
49
+ --primary-keyboard-focus : #{$keyboard-focus};
48
50
 
49
51
  --muted : #{$disabled};
50
52
 
@@ -26,6 +26,7 @@ $disabled : $medium;
26
26
  $primary : #3D98D3;
27
27
  $secondary : $darker;
28
28
  $link : #3D98D3;
29
+ $keyboard-focus : #3300ff;
29
30
 
30
31
  // Status colors
31
32
  $success : #5D995D;
@@ -53,6 +54,7 @@ BODY, .theme-light {
53
54
  --primary-border : #{$primary};
54
55
  --primary-banner-bg : #{rgba($primary, 0.15)};
55
56
  --primary-light-bg : #{rgba($primary, 0.05)};
57
+ --primary-keyboard-focus : #{$keyboard-focus};
56
58
 
57
59
 
58
60
  .text-primary {
@@ -120,6 +120,7 @@ locale:
120
120
  none: (None)
121
121
 
122
122
  nav:
123
+ expandCollapseAppBar: Expand/Collapse the Application Bar
123
124
  harvesterDashboard: Harvester Dashboard
124
125
  backToRancher: Cluster Manager
125
126
  tools: Tools
@@ -273,6 +274,9 @@ suffix:
273
274
  about:
274
275
  title: About
275
276
  versions:
277
+ downloadImages: "Download {listName} images list"
278
+ downloadCli: "Download CLI for {os}"
279
+ githubRepo: "Open GitHub repository for the {name} component"
276
280
  title: Versions
277
281
  component: Component
278
282
  version: Version
@@ -1079,14 +1083,17 @@ catalog:
1079
1083
  exponentialBackOff:
1080
1084
  label: Exponential Back Off
1081
1085
  minWait:
1082
- label: Min Wait Time (in Seconds)
1083
- placeholder: 1 (default)
1086
+ label: Min Wait Time
1087
+ placeholder: 'default: 1'
1084
1088
  maxWait:
1085
- label: Max Wait Time (in Seconds)
1086
- placeholder: 5 (default)
1089
+ label: Max Wait Time
1090
+ placeholder: 'default: 5'
1087
1091
  maxRetries:
1088
1092
  label: Max Number of Retries
1089
- placeholder: 5 (default)
1093
+ placeholder: 'default: 5'
1094
+ refreshInterval:
1095
+ label: Refresh Interval
1096
+ placeholder: 'default: {hours}'
1090
1097
  tools:
1091
1098
  header: Cluster Tools
1092
1099
  noTools: "No Cluster Tools found"
@@ -1480,16 +1487,16 @@ cluster:
1480
1487
  defaultLabel: Use default data directory configuration
1481
1488
  commonLabel: Use a common base directory for data directory configuration (sub-directories will be used for the system-agent, provisioning and distro paths)
1482
1489
  customLabel: Use custom data directories
1483
- common:
1490
+ common:
1484
1491
  label: Data directory base path
1485
1492
  tooltip: Data directory base path. We will append the sub-directories appropriate for each directory (/agent, /provisioning and either /rke2 or /k3s)
1486
- systemAgent:
1493
+ systemAgent:
1487
1494
  label: System-agent directory path
1488
1495
  tooltip: Data directory for the system-agent connection info and plans
1489
- provisioning:
1496
+ provisioning:
1490
1497
  label: Provisioning directory path
1491
1498
  tooltip: Data directory for provisioning related files
1492
- k8sDistro:
1499
+ k8sDistro:
1493
1500
  label: K8s Distro directory path
1494
1501
  tooltip: Data directory for the k8s distro
1495
1502
  machineConfig:
@@ -1921,7 +1928,7 @@ cluster:
1921
1928
  header: Registry for Rancher System Container Images
1922
1929
  label: Enable cluster scoped container registry for Rancher system container images
1923
1930
  description: "If enabled, Rancher will pull container images from this registry during cluster provisioning. By default, Rancher will also use this registry when installing Rancher's official Helm chart apps. If the cluster scoped registry is disabled, system images are pulled from the System Default Registry in the global settings."
1924
- docsLinkRke2: "For help configuring private registry mirrors, see the RKE2 <a href=\"https://docs.rke2.io/install/containerd_registry_configuration\" target=\"_blank\">documentation.</a>"
1931
+ docsLinkRke2: "For help configuring private registry mirrors, see the RKE2 <a href=\"https://docs.rke2.io/install/private_registry\" target=\"_blank\">documentation.</a>"
1925
1932
  docsLinkK3s: "For help configuring private registry mirrors, see the K3s <a href=\"https://docs.k3s.io/installation/private-registry\" target=\"_blank\">documentation.</a>"
1926
1933
  provider:
1927
1934
  aliyunecs: Aliyun ECS
@@ -2198,11 +2205,20 @@ clusterIndexPage:
2198
2205
  label: Unhealthy Nodes
2199
2206
  noRows: There are no unhealthy nodes to show.
2200
2207
  componentStatus:
2201
- etcd: Etcd
2202
- scheduler: Scheduler
2203
- controller-manager: Controller Manager
2204
- cattle: Cattle
2205
- fleet: Fleet
2208
+ component:
2209
+ etcd:
2210
+ label: Etcd
2211
+ scheduler:
2212
+ label: Scheduler
2213
+ controller-manager:
2214
+ label: Controller Manager
2215
+ cattle:
2216
+ label: Cattle
2217
+ fleet:
2218
+ label: Fleet
2219
+ tooltip:
2220
+ disconnected: Agent disconnected
2221
+ unavailableReplicas: Replicas not available
2206
2222
  certs:
2207
2223
  label: Certificates
2208
2224
 
@@ -5300,7 +5316,6 @@ setup:
5300
5316
  skip: Skip
5301
5317
  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
5318
  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
5319
  useManual: Set a specific password to use
5305
5320
  useRandom: Use a randomly generated password
5306
5321
  welcome: Welcome to {vendor}!
@@ -6139,6 +6154,14 @@ wm:
6139
6154
  clear: Clear
6140
6155
  containerName: "Container: {label}"
6141
6156
  failed: "Unable to open a shell to the container (none of the shell commmands succeeded)\n\r"
6157
+ logLevel:
6158
+ info: INFO
6159
+ error: ERROR
6160
+ warning: WARN
6161
+ debug: DEBUG
6162
+ logMessage:
6163
+ containerError: "{ logLevel }: Container missing shell executable (/bin/sh)"
6164
+
6142
6165
  kubectlShell:
6143
6166
  title: "Kubectl: {name}"
6144
6167
 
@@ -6544,7 +6567,7 @@ workload:
6544
6567
  managed: Managed
6545
6568
  shared: Shared
6546
6569
  drivers:
6547
- driver.longhorn.io: Longhorn
6570
+ driver-longhorn-io: Longhorn
6548
6571
  fsType: Filesystem Type
6549
6572
  shareName: Share Name
6550
6573
  secretName: Secret Name
@@ -7415,7 +7438,6 @@ advancedSettings:
7415
7438
  'ui-dashboard-index': 'HTML index location for the {appName} UI.'
7416
7439
  '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
7440
  'ui-pl': 'Private-Label company name.'
7418
- 'telemetry-opt': 'Telemetry reporting opt-in.'
7419
7441
  '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
7442
  'auth-user-info-resync-cron': 'Default cron schedule for resyncing auth provider group memberships.'
7421
7443
  'cluster-template-enforcement': 'Non-admins will be restricted to launching clusters via preapproved RKE Templates only.'
@@ -7440,10 +7462,6 @@ advancedSettings:
7440
7462
  'ui-default-landing':
7441
7463
  ember: Cluster Manager
7442
7464
  vue: Cluster Explorer
7443
- 'telemetry-opt':
7444
- prompt: Prompt
7445
- in: Opt-in to Telemetry
7446
- out: Opt-out of Telemetry
7447
7465
  'ui-offline-preferred':
7448
7466
  dynamic: Dynamic
7449
7467
  true: Local
@@ -1751,7 +1751,7 @@ cluster:
1751
1751
  privateRegistry:
1752
1752
  label: 为 Rancher 系统容器镜像启用集群级别的容器镜像仓库
1753
1753
  description: "如果启用,Rancher 将在集群配置期间从该镜像仓库中拉取容器镜像。默认情况下,Rancher 在安装 Rancher 的官方 Helm Chart 应用程序时也会使用此镜像仓库。如果集群级别的镜像仓库被禁用,将从全局设置中的系统默认镜像仓库中拉取系统镜像。"
1754
- docsLinkRke2: "如需配置私有镜像仓库 mirror 的帮助,请参阅 RKE2 <a href=\"https://docs.rke2.io/install/containerd_registry_configuration\" target=\"_blank\">文档</a>。"
1754
+ docsLinkRke2: "如需配置私有镜像仓库 mirror 的帮助,请参阅 RKE2 <a href=\"https://docs.rke2.io/install/private_registry\" target=\"_blank\">文档</a>。"
1755
1755
  docsLinkK3s: "如需配置私有镜像仓库 mirror 的帮助,请参阅 K3s <a href=\"https://docs.k3s.io/installation/private-registry\" target=\"_blank\">文档</a>。"
1756
1756
  provider:
1757
1757
  aliyunecs: Aliyun ECS
@@ -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: 本地
@@ -44,6 +44,6 @@ export default {
44
44
  :options="options"
45
45
  :push-tags="true"
46
46
  :taggable="true"
47
- @input="updateName"
47
+ @update:value="updateName"
48
48
  />
49
49
  </template>
@@ -132,6 +132,7 @@ export default {
132
132
  <form>
133
133
  <LabeledSelect
134
134
  v-model:value="moveTo"
135
+ data-testid="workspace_options"
135
136
  :label="t('assignTo.workspace')"
136
137
  :options="workspaceOptions"
137
138
  placement="bottom"
@@ -288,6 +288,7 @@ export default defineComponent({
288
288
  v-if="displayIcon"
289
289
  v-clean-tooltip="tooltip"
290
290
  :class="{icon: true, 'icon-lg': true, [displayIcon]: true}"
291
+ class="ml-5 mr-0"
291
292
  />
292
293
  <span
293
294
  v-if="labelAs === 'text' && displayLabel"
@@ -13,6 +13,8 @@ export default {
13
13
  v-if="link && link.name"
14
14
  :to="link"
15
15
  class="back-link"
16
+ role="link"
17
+ :aria-label="t('generic.back')"
16
18
  >
17
19
  <i class="icon icon-chevron-left" /> {{ t('generic.back') }}
18
20
  </router-link>
@@ -20,6 +22,7 @@ export default {
20
22
  v-else
21
23
  to="/"
22
24
  class="back-link"
25
+ :aria-label="t('nav.home')"
23
26
  >
24
27
  <i class="icon icon-chevron-left" /> {{ t('nav.home') }}
25
28
  </router-link>
@@ -30,9 +33,12 @@ export default {
30
33
  align-items: center;
31
34
  display: flex;
32
35
  font-size: 16px;
33
- margin-bottom: 10px;
36
+ margin: 10px 0 20px 0;
34
37
  outline: 0;
35
- padding: 10px 0;
36
38
  width: fit-content;
39
+
40
+ &:focus-visible {
41
+ @include focus-outline;
42
+ }
37
43
  }
38
44
  </style>
@@ -0,0 +1,135 @@
1
+ <script lang="ts">
2
+ import { defineComponent } from 'vue';
3
+ import ResourceFetch from '@shell/mixins/resource-fetch';
4
+ import ResourceTable from '@shell/components/ResourceTable.vue';
5
+ import { StorePaginationResult } from '@shell/types/store/pagination.types';
6
+
7
+ export type FetchSecondaryResourcesOpts = { canPaginate: boolean }
8
+ export type FetchSecondaryResources = (opts: FetchSecondaryResourcesOpts) => Promise<any>
9
+
10
+ export type FetchPageSecondaryResourcesOpts = { canPaginate: boolean, force: boolean, page: any[], pagResult: StorePaginationResult }
11
+ export type FetchPageSecondaryResources = (opts: FetchPageSecondaryResourcesOpts) => Promise<any>
12
+
13
+ /**
14
+ * This is meant to enable ResourceList like capabilities outside of List pages / components
15
+ *
16
+ * Specifically
17
+ * - Resource Fetch features, including server-side pagination
18
+ * - Some plumbing
19
+ *
20
+ * This avoids polluting the owning component with mixins
21
+ *
22
+ */
23
+ export default defineComponent({
24
+ name: 'PaginatedResourceTable',
25
+
26
+ components: { ResourceTable },
27
+
28
+ mixins: [ResourceFetch],
29
+
30
+ props: {
31
+ schema: {
32
+ type: Object,
33
+ required: true,
34
+ },
35
+
36
+ headers: {
37
+ type: Array,
38
+ default: null,
39
+ },
40
+
41
+ paginationHeaders: {
42
+ type: Array,
43
+ default: null,
44
+ },
45
+
46
+ groupable: {
47
+ type: Boolean,
48
+ default: null, // Null: auto based on namespaced and type custom groupings
49
+ },
50
+
51
+ namespaced: {
52
+ type: Boolean,
53
+ default: null, // Automatic from schema
54
+ },
55
+
56
+ /**
57
+ * Information may be required from resources other than the primary one shown per row
58
+ *
59
+ * This will fetch them ALL and will be run in a non-server-side pagination world
60
+ */
61
+ fetchSecondaryResources: {
62
+ type: Function,
63
+ default: null,
64
+ },
65
+
66
+ /**
67
+ * Information may be required from resources other than the primary one shown per row
68
+ *
69
+ * This will fetch only those relevant to the current page using server-side pagination based filters
70
+ *
71
+ * called from shell/mixins/resource-fetch-api-pagination.js
72
+ */
73
+ fetchPageSecondaryResources: {
74
+ type: Function,
75
+ default: null,
76
+ }
77
+ },
78
+
79
+ data() {
80
+ return { resource: this.schema.id };
81
+ },
82
+
83
+ async fetch() {
84
+ const promises = [
85
+ this.$fetchType(this.resource, [], this.inStore),
86
+ ];
87
+
88
+ if (this.fetchSecondaryResources) {
89
+ promises.push(this.fetchSecondaryResources({ canPaginate: this.canPaginate }));
90
+ }
91
+
92
+ await Promise.all(promises);
93
+ },
94
+
95
+ computed: {
96
+ safeHeaders() {
97
+ const customHeaders = this.canPaginate ? this.paginationHeaders : this.headers;
98
+
99
+ return customHeaders || this.$store.getters['type-map/headersFor'](this.schema, this.canPaginate);
100
+ }
101
+ }
102
+ });
103
+
104
+ </script>
105
+
106
+ <template>
107
+ <div>
108
+ <ResourceTable
109
+ v-bind="$attrs"
110
+ :schema="schema"
111
+ :rows="rows"
112
+ :alt-loading="canPaginate"
113
+ :loading="loading"
114
+ :groupable="groupable"
115
+
116
+ :headers="safeHeaders"
117
+ :namespaced="namespaced"
118
+
119
+ :external-pagination-enabled="canPaginate"
120
+ :external-pagination-result="paginationResult"
121
+ @pagination-changed="paginationChanged"
122
+ >
123
+ <!-- Pass down templates provided by the caller -->
124
+ <template
125
+ v-for="(_, slot) of $slots"
126
+ v-slot:[slot]="scope"
127
+ >
128
+ <slot
129
+ :name="slot"
130
+ v-bind="scope"
131
+ />
132
+ </template>
133
+ </ResourceTable>
134
+ </div>
135
+ </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
@@ -96,7 +96,6 @@ export default {
96
96
  const showMasthead = getters[`type-map/optionsFor`](resource).showListMasthead;
97
97
 
98
98
  return {
99
- inStore,
100
99
  schema,
101
100
  hasListComponent,
102
101
  showMasthead: showMasthead === undefined ? true : showMasthead,
@@ -429,12 +429,17 @@ export default {
429
429
  },
430
430
 
431
431
  computedGroupBy() {
432
+ // If we're not showing grouping options we shouldn't have a group by property
433
+ if (!this.showGrouping) {
434
+ return null;
435
+ }
436
+
432
437
  if ( this.groupBy ) {
433
438
  // This probably comes from the type-map config for the resource (see ResourceList)
434
439
  return this.groupBy;
435
440
  }
436
441
 
437
- if ( this.group === 'namespace' && this.showGrouping ) {
442
+ if ( this.group === 'namespace' ) {
438
443
  // This switches to group rows by a key which is the label for the group (??)
439
444
  return 'groupByLabel';
440
445
  }