@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.
Files changed (199) hide show
  1. package/assets/styles/base/_color.scss +4 -1
  2. package/assets/styles/global/_tooltip.scss +7 -4
  3. package/assets/styles/themes/_dark.scss +11 -0
  4. package/assets/styles/themes/_light.scss +13 -1
  5. package/assets/styles/themes/_modern.scss +22 -0
  6. package/assets/translations/en-us.yaml +147 -19
  7. package/assets/translations/zh-hans.yaml +0 -1
  8. package/chart/monitoring/grafana/index.vue +8 -2
  9. package/components/ActionMenuShell.vue +3 -1
  10. package/components/Cron/CronExpressionEditor.vue +299 -0
  11. package/components/Cron/CronExpressionEditorModal.vue +247 -0
  12. package/components/Cron/CronTooltip.vue +87 -0
  13. package/components/Cron/types.ts +13 -0
  14. package/components/ForceDirectedTreeChart/composable.ts +11 -0
  15. package/components/PodSecurityAdmission.vue +2 -0
  16. package/components/PromptModal.vue +1 -1
  17. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
  18. package/components/Resource/Detail/CopyToClipboard.vue +78 -0
  19. package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
  20. package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
  21. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
  22. package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
  23. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
  24. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
  25. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +24 -38
  26. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
  27. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
  28. package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
  29. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
  30. package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
  31. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
  32. package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
  33. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +0 -3
  34. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
  35. package/components/Resource/Detail/Metadata/composables.ts +1 -4
  36. package/components/Resource/Detail/Metadata/index.vue +1 -0
  37. package/components/Resource/Detail/Preview/Content.vue +63 -0
  38. package/components/Resource/Detail/Preview/Preview.vue +128 -0
  39. package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
  40. package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
  41. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
  42. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
  43. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
  44. package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
  45. package/components/Resource/Detail/SpacedRow.vue +1 -0
  46. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +0 -5
  47. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
  48. package/components/Resource/Detail/TitleBar/composables.ts +1 -3
  49. package/components/Resource/Detail/TitleBar/index.vue +2 -29
  50. package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
  51. package/components/Resource/Detail/ViewOptions/index.vue +41 -0
  52. package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
  53. package/components/ResourceDetail/Masthead/legacy.vue +0 -19
  54. package/components/ResourceDetail/index.vue +1 -26
  55. package/components/ResourceTable.vue +24 -0
  56. package/components/SortableTable/index.vue +7 -1
  57. package/components/SortableTable/paging.js +3 -0
  58. package/components/Tabbed/Tab.vue +43 -1
  59. package/components/Tabbed/index.vue +3 -1
  60. package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
  61. package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
  62. package/components/auth/login/saml.vue +86 -0
  63. package/components/form/LabeledSelect.vue +8 -8
  64. package/components/form/ProjectMemberEditor.vue +2 -0
  65. package/components/form/ResourceTabs/composable.ts +54 -0
  66. package/components/form/ResourceTabs/index.vue +10 -7
  67. package/components/form/Select.vue +13 -10
  68. package/components/form/__tests__/LabeledSelect.test.ts +133 -0
  69. package/components/form/__tests__/Select.test.ts +134 -0
  70. package/components/nav/Header.vue +6 -5
  71. package/composables/useExtensionManager.ts +17 -0
  72. package/config/home-links.js +12 -0
  73. package/config/labels-annotations.js +0 -1
  74. package/config/page-actions.js +0 -1
  75. package/config/product/explorer.js +3 -1
  76. package/config/product/fleet.js +2 -7
  77. package/config/product/manager.js +0 -5
  78. package/config/query-params.js +1 -0
  79. package/config/router/navigation-guards/clusters.js +2 -1
  80. package/config/router/navigation-guards/products.js +1 -1
  81. package/config/store.js +2 -0
  82. package/core/extension-manager-impl.js +518 -0
  83. package/core/plugins.js +35 -468
  84. package/core/types.ts +8 -2
  85. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
  86. package/detail/catalog.cattle.io.app.vue +7 -4
  87. package/detail/fleet.cattle.io.bundle.vue +1 -5
  88. package/detail/fleet.cattle.io.cluster.vue +3 -2
  89. package/detail/fleet.cattle.io.gitrepo.vue +76 -49
  90. package/detail/fleet.cattle.io.helmop.vue +78 -49
  91. package/dialog/AddonConfigConfirmationDialog.vue +1 -1
  92. package/dialog/GenericPrompt.vue +1 -1
  93. package/dialog/ImportDialog.vue +9 -2
  94. package/dialog/InstallExtensionDialog.vue +18 -10
  95. package/dialog/SloDialog.vue +1 -1
  96. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
  97. package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
  98. package/edit/auth/oidc.vue +106 -6
  99. package/edit/auth/saml.vue +5 -5
  100. package/edit/cloudcredential.vue +31 -17
  101. package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
  102. package/edit/fleet.cattle.io.cluster.vue +19 -0
  103. package/edit/fleet.cattle.io.gitrepo.vue +23 -16
  104. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
  105. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
  106. package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
  107. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -3
  108. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
  109. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
  110. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  111. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
  112. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
  113. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
  114. package/edit/resources.cattle.io.restore.vue +5 -8
  115. package/initialize/install-plugins.js +1 -3
  116. package/list/__tests__/workload.test.ts +1 -0
  117. package/list/workload.vue +8 -1
  118. package/machine-config/components/GCEImage.vue +6 -5
  119. package/machine-config/google.vue +11 -6
  120. package/mixins/__tests__/auth-config.test.ts +4 -6
  121. package/mixins/__tests__/chart.test.ts +139 -1
  122. package/mixins/auth-config.js +33 -10
  123. package/mixins/chart.js +58 -18
  124. package/models/__tests__/namespace.test.ts +69 -0
  125. package/models/apps.statefulset.js +8 -10
  126. package/models/chart.js +5 -1
  127. package/models/fleet-application.js +16 -46
  128. package/models/fleet.cattle.io.bundle.js +1 -38
  129. package/models/fleet.cattle.io.gitrepo.js +4 -0
  130. package/models/fleet.cattle.io.helmop.js +4 -0
  131. package/models/management.cattle.io.cluster.js +1 -1
  132. package/models/management.cattle.io.project.js +12 -0
  133. package/models/namespace.js +30 -0
  134. package/models/workload.js +4 -1
  135. package/package.json +10 -10
  136. package/pages/auth/login.vue +8 -3
  137. package/pages/auth/logout.vue +6 -5
  138. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -11
  139. package/pages/c/_cluster/apps/charts/chart.vue +29 -20
  140. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  141. package/pages/c/_cluster/apps/charts/install.vue +6 -5
  142. package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
  143. package/pages/c/_cluster/explorer/tools/index.vue +145 -254
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +18 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
  146. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  147. package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
  148. package/pages/c/_cluster/uiplugins/index.vue +221 -363
  149. package/pages/home.vue +1 -9
  150. package/plugins/axios.js +3 -2
  151. package/plugins/dashboard-store/resource-class.js +49 -0
  152. package/plugins/ember-cookie.js +7 -3
  153. package/plugins/steve/subscribe.js +4 -2
  154. package/public/index.html +2 -1
  155. package/rancher-components/Card/Card.vue +1 -1
  156. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  157. package/rancher-components/Form/Radio/RadioButton.vue +1 -1
  158. package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
  159. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
  160. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
  161. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
  162. package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
  163. package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
  164. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +1 -1
  165. package/rancher-components/Pill/RcStatusBadge/index.ts +1 -1
  166. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -3
  167. package/rancher-components/Pill/RcStatusIndicator/types.ts +1 -1
  168. package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
  169. package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
  170. package/rancher-components/Pill/RcTag/index.ts +1 -0
  171. package/rancher-components/Pill/RcTag/types.ts +9 -0
  172. package/rancher-components/Pill/types.ts +1 -0
  173. package/rancher-components/RcItemCard/RcItemCard.vue +1 -0
  174. package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
  175. package/scripts/test-plugins-build.sh +0 -1
  176. package/store/__tests__/catalog.test.ts +63 -0
  177. package/store/__tests__/cookies.test.ts +72 -0
  178. package/store/auth.js +33 -10
  179. package/store/catalog.js +2 -2
  180. package/store/cookies.ts +30 -0
  181. package/store/prefs.js +10 -5
  182. package/store/type-map.js +3 -15
  183. package/types/extension-manager.ts +26 -0
  184. package/types/shell/index.d.ts +123 -27
  185. package/utils/__tests__/product.test.ts +129 -0
  186. package/utils/__tests__/resource.test.ts +87 -0
  187. package/utils/alertmanagerconfig.js +2 -2
  188. package/utils/auth.js +4 -77
  189. package/utils/product.ts +39 -0
  190. package/utils/resource.ts +35 -0
  191. package/utils/select.js +0 -24
  192. package/utils/validators/formRules/__tests__/index.test.ts +3 -0
  193. package/utils/validators/formRules/index.ts +2 -1
  194. package/vue.config.js +1 -1
  195. package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
  196. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
  197. package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
  198. package/utils/cookie-universal.js +0 -10
  199. /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
@@ -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
  };
@@ -1,4 +1,3 @@
1
- export const RESET_CARDS_ACTION = 'reset-homepage-cards';
2
1
  export const SET_LOGIN_ACTION = 'set-as-login';
3
2
  export const ADD_CUSTOM_NAV_LINK = 'add-custom-nav-link';
4
3
  export const SHOW_HIDE_BANNER_ACTION = 'toggle-homepage-banner';
@@ -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: undefined
498
+ width: undefined,
499
+ formatter: undefined,
498
500
  }, AGE]);
499
501
 
500
502
  headers(STORAGE_CLASS,
@@ -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
- showListMasthead: false, hasGraph: true, graphConfig
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',
@@ -39,6 +39,7 @@ export const _IMPORT = 'import';
39
39
  export const LEGACY = 'legacy';
40
40
 
41
41
  export const AS = 'as';
42
+ export const VIEW = 'view';
42
43
  export const _DETAIL = 'detail';
43
44
  export const _CONFIG = 'config';
44
45
  export const _YAML = 'yaml';
@@ -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));
@@ -1,4 +1,4 @@
1
- import { setProduct } from '@shell/utils/auth';
1
+ import { setProduct } from '@shell/utils/product';
2
2
  import { applyProducts } from '@shell/store/type-map';
3
3
 
4
4
  export function install(router, 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;