@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.
- package/assets/data/aws-regions.json +1 -0
- package/assets/styles/base/_basic.scss +5 -0
- package/assets/styles/base/_mixins.scss +8 -0
- package/assets/styles/global/_button.scss +5 -0
- package/assets/styles/themes/_dark.scss +2 -0
- package/assets/styles/themes/_light.scss +2 -0
- package/assets/translations/en-us.yaml +40 -22
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/StorageClassSelector.vue +1 -1
- package/components/AssignTo.vue +1 -0
- package/components/AsyncButton.vue +1 -0
- package/components/BackLink.vue +8 -2
- package/components/PaginatedResourceTable.vue +135 -0
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +66 -11
- package/components/ResourceList/index.vue +0 -1
- package/components/ResourceTable.vue +6 -1
- package/components/ResourceYaml.vue +0 -53
- package/components/SortableTable/index.vue +8 -6
- package/components/Tabbed/index.vue +35 -2
- package/components/form/ResourceLabeledSelect.vue +2 -2
- package/components/form/ResourceTabs/index.vue +0 -23
- package/components/form/Taints.vue +1 -1
- package/components/form/UnitInput.vue +1 -1
- package/components/form/__tests__/UnitInput.test.ts +1 -1
- package/components/nav/TopLevelMenu.helper.ts +546 -0
- package/components/nav/TopLevelMenu.vue +125 -160
- package/components/nav/WindowManager/ContainerShell.vue +13 -4
- package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +20 -18
- package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
- package/composables/useLabeledFormElement.ts +6 -2
- package/config/pagination-table-headers.js +4 -4
- package/config/product/explorer.js +2 -0
- package/config/router/navigation-guards/index.js +1 -2
- package/config/router/routes.js +1 -1
- package/config/settings.ts +15 -8
- package/core/plugin.ts +8 -1
- package/core/types-provisioning.ts +5 -0
- package/core/types.ts +26 -1
- package/dialog/DrainNode.vue +6 -6
- package/edit/catalog.cattle.io.clusterrepo.vue +95 -52
- package/edit/provisioning.cattle.io.cluster/index.vue +8 -3
- package/edit/workload/index.vue +1 -1
- package/edit/workload/storage/csi/index.vue +29 -1
- package/edit/workload/storage/index.vue +1 -0
- package/initialize/App.vue +3 -10
- package/initialize/install-plugins.js +1 -2
- package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +6 -2
- package/list/node.vue +8 -5
- package/mixins/resource-fetch-api-pagination.js +40 -5
- package/mixins/resource-fetch.js +48 -5
- package/models/management.cattle.io.nodepool.js +5 -4
- package/models/nodedriver.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +3 -11
- package/package.json +7 -8
- package/pages/about.vue +22 -0
- package/pages/auth/setup.vue +7 -28
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +36 -24
- package/pages/c/_cluster/explorer/index.vue +100 -59
- package/pages/home.vue +308 -123
- package/plugins/dashboard-store/__tests__/mutations.test.ts +2 -0
- package/plugins/dashboard-store/actions.js +29 -19
- package/plugins/dashboard-store/getters.js +5 -2
- package/plugins/dashboard-store/mutations.js +4 -2
- package/plugins/steve/__tests__/mutations.test.ts +2 -1
- package/plugins/steve/steve-pagination-utils.ts +25 -2
- package/plugins/steve/subscribe.js +22 -8
- package/rancher-components/Banner/Banner.vue +1 -0
- package/rancher-components/Form/Checkbox/Checkbox.vue +2 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
- package/rancher-components/Form/Radio/RadioButton.vue +2 -0
- package/rancher-components/Form/Radio/RadioGroup.vue +2 -0
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +2 -0
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -0
- package/rancher-components/StringList/StringList.test.ts +15 -15
- package/rancher-components/StringList/StringList.vue +3 -0
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
- package/scripts/extension/parse-tag-name +2 -0
- package/scripts/test-plugins-build.sh +4 -2
- package/store/index.js +31 -9
- package/tsconfig.json +7 -1
- package/types/resources/settings.d.ts +1 -1
- package/types/shell/index.d.ts +1107 -1276
- package/types/store/dashboard-store.types.ts +4 -0
- package/types/store/pagination.types.ts +13 -0
- package/types/store/vuex.d.ts +8 -0
- package/types/vue-shim.d.ts +6 -31
- package/utils/cluster.js +92 -1
- package/utils/pagination-utils.ts +17 -8
- package/utils/pagination-wrapper.ts +70 -0
- package/utils/uiplugins.ts +307 -0
- package/components/templates/error.vue +0 -131
- package/config/router/navigation-guards/history.js +0 -13
- 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
|
+
}
|
package/types/store/vuex.d.ts
CHANGED
package/types/vue-shim.d.ts
CHANGED
|
@@ -1,36 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
export default Vue;
|
|
5
|
-
}
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import type { DefineComponent } from 'vue'
|
|
3
|
+
import { ComponentCustomProperties } from 'vue';
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
+
}
|