diff --git a/shell/components/__tests__/SlideInPanelManager.spec.ts b/shell/components/__tests__/SlideInPanelManager.spec.ts
index 7c838e32923..a98d0d3dce8 100644
--- a/shell/components/__tests__/SlideInPanelManager.spec.ts
+++ b/shell/components/__tests__/SlideInPanelManager.spec.ts
@@ -22,9 +22,8 @@ describe('slideInPanelManager.vue with Teleport', () => {
getters = {
'slideInPanel/isOpen': () => true,
'slideInPanel/component': () => MockComponent,
- 'slideInPanel/componentProps': () => ({
- width: '40%', title: 'Test Title', extraProp: 'extra'
- })
+ 'slideInPanel/panelOptions': () => ({ width: '40%', title: 'Test Title' }),
+ 'slideInPanel/componentProps': () => ({ extraProp: 'extra' })
};
store = createStore({
@@ -69,7 +68,7 @@ describe('slideInPanelManager.vue with Teleport', () => {
it('renders default panel title when no title is provided', async() => {
// Update getter so that no title is provided
- getters['slideInPanel/componentProps'] = () => ({ width: '40%' });
+ getters['slideInPanel/panelOptions'] = () => ({ width: '40%' });
store = createStore({
getters,
mutations: { 'slideInPanel/close': jest.fn() }
diff --git a/shell/composables/focusTrap.ts b/shell/composables/focusTrap.ts
index bac5ca8fea7..b58f38992d4 100644
--- a/shell/composables/focusTrap.ts
+++ b/shell/composables/focusTrap.ts
@@ -57,8 +57,10 @@ export function useWatcherBasedSetupFocusTrapWithDestroyIncluded(watchVar:any, f
focusTrapInstance = createFocusTrap(focusEl, opts);
+ const activate = () => focusTrapInstance.activate();
+
nextTick(() => {
- focusTrapInstance.activate();
+ setTimeout(activate, 0);
});
});
} else if (!neu && focusTrapInstance && Object.keys(focusTrapInstance).length && !useUnmountHook) {
diff --git a/shell/composables/useExtensionManager.ts b/shell/composables/useExtensionManager.ts
deleted file mode 100644
index a1f0c493b04..00000000000
--- a/shell/composables/useExtensionManager.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { ExtensionManager } from '@shell/types/extension-manager';
-import { getExtensionManager } from '@shell/core/extension-manager-impl';
-
-/**
- * Provides access to the registered extension manager instance. Used within Vue
- * components or other composables that require extension functionality.
- * @returns The extension manager instance
- */
-export const useExtensionManager = (): ExtensionManager => {
- const extension = getExtensionManager();
-
- if (!extension) {
- throw new Error('useExtensionManager must be called after the extensionManager has been initialized');
- }
-
- return extension;
-};
diff --git a/shell/core/extension-manager-impl.js b/shell/core/extension-manager-impl.js
index cb8aba09d79..9d0c0811fb3 100644
--- a/shell/core/extension-manager-impl.js
+++ b/shell/core/extension-manager-impl.js
@@ -6,9 +6,7 @@ import { UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
import { ExtensionPoint } from './types';
import { addLinkInterceptor, removeLinkInterceptor } from '@shell/plugins/clean-html';
-let extensionManagerInstance;
-
-const createExtensionManager = (context) => {
+export const createExtensionManager = (context) => {
const {
app, store, $axios, redirect
} = context;
@@ -497,22 +495,3 @@ const createExtensionManager = (context) => {
},
};
};
-
-/**
- * Initializes a new extension manager if one does not exist.
- * @param {*} context The Rancher Dashboard context object
- * @returns The extension manager instance
- */
-export const initExtensionManager = (context) => {
- if (!extensionManagerInstance) {
- extensionManagerInstance = createExtensionManager(context);
- }
-
- return extensionManagerInstance;
-};
-
-/**
- * Gets the extension manager instance.
- * @returns The extension manager instance
- */
-export const getExtensionManager = () => extensionManagerInstance;
diff --git a/shell/core/plugins.js b/shell/core/plugins.js
deleted file mode 100644
index cf623028d04..00000000000
--- a/shell/core/plugins.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { throttle } from 'lodash';
-import { initExtensionManager } from './extension-manager-impl';
-
-export default function(context, inject) {
- const extensionManager = initExtensionManager(context);
- const deprecationMessage = '[DEPRECATED] `this.$plugin` is deprecated and will be removed in a future version. Use `this.$extension` instead.';
-
- inject('plugin', deprecationProxy(extensionManager, deprecationMessage));
- inject('extension', extensionManager);
-}
-
-/**
- * Proxy to log a deprecation warning when target is accessed. Only prints
- * deprecation warnings in dev builds.
- * @param {*} target the object to proxy
- * @param {*} message the deprecation warning to print to the console
- * @returns The proxied target that prints a deprecation warning when target is
- * accessed
- */
-const deprecationProxy = (target, message) => {
- const logWarning = throttle(() => {
- // eslint-disable-next-line no-console
- console.warn(message);
- }, 150);
-
- const deprecationHandler = {
- get(target, prop) {
- logWarning();
-
- return Reflect.get(target, prop);
- }
- };
-
- // an empty handler allows the proxy to behave just like the original target
- const proxyHandler = !!process.env.dev ? deprecationHandler : {};
-
- return new Proxy(target, proxyHandler);
-};
diff --git a/shell/initialize/install-plugins.js b/shell/initialize/install-plugins.js
index 754ca2842b6..cda4eab42f7 100644
--- a/shell/initialize/install-plugins.js
+++ b/shell/initialize/install-plugins.js
@@ -19,13 +19,12 @@ import { InstallCodeMirror } from 'codemirror-editor-vue3';
import * as intNumber from '@shell/directives/int-number';
import nuxtClientInit from '@shell/plugins/nuxt-client-init';
import plugin from '@shell/plugins/plugin';
-import plugins from '@shell/core/plugins.js';
import pluginsLoader from '@shell/core/plugins-loader.js';
import replaceAll from '@shell/plugins/replaceall';
import steveCreateWorker from '@shell/plugins/steve-create-worker';
import emberCookie from '@shell/plugins/ember-cookie';
import ShortKey from '@shell/plugins/shortkey';
-import internalApiPlugin from '@shell/plugins/internal-api';
+import { initUiApis } from '@shell/apis/impl/apis';
import 'floating-vue/dist/style.css';
import { floatingVueOptions } from '@shell/plugins/floating-vue';
@@ -46,13 +45,14 @@ export async function installPlugins(vueApp) {
}
export async function installInjectedPlugins(app, vueApp) {
- const pluginDefinitions = [config, axios, plugins, pluginsLoader, axiosShell, intNumber, codeMirror, nuxtClientInit, replaceAll, plugin, steveCreateWorker, emberCookie, internalApiPlugin];
+ const pluginDefinitions = [config, axios, initUiApis, pluginsLoader, axiosShell, intNumber, codeMirror, nuxtClientInit, replaceAll, plugin, steveCreateWorker, emberCookie];
const installations = pluginDefinitions.map(async(pluginDefinition) => {
if (typeof pluginDefinition === 'function') {
await pluginDefinition(
app.context,
- (key, value) => inject(key, value, app.context, vueApp)
+ (key, value) => inject(key, value, app.context, vueApp),
+ vueApp
);
}
});
diff --git a/shell/pages/c/_cluster/fleet/index.vue b/shell/pages/c/_cluster/fleet/index.vue
index d2c34e0446f..6f2a14e2c93 100644
--- a/shell/pages/c/_cluster/fleet/index.vue
+++ b/shell/pages/c/_cluster/fleet/index.vue
@@ -321,16 +321,13 @@ export default {
this.selectedCard = selected;
- this.$shell.slideInPanel({
- component: ResourceDetails,
+ this.$shell.slideIn.open(ResourceDetails, {
+ showHeader: false,
+ width: window.innerWidth / 3 > 530 ? `${ window.innerWidth / 3 }px` : '530px',
componentProps: {
value,
statePanel,
- workspace,
- showHeader: false,
- width: window.innerWidth / 3 > 530 ? `${ window.innerWidth / 3 }px` : '530px',
- triggerFocusTrap: true,
- returnFocusSelector: `[data-testid="resource-card-${ value.id }"]`
+ workspace
}
});
},
diff --git a/shell/plugins/internal-api/index.ts b/shell/plugins/internal-api/index.ts
deleted file mode 100644
index 4de64d12fe7..00000000000
--- a/shell/plugins/internal-api/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Store } from 'vuex';
-
-interface PluginContext {
- store: Store
;
- [key: string]: any;
-}
-
-export default function(context: PluginContext, inject: (key: string, value: any) => void) {
- const { store } = context;
-
- // Load all API modules
- const apiContext = (require as any).context(
- '@shell/plugins/internal-api', // the base directory
- true, // whether to search subdirectories
- /\.api\.ts$/ // only .api.ts files
- );
-
- apiContext.keys().forEach((relativePath: string) => {
- const mod = apiContext(relativePath);
- const ApiClass = mod.default;
-
- if (typeof ApiClass === 'function') {
- // Check for a static `apiName` property, or fallback to filename
- let apiName: string = ApiClass.apiName();
-
- if (!apiName) {
- // fallback to filename (strip leading ‘./’ and extension)
- apiName = `$${ relativePath.replace(/^\.\//, '').replace(/\.\w+$/, '') }`;
- }
-
- const instance = new ApiClass(store);
-
- // The inject() method automatically adds the `$` prefix
- inject(apiName, instance);
- }
- });
-}
diff --git a/shell/plugins/internal-api/shared/base-api.ts b/shell/plugins/internal-api/shared/base-api.ts
deleted file mode 100644
index f1351bf415b..00000000000
--- a/shell/plugins/internal-api/shared/base-api.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export abstract class BaseApi {
- // The Vuex store, available to all API classes
- protected $store: any;
-
- // Documented requirement: each API should define its static apiName.
- static apiName(): string {
- throw new Error('apiName() static method has not been implemented');
- }
-
- constructor(store: any) {
- this.$store = store;
- }
-}
diff --git a/shell/plugins/internal-api/shell/shell.api.ts b/shell/plugins/internal-api/shell/shell.api.ts
deleted file mode 100644
index e3bf8e4568d..00000000000
--- a/shell/plugins/internal-api/shell/shell.api.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { GrowlConfig } from '@shell/types/internal-api/shell/growl';
-import { ModalConfig } from '@shell/types/internal-api/shell/modal';
-import { SlideInConfig } from '@shell/types/internal-api/shell/slideIn';
-
-import { BaseApi } from '@shell/plugins/internal-api/shared/base-api';
-
-export default class ShellApi extends BaseApi {
- static apiName() {
- return 'shell';
- }
-
- /**
- * Dispatches a growl notification.
- *
- * @param config - Configuration for the growl notification.
- * - If `message` is a string, it is treated as the main content of the notification.
- * - If `message` is a `DetailedMessage` object, `title` and `description` are extracted.
- *
- * Example:
- * ```ts
- * this.$shell.growl({ message: 'Operation successful!', type: 'success' });
- * this.$shell.growl({ message: { title: 'Warning', description: 'Check your input.' }, type: 'warning' });
- * ```
- */
- protected growl(config: GrowlConfig): void {
- const { type = 'error', timeout = 5000 } = config;
-
- let title = '';
- let description = '';
-
- if (typeof config.message === 'string') {
- description = config.message;
- } else {
- title = config.message.title || '';
- description = config.message.description;
- }
-
- this.$store.dispatch(
- `growl/${ type }`,
- {
- title,
- message: description,
- timeout,
- },
- { root: true }
- );
- }
-
- /**
- * Opens a modal by committing to the Vuex store.
- *
- * This method updates the store's `modal` module to show a modal with the
- * specified configuration. The modal is rendered using the `ModalManager` component,
- * and its content is dynamically loaded based on the `component` field in the configuration.
- *
- * @param config A `ModalConfig` object defining the modal’s content and behavior.
- *
- * Example:
- * ```ts
- * this.$shell.modal({
- * component: MyCustomModal,
- * componentProps: { title: 'Hello Modal' },
- * resources: [someResource],
- * modalWidth: '800px',
- * closeOnClickOutside: false
- * });
- * ```
- */
- protected modal(config: ModalConfig): void {
- this.$store.commit('modal/openModal', {
- component: config.component,
- componentProps: config.componentProps || {},
- resources: config.resources || [],
- modalWidth: config.modalWidth || '600px',
- closeOnClickOutside: config.closeOnClickOutside ?? true,
- // modalSticky: config.modalSticky ?? false // Not implemented yet
- });
- }
-
- /**
- * Opens the slide-in panel with the specified component and props.
- *
- * This method commits the `open` mutation to the `slideInPanel` Vuex module,
- * which sets the current component to be rendered and its associated props.
- * The slide-in panel becomes visible after the mutation.
- *
- * @param config - The configuration object for the slide-in panel.
- *
- * Example Usage:
- * ```ts
- * import MyComponent from '@/components/MyComponent.vue';
- *
- * this.$shell.slideInPanel({
- * component: MyComponent,
- * componentProps: { foo: 'bar' }
- * });
- * ```
- *
- * @param config.component - A Vue component (imported SFC, functional component, etc.) to be rendered in the panel.
- * @param config.componentProps - (Optional) Props to pass to the component. These should align with the component's defined props.
- */
- protected slideInPanel(config: SlideInConfig): void {
- this.$store.commit('slideInPanel/open', {
- component: config.component,
- componentProps: config.componentProps || {}
- });
- }
-}
diff --git a/shell/store/notifications.ts b/shell/store/notifications.ts
index 23b71f869aa..48f9448b9cb 100644
--- a/shell/store/notifications.ts
+++ b/shell/store/notifications.ts
@@ -267,6 +267,8 @@ export const actions = {
// Show a growl for the notification if necessary
dispatch('growl/notification', notification, { root: true });
+
+ return notification.id;
},
async fromGrowl( { commit, getters }: any, notification: Notification) {
diff --git a/shell/store/slideInPanel.ts b/shell/store/slideInPanel.ts
index 501eb1c41c6..4c201c81da9 100644
--- a/shell/store/slideInPanel.ts
+++ b/shell/store/slideInPanel.ts
@@ -5,6 +5,7 @@ export interface SlideInPanelState {
isOpen: boolean;
isClosing: boolean;
component: Component | null;
+ panelOptions: Record;
componentProps: Record;
}
@@ -12,6 +13,7 @@ const state = (): SlideInPanelState => ({
isOpen: false,
isClosing: false,
component: null,
+ panelOptions: {},
componentProps: {}
});
@@ -19,14 +21,18 @@ const getters: GetterTree = {
isOpen: (state) => state.isOpen,
isClosing: (state) => state.isClosing,
component: (state) => state.component,
+ panelOptions: (state) => state.panelOptions,
componentProps: (state) => state.componentProps
};
const mutations: MutationTree = {
- open(state, payload: { component: Component; componentProps?: Record }) {
+ open(state, payload: { component: Component; slideInConfig?:any }) {
+ const { componentProps, ...panelOptions } = payload.slideInConfig;
+
state.isOpen = true;
state.component = markRaw(payload.component);
- state.componentProps = payload.componentProps || {};
+ state.panelOptions = panelOptions || {};
+ state.componentProps = componentProps || {};
},
close(state) {
state.isClosing = true;
@@ -35,6 +41,7 @@ const mutations: MutationTree = {
// Delay clearing component/props for 500ms (same as transition duration)
setTimeout(() => {
state.component = null;
+ state.panelOptions = {};
state.componentProps = {};
state.isClosing = false;
diff --git a/shell/types/internal-api/shell/growl.d.ts b/shell/types/internal-api/shell/growl.d.ts
deleted file mode 100644
index def12827a65..00000000000
--- a/shell/types/internal-api/shell/growl.d.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-export interface DetailedMessage {
- title?: string;
- description: string;
-}
-
-export interface GrowlConfig {
- /**
- * The content of the notification message.
- * Either a simple string or an object with `title` and `description` for detailed notifications.
- */
- message: string | DetailedMessage;
-
- /**
- * Optional type of the growl notification.
- * Determines the visual style of the notification.
- * Defaults to `'error'` if not provided.
- */
- type?: 'success' | 'info' | 'warning' | 'error';
-
- /**
- * Optional duration (in milliseconds) for which the notification should be displayed.
- * Defaults to `5000` milliseconds. A value of `0` keeps the notification indefinitely.
- */
- timeout?: number;
-}
diff --git a/shell/types/internal-api/shell/modal.d.ts b/shell/types/internal-api/shell/modal.d.ts
index 4be54300ef1..283fd90be6f 100644
--- a/shell/types/internal-api/shell/modal.d.ts
+++ b/shell/types/internal-api/shell/modal.d.ts
@@ -3,7 +3,7 @@ import { Component } from 'vue';
/**
* Configuration object for opening a modal.
*/
-export interface ModalConfig {
+export interface ModalApiConfig {
/**
* The Vue component to be displayed inside the modal.
* This can be any SFC (Single-File Component) imported and passed in as a `Component`.
diff --git a/shell/types/internal-api/shell/slideIn.d.ts b/shell/types/internal-api/shell/slideIn.d.ts
deleted file mode 100644
index 2af087d8a02..00000000000
--- a/shell/types/internal-api/shell/slideIn.d.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Component } from 'vue';
-
-/**
- * Configuration object for opening a slide-in panel.
- *
- * @property component - The Vue component to render in the slide-in panel.
- * This should be a valid Vue Component, such as an imported SFC or functional component.
- *
- * @property componentProps - (Optional) An object containing props to be passed to the component rendered in the slide-in panel.
- * Keys should match the props defined in the provided component.
- */
-export interface SlideInConfig {
- component: Component | null;
- componentProps?: Record;
-}
diff --git a/shell/types/notifications/index.ts b/shell/types/notifications/index.ts
index a83b2e61377..b5b04109cd1 100644
--- a/shell/types/notifications/index.ts
+++ b/shell/types/notifications/index.ts
@@ -1,38 +1,45 @@
-import { RouteLocationRaw } from 'vue-router';
+import { NotificationApiAction, NotificationApiPreference } from '@shell/apis/intf/shell';
/**
* Type definitions for the Notification Center
*/
-/**
- * Notification Level for a notification in the Notification Center
- */
export enum NotificationLevel {
+ /**
+ * An announcement. To be used when we want to inform on high-interest topics - news, updates, changes, scheduled maintenance, etc. E.g. “New version available!”
+ */
Announcement = 0, // eslint-disable-line no-unused-vars
+ /**
+ * A task that is underway. To be used when we want to inform on a process taking place - on-going actions that might take a while. E.g. “Cluster provisioning in progress”. The progress bar will also be shown if the `progress` field is set
+ */
Task, // eslint-disable-line no-unused-vars
+ /**
+ * Information notification. To be used when we want to inform on low-interest topics. E.g. “Welcome to Rancher v2.8"
+ */
Info, // eslint-disable-line no-unused-vars
+ /**
+ * Notification that something has completed successfully. To be used when we want to confirm a successful action was completed. E.g. “Cluster provisioning completed”
+ */
Success, // eslint-disable-line no-unused-vars
+ /**
+ * Notification of a warning. To be used when we want to warn about a potential risk. E.g. “Nodes limitation warning”
+ */
Warning, // eslint-disable-line no-unused-vars
+ /**
+ * Notification of an error. To be used when we want to alert on a confirmed risk. E.g. “Extension failed to load”
+ */
Error, // eslint-disable-line no-unused-vars
}
/**
* An action that is shown as a button in the Notification Center
*/
-export type NotificationAction = {
- label: string; // Button label for the action
- target?: string; // HREF target when the button is clicked
- route?: RouteLocationRaw; // Route to navigate to when the button is clicked
-};
+export type NotificationAction = NotificationApiAction;
/**
* Defines the User Preference linked to a notification
*/
-export type NotificationPreference = {
- key: string; // User preference key to use when setting the preference when the notification is marked as read
- value: string; // User preference value to use when setting the preference when the notification is marked as read
- unsetValue?: string; // User preference value to use when setting the preference when the notification is marked as unread - defaults to empty string
-};
+export type NotificationPreference = NotificationApiPreference;
/**
* Type for Encrypted Notification data that is stored in local storage
diff --git a/shell/types/rancher/index.d.ts b/shell/types/rancher/index.d.ts
index 0458f5a9359..95d24bde46e 100644
--- a/shell/types/rancher/index.d.ts
+++ b/shell/types/rancher/index.d.ts
@@ -7,3 +7,12 @@ declare module '@shell/store/type-map' {
}
declare module '@shell/plugins/dashboard-store';
+
+declare module '@shell/config/query-params' {
+ export const _DETAIL: string;
+}
+
+declare module '@shell/config/version' {
+ export const CURRENT_RANCHER_VERSION: string;
+ export function getVersionData(): any;
+}
diff --git a/shell/types/vue-shim.d.ts b/shell/types/vue-shim.d.ts
index 389cb1b8f6f..0c3ae557c05 100644
--- a/shell/types/vue-shim.d.ts
+++ b/shell/types/vue-shim.d.ts
@@ -1,7 +1,9 @@
/* eslint-disable */
-import type ShellApi from '@shell/plugins/internal-api/shell/shell.api';
import { VuexStore } from '@shell/types/store/vuex';
+// Include the types for the APIs
+///
+
export {};
declare module 'vue' {
@@ -14,7 +16,6 @@ declare module 'vue' {
(key: string, args?: Record, raw?: boolean): string;
(options: { k: string; raw?: boolean; tag?: string | Record; escapehtml?: boolean }): string;
},
- $store: VuexStore,
- $shell: ShellApi,
+ $store: VuexStore
}
-}
+}
\ No newline at end of file