@rancher/shell 3.0.11 → 3.0.12-rc.2

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 (219) hide show
  1. package/assets/images/providers/entraid-black.svg +4 -0
  2. package/assets/images/providers/entraid.svg +9 -0
  3. package/assets/images/vendor/entraid.svg +9 -0
  4. package/assets/styles/app.scss +0 -1
  5. package/assets/styles/base/_mixins.scss +31 -0
  6. package/assets/styles/base/_variables.scss +2 -0
  7. package/assets/styles/themes/_modern.scss +6 -5
  8. package/assets/translations/en-us.yaml +24 -21
  9. package/assets/translations/zh-hans.yaml +4 -11
  10. package/chart/__tests__/S3.test.ts +10 -3
  11. package/components/CountBox.vue +20 -0
  12. package/components/CreateDriver.vue +0 -12
  13. package/components/DetailText.vue +12 -3
  14. package/components/EmptyProductPage.vue +76 -0
  15. package/components/Resource/Detail/CopyToClipboard.vue +1 -2
  16. package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
  17. package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
  18. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
  19. package/components/Resource/Detail/TitleBar/index.vue +1 -1
  20. package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
  21. package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
  22. package/components/Resource/Detail/ViewOptions/index.vue +2 -1
  23. package/components/ResourceList/Masthead.vue +25 -2
  24. package/components/SelectIconGrid.vue +5 -0
  25. package/components/SideNav.vue +13 -0
  26. package/components/__tests__/CountBox.test.ts +72 -0
  27. package/components/__tests__/DetailText.test.ts +113 -0
  28. package/components/__tests__/PromptModal.test.ts +2 -0
  29. package/components/fleet/FleetClusterTargets/index.vue +18 -1
  30. package/components/fleet/FleetClusters.vue +1 -0
  31. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  32. package/components/form/InputWithSelect.vue +18 -10
  33. package/components/form/KeyValue.vue +17 -1
  34. package/components/form/LabeledSelect.vue +82 -24
  35. package/components/form/NodeScheduling.vue +17 -3
  36. package/components/form/PrivateRegistry.vue +69 -0
  37. package/components/form/Select.vue +73 -56
  38. package/components/form/ServiceNameSelect.vue +13 -11
  39. package/components/form/__tests__/KeyValue.test.ts +66 -0
  40. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  41. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  42. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  43. package/components/formatter/WorkloadHealthScale.vue +3 -1
  44. package/components/nav/Group.vue +33 -9
  45. package/components/nav/Header.vue +56 -10
  46. package/components/nav/NotificationCenter/Notification.vue +4 -1
  47. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  48. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  49. package/components/nav/TopLevelMenu.vue +15 -1
  50. package/components/nav/Type.vue +8 -7
  51. package/components/nav/WindowManager/index.vue +2 -1
  52. package/components/nav/WorkspaceSwitcher.vue +13 -0
  53. package/components/nav/__tests__/Group.test.ts +67 -0
  54. package/components/nav/__tests__/Header.test.ts +235 -0
  55. package/components/nav/__tests__/Type.test.ts +20 -3
  56. package/components/templates/default.vue +34 -4
  57. package/components/templates/home.vue +12 -25
  58. package/components/templates/plain.vue +13 -26
  59. package/composables/useLabeledFormElement.ts +10 -2
  60. package/composables/useLabeledSelect.ts +60 -0
  61. package/composables/useUserRetentionValidation.ts +1 -49
  62. package/config/cookies.js +0 -1
  63. package/config/labels-annotations.js +1 -0
  64. package/config/pagination-table-headers.js +8 -1
  65. package/config/product/apps.js +2 -1
  66. package/config/product/auth.js +1 -0
  67. package/config/product/backup.js +1 -0
  68. package/config/product/compliance.js +1 -1
  69. package/config/product/explorer.js +25 -6
  70. package/config/product/fleet.js +1 -0
  71. package/config/product/gatekeeper.js +1 -0
  72. package/config/product/istio.js +1 -0
  73. package/config/product/logging.js +1 -0
  74. package/config/product/longhorn.js +2 -1
  75. package/config/product/manager.js +1 -0
  76. package/config/product/monitoring.js +1 -0
  77. package/config/product/navlinks.js +1 -0
  78. package/config/product/neuvector.js +2 -1
  79. package/config/product/settings.js +1 -0
  80. package/config/product/uiplugins.js +1 -0
  81. package/config/query-params.js +1 -0
  82. package/config/router/routes.js +0 -8
  83. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  84. package/core/__tests__/plugin-products.test.ts +3810 -0
  85. package/core/extension-manager-impl.js +30 -1
  86. package/core/plugin-products-base.ts +392 -0
  87. package/core/plugin-products-extending.ts +44 -0
  88. package/core/plugin-products-helpers.ts +263 -0
  89. package/core/plugin-products-top-level.ts +66 -0
  90. package/core/plugin-products-type-guards.ts +33 -0
  91. package/core/plugin-products.ts +50 -0
  92. package/core/plugin-types.ts +237 -0
  93. package/core/plugin.ts +45 -10
  94. package/core/productDebugger.js +48 -0
  95. package/core/types.ts +97 -11
  96. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  97. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  98. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  99. package/detail/fleet.cattle.io.bundle.vue +21 -34
  100. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  101. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  102. package/dialog/InstallExtensionDialog.vue +6 -27
  103. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  104. package/dialog/UninstallExtensionDialog.vue +4 -26
  105. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  106. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  107. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  108. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  109. package/edit/__tests__/nodeDriver.test.ts +5 -11
  110. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  111. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  112. package/edit/auth/__tests__/oidc.test.ts +54 -0
  113. package/edit/auth/azuread.vue +1 -1
  114. package/edit/auth/oidc.vue +8 -0
  115. package/edit/kontainerDriver.vue +1 -2
  116. package/edit/nodeDriver.vue +0 -2
  117. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  119. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  120. package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  122. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  124. package/initialize/App.vue +29 -2
  125. package/initialize/install-plugins.js +0 -2
  126. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  127. package/list/catalog.cattle.io.app.vue +25 -5
  128. package/list/management.cattle.io.feature.vue +1 -1
  129. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  130. package/list/provisioning.cattle.io.cluster.vue +0 -1
  131. package/list/workload.vue +11 -4
  132. package/machine-config/amazonec2.vue +1 -0
  133. package/mixins/chart.js +40 -9
  134. package/mixins/resource-fetch.js +12 -3
  135. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  136. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  137. package/models/__tests__/chart.test.ts +99 -6
  138. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  139. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  140. package/models/catalog.cattle.io.app.js +21 -17
  141. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  142. package/models/chart.js +33 -19
  143. package/models/fleet-application.js +1 -1
  144. package/models/fleet.cattle.io.bundle.js +1 -1
  145. package/models/kontainerdriver.js +11 -0
  146. package/models/management.cattle.io.authconfig.js +5 -1
  147. package/models/management.cattle.io.cluster.js +0 -53
  148. package/models/management.cattle.io.feature.js +3 -3
  149. package/models/management.cattle.io.kontainerdriver.js +1 -26
  150. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  151. package/models/nodedriver.js +7 -0
  152. package/models/pod.js +18 -0
  153. package/models/workload.js +20 -2
  154. package/package.json +13 -13
  155. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  156. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
  157. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  158. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  159. package/pages/c/_cluster/apps/charts/chart.vue +217 -33
  160. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  161. package/pages/c/_cluster/apps/charts/install.vue +8 -3
  162. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  163. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  164. package/pages/c/_cluster/settings/brand.vue +4 -4
  165. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
  166. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  167. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
  168. package/pages/c/_cluster/uiplugins/index.vue +166 -62
  169. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  170. package/plugins/dashboard-store/actions.js +3 -2
  171. package/plugins/dashboard-store/resource-class.js +62 -6
  172. package/plugins/plugin.js +16 -0
  173. package/plugins/steve/steve-pagination-utils.ts +7 -0
  174. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
  175. package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
  176. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  177. package/scripts/test-plugins-build.sh +5 -2
  178. package/scripts/typegen.sh +13 -1
  179. package/server/server-middleware.js +2 -2
  180. package/static/humans.txt +1 -0
  181. package/static/robots.txt +34 -0
  182. package/static/welcome-cow.svg +18 -0
  183. package/store/__tests__/catalog.test.ts +161 -11
  184. package/store/__tests__/type-map.test.ts +84 -24
  185. package/store/auth.js +0 -3
  186. package/store/catalog.js +60 -8
  187. package/store/type-map.js +42 -3
  188. package/tsconfig.paths.json +1 -0
  189. package/types/resources/pod.ts +18 -0
  190. package/types/shell/index.d.ts +8539 -2938
  191. package/types/store/dashboard-store.types.ts +5 -0
  192. package/types/store/pagination.types.ts +6 -0
  193. package/utils/__tests__/git.test.ts +270 -0
  194. package/utils/__tests__/inactivity.test.ts +316 -0
  195. package/utils/__tests__/object.test.ts +77 -0
  196. package/utils/__tests__/time.test.ts +14 -1
  197. package/utils/__tests__/url.test.ts +246 -0
  198. package/utils/axios.js +1 -4
  199. package/utils/dynamic-importer.js +3 -2
  200. package/utils/object.js +33 -2
  201. package/utils/pagination-utils.ts +1 -1
  202. package/utils/time.ts +5 -0
  203. package/utils/uiplugins.ts +12 -16
  204. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  205. package/utils/validators/private-registry.ts +28 -0
  206. package/vue.config.js +0 -9
  207. package/assets/images/providers/azuread-black.svg +0 -22
  208. package/assets/images/providers/azuread.svg +0 -25
  209. package/assets/images/vendor/azuread.svg +0 -18
  210. package/assets/styles/fonts/_dots.scss +0 -18
  211. package/components/EmberPage.vue +0 -622
  212. package/components/EmberPageView.vue +0 -39
  213. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  214. package/mixins/labeled-form-element.ts +0 -225
  215. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  216. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  217. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  218. package/plugins/ember-cookie.js +0 -17
  219. package/utils/ember-page.js +0 -30
@@ -0,0 +1,263 @@
1
+ import {
2
+ RouteRecordRawWithParams, ProductChildGroup, ProductChild,
3
+ ProductChildCustomPage, ProductChildResourcePage, ProductRegistrationRouteGenerationOptions,
4
+ OverviewPageRoutingMetadata
5
+ } from '@shell/core/plugin-types';
6
+ import { BLANK_CLUSTER } from '@shell/store/store-types';
7
+
8
+ function isProductChildGroup(child: ProductChild): child is ProductChildGroup {
9
+ return 'children' in child && Array.isArray(child.children);
10
+ }
11
+
12
+ class PluginProductsHelpers {
13
+ gatherChildrenOrdering(children: ProductChild[]): ProductChild[] {
14
+ let minWeight = children.reduce((min, item) => {
15
+ if (typeof item.weight !== 'number') return min;
16
+
17
+ return item.weight < min ? item.weight : min;
18
+ }, Infinity);
19
+
20
+ if (minWeight === Infinity) {
21
+ // 1000 is Root level default weight minus some space
22
+ minWeight = 999;
23
+ }
24
+
25
+ const processedChildren: ProductChild[] = [];
26
+
27
+ children.forEach((child: ProductChild, index) => {
28
+ const processedChild = { ...child };
29
+
30
+ if (processedChild.weight === undefined || processedChild.weight === null) {
31
+ processedChild.weight = minWeight - (index + 1);
32
+ }
33
+
34
+ if (isProductChildGroup(processedChild)) {
35
+ processedChild.children = this.gatherChildrenOrdering(processedChild.children);
36
+ }
37
+
38
+ processedChildren.push(processedChild);
39
+ });
40
+
41
+ return processedChildren.sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0));
42
+ }
43
+
44
+ generateTopLevelExtensionSimpleBaseRoute(parentName: string, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
45
+ const { component, omitPath } = options;
46
+
47
+ const route: RouteRecordRawWithParams = {
48
+ name: `${ parentName }`,
49
+ path: `${ parentName }`,
50
+ params: { product: parentName },
51
+ meta: { product: parentName },
52
+ };
53
+
54
+ if (component) {
55
+ route.component = component;
56
+ }
57
+
58
+ if (omitPath) {
59
+ delete route.path;
60
+ }
61
+
62
+ return route;
63
+ }
64
+
65
+ // VIRTUAL TYPE ROUTES
66
+ generateVirtualTypeRoute(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
67
+ if (options.extendProduct) {
68
+ return this.generateVirtualTypeRouteForExistingProduct(parentName, pageChild, options);
69
+ } else {
70
+ return this.generateVirtualTypeRouteForNewProduct(parentName, pageChild, options);
71
+ }
72
+ }
73
+
74
+ // VIRTUAL TYPE ROUTES - CLUSTER LEVEL EXTENSION
75
+ private generateVirtualTypeRouteForExistingProduct(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
76
+ 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 }`;
79
+
80
+ const route: RouteRecordRawWithParams = {
81
+ name,
82
+ path,
83
+ params: { product: parentName },
84
+ meta: { product: parentName },
85
+ };
86
+
87
+ if (component) {
88
+ route.component = component;
89
+ }
90
+
91
+ if (omitPath) {
92
+ delete route.path;
93
+ }
94
+
95
+ return route;
96
+ }
97
+
98
+ // VIRTUAL TYPE ROUTES - TOP LEVEL EXTENSION
99
+ private generateVirtualTypeRouteForNewProduct(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
100
+ 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`;
103
+
104
+ const route: RouteRecordRawWithParams = {
105
+ name,
106
+ path,
107
+ params: { product: parentName, cluster: BLANK_CLUSTER },
108
+ meta: { product: parentName, cluster: BLANK_CLUSTER },
109
+ };
110
+
111
+ if (component) {
112
+ route.component = component;
113
+ }
114
+
115
+ if (omitPath) {
116
+ delete route.path;
117
+ }
118
+
119
+ return route;
120
+ }
121
+
122
+ // CONFIGURE TYPE ROUTES
123
+ generateConfigureTypeRoute(parentName: string, pageChild: ProductChildResourcePage | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
124
+ if (options.extendProduct) {
125
+ return this.generateConfigureTypeRouteForExistingProduct(parentName, pageChild, options);
126
+ } else {
127
+ return this.generateConfigureTypeRouteForNewProduct(parentName, pageChild, options);
128
+ }
129
+ }
130
+
131
+ // CONFIGURE TYPE ROUTES - CLUSTER LEVEL EXTENSION
132
+ private generateConfigureTypeRouteForExistingProduct(parentName: string, pageChild: ProductChildResourcePage | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
133
+ const { component, omitPath } = options;
134
+
135
+ const route: RouteRecordRawWithParams = {
136
+ name: `c-cluster-${ parentName }-resource`,
137
+ path: `c/:cluster/${ parentName }/:resource`,
138
+ params: { product: parentName, resource: pageChild?.type },
139
+ meta: { product: parentName, resource: pageChild?.type },
140
+ };
141
+
142
+ if (component) {
143
+ route.component = component;
144
+ }
145
+
146
+ if (omitPath) {
147
+ delete route.path;
148
+ }
149
+
150
+ return route;
151
+ }
152
+
153
+ // CONFIGURE TYPE ROUTES - TOP LEVEL EXTENSION
154
+ private generateConfigureTypeRouteForNewProduct(parentName: string, pageChild: ProductChildResourcePage | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
155
+ const { component, omitPath } = options;
156
+
157
+ const route: RouteRecordRawWithParams = {
158
+ name: `${ parentName }-c-cluster-resource`,
159
+ path: `${ parentName }/c/:cluster/:resource`,
160
+ params: {
161
+ product: parentName, cluster: BLANK_CLUSTER, resource: pageChild?.type
162
+ },
163
+ meta: {
164
+ product: parentName, cluster: BLANK_CLUSTER, resource: pageChild?.type
165
+ },
166
+ };
167
+
168
+ if (component) {
169
+ route.component = component;
170
+ }
171
+
172
+ if (omitPath) {
173
+ delete route.path;
174
+ }
175
+
176
+ return route;
177
+ }
178
+
179
+ // RESOURCE ROUTES
180
+ generateResourceRoutes(parentName: string, pageChild: ProductChildResourcePage, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams[] {
181
+ if (options.extendProduct) {
182
+ return this.generateResourceRoutesForExistingProduct(parentName, pageChild);
183
+ } else {
184
+ return this.generateResourceRoutesForNewProduct(parentName);
185
+ }
186
+ }
187
+
188
+ // RESOURCE ROUTES - CLUSTER LEVEL EXTENSION
189
+ private generateResourceRoutesForExistingProduct(parentName: string, pageChild: ProductChildResourcePage): RouteRecordRawWithParams[] {
190
+ const interopDefault = (promise: Promise<any>) => promise.then((page) => page.default || page);
191
+
192
+ return [
193
+ {
194
+ name: `c-cluster-${ parentName }-resource`,
195
+ path: `c/:cluster/${ parentName }/:resource`,
196
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/index.vue')),
197
+ meta: { product: parentName, resource: pageChild.type }
198
+ },
199
+ {
200
+ name: `c-cluster-${ parentName }-resource-create`,
201
+ path: `c/:cluster/${ parentName }/:resource/create`,
202
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/create.vue')),
203
+ meta: { product: parentName, resource: pageChild.type }
204
+ },
205
+ {
206
+ name: `c-cluster-${ parentName }-resource-id`,
207
+ path: `c/:cluster/${ parentName }/:resource/:id`,
208
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/_id.vue')),
209
+ meta: {
210
+ product: parentName, resource: pageChild.type, asyncSetup: true
211
+ }
212
+ },
213
+ {
214
+ name: `c-cluster-${ parentName }-resource-namespace-id`,
215
+ path: `c/:cluster/${ parentName }/:resource/:namespace/:id`,
216
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/_namespace/_id.vue')),
217
+ meta: {
218
+ product: parentName, resource: pageChild.type, asyncSetup: true
219
+ }
220
+ }
221
+ ];
222
+ }
223
+
224
+ // RESOURCE ROUTES - TOP LEVEL EXTENSION
225
+ private generateResourceRoutesForNewProduct(parentName: string) {
226
+ const interopDefault = (promise: Promise<any>) => promise.then((page) => page.default || page);
227
+
228
+ return [
229
+ {
230
+ name: `${ parentName }-c-cluster-resource`,
231
+ path: `${ parentName }/c/:cluster/:resource`,
232
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/index.vue')),
233
+ meta: { product: parentName, cluster: BLANK_CLUSTER }
234
+ },
235
+ {
236
+ name: `${ parentName }-c-cluster-resource-create`,
237
+ path: `${ parentName }/c/:cluster/:resource/create`,
238
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/create.vue')),
239
+ meta: { product: parentName, cluster: BLANK_CLUSTER }
240
+ },
241
+ {
242
+ name: `${ parentName }-c-cluster-resource-id`,
243
+ path: `${ parentName }/c/:cluster/:resource/:id`,
244
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/_id.vue')),
245
+ meta: {
246
+ product: parentName, cluster: BLANK_CLUSTER, asyncSetup: true
247
+ }
248
+ },
249
+ {
250
+ name: `${ parentName }-c-cluster-resource-namespace-id`,
251
+ path: `${ parentName }/c/:cluster/:resource/:namespace/:id`,
252
+ component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/_namespace/_id.vue')),
253
+ meta: {
254
+ product: parentName, cluster: BLANK_CLUSTER, asyncSetup: true
255
+ }
256
+ }
257
+ ];
258
+ }
259
+ }
260
+
261
+ const pluginProductsHelpers = new PluginProductsHelpers();
262
+
263
+ export default pluginProductsHelpers;
@@ -0,0 +1,66 @@
1
+ import { IExtension } from '@shell/core/types';
2
+ import { ProductChild, ProductMetadata, ProductSinglePage } from '@shell/core/plugin-types';
3
+ import EmptyProductPage from '@shell/components/EmptyProductPage.vue';
4
+ import pluginProductsHelpers from '@shell/core/plugin-products-helpers';
5
+ import { BasePluginProduct } from '@shell/core/plugin-products-base';
6
+ import { isProductSinglePage } from '@shell/core/plugin-products-type-guards';
7
+
8
+ /**
9
+ * Represents a new top-level product being added by an extension
10
+ * @internal
11
+ */
12
+ export class TopLevelPluginProduct extends BasePluginProduct {
13
+ get isNewProduct(): boolean {
14
+ return true;
15
+ }
16
+
17
+ constructor(plugin: IExtension, product: ProductMetadata | ProductSinglePage | string, config: ProductChild[]) {
18
+ super(config);
19
+
20
+ // Convenience/bridge method: create a basic product from just a name string
21
+ if (typeof product === 'string') {
22
+ product = {
23
+ name: product,
24
+ label: product,
25
+ };
26
+ }
27
+
28
+ let prodName = product.name;
29
+
30
+ // the goal here is not to interfere with vue-router route names, which use dashes
31
+ if (prodName.includes('-')) {
32
+ prodName = prodName.replaceAll('-', '');
33
+ }
34
+
35
+ // convert this to "string" to match all types moving forward
36
+ // doesn't impact anything, fixes build problems of extensions
37
+ // and allows extensions to use either string literal or enum value for product name
38
+ this.name = prodName;
39
+ this.product = product;
40
+
41
+ // register the product as a top-level product in the plugin object (will be needed for routes correction when on list views for top-level products)
42
+ plugin._registerTopLevelProduct();
43
+
44
+ this.processConfigChildren();
45
+
46
+ // If the product has a `component` field, then this is a single page product
47
+ if (isProductSinglePage(product)) {
48
+ // 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 });
50
+
51
+ plugin.addRoute('plain', route);
52
+ } else if (this.config.length === 0) {
53
+ // If no config is provided, add a default empty page
54
+ this.config = [{
55
+ name: 'main',
56
+ label: 'Main',
57
+ component: EmptyProductPage,
58
+ }];
59
+
60
+ this.addRoutes(plugin, this.name, this.config);
61
+ } else {
62
+ // This is the general case - product with config items and no single page component
63
+ this.addRoutes(plugin, this.name, this.config);
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ ProductChild, ProductChildGroup, ProductMetadata,
3
+ ProductSinglePage, ProductChildCustomPage, ProductChildResourcePage
4
+ } from '@shell/core/plugin-types';
5
+
6
+ /**
7
+ * Type guard functions for discriminating union types
8
+ * @internal
9
+ */
10
+
11
+ export function isProductSinglePage(product: ProductMetadata | ProductSinglePage): product is ProductSinglePage {
12
+ return 'component' in product && product.component !== undefined;
13
+ }
14
+
15
+ export function isProductChildGroup(child: ProductChild): child is ProductChildGroup {
16
+ return 'children' in child;
17
+ }
18
+
19
+ export function isProductChildWithComponent(child: ProductChild): child is ProductChildCustomPage {
20
+ return 'component' in child && child.component !== undefined && !isProductChildGroup(child);
21
+ }
22
+
23
+ export function isProductChildWithType(child: ProductChild): child is ProductChildResourcePage {
24
+ return 'type' in child && typeof child.type === 'string' && !isProductChildGroup(child);
25
+ }
26
+
27
+ export function hasNameProperty(child: ProductChild): child is ProductChild & { name: string } {
28
+ return 'name' in child && typeof child.name === 'string';
29
+ }
30
+
31
+ export function hasTypeProperty(child: ProductChild): child is ProductChild & { type: string } {
32
+ return 'type' in child && typeof child.type === 'string';
33
+ }
@@ -0,0 +1,50 @@
1
+ import { IExtension } from '@shell/core/types';
2
+ import {
3
+ StandardProductName, ProductChild,
4
+ ProductMetadata, ProductSinglePage,
5
+ } from '@shell/core/plugin-types';
6
+ import { BasePluginProduct } from '@shell/core/plugin-products-base';
7
+ import { TopLevelPluginProduct } from '@shell/core/plugin-products-top-level';
8
+ import { ExtendingPluginProduct } from '@shell/core/plugin-products-extending';
9
+
10
+ /**
11
+ * Factory class for creating plugin products
12
+ * Maintains backward compatibility with the original PluginProduct class
13
+ * @internal
14
+ */
15
+ export class PluginProduct {
16
+ private instance: BasePluginProduct;
17
+
18
+ constructor(plugin: IExtension, product: StandardProductName | string | ProductMetadata | ProductSinglePage, config: ProductChild[]) {
19
+ if (typeof product === 'object' && product.name) {
20
+ // This is a new product being added
21
+ this.instance = new TopLevelPluginProduct(plugin, product, config);
22
+ } else if (typeof product === 'string') {
23
+ // This is extending an existing standard product
24
+ this.instance = new ExtendingPluginProduct(plugin, product, config);
25
+ } else {
26
+ // at this point we may not know the product name
27
+ throw new Error('Extensions product registration error ::: Invalid product');
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Convenience/bridge method: create a new top-level product from just a name string.
33
+ * The product will use EmptyProductPage as its default page.
34
+ */
35
+ static fromName(plugin: IExtension, productName: string): PluginProduct {
36
+ const instance = Object.create(PluginProduct.prototype);
37
+
38
+ instance.instance = new TopLevelPluginProduct(plugin, productName, []);
39
+
40
+ return instance;
41
+ }
42
+
43
+ apply(plugin: IExtension, store: any): void {
44
+ this.instance.apply(plugin, store);
45
+ }
46
+
47
+ get newProduct(): boolean {
48
+ return this.instance.isNewProduct;
49
+ }
50
+ }
@@ -0,0 +1,237 @@
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 } from '@shell/core/types';
8
+
9
+ type Async<T> = () => Promise<T>;
10
+
11
+ export type RouteRecordRawWithParams = Omit<RouteRecordRaw, 'redirect' | 'children' | 'path'> & {
12
+ /** Path for route - can include dynamic segments like ':id'. Based on vue-router routes */
13
+ path?: string;
14
+ /** Params for route - key-value pairs representing route parameters */
15
+ params?: Record<string, any>;
16
+ /** Child routes */
17
+ children?: RouteRecordRawWithParams[];
18
+ /** Optional redirect */
19
+ redirect?: RouteRecordRaw['redirect'];
20
+ };
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
+
40
+ /**
41
+ * Represents the allowed extensible products in Rancher Dashboard
42
+ */
43
+ export const StandardProductNames = {
44
+ EXPLORER: EXPLORER_PROD_NAME,
45
+ MANAGER: CLUSTER_MAN_PROD_NAME,
46
+ SETTINGS: SETTINGS_PROD_NAME,
47
+ AUTH: AUTH_PROD_NAME,
48
+ } as const;
49
+
50
+ /**
51
+ * Type representing a standard product name value
52
+ */
53
+ export type StandardProductName = (typeof StandardProductNames)[keyof typeof StandardProductNames];
54
+
55
+ /**
56
+ * Represents allowed configuration for a child page
57
+ */
58
+ export type ProductChildMetadata = {
59
+ /** Product name/unique identifier for the product */
60
+ name: string;
61
+ /** Ordering weight for the among its siblings, if applicable */
62
+ weight?: number;
63
+ } & (
64
+ /** Human-readable label for the child page
65
+ * Either label or labelKey are required */
66
+ { label: string; labelKey?: string }
67
+ /** Translation key for the label of the child page
68
+ * Either label or labelKey are required */
69
+ | { labelKey: string; label?: string }
70
+ );
71
+
72
+ /**
73
+ * Represents the allowed configuration for a custom page (virtualType)
74
+ */
75
+ export type VirtualTypeConfiguration = {
76
+ /** Display only if condition is met (relates to IF_HAVE in shell/store/type-map) */
77
+ ifHave?: boolean;
78
+ /** Display only if feature is present (relates to shell/store/features) */
79
+ ifFeature?: string;
80
+ /** Display only if resource type exists */
81
+ ifHaveType?: string;
82
+ /** Used in conjunction with "ifHaveType", display only if resource type allows this verb (GET, POST, PUT, DELETE) */
83
+ ifHaveVerb?: string;
84
+ /** Display label for the custom page */
85
+ label?: string;
86
+ /** Translation key for the label */
87
+ labelKey?: string;
88
+ /** Name of the page (unique identifier) */
89
+ name?: string;
90
+ /** Entry route definition for this custom page */
91
+ route?: RouteRecordRawWithParams | PluginRouteRecordRaw | Object;
92
+ /** Icon for the custom page (relates to icons in https://github.com/rancher/icons) */
93
+ icon?: 'compass';
94
+ /** Whether this custom page is namespaced or not */
95
+ namespaced?: boolean;
96
+ /** Ordering weight for the custom page */
97
+ weight?: number;
98
+ /** Whether this custom page is exact match */
99
+ exact?: boolean;
100
+ /** Whether this custom page will act as an overview page */
101
+ overview?: boolean;
102
+ /** Whether this custom page has an exact path match */
103
+ 'exact-path'?: boolean;
104
+ }
105
+
106
+ /**
107
+ * Represents the allowed configuration for a resource page (configureType)
108
+ */
109
+ export type ConfigureTypeConfiguration = {
110
+ /** Override for the name displayed */
111
+ displayName?: string;
112
+ /** Override for the create button string on a list view */
113
+ listCreateButtonLabelKey?: string;
114
+ /** If false, disable create even if schema says it's allowed */
115
+ isCreatable?: boolean;
116
+ /** If false, disable for edit */
117
+ isEditable?: boolean;
118
+ /** If false, disable for remove/delete */
119
+ isRemovable?: boolean;
120
+ /** If false, hide state in columns and masthead */
121
+ showState?: boolean;
122
+ /** If false, hide age in columns and masthead */
123
+ showAge?: boolean;
124
+ /** If false, hide masthead config button in view mode */
125
+ showConfigView?: boolean;
126
+ /** If false, hide masthead in list view */
127
+ showListMasthead?: boolean;
128
+ /** If false, cannot edit or show yaml */
129
+ canYaml?: boolean;
130
+ /** Show the Masthead in the edit resource component */
131
+ resourceEditMasthead?: boolean;
132
+ /** Entry route definition for this resource page */
133
+ customRoute?: RouteRecordRawWithParams;
134
+ /** Hide this type from the nav/search bar on downstream clusters (will only show in "local" cluster) */
135
+ localOnly?: boolean;
136
+ // resource: undefined; // Use this resource in ResourceDetails instead
137
+ // resourceDetail: undefined; // Use this resource specifically for ResourceDetail's detail component
138
+ // resourceEdit: undefined; // Use this resource specifically for ResourceDetail's edit component
139
+ // depaginate: undefined; // Use this to depaginate requests for this type
140
+ // notFilterNamespace: undefined; // Define namespaces that do not need to be filtered
141
+ // used in configureType options, to be typed later if needed
142
+ // listGroups: [
143
+ // {
144
+ // icon: 'icon-role-binding',
145
+ // value: 'node',
146
+ // field: 'roleDisplay',
147
+ // hideColumn: ROLE.name,
148
+ // tooltipKey: 'resourceTable.groupBy.role'
149
+ // }
150
+ // ]
151
+ }
152
+
153
+ /**
154
+ * Represents a Vue component or an async function that resolves to a Vue component, used for route components in product configuration
155
+ */
156
+ export type VueRouteComponent = RouteComponent | Async<RouteComponent>;
157
+
158
+ /**
159
+ * Metadata for route generation to a product overview page
160
+ */
161
+ export type OverviewPageRoutingMetadata = {
162
+ /** Name of the overview page */
163
+ name: string;
164
+ /** Component to render for the overview page */
165
+ component: VueRouteComponent;
166
+ }
167
+
168
+ /**
169
+ * Represents a custom page with a component
170
+ */
171
+ export type ProductChildCustomPage = ProductChildMetadata & {
172
+ /** Component to render for this custom page */
173
+ component: VueRouteComponent;
174
+ /** Optional configuration for the page */
175
+ config?: VirtualTypeConfiguration;
176
+ };
177
+
178
+ /**
179
+ * Represents a resource page with a type (K8s resource)
180
+ */
181
+ export type ProductChildResourcePage = {
182
+ /** K8s resource type name for a resource page */
183
+ type: string;
184
+ /** Optional configuration for the resource page */
185
+ config?: ConfigureTypeConfiguration;
186
+ /** Ordering weight for this page among its siblings */
187
+ weight?: number;
188
+ };
189
+
190
+ /**
191
+ * Represents a page item (custom page or resource page) in a product's config
192
+ * - For custom pages: use `component` with `name` and `label`/`labelKey`
193
+ * - For resource pages: use `type` with optional `config` and `headers`
194
+ */
195
+ export type ProductChildPage = ProductChildCustomPage | ProductChildResourcePage;
196
+
197
+ /**
198
+ * Represents a product child in the navigation
199
+ * This can be a type, a group, or a route.
200
+ */
201
+ export type ProductChild = ProductChildGroup | ProductChildPage; // eslint-disable-line no-use-before-define
202
+
203
+ /**
204
+ * Represents a group of child pages in a product configuration
205
+ */
206
+ export type ProductChildGroup = ProductChildMetadata & {
207
+ component?: VueRouteComponent;
208
+ children: ProductChild[];
209
+ /** Default child to navigate to */
210
+ default?: string;
211
+ };
212
+
213
+ /**
214
+ * Represents the allowed configuration for a product
215
+ */
216
+ export type ProductMetadata = Omit<ProductOptions, 'name' | 'label' | 'labelKey' | 'category' | 'to' | 'version' | 'inStore'> & {
217
+ /**
218
+ * Product name (unique identifier)
219
+ */
220
+ name: string;
221
+ } & (
222
+ /** Human-readable label for the product
223
+ * Either label or labelKey are required */
224
+ | { label: string; labelKey?: string }
225
+ /** Translation key for the label of the product
226
+ * Either label or labelKey are required */
227
+ | { labelKey: string; label?: string }
228
+ );
229
+
230
+ /**
231
+ * Represents a single page product, which is a product that only has one page and
232
+ * therefore does not need to be represented in the navigation as a group with children.
233
+ */
234
+ export type ProductSinglePage = ProductMetadata & {
235
+ /** Component to render for this product (single page product) */
236
+ component: VueRouteComponent;
237
+ };