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

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 (200) hide show
  1. package/assets/images/icons/document.svg +3 -0
  2. package/assets/images/vendor/cognito.svg +1 -0
  3. package/assets/styles/app.scss +1 -0
  4. package/assets/styles/base/_basic.scss +10 -0
  5. package/assets/styles/base/_spacing.scss +29 -0
  6. package/assets/styles/global/_layout.scss +1 -1
  7. package/assets/styles/themes/_dark.scss +25 -0
  8. package/assets/styles/themes/_light.scss +65 -0
  9. package/assets/translations/en-us.yaml +322 -24
  10. package/assets/translations/zh-hans.yaml +8 -5
  11. package/components/Certificates.vue +5 -0
  12. package/components/FilterPanel.vue +156 -0
  13. package/components/{fleet/ForceDirectedTreeChart/index.vue → ForceDirectedTreeChart.vue} +47 -41
  14. package/components/IconOrSvg.vue +14 -35
  15. package/components/PromptRemove.vue +5 -1
  16. package/components/Resource/Detail/Card/PodsCard/Bubble.vue +13 -0
  17. package/components/Resource/Detail/Card/PodsCard/composable.ts +30 -0
  18. package/components/Resource/Detail/Card/PodsCard/index.vue +118 -0
  19. package/components/Resource/Detail/Card/ResourceUsageCard/composable.ts +51 -0
  20. package/components/Resource/Detail/Card/ResourceUsageCard/index.vue +79 -0
  21. package/components/Resource/Detail/Card/Scaler.vue +89 -0
  22. package/components/Resource/Detail/Card/StateCard/composables.ts +112 -0
  23. package/components/Resource/Detail/Card/StateCard/index.vue +39 -0
  24. package/components/Resource/Detail/Card/VerticalGap.vue +11 -0
  25. package/components/Resource/Detail/Card/__tests__/Card.test.ts +36 -0
  26. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +84 -0
  27. package/components/Resource/Detail/Card/__tests__/ResourceUsageCard.test.ts +72 -0
  28. package/components/Resource/Detail/Card/__tests__/Scaler.test.ts +87 -0
  29. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +53 -0
  30. package/components/Resource/Detail/Card/__tests__/VerticalGap.test.ts +14 -0
  31. package/components/Resource/Detail/Card/__tests__/index.test.ts +36 -0
  32. package/components/Resource/Detail/Card/index.vue +56 -0
  33. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +19 -0
  34. package/components/Resource/Detail/Metadata/Annotations/composable.ts +12 -0
  35. package/components/Resource/Detail/Metadata/Annotations/index.vue +26 -0
  36. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +103 -0
  37. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +281 -0
  38. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +111 -0
  39. package/components/Resource/Detail/Metadata/KeyValue.vue +130 -0
  40. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +18 -0
  41. package/components/Resource/Detail/Metadata/Labels/composable.ts +12 -0
  42. package/components/Resource/Detail/Metadata/Labels/index.vue +27 -0
  43. package/components/Resource/Detail/Metadata/Rectangle.vue +32 -0
  44. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +107 -0
  45. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +24 -0
  46. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +91 -0
  47. package/components/Resource/Detail/Metadata/composables.ts +29 -0
  48. package/components/Resource/Detail/Metadata/index.vue +66 -0
  49. package/components/Resource/Detail/Page.vue +22 -0
  50. package/components/Resource/Detail/PercentageBar.vue +40 -0
  51. package/components/Resource/Detail/ResourceRow.vue +119 -0
  52. package/components/Resource/Detail/SpacedRow.vue +14 -0
  53. package/components/Resource/Detail/StatusBar.vue +59 -0
  54. package/components/Resource/Detail/StatusRow.vue +61 -0
  55. package/components/Resource/Detail/TitleBar/Title.vue +13 -0
  56. package/components/Resource/Detail/TitleBar/Top.vue +14 -0
  57. package/components/Resource/Detail/TitleBar/__tests__/Title.test.ts +17 -0
  58. package/components/Resource/Detail/TitleBar/__tests__/Top.test.ts +17 -0
  59. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +142 -0
  60. package/components/Resource/Detail/TitleBar/composable.ts +31 -0
  61. package/components/Resource/Detail/TitleBar/index.vue +124 -0
  62. package/components/Resource/Detail/Top/index.vue +34 -0
  63. package/components/Resource/Detail/__tests__/Page.test.ts +32 -0
  64. package/components/ResourceDetail/__tests__/index.test.ts +114 -0
  65. package/components/ResourceDetail/index.vue +64 -562
  66. package/components/ResourceDetail/legacy.vue +545 -0
  67. package/components/ResourceTable.vue +41 -7
  68. package/components/SlideInPanelManager.vue +76 -8
  69. package/components/SortableTable/index.vue +13 -2
  70. package/components/SortableTable/selection.js +21 -8
  71. package/components/StatusBadge.vue +6 -4
  72. package/components/SubtleLink.vue +25 -0
  73. package/components/Wizard.vue +12 -1
  74. package/components/YamlEditor.vue +1 -1
  75. package/components/__tests__/FilterPanel.test.ts +81 -0
  76. package/components/auth/AuthBanner.vue +2 -3
  77. package/components/auth/RoleDetailEdit.vue +45 -3
  78. package/components/auth/login/oidc.vue +6 -1
  79. package/components/fleet/FleetApplications.vue +181 -0
  80. package/components/fleet/FleetHelmOps.vue +115 -0
  81. package/components/fleet/FleetIntro.vue +58 -28
  82. package/components/fleet/FleetNoWorkspaces.vue +5 -1
  83. package/components/fleet/FleetOCIStorageSecret.vue +171 -0
  84. package/components/fleet/FleetRepos.vue +38 -76
  85. package/components/fleet/FleetResources.vue +50 -22
  86. package/components/fleet/FleetSummary.vue +26 -51
  87. package/components/fleet/__tests__/FleetOCIStorageSecret.test.ts +213 -0
  88. package/components/fleet/__tests__/FleetSummary.test.ts +39 -39
  89. package/components/fleet/dashboard/Empty.vue +73 -0
  90. package/components/fleet/dashboard/ResourceCard.vue +183 -0
  91. package/components/fleet/dashboard/ResourceCardSummary.vue +199 -0
  92. package/components/fleet/dashboard/ResourceDetails.vue +196 -0
  93. package/components/fleet/dashboard/ResourcePanel.vue +376 -0
  94. package/components/form/ArrayList.vue +6 -0
  95. package/components/form/SimpleSecretSelector.vue +8 -2
  96. package/components/form/ValueFromResource.vue +31 -19
  97. package/components/formatter/FleetApplicationClustersReady.vue +77 -0
  98. package/components/formatter/FleetApplicationSource.vue +71 -0
  99. package/components/formatter/FleetSummaryGraph.vue +7 -0
  100. package/components/nav/Header.vue +8 -7
  101. package/components/nav/TopLevelMenu.helper.ts +55 -34
  102. package/components/nav/TopLevelMenu.vue +11 -0
  103. package/components/nav/Type.vue +4 -1
  104. package/composables/useI18n.ts +12 -11
  105. package/config/labels-annotations.js +14 -11
  106. package/config/product/auth.js +1 -0
  107. package/config/product/fleet.js +70 -17
  108. package/config/query-params.js +3 -1
  109. package/config/roles.ts +1 -0
  110. package/config/router/routes.js +20 -2
  111. package/config/secret.ts +15 -0
  112. package/config/settings.ts +3 -2
  113. package/config/table-headers.js +52 -22
  114. package/config/types.js +2 -0
  115. package/core/plugin-helpers.ts +3 -2
  116. package/detail/fleet.cattle.io.cluster.vue +28 -15
  117. package/detail/fleet.cattle.io.gitrepo.vue +10 -1
  118. package/detail/fleet.cattle.io.helmop.vue +157 -0
  119. package/dialog/HelmOpForceUpdateDialog.vue +132 -0
  120. package/dialog/RedeployWorkloadDialog.vue +164 -0
  121. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +56 -67
  122. package/edit/auth/oidc.vue +159 -93
  123. package/edit/fleet.cattle.io.gitrepo.vue +26 -33
  124. package/edit/fleet.cattle.io.helmop.vue +997 -0
  125. package/edit/management.cattle.io.fleetworkspace.vue +43 -10
  126. package/list/fleet.cattle.io.gitrepo.vue +1 -1
  127. package/list/fleet.cattle.io.helmop.vue +108 -0
  128. package/list/namespace.vue +5 -2
  129. package/mixins/auth-config.js +8 -1
  130. package/mixins/preset.js +100 -0
  131. package/mixins/resource-fetch-api-pagination.js +2 -0
  132. package/mixins/resource-fetch.js +1 -1
  133. package/mixins/resource-table-watch.js +45 -0
  134. package/models/__tests__/chart.test.ts +273 -0
  135. package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  136. package/models/chart.js +144 -2
  137. package/models/fleet-application.js +385 -0
  138. package/models/fleet.cattle.io.bundle.js +9 -8
  139. package/models/fleet.cattle.io.gitrepo.js +41 -365
  140. package/models/fleet.cattle.io.helmop.js +228 -0
  141. package/models/management.cattle.io.authconfig.js +1 -0
  142. package/models/management.cattle.io.fleetworkspace.js +12 -0
  143. package/models/workload.js +14 -18
  144. package/package.json +2 -1
  145. package/pages/auth/verify.vue +13 -1
  146. package/pages/c/_cluster/apps/charts/AddRepoLink.vue +37 -0
  147. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +80 -0
  148. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +54 -0
  149. package/pages/c/_cluster/apps/charts/StatusLabel.vue +33 -0
  150. package/pages/c/_cluster/apps/charts/index.vue +302 -484
  151. package/pages/c/_cluster/explorer/EventsTable.vue +1 -1
  152. package/pages/c/_cluster/fleet/__tests__/index.test.ts +426 -0
  153. package/pages/c/_cluster/fleet/application/_resource/_id.vue +14 -0
  154. package/pages/c/_cluster/fleet/application/_resource/create.vue +14 -0
  155. package/pages/c/_cluster/fleet/application/create.vue +340 -0
  156. package/pages/c/_cluster/fleet/application/index.vue +139 -0
  157. package/pages/c/_cluster/fleet/graph/config.js +277 -0
  158. package/pages/c/_cluster/fleet/index.vue +772 -330
  159. package/pages/explorer/resource/detail/configmap.vue +19 -0
  160. package/plugins/dashboard-store/actions.js +31 -9
  161. package/plugins/dashboard-store/getters.js +34 -21
  162. package/plugins/dashboard-store/mutations.js +51 -7
  163. package/plugins/dashboard-store/resource-class.js +14 -2
  164. package/plugins/steve/__tests__/subscribe.spec.ts +66 -1
  165. package/plugins/steve/actions.js +3 -0
  166. package/plugins/steve/steve-pagination-utils.ts +14 -13
  167. package/plugins/steve/subscribe.js +229 -42
  168. package/rancher-components/BadgeState/BadgeState.vue +3 -1
  169. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -2
  170. package/rancher-components/RcItemCard/RcItemCard.test.ts +189 -0
  171. package/rancher-components/RcItemCard/RcItemCard.vue +425 -0
  172. package/rancher-components/RcItemCard/RcItemCardAction.vue +24 -0
  173. package/rancher-components/RcItemCard/index.ts +2 -0
  174. package/store/auth.js +1 -0
  175. package/store/catalog.js +62 -24
  176. package/store/index.js +33 -14
  177. package/store/slideInPanel.ts +6 -0
  178. package/store/type-map.js +1 -0
  179. package/types/fleet.d.ts +35 -0
  180. package/types/resources/settings.d.ts +19 -1
  181. package/types/shell/index.d.ts +339 -272
  182. package/types/store/dashboard-store.types.ts +17 -3
  183. package/types/store/pagination.types.ts +6 -1
  184. package/types/store/subscribe.types.ts +50 -0
  185. package/utils/auth.js +32 -3
  186. package/utils/fleet-types.ts +0 -0
  187. package/utils/fleet.ts +200 -1
  188. package/utils/pagination-utils.ts +26 -1
  189. package/utils/pagination-wrapper.ts +132 -50
  190. package/utils/settings.ts +4 -1
  191. package/utils/style.ts +39 -0
  192. package/utils/validators/formRules/__tests__/index.test.ts +36 -3
  193. package/utils/validators/formRules/index.ts +10 -3
  194. package/utils/window.js +11 -7
  195. package/components/__tests__/ApplicationCard.test.ts +0 -27
  196. package/components/cards/ApplicationCard.vue +0 -145
  197. package/components/fleet/ForceDirectedTreeChart/chartIcons.js +0 -17
  198. package/config/secret.js +0 -14
  199. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +0 -249
  200. /package/{components/form/SSHKnownHosts → dialog}/__tests__/KnownHostsEditDialog.test.ts +0 -0
@@ -0,0 +1,545 @@
1
+ <script>
2
+ import CreateEditView from '@shell/mixins/create-edit-view/impl';
3
+ import Loading from '@shell/components/Loading';
4
+ import ResourceYaml from '@shell/components/ResourceYaml';
5
+ import {
6
+ _VIEW, _EDIT, _CLONE, _IMPORT, _STAGE, _CREATE,
7
+ AS, _YAML, _DETAIL, _CONFIG, _GRAPH, PREVIEW, MODE,
8
+ } from '@shell/config/query-params';
9
+ import { SCHEMA } from '@shell/config/types';
10
+ import { createYaml } from '@shell/utils/create-yaml';
11
+ import Masthead from '@shell/components/ResourceDetail/Masthead';
12
+ import DetailTop from '@shell/components/DetailTop';
13
+ import { clone, diff } from '@shell/utils/object';
14
+ import IconMessage from '@shell/components/IconMessage';
15
+ import ForceDirectedTreeChart from '@shell/components/ForceDirectedTreeChart';
16
+ import { stringify } from '@shell/utils/error';
17
+ import { Banner } from '@components/Banner';
18
+
19
+ function modeFor(route) {
20
+ if ( route.query?.mode === _IMPORT ) {
21
+ return _IMPORT;
22
+ }
23
+
24
+ if ( route.params?.id ) {
25
+ return route.query.mode || _VIEW;
26
+ } else {
27
+ return _CREATE;
28
+ }
29
+ }
30
+
31
+ async function getYaml(store, model) {
32
+ let yaml;
33
+ const opt = { headers: { accept: 'application/yaml' } };
34
+
35
+ if ( model.hasLink('view') ) {
36
+ yaml = (await model.followLink('view', opt)).data;
37
+ }
38
+
39
+ return model.cleanForDownload(yaml);
40
+ }
41
+
42
+ export default {
43
+ emits: ['input'],
44
+
45
+ components: {
46
+ Loading,
47
+ DetailTop,
48
+ ForceDirectedTreeChart,
49
+ ResourceYaml,
50
+ Masthead,
51
+ IconMessage,
52
+ Banner
53
+ },
54
+
55
+ mixins: [CreateEditView],
56
+
57
+ props: {
58
+ storeOverride: {
59
+ type: String,
60
+ default: null,
61
+ },
62
+
63
+ resourceOverride: {
64
+ type: String,
65
+ default: null,
66
+ },
67
+
68
+ parentRouteOverride: {
69
+ type: String,
70
+ default: null,
71
+ },
72
+
73
+ flexContent: {
74
+ type: Boolean,
75
+ default: false,
76
+ },
77
+
78
+ /**
79
+ * Inherited global identifier prefix for tests
80
+ * Define a term based on the parent component to avoid conflicts on multiple components
81
+ */
82
+ componentTestid: {
83
+ type: String,
84
+ default: 'resource-details'
85
+ },
86
+ errorsMap: {
87
+ type: Object,
88
+ default: null
89
+ },
90
+ },
91
+
92
+ async fetch() {
93
+ const store = this.$store;
94
+ const route = this.$route;
95
+ const params = route.params;
96
+ let resourceType = this.resourceOverride || params.resource;
97
+
98
+ const inStore = this.storeOverride || store.getters['currentStore'](resourceType);
99
+ const realMode = this.realMode;
100
+
101
+ // eslint-disable-next-line prefer-const
102
+ let { namespace, id } = params;
103
+
104
+ // There are 6 "real" modes that can be put into the query string
105
+ // These are mapped down to the 3 regular page "mode"s that create-edit-view components
106
+ // know about: view, edit, create (stage, import and clone become "create")
107
+ const mode = ([_CLONE, _IMPORT, _STAGE].includes(realMode) ? _CREATE : realMode);
108
+
109
+ const getGraphConfig = store.getters['type-map/hasGraph'](resourceType);
110
+ const hasGraph = !!getGraphConfig;
111
+ const hasCustomDetail = store.getters['type-map/hasCustomDetail'](resourceType, id);
112
+ const hasCustomEdit = store.getters['type-map/hasCustomEdit'](resourceType, id);
113
+
114
+ const schemas = store.getters[`${ inStore }/all`](SCHEMA);
115
+
116
+ // As determines what component will be rendered
117
+ const requested = route.query[AS];
118
+ let as;
119
+ let notFound = false;
120
+
121
+ if ( mode === _VIEW && hasCustomDetail && (!requested || requested === _DETAIL) ) {
122
+ as = _DETAIL;
123
+ } else if ( mode === _VIEW && hasGraph && requested === _GRAPH) {
124
+ as = _GRAPH;
125
+ } else if ( hasCustomEdit && (!requested || requested === _CONFIG) ) {
126
+ as = _CONFIG;
127
+ } else {
128
+ as = _YAML;
129
+ }
130
+
131
+ this.as = as;
132
+
133
+ const options = store.getters[`type-map/optionsFor`](resourceType);
134
+
135
+ this.showMasthead = [_CREATE, _EDIT].includes(mode) ? options.resourceEditMasthead : true;
136
+ const canViewYaml = options.canYaml;
137
+
138
+ if ( options.resource ) {
139
+ resourceType = options.resource;
140
+ }
141
+
142
+ const schema = store.getters[`${ inStore }/schemaFor`](resourceType);
143
+ let model, initialModel, liveModel, yaml;
144
+
145
+ if ( realMode === _CREATE || realMode === _IMPORT ) {
146
+ if ( !namespace ) {
147
+ namespace = store.getters['defaultNamespace'];
148
+ }
149
+
150
+ const data = { type: resourceType };
151
+
152
+ if ( schema?.attributes?.namespaced ) {
153
+ data.metadata = { namespace };
154
+ }
155
+
156
+ liveModel = await store.dispatch(`${ inStore }/create`, data);
157
+ initialModel = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
158
+ model = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
159
+
160
+ if (model.forceYaml === true) {
161
+ as = _YAML;
162
+ this.as = as;
163
+ }
164
+
165
+ if ( as === _YAML ) {
166
+ if (schema?.fetchResourceFields) {
167
+ // fetch resourceFields for createYaml
168
+ await schema.fetchResourceFields();
169
+ }
170
+
171
+ yaml = createYaml(schemas, resourceType, data);
172
+ }
173
+ } else {
174
+ let fqid = id;
175
+
176
+ if ( schema.attributes?.namespaced && namespace ) {
177
+ fqid = `${ namespace }/${ fqid }`;
178
+ }
179
+
180
+ try {
181
+ liveModel = await store.dispatch(`${ inStore }/find`, {
182
+ type: resourceType,
183
+ id: fqid,
184
+ opt: { watch: true }
185
+ });
186
+ } catch (e) {
187
+ if (e.status === 404 || e.status === 403) {
188
+ store.dispatch('loadingError', new Error(this.t('nav.failWhale.resourceIdNotFound', { resource: resourceType, fqid }, true)));
189
+ }
190
+ liveModel = {};
191
+ notFound = fqid;
192
+ }
193
+
194
+ try {
195
+ if (realMode === _VIEW) {
196
+ model = liveModel;
197
+ } else {
198
+ model = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
199
+ }
200
+ initialModel = await store.dispatch(`${ inStore }/clone`, { resource: liveModel });
201
+
202
+ if ( as === _YAML ) {
203
+ yaml = await getYaml(this.$store, liveModel);
204
+ }
205
+ } catch (e) {
206
+ this.errors.push(e);
207
+ }
208
+ if ( as === _YAML ) {
209
+ try {
210
+ yaml = await getYaml(this.$store, liveModel);
211
+ } catch (e) {
212
+ this.errors.push(e);
213
+ }
214
+ }
215
+
216
+ if ( as === _GRAPH ) {
217
+ this.chartData = liveModel;
218
+ }
219
+
220
+ if ( [_CLONE, _IMPORT, _STAGE].includes(realMode) ) {
221
+ model.cleanForNew();
222
+ yaml = model.cleanYaml(yaml, realMode);
223
+ }
224
+ }
225
+
226
+ // Ensure common properties exists
227
+ try {
228
+ model = await store.dispatch(`${ inStore }/cleanForDetail`, model);
229
+ } catch (e) {
230
+ this.errors.push(e);
231
+ }
232
+
233
+ const out = {
234
+ hasGraph,
235
+ getGraphConfig,
236
+ hasCustomDetail,
237
+ hasCustomEdit,
238
+ canViewYaml,
239
+ resourceType,
240
+ as,
241
+ yaml,
242
+ initialModel,
243
+ liveModel,
244
+ mode,
245
+ value: model,
246
+ notFound,
247
+ };
248
+
249
+ for ( const key in out ) {
250
+ this[key] = out[key];
251
+ }
252
+
253
+ if ( this.mode === _CREATE ) {
254
+ this.value.applyDefaults(this, realMode);
255
+ }
256
+ },
257
+ data() {
258
+ return {
259
+ chartData: null,
260
+ resourceSubtype: null,
261
+
262
+ // Set by fetch
263
+ hasGraph: null,
264
+ hasCustomDetail: null,
265
+ hasCustomEdit: null,
266
+ resourceType: null,
267
+ asYaml: null,
268
+ yaml: null,
269
+ liveModel: null,
270
+ initialModel: null,
271
+ mode: null,
272
+ as: null,
273
+ value: null,
274
+ model: null,
275
+ notFound: null,
276
+ canViewYaml: null,
277
+ errors: []
278
+ };
279
+ },
280
+
281
+ computed: {
282
+ realMode() {
283
+ // There are 5 "real" modes that you can start in: view, edit, create, stage, clone
284
+ const realMode = modeFor(this.$route);
285
+
286
+ return realMode;
287
+ },
288
+
289
+ isView() {
290
+ return this.mode === _VIEW;
291
+ },
292
+
293
+ isYaml() {
294
+ return this.as === _YAML;
295
+ },
296
+
297
+ isDetail() {
298
+ return this.as === _DETAIL;
299
+ },
300
+
301
+ isGraph() {
302
+ return this.as === _GRAPH;
303
+ },
304
+
305
+ offerPreview() {
306
+ return this.as === _YAML && [_EDIT, _CLONE, _IMPORT, _STAGE].includes(this.mode);
307
+ },
308
+
309
+ showComponent() {
310
+ switch ( this.as ) {
311
+ case _DETAIL: return this.detailComponent;
312
+ case _CONFIG: return this.editComponent;
313
+ }
314
+
315
+ return null;
316
+ },
317
+ hasErrors() {
318
+ return this.errors?.length && Array.isArray(this.errors);
319
+ },
320
+ mappedErrors() {
321
+ return !this.errors ? {} : this.errorsMap || this.errors.reduce((acc, error) => ({
322
+ ...acc,
323
+ [error]: {
324
+ message: error?.data?.message || error,
325
+ icon: null
326
+ }
327
+ }), {});
328
+ },
329
+ },
330
+
331
+ watch: {
332
+ '$route'(current, prev) {
333
+ if (current.name !== prev.name) {
334
+ return;
335
+ }
336
+ const neu = clone(current.query);
337
+ const old = clone(prev.query);
338
+
339
+ delete neu[PREVIEW];
340
+ delete old[PREVIEW];
341
+
342
+ if ( !this.isView ) {
343
+ delete neu[AS];
344
+ delete old[AS];
345
+ }
346
+
347
+ const queryDiff = Object.keys(diff(neu, old));
348
+
349
+ if (queryDiff.includes(MODE) || queryDiff.includes(AS)) {
350
+ this.$fetch();
351
+ }
352
+ },
353
+
354
+ // Auto refresh YAML when the model changes
355
+ async 'value.metadata.resourceVersion'(a, b) {
356
+ if ( this.mode === _VIEW && this.as === _YAML && a && b && a !== b) {
357
+ this.yaml = await getYaml(this.$store, this.liveModel);
358
+ }
359
+ }
360
+ },
361
+
362
+ created() {
363
+ this.configureResource();
364
+ },
365
+
366
+ methods: {
367
+ stringify,
368
+ setSubtype(subtype) {
369
+ this.resourceSubtype = subtype;
370
+ },
371
+
372
+ keyAction(act) {
373
+ const m = this.liveModel;
374
+
375
+ if ( m?.[act] ) {
376
+ m[act]();
377
+ }
378
+ },
379
+ closeError(index) {
380
+ this.errors = this.errors.filter((_, i) => i !== index);
381
+ },
382
+ /**
383
+ * Initializes the resource components based on the provided user and
384
+ * resource override.
385
+ *
386
+ * Configures the detail and edit components for a resource based on the
387
+ * user's ID and the specified resource.
388
+ *
389
+ * @param {Object} user - The user object containing user-specific
390
+ * information.
391
+ * @param {string|null} resourceOverride - An optional resource override
392
+ * string. If not provided, the method will use the default resource from
393
+ * the route parameters or the instance's resourceOverride property.
394
+ */
395
+ configureResource(userId = '', resourceOverride = null) {
396
+ const id = userId || this.$route.params.id;
397
+ const resource = resourceOverride || this.resourceOverride || this.$route.params.resource;
398
+ const options = this.$store.getters[`type-map/optionsFor`](resource);
399
+
400
+ const detailResource = options.resourceDetail || options.resource || resource;
401
+ const editResource = options.resourceEdit || options.resource || resource;
402
+
403
+ // FIXME: These aren't right... signature is (rawType, subType).. not (rawType, resourceId)
404
+ // Remove id? How does subtype get in (cluster/node)
405
+ this.detailComponent = this.$store.getters['type-map/importDetail'](detailResource, id);
406
+ this.editComponent = this.$store.getters['type-map/importEdit'](editResource, id);
407
+ },
408
+ /**
409
+ * Sets the mode and initializes the resource components.
410
+ *
411
+ * This method sets the mode of the component and configures the resource
412
+ * components based on the provided user and resource.
413
+ *
414
+ * @param {Object} payload - An object containing the mode, user, and
415
+ * resource properties.
416
+ * @param {string} payload.mode - The mode to set.
417
+ * @param {Object} payload.user - The user object containing user-specific
418
+ * information.
419
+ * @param {string} payload.resource - The resource string to use for
420
+ * initialization.
421
+ */
422
+ setMode({ mode, userId, resource }) {
423
+ this.mode = mode;
424
+ this.value.id = userId;
425
+ this.configureResource(userId, resource);
426
+ }
427
+ }
428
+ };
429
+ </script>
430
+
431
+ <template>
432
+ <Loading v-if="$fetchState.pending || notFound" />
433
+ <div v-else>
434
+ <Masthead
435
+ v-if="showMasthead"
436
+ :resource="resourceType"
437
+ :value="liveModel"
438
+ :mode="mode"
439
+ :real-mode="realMode"
440
+ :as="as"
441
+ :has-graph="hasGraph"
442
+ :has-detail="hasCustomDetail"
443
+ :has-edit="hasCustomEdit"
444
+ :can-view-yaml="canViewYaml"
445
+ :resource-subtype="resourceSubtype"
446
+ :parent-route-override="parentRouteOverride"
447
+ :store-override="storeOverride"
448
+ >
449
+ <DetailTop
450
+ v-if="isView && isDetail"
451
+ :value="liveModel"
452
+ />
453
+ </Masthead>
454
+ <div
455
+ v-if="hasErrors"
456
+ id="cru-errors"
457
+ class="cru__errors"
458
+ >
459
+ <Banner
460
+ v-for="(err, i) in errors"
461
+ :key="i"
462
+ color="error"
463
+ :data-testid="`error-banner${i}`"
464
+ :label="stringify(mappedErrors[err].message)"
465
+ :icon="mappedErrors[err].icon"
466
+ :closable="true"
467
+ @close="closeError(i)"
468
+ />
469
+ </div>
470
+
471
+ <ForceDirectedTreeChart
472
+ v-if="isGraph"
473
+ :data="chartData"
474
+ :fdc-config="getGraphConfig"
475
+ />
476
+
477
+ <ResourceYaml
478
+ v-else-if="isYaml"
479
+ ref="resourceyaml"
480
+ :value="value"
481
+ :mode="mode"
482
+ :yaml="yaml"
483
+ :offer-preview="offerPreview"
484
+ :done-route="doneRoute"
485
+ :done-override="value ? value.doneOverride : null"
486
+ @update:value="$emit('input', $event)"
487
+ @error="e=>errors.push(e)"
488
+ />
489
+
490
+ <component
491
+ :is="showComponent"
492
+ v-else
493
+ ref="comp"
494
+ v-model:value="value"
495
+ v-bind="$data"
496
+ :done-params="doneParams"
497
+ :done-route="doneRoute"
498
+ :mode="mode"
499
+ :initial-value="initialModel"
500
+ :live-value="liveModel"
501
+ :real-mode="realMode"
502
+ :class="{'flex-content': flexContent}"
503
+ @update:value="$emit('input', $event)"
504
+ @update:mode="setMode"
505
+ @set-subtype="setSubtype"
506
+ />
507
+
508
+ <button
509
+ v-if="isView"
510
+ v-shortkey.once="['shift','d']"
511
+ :data-testid="componentTestid + '-detail'"
512
+ class="hide"
513
+ @shortkey="keyAction('goToDetail')"
514
+ />
515
+ <button
516
+ v-if="isView"
517
+ v-shortkey.once="['shift','c']"
518
+ :data-testid="componentTestid + '-config'"
519
+ class="hide"
520
+ @shortkey="keyAction('goToViewConfig')"
521
+ />
522
+ <button
523
+ v-if="isView"
524
+ v-shortkey.once="['shift','y']"
525
+ :data-testid="componentTestid + '-yaml'"
526
+ class="hide"
527
+ @shortkey="keyAction('goToViewYaml')"
528
+ />
529
+ <button
530
+ v-if="isView"
531
+ v-shortkey.once="['shift','e']"
532
+ :data-testid="componentTestid + '-edit'"
533
+ class="hide"
534
+ @shortkey="keyAction('goToEdit')"
535
+ />
536
+ </div>
537
+ </template>
538
+
539
+ <style lang='scss' scoped>
540
+ .flex-content {
541
+ display: flex;
542
+ flex-direction: column;
543
+ flex-grow: 1;
544
+ }
545
+ </style>
@@ -8,6 +8,9 @@ import { NAMESPACE, AGE } from '@shell/config/table-headers';
8
8
  import { findBy } from '@shell/utils/array';
9
9
  import { ExtensionPoint, TableColumnLocation } from '@shell/core/types';
10
10
  import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
11
+ import { ToggleSwitch } from '@components/Form/ToggleSwitch';
12
+ import ResourceTableWatch from '@shell/mixins/resource-table-watch';
13
+ import paginationUtils from '@shell/utils/pagination-utils';
11
14
 
12
15
  // Default group-by in the case the group stored in the preference does not apply
13
16
  const DEFAULT_GROUP = 'namespace';
@@ -43,7 +46,13 @@ export default {
43
46
 
44
47
  emits: ['clickedActionButton'],
45
48
 
46
- components: { ButtonGroup, SortableTable },
49
+ components: {
50
+ ButtonGroup, SortableTable, ToggleSwitch
51
+ },
52
+
53
+ mixins: [
54
+ ResourceTableWatch
55
+ ],
47
56
 
48
57
  props: {
49
58
  schema: {
@@ -222,7 +231,8 @@ export default {
222
231
  * Primary purpose is to directly connect an iteration of `rows` with a sortGeneration string. This avoids
223
232
  * reactivity issues where `rows` hasn't yet changed but something like workspaces has (stale values stored against fresh key)
224
233
  */
225
- sortGeneration: undefined
234
+ sortGeneration: undefined,
235
+ listAutoRefreshToggleEnabled: paginationUtils.listAutoRefreshToggleEnabled({ rootGetters: this.$store.getters }),
226
236
  };
227
237
  },
228
238
 
@@ -238,7 +248,8 @@ export default {
238
248
  }
239
249
  },
240
250
  immediate: true
241
- }
251
+ },
252
+
242
253
  },
243
254
 
244
255
  computed: {
@@ -528,7 +539,6 @@ export default {
528
539
  pluralLabel: this.$store.getters['type-map/labelFor'](this.schema, 99),
529
540
  };
530
541
  },
531
-
532
542
  },
533
543
 
534
544
  methods: {
@@ -589,8 +599,9 @@ export default {
589
599
  if (event.key === 'Enter') {
590
600
  this.keyAction('detail');
591
601
  }
592
- }
593
- },
602
+ },
603
+
604
+ }
594
605
  };
595
606
  </script>
596
607
 
@@ -646,7 +657,24 @@ export default {
646
657
  v-if="showGrouping"
647
658
  #header-right
648
659
  >
649
- <slot name="header-right" />
660
+ <slot
661
+ name="header-right"
662
+ />
663
+ </template>
664
+
665
+ <template
666
+ v-if="externalPaginationEnabled"
667
+ #watch-controls
668
+ >
669
+ <!-- See https://github.com/rancher/dashboard/issues/14359 -->
670
+ <ToggleSwitch
671
+ v-if="listAutoRefreshToggleEnabled"
672
+ class="auto-update"
673
+ :value="watching"
674
+ name="label-system-toggle"
675
+ :on-label="t('resourceTable.autoRefresh.label')"
676
+ @update:value="toggleWatch"
677
+ />
650
678
  </template>
651
679
 
652
680
  <template #group-by="{group: thisGroup}">
@@ -694,3 +722,9 @@ export default {
694
722
  </template>
695
723
  </SortableTable>
696
724
  </template>
725
+
726
+ <style lang="scss" scoped>
727
+ .auto-update {
728
+ min-width: 150px; height: 40px
729
+ }
730
+ </style>