@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.
- package/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/translations/en-us.yaml +10 -4
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/form/Security.vue +1 -1
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/config/labels-annotations.js +1 -0
- package/config/table-headers.js +5 -2
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +0 -2
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/pod.js +12 -10
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
- package/pages/c/_cluster/apps/charts/install.vue +29 -0
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/plugins/dashboard-store/resource-class.js +10 -3
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +1 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +58 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- 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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
14
|
+
const weight = this.weightFromProductChild(item);
|
|
15
|
+
|
|
16
|
+
if (typeof weight !== 'number') return min;
|
|
16
17
|
|
|
17
|
-
return
|
|
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.
|
|
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,
|
|
70
|
+
generateVirtualTypeRoute(parentName: string, childName: string | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
67
71
|
if (options.extendProduct) {
|
|
68
|
-
return this.generateVirtualTypeRouteForExistingProduct(parentName,
|
|
72
|
+
return this.generateVirtualTypeRouteForExistingProduct(parentName, childName, options);
|
|
69
73
|
} else {
|
|
70
|
-
return this.generateVirtualTypeRouteForNewProduct(parentName,
|
|
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,
|
|
79
|
+
private generateVirtualTypeRouteForExistingProduct(parentName: string, childName: string | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
76
80
|
const { component, omitPath } = options;
|
|
77
|
-
const name =
|
|
78
|
-
const path =
|
|
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,
|
|
103
|
+
private generateVirtualTypeRouteForNewProduct(parentName: string, childName: string | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
100
104
|
const { component, omitPath } = options;
|
|
101
|
-
const name =
|
|
102
|
-
const path =
|
|
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
|
+
};
|