@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,10 +1,4 @@
1
1
  import { IExtension } from '@shell/core/types';
2
- import {
3
- ProductChild, ProductMetadata,
4
- ResourcePageConfiguration, CustomPageConfiguration,
5
- ProductChildCustomPage, VueRouteComponent,
6
- OverviewPageRoutingMetadata
7
- } from '@shell/core/plugin-types';
8
2
  import EmptyProductPage from '@shell/components/EmptyProductPage.vue';
9
3
  import pluginProductsHelpers from '@shell/core/plugin-products-helpers';
10
4
  import {
@@ -13,7 +7,36 @@ import {
13
7
  isProductChildWithType,
14
8
  hasNameProperty,
15
9
  hasTypeProperty,
10
+ isProductConfigInternal,
16
11
  } from '@shell/core/plugin-products-type-guards';
12
+ import {
13
+ ProductChild, ProductChildCustomPage, ProductChildGroup, ProductChildResourcePage, ProductMetadata, ProductMetadataSinglePage
14
+ } from '@shell/core/plugin-products-external';
15
+ import { TypeMapConfigureType, TypeMapProduct, TypeMapVirtualType } from '@shell/types/store/type-map';
16
+ import { ProductChildCustomPageInternal, ProductChildResourcePageInternal, ProductMetadataInternal } from '@shell/core/plugin-products-internal';
17
+ import { RouteRecordRaw } from 'vue-router';
18
+ import { RouteRecordRawWithParams } from '@shell/core/plugin-types';
19
+
20
+ /**
21
+ * What's the point of this?
22
+ *
23
+ * Example
24
+ * `applyIfDefined(this.product?.label, () => typeMapProduct.label = this.product?.label); `
25
+ *
26
+ * If the property is defined we apply it to the object
27
+ * - this avoids verbose objects filled with `: undefined` (which fail unit tests)
28
+ * - { a: undefined, b: undefined, c: true} vs { c: true}
29
+ *
30
+ * Caller supplied direct object.property values
31
+ * - this ensure any further refactoring can happen automatically
32
+ * - type failures are very visible
33
+ * - both above would be lost if we had this as a cleaner but generic function(obj, targetPathString, targetValue)
34
+ */
35
+ const applyIfDefined = function(value: any, apply: () => void) {
36
+ if (value !== undefined) {
37
+ apply();
38
+ }
39
+ };
17
40
 
18
41
  /**
19
42
  * Base class for product registration in extensions
@@ -22,7 +45,7 @@ import {
22
45
  export abstract class BasePluginProduct {
23
46
  protected name!: string;
24
47
 
25
- protected product?: ProductMetadata;
48
+ protected product?: ProductMetadata | ProductMetadataSinglePage;
26
49
 
27
50
  protected addedResourceRoutes = false;
28
51
 
@@ -86,13 +109,6 @@ export abstract class BasePluginProduct {
86
109
  }
87
110
  }
88
111
 
89
- /**
90
- * Generates data for group overview page routing
91
- */
92
- protected generateMetadataForGroupOverviewPageRouting(name: string, component: VueRouteComponent): OverviewPageRoutingMetadata {
93
- return { name, component };
94
- }
95
-
96
112
  /**
97
113
  * This is where we register the product and its children via the DSL
98
114
  */
@@ -143,20 +159,18 @@ export abstract class BasePluginProduct {
143
159
  return;
144
160
  }
145
161
 
146
- const itemGroup = item;
162
+ const itemGroup = item as ProductChildGroup;
147
163
  const groupName = parentGroupName ? `${ productName }-${ parentGroupName }-${ itemGroup.name }` : `${ productName }-${ itemGroup.name }`;
148
164
 
149
165
  // Map the user's friendly group name to the resolved internal name
150
166
  // so that moveToGroup can translate friendly names automatically
151
167
  this.groupNameMap.set(itemGroup.name, groupName);
152
168
 
153
- if (!Array.isArray(itemGroup.children)) {
169
+ if (!Array.isArray(itemGroup.sideMenu.children)) {
154
170
  this.surfaceError('Children defined for group are not in an array format');
155
-
156
- return;
157
171
  }
158
172
 
159
- const navNames = this.getIDsForGroupsOrBasicTypes(groupName, itemGroup.children);
173
+ const navNames = this.getIDsForGroupsOrBasicTypes(groupName, itemGroup.sideMenu.children);
160
174
 
161
175
  // Build the full hierarchical path with :: separators for the store's _ensureGroup function
162
176
  // For example: "explorer-root::explorer-root-group1" tells the store to nest group1 inside root
@@ -175,7 +189,7 @@ export abstract class BasePluginProduct {
175
189
  basicType(navNames, hierarchicalPath);
176
190
 
177
191
  // register virtualTypes/configureTypes for each child item
178
- itemGroup.children.forEach((subItem: ProductChild) => {
192
+ itemGroup.sideMenu.children.forEach((subItem: ProductChild) => {
179
193
  const currentGroupName = parentGroupName ? `${ parentGroupName }-${ itemGroup.name }` : itemGroup.name;
180
194
 
181
195
  this.configurePageItem(productName, subItem, currentGroupName);
@@ -196,8 +210,8 @@ export abstract class BasePluginProduct {
196
210
  }
197
211
 
198
212
  // group weight
199
- if (itemGroup.weight) {
200
- weightGroup(groupName, itemGroup.weight, true);
213
+ if (itemGroup.sideMenu?.weight) {
214
+ weightGroup(groupName, itemGroup.sideMenu?.weight, true);
201
215
  }
202
216
 
203
217
  // group label
@@ -221,31 +235,50 @@ export abstract class BasePluginProduct {
221
235
  const firstConfig = this.config[0];
222
236
 
223
237
  if (isProductChildGroup(firstConfig)) {
238
+ const config = firstConfig as ProductChildGroup;
239
+
224
240
  // First config item is a group
225
- if (firstConfig.children.length > 0) {
226
- const entryChild = firstConfig.children[0];
241
+ if (config.sideMenu.children.length > 0) {
242
+ const entryChild = config.sideMenu.children[0];
227
243
 
228
- if (!firstConfig.component) {
244
+ if (!config.component) {
229
245
  // Group without component - route to first child
230
246
  if (isProductChildWithType(entryChild)) {
231
- defaultRoute = pluginProductsHelpers.generateConfigureTypeRoute(this.name, entryChild, { omitPath: true, extendProduct: !this.isNewProduct });
247
+ const entry = entryChild as ProductChildResourcePage;
248
+
249
+ defaultRoute = pluginProductsHelpers.generateConfigureTypeRoute(this.name, entry, { omitPath: true, extendProduct: !this.isNewProduct });
232
250
  } else if (isProductChildWithComponent(entryChild)) {
233
- defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, entryChild, { omitPath: true, extendProduct: !this.isNewProduct });
251
+ const entry = entryChild as ProductChildCustomPage;
252
+
253
+ defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, entry.name, {
254
+ omitPath: true, extendProduct: !this.isNewProduct, component: entry.component
255
+ });
234
256
  }
235
257
  } else {
258
+ // generateMetadataForGroupOverviewPageRouting
236
259
  // Group with component - route to the group overview page (which will render the group's component and side-menu)
237
- defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, this.generateMetadataForGroupOverviewPageRouting(firstConfig.name, firstConfig.component), { omitPath: true, extendProduct: !this.isNewProduct });
260
+ defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, firstConfig.name, {
261
+ omitPath: true, extendProduct: !this.isNewProduct, component: firstConfig.component
262
+ });
238
263
  }
239
264
  } else if (firstConfig.component) {
240
265
  // Group with component but no children - route to the group page itself
241
- defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, this.generateMetadataForGroupOverviewPageRouting(firstConfig.name, firstConfig.component), { omitPath: true, extendProduct: !this.isNewProduct });
266
+ defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, firstConfig.name, {
267
+ omitPath: true, extendProduct: !this.isNewProduct, component: firstConfig.component
268
+ });
242
269
  }
243
270
  } else if (isProductChildWithType(firstConfig)) {
244
271
  // Simple configureType page (resource page)
245
- defaultRoute = pluginProductsHelpers.generateConfigureTypeRoute(this.name, firstConfig, { omitPath: true, extendProduct: !this.isNewProduct });
272
+ const config = firstConfig as ProductChildResourcePage;
273
+
274
+ defaultRoute = pluginProductsHelpers.generateConfigureTypeRoute(this.name, config, { omitPath: true, extendProduct: !this.isNewProduct });
246
275
  } else if (isProductChildWithComponent(firstConfig)) {
247
276
  // Simple virtual type page (custom page)
248
- defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, firstConfig, { omitPath: true, extendProduct: !this.isNewProduct });
277
+ const config = firstConfig as ProductChildCustomPage;
278
+
279
+ defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, config.name, {
280
+ omitPath: true, extendProduct: !this.isNewProduct, component: config.component
281
+ });
249
282
  }
250
283
  } else if (this.isNewProduct) {
251
284
  // this is the "to" route for a simple page product (no config items)
@@ -253,18 +286,63 @@ export abstract class BasePluginProduct {
253
286
  basicType(names);
254
287
  }
255
288
 
256
- // register the product via DSL
257
- product({
289
+ const typeMapProduct: TypeMapProduct = {
258
290
  showClusterSwitcher: false,
259
291
  extendable: false,
260
- ...this.product,
292
+ // ...this.product,
261
293
  category: 'global',
262
294
  to: defaultRoute,
263
- icon: this.product?.icon || 'extension',
295
+ icon: 'extension',
264
296
  version: 2,
265
297
  inStore: 'management',
266
298
  name: this.name,
267
- });
299
+ };
300
+
301
+ if (this.product) {
302
+ // ProductMetadata
303
+
304
+ applyIfDefined(this.product?.label, () => typeMapProduct.label = this.product?.label); // eslint-disable-line no-return-assign
305
+ applyIfDefined(this.product?.labelKey, () => typeMapProduct.labelKey = this.product?.labelKey); // eslint-disable-line no-return-assign
306
+
307
+ applyIfDefined(this.product?.enable?.ifFeature, () => typeMapProduct.ifFeature = this.product?.enable?.ifFeature); // eslint-disable-line no-return-assign
308
+ applyIfDefined(this.product?.enable?.ifHave, () => typeMapProduct.ifHave = this.product?.enable?.ifHave); // eslint-disable-line no-return-assign
309
+ applyIfDefined(this.product?.enable?.ifHaveGroup, () => typeMapProduct.ifHaveGroup = this.product?.enable?.ifHaveGroup); // eslint-disable-line no-return-assign
310
+ applyIfDefined(this.product?.enable?.ifHaveType, () => typeMapProduct.ifHaveType = this.product?.enable?.ifHaveType); // eslint-disable-line no-return-assign
311
+ applyIfDefined(this.product?.enable?.ifNotHaveType, () => typeMapProduct.ifNotHaveType = this.product?.enable?.ifNotHaveType); // eslint-disable-line no-return-assign
312
+
313
+ applyIfDefined(this.product?.resources?.hideSystemResources, () => typeMapProduct.hideSystemResources = this.product?.resources?.hideSystemResources); // eslint-disable-line no-return-assign
314
+ typeMapProduct.inStore = this.product.resources?.store ?? 'management';
315
+
316
+ applyIfDefined(this.product?.extendable, () => typeMapProduct.extendable = this.product?.extendable); // eslint-disable-line no-return-assign
317
+
318
+ applyIfDefined(this.product?.appHeader?.hideCopyConfig, () => typeMapProduct.hideCopyConfig = this.product?.appHeader?.hideCopyConfig); // eslint-disable-line no-return-assign
319
+ applyIfDefined(this.product?.appHeader?.hideKubeConfig, () => typeMapProduct.hideKubeConfig = this.product?.appHeader?.hideKubeConfig); // eslint-disable-line no-return-assign
320
+ applyIfDefined(this.product?.appHeader?.hideKubeShell, () => typeMapProduct.hideKubeShell = this.product?.appHeader?.hideKubeShell); // eslint-disable-line no-return-assign
321
+ applyIfDefined(this.product?.appHeader?.showClusterInfo, () => typeMapProduct.showClusterSwitcher = this.product?.appHeader?.showClusterInfo); // eslint-disable-line no-return-assign
322
+ applyIfDefined(this.product?.appHeader?.showNamespaceFilter, () => typeMapProduct.showNamespaceFilter = this.product?.appHeader?.showNamespaceFilter); // eslint-disable-line no-return-assign
323
+ applyIfDefined(this.product?.appHeader?.icon, () => typeMapProduct.iconHeader = this.product?.appHeader?.icon); // eslint-disable-line no-return-assign
324
+
325
+ applyIfDefined(this.product?.sideBar?.weight, () => typeMapProduct.weight = this.product?.sideBar?.weight); // eslint-disable-line no-return-assign
326
+ typeMapProduct.icon = this.product?.sideBar?.icon?.name ?? 'extension';
327
+ applyIfDefined(this.product?.sideBar?.icon?.svg, () => typeMapProduct.svg = this.product?.sideBar?.icon?.svg); // eslint-disable-line no-return-assign
328
+
329
+ // ProductMetadataAddInternal
330
+ if (isProductConfigInternal(this.product)) {
331
+ const poI = this.product as ProductMetadataInternal;
332
+
333
+ applyIfDefined(poI.category, () => typeMapProduct.category = poI.category); // eslint-disable-line no-return-assign
334
+ applyIfDefined(poI?.appHeader?.hideNamespaceLocation, () => typeMapProduct.hideNamespaceLocation = poI?.appHeader?.hideNamespaceLocation); // eslint-disable-line no-return-assign
335
+ applyIfDefined(poI?.sideMenu?.renameGroups, () => typeMapProduct.renameGroups = poI?.sideMenu?.renameGroups); // eslint-disable-line no-return-assign
336
+ applyIfDefined(poI?.sideMenu?.ignoreGroups, () => typeMapProduct.ignoreGroups = poI?.sideMenu?.ignoreGroups); // eslint-disable-line no-return-assign
337
+ applyIfDefined(poI?.sideMenu?.moveToGroup, () => typeMapProduct.moveToGroup = poI?.sideMenu?.moveToGroup); // eslint-disable-line no-return-assign
338
+
339
+ applyIfDefined(poI?.appHeader?.showWorkspaceSwitcher, () => typeMapProduct.showWorkspaceSwitcher = poI?.appHeader?.showWorkspaceSwitcher); // eslint-disable-line no-return-assign
340
+ applyIfDefined(poI?.removable, () => typeMapProduct.removable = poI?.removable); // eslint-disable-line no-return-assign
341
+ }
342
+ }
343
+
344
+ // register the product via DSL
345
+ product(typeMapProduct);
268
346
  }
269
347
 
270
348
  /**
@@ -276,47 +354,51 @@ export abstract class BasePluginProduct {
276
354
  mapGroup, ignoreGroup, moveType, basicType
277
355
  } = this.DSLMethods;
278
356
 
279
- if (this.product?.renameGroups?.length) {
280
- this.product.renameGroups.forEach((mapping) => {
281
- mapGroup(mapping.groupSelector, mapping.newName);
282
- });
283
- }
357
+ if (this.product && isProductConfigInternal(this.product)) {
358
+ const productConfigInternal = this.product as ProductMetadataInternal;
284
359
 
285
- if (this.product?.ignoreGroups?.length) {
286
- this.product.ignoreGroups.forEach((ignore) => {
287
- if (ignore.condition) {
288
- ignoreGroup(ignore.groupSelector, ignore.condition);
289
- } else {
290
- ignoreGroup(ignore.groupSelector);
291
- }
292
- });
293
- }
360
+ if (productConfigInternal?.sideMenu?.renameGroups?.length) {
361
+ productConfigInternal?.sideMenu?.renameGroups.forEach((mapping) => {
362
+ mapGroup(mapping.groupSelector, mapping.newName);
363
+ });
364
+ }
294
365
 
295
- if (this.product?.moveToGroup?.length) {
296
- this.product.moveToGroup.forEach((move) => {
297
- const resolvedGroup = this.groupNameMap.get(move.groupName);
366
+ if (productConfigInternal?.sideMenu?.ignoreGroups?.length) {
367
+ productConfigInternal?.sideMenu?.ignoreGroups.forEach((ignore) => {
368
+ if (ignore.condition) {
369
+ ignoreGroup(ignore.groupSelector, ignore.condition);
370
+ } else {
371
+ ignoreGroup(ignore.groupSelector);
372
+ }
373
+ });
374
+ }
298
375
 
299
- if (!resolvedGroup) {
300
- this.surfaceError(`moveToGroup target group "${ move.groupName }" not found. Available groups: ${ Array.from(this.groupNameMap.keys()).join(', ') }`);
301
- }
376
+ if (productConfigInternal?.sideMenu?.moveToGroup?.length) {
377
+ productConfigInternal?.sideMenu?.moveToGroup.forEach((move) => {
378
+ const resolvedGroup = this.groupNameMap.get(move.groupName);
302
379
 
303
- const resolvedPageId = this.pageIdMap.get(move.entryId);
380
+ if (!resolvedGroup) {
381
+ this.surfaceError(`moveToGroup target group "${ move.groupName }" not found. Available groups: ${ Array.from(this.groupNameMap.keys()).join(', ') }`);
382
+ }
304
383
 
305
- if (!resolvedPageId) {
306
- this.surfaceError(`moveToGroup entryId "${ move.entryId }" not found. Available pages: ${ Array.from(this.pageIdMap.keys()).join(', ') }`);
307
- }
384
+ const resolvedPageId = this.pageIdMap.get(move.entryId);
308
385
 
309
- // Re-register via basicType to move the page in the nav tree (basic view mode)
310
- basicType([resolvedPageId], resolvedGroup);
386
+ if (!resolvedPageId) {
387
+ this.surfaceError(`moveToGroup entryId "${ move.entryId }" not found. Available pages: ${ Array.from(this.pageIdMap.keys()).join(', ') }`);
388
+ }
311
389
 
312
- // Also register via moveType for non-basic view modes (e.g. "in use" mode).
313
- // moveType uses regex matching against schema IDs, so it only works for resource types.
314
- const isResourceType = resolvedPageId === move.entryId;
390
+ // Re-register via basicType to move the page in the nav tree (basic view mode)
391
+ basicType([resolvedPageId], resolvedGroup);
315
392
 
316
- if (isResourceType) {
317
- moveType(move.entryId, resolvedGroup, move.weight);
318
- }
319
- });
393
+ // Also register via moveType for non-basic view modes (e.g. "in use" mode).
394
+ // moveType uses regex matching against schema IDs, so it only works for resource types.
395
+ const isResourceType = resolvedPageId === move.entryId;
396
+
397
+ if (isResourceType) {
398
+ moveType(move.entryId, resolvedGroup, move.weight);
399
+ }
400
+ });
401
+ }
320
402
  }
321
403
  }
322
404
 
@@ -343,12 +425,12 @@ export abstract class BasePluginProduct {
343
425
  this.registeredPageNames.add(finalName);
344
426
  this.pageIdMap.set(item.name, finalName);
345
427
 
346
- const virtualTypeConfig: CustomPageConfiguration = {
428
+ const virtualTypeConfig: TypeMapVirtualType = {
347
429
  label: item.label,
348
430
  labelKey: item.labelKey,
349
431
  namespaced: false,
350
432
  name: finalName,
351
- weight: item.weight, // ordering is done here and not via "weightType"
433
+ weight: item.sideMenu?.weight, // ordering is done here and not via "weightType"
352
434
  };
353
435
 
354
436
  // if the item with COMPONENT has children then it's a GROUP virtualType, so set "exact" and "overview" to "true"
@@ -357,12 +439,24 @@ export abstract class BasePluginProduct {
357
439
  virtualTypeConfig.exact = true;
358
440
  virtualTypeConfig.overview = true;
359
441
  // Pass group metadata as pageChild so the route gets a unique path segment (e.g. /product/c/:cluster/groupName)
360
- virtualTypeConfig.route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, this.generateMetadataForGroupOverviewPageRouting(item.name, item.component as ProductChildCustomPage['component']), { extendProduct: !this.isNewProduct });
442
+ virtualTypeConfig.route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, item.name, { extendProduct: !this.isNewProduct, component: item.component });
361
443
  } else {
362
- virtualTypeConfig.route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, item, { extendProduct: !this.isNewProduct });
444
+ virtualTypeConfig.route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, item.name, { extendProduct: !this.isNewProduct, component: item.component });
363
445
  }
364
446
 
365
- virtualType({ ...virtualTypeConfig, ...(isProductChildWithComponent(item) ? item.config || {} : {}) });
447
+ if (isProductChildWithComponent(item)) {
448
+ const itemCustomPage:ProductChildCustomPageInternal = item as ProductChildCustomPageInternal;
449
+
450
+ // virtualTypeConfig.component = itemCustomPage.component;
451
+ applyIfDefined(itemCustomPage.enable?.ifHave, () => virtualTypeConfig.ifHave = itemCustomPage.enable?.ifHave); // eslint-disable-line no-return-assign
452
+ applyIfDefined(itemCustomPage.enable?.ifFeature, () => virtualTypeConfig.ifFeature = itemCustomPage.enable?.ifFeature); // eslint-disable-line no-return-assign
453
+ applyIfDefined(itemCustomPage.enable?.ifHaveType, () => virtualTypeConfig.ifHaveType = itemCustomPage.enable?.ifHaveType); // eslint-disable-line no-return-assign
454
+ applyIfDefined(itemCustomPage.enable?.ifHaveVerb, () => virtualTypeConfig.ifHaveVerb = itemCustomPage.enable?.ifHaveVerb); // eslint-disable-line no-return-assign
455
+
456
+ applyIfDefined(itemCustomPage.resource?.namespaced, () => virtualTypeConfig.namespaced = itemCustomPage.resource?.namespaced); // eslint-disable-line no-return-assign
457
+ }
458
+
459
+ virtualType(virtualTypeConfig);
366
460
  } else if (isProductChildWithType(item)) {
367
461
  // Page with a "type" specified maps to a configureType
368
462
  const typeValue = item.type;
@@ -372,39 +466,53 @@ export abstract class BasePluginProduct {
372
466
  this.surfaceError(`Duplicate resource type "${ typeValue }" - each resource type must be unique within a product`);
373
467
  }
374
468
 
469
+ const itemRP: ProductChildResourcePageInternal = item as ProductChildResourcePageInternal;
470
+
375
471
  this.registeredPageNames.add(typeValue);
376
472
  this.pageIdMap.set(typeValue, typeValue);
377
473
 
378
474
  const route = pluginProductsHelpers.generateConfigureTypeRoute(parentName, item, { extendProduct: !this.isNewProduct });
379
475
 
380
- const configureTypeConfig: ResourcePageConfiguration = {
381
- isCreatable: true,
382
- isEditable: true,
383
- isRemovable: true,
384
- canYaml: true,
476
+ const configureTypeConfig: TypeMapConfigureType = {
477
+ isCreatable: itemRP.can?.create ?? true,
478
+ isEditable: itemRP.can?.edit ?? true,
479
+ isRemovable: itemRP.can?.create ?? true,
480
+ canYaml: itemRP.can?.yaml ?? true,
385
481
  customRoute: route
386
482
  };
387
483
 
388
- if (item.headers || item.sspHeaders) {
389
- headers(item.type, item.headers, item.sspHeaders);
484
+ applyIfDefined(itemRP.listConfig?.listGroups, () => configureTypeConfig.listGroups = itemRP.listConfig?.listGroups); // eslint-disable-line no-return-assign
485
+ applyIfDefined(itemRP.listConfig?.listGroupsWillOverride, () => configureTypeConfig.listGroupsWillOverride = itemRP.listConfig?.listGroupsWillOverride); // eslint-disable-line no-return-assign
486
+ applyIfDefined(itemRP.listConfig?.subTypes, () => configureTypeConfig.subTypes = itemRP.listConfig?.subTypes); // eslint-disable-line no-return-assign
487
+
488
+ if (itemRP.listConfig?.localHeaders || itemRP.listConfig?.headers) {
489
+ headers(item.type, itemRP.listConfig?.localHeaders, itemRP.listConfig?.headers);
390
490
  }
391
491
 
392
- if (item.overrideListResourceName) {
393
- mapType(item.type, item.overrideListResourceName);
492
+ if (itemRP.label) {
493
+ mapType(item.type, itemRP.label);
394
494
  }
395
495
 
396
- if (item.hideFromNav) {
496
+ if (itemRP.sideMenu?.hideFromNav) {
397
497
  ignoreType(item.type);
398
498
  }
399
499
 
400
- if (item.hideBulkActions) {
500
+ if (itemRP.listConfig?.hideBulkActions) {
401
501
  hideBulkActions(item.type, true);
402
502
  }
403
503
 
404
- configureType(typeValue, { ...configureTypeConfig, ...(item.config || {}) });
504
+ applyIfDefined(item.views?.list?.createLabelKey, () => configureTypeConfig.listCreateButtonLabelKey = item.views?.list?.createLabelKey); // eslint-disable-line no-return-assign
505
+ configureTypeConfig.showState = item.display?.showState ?? true;
506
+ configureTypeConfig.showAge = item.display?.showAge ?? true;
507
+ configureTypeConfig.showConfigView = item.views?.detail?.showConfigView ?? true;
508
+ configureTypeConfig.showListMasthead = item.views?.list?.showListMasthead ?? true;
509
+ configureTypeConfig.resourceEditMasthead = item.views?.createEdit?.showMasthead ?? true;
510
+ configureTypeConfig.localOnly = item.sideMenu?.localOnly ?? false;
511
+
512
+ configureType(typeValue, configureTypeConfig);
405
513
 
406
- if (item.weight) {
407
- weightType(typeValue, item.weight, true);
514
+ if (itemRP.sideMenu?.weight !== undefined) {
515
+ weightType(typeValue, itemRP.sideMenu?.weight, true);
408
516
  }
409
517
  }
410
518
  }
@@ -421,7 +529,7 @@ export abstract class BasePluginProduct {
421
529
  this.surfaceError('Group items cannot have a "type" property - only custom pages can have groups.');
422
530
  }
423
531
 
424
- let route;
532
+ let route: RouteRecordRaw | RouteRecordRawWithParams;
425
533
 
426
534
  if (!child.component) {
427
535
  // Create minimal page object for route generation
@@ -431,23 +539,23 @@ export abstract class BasePluginProduct {
431
539
  component: EmptyProductPage
432
540
  };
433
541
 
434
- route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, pageForRoute, { extendProduct: !this.isNewProduct });
542
+ route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, pageForRoute.name, { extendProduct: !this.isNewProduct });
435
543
  } else {
436
- route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, this.generateMetadataForGroupOverviewPageRouting(child.name, child.component), { component: child.component, extendProduct: !this.isNewProduct });
544
+ route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, child.name, { component: child.component, extendProduct: !this.isNewProduct });
437
545
  }
438
546
 
439
547
  // add the route for the group page/parent
440
548
  plugin.addRoute(route);
441
549
 
442
550
  // add children routes
443
- this.addRoutes(plugin, `${ parentName }`, child.children);
551
+ this.addRoutes(plugin, `${ parentName }`, child.sideMenu.children);
444
552
  } else if (isProductChildWithComponent(child)) {
445
553
  // virtualType page
446
554
  if (hasTypeProperty(child)) {
447
555
  this.surfaceError('Custom pages cannot have a "type" property - only resource pages can use "type".');
448
556
  }
449
557
 
450
- const route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, child, { component: child.component, extendProduct: !this.isNewProduct });
558
+ const route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, child.name, { component: child.component, extendProduct: !this.isNewProduct });
451
559
 
452
560
  plugin.addRoute(route);
453
561
  } else if (isProductChildWithType(child)) {
@@ -1,7 +1,7 @@
1
1
  import { IExtension } from '@shell/core/types';
2
- import { ProductChild, StandardProductName } from '@shell/core/plugin-types';
3
2
  import EmptyProductPage from '@shell/components/EmptyProductPage.vue';
4
3
  import { BasePluginProduct } from '@shell/core/plugin-products-base';
4
+ import { ProductChild, StandardProductName } from '@shell/core/plugin-products-external';
5
5
 
6
6
  /**
7
7
  * Represents extending an existing standard product