@rancher/shell 3.0.5-rc.8 → 3.0.5
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/styles/base/_color.scss +4 -1
- package/assets/styles/global/_tooltip.scss +7 -4
- package/assets/styles/themes/_dark.scss +11 -0
- package/assets/styles/themes/_light.scss +13 -1
- package/assets/styles/themes/_modern.scss +22 -0
- package/assets/translations/en-us.yaml +147 -19
- package/assets/translations/zh-hans.yaml +0 -1
- package/chart/monitoring/grafana/index.vue +8 -2
- package/components/ActionMenuShell.vue +3 -1
- package/components/Cron/CronExpressionEditor.vue +299 -0
- package/components/Cron/CronExpressionEditorModal.vue +247 -0
- package/components/Cron/CronTooltip.vue +87 -0
- package/components/Cron/types.ts +13 -0
- package/components/ForceDirectedTreeChart/composable.ts +11 -0
- package/components/PodSecurityAdmission.vue +2 -0
- package/components/PromptModal.vue +1 -1
- package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
- package/components/Resource/Detail/CopyToClipboard.vue +78 -0
- package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
- package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
- package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
- package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +24 -38
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
- package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
- package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
- package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
- package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
- package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
- package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +0 -3
- package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
- package/components/Resource/Detail/Metadata/composables.ts +1 -4
- package/components/Resource/Detail/Metadata/index.vue +1 -0
- package/components/Resource/Detail/Preview/Content.vue +63 -0
- package/components/Resource/Detail/Preview/Preview.vue +128 -0
- package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
- package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
- package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
- package/components/Resource/Detail/SpacedRow.vue +1 -0
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +0 -5
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
- package/components/Resource/Detail/TitleBar/composables.ts +1 -3
- package/components/Resource/Detail/TitleBar/index.vue +2 -29
- package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
- package/components/Resource/Detail/ViewOptions/index.vue +41 -0
- package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
- package/components/ResourceDetail/Masthead/legacy.vue +0 -19
- package/components/ResourceDetail/index.vue +1 -26
- package/components/ResourceTable.vue +24 -0
- package/components/SortableTable/index.vue +7 -1
- package/components/SortableTable/paging.js +3 -0
- package/components/Tabbed/Tab.vue +43 -1
- package/components/Tabbed/index.vue +3 -1
- package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
- package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
- package/components/auth/login/saml.vue +86 -0
- package/components/form/LabeledSelect.vue +8 -8
- package/components/form/ProjectMemberEditor.vue +2 -0
- package/components/form/ResourceTabs/composable.ts +54 -0
- package/components/form/ResourceTabs/index.vue +10 -7
- package/components/form/Select.vue +13 -10
- package/components/form/__tests__/LabeledSelect.test.ts +133 -0
- package/components/form/__tests__/Select.test.ts +134 -0
- package/components/nav/Header.vue +6 -5
- package/composables/useExtensionManager.ts +17 -0
- package/config/home-links.js +12 -0
- package/config/labels-annotations.js +0 -1
- package/config/page-actions.js +0 -1
- package/config/product/explorer.js +3 -1
- package/config/product/fleet.js +2 -7
- package/config/product/manager.js +0 -5
- package/config/query-params.js +1 -0
- package/config/router/navigation-guards/clusters.js +2 -1
- package/config/router/navigation-guards/products.js +1 -1
- package/config/store.js +2 -0
- package/core/extension-manager-impl.js +518 -0
- package/core/plugins.js +35 -468
- package/core/types.ts +8 -2
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
- package/detail/catalog.cattle.io.app.vue +7 -4
- package/detail/fleet.cattle.io.bundle.vue +1 -5
- package/detail/fleet.cattle.io.cluster.vue +3 -2
- package/detail/fleet.cattle.io.gitrepo.vue +76 -49
- package/detail/fleet.cattle.io.helmop.vue +78 -49
- package/dialog/AddonConfigConfirmationDialog.vue +1 -1
- package/dialog/GenericPrompt.vue +1 -1
- package/dialog/ImportDialog.vue +9 -2
- package/dialog/InstallExtensionDialog.vue +18 -10
- package/dialog/SloDialog.vue +1 -1
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
- package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
- package/edit/auth/oidc.vue +106 -6
- package/edit/auth/saml.vue +5 -5
- package/edit/cloudcredential.vue +31 -17
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
- package/edit/fleet.cattle.io.cluster.vue +19 -0
- package/edit/fleet.cattle.io.gitrepo.vue +23 -16
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
- package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -3
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
- package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
- package/edit/resources.cattle.io.restore.vue +5 -8
- package/initialize/install-plugins.js +1 -3
- package/list/__tests__/workload.test.ts +1 -0
- package/list/workload.vue +8 -1
- package/machine-config/components/GCEImage.vue +6 -5
- package/machine-config/google.vue +11 -6
- package/mixins/__tests__/auth-config.test.ts +4 -6
- package/mixins/__tests__/chart.test.ts +139 -1
- package/mixins/auth-config.js +33 -10
- package/mixins/chart.js +58 -18
- package/models/__tests__/namespace.test.ts +69 -0
- package/models/apps.statefulset.js +8 -10
- package/models/chart.js +5 -1
- package/models/fleet-application.js +16 -46
- package/models/fleet.cattle.io.bundle.js +1 -38
- package/models/fleet.cattle.io.gitrepo.js +4 -0
- package/models/fleet.cattle.io.helmop.js +4 -0
- package/models/management.cattle.io.cluster.js +1 -1
- package/models/management.cattle.io.project.js +12 -0
- package/models/namespace.js +30 -0
- package/models/workload.js +4 -1
- package/package.json +10 -10
- package/pages/auth/login.vue +8 -3
- package/pages/auth/logout.vue +6 -5
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -11
- package/pages/c/_cluster/apps/charts/chart.vue +29 -20
- package/pages/c/_cluster/apps/charts/index.vue +1 -0
- package/pages/c/_cluster/apps/charts/install.vue +6 -5
- package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
- package/pages/c/_cluster/explorer/tools/index.vue +145 -254
- package/pages/c/_cluster/manager/cloudCredential/index.vue +18 -1
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
- package/pages/c/_cluster/uiplugins/index.vue +221 -363
- package/pages/home.vue +1 -9
- package/plugins/axios.js +3 -2
- package/plugins/dashboard-store/resource-class.js +49 -0
- package/plugins/ember-cookie.js +7 -3
- package/plugins/steve/subscribe.js +4 -2
- package/public/index.html +2 -1
- package/rancher-components/Card/Card.vue +1 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Form/Radio/RadioButton.vue +1 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
- package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
- package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/index.ts +1 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -3
- package/rancher-components/Pill/RcStatusIndicator/types.ts +1 -1
- package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
- package/rancher-components/Pill/RcTag/index.ts +1 -0
- package/rancher-components/Pill/RcTag/types.ts +9 -0
- package/rancher-components/Pill/types.ts +1 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +1 -0
- package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
- package/scripts/test-plugins-build.sh +0 -1
- package/store/__tests__/catalog.test.ts +63 -0
- package/store/__tests__/cookies.test.ts +72 -0
- package/store/auth.js +33 -10
- package/store/catalog.js +2 -2
- package/store/cookies.ts +30 -0
- package/store/prefs.js +10 -5
- package/store/type-map.js +3 -15
- package/types/extension-manager.ts +26 -0
- package/types/shell/index.d.ts +123 -27
- package/utils/__tests__/product.test.ts +129 -0
- package/utils/__tests__/resource.test.ts +87 -0
- package/utils/alertmanagerconfig.js +2 -2
- package/utils/auth.js +4 -77
- package/utils/product.ts +39 -0
- package/utils/resource.ts +35 -0
- package/utils/select.js +0 -24
- package/utils/validators/formRules/__tests__/index.test.ts +3 -0
- package/utils/validators/formRules/index.ts +2 -1
- package/vue.config.js +1 -1
- package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
- package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
- package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
- package/utils/cookie-universal.js +0 -10
- /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
package/config/home-links.js
CHANGED
|
@@ -3,6 +3,7 @@ import { MANAGEMENT } from '@shell/config/types';
|
|
|
3
3
|
import { SETTING } from '@shell/config/settings';
|
|
4
4
|
import { allHash } from '@shell/utils/promise';
|
|
5
5
|
import { isRancherPrime } from '@shell/config/version';
|
|
6
|
+
import DOMPurify from 'dompurify';
|
|
6
7
|
|
|
7
8
|
// i18n-uses customLinks.defaults.*
|
|
8
9
|
const DEFAULT_LINKS = [
|
|
@@ -112,6 +113,17 @@ export async function fetchLinks(store, hasSupport, isSupportPage, t) {
|
|
|
112
113
|
uiLinks.defaults = defaults;
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
// Check the link values for each custom link
|
|
117
|
+
uiLinks.custom.forEach((link) => {
|
|
118
|
+
const anchor = `<a href="${ link.value }"></a>`;
|
|
119
|
+
const cleanedLink = DOMPurify.sanitize(anchor);
|
|
120
|
+
|
|
121
|
+
if (cleanedLink !== anchor) {
|
|
122
|
+
console.error(`Custom link value "${ link.value }" is not valid for link "${ link.label }"`); // eslint-disable-line no-console
|
|
123
|
+
link.value = '/#';
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
115
127
|
return ensureSupportLink(uiLinks, hasSupport, isSupportPage, t, store);
|
|
116
128
|
}
|
|
117
129
|
|
|
@@ -129,7 +129,6 @@ export const FLEET = {
|
|
|
129
129
|
CLUSTER_NAMESPACE: 'fleet.cattle.io/cluster-namespace',
|
|
130
130
|
CLUSTER: 'fleet.cattle.io/cluster',
|
|
131
131
|
CREATED_BY_USER_ID: 'fleet.cattle.io/created-by-user-id',
|
|
132
|
-
CREATED_BY_USER_NAME: 'fleet.cattle.io/created-by-display-name',
|
|
133
132
|
OCI_STORAGE_SECRET_DEFAULT: 'ui-default-oci-registry',
|
|
134
133
|
OCI_STORAGE_SECRET_GENERATED: 'fleet.cattle.io/bundle-internal-secret',
|
|
135
134
|
};
|
package/config/page-actions.js
CHANGED
|
@@ -176,6 +176,7 @@ export function init(store) {
|
|
|
176
176
|
mapGroup(/^(.*\.)?resources\.cattle\.io$/, 'Backup-Restore');
|
|
177
177
|
mapGroup(/^(.*\.)?cluster\.x-k8s\.io$/, 'clusterProvisioning');
|
|
178
178
|
mapGroup(/^(aks|eks|gke|rke|rke-machine-config|rke-machine|provisioning)\.cattle\.io$/, 'clusterProvisioning');
|
|
179
|
+
mapGroup(/^(.*\.)?(scc)\.cattle\.io$/, 'SCC');
|
|
179
180
|
|
|
180
181
|
const dePaginateBindings = configureConditionalDepaginate({ maxResourceCount: 5000 });
|
|
181
182
|
const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true }) ;
|
|
@@ -494,7 +495,8 @@ export function init(store) {
|
|
|
494
495
|
|
|
495
496
|
headers(MANAGEMENT.PSA, [STATE, NAME_COL, {
|
|
496
497
|
...DESCRIPTION,
|
|
497
|
-
width:
|
|
498
|
+
width: undefined,
|
|
499
|
+
formatter: undefined,
|
|
498
500
|
}, AGE]);
|
|
499
501
|
|
|
500
502
|
headers(STORAGE_CLASS,
|
package/config/product/fleet.js
CHANGED
|
@@ -2,7 +2,6 @@ import { DSL } from '@shell/store/type-map';
|
|
|
2
2
|
import { FLEET } from '@shell/config/types';
|
|
3
3
|
import { STATE, NAME as NAME_COL, AGE, FLEET_APPLICATION_TYPE } from '@shell/config/table-headers';
|
|
4
4
|
import { FLEET as FLEET_FEATURE } from '@shell/store/features';
|
|
5
|
-
import { graphConfig } from '@shell/pages/c/_cluster/fleet/graph/config';
|
|
6
5
|
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
7
6
|
|
|
8
7
|
export const SOURCE_TYPE = {
|
|
@@ -148,12 +147,8 @@ export function init(store) {
|
|
|
148
147
|
FLEET.GIT_REPO_RESTRICTION
|
|
149
148
|
], 'resources');
|
|
150
149
|
|
|
151
|
-
configureType(FLEET.GIT_REPO, {
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
configureType(FLEET.HELM_OP, {
|
|
155
|
-
showListMasthead: false, hasGraph: true, graphConfig
|
|
156
|
-
});
|
|
150
|
+
configureType(FLEET.GIT_REPO, { showListMasthead: false });
|
|
151
|
+
configureType(FLEET.HELM_OP, { showListMasthead: false });
|
|
157
152
|
|
|
158
153
|
weightType(FLEET.GIT_REPO, 110, true);
|
|
159
154
|
weightType(FLEET.HELM_OP, 109, true);
|
|
@@ -71,16 +71,11 @@ export function init(store) {
|
|
|
71
71
|
configureType(CAPI.RANCHER_CLUSTER, {
|
|
72
72
|
showListMasthead: false, namespaced: false, alias: [HCI.CLUSTER]
|
|
73
73
|
});
|
|
74
|
-
// configureType(NORMAN.CLOUD_CREDENTIAL, { showListMasthead: false, namespaced: false });
|
|
75
74
|
weightType(CAPI.RANCHER_CLUSTER, 100, true);
|
|
76
75
|
weightType('cloud-credentials', 99, true);
|
|
77
76
|
weightType('drivers', 98, true);
|
|
78
77
|
weightType(CATALOG.CLUSTER_REPO, 97, true);
|
|
79
78
|
|
|
80
|
-
configureType(NORMAN.CLOUD_CREDENTIAL, {
|
|
81
|
-
showState: false, showAge: false, canYaml: false
|
|
82
|
-
});
|
|
83
|
-
|
|
84
79
|
virtualType({
|
|
85
80
|
labelKey: 'drivers.kontainer.title',
|
|
86
81
|
name: 'rke-kontainer-drivers',
|
package/config/query-params.js
CHANGED
|
@@ -3,8 +3,9 @@ import { ClusterNotFoundError, RedirectToError } from '@shell/utils/error';
|
|
|
3
3
|
import { get } from '@shell/utils/object';
|
|
4
4
|
import { AFTER_LOGIN_ROUTE, WORKSPACE } from '@shell/store/prefs';
|
|
5
5
|
import { NAME as FLEET_NAME } from '@shell/config/product/fleet.js';
|
|
6
|
-
import { validateResource, setProduct } from '@shell/utils/auth';
|
|
7
6
|
import { getClusterFromRoute, getProductFromRoute, getPackageFromRoute, routeRequiresAuthentication } from '@shell/utils/router';
|
|
7
|
+
import { setProduct } from '@shell/utils/product';
|
|
8
|
+
import { validateResource } from '@shell/utils/resource';
|
|
8
9
|
|
|
9
10
|
export function install(router, context) {
|
|
10
11
|
router.beforeEach((to, from, next) => loadClusters(to, from, next, context));
|
package/config/store.js
CHANGED
|
@@ -39,6 +39,7 @@ let store = {};
|
|
|
39
39
|
resolveStoreModules(require('../store/customisation.js'), 'customisation.js');
|
|
40
40
|
resolveStoreModules(require('../store/cru-resource.ts'), 'cru-resource.ts');
|
|
41
41
|
resolveStoreModules(require('../store/notifications.ts'), 'notifications.ts');
|
|
42
|
+
resolveStoreModules(require('../store/cookies.ts'), 'cookies.ts');
|
|
42
43
|
|
|
43
44
|
// If the environment supports hot reloading...
|
|
44
45
|
|
|
@@ -69,6 +70,7 @@ let store = {};
|
|
|
69
70
|
'../store/customisation.js',
|
|
70
71
|
'../store/cru-resource.ts',
|
|
71
72
|
'../store/notifications.ts',
|
|
73
|
+
'../store/cookies.ts',
|
|
72
74
|
], () => {
|
|
73
75
|
// Update `root.modules` with the latest definitions.
|
|
74
76
|
updateModules();
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { productsLoaded } from '@shell/store/type-map';
|
|
2
|
+
import { clearModelCache } from '@shell/plugins/dashboard-store/model-loader';
|
|
3
|
+
import { EXT_IDS, Plugin } from './plugin';
|
|
4
|
+
import { PluginRoutes } from './plugin-routes';
|
|
5
|
+
import { UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
|
|
6
|
+
import { ExtensionPoint } from './types';
|
|
7
|
+
import { addLinkInterceptor, removeLinkInterceptor } from '@shell/plugins/clean-html';
|
|
8
|
+
|
|
9
|
+
let extensionManagerInstance;
|
|
10
|
+
|
|
11
|
+
const createExtensionManager = (context) => {
|
|
12
|
+
const {
|
|
13
|
+
app, store, $axios, redirect
|
|
14
|
+
} = context;
|
|
15
|
+
const dynamic = {};
|
|
16
|
+
const validators = {};
|
|
17
|
+
let _lastLoaded = 0;
|
|
18
|
+
|
|
19
|
+
// Track which plugin loaded what, so we can unload stuff
|
|
20
|
+
const plugins = {};
|
|
21
|
+
|
|
22
|
+
const pluginRoutes = new PluginRoutes(app.router);
|
|
23
|
+
|
|
24
|
+
const uiConfig = {};
|
|
25
|
+
|
|
26
|
+
// Builtin extensions - these are registered when the UI loads and then initialized/loaded at the same time as the external extensions
|
|
27
|
+
let builtin = [];
|
|
28
|
+
|
|
29
|
+
for (const ep in ExtensionPoint) {
|
|
30
|
+
uiConfig[ExtensionPoint[ep]] = {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* When an extension adds a model extension, it provides the class - we will instantiate that class and store and use that
|
|
35
|
+
*/
|
|
36
|
+
function instantiateModelExtension($plugin, clz) {
|
|
37
|
+
const context = {
|
|
38
|
+
dispatch: store.dispatch,
|
|
39
|
+
getters: store.getters,
|
|
40
|
+
t: store.getters['i18n/t'],
|
|
41
|
+
$axios,
|
|
42
|
+
$plugin,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return new clz(context);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
// Plugins should not use these - but we will pass them in for now as a 2nd argument
|
|
50
|
+
// in case there are use cases not covered that require direct access - we may remove access later
|
|
51
|
+
internal() {
|
|
52
|
+
const internal = {
|
|
53
|
+
app,
|
|
54
|
+
store,
|
|
55
|
+
$axios,
|
|
56
|
+
redirect,
|
|
57
|
+
plugins: this
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return internal;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Load a plugin from a UI package
|
|
64
|
+
loadPluginAsync(plugin) {
|
|
65
|
+
const { name, version } = plugin;
|
|
66
|
+
const id = `${ name }-${ version }`;
|
|
67
|
+
let url;
|
|
68
|
+
|
|
69
|
+
if (plugin?.metadata?.direct === 'true') {
|
|
70
|
+
url = plugin.endpoint;
|
|
71
|
+
} else {
|
|
72
|
+
// See if the plugin has a main metadata property set
|
|
73
|
+
const main = plugin?.metadata?.main || `${ id }.umd.min.js`;
|
|
74
|
+
|
|
75
|
+
url = `${ UI_PLUGIN_BASE_URL }/${ name }/${ version }/plugin/${ main }`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.loadAsync(id, url);
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Load a plugin from a UI package
|
|
82
|
+
loadAsync(id, mainFile) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
// The plugin is already loaded so we should avoid loading it again.
|
|
85
|
+
// This will primarily affect plugins that load prior to authentication and we attempt to load again after authentication.
|
|
86
|
+
if (document.getElementById(id)) {
|
|
87
|
+
return resolve();
|
|
88
|
+
}
|
|
89
|
+
const moduleUrl = mainFile;
|
|
90
|
+
const element = document.createElement('script');
|
|
91
|
+
|
|
92
|
+
element.src = moduleUrl;
|
|
93
|
+
element.type = 'text/javascript';
|
|
94
|
+
element.async = true;
|
|
95
|
+
element.id = id;
|
|
96
|
+
element.dataset.purpose = 'extension';
|
|
97
|
+
|
|
98
|
+
element.onload = () => {
|
|
99
|
+
if (!window[id] || (typeof window[id].default !== 'function')) {
|
|
100
|
+
return reject(new Error('Could not load plugin code'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Update the timestamp that new plugins were loaded - may be needed
|
|
104
|
+
// to update caches when new plugins are loaded
|
|
105
|
+
_lastLoaded = new Date().getTime();
|
|
106
|
+
|
|
107
|
+
// name is the name of the plugin, including the version number
|
|
108
|
+
const plugin = new Plugin(id);
|
|
109
|
+
|
|
110
|
+
plugins[id] = plugin;
|
|
111
|
+
|
|
112
|
+
// Initialize the plugin
|
|
113
|
+
try {
|
|
114
|
+
window[id].default(plugin, this.internal());
|
|
115
|
+
} catch (e) {
|
|
116
|
+
delete plugins[id];
|
|
117
|
+
|
|
118
|
+
return reject(new Error('Could not initialize plugin'));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Load all of the types etc from the plugin
|
|
122
|
+
this.applyPlugin(plugin);
|
|
123
|
+
|
|
124
|
+
// Add the plugin to the store
|
|
125
|
+
store.dispatch('uiplugins/addPlugin', plugin);
|
|
126
|
+
|
|
127
|
+
resolve();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
element.onerror = (e) => {
|
|
131
|
+
element.parentElement.removeChild(element);
|
|
132
|
+
|
|
133
|
+
// Massage the error into something useful
|
|
134
|
+
const errorMessage = `Failed to load script from '${ e.target.src }'`;
|
|
135
|
+
|
|
136
|
+
console.error(errorMessage, e); // eslint-disable-line no-console
|
|
137
|
+
reject(new Error(errorMessage)); // This is more useful where it's used
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
document.head.appendChild(element);
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Load the builtin extensions by initializing them in turn
|
|
146
|
+
*/
|
|
147
|
+
loadBuiltinExtensions() {
|
|
148
|
+
builtin.forEach((ext) => {
|
|
149
|
+
this.initBuiltinExtension(ext.id, ext.module);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// We've loaded the builtin extensions, so clear out the list so we don't load again
|
|
153
|
+
builtin = [];
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Register a builtin extension that should be loaded
|
|
158
|
+
*
|
|
159
|
+
* Used by the dynamic loader when a plugin is included in the build (see shell/vue.config.js)
|
|
160
|
+
*/
|
|
161
|
+
registerBuiltinExtension(id, module) {
|
|
162
|
+
builtin.push({ id, module });
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Initialize a builtin extension
|
|
167
|
+
*
|
|
168
|
+
* This is only used by the 'loadBuiltinExtensions' function above
|
|
169
|
+
*/
|
|
170
|
+
initBuiltinExtension(id, module) {
|
|
171
|
+
const plugin = new Plugin(id);
|
|
172
|
+
|
|
173
|
+
// Mark the plugin as being built-in
|
|
174
|
+
plugin.builtin = true;
|
|
175
|
+
|
|
176
|
+
plugins[id] = plugin;
|
|
177
|
+
|
|
178
|
+
// Initialize the plugin
|
|
179
|
+
const p = module;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const load = p.default(plugin, this.internal());
|
|
183
|
+
|
|
184
|
+
// The function must explicitly return false to skip loading of the extension (this is only allows on builtin extensions)
|
|
185
|
+
// Only built-in extensions can return that they should not be loaded, because the extension can still do 'things'
|
|
186
|
+
// in its init code (inject code, styles etc), so we do not want to hide an extension that has 'partially' loaded,
|
|
187
|
+
// just because it tells us it should not load.
|
|
188
|
+
// Built-in extensions are compiled into the app, so there is a level of trust assumed with them
|
|
189
|
+
if (load !== false) {
|
|
190
|
+
// Update last load so that the translations get loaded
|
|
191
|
+
_lastLoaded = new Date().getTime();
|
|
192
|
+
|
|
193
|
+
// Load all of the types etc from the extension
|
|
194
|
+
this.applyPlugin(plugin);
|
|
195
|
+
|
|
196
|
+
// Add the extension to the store
|
|
197
|
+
store.dispatch('uiplugins/addPlugin', plugin);
|
|
198
|
+
} else {
|
|
199
|
+
// Plugin did not load, so remove it so it is not shown as loaded
|
|
200
|
+
delete plugins[id];
|
|
201
|
+
}
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(`Error loading extension ${ plugin.name }`); // eslint-disable-line no-console
|
|
204
|
+
console.error(e); // eslint-disable-line no-console
|
|
205
|
+
|
|
206
|
+
// Plugin did not load, so remove it so it is not shown as loaded
|
|
207
|
+
delete plugins[id];
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
async logout() {
|
|
212
|
+
const all = Object.values(plugins);
|
|
213
|
+
|
|
214
|
+
for (let i = 0; i < all.length; i++) {
|
|
215
|
+
const plugin = all[i];
|
|
216
|
+
|
|
217
|
+
if (plugin.builtin) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
await this.removePlugin(plugin.name);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error('Error removing extension', e); // eslint-disable-line no-console
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
delete plugins[plugin.id];
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// Remove the plugin
|
|
232
|
+
async removePlugin(name) {
|
|
233
|
+
const plugin = Object.values(plugins).find((p) => p.name === name);
|
|
234
|
+
|
|
235
|
+
if (!plugin) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const promises = [];
|
|
240
|
+
|
|
241
|
+
plugin.productNames.forEach((product) => {
|
|
242
|
+
promises.push(store.dispatch('type-map/removeProduct', { product, plugin }));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Remove all of the types
|
|
246
|
+
Object.keys(plugin.types).forEach((typ) => {
|
|
247
|
+
Object.keys(plugin.types[typ]).forEach((name) => {
|
|
248
|
+
this.unregister(typ, name);
|
|
249
|
+
|
|
250
|
+
if (typ === EXT_IDS.MODELS) {
|
|
251
|
+
clearModelCache(name);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Remove locales
|
|
257
|
+
plugin.locales.forEach((localeObj) => {
|
|
258
|
+
promises.push(store.dispatch('i18n/removeLocale', localeObj));
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (plugin.types.models) {
|
|
262
|
+
// Ask the Steve stores to forget any data it has for models that we are removing
|
|
263
|
+
promises.push(...this.removeTypeFromStore(store, 'rancher', Object.keys(plugin.types.models)));
|
|
264
|
+
promises.push(...this.removeTypeFromStore(store, 'management', Object.keys(plugin.types.models)));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Call plugin uninstall hooks
|
|
268
|
+
plugin.uninstallHooks.forEach((fn) => fn(plugin, this.internal()));
|
|
269
|
+
|
|
270
|
+
// Remove the plugin itself
|
|
271
|
+
promises.push( store.dispatch('uiplugins/removePlugin', name));
|
|
272
|
+
|
|
273
|
+
// Unregister vuex stores
|
|
274
|
+
plugin.stores.forEach((pStore) => pStore.unregister(store));
|
|
275
|
+
|
|
276
|
+
// Remove validators
|
|
277
|
+
Object.keys(plugin.validators).forEach((key) => {
|
|
278
|
+
delete validators[key];
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Remove link interceptors
|
|
282
|
+
if (plugin.types.linkInterceptor) {
|
|
283
|
+
Object.keys(plugin.types.linkInterceptor).forEach((name) => {
|
|
284
|
+
removeLinkInterceptor(plugin.types.linkInterceptor[name]);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await Promise.all(promises);
|
|
289
|
+
|
|
290
|
+
// Update last load since we removed a plugin
|
|
291
|
+
_lastLoaded = new Date().getTime();
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
removeTypeFromStore(store, storeName, types) {
|
|
295
|
+
return (types || []).map((type) => store.commit(`${ storeName }/forgetType`, type));
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// Apply the plugin based on its metadata
|
|
299
|
+
applyPlugin(plugin) {
|
|
300
|
+
// Types
|
|
301
|
+
Object.keys(plugin.types).forEach((typ) => {
|
|
302
|
+
Object.keys(plugin.types[typ]).forEach((name) => {
|
|
303
|
+
this.register(typ, name, plugin.types[typ][name]);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// UI Configuration - copy UI config from a plugin into the global uiConfig object
|
|
308
|
+
Object.keys(plugin.uiConfig).forEach((actionType) => {
|
|
309
|
+
Object.keys(plugin.uiConfig[actionType]).forEach((actionLocation) => {
|
|
310
|
+
plugin.uiConfig[actionType][actionLocation].forEach((action) => {
|
|
311
|
+
if (!uiConfig[actionType][actionLocation]) {
|
|
312
|
+
uiConfig[actionType][actionLocation] = [];
|
|
313
|
+
}
|
|
314
|
+
uiConfig[actionType][actionLocation].push(action);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// l10n
|
|
320
|
+
Object.keys(plugin.l10n).forEach((name) => {
|
|
321
|
+
plugin.l10n[name].forEach((fn) => {
|
|
322
|
+
this.register('l10n', name, fn);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Model extensions
|
|
327
|
+
Object.keys(plugin.modelExtensions).forEach((name) => {
|
|
328
|
+
plugin.modelExtensions[name].forEach((fn) => {
|
|
329
|
+
this.register(EXT_IDS.MODEL_EXTENSION, name, instantiateModelExtension(this, fn));
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Initialize the product if the store is ready
|
|
334
|
+
if (productsLoaded()) {
|
|
335
|
+
this.loadProducts([plugin]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Register vuex stores
|
|
339
|
+
plugin.stores.forEach((pStore) => pStore.register()(store));
|
|
340
|
+
|
|
341
|
+
// Locales
|
|
342
|
+
plugin.locales.forEach((localeObj) => {
|
|
343
|
+
store.dispatch('i18n/addLocale', localeObj);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Routes
|
|
347
|
+
pluginRoutes.addRoutes(plugin.routes);
|
|
348
|
+
|
|
349
|
+
// Validators
|
|
350
|
+
Object.keys(plugin.validators).forEach((key) => {
|
|
351
|
+
validators[key] = plugin.validators[key];
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Link Interceptors
|
|
355
|
+
if (dynamic.linkInterceptor) {
|
|
356
|
+
Object.keys(dynamic.linkInterceptor).forEach((name) => {
|
|
357
|
+
addLinkInterceptor(dynamic.linkInterceptor[name], name);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Register 'something' that can be dynamically loaded - e.g. model, edit, create, list, i18n
|
|
364
|
+
* @param {String} type type of thing to register, e.g. 'edit'
|
|
365
|
+
* @param {String} name unique name of 'something'
|
|
366
|
+
* @param {Function} fn function that dynamically loads the module for the thing being registered
|
|
367
|
+
*/
|
|
368
|
+
register(type, name, fn) {
|
|
369
|
+
if (!dynamic[type]) {
|
|
370
|
+
dynamic[type] = {};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Accumulate l10n resources and model extensions rather than replace
|
|
374
|
+
if (type === 'l10n' || type === EXT_IDS.MODEL_EXTENSION) {
|
|
375
|
+
if (!dynamic[type][name]) {
|
|
376
|
+
dynamic[type][name] = [];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
dynamic[type][name].push(fn);
|
|
380
|
+
} else {
|
|
381
|
+
dynamic[type][name] = fn;
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
unregister(type, name, fn) {
|
|
386
|
+
if (type === 'l10n') {
|
|
387
|
+
if (dynamic[type]?.[name]) {
|
|
388
|
+
const index = dynamic[type][name].find((func) => func === fn);
|
|
389
|
+
|
|
390
|
+
if (index !== -1) {
|
|
391
|
+
dynamic[type][name].splice(index, 1);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} else if (dynamic[type]?.[name]) {
|
|
395
|
+
delete dynamic[type][name];
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
// For debugging
|
|
400
|
+
getAll() {
|
|
401
|
+
return dynamic;
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
getPlugins() {
|
|
405
|
+
return plugins;
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
getDynamic(typeName, name) {
|
|
409
|
+
return dynamic[typeName]?.[name];
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
getValidator(name) {
|
|
413
|
+
return validators[name];
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Return the UI configuration for the given type and location
|
|
418
|
+
*/
|
|
419
|
+
getUIConfig(type, uiArea) {
|
|
420
|
+
return uiConfig[type][uiArea] || [];
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Returns all UI Configuration (useful for debugging)
|
|
425
|
+
*/
|
|
426
|
+
getAllUIConfig() {
|
|
427
|
+
return uiConfig;
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
// Timestamp that a UI package was last loaded
|
|
431
|
+
// Typically used to invalidate caches (e.g. i18n) when new plugins are loaded
|
|
432
|
+
get lastLoad() {
|
|
433
|
+
return _lastLoaded;
|
|
434
|
+
},
|
|
435
|
+
/**
|
|
436
|
+
*
|
|
437
|
+
* @param {*} context is of type ClusterProvisionerContext
|
|
438
|
+
* Example:
|
|
439
|
+
* dispatch: this.$store.dispatch,
|
|
440
|
+
getters: this.$store.getters,
|
|
441
|
+
axios: this.$store.$axios,
|
|
442
|
+
$extension: this.$store.app.$extension,
|
|
443
|
+
t: (...args) => this.t.apply(this, args),
|
|
444
|
+
isCreate: this.isCreate,
|
|
445
|
+
isEdit: this.isEdit,
|
|
446
|
+
isView: this.isView,
|
|
447
|
+
* @returns array of all extension provisioners
|
|
448
|
+
*/
|
|
449
|
+
|
|
450
|
+
getProviders(context) {
|
|
451
|
+
// Custom Providers from extensions - initialize each with the store and the i18n service
|
|
452
|
+
// Wrap in try ... catch, to prevent errors in an extension breaking the page
|
|
453
|
+
|
|
454
|
+
const extensions = context.$extension.listDynamic('provisioner').map((name) => {
|
|
455
|
+
try {
|
|
456
|
+
const provisioner = context.$extension.getDynamic('provisioner', name);
|
|
457
|
+
|
|
458
|
+
return new provisioner({ ...context });
|
|
459
|
+
} catch (e) {
|
|
460
|
+
console.error('Error loading provisioner(s) from extensions', e); // eslint-disable-line no-console
|
|
461
|
+
}
|
|
462
|
+
}).filter((ext) => !!ext);
|
|
463
|
+
|
|
464
|
+
return extensions;
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
listDynamic(typeName) {
|
|
468
|
+
if (!dynamic[typeName]) {
|
|
469
|
+
return [];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return Object.keys(dynamic[typeName]);
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
// Get the products provided by plugins
|
|
476
|
+
get products() {
|
|
477
|
+
return dynamic.products || [];
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
// Load all of the products provided by plugins
|
|
481
|
+
loadProducts(loadPlugins) {
|
|
482
|
+
if (!loadPlugins) {
|
|
483
|
+
loadPlugins = Object.values(plugins);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
loadPlugins.forEach((plugin) => {
|
|
487
|
+
if (plugin.products) {
|
|
488
|
+
plugin.products.forEach(async(p) => {
|
|
489
|
+
const impl = await p;
|
|
490
|
+
|
|
491
|
+
if (impl.init) {
|
|
492
|
+
impl.init(plugin, store);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Initializes a new extension manager if one does not exist.
|
|
503
|
+
* @param {*} context The Rancher Dashboard context object
|
|
504
|
+
* @returns The extension manager instance
|
|
505
|
+
*/
|
|
506
|
+
export const initExtensionManager = (context) => {
|
|
507
|
+
if (!extensionManagerInstance) {
|
|
508
|
+
extensionManagerInstance = createExtensionManager(context);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return extensionManagerInstance;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Gets the extension manager instance.
|
|
516
|
+
* @returns The extension manager instance
|
|
517
|
+
*/
|
|
518
|
+
export const getExtensionManager = () => extensionManagerInstance;
|