From dbdf287248efb11e3083b1d5fe40b9b3cf8baa3f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 21 May 2024 11:52:25 +0100 Subject: [PATCH 01/10] Add hooks to support virtual clusters --- shell/assets/translations/en-us.yaml | 1 + .../detail/provisioning.cattle.io.cluster.vue | 2 +- shell/list/provisioning.cattle.io.cluster.vue | 7 +++ .../models/provisioning.cattle.io.cluster.js | 60 +++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/shell/assets/translations/en-us.yaml b/shell/assets/translations/en-us.yaml index dde47ac0177..aca91c20951 100644 --- a/shell/assets/translations/en-us.yaml +++ b/shell/assets/translations/en-us.yaml @@ -5042,6 +5042,7 @@ resourceTable: role: Group by Role cluster: Group by Cluster device: Group by Device + hostCluster: Group by Host Cluster groupLabel: cluster: "Cluster: {name}" notInACluster: Not in a Cluster diff --git a/shell/detail/provisioning.cattle.io.cluster.vue b/shell/detail/provisioning.cattle.io.cluster.vue index 9131596a572..36cc31999e6 100644 --- a/shell/detail/provisioning.cattle.io.cluster.vue +++ b/shell/detail/provisioning.cattle.io.cluster.vue @@ -379,7 +379,7 @@ export default { }, showNodes() { - return !this.showMachines && this.haveNodes && !!this.nodes.length; + return !this.showMachines && this.haveNodes && !!this.nodes.length && this.extDetailTabs.machines; }, showSnapshots() { diff --git a/shell/list/provisioning.cattle.io.cluster.vue b/shell/list/provisioning.cattle.io.cluster.vue index e2f2fb0c404..aac08a7a65a 100644 --- a/shell/list/provisioning.cattle.io.cluster.vue +++ b/shell/list/provisioning.cattle.io.cluster.vue @@ -88,6 +88,11 @@ export default { }, computed: { + groupable() { + // Groupable if at least one cluster has a parent cluster + return !!this.filteredRows.find((c) => !!c.groupByParent); + }, + filteredRows() { // If Harvester feature is enabled, hide Harvester Clusters if (this.harvesterEnabled) { @@ -228,6 +233,8 @@ export default { :data-testid="'cluster-list'" :force-update-live-and-delayed="forceUpdateLiveAndDelayed" :sub-rows="true" + :groupable="groupable" + group-tooltip="resourceTable.groupBy.hostCluster" > diff --git a/shell/models/provisioning.cattle.io.cluster.js b/shell/models/provisioning.cattle.io.cluster.js index e5c14c5d134..ac13100ccf4 100644 --- a/shell/models/provisioning.cattle.io.cluster.js +++ b/shell/models/provisioning.cattle.io.cluster.js @@ -10,12 +10,48 @@ import { compare } from '@shell/utils/version'; import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params'; import { HARVESTER_NAME as HARVESTER } from '@shell/config/features'; import { CAPI as CAPI_ANNOTATIONS, NODE_ARCHITECTURE } from '@shell/config/labels-annotations'; +import { escapeHtml } from '@shell/utils/string'; + +/** + * Cache of instantiated provisioner helpers + * + * One per type rather than one per model instance. + */ +const customProvisionerHelperCache = {}; /** * Class representing Cluster resource. * @extends SteveModel */ export default class ProvCluster extends SteveModel { + /** + * customProvisionerHelper returns a custom helper if applicable that can be used for this cluster + */ + get customProvisionerHelper() { + const fromAnnotation = this.annotations?.[CAPI_ANNOTATIONS.UI_CUSTOM_PROVIDER]; + + if (fromAnnotation) { + // Check if we have an instance of the helper already cached + if (!customProvisionerHelperCache[fromAnnotation]) { + const customProvisionerCls = this.$rootState.$plugin.getDynamic('provisioner', fromAnnotation); + + if (customProvisionerCls) { + const context = { + dispatch: this.$dispatch, + getters: this.$getters, + $plugin: this.$rootState.$plugin, + $t: this.t + }; + + customProvisionerHelperCache[fromAnnotation] = new customProvisionerCls(context); + } + } + } + + // Helper, of undefined if no helper for this cluster + return customProvisionerHelperCache[fromAnnotation]; + } + get details() { const out = [ { @@ -496,6 +532,10 @@ export default class ProvCluster extends SteveModel { } get machineProviderDisplay() { + if (this.customProvisionerHelper?.machineProviderDisplay) { + return this.customProvisionerHelper?.machineProviderDisplay(this); + } + if ( this.isImported ) { return null; } @@ -954,6 +994,26 @@ export default class ProvCluster extends SteveModel { if ( res?._status === 204 ) { await this.$dispatch('ws.resource.remove', { data: this }); } + + // If this cluster has a custom provisioner, allow it to do custom deletion + if (this.customProvisionerHelper?.postDelete) { + return this.customProvisionerHelper?.postDelete(this); + } + } + + get groupByParent() { + // Customer helper can report if the cluster has a parent cluster + return this.customProvisionerHelper?.parentCluster(this); + } + + get groupByLabel() { + const name = this.groupByParent; + + if (name) { + return this.$rootGetters['i18n/t']('resourceTable.groupLabel.cluster', { name: escapeHtml(name) }); + } else { + return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInACluster'); + } } get hasError() { From a931966011533c5f3ed7473d039aa2cab62e3095 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 21 May 2024 12:24:56 +0100 Subject: [PATCH 02/10] Fix lint issues --- shell/models/provisioning.cattle.io.cluster.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shell/models/provisioning.cattle.io.cluster.js b/shell/models/provisioning.cattle.io.cluster.js index ac13100ccf4..6b1b936997b 100644 --- a/shell/models/provisioning.cattle.io.cluster.js +++ b/shell/models/provisioning.cattle.io.cluster.js @@ -5,16 +5,15 @@ import SteveModel from '@shell/plugins/steve/steve-class'; import { findBy } from '@shell/utils/array'; import { get, set } from '@shell/utils/object'; import { sortBy } from '@shell/utils/sort'; -import { ucFirst } from '@shell/utils/string'; +import { escapeHtml, ucFirst } from '@shell/utils/string'; import { compare } from '@shell/utils/version'; import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params'; import { HARVESTER_NAME as HARVESTER } from '@shell/config/features'; import { CAPI as CAPI_ANNOTATIONS, NODE_ARCHITECTURE } from '@shell/config/labels-annotations'; -import { escapeHtml } from '@shell/utils/string'; /** * Cache of instantiated provisioner helpers - * + * * One per type rather than one per model instance. */ const customProvisionerHelperCache = {}; From 47a877bd88aefd26ccb1a18f7d59d3d777740a5d Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 12 Jun 2024 12:53:54 +0100 Subject: [PATCH 03/10] Refinements --- shell/core/types-provisioning.ts | 18 +++ .../detail/provisioning.cattle.io.cluster.vue | 13 +- .../provisioning.cattle.io.cluster/rke2.vue | 15 +-- .../models/provisioning.cattle.io.cluster.js | 56 ++++---- .../plugins/dashboard-store/resource-class.js | 8 ++ shell/store/index.js | 1 + shell/utils/model-extensions.ts | 126 ++++++++++++++++++ 7 files changed, 182 insertions(+), 55 deletions(-) create mode 100644 shell/utils/model-extensions.ts diff --git a/shell/core/types-provisioning.ts b/shell/core/types-provisioning.ts index d9f190d1ff0..82d8c12da2e 100644 --- a/shell/core/types-provisioning.ts +++ b/shell/core/types-provisioning.ts @@ -58,6 +58,16 @@ export interface ClusterProvisionerContext { */ export interface IClusterProvisioner { + /** + * Indicates if this provisioner/helper should be used for the given cluster. + * + * This allows the provisioner to determine if it should be used for a cluster based on attributes/metadata of its choosing + * + * @param cluster The cluster (`provisioning.cattle.io.cluster`) + * @returns Whether to use this provisioner for the given cluster. + */ + // static useForModel?(cluster: any): boolean; + /** * Unique ID of the Cluster Provisioner * If this overlaps with the name of an existing provisioner (seen in the type query param while creating a cluster) this provisioner will overwrite the built-in ui @@ -262,4 +272,12 @@ export interface IClusterProvisioner { * @returns Array of errors. If there are no errors the array will be empty */ provision?(cluster: any, pools: any[]): Promise; + + /** + * Optionally Process the available actions for a cluster and return a (possibly modified) set of actions + * + * @param cluster The cluster (`provisioning.cattle.io.cluster`) + * @returns List of actions for the cluster + */ + availableActions?(cluster: any, actions: any[]): any[] | undefined; } diff --git a/shell/detail/provisioning.cattle.io.cluster.vue b/shell/detail/provisioning.cattle.io.cluster.vue index 36cc31999e6..478d2a067ee 100644 --- a/shell/detail/provisioning.cattle.io.cluster.vue +++ b/shell/detail/provisioning.cattle.io.cluster.vue @@ -84,19 +84,10 @@ export default { async fetch() { await this.value.waitForProvisioner(); - const extClass = this.$plugin.getDynamic('provisioner', this.value.machineProvider); - - if (extClass) { - this.extProvider = new extClass({ - dispatch: this.$store.dispatch, - getters: this.$store.getters, - axios: this.$store.$axios, - $plugin: this.$store.app.$plugin, - $t: this.t - }); + if (this.value.customProvisionerHelper) { this.extDetailTabs = { ...this.extDetailTabs, - ...this.extProvider.detailTabs + ...this.value.customProvisionerHelper.detailTabs }; this.extCustomParams = { provider: this.value.machineProvider }; } diff --git a/shell/edit/provisioning.cattle.io.cluster/rke2.vue b/shell/edit/provisioning.cattle.io.cluster/rke2.vue index f304a8bfe16..4838b007847 100644 --- a/shell/edit/provisioning.cattle.io.cluster/rke2.vue +++ b/shell/edit/provisioning.cattle.io.cluster/rke2.vue @@ -469,20 +469,7 @@ export default { * Extension provider where being provisioned by an extension */ extensionProvider() { - const extClass = this.$plugin.getDynamic('provisioner', this.provider); - - if (extClass) { - return new extClass({ - dispatch: this.$store.dispatch, - getters: this.$store.getters, - axios: this.$store.$axios, - $plugin: this.$store.app.$plugin, - $t: this.t, - isCreate: this.isCreate - }); - } - - return undefined; + return this.value.customProvisionerHelper; }, /** diff --git a/shell/models/provisioning.cattle.io.cluster.js b/shell/models/provisioning.cattle.io.cluster.js index 6b1b936997b..03ec912c5c8 100644 --- a/shell/models/provisioning.cattle.io.cluster.js +++ b/shell/models/provisioning.cattle.io.cluster.js @@ -10,45 +10,33 @@ import { compare } from '@shell/utils/version'; import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params'; import { HARVESTER_NAME as HARVESTER } from '@shell/config/features'; import { CAPI as CAPI_ANNOTATIONS, NODE_ARCHITECTURE } from '@shell/config/labels-annotations'; - -/** - * Cache of instantiated provisioner helpers - * - * One per type rather than one per model instance. - */ -const customProvisionerHelperCache = {}; +import { ModelExtensions } from 'utils/model-extensions'; /** * Class representing Cluster resource. * @extends SteveModel */ export default class ProvCluster extends SteveModel { + constructor(data, ctx, rehydrateNamespace = null, setClone = false) { + super(data, ctx, rehydrateNamespace, setClone); + } + /** - * customProvisionerHelper returns a custom helper if applicable that can be used for this cluster + * Instance of model extensions utility that we can use for accessing model helpers provided by extensions */ - get customProvisionerHelper() { - const fromAnnotation = this.annotations?.[CAPI_ANNOTATIONS.UI_CUSTOM_PROVIDER]; - - if (fromAnnotation) { - // Check if we have an instance of the helper already cached - if (!customProvisionerHelperCache[fromAnnotation]) { - const customProvisionerCls = this.$rootState.$plugin.getDynamic('provisioner', fromAnnotation); - - if (customProvisionerCls) { - const context = { - dispatch: this.$dispatch, - getters: this.$getters, - $plugin: this.$rootState.$plugin, - $t: this.t - }; - - customProvisionerHelperCache[fromAnnotation] = new customProvisionerCls(context); - } - } + get modelExtensions() { + if (!this._modelExtensions) { + this._modelExtensions = new ModelExtensions(this, 'provisioner', (model) => model.machineProvider); } - // Helper, of undefined if no helper for this cluster - return customProvisionerHelperCache[fromAnnotation]; + return this._modelExtensions; + } + + /** + * customProvisionerHelper returns a custom helper if applicable that can be used for this cluster + */ + get customProvisionerHelper() { + return this.modelExtensions.modelHelper; } get details() { @@ -213,7 +201,15 @@ export default class ProvCluster extends SteveModel { }); } - return actions.concat(out); + const all = actions.concat(out); + + // If we have a helper that wants to modify the available actions, let it do it + if (this.customProvisionerHelper?.availableActions) { + // Provider can either modify the provided list or return one of its own + return this.customProvisionerHelper?.availableActions(this, all) || all; + } + + return all; } get normanCluster() { diff --git a/shell/plugins/dashboard-store/resource-class.js b/shell/plugins/dashboard-store/resource-class.js index 38ea3e5bfc9..a1ee769a8a3 100644 --- a/shell/plugins/dashboard-store/resource-class.js +++ b/shell/plugins/dashboard-store/resource-class.js @@ -604,6 +604,14 @@ export default class Resource { return this.$ctx.rootState; } + get '$plugin'() { + return this.$ctx.rootState?.$plugin; + } + + get '$axios'() { + return this.$ctx.rootState?.$axios; + } + get customValidationRules() { return [ /** diff --git a/shell/store/index.js b/shell/store/index.js index 89367962649..c33697e7ad7 100644 --- a/shell/store/index.js +++ b/shell/store/index.js @@ -1210,6 +1210,7 @@ export const actions = { commit('setRouter', nuxt.app.router); commit('setRoute', nuxt.route); commit('setPlugin', nuxt.app.$plugin); + Object.defineProperty(rootState, '$axios', { value: nuxt.app.$axios }); dispatch('management/rehydrateSubscribe'); dispatch('cluster/rehydrateSubscribe'); diff --git a/shell/utils/model-extensions.ts b/shell/utils/model-extensions.ts new file mode 100644 index 00000000000..9c1695886ed --- /dev/null +++ b/shell/utils/model-extensions.ts @@ -0,0 +1,126 @@ +/** + * Helper for models to use to simplify using a helper class provided by an extension. + * + * An example use is with provisioning.cattle.io.cluster, where a custom helper can be added via an extension + * to support new provisioners. + */ + +// ============================================================================================== + +/** + * Interface for an object that can determine if it should be used for a given model + */ +interface UseForModel { + name: string, + useForModel: (model: any) => boolean, +} + +// Expected properties on the model +interface IModel { + $rootState: any; + $dispatch: any; + $getters: any; + $axios: any; + $plugin: any; + t: any; + _modelHelper?: any; + annotations: {[key: string]: string}; +} + +// Function that for a model will return the name of the model extension to use for it +type UseForFunction = (model: any) => string; + +// Cache of instantiated model helpers for a given model and helpername +const modelExtensionCache: {[modelName: string]: {[name: string]: any[]}} = {}; + +// Cache of instantiated functions to determine if a helper should be used for a given model +const modelExtensionUseForCache: {[modelName: string]: UseForModel[]} = {}; + +// ============================================================================================== + +export class ModelExtensions { + constructor(private model: IModel, private modelExtensionName: string, private defaultUseFor?: UseForFunction) { + // Initialize the cache if needed for this model extension name + if (!modelExtensionCache[this.modelExtensionName]) { + modelExtensionCache[this.modelExtensionName] = {}; + } + } + + /** + * Get a model helper for the model + */ + get modelHelper(): any { + // Use cached helper if set + if (this.model._modelHelper) { + return this.model._modelHelper; + } + + // First ask all of the helpers that have a 'useForModel' function if they should be used + let helper = this.useForHelpers.find((h) => h.useForModel(this.model))?.name; + + // If no helper and we have a default function, look for a helper that matches the value returned by the function + if (!helper && this.defaultUseFor) { + helper = this.defaultUseFor(this.model); + } + + // Cache for next time + this.model._modelHelper = helper ? this.instantiateModelHelper(helper) : undefined; + + return this.model._modelHelper; + } + + /** + * Go through all of the extension helpers with the required name and find the ones that + * have a custom 'useForModel' static function and return those as an array + * + * Cache this list for a given model extension so we can use it in future calls + */ + get useForHelpers(): UseForModel[] { + if (!modelExtensionUseForCache[this.modelExtensionName]) { + modelExtensionUseForCache[this.modelExtensionName] = []; + + const helpers = this.model.$rootState.$plugin.listDynamic(this.modelExtensionName); + + helpers.forEach((name: string) => { + const customProvisionerCls = this.model.$rootState.$plugin.getDynamic(this.modelExtensionName, name); + const useForModel = customProvisionerCls.useForModel; + + if (useForModel) { + modelExtensionUseForCache[this.modelExtensionName].push({ + name, + useForModel + }); + } + }); + } + + return modelExtensionUseForCache[this.modelExtensionName]; + } + + /** + * Instantiate the given model helper + * + * @param name Name of the helper + * @returns Instance of the helper + */ + instantiateModelHelper(name: string): any { + // Check if we have an instance of the helper already cached + if (!modelExtensionCache[this.modelExtensionName][name]) { + const customProvisionerCls = this.model.$rootState.$plugin.getDynamic(this.modelExtensionName, name); + + if (customProvisionerCls) { + const context = { + dispatch: this.model.$dispatch, + getters: this.model.$getters, + $axios: this.model.$axios, + $plugin: this.model.$plugin, + $t: this.model.t + }; + + modelExtensionCache[this.modelExtensionName][name] = new customProvisionerCls(context); + } + } + + return modelExtensionCache[this.modelExtensionName][name]; + } +} From f6a77075664bcfaeba4e17915eb6aaba8bfe2d88 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 12 Jun 2024 13:11:11 +0100 Subject: [PATCH 04/10] Update for Vue 3 changes --- shell/store/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shell/store/index.js b/shell/store/index.js index c33697e7ad7..9d92ae17db3 100644 --- a/shell/store/index.js +++ b/shell/store/index.js @@ -259,7 +259,7 @@ export const state = () => { $router: markRaw({}), $route: markRaw({}), $plugin: markRaw({}), - /** + $axios: markRaw({}), * Cache state of side nav clusters. This avoids flickering when the user changes pages and the side nav component re-renders */ sideNavCache: undefined, @@ -763,6 +763,10 @@ export const mutations = { setSideNavCache(state, sideNavCache) { state.sideNavCache = sideNavCache; + }, + + setAxios(state, axios) { + state.$axios = markRaw(axios || {}); } }; @@ -1210,7 +1214,7 @@ export const actions = { commit('setRouter', nuxt.app.router); commit('setRoute', nuxt.route); commit('setPlugin', nuxt.app.$plugin); - Object.defineProperty(rootState, '$axios', { value: nuxt.app.$axios }); + commit('setAxios', nuxt.app.$axios); dispatch('management/rehydrateSubscribe'); dispatch('cluster/rehydrateSubscribe'); From e2203c60c3369f99a80acaf5c9fbd9dcd3d403ad Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 12 Jun 2024 14:43:43 +0100 Subject: [PATCH 05/10] Fix import --- shell/models/provisioning.cattle.io.cluster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/models/provisioning.cattle.io.cluster.js b/shell/models/provisioning.cattle.io.cluster.js index 03ec912c5c8..c6c90a0b16d 100644 --- a/shell/models/provisioning.cattle.io.cluster.js +++ b/shell/models/provisioning.cattle.io.cluster.js @@ -10,7 +10,7 @@ import { compare } from '@shell/utils/version'; import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params'; import { HARVESTER_NAME as HARVESTER } from '@shell/config/features'; import { CAPI as CAPI_ANNOTATIONS, NODE_ARCHITECTURE } from '@shell/config/labels-annotations'; -import { ModelExtensions } from 'utils/model-extensions'; +import { ModelExtensions } from '@shell/utils/model-extensions'; /** * Class representing Cluster resource. From 76bac7f92b7ff84710fa0916b21b47ab2a902e51 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 12 Jun 2024 21:22:01 +0100 Subject: [PATCH 06/10] Minor tweaks --- shell/edit/provisioning.cattle.io.cluster/rke2.vue | 2 +- shell/models/provisioning.cattle.io.cluster.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/shell/edit/provisioning.cattle.io.cluster/rke2.vue b/shell/edit/provisioning.cattle.io.cluster/rke2.vue index 4838b007847..d9e8aa9c4c6 100644 --- a/shell/edit/provisioning.cattle.io.cluster/rke2.vue +++ b/shell/edit/provisioning.cattle.io.cluster/rke2.vue @@ -469,7 +469,7 @@ export default { * Extension provider where being provisioned by an extension */ extensionProvider() { - return this.value.customProvisionerHelper; + return this.value?.customProvisionerHelper; }, /** diff --git a/shell/models/provisioning.cattle.io.cluster.js b/shell/models/provisioning.cattle.io.cluster.js index c6c90a0b16d..6886c3c8c0d 100644 --- a/shell/models/provisioning.cattle.io.cluster.js +++ b/shell/models/provisioning.cattle.io.cluster.js @@ -17,10 +17,6 @@ import { ModelExtensions } from '@shell/utils/model-extensions'; * @extends SteveModel */ export default class ProvCluster extends SteveModel { - constructor(data, ctx, rehydrateNamespace = null, setClone = false) { - super(data, ctx, rehydrateNamespace, setClone); - } - /** * Instance of model extensions utility that we can use for accessing model helpers provided by extensions */ From 4661d8565cd5badcc964368406eb12821f18271e Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 13 Jun 2024 13:19:37 +0100 Subject: [PATCH 07/10] Fix bug causing e2e tests to fail --- .../provisioning.cattle.io.cluster/rke2.vue | 17 ++++++++++++++++- shell/utils/model-extensions.ts | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/shell/edit/provisioning.cattle.io.cluster/rke2.vue b/shell/edit/provisioning.cattle.io.cluster/rke2.vue index d9e8aa9c4c6..7d9631005f7 100644 --- a/shell/edit/provisioning.cattle.io.cluster/rke2.vue +++ b/shell/edit/provisioning.cattle.io.cluster/rke2.vue @@ -469,7 +469,22 @@ export default { * Extension provider where being provisioned by an extension */ extensionProvider() { - return this.value?.customProvisionerHelper; + // Note we don't use the model customProvisionerHelper here, as for create it won't be set + // Instead we create from the provider value + const extClass = this.$plugin.getDynamic('provisioner', this.provider); + + if (extClass) { + return new extClass({ + dispatch: this.$store.dispatch, + getters: this.$store.getters, + axios: this.$store.$axios, + $plugin: this.$store.app.$plugin, + $t: this.t, + isCreate: this.isCreate + }); + } + + return undefined; }, /** diff --git a/shell/utils/model-extensions.ts b/shell/utils/model-extensions.ts index 9c1695886ed..25465aa5d91 100644 --- a/shell/utils/model-extensions.ts +++ b/shell/utils/model-extensions.ts @@ -30,7 +30,7 @@ interface IModel { // Function that for a model will return the name of the model extension to use for it type UseForFunction = (model: any) => string; -// Cache of instantiated model helpers for a given model and helpername +// Cache of instantiated model helpers for a given model and helper name const modelExtensionCache: {[modelName: string]: {[name: string]: any[]}} = {}; // Cache of instantiated functions to determine if a helper should be used for a given model From 169ae7f64b4ff5137ea8877d763351da178e3774 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 13 Jun 2024 13:57:22 +0100 Subject: [PATCH 08/10] Fix lint issue --- shell/edit/provisioning.cattle.io.cluster/rke2.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/edit/provisioning.cattle.io.cluster/rke2.vue b/shell/edit/provisioning.cattle.io.cluster/rke2.vue index 7d9631005f7..ade693249b9 100644 --- a/shell/edit/provisioning.cattle.io.cluster/rke2.vue +++ b/shell/edit/provisioning.cattle.io.cluster/rke2.vue @@ -471,7 +471,7 @@ export default { extensionProvider() { // Note we don't use the model customProvisionerHelper here, as for create it won't be set // Instead we create from the provider value - const extClass = this.$plugin.getDynamic('provisioner', this.provider); + const extClass = this.$plugin.getDynamic('provisioner', this.provider); if (extClass) { return new extClass({ From 8633ad7f50bc9998648e0e0ccc23379d4a90d272 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 14 Jun 2024 16:19:20 +0100 Subject: [PATCH 09/10] Rename internal properties and ensure they don't break clone/save --- shell/models/provisioning.cattle.io.cluster.js | 16 +++++++++++++--- shell/utils/model-extensions.ts | 10 +++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/shell/models/provisioning.cattle.io.cluster.js b/shell/models/provisioning.cattle.io.cluster.js index 6886c3c8c0d..3832fd29418 100644 --- a/shell/models/provisioning.cattle.io.cluster.js +++ b/shell/models/provisioning.cattle.io.cluster.js @@ -21,11 +21,11 @@ export default class ProvCluster extends SteveModel { * Instance of model extensions utility that we can use for accessing model helpers provided by extensions */ get modelExtensions() { - if (!this._modelExtensions) { - this._modelExtensions = new ModelExtensions(this, 'provisioner', (model) => model.machineProvider); + if (!this.__modelExtensions) { + this.__modelExtensions = new ModelExtensions(this, 'provisioner', (model) => model.machineProvider); } - return this._modelExtensions; + return this.__modelExtensions; } /** @@ -35,6 +35,16 @@ export default class ProvCluster extends SteveModel { return this.modelExtensions.modelHelper; } + // Ensure we remove the properties for the model extension from the model on save + // Otherwise we get a problem when editing a cluster + cleanForSave(data, forNew) { + super.cleanForSave(data, forNew); + delete data.__modelExtensions; + delete data.__modelHelper; + + return data; + } + get details() { const out = [ { diff --git a/shell/utils/model-extensions.ts b/shell/utils/model-extensions.ts index 25465aa5d91..6a2bbc14b26 100644 --- a/shell/utils/model-extensions.ts +++ b/shell/utils/model-extensions.ts @@ -23,7 +23,7 @@ interface IModel { $axios: any; $plugin: any; t: any; - _modelHelper?: any; + __modelHelper?: any; annotations: {[key: string]: string}; } @@ -51,8 +51,8 @@ export class ModelExtensions { */ get modelHelper(): any { // Use cached helper if set - if (this.model._modelHelper) { - return this.model._modelHelper; + if (this.model.__modelHelper) { + return this.model.__modelHelper; } // First ask all of the helpers that have a 'useForModel' function if they should be used @@ -64,9 +64,9 @@ export class ModelExtensions { } // Cache for next time - this.model._modelHelper = helper ? this.instantiateModelHelper(helper) : undefined; + this.model.__modelHelper = helper ? this.instantiateModelHelper(helper) : undefined; - return this.model._modelHelper; + return this.model.__modelHelper; } /** From 06756d1ddc7f1e023d5529bf62523150ae3cf8f1 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 10 Jul 2024 17:54:52 +0100 Subject: [PATCH 10/10] Ensure we generate types for the plugins package to give us access to mapDriver --- shell/scripts/typegen.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/scripts/typegen.sh b/shell/scripts/typegen.sh index e52e8ad0d99..88f148f4a4b 100755 --- a/shell/scripts/typegen.sh +++ b/shell/scripts/typegen.sh @@ -25,6 +25,7 @@ ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/config/labels-annotations.js --de # # store ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/store/features.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/store > /dev/null ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/store/prefs.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/store > /dev/null +${BASE_DIR}/node_modules/.bin/tsc shell/store/plugins.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/store > /dev/null # # plugins ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/normalize.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/dashboard-store/ > /dev/null