@rancher/shell 3.0.1-rc.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/assets/data/aws-regions.json +1 -0
  2. package/assets/styles/base/_basic.scss +5 -0
  3. package/assets/styles/base/_mixins.scss +8 -0
  4. package/assets/styles/global/_button.scss +5 -0
  5. package/assets/styles/themes/_dark.scss +2 -0
  6. package/assets/styles/themes/_light.scss +2 -0
  7. package/assets/translations/en-us.yaml +40 -22
  8. package/assets/translations/zh-hans.yaml +1 -7
  9. package/chart/monitoring/StorageClassSelector.vue +1 -1
  10. package/components/AssignTo.vue +1 -0
  11. package/components/AsyncButton.vue +1 -0
  12. package/components/BackLink.vue +8 -2
  13. package/components/PaginatedResourceTable.vue +135 -0
  14. package/components/ResourceDetail/Masthead.vue +1 -1
  15. package/components/ResourceDetail/index.vue +66 -11
  16. package/components/ResourceList/index.vue +0 -1
  17. package/components/ResourceTable.vue +6 -1
  18. package/components/ResourceYaml.vue +0 -53
  19. package/components/SortableTable/index.vue +8 -6
  20. package/components/Tabbed/index.vue +35 -2
  21. package/components/form/ResourceLabeledSelect.vue +2 -2
  22. package/components/form/ResourceTabs/index.vue +0 -23
  23. package/components/form/Taints.vue +1 -1
  24. package/components/form/UnitInput.vue +1 -1
  25. package/components/form/__tests__/UnitInput.test.ts +1 -1
  26. package/components/nav/TopLevelMenu.helper.ts +546 -0
  27. package/components/nav/TopLevelMenu.vue +125 -160
  28. package/components/nav/WindowManager/ContainerShell.vue +13 -4
  29. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +20 -18
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
  31. package/composables/useLabeledFormElement.ts +6 -2
  32. package/config/pagination-table-headers.js +4 -4
  33. package/config/product/explorer.js +2 -0
  34. package/config/router/navigation-guards/index.js +1 -2
  35. package/config/router/routes.js +1 -1
  36. package/config/settings.ts +15 -8
  37. package/core/plugin.ts +8 -1
  38. package/core/types-provisioning.ts +5 -0
  39. package/core/types.ts +26 -1
  40. package/dialog/DrainNode.vue +6 -6
  41. package/edit/catalog.cattle.io.clusterrepo.vue +95 -52
  42. package/edit/provisioning.cattle.io.cluster/index.vue +8 -3
  43. package/edit/workload/index.vue +1 -1
  44. package/edit/workload/storage/csi/index.vue +29 -1
  45. package/edit/workload/storage/index.vue +1 -0
  46. package/initialize/App.vue +3 -10
  47. package/initialize/install-plugins.js +1 -2
  48. package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +6 -2
  49. package/list/node.vue +8 -5
  50. package/mixins/resource-fetch-api-pagination.js +40 -5
  51. package/mixins/resource-fetch.js +48 -5
  52. package/models/management.cattle.io.nodepool.js +5 -4
  53. package/models/nodedriver.js +2 -2
  54. package/models/provisioning.cattle.io.cluster.js +3 -11
  55. package/package.json +7 -8
  56. package/pages/about.vue +22 -0
  57. package/pages/auth/setup.vue +7 -28
  58. package/pages/c/_cluster/explorer/__tests__/index.test.ts +36 -24
  59. package/pages/c/_cluster/explorer/index.vue +100 -59
  60. package/pages/home.vue +308 -123
  61. package/plugins/dashboard-store/__tests__/mutations.test.ts +2 -0
  62. package/plugins/dashboard-store/actions.js +29 -19
  63. package/plugins/dashboard-store/getters.js +5 -2
  64. package/plugins/dashboard-store/mutations.js +4 -2
  65. package/plugins/steve/__tests__/mutations.test.ts +2 -1
  66. package/plugins/steve/steve-pagination-utils.ts +25 -2
  67. package/plugins/steve/subscribe.js +22 -8
  68. package/rancher-components/Banner/Banner.vue +1 -0
  69. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -0
  70. package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
  71. package/rancher-components/Form/Radio/RadioButton.vue +2 -0
  72. package/rancher-components/Form/Radio/RadioGroup.vue +2 -0
  73. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +2 -0
  74. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -0
  75. package/rancher-components/StringList/StringList.test.ts +15 -15
  76. package/rancher-components/StringList/StringList.vue +3 -0
  77. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
  78. package/scripts/extension/parse-tag-name +2 -0
  79. package/scripts/test-plugins-build.sh +4 -2
  80. package/store/index.js +31 -9
  81. package/tsconfig.json +7 -1
  82. package/types/resources/settings.d.ts +1 -1
  83. package/types/shell/index.d.ts +1107 -1276
  84. package/types/store/dashboard-store.types.ts +4 -0
  85. package/types/store/pagination.types.ts +13 -0
  86. package/types/store/vuex.d.ts +8 -0
  87. package/types/vue-shim.d.ts +6 -31
  88. package/utils/cluster.js +92 -1
  89. package/utils/pagination-utils.ts +17 -8
  90. package/utils/pagination-wrapper.ts +70 -0
  91. package/utils/uiplugins.ts +307 -0
  92. package/components/templates/error.vue +0 -131
  93. package/config/router/navigation-guards/history.js +0 -13
  94. package/plugins/back-button.js +0 -3
@@ -38,5 +38,9 @@ export interface ActionFindPageArgs extends ActionCoreFindArgs {
38
38
  * The single namespace to filter by (used in url path, not part of pagination params)
39
39
  */
40
40
  namespaced?: string,
41
+ /**
42
+ * Result of request is transient and not persisted to store
43
+ */
44
+ transient?: boolean,
41
45
  hasManualRefresh?: boolean,
42
46
  }
@@ -455,3 +455,16 @@ export interface StorePagination {
455
455
  */
456
456
  result: StorePaginationResult
457
457
  }
458
+
459
+ /**
460
+ * The resource and context that the pagination request will be used
461
+ *
462
+ * Used to determine if the request is supported
463
+ */
464
+ export interface PaginationResourceContext {
465
+ store: string,
466
+ resource?: {
467
+ id: string,
468
+ context?: string,
469
+ }
470
+ }
@@ -7,3 +7,11 @@
7
7
  export interface VuexStoreGetters {
8
8
  [name:string]: Function
9
9
  }
10
+
11
+ export interface VuexStore {
12
+ getters: VuexStoreGetters,
13
+ dispatch: any,
14
+
15
+ // When we have exact properties above we can remove below
16
+ [name:string]: any
17
+ }
@@ -1,36 +1,13 @@
1
- // eslint-disable-next-line no-unused-vars
2
- import Vue, { ComponentCustomProperties } from 'vue';
3
- declare module '*.vue' {
4
- export default Vue;
5
- }
1
+ /* eslint-disable */
2
+ import type { DefineComponent } from 'vue'
3
+ import { ComponentCustomProperties } from 'vue';
6
4
 
7
- // This is required to keep typescript from complaining. It is required for
8
- // our i18n plugin. For more info see:
9
- // https://v2.vuejs.org/v2/guide/typescript.html?redirect=true#Augmenting-Types-for-Use-with-Plugins
10
- declare module 'vue/types/vue' {
11
- // eslint-disable-next-line no-unused-vars
12
- interface Vue {
13
- /**
14
- * Lookup a given string with the given arguments
15
- * @param raw if set, do not do HTML escaping.
16
- */
17
- t: {
18
- (key: string, args?: Record<string, any>, raw?: boolean): string;
19
- (options: { k: string; raw?: boolean; tag?: string | Record<string, any>; escapehtml?: boolean }): string;
20
- };
21
- }
5
+ declare module '*.vue' {
6
+ const component: DefineComponent<{}, {}, any>
7
+ export default component
22
8
  }
23
9
 
24
10
  declare module '@vue/runtime-core' {
25
- // eslint-disable-next-line no-unused-vars
26
- interface Vue {
27
- t: {
28
- (key: string, args?: Record<string, any>, raw?: boolean): string;
29
- (options: { k: string; raw?: boolean; tag?: string | Record<string, any>; escapehtml?: boolean }): string;
30
- }
31
- }
32
-
33
- // eslint-disable-next-line no-unused-vars
34
11
  interface ComponentCustomProperties {
35
12
  t: {
36
13
  (key: string, args?: Record<string, any>, raw?: boolean): string;
@@ -47,5 +24,3 @@ declare module '@vue/runtime-core' {
47
24
  }
48
25
  }
49
26
  }
50
-
51
- declare module 'js-yaml';
package/utils/cluster.js CHANGED
@@ -3,8 +3,99 @@ import { camelToTitle } from '@shell/utils/string';
3
3
  import { CAPI } from '@shell/config/labels-annotations';
4
4
  import { MANAGEMENT, VIRTUAL_HARVESTER_PROVIDER } from '@shell/config/types';
5
5
  import { SETTING } from '@shell/config/settings';
6
+ import { PaginationFilterField, PaginationParamFilter } from '@shell/types/store/pagination.types';
6
7
 
7
- // Filter out any clusters that are not Kubernetes Clusters
8
+ /**
9
+ * Combination of paginationFilterHiddenLocalCluster and paginationFilterOnlyKubernetesClusters
10
+ *
11
+ * @param {*} store
12
+ * @returns PaginationParam[]
13
+ */
14
+ export function paginationFilterClusters(store, filterMgmtCluster = true) {
15
+ const paginationRequestFilters = [];
16
+
17
+ // Commenting out for the moment. This is broken for non-paginated world
18
+ // filterOnlyKubernetesClusters expects a mgmt cluster, however in the home page it's given a prov cluster
19
+ // note - filterHiddenLocalCluster works because it uses model isLocal which is on both cluster types
20
+ // const pFilterOnlyKubernetesClusters = paginationFilterOnlyKubernetesClusters(store);
21
+ // if (pFilterOnlyKubernetesClusters) {
22
+ // paginationRequestFilters.push(pFilterOnlyKubernetesClusters);
23
+ // }
24
+ const pFilterHiddenLocalCluster = paginationFilterHiddenLocalCluster(store, filterMgmtCluster);
25
+
26
+ if (pFilterHiddenLocalCluster) {
27
+ paginationRequestFilters.push(pFilterHiddenLocalCluster);
28
+ }
29
+
30
+ return paginationRequestFilters;
31
+ }
32
+
33
+ /**
34
+ * The vai backed api's `filter` equivalent of `filterHiddenLocalCluster`
35
+ *
36
+ * @export
37
+ * @param {*} store
38
+ * @returns PaginationParam | null
39
+ */
40
+ export function paginationFilterHiddenLocalCluster(store, filterMgmtCluster = true) {
41
+ const hideLocalSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.HIDE_LOCAL_CLUSTER) || {};
42
+ const value = hideLocalSetting.value || hideLocalSetting.default || 'false';
43
+ const hideLocal = value === 'true';
44
+
45
+ if (!hideLocal) {
46
+ return null;
47
+ }
48
+
49
+ const filter = filterMgmtCluster ? [
50
+ new PaginationFilterField({
51
+ field: `spec.internal`,
52
+ value: false,
53
+ })
54
+ ] : [
55
+ new PaginationFilterField({
56
+ field: `id`,
57
+ value: 'fleet-local/local',
58
+ exact: true,
59
+ equals: false,
60
+ })
61
+ ];
62
+
63
+ return PaginationParamFilter.createMultipleFields(filter);
64
+ }
65
+
66
+ /**
67
+ * The vai backed api's `filter` equivalent of `filterOnlyKubernetesClusters`
68
+ *
69
+ * @export
70
+ * @param {*} store
71
+ * @returns PaginationParam | null
72
+ */
73
+ export function paginationFilterOnlyKubernetesClusters(store) {
74
+ const openHarvesterContainerWorkload = store.getters['features/get']('harvester-baremetal-container-workload');
75
+
76
+ if (!openHarvesterContainerWorkload) {
77
+ return null;
78
+ }
79
+
80
+ return PaginationParamFilter.createMultipleFields([
81
+ new PaginationFilterField({
82
+ field: `metadata.labels."${ CAPI.PROVIDER }"`, // Pending API Support - https://github.com/rancher/rancher/issues/48256
83
+ equals: false,
84
+ value: VIRTUAL_HARVESTER_PROVIDER,
85
+ exact: true
86
+ }),
87
+ new PaginationFilterField({
88
+ field: `status.provider`, // Pending API Support - https://github.com/rancher/rancher/issues/48256
89
+ equals: false,
90
+ value: VIRTUAL_HARVESTER_PROVIDER,
91
+ exact: true
92
+ }),
93
+ ]);
94
+ }
95
+
96
+ /**
97
+ * Filter out any clusters that are not Kubernetes Clusters
98
+ **/
8
99
  export function filterOnlyKubernetesClusters(mgmtClusters, store) {
9
100
  const openHarvesterContainerWorkload = store.getters['features/get']('harvester-baremetal-container-workload');
10
101
 
@@ -9,7 +9,7 @@ import {
9
9
  NAMESPACE_FILTER_NS_FULL_PREFIX,
10
10
  NAMESPACE_FILTER_P_FULL_PREFIX,
11
11
  } from '@shell/utils/namespace-filter';
12
- import { PaginationArgs, PaginationParam, PaginationSort } from '@shell/types/store/pagination.types';
12
+ import { PaginationArgs, PaginationResourceContext, PaginationParam, PaginationSort } from '@shell/types/store/pagination.types';
13
13
  import { sameArrayObjects } from '@shell/utils/array';
14
14
  import { isEqual } from '@shell/utils/object';
15
15
  import { STEVE_CACHE } from '@shell/store/features';
@@ -40,12 +40,7 @@ class PaginationUtils {
40
40
  /**
41
41
  * Is pagination enabled at a global level or for a specific resource
42
42
  */
43
- isEnabled({ rootGetters }: any, enabledFor: {
44
- store: string,
45
- resource?: {
46
- id: string,
47
- }
48
- }) {
43
+ isEnabled({ rootGetters }: any, enabledFor: PaginationResourceContext) {
49
44
  // Cache must be enabled to support pagination api
50
45
  if (!this.isSteveCacheEnabled({ rootGetters })) {
51
46
  return false;
@@ -95,7 +90,21 @@ class PaginationUtils {
95
90
  return true;
96
91
  }
97
92
 
98
- if (storeSettings.resources.enableSome.enabled.includes(enabledFor.resource.id)) {
93
+ if (storeSettings.resources.enableSome.enabled.find((setting) => {
94
+ if (typeof setting === 'string') {
95
+ return setting === enabledFor.resource?.id;
96
+ }
97
+
98
+ if (setting.resource === enabledFor.resource?.id) {
99
+ if (!!setting.context) {
100
+ return enabledFor.resource?.context ? setting.context.includes(enabledFor.resource.context) : false;
101
+ }
102
+
103
+ return true;
104
+ }
105
+
106
+ return false;
107
+ })) {
99
108
  return true;
100
109
  }
101
110
 
@@ -0,0 +1,70 @@
1
+ import paginationUtils from '@shell/utils/pagination-utils';
2
+ import { PaginationArgs, PaginationResourceContext, StorePagination } from '@shell/types/store/pagination.types';
3
+ import { VuexStore } from '@shell/types/store/vuex';
4
+ import { ActionFindPageArgs } from '@shell/types/store/dashboard-store.types';
5
+
6
+ interface Result<T> {
7
+ data: Array<T>
8
+ pagination: StorePagination
9
+ }
10
+
11
+ /**
12
+ * This is a helper class that will assist in fetching a resource
13
+ * - Handle if the resource can be fetched by the new pagination api
14
+ * - Make a request to get a page (including classify)
15
+ * - Provide updates when the resource changes
16
+ *
17
+ * This is designed to work in places where we don't/can't store the resource in the store
18
+ * - There already exists a resource we don't want to overwrite
19
+ * - We're transient and want something nicer than just cluster/request
20
+ */
21
+ class PaginationWrapper<T = any> {
22
+ private $store: VuexStore;
23
+ private enabledFor: PaginationResourceContext;
24
+
25
+ // Blocked on https://github.com/rancher/rancher/issues/40773 / https://github.com/rancher/dashboard/issues/12734
26
+ private onUpdate: (out: Result<T>) => void;
27
+
28
+ public isEnabled: boolean;
29
+
30
+ constructor({
31
+ $store,
32
+ enabledFor,
33
+ onUpdate,
34
+ }: {
35
+ $store: VuexStore,
36
+ onUpdate: (res: Result<T>) => void,
37
+ enabledFor: PaginationResourceContext,
38
+ }) {
39
+ this.$store = $store;
40
+ this.isEnabled = paginationUtils.isEnabled({ rootGetters: $store.getters }, enabledFor);
41
+ this.enabledFor = enabledFor;
42
+ this.onUpdate = onUpdate;
43
+ }
44
+
45
+ async request(args: {
46
+ pagination: PaginationArgs,
47
+ classify?: boolean,
48
+ }): Promise<Result<T>> {
49
+ if (!this.isEnabled) {
50
+ throw new Error(`Wrapper for type '${ this.enabledFor.store }/${ this.enabledFor.resource?.id }' in context '${ this.enabledFor.resource?.context }' not supported`);
51
+ }
52
+ const { pagination, classify: doClassify } = args;
53
+ const opt: ActionFindPageArgs = {
54
+ transient: true,
55
+ pagination
56
+ };
57
+
58
+ const out: Result<T> = await this.$store.dispatch(`${ this.enabledFor.store }/findPage`, { opt, type: this.enabledFor.resource?.id });
59
+
60
+ if (doClassify) {
61
+ for (let i = 0; i < out.data.length; i++) {
62
+ out.data[i] = await this.$store.dispatch(`${ this.enabledFor.store }/create`, out.data[i]);
63
+ }
64
+ }
65
+
66
+ return out;
67
+ }
68
+ }
69
+
70
+ export default PaginationWrapper;
@@ -0,0 +1,307 @@
1
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
2
+ import { CATALOG } from '@shell/config/types';
3
+ import { UI_PLUGIN_BASE_URL, isSupportedChartVersion } from '@shell/config/uiplugins';
4
+
5
+ const MAX_RETRIES = 10;
6
+ const RETRY_WAIT = 2500;
7
+
8
+ type Action = 'install' | 'upgrade';
9
+ export type HelmRepository = any;
10
+ export type HelmChart = any;
11
+
12
+ /**
13
+ *
14
+ * @param store Vue store
15
+ * @param chartName The chartName
16
+ * @param rancherVersion Rancher version
17
+ * @param kubeVersion K8s version
18
+ * @param opt Store options
19
+ * @returns The latest compatible version of the extension; return null If there are no compatible versions.
20
+ */
21
+ export async function getLatestExtensionVersion(
22
+ store: any,
23
+ chartName: string,
24
+ rancherVersion: string,
25
+ kubeVersion: string,
26
+ opt = { reset: true, force: true },
27
+ ) {
28
+ await store.dispatch('catalog/load', opt);
29
+
30
+ const chart = store.getters['catalog/chart']({ chartName });
31
+
32
+ const versions = chart?.versions || [];
33
+
34
+ const compatibleVersions = versions.filter((version: any) => isSupportedChartVersion({
35
+ version, rancherVersion, kubeVersion
36
+ }));
37
+
38
+ return compatibleVersions[0]?.version;
39
+ }
40
+
41
+ /**
42
+ * Wait for a given UI Extension to be available
43
+ *
44
+ * @param store Vue store
45
+ * @param name Name of the extension
46
+ * @returns the extension object when available, null if timed out waiting for it to be available
47
+ */
48
+ export async function waitForUIExtension(store: any, name: string): Promise<any> {
49
+ let tries = 0;
50
+
51
+ while (true) {
52
+ try {
53
+ const res = await store.dispatch('management/request', {
54
+ url: `${ UI_PLUGIN_BASE_URL }`,
55
+ method: 'GET',
56
+ headers: { accept: 'application/json' },
57
+ redirectUnauthorized: false,
58
+ });
59
+
60
+ const entries = res.entries || res.Entries || {};
61
+ const extension = entries[name];
62
+
63
+ if (extension) {
64
+ return extension;
65
+ }
66
+ } catch (e) {
67
+ }
68
+
69
+ tries++;
70
+
71
+ if (tries > MAX_RETRIES) {
72
+ return null;
73
+ }
74
+
75
+ await new Promise((resolve) => setTimeout(resolve, RETRY_WAIT));
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Wait for a given UI Extension package to be available
81
+ *
82
+ * @param store Vue store
83
+ * @param extension Extension object
84
+ * @returns true when available, false if timed out waiting for it to be available
85
+ */
86
+ export async function waitForUIPackage(store: any, extension: any): Promise<boolean> {
87
+ let tries = 0;
88
+
89
+ const { name, version } = extension;
90
+
91
+ while (true) {
92
+ try {
93
+ await store.dispatch('management/request', {
94
+ url: `${ UI_PLUGIN_BASE_URL }/${ name }/${ version }/plugin/${ name }-${ version }.umd.min.js`,
95
+ method: 'GET',
96
+ headers: { accept: 'application/json' },
97
+ redirectUnauthorized: false,
98
+ });
99
+
100
+ return true;
101
+ } catch (error) {
102
+ }
103
+
104
+ tries++;
105
+
106
+ if (tries > MAX_RETRIES) {
107
+ return false;
108
+ }
109
+
110
+ await new Promise((resolve) => setTimeout(resolve, RETRY_WAIT));
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Install Helm Chart
116
+ *
117
+ * Note: This should really be provided via the shell rather than copied here
118
+ */
119
+ export async function installHelmChart(repo: any, chart: any, values: any = {}, namespace = 'default', action: Action = 'install') {
120
+ /*
121
+ Refer to the developer docs at docs/developer/helm-chart-apps.md
122
+ for details on what values are injected and where they come from.
123
+ */
124
+ // TODO: This is needed in order to support system registry for air-gapped environments
125
+ // this.addGlobalValuesTo(values);
126
+
127
+ const chartInstall = {
128
+ chartName: chart.name,
129
+ version: chart.version,
130
+ releaseName: chart.name,
131
+ description: chart.name,
132
+ annotations: {
133
+ [CATALOG_ANNOTATIONS.SOURCE_REPO_TYPE]: chart.repoType,
134
+ [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: chart.repoName
135
+ },
136
+ values,
137
+ };
138
+
139
+ /*
140
+ Configure Helm CLI options for doing the install or
141
+ upgrade operation.
142
+ */
143
+ const installRequest = {
144
+ charts: [chartInstall],
145
+ noHooks: false,
146
+ timeout: '1000s',
147
+ wait: true,
148
+ namespace,
149
+ projectId: '',
150
+ disableOpenAPIValidation: false,
151
+ skipCRDs: false,
152
+ };
153
+
154
+ // Install the Chart
155
+ const res = await repo.doAction(action, installRequest);
156
+
157
+ return res;
158
+ }
159
+
160
+ /**
161
+ * Get the Helm repository object
162
+ *
163
+ * @param store Vue store
164
+ * @param url The url of the Helm repository
165
+ * @param branch The branch of the Helm repository
166
+ * @returns HelmRepository
167
+ */
168
+ export async function getHelmRepository(store: any, url: string, branch?: string): Promise<HelmRepository> {
169
+ if (store.getters['management/schemaFor'](CATALOG.CLUSTER_REPO)) {
170
+ const repos = await store.dispatch('management/findAll', { type: CATALOG.CLUSTER_REPO, opt: { force: true, watch: false } });
171
+
172
+ return repos.find((r: any) => {
173
+ const target = branch ? r.spec?.gitRepo : r.spec?.url ;
174
+
175
+ return target === url;
176
+ });
177
+ } else {
178
+ throw new Error('No permissions');
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Refresh the Helm repository
184
+ * Ensures that we find the latest extension versions
185
+ *
186
+ * @param store Vue store
187
+ * @param gitRepo Extension Repository url
188
+ * @param gitBranch Extension Repository branch
189
+ */
190
+ export async function refreshHelmRepository(store: any, gitRepo: string, gitBranch: string): Promise<void> {
191
+ const repository = await getHelmRepository(store, gitRepo, gitBranch);
192
+
193
+ const now = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
194
+
195
+ repository.spec.forceUpdate = now;
196
+
197
+ await repository.save();
198
+
199
+ await repository.waitForState('active', 10000, 1000);
200
+
201
+ await new Promise((resolve) => setTimeout(resolve, 2000));
202
+ }
203
+
204
+ /**
205
+ * Ensure the required Helm Repository exits, if it does not, add it with the specified name
206
+ *
207
+ * Wait until the newly added repository has been downloaded
208
+ *
209
+ * @param store Vue store
210
+ * @param url The url of the Helm repository
211
+ * @param name The name of the cluster repository
212
+ * @param branch The branch of the Helm repository
213
+ * @returns HelmRepository object
214
+ */
215
+ export async function ensureHelmRepository(store: any, url: string, name: string, branch?: string): Promise<HelmRepository> {
216
+ let helmRepo = await getHelmRepository(store, url, branch);
217
+
218
+ // Add the Helm repository, if it is not there
219
+ if (!helmRepo) {
220
+ const data = {
221
+ type: CATALOG.CLUSTER_REPO,
222
+ metadata: { name },
223
+ spec: {} as any
224
+ };
225
+
226
+ if (branch) {
227
+ data.spec.gitBranch = branch;
228
+ data.spec.gitRepo = url;
229
+ } else {
230
+ data.spec.url = url;
231
+ }
232
+
233
+ // Create a model for the new repository and save it
234
+ const repo = await store.dispatch('management/create', data);
235
+
236
+ helmRepo = await repo.save();
237
+ }
238
+
239
+ // Poll the repository until it says it has been downloaded
240
+ let fetched = false;
241
+ let tries = 0;
242
+
243
+ while (!fetched) {
244
+ const repo = await store.dispatch('management/find', {
245
+ type: CATALOG.CLUSTER_REPO,
246
+ id: helmRepo.id, // Get the ID from the Helm Repository
247
+ opt: { force: true, watch: false }
248
+ });
249
+
250
+ tries++;
251
+
252
+ const downloaded = repo.status.conditions.find((s: any) => s.type === 'Downloaded');
253
+
254
+ if (downloaded) {
255
+ if (downloaded.status === 'True') {
256
+ fetched = true;
257
+ }
258
+ }
259
+
260
+ if (!fetched) {
261
+ tries++;
262
+
263
+ if (tries > MAX_RETRIES) {
264
+ throw new Error('Failed to add Helm Chart Repository');
265
+ }
266
+
267
+ await new Promise((resolve) => setTimeout(resolve, RETRY_WAIT));
268
+ }
269
+
270
+ fetched = true;
271
+ }
272
+
273
+ // Return the Helm Repository
274
+ return helmRepo;
275
+ }
276
+
277
+ /**
278
+ * Get the given Helm Chart from the specified Helm Repository
279
+ *
280
+ * @param store Vue store
281
+ * @param repository Helm Repository
282
+ * @param chartName Helm Chart name
283
+ * @returns Helm Chart
284
+ */
285
+ export async function getHelmChart(store: any, repository: any, chartName: string): Promise<HelmChart | null> {
286
+ let tries = 0;
287
+
288
+ while (true) {
289
+ try {
290
+ const versionInfo = await store.dispatch('management/request', {
291
+ method: 'GET',
292
+ url: `${ repository?.links?.info }&chartName=${ chartName }`,
293
+ });
294
+
295
+ return versionInfo.chart;
296
+ } catch (error) {
297
+ }
298
+
299
+ tries++;
300
+
301
+ if (tries > MAX_RETRIES) {
302
+ return null;
303
+ }
304
+
305
+ await new Promise((resolve) => setTimeout(resolve, RETRY_WAIT));
306
+ }
307
+ }