@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.
- package/assets/images/providers/entraid-black.svg +4 -0
- package/assets/images/providers/entraid.svg +9 -0
- package/assets/images/vendor/entraid.svg +9 -0
- package/assets/styles/app.scss +0 -1
- package/assets/styles/base/_mixins.scss +31 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -5
- package/assets/translations/en-us.yaml +24 -21
- package/assets/translations/zh-hans.yaml +4 -11
- package/chart/__tests__/S3.test.ts +10 -3
- package/components/CountBox.vue +20 -0
- package/components/CreateDriver.vue +0 -12
- package/components/DetailText.vue +12 -3
- package/components/EmptyProductPage.vue +76 -0
- package/components/Resource/Detail/CopyToClipboard.vue +1 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
- package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
- package/components/Resource/Detail/TitleBar/index.vue +1 -1
- package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
- package/components/Resource/Detail/ViewOptions/index.vue +2 -1
- package/components/ResourceList/Masthead.vue +25 -2
- package/components/SelectIconGrid.vue +5 -0
- package/components/SideNav.vue +13 -0
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/__tests__/PromptModal.test.ts +2 -0
- package/components/fleet/FleetClusterTargets/index.vue +18 -1
- package/components/fleet/FleetClusters.vue +1 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +82 -24
- package/components/form/NodeScheduling.vue +17 -3
- package/components/form/PrivateRegistry.vue +69 -0
- package/components/form/Select.vue +73 -56
- package/components/form/ServiceNameSelect.vue +13 -11
- package/components/form/__tests__/KeyValue.test.ts +66 -0
- package/components/form/__tests__/NodeScheduling.test.ts +9 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
- package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/formatter/WorkloadHealthScale.vue +3 -1
- package/components/nav/Group.vue +33 -9
- package/components/nav/Header.vue +56 -10
- package/components/nav/NotificationCenter/Notification.vue +4 -1
- package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
- package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
- package/components/nav/TopLevelMenu.vue +15 -1
- package/components/nav/Type.vue +8 -7
- package/components/nav/WindowManager/index.vue +2 -1
- package/components/nav/WorkspaceSwitcher.vue +13 -0
- package/components/nav/__tests__/Group.test.ts +67 -0
- package/components/nav/__tests__/Header.test.ts +235 -0
- package/components/nav/__tests__/Type.test.ts +20 -3
- package/components/templates/default.vue +34 -4
- package/components/templates/home.vue +12 -25
- package/components/templates/plain.vue +13 -26
- package/composables/useLabeledFormElement.ts +10 -2
- package/composables/useLabeledSelect.ts +60 -0
- package/composables/useUserRetentionValidation.ts +1 -49
- package/config/cookies.js +0 -1
- package/config/labels-annotations.js +1 -0
- package/config/pagination-table-headers.js +8 -1
- package/config/product/apps.js +2 -1
- package/config/product/auth.js +1 -0
- package/config/product/backup.js +1 -0
- package/config/product/compliance.js +1 -1
- package/config/product/explorer.js +25 -6
- package/config/product/fleet.js +1 -0
- package/config/product/gatekeeper.js +1 -0
- package/config/product/istio.js +1 -0
- package/config/product/logging.js +1 -0
- package/config/product/longhorn.js +2 -1
- package/config/product/manager.js +1 -0
- package/config/product/monitoring.js +1 -0
- package/config/product/navlinks.js +1 -0
- package/config/product/neuvector.js +2 -1
- package/config/product/settings.js +1 -0
- package/config/product/uiplugins.js +1 -0
- package/config/query-params.js +1 -0
- package/config/router/routes.js +0 -8
- package/core/__tests__/plugin-products-helpers.test.ts +454 -0
- package/core/__tests__/plugin-products.test.ts +3810 -0
- package/core/extension-manager-impl.js +30 -1
- package/core/plugin-products-base.ts +392 -0
- package/core/plugin-products-extending.ts +44 -0
- package/core/plugin-products-helpers.ts +263 -0
- package/core/plugin-products-top-level.ts +66 -0
- package/core/plugin-products-type-guards.ts +33 -0
- package/core/plugin-products.ts +50 -0
- package/core/plugin-types.ts +237 -0
- package/core/plugin.ts +45 -10
- package/core/productDebugger.js +48 -0
- package/core/types.ts +97 -11
- package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
- package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/fleet.cattle.io.bundle.vue +21 -34
- package/detail/management.cattle.io.fleetworkspace.vue +49 -0
- package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
- package/dialog/InstallExtensionDialog.vue +6 -27
- package/dialog/UninstallExistingExtensionDialog.vue +141 -0
- package/dialog/UninstallExtensionDialog.vue +4 -26
- package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
- package/edit/__tests__/kontainerDriver.test.ts +0 -13
- package/edit/__tests__/nodeDriver.test.ts +5 -11
- package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auth/__tests__/oidc.test.ts +54 -0
- package/edit/auth/azuread.vue +1 -1
- package/edit/auth/oidc.vue +8 -0
- package/edit/kontainerDriver.vue +1 -2
- package/edit/nodeDriver.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
- package/initialize/App.vue +29 -2
- package/initialize/install-plugins.js +0 -2
- package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
- package/list/catalog.cattle.io.app.vue +25 -5
- package/list/management.cattle.io.feature.vue +1 -1
- package/list/management.cattle.io.fleetworkspace.vue +8 -0
- package/list/provisioning.cattle.io.cluster.vue +0 -1
- package/list/workload.vue +11 -4
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/chart.js +40 -9
- package/mixins/resource-fetch.js +12 -3
- package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
- package/models/__tests__/chart.test.ts +99 -6
- package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
- package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
- package/models/catalog.cattle.io.app.js +21 -17
- package/models/catalog.cattle.io.clusterrepo.js +39 -11
- package/models/chart.js +33 -19
- package/models/fleet-application.js +1 -1
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/kontainerdriver.js +11 -0
- package/models/management.cattle.io.authconfig.js +5 -1
- package/models/management.cattle.io.cluster.js +0 -53
- package/models/management.cattle.io.feature.js +3 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -26
- package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
- package/models/nodedriver.js +7 -0
- package/models/pod.js +18 -0
- package/models/workload.js +20 -2
- package/package.json +13 -13
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
- package/pages/c/_cluster/apps/charts/chart.vue +217 -33
- package/pages/c/_cluster/apps/charts/index.vue +2 -2
- package/pages/c/_cluster/apps/charts/install.vue +8 -3
- package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
- package/pages/c/_cluster/settings/brand.vue +4 -4
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
- package/pages/c/_cluster/uiplugins/index.vue +166 -62
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
- package/plugins/dashboard-store/actions.js +3 -2
- package/plugins/dashboard-store/resource-class.js +62 -6
- package/plugins/plugin.js +16 -0
- package/plugins/steve/steve-pagination-utils.ts +7 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
- package/scripts/test-plugins-build.sh +5 -2
- package/scripts/typegen.sh +13 -1
- package/server/server-middleware.js +2 -2
- package/static/humans.txt +1 -0
- package/static/robots.txt +34 -0
- package/static/welcome-cow.svg +18 -0
- package/store/__tests__/catalog.test.ts +161 -11
- package/store/__tests__/type-map.test.ts +84 -24
- package/store/auth.js +0 -3
- package/store/catalog.js +60 -8
- package/store/type-map.js +42 -3
- package/tsconfig.paths.json +1 -0
- package/types/resources/pod.ts +18 -0
- package/types/shell/index.d.ts +8539 -2938
- package/types/store/dashboard-store.types.ts +5 -0
- package/types/store/pagination.types.ts +6 -0
- package/utils/__tests__/git.test.ts +270 -0
- package/utils/__tests__/inactivity.test.ts +316 -0
- package/utils/__tests__/object.test.ts +77 -0
- package/utils/__tests__/time.test.ts +14 -1
- package/utils/__tests__/url.test.ts +246 -0
- package/utils/axios.js +1 -4
- package/utils/dynamic-importer.js +3 -2
- package/utils/object.js +33 -2
- package/utils/pagination-utils.ts +1 -1
- package/utils/time.ts +5 -0
- package/utils/uiplugins.ts +12 -16
- package/utils/validators/__tests__/private-registry.test.ts +76 -0
- package/utils/validators/private-registry.ts +28 -0
- package/vue.config.js +0 -9
- package/assets/images/providers/azuread-black.svg +0 -22
- package/assets/images/providers/azuread.svg +0 -25
- package/assets/images/vendor/azuread.svg +0 -18
- package/assets/styles/fonts/_dots.scss +0 -18
- package/components/EmberPage.vue +0 -622
- package/components/EmberPageView.vue +0 -39
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
- package/mixins/labeled-form-element.ts +0 -225
- package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
- package/pages/c/_cluster/manager/pages/_page.vue +0 -22
- package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
- package/plugins/ember-cookie.js +0 -17
- 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
|
+
};
|