@rancher/shell 3.0.12-rc.5 → 3.0.12-rc.6

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 (89) hide show
  1. package/apis/intf/resources-api/resource-base.ts +42 -2
  2. package/apis/intf/resources-api/resource-instance.ts +101 -0
  3. package/apis/intf/resources-api/resources-api.ts +177 -23
  4. package/apis/intf/resources.ts +8 -6
  5. package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
  6. package/apis/resources/resources-api-class.ts +232 -43
  7. package/assets/images/providers/traefik.png +0 -0
  8. package/assets/translations/en-us.yaml +10 -4
  9. package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
  10. package/chart/rancher-monitoring-dashboards/index.vue +155 -0
  11. package/cloud-credential/__tests__/generic.test.ts +132 -0
  12. package/cloud-credential/generic.vue +33 -3
  13. package/components/EtcdInfoBanner.vue +2 -12
  14. package/components/GrafanaDashboard.vue +2 -8
  15. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
  16. package/components/Resource/Detail/Masthead/index.vue +8 -3
  17. package/components/form/Security.vue +1 -1
  18. package/components/formatter/Translate.vue +22 -1
  19. package/components/nav/Group.vue +5 -10
  20. package/components/nav/TopLevelMenu.vue +1 -30
  21. package/components/nav/__tests__/Group.test.ts +61 -0
  22. package/config/labels-annotations.js +1 -0
  23. package/config/table-headers.js +5 -2
  24. package/core/__tests__/plugin-products-apply.test.ts +787 -0
  25. package/core/__tests__/plugin-products-extend.test.ts +189 -0
  26. package/core/__tests__/plugin-products-helpers.test.ts +81 -78
  27. package/core/__tests__/plugin-products-new.test.ts +389 -0
  28. package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
  29. package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
  30. package/core/plugin-products-base.ts +201 -93
  31. package/core/plugin-products-extending.ts +1 -1
  32. package/core/plugin-products-external.ts +380 -0
  33. package/core/plugin-products-helpers.ts +28 -24
  34. package/core/plugin-products-internal.ts +207 -0
  35. package/core/plugin-products-top-level.ts +10 -5
  36. package/core/plugin-products-type-guards.ts +17 -6
  37. package/core/plugin-products.ts +5 -7
  38. package/core/plugin-types.ts +1 -389
  39. package/core/plugin.ts +10 -11
  40. package/core/types.ts +6 -202
  41. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
  42. package/detail/pod.vue +1 -1
  43. package/detail/provisioning.cattle.io.cluster.vue +0 -2
  44. package/edit/auth/AuthProviderWarningBanners.vue +12 -0
  45. package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
  46. package/edit/auth/__tests__/azuread.test.ts +1 -0
  47. package/edit/auth/__tests__/github.test.ts +1 -0
  48. package/edit/auth/__tests__/oidc.test.ts +1 -0
  49. package/edit/auth/__tests__/saml.test.ts +1 -0
  50. package/edit/auth/ldap/__tests__/index.test.ts +1 -0
  51. package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
  52. package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
  53. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
  54. package/list/management.cattle.io.user.vue +24 -5
  55. package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
  56. package/machine-config/__tests__/generic.test.ts +163 -0
  57. package/machine-config/components/EC2Networking.vue +31 -7
  58. package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
  59. package/machine-config/generic.vue +32 -5
  60. package/mixins/resource-fetch.js +1 -1
  61. package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
  62. package/models/pod.js +12 -10
  63. package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
  64. package/package.json +1 -1
  65. package/pages/auth/login.vue +1 -1
  66. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
  67. package/pages/c/_cluster/apps/charts/install.vue +29 -0
  68. package/pages/c/_cluster/auth/config/index.vue +11 -4
  69. package/pages/c/_cluster/monitoring/index.vue +6 -1
  70. package/plugins/dashboard-store/resource-class.js +10 -3
  71. package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
  72. package/plugins/steve/steve-class.js +43 -0
  73. package/plugins/steve/subscribe.js +11 -0
  74. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
  75. package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
  76. package/store/__tests__/auth.test.ts +18 -0
  77. package/store/__tests__/notifications.test.ts +466 -1
  78. package/store/__tests__/ui-context.test.ts +255 -0
  79. package/store/auth.js +11 -1
  80. package/store/features.js +1 -0
  81. package/store/plugins.js +6 -0
  82. package/types/rancher/steve.api.ts +2 -2
  83. package/types/shell/index.d.ts +58 -0
  84. package/types/store/dashboard-store.types.ts +2 -2
  85. package/types/store/type-map.ts +262 -1
  86. package/utils/async.ts +2 -0
  87. package/utils/grafana.js +2 -17
  88. package/utils/monitoring.js +38 -1
  89. package/core/__tests__/plugin-products.test.ts +0 -4694
@@ -1,28 +1,32 @@
1
1
  import { IExtension } from '@shell/core/types';
2
- import { ProductChild, ProductMetadata, ProductSinglePage } from '@shell/core/plugin-types';
3
2
  import EmptyProductPage from '@shell/components/EmptyProductPage.vue';
4
3
  import pluginProductsHelpers from '@shell/core/plugin-products-helpers';
5
4
  import { BasePluginProduct } from '@shell/core/plugin-products-base';
6
5
  import { isProductSinglePage } from '@shell/core/plugin-products-type-guards';
6
+ import { ProductChild, ProductMetadata, ProductMetadataSinglePage } from '@shell/core/plugin-products-external';
7
7
 
8
8
  /**
9
9
  * Represents a new top-level product being added by an extension
10
10
  * @internal
11
11
  */
12
12
  export class TopLevelPluginProduct extends BasePluginProduct {
13
+ protected product?: ProductMetadata | ProductMetadataSinglePage;
14
+
13
15
  get isNewProduct(): boolean {
14
16
  return true;
15
17
  }
16
18
 
17
- constructor(plugin: IExtension, product: ProductMetadata | ProductSinglePage | string, config: ProductChild[]) {
18
- super(config);
19
+ constructor(plugin: IExtension, product: ProductMetadata | ProductMetadataSinglePage | string, pages: ProductChild[]) {
20
+ super(pages);
19
21
 
20
22
  // Convenience/bridge method: create a basic product from just a name string
21
23
  if (typeof product === 'string') {
22
- product = {
24
+ const emptyProduct: ProductMetadata = {
23
25
  name: product,
24
26
  label: product,
25
27
  };
28
+
29
+ product = emptyProduct;
26
30
  }
27
31
 
28
32
  let prodName = product.name;
@@ -45,8 +49,9 @@ export class TopLevelPluginProduct extends BasePluginProduct {
45
49
 
46
50
  // If the product has a `component` field, then this is a single page product
47
51
  if (isProductSinglePage(product)) {
52
+ const productSinglePage: ProductMetadataSinglePage = product as ProductMetadataSinglePage;
48
53
  // Add the route to vue-router (here we go with the 'plain' layout for simple single page products)
49
- const route = pluginProductsHelpers.generateTopLevelExtensionSimpleBaseRoute(this.name, { component: product.component });
54
+ const route = pluginProductsHelpers.generateTopLevelExtensionSimpleBaseRoute(this.name, { component: productSinglePage.component });
50
55
 
51
56
  plugin.addRoute('plain', route);
52
57
  } else if (this.config.length === 0) {
@@ -1,19 +1,30 @@
1
- import {
2
- ProductChild, ProductChildGroup, ProductMetadata,
3
- ProductSinglePage, ProductChildCustomPage, ProductChildResourcePage
4
- } from '@shell/core/plugin-types';
5
1
 
6
2
  /**
7
3
  * Type guard functions for discriminating union types
8
4
  * @internal
9
5
  */
10
6
 
11
- export function isProductSinglePage(product: ProductMetadata | ProductSinglePage): product is ProductSinglePage {
7
+ import {
8
+ ProductChild, ProductChildCustomPage, ProductChildGroup, ProductChildResourcePage, ProductMetadata, ProductMetadataSinglePage
9
+ } from '@shell/core/plugin-products-external';
10
+ import { ProductMetadataInternal } from '@shell/core/plugin-products-internal';
11
+
12
+ export function isProductConfigInternal(product: ProductMetadata | ProductMetadataSinglePage): product is ProductMetadataInternal {
13
+ const props = ['category', 'version'];
14
+
15
+ return props.some((prop) => prop in product);
16
+ }
17
+
18
+ export function isProductSinglePage(product: ProductMetadata | ProductMetadataSinglePage): product is ProductMetadataSinglePage {
12
19
  return 'component' in product && product.component !== undefined;
13
20
  }
14
21
 
22
+ export function isProductAdd(product: ProductMetadata | ProductMetadataSinglePage): product is ProductMetadata {
23
+ return !('component' in product && product.component !== undefined);
24
+ }
25
+
15
26
  export function isProductChildGroup(child: ProductChild): child is ProductChildGroup {
16
- return 'children' in child;
27
+ return child.sideMenu !== undefined && 'children' in child.sideMenu;
17
28
  }
18
29
 
19
30
  export function isProductChildWithComponent(child: ProductChild): child is ProductChildCustomPage {
@@ -1,11 +1,9 @@
1
1
  import { IExtension } from '@shell/core/types';
2
- import {
3
- StandardProductName, ProductChild,
4
- ProductMetadata, ProductSinglePage,
5
- } from '@shell/core/plugin-types';
6
2
  import { BasePluginProduct } from '@shell/core/plugin-products-base';
7
3
  import { TopLevelPluginProduct } from '@shell/core/plugin-products-top-level';
8
4
  import { ExtendingPluginProduct } from '@shell/core/plugin-products-extending';
5
+ import { ProductChild, ProductMetadata, ProductMetadataSinglePage, StandardProductName } from '@shell/core/plugin-products-external';
6
+ import { ProductFunction } from '@shell/core/plugin';
9
7
 
10
8
  /**
11
9
  * Factory class for creating plugin products
@@ -15,13 +13,13 @@ import { ExtendingPluginProduct } from '@shell/core/plugin-products-extending';
15
13
  export class PluginProduct {
16
14
  private instance: BasePluginProduct;
17
15
 
18
- constructor(plugin: IExtension, product: StandardProductName | string | ProductMetadata | ProductSinglePage, config: ProductChild[]) {
16
+ constructor(plugin: IExtension, product: StandardProductName | string | ProductMetadata | ProductMetadataSinglePage | ProductFunction, pages: ProductChild[]) {
19
17
  if (typeof product === 'object' && product.name) {
20
18
  // This is a new product being added
21
- this.instance = new TopLevelPluginProduct(plugin, product, config);
19
+ this.instance = new TopLevelPluginProduct(plugin, product, pages);
22
20
  } else if (typeof product === 'string') {
23
21
  // This is extending an existing standard product
24
- this.instance = new ExtendingPluginProduct(plugin, product, config);
22
+ this.instance = new ExtendingPluginProduct(plugin, product, pages);
25
23
  } else {
26
24
  // at this point we may not know the product name
27
25
  throw new Error('Extensions product registration error ::: Invalid product');
@@ -1,12 +1,4 @@
1
- import type { RouteComponent, RouteRecordRaw } from 'vue-router';
2
- import type { PluginRouteRecordRaw } from './types';
3
- import { NAME as EXPLORER_PROD_NAME } from '@shell/config/product/explorer.js';
4
- import { NAME as CLUSTER_MAN_PROD_NAME } from '@shell/config/product/manager.js';
5
- import { NAME as SETTINGS_PROD_NAME } from '@shell/config/product/settings.js';
6
- import { NAME as AUTH_PROD_NAME } from '@shell/config/product/auth.js';
7
- import { ProductOptions, HeaderOptions, PaginationHeaderOptions } from '@shell/core/types';
8
-
9
- type Async<T> = () => Promise<T>;
1
+ import { RouteRecordRaw } from 'vue-router';
10
2
 
11
3
  export type RouteRecordRawWithParams = Omit<RouteRecordRaw, 'redirect' | 'children' | 'path'> & {
12
4
  /** Path for route - can include dynamic segments like ':id'. Based on vue-router routes */
@@ -18,383 +10,3 @@ export type RouteRecordRawWithParams = Omit<RouteRecordRaw, 'redirect' | 'childr
18
10
  /** Optional redirect */
19
11
  redirect?: RouteRecordRaw['redirect'];
20
12
  };
21
-
22
- /**
23
- * Product registration route generation options
24
- */
25
- export type ProductRegistrationRouteGenerationOptions = {
26
- /**
27
- * Whether to generate the route for an existing product - extending a product - or a new top level product
28
- */
29
- extendProduct? : boolean;
30
- /**
31
- * Component to be used for the route (used in virtualType and configureType routes)
32
- */
33
- component?: any;
34
- /**
35
- * Generated route should omit the path property
36
- */
37
- omitPath?: boolean;
38
- /**
39
- * @internal
40
- * Whether the route should start with the product name or not (e.g. "my-product/c/:cluster/:resource" vs "c/:cluster/my-product/:resource")
41
- * only to be used in very special usecases (internal use only - check FLEET product config for an example)
42
- * */
43
- startRouteWithProduct?: boolean;
44
- }
45
-
46
- /**
47
- * Represents the allowed extensible products in Rancher Dashboard
48
- */
49
- export const StandardProductNames = {
50
- EXPLORER: EXPLORER_PROD_NAME,
51
- MANAGER: CLUSTER_MAN_PROD_NAME,
52
- SETTINGS: SETTINGS_PROD_NAME,
53
- AUTH: AUTH_PROD_NAME,
54
- } as const;
55
-
56
- /**
57
- * Type representing a standard product name value
58
- */
59
- export type StandardProductName = (typeof StandardProductNames)[keyof typeof StandardProductNames];
60
-
61
- /**
62
- * Represents allowed configuration for a child page
63
- */
64
- export type ProductChildMetadata = {
65
- /** Product name/unique identifier for the product */
66
- name: string;
67
- /** Ordering weight for the among its siblings, if applicable */
68
- weight?: number;
69
- } & (
70
- /** Human-readable label for the child page
71
- * Either label or labelKey are required */
72
- { label: string; labelKey?: string }
73
- /** Translation key for the label of the child page
74
- * Either label or labelKey are required */
75
- | { labelKey: string; label?: string }
76
- );
77
-
78
- /**
79
- * Represents the allowed configuration for a custom page (virtualType)
80
- */
81
- export type CustomPageConfiguration = {
82
- /** Display only if condition is met (relates to IF_HAVE in shell/store/type-map) */
83
- ifHave?: boolean;
84
- /** Display only if feature is present (relates to shell/store/features) */
85
- ifFeature?: string;
86
- /** Display only if resource type exists */
87
- ifHaveType?: string;
88
- /** Used in conjunction with "ifHaveType", display only if resource type allows this verb (GET, POST, PUT, DELETE) */
89
- ifHaveVerb?: string;
90
- /**
91
- * @internal
92
- * Display label for the custom page
93
- */
94
- label?: string;
95
- /**
96
- * @internal
97
- * Translation key for the label
98
- * */
99
- labelKey?: string;
100
- /**
101
- * @internal
102
- * Name of the page (unique identifier)
103
- */
104
- name?: string;
105
- /**
106
- * @internal
107
- * Entry route definition for this custom page
108
- */
109
- route?: RouteRecordRawWithParams | PluginRouteRecordRaw | Object;
110
- /**
111
- * @internal
112
- * Icon for the custom page (relates to icons in https://github.com/rancher/icons)
113
- */
114
- icon?: 'compass';
115
- /** Whether this custom page is namespaced or not */
116
- namespaced?: boolean;
117
- /**
118
- * @internal
119
- * Ordering weight for the custom page
120
- */
121
- weight?: number;
122
- /** Whether this custom page is exact match */
123
- exact?: boolean;
124
- /**
125
- * @internal
126
- * Whether this custom page will act as an overview page
127
- * */
128
- overview?: boolean;
129
- /** Whether this custom page has an exact path match */
130
- 'exact-path'?: boolean;
131
- }
132
-
133
- /**
134
- * Represents the allowed configuration for a resource page (configureType)
135
- */
136
- export type ResourcePageConfiguration = {
137
- /**
138
- * @internal
139
- * Entry route definition for this custom page
140
- */
141
- /** Override for the name displayed */
142
- displayName?: string;
143
- /** Override for the create button string on a list view */
144
- listCreateButtonLabelKey?: string;
145
- /** If false, disable create even if schema says it's allowed */
146
- isCreatable?: boolean;
147
- /** If false, disable for edit */
148
- isEditable?: boolean;
149
- /** If false, disable for remove/delete */
150
- isRemovable?: boolean;
151
- /** If false, hide state in columns and masthead */
152
- showState?: boolean;
153
- /** If false, hide age in columns and masthead */
154
- showAge?: boolean;
155
- /** If false, hide masthead config button in view mode */
156
- showConfigView?: boolean;
157
- /** If false, hide masthead in list view */
158
- showListMasthead?: boolean;
159
- /** If false, cannot edit or show yaml */
160
- canYaml?: boolean;
161
- /** Show the Masthead in the edit resource component */
162
- resourceEditMasthead?: boolean;
163
- /**
164
- * @internal
165
- * Entry route definition for this resource page
166
- */
167
- customRoute?: RouteRecordRawWithParams;
168
- /** Hide this type from the nav/search bar on downstream clusters (will only show in "local" cluster) */
169
- localOnly?: boolean;
170
- /** Whether this custom page is namespaced or not */
171
- namespaced?: boolean;
172
- /**
173
- * @internal
174
- * Whether this custom page has list groups (definition for grouping items in the list view)
175
- */
176
- listGroups?: {
177
- /** Icon for the group (relates to icons in rancher-icons */
178
- icon?: string;
179
- /** Value for the group (used for grouping items in the list view) */
180
- value?: string;
181
- /** Field for the group (used for grouping items in the list view) */
182
- field?: string;
183
- /** Column to hide when this group is active */
184
- hideColumn?: string;
185
- /** Tooltip key for the group */
186
- tooltipKey?: string;
187
- }[];
188
- /**
189
- * @internal
190
- * Whether the provided list groups will override the default grouping options (e.g. group by namespace, group by cluster, etc.) or be added to them
191
- */
192
- listGroupsWillOverride?: boolean;
193
- /**
194
- * @internal
195
- * Use this to configure subtypes that should be shown in the list view for this type (e.g. show "pods" and "deployments" in the list view for "workloads")
196
- */
197
- subTypes?: string[];
198
- // resource: undefined; // Use this resource in ResourceDetails instead
199
- // resourceDetail: undefined; // Use this resource specifically for ResourceDetail's detail component
200
- // resourceEdit: undefined; // Use this resource specifically for ResourceDetail's edit component
201
- // depaginate: undefined; // Use this to depaginate requests for this type
202
- // notFilterNamespace: undefined; // Define namespaces that do not need to be filtered
203
- // used in configureType options, to be typed later if needed
204
- }
205
-
206
- /**
207
- * Represents a Vue component or an async function that resolves to a Vue component, used for route components in product configuration
208
- */
209
- export type VueRouteComponent = RouteComponent | Async<RouteComponent>;
210
-
211
- /**
212
- * Metadata for route generation to a product overview page
213
- */
214
- export type OverviewPageRoutingMetadata = {
215
- /** Name of the overview page */
216
- name: string;
217
- /** Component to render for the overview page */
218
- component: VueRouteComponent;
219
- }
220
-
221
- /**
222
- * Represents a resource page with a type (K8s resource)
223
- */
224
- export type ProductChildResourcePage = {
225
- /** K8s resource type name for a resource page */
226
- type: string;
227
- /** Optional configuration for the resource page */
228
- config?: ResourcePageConfiguration;
229
- /** Ordering weight for this page among its siblings */
230
- weight?: number;
231
- /** Use this to override the resource name used in the list view for this type */
232
- overrideListResourceName?: string;
233
- /** Whether to hide this resource from the side-menu entirely */
234
- hideFromNav?: boolean;
235
- /** Whether to hide bulk actions for this resource */
236
- hideBulkActions?: boolean;
237
- /** Table headers for this resource type (client-side pagination) */
238
- headers?: HeaderOptions[];
239
- /** Table headers for this resource type (server-side pagination) */
240
- sspHeaders?: PaginationHeaderOptions[];
241
- };
242
-
243
- /**
244
- * Represents a custom page with a component
245
- */
246
- export type ProductChildCustomPage = ProductChildMetadata & {
247
- /** Component to render for this custom page */
248
- component: VueRouteComponent;
249
- /** Optional configuration for the page */
250
- config?: CustomPageConfiguration;
251
- };
252
-
253
- /**
254
- * Represents a page item (custom page or resource page) in a product's config
255
- * - For custom pages: use `component` with `name` and `label`/`labelKey`
256
- * - For resource pages: use `type` with optional `config` and `headers`
257
- */
258
- export type ProductChildPage = ProductChildCustomPage | ProductChildResourcePage;
259
-
260
- /**
261
- * Represents a product child in the navigation
262
- * This can be a type, a group, or a route.
263
- */
264
- export type ProductChild = ProductChildGroup | ProductChildPage; // eslint-disable-line no-use-before-define
265
-
266
- /**
267
- * Represents a group of child pages in a product configuration
268
- */
269
- export type ProductChildGroup = ProductChildMetadata & {
270
- component?: VueRouteComponent;
271
- children: ProductChild[];
272
- /** Default child to navigate to */
273
- default?: string;
274
- };
275
-
276
- /**
277
- * Represents the allowed configuration for a product
278
- */
279
- export type ProductMetadata = Omit<ProductOptions, 'name' | 'label' | 'labelKey' | 'category' | 'to' | 'version' | 'inStore'> & {
280
- /**
281
- * Product name (unique identifier)
282
- */
283
- name: string;
284
- /**
285
- * @internal
286
- * Use `renameGroups` on the product metadata to remap group display names in the side-menu. Each entry matches a group's internal ID (via string or regex) and replaces its display label with a new name. This only changes how the group is labelled in the UI — it does not move resources between groups.
287
- *
288
- * The `groupSelector` is evaluated against group internal IDs. It can be an exact string or a `RegExp` pattern. The `newName` value is the new display name.
289
- *
290
- * const product: ProductMetadata = {
291
- * name: 'my-app',
292
- * label: 'My App',
293
- * renameGroups: [
294
- * // Rename a group with an ugly internal ID to a friendlier display name
295
- * { groupSelector: 'cert-manager.io', newName: 'Certificates' },
296
- * // Use a regex to rename all groups matching a pattern
297
- * { groupSelector: /^networking\./, newName: 'Networking' },
298
- * ],
299
- * };
300
- */
301
- renameGroups?: {
302
- /** String or regex to match against group internal IDs */
303
- groupSelector: RegExp | string;
304
- /** Display name to use for matching groups */
305
- newName: string;
306
- }[];
307
- /**
308
- * @internal
309
- *
310
- * Use `moveToGroup` on the product metadata to move pages (resource types or custom pages) into specific side-menu groups. This is useful when a page should appear inside a group but isn't defined as a child of that group in the config.
311
- * Each entry identifies a page by its `entryId` — the resource `type` string or the custom page `name` — and moves it into the specified group. Use the group's `name` as you defined it in your config.
312
- *
313
- * const monitoringGroup: ProductChildGroup = {
314
- * name: 'monitoring',
315
- * label: 'Monitoring',
316
- * children: [
317
- * { name: 'alerts', label: 'Alerts', component: () => import('./pages/Alerts.vue') },
318
- * ],
319
- * };
320
-
321
- * const dashboardPage: ProductChildCustomPage = {
322
- * name: 'dashboard', label: 'Dashboard', component: () => import('./pages/Dashboard.vue'),
323
- * };
324
-
325
- * const product: ProductMetadata = {
326
- * name: 'my-app',
327
- * label: 'My App',
328
- * moveToGroup: [
329
- * // Move the 'pod' resource type into the 'monitoring' group
330
- * { entryId: 'pod', groupName: 'monitoring' },
331
- * // Move a custom page into the 'monitoring' group
332
- * { entryId: 'dashboard', groupName: 'monitoring' },
333
- * ],
334
- * };
335
- *
336
- * extension.addProduct(product, [monitoringGroup, { type: 'pod' }, dashboardPage]);
337
- *
338
- * Note: The `entryId` must match a page declared in the same product config — either a resource page's `type` or a custom page's `name`. The target `groupName` must be a `ProductChildGroup` defined in the same config. If either is not found, an error is thrown at registration time listing the available options. Only exact string identifiers are supported (no regex).
339
- *
340
- * The optional `weight` parameter controls precedence when multiple `moveToGroup` rules target the same page (default: `5`). Higher weight takes precedence.
341
- */
342
- moveToGroup?: {
343
- /** Page identifier — the resource `type` string or the custom page `name` */
344
- entryId: string;
345
- /** Target group name as defined in your group config (`name` property) */
346
- groupName: string;
347
- /** Ordering weight for the mapping (default: 5). Higher weight takes precedence when multiple rules match */
348
- weight?: number;
349
- }[];
350
- /**
351
- * @internal
352
- *
353
- * maps to DSL ignoreGroup
354
- *
355
- * Use `ignoreGroups` on the product metadata to hide specific side-menu groups. Each entry specifies a `groupSelector` to match group names — either an exact string or a regex pattern.
356
- *
357
- * The `condition` callback is optional. When provided, it receives the store getters and returns `true` to hide the group (conditional hide). When omitted, the group is always hidden (unconditional hide).
358
- *
359
- * Example usage:
360
- * const product: ProductMetadata = {
361
- * name: 'my-app',
362
- * label: 'My App',
363
- * ignoreGroups: [
364
- * // Always hide the "internal" group (unconditional — no condition)
365
- * { groupSelector: 'internal' },
366
- * // Hide all groups matching a regex pattern (unconditional)
367
- * { groupSelector: /^deprecated/ },
368
- * // Conditionally hide based on a feature flag
369
- * {
370
- * groupSelector: 'experimental',
371
- * condition: (getters) => !getters['features/isEnabled']('experimental-feature'),
372
- * },
373
- * ],
374
- * };
375
- *
376
- *
377
- * In this example, the "internal" group is always hidden, any group with a name starting with "deprecated-" is hidden, and the "experimental" group is hidden unless the "experimental-feature" flag is enabled in the store.
378
- */
379
- ignoreGroups?: {
380
- /** String or regex to match against group names */
381
- groupSelector: string | RegExp;
382
- /** Optional conditional function that accepts the root Dashboard Vuex store getters and returns true if the group should be ignored */
383
- condition?: (getters: any) => boolean }[];
384
- } & (
385
- /** Human-readable label for the product
386
- * Either label or labelKey are required */
387
- | { label: string; labelKey?: string }
388
- /** Translation key for the label of the product
389
- * Either label or labelKey are required */
390
- | { labelKey: string; label?: string }
391
- );
392
-
393
- /**
394
- * Represents a single page product, which is a product that only has one page and
395
- * therefore does not need to be represented in the navigation as a group with children.
396
- */
397
- export type ProductSinglePage = ProductMetadata & {
398
- /** Component to render for this product (single page product) */
399
- component: VueRouteComponent;
400
- };
package/core/plugin.ts CHANGED
@@ -21,18 +21,17 @@ import {
21
21
  ServerSidePaginationExtensionConfig,
22
22
  TableAction,
23
23
  } from './types';
24
- import {
25
- ProductMetadata,
26
- ProductSinglePage,
27
- ProductChild,
28
- StandardProductName,
29
- RouteRecordRawWithParams
30
- } from './plugin-types';
24
+ import { RouteRecordRawWithParams } from './plugin-types';
31
25
  import coreStore, { coreStoreModule, coreStoreState } from '@shell/plugins/dashboard-store';
32
26
  import { defineAsyncComponent, markRaw, Component } from 'vue';
33
27
  import { getVersionData, CURRENT_RANCHER_VERSION } from '@shell/config/version';
34
28
  import { ExtensionManagerTypes } from '@shell/types/extension-manager';
35
29
  import { PluginProduct } from './plugin-products';
30
+ import {
31
+ ProductMetadata, ProductMetadataSinglePage,
32
+ StandardProductName,
33
+ ProductChild
34
+ } from '@shell/core/plugin-products-external';
36
35
 
37
36
  /** Registration IDs used for different extension points in the extensions catalog */
38
37
  export const EXT_IDS = {
@@ -137,16 +136,16 @@ export class Plugin implements IPlugin {
137
136
  return storeDSL;
138
137
  }
139
138
 
140
- addProduct(product: ProductFunction | ProductMetadata | ProductSinglePage | string, config?: ProductChild[]): void {
139
+ addProduct(product: ProductFunction | ProductMetadata | ProductMetadataSinglePage | string, pages?: ProductChild[]): void {
141
140
  let pluginProduct: PluginProduct;
142
141
 
143
142
  if (typeof product === 'string') {
144
143
  pluginProduct = PluginProduct.fromName(this, product);
145
144
  } else if (product?.name) {
146
- if (!config) {
147
- pluginProduct = new PluginProduct(this, product as ProductSinglePage, []);
145
+ if (!pages) {
146
+ pluginProduct = new PluginProduct(this, product, []);
148
147
  } else {
149
- pluginProduct = new PluginProduct(this, product as ProductMetadata, config);
148
+ pluginProduct = new PluginProduct(this, product, pages);
150
149
  }
151
150
  } else {
152
151
  this.products.push(product as ProductFunction);