@rancher/shell 3.0.5-rc.6 → 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 (243) 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 +18 -12
  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 -258
  9. package/assets/styles/themes/_light.scss +538 -509
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +110 -29
  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/CodeMirror.vue +1 -1
  18. package/components/Drawer/Chrome.vue +0 -1
  19. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
  20. package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
  21. package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
  22. package/components/InstallHelmCharts.vue +656 -0
  23. package/components/LazyImage.vue +60 -4
  24. package/components/Loading.vue +1 -1
  25. package/components/LocaleSelector.vue +7 -2
  26. package/components/Markdown.vue +4 -0
  27. package/components/PaginatedResourceTable.vue +46 -1
  28. package/components/PromptRestore.vue +22 -44
  29. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  30. package/components/Resource/Detail/Masthead/index.vue +37 -0
  31. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
  32. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
  33. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
  34. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
  35. package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
  36. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  37. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  38. package/components/Resource/Detail/Metadata/index.vue +17 -2
  39. package/components/Resource/Detail/Page.vue +35 -21
  40. package/components/Resource/Detail/SpacedRow.vue +1 -1
  41. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  42. package/components/Resource/Detail/TitleBar/composables.ts +5 -5
  43. package/components/Resource/Detail/TitleBar/index.vue +12 -3
  44. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  45. package/components/ResourceDetail/index.vue +569 -72
  46. package/components/ResourceList/index.vue +1 -0
  47. package/components/ResourceTable.vue +6 -1
  48. package/components/ResourceYaml.vue +1 -1
  49. package/components/RichTranslation.vue +106 -0
  50. package/components/SlideInPanelManager.vue +13 -10
  51. package/components/SortableTable/index.vue +5 -5
  52. package/components/SortableTable/selection.js +0 -1
  53. package/components/Tabbed/index.vue +35 -4
  54. package/components/__tests__/LazyImage.spec.ts +121 -0
  55. package/components/__tests__/PromptRestore.test.ts +1 -65
  56. package/components/__tests__/RichTranslation.test.ts +115 -0
  57. package/components/fleet/FleetStatus.vue +4 -0
  58. package/components/fleet/dashboard/ResourcePanel.vue +2 -1
  59. package/components/form/ClusterAppearance.vue +5 -0
  60. package/components/form/FileImageSelector.vue +1 -1
  61. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  62. package/components/form/NameNsDescription.vue +1 -0
  63. package/components/form/Networking.vue +24 -19
  64. package/components/form/ProjectMemberEditor.vue +1 -1
  65. package/components/form/ResourceLabeledSelect.vue +22 -8
  66. package/components/form/ResourceTabs/index.vue +20 -0
  67. package/components/form/SecretSelector.vue +9 -0
  68. package/components/form/SelectOrCreateAuthSecret.vue +6 -3
  69. package/components/form/__tests__/Networking.test.ts +116 -0
  70. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  71. package/components/formatter/FleetApplicationSource.vue +25 -17
  72. package/components/formatter/PodImages.vue +1 -1
  73. package/components/formatter/__tests__/LiveDate.test.ts +10 -2
  74. package/components/google/AccountAccess.vue +44 -46
  75. package/components/nav/Favorite.vue +4 -0
  76. package/components/nav/Group.vue +4 -1
  77. package/components/nav/NotificationCenter/Notification.vue +1 -27
  78. package/components/nav/WindowManager/index.vue +3 -3
  79. package/composables/resources.ts +2 -2
  80. package/config/labels-annotations.js +3 -2
  81. package/config/pagination-table-headers.js +8 -1
  82. package/config/product/explorer.js +27 -2
  83. package/config/product/manager.js +0 -1
  84. package/config/query-params.js +10 -0
  85. package/config/router/routes.js +21 -1
  86. package/config/system-namespaces.js +1 -1
  87. package/config/table-headers.js +30 -1
  88. package/config/types.js +1 -1
  89. package/config/version.js +1 -1
  90. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  91. package/detail/__tests__/workload.test.ts +164 -0
  92. package/detail/configmap.vue +33 -75
  93. package/detail/projectsecret.vue +11 -0
  94. package/detail/provisioning.cattle.io.cluster.vue +351 -369
  95. package/detail/secret.vue +49 -308
  96. package/detail/workload/index.vue +38 -21
  97. package/dialog/InstallExtensionDialog.vue +8 -5
  98. package/dialog/RotateEncryptionKeyDialog.vue +10 -30
  99. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  100. package/edit/auth/ldap/__tests__/config.test.ts +14 -0
  101. package/edit/auth/ldap/config.vue +24 -0
  102. package/edit/compliance.cattle.io.clusterscan.vue +1 -1
  103. package/edit/configmap.vue +4 -1
  104. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  105. package/edit/fleet.cattle.io.helmop.vue +78 -56
  106. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  107. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  108. package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
  109. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  110. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  111. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  112. package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
  113. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  114. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  115. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  116. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
  117. package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
  118. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  119. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
  120. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  121. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  122. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  123. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  124. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
  125. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  126. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  127. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  128. package/edit/secret/basic.vue +1 -0
  129. package/edit/secret/index.vue +126 -15
  130. package/edit/workload/index.vue +5 -14
  131. package/list/projectsecret.vue +345 -0
  132. package/list/provisioning.cattle.io.cluster.vue +1 -69
  133. package/list/secret.vue +109 -0
  134. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  135. package/machine-config/google.vue +9 -1
  136. package/machine-config/vmwarevsphere.vue +7 -17
  137. package/mixins/__tests__/brand.spec.ts +2 -2
  138. package/mixins/chart.js +0 -2
  139. package/mixins/create-edit-view/impl.js +10 -1
  140. package/mixins/resource-fetch-api-pagination.js +11 -12
  141. package/mixins/resource-fetch.js +3 -1
  142. package/models/__tests__/chart.test.ts +111 -80
  143. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  144. package/models/__tests__/node.test.ts +7 -63
  145. package/models/catalog.cattle.io.app.js +1 -1
  146. package/models/catalog.cattle.io.operation.js +1 -1
  147. package/models/chart.js +36 -20
  148. package/models/cloudcredential.js +2 -163
  149. package/models/cluster/node.js +7 -7
  150. package/models/cluster.x-k8s.io.machine.js +3 -3
  151. package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
  152. package/models/compliance.cattle.io.clusterscan.js +2 -2
  153. package/models/configmap.js +4 -0
  154. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  155. package/models/fleet-application.js +0 -17
  156. package/models/fleet.cattle.io.cluster.js +2 -2
  157. package/models/fleet.cattle.io.gitrepo.js +15 -1
  158. package/models/fleet.cattle.io.helmop.js +26 -22
  159. package/models/management.cattle.io.setting.js +4 -0
  160. package/models/persistentvolumeclaim.js +1 -1
  161. package/models/pod.js +2 -2
  162. package/models/provisioning.cattle.io.cluster.js +39 -67
  163. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  164. package/models/secret.js +161 -2
  165. package/models/storage.k8s.io.storageclass.js +2 -2
  166. package/models/workload.js +3 -3
  167. package/package.json +11 -10
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  172. package/pages/c/_cluster/apps/charts/index.vue +46 -35
  173. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  174. package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
  175. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  176. package/pages/c/_cluster/fleet/index.vue +103 -45
  177. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  178. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  179. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  180. package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
  181. package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
  182. package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
  183. package/plugins/dashboard-store/actions.js +42 -22
  184. package/plugins/dashboard-store/normalize.js +29 -17
  185. package/plugins/dashboard-store/resource-class.js +83 -17
  186. package/plugins/steve/__tests__/getters.test.ts +1 -1
  187. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  188. package/plugins/steve/getters.js +8 -2
  189. package/plugins/steve/resourceWatcher.js +10 -3
  190. package/plugins/steve/steve-pagination-utils.ts +14 -3
  191. package/plugins/steve/subscribe.js +192 -19
  192. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  193. package/rancher-components/Card/Card.vue +0 -18
  194. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  195. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  196. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  197. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  198. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  199. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  200. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  201. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  202. package/rancher-components/Pill/types.ts +2 -0
  203. package/rancher-components/RcButton/RcButton.vue +1 -1
  204. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  205. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  206. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  207. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  208. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  209. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  210. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  213. package/store/__tests__/catalog.test.ts +93 -1
  214. package/store/aws.js +19 -8
  215. package/store/catalog.js +8 -3
  216. package/types/kube/kube-api.ts +12 -0
  217. package/types/resources/settings.d.ts +1 -1
  218. package/types/shell/index.d.ts +643 -585
  219. package/types/store/pagination.types.ts +16 -6
  220. package/types/uiplugins.ts +73 -0
  221. package/utils/__tests__/back-off.test.ts +354 -0
  222. package/utils/__tests__/create-yaml.test.ts +235 -0
  223. package/utils/__tests__/kontainer.test.ts +19 -0
  224. package/utils/__tests__/uiplugins.test.ts +84 -0
  225. package/utils/back-off.ts +176 -0
  226. package/utils/create-yaml.js +103 -9
  227. package/utils/dynamic-importer.js +8 -0
  228. package/utils/kontainer.ts +3 -5
  229. package/utils/pagination-utils.ts +18 -0
  230. package/utils/style.ts +3 -0
  231. package/utils/uiplugins.ts +29 -2
  232. package/utils/validators/__tests__/setting.test.js +92 -0
  233. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  234. package/utils/validators/formRules/index.ts +83 -8
  235. package/utils/validators/setting.js +17 -0
  236. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  237. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  238. package/components/ResourceDetail/legacy.vue +0 -562
  239. package/components/formatter/CloudCredExpired.vue +0 -69
  240. package/models/etcdbackup.js +0 -45
  241. package/pages/explorer/resource/detail/configmap.vue +0 -42
  242. package/pages/explorer/resource/detail/secret.vue +0 -50
  243. package/utils/aws.js +0 -0
@@ -1,85 +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
-
10
- export interface Props {
11
- flexContent?: boolean;
12
- componentTestId?: string;
13
- storeOverride?: string;
14
- resourceOverride?: string;
15
- parentRouteOverride?: string;
16
- 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
+ }
17
29
  }
18
30
 
19
- // Ideally I'd prefer to have separate routes/pages for each of these but our app makes a
20
- // fair amount of assumptions around having one detail page for each resource and that the
21
- // detail, config, edit, yaml, create pages are all derived from the same page.
22
- //
23
- // I could also dynamically check for and import these pages but I wanted this to be easier
24
- // to be explicit and easier to search for.
25
- const resourceToPage: any = {
26
- // 'apps.daemonset': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/apps.daemonset.vue')),
27
- // 'apps.deployment': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/apps.deployment.vue')),
28
- // 'apps.statefulset': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/apps.statefulset.vue')),
29
- // 'batch.cronjob': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/batch.cronjob.vue')),
30
- // 'batch.job': defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/batch.job.vue')),
31
- configmap: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/configmap.vue')),
32
- // namespace: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/namespace.vue')),
33
- // node: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/node.vue')),
34
- // pod: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/pod.vue')),
35
- secret: defineAsyncComponent(() => import('@shell/pages/explorer/resource/detail/secret.vue')),
36
- };
31
+ async function getYaml(store, model) {
32
+ let yaml;
33
+ const opt = { headers: { accept: 'application/yaml' } };
37
34
 
38
- defineOptions({ inheritAttrs: false });
35
+ if ( model.hasLink('view') ) {
36
+ yaml = (await model.followLink('view', opt)).data;
37
+ }
39
38
 
40
- const route = useRoute();
41
- const props = withDefaults(defineProps<Props>(), {
42
- flexContent: false,
43
- componentTestId: 'resource-details',
44
- storeOverride: undefined,
45
- resourceOverride: undefined,
46
- parentRouteOverride: undefined,
47
- errorsMap: undefined
48
- });
39
+ return model.cleanForDownload(yaml);
40
+ }
49
41
 
50
- const currentResourceName = computed(() => {
51
- const resource = route?.params?.resource;
42
+ export default {
43
+ emits: ['input'],
52
44
 
53
- if (!resource) {
54
- return;
55
- }
45
+ components: {
46
+ Loading,
47
+ DetailTop,
48
+ ForceDirectedTreeChart,
49
+ ResourceYaml,
50
+ Masthead,
51
+ IconMessage,
52
+ Banner
53
+ },
56
54
 
57
- if (typeof resource === 'string') {
58
- return resource;
59
- }
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
+ }
60
215
 
61
- // This should never occur, just satisfying the types
62
- return resource[0];
63
- });
64
- const mode = computed(() => route?.query?.[MODE]);
65
- const isView = computed(() => route?.params?.id && (!mode.value || mode.value === _VIEW));
66
- // We're defaulting to legacy being on, we'll switch this once we want to enable the new detail page by default
67
- const iseNewDetailPageEnabled = useIsNewDetailPageEnabled();
68
- const page = computed(() => currentResourceName.value ? resourceToPage[currentResourceName.value] : undefined);
69
- 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
+ };
70
442
  </script>
71
443
 
72
444
  <template>
73
- <Suspense v-if="useLatest">
74
- <template #default>
75
- <component :is="page" />
76
- </template>
77
- <template #fallback>
78
- <Loading />
79
- </template>
80
- </Suspense>
81
- <Legacy
82
- v-else
83
- 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"
84
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>
85
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>
@@ -100,6 +100,7 @@ export default {
100
100
 
101
101
  return {
102
102
  schema,
103
+ overrideInStore: undefined,
103
104
  hasListComponent,
104
105
  showMasthead: showMasthead === undefined ? true : showMasthead,
105
106
  resource,
@@ -217,11 +217,16 @@ export default {
217
217
  default: null, // Default comes from the user preference
218
218
  },
219
219
 
220
+ overrideInStore: {
221
+ type: String,
222
+ default: undefined,
223
+ },
224
+
220
225
  },
221
226
 
222
227
  data() {
223
228
  // Confirm which store we're in, if schema isn't available we're probably showing a list with different types
224
- const inStore = this.schema?.id ? this.$store.getters['currentStore'](this.schema.id) : undefined;
229
+ const inStore = this.overrideInStore || (this.schema?.id ? this.$store.getters['currentStore'](this.schema.id) : undefined);
225
230
 
226
231
  return {
227
232
  inStore,
@@ -234,7 +234,7 @@ export default {
234
234
  }
235
235
 
236
236
  try {
237
- await this.value.saveYaml(yaml);
237
+ await this.value.saveYaml(yaml, this.initialYaml);
238
238
  } catch (err) {
239
239
  return onError.call(this, err);
240
240
  }