@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
@@ -0,0 +1,380 @@
1
+ import { ProductFunction } from '@shell/core/plugin';
2
+ import { HeaderOptions } from '@shell/core/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 { RouteComponent } from 'vue-router';
8
+
9
+ type Async<T> = () => Promise<T>;
10
+
11
+ /**
12
+ * Represents the allowed extensible products in Rancher Dashboard
13
+ */
14
+ export const StandardProductNames = {
15
+ EXPLORER: EXPLORER_PROD_NAME,
16
+ MANAGER: CLUSTER_MAN_PROD_NAME,
17
+ SETTINGS: SETTINGS_PROD_NAME,
18
+ AUTH: AUTH_PROD_NAME,
19
+ } as const;
20
+
21
+ /**
22
+ * Type representing a standard product name value
23
+ */
24
+ export type StandardProductName = (typeof StandardProductNames)[keyof typeof StandardProductNames];
25
+
26
+ /**
27
+ * Represents a Vue component or an async function that resolves to a Vue component, used for route components in product configuration
28
+ */
29
+ export type VueRouteComponent = RouteComponent | Async<RouteComponent>;
30
+
31
+ // -------------- Page Related Resource --------------
32
+ // When updating external page types also update where they're converted to the internal page type
33
+ // i.e. `TypeMapVirtualType` or `TypeMapConfigureType` usage in shell/core/plugin-products-base.ts
34
+
35
+ /**
36
+ * Type that enforces either a `label` or `labelKey`
37
+ */
38
+ type LabelOrLabelKey = (
39
+ /** Human-readable label for the product
40
+ * Either label or labelKey are required */
41
+ | { label: string; labelKey?: string }
42
+ /** Translation key for the label of the product
43
+ * Either label or labelKey are required */
44
+ | { labelKey: string; label?: string }
45
+ )
46
+
47
+ /**
48
+ * Represents a custom page with a component
49
+ */
50
+ export type ProductChildCustomPage = LabelOrLabelKey & {
51
+ /** Product name/unique identifier for the product */
52
+ name: string;
53
+
54
+ /** Component to render for this custom page */
55
+ component: VueRouteComponent;
56
+
57
+ /** Determine when this product is enabled and shown */
58
+ enable?: {
59
+ /** Display only if condition is met (relates to IF_HAVE in shell/store/type-map) */
60
+ ifHave?: boolean;
61
+ /** Display only if feature is present (relates to shell/store/features) */
62
+ ifFeature?: string;
63
+ /** Display only if resource type exists */
64
+ ifHaveType?: string;
65
+ /** Used in conjunction with "ifHaveType", display only if resource type allows this verb (GET, POST, PUT, DELETE) */
66
+ ifHaveVerb?: string;
67
+ };
68
+
69
+ /** Control how the child displays in the side menu */
70
+ sideMenu?: {
71
+ /** Ordering weight for this page among its siblings */
72
+ weight?: number;
73
+ }
74
+
75
+ /** Control how resources are handled in this product */
76
+ resource?: {
77
+ /** Whether resources this page represents is namespaced */
78
+ namespaced?: boolean;
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Represents a resource page with a type (K8s resource)
84
+ */
85
+ export type ProductChildResourcePage = {
86
+ /** K8s resource type name for a resource page */
87
+ type: string;
88
+
89
+ /**
90
+ * Optional. Override the label for this page
91
+ */
92
+ label?: string
93
+
94
+ /** Control how the child displays in the side menu */
95
+ sideMenu?: {
96
+ /** Ordering weight for this page among its siblings */
97
+ weight?: number;
98
+ /** Hide this type from the nav/search bar on downstream clusters (will only show in "local" cluster) */
99
+ localOnly?: boolean;
100
+ }
101
+
102
+ /** Control how the product displays some information */
103
+ display?: {
104
+ /** If false, hide state in columns and masthead */
105
+ showState?: boolean;
106
+ /** If false, hide age in columns and masthead */
107
+ showAge?: boolean;
108
+ }
109
+
110
+ /** Control what actions and features can be carried out for this resource type */
111
+ can?: {
112
+ /** If false, disable create even if schema says it's allowed */
113
+ create?: boolean;
114
+ /** If false, disable for edit */
115
+ edit?: boolean;
116
+ /** If false, disable for remove/delete */
117
+ remove?: boolean;
118
+ /** If false, cannot edit or show yaml */
119
+ yaml?: boolean;
120
+ }
121
+
122
+ /** Control how this resource behaves in resource list, detail and create/edit pages */
123
+ views?: {
124
+
125
+ /** Control how this resource behaves in a resource list page */
126
+ list?: {
127
+ /** Override for the create button string on a list view */
128
+ createLabelKey?: string;
129
+
130
+ /** If false, hide masthead in list view. Defaults to true */
131
+ showListMasthead?: boolean;
132
+ }
133
+
134
+ /** Control how this resource behaves in a resource detail page */
135
+ detail?: {
136
+ /** If false, hide masthead config button in view mode. Defaults to true */
137
+ showConfigView?: boolean;
138
+ }
139
+
140
+ /** Control how this resource behaves in a resource create/edit page */
141
+ createEdit?: {
142
+ /** Show the Masthead in the edit resource component. Defaults to true */
143
+ showMasthead?: boolean;
144
+ }
145
+ }
146
+
147
+ /** Control how all lists that show this resource behave */
148
+ listConfig?: {
149
+ /** Table headers for this resource type (server-side pagination) */
150
+ headers?: HeaderOptions[];
151
+ /** Whether to hide bulk actions for this resource */
152
+ hideBulkActions?: boolean;
153
+ }
154
+ };
155
+
156
+ /**
157
+ * Represents a page item (custom page or resource page) in a product's config
158
+ */
159
+ export type ProductChildPage = ProductChildCustomPage | ProductChildResourcePage;
160
+
161
+ /**
162
+ * Represents a product child in the navigation
163
+ * This can be a type, a group, or a route.
164
+ */
165
+ export type ProductChild = ProductChildGroup | ProductChildPage; // eslint-disable-line no-use-before-define
166
+
167
+ /**
168
+ * Represents a group of child pages in a product configuration
169
+ */
170
+ export type ProductChildGroup = LabelOrLabelKey & {
171
+ /** Product name/unique identifier for the product */
172
+ name: string;
173
+
174
+ /** Component to render for this group */
175
+ component?: VueRouteComponent;
176
+
177
+ /** Control how the child displays menu items in the side menu */
178
+ sideMenu: {
179
+ /**
180
+ * Add entries to the side menu
181
+ */
182
+ children: ProductChild[];
183
+ /** Ordering weight for this group among its siblings */
184
+ weight?: number;
185
+ /** Default child to navigate to */
186
+ default?: string;
187
+ }
188
+ };
189
+
190
+ // -------------- Product Related Resource --------------
191
+
192
+ // When updating external product types also update where they're converted to the internal product type
193
+ // i.e. `TypeMapProduct` usage in shell/core/plugin-products-base.ts
194
+
195
+ /**
196
+ * Represents the allowed configuration for a product
197
+ */
198
+ type _ProductMetadata = LabelOrLabelKey & {
199
+ /**
200
+ * Product name (unique identifier)
201
+ */
202
+ name: string;
203
+
204
+ /**
205
+ * Control conditions on when to show the product
206
+ */
207
+ enable?: {
208
+ /**
209
+ * Only load the product if the feature is present
210
+ */
211
+ ifFeature?: string | RegExp;
212
+
213
+ /**
214
+ * Only load the product if the type is present
215
+ */
216
+ ifHave?: string;
217
+
218
+ /**
219
+ * Only load the product if the group is present
220
+ */
221
+ ifHaveGroup?: string | RegExp;
222
+
223
+ /**
224
+ * Only load the product if the type is present
225
+ */
226
+ ifHaveType?: string | RegExp;
227
+
228
+ /**
229
+ * Hide the product if the type is present (opposite of ifHaveType)
230
+ */
231
+ ifNotHaveType?: string | RegExp;
232
+ }
233
+
234
+ /** Control what appears in the UI's main application header */
235
+ appHeader?: {
236
+ /**
237
+ * Hide the Copy KubeConfig button in the header
238
+ */
239
+ hideCopyConfig?: boolean;
240
+
241
+ /**
242
+ * Hide the Download KubeConfig button in the header
243
+ */
244
+ hideKubeConfig?: boolean;
245
+
246
+ /**
247
+ * Hide the Kubectl Shell button in the header
248
+ */
249
+ hideKubeShell?: boolean;
250
+
251
+ /**
252
+ * Show the cluster info
253
+ */
254
+ showClusterInfo?: boolean;
255
+
256
+ /**
257
+ * Show the namespace filter in the header
258
+ */
259
+ showNamespaceFilter?: boolean;
260
+
261
+ /**
262
+ * Show a product icon in the header
263
+ */
264
+ icon?: string;
265
+ },
266
+
267
+ /**
268
+ * Control what appears in the vertical side bar on the left of the UI
269
+ */
270
+ sideBar?: {
271
+ /**
272
+ * A number used to determine where in navigation this item will be placed. The highest number will be at the top of the list.
273
+ */
274
+ weight?: number;
275
+
276
+ /**
277
+ * The icon to display in the side bar for this product
278
+ */
279
+ icon: {
280
+ /**
281
+ * Icon pack name
282
+ */
283
+ name?: string;
284
+
285
+ /**
286
+ * Alternative to the icon property. Uses require
287
+ */
288
+ svg?: Function;
289
+ },
290
+
291
+ }
292
+
293
+ /**
294
+ * Control how resources are fetched, filtered and stored
295
+ */
296
+ resources?: {
297
+ /**
298
+ * Hide the system resources in lists
299
+ */
300
+ hideSystemResources?: boolean;
301
+
302
+ /**
303
+ * The vuex store that this product should use by default i.e. 'management'
304
+ */
305
+ store?: string;
306
+ }
307
+
308
+ /**
309
+ * Indicates whether UI Extensions can add pages to this product
310
+ */
311
+ extendable?: boolean;
312
+ }
313
+
314
+ export type ProductMetadata = _ProductMetadata
315
+
316
+ /**
317
+ * Represents a single page product, which is a product that only has one page and
318
+ * therefore does not need to be represented in the navigation as a group with children.
319
+ */
320
+ export type ProductMetadataSinglePage = ProductMetadata & {
321
+ /** Component to render for this product (single page product) */
322
+ component: VueRouteComponent;
323
+ };
324
+
325
+ // -------------- Extension methods --------------
326
+
327
+ export interface IExtensionProducts {
328
+ /**
329
+ * Register a top-level product as a flag on the plugin
330
+ * @internal - DO NOT USE - Internal API only
331
+ */
332
+ _registerTopLevelProduct(): void;
333
+
334
+ /**
335
+ *
336
+ * @internal - DO NOT USE - Internal API only
337
+ */
338
+ _setStartRouteWithProduct(value: boolean): void;
339
+
340
+ /**
341
+ * Add a product to the sidebar, with children and a side menu for navigation for internal pages
342
+ * @param product
343
+ * @param sideMenu Pages associated with the product. These will appear in the side menu
344
+ */
345
+ addProduct(product: ProductMetadata, sideMenu: ProductChildPage[]): void;
346
+ addProduct(product: ProductMetadata, sideMenu: ProductChildGroup[]): void;
347
+ addProduct(product: ProductMetadata, sideMenu: ProductChild[]): void;
348
+
349
+ /**
350
+ * Add a product to the sidebar, without children (no side menu, single page only)
351
+ * @param product
352
+ */
353
+ addProduct(product: ProductMetadataSinglePage): void;
354
+
355
+ /**
356
+ * Add a product with just a name (convenience/bridge method for quick setup).
357
+ * Creates a basic product with an empty page component automatically.
358
+ * This is useful for getting started quickly - expand to the full API once you're ready to add custom pages.
359
+ * @param productName Simple product name - will be used as both the name and label
360
+ */
361
+ addProduct(productName: string): void;
362
+
363
+ /**
364
+ * Add a product to the sidebar (deprecated, use other signatures of addProduct instead)
365
+ *
366
+ * Once other `addProduct` signatures reach maturity this will be deprecated
367
+ * @param importFn Function that will import the module containing a product definition
368
+ */
369
+ addProduct(importFn: ProductFunction): void;
370
+
371
+ /**
372
+ * Extend an existing product in Rancher, with children and a side menu for navigation for internal pages
373
+ *
374
+ * @param productName Name of the product to be extended
375
+ * @param sideMenu Pages that will be added to the product. These will appear in the side menu
376
+ */
377
+ extendProduct(productName: StandardProductName | string, sideMenu: ProductChildPage[]): void;
378
+ extendProduct(productName: StandardProductName | string, sideMenu: ProductChildGroup[]): void;
379
+ extendProduct(productName: StandardProductName | string, sideMenu: ProductChild[]): void;
380
+ }
@@ -1,20 +1,21 @@
1
- import {
2
- RouteRecordRawWithParams, ProductChildGroup, ProductChild,
3
- ProductChildCustomPage, ProductChildResourcePage, ProductRegistrationRouteGenerationOptions,
4
- OverviewPageRoutingMetadata
5
- } from '@shell/core/plugin-types';
1
+ import { ProductChild, ProductChildCustomPage, ProductChildResourcePage } from '@shell/core/plugin-products-external';
2
+ import { ProductRegistrationRouteGenerationOptions } from '@shell/core/plugin-products-internal';
3
+ import { isProductChildGroup } from '@shell/core/plugin-products-type-guards';
4
+ import { RouteRecordRawWithParams } from '@shell/core/plugin-types';
6
5
  import { BLANK_CLUSTER } from '@shell/store/store-types';
7
6
 
8
- function isProductChildGroup(child: ProductChild): child is ProductChildGroup {
9
- return 'children' in child && Array.isArray(child.children);
10
- }
11
-
12
7
  class PluginProductsHelpers {
8
+ private weightFromProductChild(item: ProductChild): number | undefined {
9
+ return (item as ProductChildCustomPage).sideMenu?.weight || (item as ProductChildResourcePage).sideMenu?.weight;
10
+ }
11
+
13
12
  gatherChildrenOrdering(children: ProductChild[]): ProductChild[] {
14
13
  let minWeight = children.reduce((min, item) => {
15
- if (typeof item.weight !== 'number') return min;
14
+ const weight = this.weightFromProductChild(item);
15
+
16
+ if (typeof weight !== 'number') return min;
16
17
 
17
- return item.weight < min ? item.weight : min;
18
+ return weight < min ? weight : min;
18
19
  }, Infinity);
19
20
 
20
21
  if (minWeight === Infinity) {
@@ -27,18 +28,21 @@ class PluginProductsHelpers {
27
28
  children.forEach((child: ProductChild, index) => {
28
29
  const processedChild = { ...child };
29
30
 
30
- if (processedChild.weight === undefined || processedChild.weight === null) {
31
- processedChild.weight = minWeight - (index + 1);
31
+ if (processedChild.sideMenu?.weight === undefined || processedChild.sideMenu?.weight === null) {
32
+ if (!processedChild.sideMenu) {
33
+ processedChild.sideMenu = {};
34
+ }
35
+ processedChild.sideMenu.weight = minWeight - (index + 1);
32
36
  }
33
37
 
34
38
  if (isProductChildGroup(processedChild)) {
35
- processedChild.children = this.gatherChildrenOrdering(processedChild.children);
39
+ processedChild.sideMenu.children = this.gatherChildrenOrdering(processedChild.sideMenu.children);
36
40
  }
37
41
 
38
42
  processedChildren.push(processedChild);
39
43
  });
40
44
 
41
- return processedChildren.sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0));
45
+ return processedChildren.sort((a, b) => (b.sideMenu?.weight ?? 0) - (a.sideMenu?.weight ?? 0));
42
46
  }
43
47
 
44
48
  generateTopLevelExtensionSimpleBaseRoute(parentName: string, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
@@ -63,19 +67,19 @@ class PluginProductsHelpers {
63
67
  }
64
68
 
65
69
  // VIRTUAL TYPE ROUTES
66
- generateVirtualTypeRoute(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
70
+ generateVirtualTypeRoute(parentName: string, childName: string | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
67
71
  if (options.extendProduct) {
68
- return this.generateVirtualTypeRouteForExistingProduct(parentName, pageChild, options);
72
+ return this.generateVirtualTypeRouteForExistingProduct(parentName, childName, options);
69
73
  } else {
70
- return this.generateVirtualTypeRouteForNewProduct(parentName, pageChild, options);
74
+ return this.generateVirtualTypeRouteForNewProduct(parentName, childName, options);
71
75
  }
72
76
  }
73
77
 
74
78
  // VIRTUAL TYPE ROUTES - CLUSTER LEVEL EXTENSION
75
- private generateVirtualTypeRouteForExistingProduct(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
79
+ private generateVirtualTypeRouteForExistingProduct(parentName: string, childName: string | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
76
80
  const { component, omitPath } = options;
77
- const name = pageChild ? `c-cluster-${ parentName }-${ pageChild.name }` : `c-cluster-${ parentName }`;
78
- const path = pageChild ? `c/:cluster/${ parentName }/${ pageChild.name }` : `c/:cluster/${ parentName }`;
81
+ const name = childName ? `c-cluster-${ parentName }-${ childName }` : `c-cluster-${ parentName }`;
82
+ const path = childName ? `c/:cluster/${ parentName }/${ childName }` : `c/:cluster/${ parentName }`;
79
83
 
80
84
  const route: RouteRecordRawWithParams = {
81
85
  name,
@@ -96,10 +100,10 @@ class PluginProductsHelpers {
96
100
  }
97
101
 
98
102
  // VIRTUAL TYPE ROUTES - TOP LEVEL EXTENSION
99
- private generateVirtualTypeRouteForNewProduct(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
103
+ private generateVirtualTypeRouteForNewProduct(parentName: string, childName: string | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
100
104
  const { component, omitPath } = options;
101
- const name = pageChild ? `${ parentName }-c-cluster-${ pageChild.name }` : `${ parentName }-c-cluster`;
102
- const path = pageChild ? `${ parentName }/c/:cluster/${ pageChild.name }` : `${ parentName }/c/:cluster`;
105
+ const name = childName ? `${ parentName }-c-cluster-${ childName }` : `${ parentName }-c-cluster`;
106
+ const path = childName ? `${ parentName }/c/:cluster/${ childName }` : `${ parentName }/c/:cluster`;
103
107
 
104
108
  const route: RouteRecordRawWithParams = {
105
109
  name,
@@ -0,0 +1,207 @@
1
+ import {
2
+ ProductMetadata,
3
+ ProductChildResourcePage,
4
+ ProductChildCustomPage,
5
+ } from '@shell/core/plugin-products-external';
6
+ import { PaginationHeaderOptions, PluginRouteRecordRaw } from '@shell/core/types';
7
+
8
+ /**
9
+ * Product registration route generation options
10
+ */
11
+ export type ProductRegistrationRouteGenerationOptions = {
12
+ /**
13
+ * Whether to generate the route for an existing product - extending a product - or a new top level product
14
+ */
15
+ extendProduct? : boolean;
16
+ /**
17
+ * Component to be used for the route (used in virtualType and configureType routes)
18
+ */
19
+ component?: any;
20
+ /**
21
+ * Generated route should omit the path property
22
+ */
23
+ omitPath?: boolean;
24
+ /**
25
+ * @internal
26
+ * Whether the route should start with the product name or not (e.g. "my-product/c/:cluster/:resource" vs "c/:cluster/my-product/:resource")
27
+ * only to be used in very special usecases (internal use only - check FLEET product config for an example)
28
+ * */
29
+ startRouteWithProduct?: boolean;
30
+ }
31
+
32
+ // -------------- Page Related Resource --------------
33
+ // When updating external page types also update where they're converted to the internal page type
34
+ // i.e. `TypeMapVirtualType` or `TypeMapConfigureType` usage in shell/core/plugin-products-base.ts
35
+
36
+ /**
37
+ * Conceptually a `TypeMapVirtualType`
38
+ */
39
+ export type ProductChildCustomPageInternal = ProductChildCustomPage & {
40
+ navigation?: {
41
+ /** Whether this custom page is exact match */
42
+ exact?: boolean;
43
+ /** Whether this custom page has an exact path match */
44
+ 'exact-path'?: boolean;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Conceptually a `TypeMapConfigureType`
50
+ */
51
+ export type ProductChildResourcePageInternal = ProductChildResourcePage & {
52
+ /** Control how all lists that show this resource behave */
53
+ listConfig?: {
54
+ /** Table headers for this resource type (client-side pagination) */
55
+ localHeaders?: PaginationHeaderOptions[];
56
+
57
+ /**
58
+ * Whether this custom page has list groups (definition for grouping items in the list view)
59
+ */
60
+ listGroups?: {
61
+ /** Icon for the group (relates to icons in rancher-icons */
62
+ icon?: string;
63
+ /** Value for the group (used for grouping items in the list view) */
64
+ value?: string;
65
+ /** Field for the group (used for grouping items in the list view) */
66
+ field?: string;
67
+ /** Column to hide when this group is active */
68
+ hideColumn?: string;
69
+ /** Tooltip key for the group */
70
+ tooltipKey?: string;
71
+ }[];
72
+
73
+ /**
74
+ * 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
75
+ */
76
+ listGroupsWillOverride?: boolean;
77
+
78
+ /**
79
+ * 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")
80
+ */
81
+ subTypes?: string[];
82
+ }
83
+
84
+ /** Control how the child displays in the side menu */
85
+ sideMenu?: {
86
+ /** Whether to hide this resource from the side-menu entirely */
87
+ hideFromNav?: boolean;
88
+ }
89
+
90
+ /** Control how resources are handled in this product */
91
+ resource?: {
92
+ /**
93
+ * When requests are made to fetch this resource, outside of paginated requests, and they are paged.. iteratively fetch all pages
94
+ *
95
+ * We want to move away from this and use one of the new paginated requests to only fetch the data we need
96
+ */
97
+ depaginate?: boolean;
98
+ }
99
+ }
100
+
101
+ // -------------- Product Related Resource --------------
102
+
103
+ // When updating external product types also update where they're converted to the internal product type
104
+ // i.e. `TypeMapProduct` usage in shell/core/plugin-products-base.ts
105
+
106
+ // -----
107
+ export type ProductMetadataInternal = ProductMetadata & {
108
+ /**
109
+ * The category this product belongs under. i.e. 'config'
110
+ */
111
+ category?: string;
112
+
113
+ /** Do not use - internal use only */
114
+ version?: number;
115
+
116
+ /** Control what appears in the UI's main application header */
117
+ appHeader?: {
118
+ /**
119
+ * Hide the Namespace location
120
+ */
121
+ hideNamespaceLocation?: boolean;
122
+ /**
123
+ * controls whether a workspace switcher dropdown appears in the header (instead of the namespace filter) if set to true
124
+ */
125
+ showWorkspaceSwitcher?: boolean;
126
+ },
127
+
128
+ /**
129
+ * Control what appears in the vertical side bar on the left of the UI
130
+ */
131
+ sideBar? :{
132
+ /**
133
+ * Control page links
134
+ */
135
+ navigation?: {
136
+ /**
137
+ * The route that the product will lead to if click on in navigation.
138
+ */
139
+ to?: PluginRouteRecordRaw;
140
+ }
141
+ }
142
+
143
+ /** Control how the child displays in the side menu */
144
+ sideMenu?: {
145
+ /**
146
+ * @internal
147
+ * 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.
148
+ *
149
+ * 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.
150
+ *
151
+ */
152
+ renameGroups?: {
153
+ /** String or regex to match against group internal IDs */
154
+ groupSelector: RegExp | string;
155
+ /** Display name to use for matching groups */
156
+ newName: string;
157
+ }[];
158
+
159
+ /**
160
+ * @internal
161
+ *
162
+ * 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.
163
+ * 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.
164
+ *
165
+ * 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).
166
+ *
167
+ * The optional `weight` parameter controls precedence when multiple `moveToGroup` rules target the same page (default: `5`). Higher weight takes precedence.
168
+ */
169
+ moveToGroup?: {
170
+ /** Page identifier — the resource `type` string or the custom page `name` */
171
+ entryId: string;
172
+ /** Target group name as defined in your group config (`name` property) */
173
+ groupName: string;
174
+ /** Ordering weight for the mapping (default: 5). Higher weight takes precedence when multiple rules match */
175
+ weight?: number;
176
+ }[];
177
+
178
+ /**
179
+ * @internal
180
+ *
181
+ * maps to DSL ignoreGroup
182
+ *
183
+ * 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.
184
+ *
185
+ * 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).
186
+ *
187
+ */
188
+ ignoreGroups?: {
189
+ /** String or regex to match against group names */
190
+ groupSelector: string | RegExp;
191
+ /** Optional conditional function that accepts the root Dashboard Vuex store getters and returns true if the group should be ignored */
192
+ condition?: (getters: any) => boolean;
193
+ }[]
194
+ }
195
+
196
+ /**
197
+ * Whether the product can be removed by users (default: false — products are built-in/not removable unless explicitly set to true)
198
+ */
199
+ removable?: boolean;
200
+
201
+ /**
202
+ * Leaving these here for completeness but I don't think these should be advertised as useable to plugin creators.
203
+ */
204
+ // ifHaveVerb: string | RegExp;
205
+ // supportRoute: string;
206
+ // typeStoreMap: string;
207
+ };