@rancher/shell 3.0.5-rc.7 → 3.0.5-rc.8

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 (177) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +16 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +1 -1
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/themes/_dark.scss +262 -260
  9. package/assets/styles/themes/_light.scss +538 -515
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +84 -25
  12. package/chart/__tests__/S3.test.ts +2 -1
  13. package/cloud-credential/generic.vue +18 -10
  14. package/cloud-credential/harvester.vue +1 -9
  15. package/components/AdvancedSection.vue +8 -0
  16. package/components/ChartReadme.vue +17 -7
  17. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +1 -26
  18. package/components/Drawer/ResourceDetailDrawer/composables.ts +0 -23
  19. package/components/Drawer/ResourceDetailDrawer/index.vue +17 -4
  20. package/components/InstallHelmCharts.vue +656 -0
  21. package/components/LazyImage.vue +60 -4
  22. package/components/LocaleSelector.vue +7 -2
  23. package/components/Markdown.vue +4 -0
  24. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  25. package/components/Resource/Detail/Masthead/index.vue +37 -0
  26. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -5
  27. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  28. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  29. package/components/Resource/Detail/Metadata/index.vue +17 -2
  30. package/components/Resource/Detail/Page.vue +35 -21
  31. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  32. package/components/Resource/Detail/TitleBar/composables.ts +2 -3
  33. package/components/Resource/Detail/TitleBar/index.vue +10 -1
  34. package/components/ResourceDetail/index.vue +569 -74
  35. package/components/SlideInPanelManager.vue +10 -3
  36. package/components/SortableTable/index.vue +4 -4
  37. package/components/Tabbed/index.vue +29 -3
  38. package/components/__tests__/LazyImage.spec.ts +121 -0
  39. package/components/fleet/FleetStatus.vue +4 -0
  40. package/components/form/ClusterAppearance.vue +5 -0
  41. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  42. package/components/form/ProjectMemberEditor.vue +1 -1
  43. package/components/form/ResourceLabeledSelect.vue +19 -6
  44. package/components/form/ResourceTabs/index.vue +20 -0
  45. package/components/form/SecretSelector.vue +9 -0
  46. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  47. package/components/formatter/FleetApplicationSource.vue +25 -17
  48. package/components/nav/Favorite.vue +4 -0
  49. package/components/nav/NotificationCenter/Notification.vue +1 -27
  50. package/components/nav/WindowManager/index.vue +3 -3
  51. package/config/labels-annotations.js +1 -2
  52. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  53. package/detail/__tests__/workload.test.ts +164 -0
  54. package/detail/configmap.vue +33 -75
  55. package/detail/projectsecret.vue +11 -0
  56. package/detail/provisioning.cattle.io.cluster.vue +350 -324
  57. package/detail/secret.vue +49 -308
  58. package/detail/workload/index.vue +38 -21
  59. package/dialog/InstallExtensionDialog.vue +8 -5
  60. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  61. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  62. package/edit/fleet.cattle.io.helmop.vue +78 -56
  63. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  64. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  65. package/edit/networking.k8s.io.ingress/Certificate.vue +9 -11
  66. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  67. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  68. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  69. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  70. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  71. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  72. package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -13
  73. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  74. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  75. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  76. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  77. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  78. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +30 -31
  79. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  80. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  81. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  82. package/edit/workload/index.vue +5 -14
  83. package/list/provisioning.cattle.io.cluster.vue +1 -69
  84. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  85. package/machine-config/google.vue +9 -1
  86. package/machine-config/vmwarevsphere.vue +7 -17
  87. package/mixins/chart.js +0 -2
  88. package/mixins/resource-fetch-api-pagination.js +3 -4
  89. package/models/__tests__/chart.test.ts +111 -80
  90. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  91. package/models/__tests__/node.test.ts +7 -63
  92. package/models/catalog.cattle.io.app.js +1 -1
  93. package/models/catalog.cattle.io.operation.js +1 -1
  94. package/models/chart.js +36 -20
  95. package/models/cloudcredential.js +2 -163
  96. package/models/cluster/node.js +7 -7
  97. package/models/cluster.x-k8s.io.machine.js +3 -3
  98. package/models/compliance.cattle.io.clusterscan.js +2 -2
  99. package/models/configmap.js +4 -0
  100. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  101. package/models/fleet-application.js +0 -17
  102. package/models/fleet.cattle.io.gitrepo.js +15 -1
  103. package/models/fleet.cattle.io.helmop.js +26 -22
  104. package/models/management.cattle.io.setting.js +4 -0
  105. package/models/persistentvolumeclaim.js +1 -1
  106. package/models/pod.js +2 -2
  107. package/models/provisioning.cattle.io.cluster.js +16 -40
  108. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  109. package/models/secret.js +4 -0
  110. package/models/storage.k8s.io.storageclass.js +2 -2
  111. package/models/workload.js +3 -3
  112. package/package.json +11 -10
  113. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  114. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  115. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  116. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  117. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  118. package/pages/c/_cluster/explorer/projectsecret.vue +3 -13
  119. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  120. package/pages/c/_cluster/fleet/index.vue +103 -44
  121. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  122. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  123. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  124. package/plugins/dashboard-store/actions.js +42 -22
  125. package/plugins/dashboard-store/resource-class.js +31 -0
  126. package/plugins/steve/__tests__/getters.test.ts +1 -1
  127. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  128. package/plugins/steve/getters.js +8 -2
  129. package/plugins/steve/resourceWatcher.js +10 -3
  130. package/plugins/steve/subscribe.js +192 -19
  131. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  132. package/rancher-components/Card/Card.vue +0 -18
  133. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  134. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  135. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  136. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  137. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  138. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  139. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  140. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  141. package/rancher-components/Pill/types.ts +2 -0
  142. package/rancher-components/RcButton/RcButton.vue +1 -1
  143. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  144. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  145. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  146. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  147. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  148. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  149. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  150. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  151. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  152. package/store/__tests__/catalog.test.ts +93 -1
  153. package/store/aws.js +19 -8
  154. package/store/catalog.js +8 -3
  155. package/types/resources/settings.d.ts +1 -1
  156. package/types/shell/index.d.ts +28 -28
  157. package/types/uiplugins.ts +73 -0
  158. package/utils/__tests__/back-off.test.ts +354 -0
  159. package/utils/__tests__/kontainer.test.ts +19 -0
  160. package/utils/__tests__/uiplugins.test.ts +84 -0
  161. package/utils/back-off.ts +176 -0
  162. package/utils/dynamic-importer.js +8 -0
  163. package/utils/kontainer.ts +3 -5
  164. package/utils/style.ts +3 -0
  165. package/utils/uiplugins.ts +29 -2
  166. package/utils/validators/__tests__/setting.test.js +92 -0
  167. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  168. package/utils/validators/formRules/index.ts +83 -8
  169. package/utils/validators/setting.js +17 -0
  170. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  171. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  172. package/components/ResourceDetail/legacy.vue +0 -562
  173. package/components/formatter/CloudCredExpired.vue +0 -69
  174. package/pages/explorer/resource/detail/configmap.vue +0 -42
  175. package/pages/explorer/resource/detail/projectsecret.vue +0 -9
  176. package/pages/explorer/resource/detail/secret.vue +0 -63
  177. package/utils/aws.js +0 -0
@@ -1,87 +1,582 @@
1
- <script lang="ts" setup>
2
- import { useRoute } from 'vue-router';
3
- import { computed, defineAsyncComponent } from 'vue';
4
-
5
- import { MODE, _VIEW } from '@shell/config/query-params';
6
- import Legacy from '@shell/components/ResourceDetail/legacy.vue';
7
- import Loading from '@shell/components/Loading.vue';
8
- import { useIsNewDetailPageEnabled } from '@shell/composables/useIsNewDetailPageEnabled';
9
- import { VIRTUAL_TYPES } from '@shell/config/types';
10
-
11
- export interface Props {
12
- flexContent?: boolean;
13
- componentTestId?: string;
14
- storeOverride?: string;
15
- resourceOverride?: string;
16
- parentRouteOverride?: string;
17
- errorsMap?: any;
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
+ }
18
29
  }
19
30
 
20
- // Ideally I'd prefer to have separate routes/pages for each of these but our app makes a
21
- // fair amount of assumptions around having one detail page for each resource and that the
22
- // detail, config, edit, yaml, create pages are all derived from the same page.
23
- //
24
- // I could also dynamically check for and import these pages but I wanted this to be easier
25
- // to be explicit and easier to search for.
26
- const resourceToPage: any = {
27
- // 'apps.daemonset': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/apps.daemonset.vue')),
28
- // 'apps.deployment': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/apps.deployment.vue')),
29
- // 'apps.statefulset': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/apps.statefulset.vue')),
30
- // 'batch.cronjob': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/batch.cronjob.vue')),
31
- // 'batch.job': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/batch.job.vue')),
32
- configmap: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/configmap.vue')),
33
- // namespace: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/namespace.vue')),
34
- // node: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/node.vue')),
35
- // pod: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/pod.vue')),
36
- secret: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/secret.vue')),
37
- [VIRTUAL_TYPES.PROJECT_SECRETS]: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/projectsecret.vue')),
38
- };
31
+ async function getYaml(store, model) {
32
+ let yaml;
33
+ const opt = { headers: { accept: 'application/yaml' } };
39
34
 
40
- defineOptions({ inheritAttrs: false });
35
+ if ( model.hasLink('view') ) {
36
+ yaml = (await model.followLink('view', opt)).data;
37
+ }
41
38
 
42
- const route = useRoute();
43
- const props = withDefaults(defineProps<Props>(), {
44
- flexContent: false,
45
- componentTestId: 'resource-details',
46
- storeOverride: undefined,
47
- resourceOverride: undefined,
48
- parentRouteOverride: undefined,
49
- errorsMap: undefined
50
- });
39
+ return model.cleanForDownload(yaml);
40
+ }
51
41
 
52
- const currentResourceName = computed(() => {
53
- const resource = props.resourceOverride || route?.params?.resource;
42
+ export default {
43
+ emits: ['input'],
54
44
 
55
- if (!resource) {
56
- return;
57
- }
45
+ components: {
46
+ Loading,
47
+ DetailTop,
48
+ ForceDirectedTreeChart,
49
+ ResourceYaml,
50
+ Masthead,
51
+ IconMessage,
52
+ Banner
53
+ },
58
54
 
59
- if (typeof resource === 'string') {
60
- return resource;
61
- }
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
+ }
62
215
 
63
- // This should never occur, just satisfying the types
64
- return resource[0];
65
- });
66
- const mode = computed(() => route?.query?.[MODE]);
67
- const isView = computed(() => route?.params?.id && (!mode.value || mode.value === _VIEW));
68
- // We're defaulting to legacy being on, we'll switch this once we want to enable the new detail page by default
69
- const iseNewDetailPageEnabled = useIsNewDetailPageEnabled();
70
- const page = computed(() => currentResourceName.value ? resourceToPage[currentResourceName.value] : undefined);
71
- const useLatest = computed(() => !!(iseNewDetailPageEnabled.value && isView.value && page.value));
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
+ isFullPageOverride() {
330
+ return this.isView && this.value.fullDetailPageOverride;
331
+ }
332
+ },
333
+
334
+ watch: {
335
+ '$route'(current, prev) {
336
+ if (current.name !== prev.name) {
337
+ return;
338
+ }
339
+ const neu = clone(current.query);
340
+ const old = clone(prev.query);
341
+
342
+ delete neu[PREVIEW];
343
+ delete old[PREVIEW];
344
+
345
+ if ( !this.isView ) {
346
+ delete neu[AS];
347
+ delete old[AS];
348
+ }
349
+
350
+ const queryDiff = Object.keys(diff(neu, old));
351
+
352
+ if (queryDiff.includes(MODE) || queryDiff.includes(AS)) {
353
+ this.$fetch();
354
+ }
355
+ },
356
+
357
+ // Auto refresh YAML when the model changes
358
+ async 'value.metadata.resourceVersion'(a, b) {
359
+ if ( this.mode === _VIEW && this.as === _YAML && a && b && a !== b) {
360
+ this.yaml = await getYaml(this.$store, this.liveModel);
361
+ }
362
+ }
363
+ },
364
+
365
+ created() {
366
+ this.configureResource();
367
+ },
368
+
369
+ methods: {
370
+ stringify,
371
+ setSubtype(subtype) {
372
+ this.resourceSubtype = subtype;
373
+ },
374
+
375
+ keyAction(act) {
376
+ const m = this.liveModel;
377
+
378
+ if ( m?.[act] ) {
379
+ m[act]();
380
+ }
381
+ },
382
+ closeError(index) {
383
+ this.errors = this.errors.filter((_, i) => i !== index);
384
+ },
385
+ onYamlError(err) {
386
+ this.errors = [];
387
+ const errors = Array.isArray(err) ? err : [err];
388
+
389
+ errors.forEach((e) => {
390
+ if (this.errors.indexOf(e) === -1) {
391
+ this.errors.push(e);
392
+ }
393
+ });
394
+ },
395
+ /**
396
+ * Initializes the resource components based on the provided user and
397
+ * resource override.
398
+ *
399
+ * Configures the detail and edit components for a resource based on the
400
+ * user's ID and the specified resource.
401
+ *
402
+ * @param {Object} user - The user object containing user-specific
403
+ * information.
404
+ * @param {string|null} resourceOverride - An optional resource override
405
+ * string. If not provided, the method will use the default resource from
406
+ * the route parameters or the instance's resourceOverride property.
407
+ */
408
+ configureResource(userId = '', resourceOverride = null) {
409
+ const id = userId || this.$route.params.id;
410
+ const resource = resourceOverride || this.resourceOverride || this.$route.params.resource;
411
+ const options = this.$store.getters[`type-map/optionsFor`](resource);
412
+
413
+ const detailResource = options.resourceDetail || options.resource || resource;
414
+ const editResource = options.resourceEdit || options.resource || resource;
415
+
416
+ // FIXME: These aren't right... signature is (rawType, subType).. not (rawType, resourceId)
417
+ // Remove id? How does subtype get in (cluster/node)
418
+ this.detailComponent = this.$store.getters['type-map/importDetail'](detailResource, id);
419
+ this.editComponent = this.$store.getters['type-map/importEdit'](editResource, id);
420
+ },
421
+ /**
422
+ * Sets the mode and initializes the resource components.
423
+ *
424
+ * This method sets the mode of the component and configures the resource
425
+ * components based on the provided user and resource.
426
+ *
427
+ * @param {Object} payload - An object containing the mode, user, and
428
+ * resource properties.
429
+ * @param {string} payload.mode - The mode to set.
430
+ * @param {Object} payload.user - The user object containing user-specific
431
+ * information.
432
+ * @param {string} payload.resource - The resource string to use for
433
+ * initialization.
434
+ */
435
+ setMode({ mode, userId, resource }) {
436
+ this.mode = mode;
437
+ this.value.id = userId;
438
+ this.configureResource(userId, resource);
439
+ }
440
+ }
441
+ };
72
442
  </script>
73
443
 
74
444
  <template>
75
- <Suspense v-if="useLatest">
76
- <template #default>
77
- <component :is="page" />
78
- </template>
79
- <template #fallback>
80
- <Loading />
81
- </template>
82
- </Suspense>
83
- <Legacy
84
- v-else
85
- v-bind="{...$attrs, ...props}"
445
+ <Loading v-if="$fetchState.pending || notFound" />
446
+ <component
447
+ :is="showComponent"
448
+ v-else-if="isFullPageOverride"
449
+ v-model:value="value"
450
+ v-bind="$data"
451
+ :done-params="doneParams"
452
+ :done-route="doneRoute"
453
+ :mode="mode"
454
+ :initial-value="initialModel"
455
+ :live-value="liveModel"
456
+ :real-mode="realMode"
457
+ :class="{'flex-content': flexContent}"
458
+ :resource-errors="errors"
459
+ @update:value="$emit('input', $event)"
460
+ @update:mode="setMode"
461
+ @set-subtype="setSubtype"
86
462
  />
463
+ <div v-else>
464
+ <Masthead
465
+ v-if="showMasthead"
466
+ :resource="resourceType"
467
+ :value="liveModel"
468
+ :mode="mode"
469
+ :real-mode="realMode"
470
+ :as="as"
471
+ :has-graph="hasGraph"
472
+ :has-detail="hasCustomDetail"
473
+ :has-edit="hasCustomEdit"
474
+ :can-view-yaml="canViewYaml"
475
+ :resource-subtype="resourceSubtype"
476
+ :parent-route-override="parentRouteOverride"
477
+ :store-override="storeOverride"
478
+ >
479
+ <DetailTop
480
+ v-if="isView && isDetail"
481
+ :value="liveModel"
482
+ />
483
+ </Masthead>
484
+ <div
485
+ v-if="hasErrors"
486
+ id="cru-errors"
487
+ class="cru__errors"
488
+ >
489
+ <Banner
490
+ v-for="(err, i) in errors"
491
+ :key="i"
492
+ color="error"
493
+ :data-testid="`error-banner${i}`"
494
+ :label="stringify(mappedErrors[err].message)"
495
+ :icon="mappedErrors[err].icon"
496
+ :closable="true"
497
+ @close="closeError(i)"
498
+ />
499
+ </div>
500
+
501
+ <ForceDirectedTreeChart
502
+ v-if="isGraph"
503
+ :data="chartData"
504
+ :fdc-config="getGraphConfig"
505
+ />
506
+
507
+ <ResourceYaml
508
+ v-else-if="isYaml"
509
+ ref="resourceyaml"
510
+ :value="value"
511
+ :mode="mode"
512
+ :yaml="yaml"
513
+ :offer-preview="offerPreview"
514
+ :done-route="doneRoute"
515
+ :done-override="value ? value.doneOverride : null"
516
+ :show-errors="false"
517
+ @update:value="$emit('input', $event)"
518
+ @error="onYamlError"
519
+ />
520
+
521
+ <component
522
+ :is="showComponent"
523
+ v-else
524
+ ref="comp"
525
+ v-model:value="value"
526
+ v-bind="$data"
527
+ :done-params="doneParams"
528
+ :done-route="doneRoute"
529
+ :mode="mode"
530
+ :initial-value="initialModel"
531
+ :live-value="liveModel"
532
+ :real-mode="realMode"
533
+ :class="{'flex-content': flexContent}"
534
+ @update:value="$emit('input', $event)"
535
+ @update:mode="setMode"
536
+ @set-subtype="setSubtype"
537
+ />
538
+
539
+ <button
540
+ v-if="isView"
541
+ v-shortkey.once="['shift','d']"
542
+ :data-testid="componentTestid + '-detail'"
543
+ class="hide"
544
+ @shortkey="keyAction('goToDetail')"
545
+ />
546
+ <button
547
+ v-if="isView"
548
+ v-shortkey.once="['shift','c']"
549
+ :data-testid="componentTestid + '-config'"
550
+ class="hide"
551
+ @shortkey="keyAction('goToViewConfig')"
552
+ />
553
+ <button
554
+ v-if="isView"
555
+ v-shortkey.once="['shift','y']"
556
+ :data-testid="componentTestid + '-yaml'"
557
+ class="hide"
558
+ @shortkey="keyAction('goToViewYaml')"
559
+ />
560
+ <button
561
+ v-if="isView"
562
+ v-shortkey.once="['shift','e']"
563
+ :data-testid="componentTestid + '-edit'"
564
+ class="hide"
565
+ @shortkey="keyAction('goToEdit')"
566
+ />
567
+ </div>
87
568
  </template>
569
+
570
+ <style lang='scss' scoped>
571
+ .flex-content {
572
+ display: flex;
573
+ flex-direction: column;
574
+ flex-grow: 1;
575
+ }
576
+ .cru__errors {
577
+ position: sticky;
578
+ top: 0;
579
+ z-index: 1;
580
+ background-color: var(--header-bg);
581
+ }
582
+ </style>