@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
@@ -17,6 +17,8 @@ export interface Props {
17
17
  }
18
18
  </script>
19
19
  <script setup lang="ts">
20
+ const editBttnDataTestId = 'save-configuration-bttn';
21
+ const componentTestid = 'configuration-drawer-tabbed';
20
22
  const props = defineProps<Props>();
21
23
  const emit = defineEmits(['close']);
22
24
  const store = useStore();
@@ -38,11 +40,14 @@ const title = computed(() => {
38
40
 
39
41
  const activeTab = ref<string>(configTabProps ? 'config-tab' : 'yaml-tab');
40
42
 
43
+ const isConfig = computed(() => {
44
+ return activeTab.value === 'config-tab';
45
+ });
46
+
41
47
  const action = computed(() => {
42
- const isConfig = activeTab.value === 'config-tab';
43
- const ariaLabel = isConfig ? i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editConfig') : i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editYaml');
44
- const label = isConfig ? i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editConfig') : i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editYaml');
45
- const action = isConfig ? () => props.resource.goToEdit() : () => props.resource.goToEditYaml();
48
+ const ariaLabel = isConfig.value ? i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editConfig') : i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editYaml');
49
+ const label = isConfig.value ? i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editConfig') : i18n.t('component.drawer.resourceDetailDrawer.ariaLabel.editYaml');
50
+ const action = isConfig.value ? () => props.resource.goToEdit() : () => props.resource.goToEditYaml();
46
51
 
47
52
  return {
48
53
  ariaLabel,
@@ -50,6 +55,11 @@ const action = computed(() => {
50
55
  action
51
56
  };
52
57
  });
58
+
59
+ const canEdit = computed(() => {
60
+ return isConfig.value ? props.resource.canEdit : props.resource.canEditYaml;
61
+ });
62
+
53
63
  </script>
54
64
  <template>
55
65
  <Drawer
@@ -68,6 +78,8 @@ const action = computed(() => {
68
78
  <Tabbed
69
79
  class="tabbed"
70
80
  :useHash="false"
81
+ :showExtensionTabs="false"
82
+ :componentTestid="componentTestid"
71
83
  @changed="({selectedName}) => {activeTab = selectedName;}"
72
84
  >
73
85
  <ConfigTab
@@ -82,8 +94,10 @@ const action = computed(() => {
82
94
  </template>
83
95
  <template #additional-actions>
84
96
  <RcButton
97
+ v-if="canEdit"
85
98
  :primary="true"
86
99
  :aria-label="action.ariaLabel"
100
+ :data-testid="editBttnDataTestId"
87
101
  @click="action.action"
88
102
  >
89
103
  {{ action.label }}
@@ -0,0 +1,656 @@
1
+ <script>
2
+ import { mapGetters } from 'vuex';
3
+ import throttle from 'lodash/throttle';
4
+
5
+ import { CATALOG, MANAGEMENT } from '@shell/config/types';
6
+ import { SETTING } from '@shell/config/settings';
7
+ import { WINDOWS } from '@shell/store/catalog';
8
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
9
+
10
+ import AsyncButton from '@shell/components/AsyncButton';
11
+ import Loading from '@shell/components/Loading';
12
+
13
+ import { isPrerelease } from '@shell/utils/version';
14
+ import { set } from '@shell/utils/object';
15
+ import { defaultCmdOpts as defaultOpts } from '@shell/pages/c/_cluster/apps/charts/install.vue';
16
+
17
+ /**
18
+ * Assumptions made by this component
19
+ * If not in a cluster context, you want to install things in the mgmt cluster
20
+ * You want to use the latest non-pre-release version of the chart
21
+ * You want global values (rancher values) configured just as they would be when using the full install experience
22
+ * Slots:
23
+ * errors - this component doesn't render errors but it tracks them and provides a slot for the parent component to render errors if needed
24
+ * cant install - rendered when the user can't create repos or cant see app resources or cant do the install action on the target repo
25
+ * ready to install - rendered when a chart is available and the user appears to have permission to install it; this slot lets parent components expose some values customization
26
+ */
27
+
28
+ const defaultCmdOpts = { ...defaultOpts, timeout: '600s' };
29
+
30
+ const MAX_TRIES = 5;
31
+ const RETRY_WAIT = 500;
32
+
33
+ export default {
34
+ name: 'InstallHelmCharts',
35
+
36
+ components: { AsyncButton, Loading },
37
+
38
+ emits: ['done'],
39
+
40
+ props: {
41
+ // vuex store management or cluster
42
+ store: {
43
+ type: String,
44
+ default: 'cluster'
45
+ },
46
+
47
+ chartName: {
48
+ type: String,
49
+ required: true
50
+ },
51
+
52
+ repoUrl: {
53
+ type: String,
54
+ required: true
55
+ },
56
+
57
+ repoName: {
58
+ type: String,
59
+ required: true
60
+ },
61
+
62
+ repoType: {
63
+ type: String,
64
+ default: 'cluster'
65
+ },
66
+
67
+ // if not set will use chart targetNamespace. If neither this prop nor chart's targetNamespace are defined, will use default
68
+ targetNamespace: {
69
+ type: String,
70
+ default: null
71
+ },
72
+
73
+ // consuming component can supply some values to merge with chart values
74
+ extraValues: {
75
+ type: Object,
76
+ default: () => {
77
+ return {};
78
+ }
79
+ },
80
+
81
+ // how long in ms to show a done/success label between each step
82
+ delay: {
83
+ type: Number,
84
+ default: 1000
85
+ },
86
+
87
+ chartDisplayName: {
88
+ type: String,
89
+ default: null
90
+ },
91
+
92
+ repoDisplayName: {
93
+ type: String,
94
+ default: null
95
+ }
96
+
97
+ },
98
+
99
+ async fetch() {
100
+ // check if the user can even load repos
101
+ const repoSchema = this.$store.getters[`${ this.store }/schemaFor`](CATALOG.CLUSTER_REPO);
102
+
103
+ if (repoSchema && repoSchema?.resourceActions?.install) {
104
+ this.canCreateRepos = true;
105
+
106
+ this.throttledRefreshCharts = throttle(() => {
107
+ try {
108
+ this.$store.dispatch('catalog/load', { force: true });
109
+ } catch (e) {
110
+ this.$store.dispatch('growl/fromError', { err: e });
111
+ }
112
+ }, 500, { trailing: false });
113
+
114
+ if (!this.targetRepo || !this.chart) {
115
+ this.throttledRefreshCharts();
116
+ }
117
+
118
+ // server url and project ids are used in global values
119
+ try {
120
+ this.serverUrlSetting = await this.$store.dispatch(`${ this.store }/find`, {
121
+ type: MANAGEMENT.SETTING,
122
+ id: SETTING.SERVER_URL,
123
+ });
124
+ } catch (e) {
125
+ this.$store.dispatch('growl/fromError', { err: e });
126
+ }
127
+
128
+ await this.$store.dispatch(`${ this.store }/findAll`, { type: MANAGEMENT.PROJECT });
129
+ }
130
+ },
131
+
132
+ data() {
133
+ const repo = this.repoDisplayName || this.repoName;
134
+ const chart = this.chartDisplayName || this.chartName;
135
+ const stageNames = {
136
+ ADD_REPO: 'addRepo',
137
+ LOAD_CHARTS: 'loadCharts',
138
+ INSTALL: 'install',
139
+ WAIT: 'waitForLogs'
140
+ };
141
+
142
+ return {
143
+ throttledRefreshCharts: null,
144
+
145
+ // check the cluster repo schema
146
+ canCreateRepos: false,
147
+
148
+ serverUrlSetting: null,
149
+ versionInfo: null,
150
+ userValues: {},
151
+ // payload for 'install' action on clusterrepo resource
152
+ // contains array of chart install info as well as some helm install opts that are set by default in the regular chart install ui
153
+ installCmd: { charts: [], ...defaultCmdOpts },
154
+
155
+ // disable the button and show a loading spinner while
156
+ // creating repo
157
+ // fetching charts from the repo
158
+ // making the 'install' api call
159
+ // waiting for logs
160
+ doingButtonAction: false,
161
+
162
+ loadingCharts: false,
163
+ waitingForLogs: false,
164
+
165
+ // app has been installed, either by this component or elsewhere
166
+ isInstalled: false,
167
+ // app installation has been initiated by this component
168
+ didInstall: false,
169
+ installedVersion: '',
170
+
171
+ installOperationName: '',
172
+ installOperationNamespace: '',
173
+ operation: {}, // crd used to view helm install logs
174
+ logsReady: false,
175
+ stageNames,
176
+ stages: {
177
+ [stageNames.ADD_REPO]: {
178
+ actionLabel: this.t('catalog.install.button.stages.addRepo.action', { repo }),
179
+ waitingLabel: this.t('catalog.install.button.stages.addRepo.waiting', { repo }),
180
+ successLabel: this.t('catalog.install.button.stages.addRepo.success', { repo }),
181
+ errorLabel: this.t('catalog.install.button.stages.addRepo.error', { repo }),
182
+ action: this.addRepository
183
+ },
184
+ [stageNames.LOAD_CHARTS]: {
185
+ actionLabel: this.t('catalog.install.button.stages.loadCharts.action', { repo }),
186
+ waitingLabel: this.t('catalog.install.button.stages.loadCharts.waiting', { repo }),
187
+ successLabel: this.t('catalog.install.button.stages.loadCharts.success', { chart }),
188
+ errorLabel: this.t('catalog.install.button.stages.loadCharts.error', { chart }),
189
+ action: this.fetchRepoCharts
190
+ },
191
+ // using action label for 'success' here and in waitForLogs to hack around a timing issue where the success message briefly shows
192
+ [stageNames.INSTALL]: {
193
+ actionLabel: this.t('catalog.install.button.stages.installChart.action', { chart }),
194
+ waitingLabel: this.t('catalog.install.button.stages.installChart.waiting', { chart }),
195
+ successLabel: this.t('catalog.install.button.stages.installChart.action', { chart }),
196
+ errorLabel: this.t('catalog.install.button.stages.installChart.error', { chart }),
197
+ action: this.installChart
198
+ },
199
+ [stageNames.WAIT]: {
200
+ actionLabel: this.t('catalog.install.button.stages.waitForLogs.action', { chart }),
201
+ waitingLabel: this.t('catalog.install.button.stages.waitForLogs.waiting', { chart }),
202
+ successLabel: this.t('catalog.install.button.stages.waitForLogs.action', { chart }),
203
+ errorLabel: this.t('catalog.install.button.stages.waitForLogs.error', { chart }),
204
+ action: () => this.logsReady ? this.openLogs() : this.waitForLogs()
205
+ }
206
+ },
207
+
208
+ errors: [],
209
+ btnCb: () => {}
210
+ };
211
+ },
212
+
213
+ watch: {
214
+ targetRepo: {
215
+ handler(neu) {
216
+ if (neu) {
217
+ if (!this.chart) {
218
+ this.doingButtonAction = true;
219
+ this.fetchRepoCharts();
220
+ }
221
+ }
222
+ },
223
+ immediate: true
224
+ },
225
+
226
+ chart: {
227
+ handler(neu) {
228
+ if (neu) {
229
+ this.fetchVersionInfo();
230
+ }
231
+ },
232
+ immediate: true
233
+ },
234
+
235
+ versionInfo(neu) {
236
+ this.checkIfInstalled();
237
+
238
+ const { chart: chartInfo } = neu;
239
+
240
+ this.installCmd.charts[0] = {
241
+ chartName: this.chartName,
242
+ releaseName: chartInfo.name,
243
+ version: chartInfo.version,
244
+ annotations: {
245
+ [CATALOG_ANNOTATIONS.SOURCE_REPO_TYPE]: this.repoType,
246
+ [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: this.repoName
247
+ },
248
+
249
+ };
250
+
251
+ this.installCmd.namespace = this.targetNamespace || this.chart?.targetNamespace || 'default';
252
+ }
253
+ },
254
+
255
+ methods: {
256
+ async addRepository() {
257
+ this.errors = [];
258
+ this.doingButtonAction = true;
259
+
260
+ try {
261
+ const repoObj = await this.$store.dispatch(`${ this.store }/create`, {
262
+ type: CATALOG.CLUSTER_REPO,
263
+ metadata: { name: this.repoName },
264
+ spec: { url: this.repoUrl },
265
+ });
266
+
267
+ try {
268
+ await repoObj.save();
269
+ } catch (e) {
270
+ this.$store.dispatch('growl/fromError', { err: e });
271
+
272
+ this.errors.push(e);
273
+ this.doingButtonAction = false;
274
+
275
+ this.btnCb(false);
276
+
277
+ return;
278
+ }
279
+
280
+ this.throttledRefreshCharts();
281
+ } catch (e) {
282
+ this.$store.dispatch('growl/fromError', { err: e });
283
+
284
+ this.errors.push(e);
285
+ this.doingButtonAction = false;
286
+
287
+ this.btnCb(false);
288
+ }
289
+ },
290
+
291
+ // it takes time to fetch a repo's charts when the repo resource is created
292
+ // so we retry loading the chart info using the "refresh" clusterrepo model action
293
+ async fetchRepoCharts() {
294
+ this.doingButtonAction = true;
295
+ let tries = 0;
296
+
297
+ while (tries < MAX_TRIES) {
298
+ try {
299
+ tries++;
300
+ await this.targetRepo.refresh();
301
+ if (this.chart) {
302
+ break;
303
+ }
304
+ await new Promise((resolve) => setTimeout(resolve, RETRY_WAIT));
305
+ } catch (err) {
306
+ // some errors here are expected
307
+ // we only show error state when while block finishes and still no chart found
308
+ }
309
+ }
310
+
311
+ // tried all our tries and the chart still isn't found - tell users something has gone wrong
312
+ if (!this.chart) {
313
+ this.btnCb(false);
314
+ this.$store.dispatch('growl/fromError', { err: this.t('catalog.install.button.errors.fetchChart') });
315
+
316
+ this.doingButtonAction = false;
317
+ } else {
318
+ this.btnCb(true);
319
+ }
320
+ },
321
+
322
+ // once a chart is located fetch installation info from the repo
323
+ async fetchVersionInfo() {
324
+ try {
325
+ // assume we want the latest non-prerelease version
326
+ const targetVersion = (this.chart?.versions || []).find((v) => v.version && !isPrerelease(v.version))?.version;
327
+
328
+ if (!targetVersion) {
329
+ return;
330
+ }
331
+ this.versionInfo = await this.$store.dispatch('catalog/getVersionInfo', {
332
+ repoType: this.repoType,
333
+ repoName: this.repoName,
334
+ chartName: this.chartName,
335
+ versionName: targetVersion
336
+ });
337
+ } catch (e) {
338
+ this.$store.dispatch('growl/fromError', { err: e });
339
+ this.errors.push(e);
340
+ this.doingButtonAction = false;
341
+ }
342
+ },
343
+
344
+ /**
345
+ * Check if the chart is already installed
346
+ * also check if the user can install charts generally, and can install charts from this repo
347
+ * If the chart has provides-gvr annotation, check that
348
+ * if no gvr annotation, look for an app with id targetNamespce/targetName
349
+ * if app is found, tell the user which version is installed
350
+ */
351
+ async checkIfInstalled() {
352
+ const chartInfo = this.versionInfo?.chart || {};
353
+
354
+ if (chartInfo.annotations?.[CATALOG_ANNOTATIONS.PROVIDES]) {
355
+ this.isInstalled = this.$store.getters['catalog/isInstalled']({ gvr: chartInfo?.annotations?.[CATALOG_ANNOTATIONS.PROVIDES] });
356
+ } else {
357
+ const appSchema = this.$store.getters[`${ this.store }/schemaFor`](CATALOG.APP);
358
+
359
+ if (!appSchema) {
360
+ this.canInstallChart = false;
361
+ } else {
362
+ try {
363
+ const app = await this.$store.dispatch(`${ this.store }/find`, { type: CATALOG.APP, id: `${ this.targetNamespace || this.chart?.targetNamespace || 'default' }/${ this.chartName }` });
364
+
365
+ if (app) {
366
+ this.installedVersion = app?.spec?.chart?.metadata?.appVersion;
367
+ this.isInstalled = true;
368
+ }
369
+ } catch {
370
+ }
371
+ }
372
+ }
373
+
374
+ this.doingButtonAction = false;
375
+ },
376
+
377
+ async installChart() {
378
+ this.doingButtonAction = true;
379
+ this.errors = [];
380
+
381
+ if (this.isInstalled) {
382
+ this.doingButtonAction = false;
383
+
384
+ this.errors.push(this.t('catalog.install.button.errors.alreadyInstalled'));
385
+ this.$store.dispatch('growl/fromError', { err: this.t('catalog.install.button.errors.alreadyInstalled') });
386
+
387
+ return;
388
+ }
389
+ let res;
390
+
391
+ try {
392
+ this.didInstall = true;
393
+ // should only be values that differ from default
394
+ this.installCmd.charts[0].values = {
395
+ ...this.getGlobalValues(), ...this.extraValues, ...this.userValues
396
+ };
397
+ res = await this.targetRepo.doAction('install', this.installCmd);
398
+ } catch (err) {
399
+ this.btnCb(false);
400
+ this.errors.push(err);
401
+ this.doingButtonAction = false;
402
+
403
+ return;
404
+ }
405
+
406
+ this.installOperationName = res.operationName;
407
+ this.installOperationNamespace = res.operationNamespace;
408
+ await this.waitForLogs();
409
+ this.btnCb(true);
410
+
411
+ setTimeout(() => this.$emit('done', { namespace: res.operationNamespace, name: res.operationName }), this.delay);
412
+ },
413
+
414
+ // find the helm operation and check if logs are available
415
+ async waitForLogs() {
416
+ const { installOperationName, installOperationNamespace } = this;
417
+ const operationId = `${ installOperationNamespace }/${ installOperationName }`;
418
+
419
+ await this.targetRepo.waitForOperation(operationId);
420
+
421
+ this.operation = await this.$store.dispatch(`${ this.store }/find`, {
422
+ type: CATALOG.OPERATION,
423
+ id: operationId
424
+ });
425
+
426
+ try {
427
+ await this.operation.waitForLink('logs');
428
+ this.logsReady = true;
429
+ this.doingButtonAction = false;
430
+ } catch (e) {
431
+ // The wait times out eventually, move on...
432
+ this.doingButtonAction = false;
433
+ this.errors.push(this.t('catalog.install.button.errors.timeoutWaiting'));
434
+ this.$store.dispatch('growl/fromError', { err: this.t('catalog.install.button.errors.timeoutWaiting') });
435
+ }
436
+ },
437
+
438
+ // set some global values based off the target cluster's configuration
439
+ getGlobalValues() {
440
+ let clusterId = '';
441
+ let clusterName = '';
442
+
443
+ if (this.currentCluster) {
444
+ clusterId = this.currentCluster?.id;
445
+ clusterName = this.currentCluster.nameDisplay;
446
+ } else {
447
+ clusterId = 'local';
448
+ clusterName = 'local';
449
+ }
450
+
451
+ const serverUrl = this.serverUrlSetting?.value || '';
452
+ const rkePathPrefix = this.currentCluster?.spec?.rancherKubernetesEngineConfig?.prefixPath || '';
453
+ const rkeWindowsPathPrefix = this.currentCluster?.spec?.rancherKubernetesEngineConfig?.winPrefixPath || '';
454
+ const isWindows = (this.currentCluster?.workerOSs || []).includes(WINDOWS);
455
+
456
+ const projects = this.$store.getters[`${ this.store }/all`](MANAGEMENT.PROJECT);
457
+
458
+ const systemProjectId = projects.find((p) => p.spec?.chartDisplayName === 'System')?.id?.split('/')?.[1] || '';
459
+
460
+ const values = {
461
+ global: {
462
+ cattle: {
463
+ clusterName,
464
+ clusterId,
465
+ url: serverUrl,
466
+ rkePathPrefix,
467
+ rkeWindowsPathPrefix,
468
+ systemProjectId
469
+ }
470
+ }
471
+ };
472
+
473
+ if (isWindows) {
474
+ values.global.cattle.windows = { enabled: true };
475
+ }
476
+
477
+ return values;
478
+ },
479
+
480
+ // this method is passed to a slot parent components can use to expose some chart configuration options
481
+ setValue(key, val) {
482
+ set(this.userValues, key, val);
483
+ },
484
+
485
+ openLogs() {
486
+ this.operation.openLogs();
487
+ this.btnCb(true);
488
+ },
489
+
490
+ // go to the full chart install/upgrade experience
491
+ goToInstall() {
492
+ if (!this.chart) {
493
+ return;
494
+ }
495
+ this.chart.goToInstall(null, 'local');
496
+ }
497
+ },
498
+
499
+ computed: {
500
+ ...mapGetters({
501
+ charts: 'catalog/charts',
502
+ repos: 'catalog/repos',
503
+ t: 'i18n/t'
504
+ }),
505
+
506
+ currentCluster() {
507
+ const storeCluster = this.$store.getters['currentCluster'];
508
+
509
+ if (storeCluster) {
510
+ return storeCluster;
511
+ }
512
+
513
+ return this.$store.getters[`management/byId`](MANAGEMENT.CLUSTER, 'local' );
514
+ },
515
+
516
+ chart() {
517
+ if (this.targetRepo) {
518
+ return this.$store.getters['catalog/chart']({
519
+ repoName: this.repoName,
520
+ repoType: this.repoType,
521
+ chartName: this.chartName
522
+ });
523
+ }
524
+
525
+ return null;
526
+ },
527
+
528
+ targetRepo() {
529
+ return this.$store.getters['catalog/repo']({ repoType: this.repoType, repoName: this.repoName });
530
+ },
531
+
532
+ canInstallChart() {
533
+ const appSchema = this.$store.getters[`${ this.store }/schemaFor`](CATALOG.APP);
534
+
535
+ return appSchema && this.targetRepo && this.targetRepo?.actions?.install;
536
+ },
537
+
538
+ /**
539
+ * Determine what set of labels and what action to use with the button depending on what catalog resources the component has available
540
+ * There are 4 stages of chart installation:
541
+ * creating the repo
542
+ * getting charts from the repo + configuring the chart
543
+ * initializing the installation and waiting for logs
544
+ * offering logs
545
+ */
546
+ buttonStage() {
547
+ if (!this.targetRepo) {
548
+ return this.stageNames.ADD_REPO;
549
+ }
550
+
551
+ if (!this.chart || (!this.didInstall && this.doingButtonAction)) {
552
+ return this.stageNames.LOAD_CHARTS;
553
+ }
554
+
555
+ if (!this.installOperationName) {
556
+ return this.stageNames.INSTALL;
557
+ }
558
+
559
+ return this.stageNames.WAIT;
560
+ },
561
+
562
+ // at each stage of installing the chart, the button may be in one of 3 states
563
+ // action (blue, ready to be clicked)
564
+ // waiting (disabled with a loading spinner, doing the thing it was clicked to do)
565
+ // error (red, re-enabled, showing generic error label)
566
+ buttonPhase() {
567
+ if (this.errors.length) {
568
+ return 'error';
569
+ }
570
+
571
+ if (this.doingButtonAction) {
572
+ return 'waiting';
573
+ }
574
+
575
+ return 'action';
576
+ },
577
+
578
+ // the user has permission to install charts, the chart has been found, and it has not been installed already
579
+ readyToInstall() {
580
+ return this.canInstallChart && !this.isInstalled && this.chart && !this.doingButtonAction && this.buttonStage === this.stageNames.INSTALL;
581
+ },
582
+ },
583
+
584
+ }
585
+ ;
586
+ </script>
587
+
588
+ <template>
589
+ <Loading v-if="$fetchState.pending" />
590
+ <!-- the user doesn't have permission either to create repos or install charts generally or install from this repo -->
591
+ <div v-else-if="!canCreateRepos && !doingButtonAction">
592
+ <slot name="cant-install">
593
+ <span>{{ t('catalog.install.button.canNotCreateRepos', ) }}</span>
594
+ </slot>
595
+ </div>
596
+ <div v-else-if="!canInstallChart && !doingButtonAction && targetRepo">
597
+ <slot name="cant-install">
598
+ <span>{{ t('catalog.install.button.canNotInstallApps', {repo: repoName} ) }}</span>
599
+ </slot>
600
+ </div>
601
+
602
+ <!-- the chart is already installed -->
603
+ <div
604
+ v-else-if="isInstalled && !installOperationName"
605
+ >
606
+ <span>{{ t('catalog.install.button.alreadyInstalled', {chart: chartName, version: installedVersion}) }}
607
+ <a
608
+ aria-label="go to chart installation page"
609
+ @click="goToInstall"
610
+ >
611
+ {{ t('catalog.install.button.viewDetails') }}
612
+ </a>
613
+ </span>
614
+ </div>
615
+
616
+ <div v-else>
617
+ <div
618
+ v-if="readyToInstall"
619
+ >
620
+ <!-- the chart is ready to be installed, and installation has not yet begun -->
621
+ <slot
622
+ :set-value="setValue"
623
+ :values="userValues"
624
+ name="values"
625
+ />
626
+ </div>
627
+ <div v-if="errors && errors.length">
628
+ <slot
629
+ :errors="errors"
630
+ name="errors"
631
+ />
632
+ </div>
633
+ <AsyncButton
634
+ v-if="canCreateRepos && buttonStage === stageNames.ADD_REPO || canInstallChart && buttonStage !== stageNames.ADD_REPO"
635
+ :current-phase="buttonPhase"
636
+ :action-label="stages[buttonStage].actionLabel"
637
+ :waiting-label="stages[buttonStage].waitingLabel"
638
+ :success-label="stages[buttonStage].successLabel"
639
+ :error-label="stages[buttonStage].errorLabel"
640
+ :delay="delay"
641
+ type="button"
642
+ class="btn role-primary"
643
+ @click="cb=>{btnCb=cb;stages[buttonStage].action()}"
644
+ />
645
+ <div>
646
+ <button
647
+ v-if="readyToInstall"
648
+ type="button"
649
+ class="btn btn-sm role-link"
650
+ @click="goToInstall"
651
+ >
652
+ {{ t('catalog.install.button.customizeInstall') }}
653
+ </button>
654
+ </div>
655
+ </div>
656
+ </template>