@rancher/shell 3.0.12-rc.1 → 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/translations/en-us.yaml +19 -17
- package/assets/translations/zh-hans.yaml +4 -8
- 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/SelectIconGrid.vue +5 -0
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/fleet/FleetClusterTargets/index.vue +18 -1
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +82 -24
- 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/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/nav/Group.vue +7 -6
- package/components/nav/Header.vue +24 -3
- 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/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/query-params.js +1 -0
- package/config/router/routes.js +0 -8
- package/core/__tests__/plugin-products.test.ts +616 -25
- package/core/plugin-products-base.ts +31 -14
- package/core/plugin-products-helpers.ts +5 -4
- package/core/plugin-types.ts +18 -3
- package/core/types.ts +3 -1
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/management.cattle.io.fleetworkspace.vue +49 -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/index.vue +70 -99
- 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/machine-config/amazonec2.vue +1 -0
- package/mixins/chart.js +40 -9
- 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/package.json +13 -12
- 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/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 +15 -10
- package/pages/c/_cluster/uiplugins/index.vue +23 -25
- 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/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/auth.js +0 -3
- package/store/catalog.js +60 -8
- package/types/shell/index.d.ts +26 -22
- 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/object.js +33 -2
- package/utils/time.ts +5 -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
|
@@ -2,7 +2,7 @@ import { IExtension } from '@shell/core/types';
|
|
|
2
2
|
import {
|
|
3
3
|
ProductChild, ProductMetadata,
|
|
4
4
|
ConfigureTypeConfiguration, VirtualTypeConfiguration,
|
|
5
|
-
ProductChildCustomPage
|
|
5
|
+
ProductChildCustomPage, VueRouteComponent, OverviewPageRoutingMetadata
|
|
6
6
|
} from '@shell/core/plugin-types';
|
|
7
7
|
import EmptyProductPage from '@shell/components/EmptyProductPage.vue';
|
|
8
8
|
import pluginProductsHelpers from '@shell/core/plugin-products-helpers';
|
|
@@ -25,6 +25,8 @@ export abstract class BasePluginProduct {
|
|
|
25
25
|
|
|
26
26
|
protected addedResourceRoutes = false;
|
|
27
27
|
|
|
28
|
+
protected registeredPageNames: Set<string> = new Set();
|
|
29
|
+
|
|
28
30
|
protected DSLMethods: any;
|
|
29
31
|
|
|
30
32
|
protected config: ProductChild[];
|
|
@@ -68,6 +70,13 @@ export abstract class BasePluginProduct {
|
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Generates data for group overview page routing
|
|
75
|
+
*/
|
|
76
|
+
protected generateMetadataForGroupOverviewPageRouting(name: string, component: VueRouteComponent): OverviewPageRoutingMetadata {
|
|
77
|
+
return { name, component };
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
/**
|
|
72
81
|
* This is where we register the product and its children via the DSL
|
|
73
82
|
*/
|
|
@@ -198,16 +207,12 @@ export abstract class BasePluginProduct {
|
|
|
198
207
|
defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, entryChild, { omitPath: true, extendProduct: !this.isNewProduct });
|
|
199
208
|
}
|
|
200
209
|
} else {
|
|
201
|
-
// Group with component - route to the group page
|
|
202
|
-
defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name,
|
|
203
|
-
omitPath: true, component: firstConfig.component, extendProduct: !this.isNewProduct
|
|
204
|
-
});
|
|
210
|
+
// Group with component - route to the group overview page (which will render the group's component and side-menu)
|
|
211
|
+
defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, this.generateMetadataForGroupOverviewPageRouting(firstConfig.name, firstConfig.component), { omitPath: true, extendProduct: !this.isNewProduct });
|
|
205
212
|
}
|
|
206
213
|
} else if (firstConfig.component) {
|
|
207
214
|
// Group with component but no children - route to the group page itself
|
|
208
|
-
defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name,
|
|
209
|
-
omitPath: true, component: firstConfig.component, extendProduct: !this.isNewProduct
|
|
210
|
-
});
|
|
215
|
+
defaultRoute = pluginProductsHelpers.generateVirtualTypeRoute(this.name, this.generateMetadataForGroupOverviewPageRouting(firstConfig.name, firstConfig.component), { omitPath: true, extendProduct: !this.isNewProduct });
|
|
211
216
|
}
|
|
212
217
|
} else if (isProductChildWithType(firstConfig)) {
|
|
213
218
|
// Simple configureType page (resource page)
|
|
@@ -248,6 +253,13 @@ export abstract class BasePluginProduct {
|
|
|
248
253
|
const name = `${ parentName }-${ item.name }`;
|
|
249
254
|
const finalName = groupNaming ? `${ parentName }-${ groupNaming }-${ item.name }` : name;
|
|
250
255
|
|
|
256
|
+
// Check for duplicate page names within the same product
|
|
257
|
+
if (this.registeredPageNames.has(finalName)) {
|
|
258
|
+
this.surfaceError(`Duplicate page name "${ item.name }" - each page must have a unique name within a product`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.registeredPageNames.add(finalName);
|
|
262
|
+
|
|
251
263
|
const virtualTypeConfig: VirtualTypeConfiguration = {
|
|
252
264
|
label: item.label,
|
|
253
265
|
labelKey: item.labelKey,
|
|
@@ -261,7 +273,8 @@ export abstract class BasePluginProduct {
|
|
|
261
273
|
if (isProductChildGroup(item)) {
|
|
262
274
|
virtualTypeConfig.exact = true;
|
|
263
275
|
virtualTypeConfig.overview = true;
|
|
264
|
-
|
|
276
|
+
// Pass group metadata as pageChild so the route gets a unique path segment (e.g. /product/c/:cluster/groupName)
|
|
277
|
+
virtualTypeConfig.route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, this.generateMetadataForGroupOverviewPageRouting(item.name, item.component as ProductChildCustomPage['component']), { extendProduct: !this.isNewProduct });
|
|
265
278
|
} else {
|
|
266
279
|
virtualTypeConfig.route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, item, { extendProduct: !this.isNewProduct });
|
|
267
280
|
}
|
|
@@ -270,6 +283,14 @@ export abstract class BasePluginProduct {
|
|
|
270
283
|
} else if (isProductChildWithType(item)) {
|
|
271
284
|
// Page with a "type" specified maps to a configureType
|
|
272
285
|
const typeValue = item.type;
|
|
286
|
+
|
|
287
|
+
// Check for duplicate resource type within the same product
|
|
288
|
+
if (this.registeredPageNames.has(typeValue)) {
|
|
289
|
+
this.surfaceError(`Duplicate resource type "${ typeValue }" - each resource type must be unique within a product`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.registeredPageNames.add(typeValue);
|
|
293
|
+
|
|
273
294
|
const route = pluginProductsHelpers.generateConfigureTypeRoute(parentName, item, { extendProduct: !this.isNewProduct });
|
|
274
295
|
|
|
275
296
|
const configureTypeConfig: ConfigureTypeConfiguration = {
|
|
@@ -300,10 +321,6 @@ export abstract class BasePluginProduct {
|
|
|
300
321
|
this.surfaceError('Group items cannot have a "type" property - only custom pages can have groups.');
|
|
301
322
|
}
|
|
302
323
|
|
|
303
|
-
if (child.component && !this.isNewProduct) {
|
|
304
|
-
this.surfaceError('When extending an existing product, group parent items cannot have a component because of route matching conflicts.');
|
|
305
|
-
}
|
|
306
|
-
|
|
307
324
|
let route;
|
|
308
325
|
|
|
309
326
|
if (!child.component) {
|
|
@@ -316,7 +333,7 @@ export abstract class BasePluginProduct {
|
|
|
316
333
|
|
|
317
334
|
route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, pageForRoute, { extendProduct: !this.isNewProduct });
|
|
318
335
|
} else {
|
|
319
|
-
route = pluginProductsHelpers.generateVirtualTypeRoute(parentName,
|
|
336
|
+
route = pluginProductsHelpers.generateVirtualTypeRoute(parentName, this.generateMetadataForGroupOverviewPageRouting(child.name, child.component), { component: child.component, extendProduct: !this.isNewProduct });
|
|
320
337
|
}
|
|
321
338
|
|
|
322
339
|
// add the route for the group page/parent
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RouteRecordRawWithParams, ProductChildGroup, ProductChild,
|
|
3
|
-
ProductChildCustomPage, ProductChildResourcePage, ProductRegistrationRouteGenerationOptions
|
|
3
|
+
ProductChildCustomPage, ProductChildResourcePage, ProductRegistrationRouteGenerationOptions,
|
|
4
|
+
OverviewPageRoutingMetadata
|
|
4
5
|
} from '@shell/core/plugin-types';
|
|
5
6
|
import { BLANK_CLUSTER } from '@shell/store/store-types';
|
|
6
7
|
|
|
@@ -62,7 +63,7 @@ class PluginProductsHelpers {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// VIRTUAL TYPE ROUTES
|
|
65
|
-
generateVirtualTypeRoute(parentName: string, pageChild: ProductChildCustomPage | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
66
|
+
generateVirtualTypeRoute(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
66
67
|
if (options.extendProduct) {
|
|
67
68
|
return this.generateVirtualTypeRouteForExistingProduct(parentName, pageChild, options);
|
|
68
69
|
} else {
|
|
@@ -71,7 +72,7 @@ class PluginProductsHelpers {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
// VIRTUAL TYPE ROUTES - CLUSTER LEVEL EXTENSION
|
|
74
|
-
private generateVirtualTypeRouteForExistingProduct(parentName: string, pageChild: ProductChildCustomPage | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
75
|
+
private generateVirtualTypeRouteForExistingProduct(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
75
76
|
const { component, omitPath } = options;
|
|
76
77
|
const name = pageChild ? `c-cluster-${ parentName }-${ pageChild.name }` : `c-cluster-${ parentName }`;
|
|
77
78
|
const path = pageChild ? `c/:cluster/${ parentName }/${ pageChild.name }` : `c/:cluster/${ parentName }`;
|
|
@@ -95,7 +96,7 @@ class PluginProductsHelpers {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// VIRTUAL TYPE ROUTES - TOP LEVEL EXTENSION
|
|
98
|
-
private generateVirtualTypeRouteForNewProduct(parentName: string, pageChild: ProductChildCustomPage | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
99
|
+
private generateVirtualTypeRouteForNewProduct(parentName: string, pageChild: ProductChildCustomPage | OverviewPageRoutingMetadata | undefined, options: ProductRegistrationRouteGenerationOptions = {}): RouteRecordRawWithParams {
|
|
99
100
|
const { component, omitPath } = options;
|
|
100
101
|
const name = pageChild ? `${ parentName }-c-cluster-${ pageChild.name }` : `${ parentName }-c-cluster`;
|
|
101
102
|
const path = pageChild ? `${ parentName }/c/:cluster/${ pageChild.name }` : `${ parentName }/c/:cluster`;
|
package/core/plugin-types.ts
CHANGED
|
@@ -150,12 +150,27 @@ export type ConfigureTypeConfiguration = {
|
|
|
150
150
|
// ]
|
|
151
151
|
}
|
|
152
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
|
+
|
|
153
168
|
/**
|
|
154
169
|
* Represents a custom page with a component
|
|
155
170
|
*/
|
|
156
171
|
export type ProductChildCustomPage = ProductChildMetadata & {
|
|
157
172
|
/** Component to render for this custom page */
|
|
158
|
-
component:
|
|
173
|
+
component: VueRouteComponent;
|
|
159
174
|
/** Optional configuration for the page */
|
|
160
175
|
config?: VirtualTypeConfiguration;
|
|
161
176
|
};
|
|
@@ -189,7 +204,7 @@ export type ProductChild = ProductChildGroup | ProductChildPage; // eslint-disab
|
|
|
189
204
|
* Represents a group of child pages in a product configuration
|
|
190
205
|
*/
|
|
191
206
|
export type ProductChildGroup = ProductChildMetadata & {
|
|
192
|
-
component?:
|
|
207
|
+
component?: VueRouteComponent;
|
|
193
208
|
children: ProductChild[];
|
|
194
209
|
/** Default child to navigate to */
|
|
195
210
|
default?: string;
|
|
@@ -218,5 +233,5 @@ export type ProductMetadata = Omit<ProductOptions, 'name' | 'label' | 'labelKey'
|
|
|
218
233
|
*/
|
|
219
234
|
export type ProductSinglePage = ProductMetadata & {
|
|
220
235
|
/** Component to render for this product (single page product) */
|
|
221
|
-
component:
|
|
236
|
+
component: VueRouteComponent;
|
|
222
237
|
};
|
package/core/types.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { PaginationSettingsStores } from '@shell/types/resources/settings';
|
|
|
5
5
|
import type {
|
|
6
6
|
ProductMetadata, ProductSinglePage,
|
|
7
7
|
StandardProductName, RouteRecordRawWithParams, ProductChildGroup,
|
|
8
|
-
ProductChildPage
|
|
8
|
+
ProductChildPage, ProductChild
|
|
9
9
|
} from './plugin-types';
|
|
10
10
|
|
|
11
11
|
// Cluster Provisioning types
|
|
@@ -665,6 +665,7 @@ export interface IExtension {
|
|
|
665
665
|
*/
|
|
666
666
|
addProduct(product: ProductMetadata, config: ProductChildGroup[]): void;
|
|
667
667
|
addProduct(product: ProductMetadata, config: ProductChildPage[]): void;
|
|
668
|
+
addProduct(product: ProductMetadata, config: ProductChild[]): void;
|
|
668
669
|
|
|
669
670
|
/**
|
|
670
671
|
* Add a product to the sidebar, without children (no side menu, single page only)
|
|
@@ -695,6 +696,7 @@ export interface IExtension {
|
|
|
695
696
|
*/
|
|
696
697
|
extendProduct(product: StandardProductName | string, config: ProductChildGroup[]): void;
|
|
697
698
|
extendProduct(product: StandardProductName | string, config: ProductChildPage[]): void;
|
|
699
|
+
extendProduct(product: StandardProductName | string, config: ProductChild[]): void;
|
|
698
700
|
|
|
699
701
|
/**
|
|
700
702
|
* Add a locale to the i18n store
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import DetailWorkspace from '@shell/detail/management.cattle.io.fleetworkspace.vue';
|
|
3
|
+
import { FLEET } from '@shell/config/types';
|
|
4
|
+
import { NAME as FLEET_NAME } from '@shell/config/product/fleet';
|
|
5
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
6
|
+
|
|
7
|
+
describe('component: DetailWorkspace', () => {
|
|
8
|
+
const mockValue = {
|
|
9
|
+
id: 'fleet-default',
|
|
10
|
+
counts: {
|
|
11
|
+
gitRepos: 3,
|
|
12
|
+
helmOps: 2,
|
|
13
|
+
clusters: 5,
|
|
14
|
+
cluster: 5,
|
|
15
|
+
clusterGroup: 1,
|
|
16
|
+
clusterGroups: 1,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const mockRouter = { push: jest.fn() };
|
|
21
|
+
|
|
22
|
+
const defaultStore = {
|
|
23
|
+
commit: jest.fn(),
|
|
24
|
+
dispatch: jest.fn(),
|
|
25
|
+
getters: {
|
|
26
|
+
'i18n/t': (key: string) => key,
|
|
27
|
+
'i18n/exists': () => true,
|
|
28
|
+
currentProduct: { name: FLEET_NAME },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const createWrapper = (props = {}) => {
|
|
33
|
+
return shallowMount(DetailWorkspace, {
|
|
34
|
+
props: { value: mockValue, ...props },
|
|
35
|
+
global: {
|
|
36
|
+
mocks: {
|
|
37
|
+
$store: defaultStore,
|
|
38
|
+
$route: { params: {} },
|
|
39
|
+
$router: mockRouter,
|
|
40
|
+
},
|
|
41
|
+
stubs: {
|
|
42
|
+
CountBox: { template: '<div />', props: ['clickable', 'count', 'name', 'primaryColorVar'] },
|
|
43
|
+
ResourceTabs: { template: '<div />' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('applicationRoute', () => {
|
|
54
|
+
it('should return the fleet application route', () => {
|
|
55
|
+
const wrapper = createWrapper();
|
|
56
|
+
|
|
57
|
+
expect(wrapper.vm.applicationRoute).toStrictEqual({
|
|
58
|
+
name: 'c-cluster-fleet-application',
|
|
59
|
+
params: { cluster: BLANK_CLUSTER },
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('clustersRoute', () => {
|
|
65
|
+
it('should return the fleet clusters list route', () => {
|
|
66
|
+
const wrapper = createWrapper();
|
|
67
|
+
|
|
68
|
+
expect(wrapper.vm.clustersRoute).toStrictEqual({
|
|
69
|
+
name: 'c-cluster-product-resource',
|
|
70
|
+
params: {
|
|
71
|
+
cluster: BLANK_CLUSTER,
|
|
72
|
+
product: FLEET_NAME,
|
|
73
|
+
resource: FLEET.CLUSTER,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('clusterGroupsRoute', () => {
|
|
80
|
+
it('should return the fleet cluster groups list route', () => {
|
|
81
|
+
const wrapper = createWrapper();
|
|
82
|
+
|
|
83
|
+
expect(wrapper.vm.clusterGroupsRoute).toStrictEqual({
|
|
84
|
+
name: 'c-cluster-product-resource',
|
|
85
|
+
params: {
|
|
86
|
+
cluster: BLANK_CLUSTER,
|
|
87
|
+
product: FLEET_NAME,
|
|
88
|
+
resource: FLEET.CLUSTER_GROUP,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('setWorkspaceAndNavigate', () => {
|
|
95
|
+
it('should commit updateWorkspace with the workspace id', () => {
|
|
96
|
+
const wrapper = createWrapper();
|
|
97
|
+
const route = wrapper.vm.applicationRoute;
|
|
98
|
+
|
|
99
|
+
wrapper.vm.setWorkspaceAndNavigate(route);
|
|
100
|
+
|
|
101
|
+
expect(defaultStore.commit).toHaveBeenCalledWith('updateWorkspace', {
|
|
102
|
+
value: 'fleet-default',
|
|
103
|
+
getters: defaultStore.getters,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should dispatch prefs/set with the workspace id', () => {
|
|
108
|
+
const wrapper = createWrapper();
|
|
109
|
+
const route = wrapper.vm.applicationRoute;
|
|
110
|
+
|
|
111
|
+
wrapper.vm.setWorkspaceAndNavigate(route);
|
|
112
|
+
|
|
113
|
+
expect(defaultStore.dispatch).toHaveBeenCalledWith('prefs/set', {
|
|
114
|
+
key: expect.any(String),
|
|
115
|
+
value: 'fleet-default',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should navigate to the given route', () => {
|
|
120
|
+
const wrapper = createWrapper();
|
|
121
|
+
const route = wrapper.vm.clustersRoute;
|
|
122
|
+
|
|
123
|
+
wrapper.vm.setWorkspaceAndNavigate(route);
|
|
124
|
+
|
|
125
|
+
expect(mockRouter.push).toHaveBeenCalledWith(route);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -4,6 +4,8 @@ import ResourceTabs from '@shell/components/form/ResourceTabs';
|
|
|
4
4
|
import { SCOPE_NAMESPACE, SCOPE_CLUSTER } from '@shell/components/RoleBindings.vue';
|
|
5
5
|
import { NAME as FLEET_NAME } from '@shell/config/product/fleet';
|
|
6
6
|
import { FLEET } from '@shell/config/types';
|
|
7
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
8
|
+
import { WORKSPACE } from '@shell/store/prefs';
|
|
7
9
|
|
|
8
10
|
export default {
|
|
9
11
|
name: 'DetailWorkspace',
|
|
@@ -34,6 +36,35 @@ export default {
|
|
|
34
36
|
return this.t(`typeLabel."${ FLEET.HELM_OP }"`, { count: this.value.counts.helmOps });
|
|
35
37
|
},
|
|
36
38
|
|
|
39
|
+
applicationRoute() {
|
|
40
|
+
return {
|
|
41
|
+
name: 'c-cluster-fleet-application',
|
|
42
|
+
params: { cluster: BLANK_CLUSTER }
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
clustersRoute() {
|
|
47
|
+
return {
|
|
48
|
+
name: 'c-cluster-product-resource',
|
|
49
|
+
params: {
|
|
50
|
+
cluster: BLANK_CLUSTER,
|
|
51
|
+
product: FLEET_NAME,
|
|
52
|
+
resource: FLEET.CLUSTER,
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
clusterGroupsRoute() {
|
|
58
|
+
return {
|
|
59
|
+
name: 'c-cluster-product-resource',
|
|
60
|
+
params: {
|
|
61
|
+
cluster: BLANK_CLUSTER,
|
|
62
|
+
product: FLEET_NAME,
|
|
63
|
+
resource: FLEET.CLUSTER_GROUP,
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
|
|
37
68
|
SCOPE_NAMESPACE() {
|
|
38
69
|
return SCOPE_NAMESPACE;
|
|
39
70
|
},
|
|
@@ -46,6 +77,16 @@ export default {
|
|
|
46
77
|
return FLEET_NAME;
|
|
47
78
|
}
|
|
48
79
|
},
|
|
80
|
+
|
|
81
|
+
methods: {
|
|
82
|
+
setWorkspaceAndNavigate(route) {
|
|
83
|
+
const workspaceId = this.value.id;
|
|
84
|
+
|
|
85
|
+
this.$store.commit('updateWorkspace', { value: workspaceId, getters: this.$store.getters });
|
|
86
|
+
this.$store.dispatch('prefs/set', { key: WORKSPACE, value: workspaceId });
|
|
87
|
+
this.$router.push(route);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
49
90
|
};
|
|
50
91
|
</script>
|
|
51
92
|
|
|
@@ -58,6 +99,8 @@ export default {
|
|
|
58
99
|
:count="value.counts.gitRepos"
|
|
59
100
|
:name="gitRepoLabel"
|
|
60
101
|
:primary-color-var="'--sizzle-3'"
|
|
102
|
+
:clickable="true"
|
|
103
|
+
@click="setWorkspaceAndNavigate(applicationRoute)"
|
|
61
104
|
/>
|
|
62
105
|
</div>
|
|
63
106
|
<div class="col span-3">
|
|
@@ -65,6 +108,8 @@ export default {
|
|
|
65
108
|
:count="value.counts.helmOps"
|
|
66
109
|
:name="helmOpsLabel"
|
|
67
110
|
:primary-color-var="'--sizzle-3'"
|
|
111
|
+
:clickable="true"
|
|
112
|
+
@click="setWorkspaceAndNavigate(applicationRoute)"
|
|
68
113
|
/>
|
|
69
114
|
</div>
|
|
70
115
|
<div class="col span-3">
|
|
@@ -72,6 +117,8 @@ export default {
|
|
|
72
117
|
:count="value.counts.clusters"
|
|
73
118
|
:name="clustersLabel"
|
|
74
119
|
:primary-color-var="'--sizzle-1'"
|
|
120
|
+
:clickable="true"
|
|
121
|
+
@click="setWorkspaceAndNavigate(clustersRoute)"
|
|
75
122
|
/>
|
|
76
123
|
</div>
|
|
77
124
|
<div class="col span-3">
|
|
@@ -79,6 +126,8 @@ export default {
|
|
|
79
126
|
:count="value.counts.clusterGroups"
|
|
80
127
|
:name="clusterGroupsLabel"
|
|
81
128
|
:primary-color-var="'--sizzle-2'"
|
|
129
|
+
:clickable="true"
|
|
130
|
+
@click="setWorkspaceAndNavigate(clusterGroupsRoute)"
|
|
82
131
|
/>
|
|
83
132
|
</div>
|
|
84
133
|
</div>
|
|
@@ -4,6 +4,7 @@ import HelmOp from '@shell/models/fleet.cattle.io.helmop';
|
|
|
4
4
|
import HelmOpComponent from '@shell/edit/fleet.cattle.io.helmop.vue';
|
|
5
5
|
import FleetSecretSelector from '@shell/components/fleet/FleetSecretSelector.vue';
|
|
6
6
|
import FleetConfigMapSelector from '@shell/components/fleet/FleetConfigMapSelector.vue';
|
|
7
|
+
import { createStore } from 'vuex';
|
|
7
8
|
|
|
8
9
|
const mockStore = {
|
|
9
10
|
dispatch: jest.fn(),
|
|
@@ -85,6 +86,14 @@ const initHelmOp = (props: any, options = {}) => {
|
|
|
85
86
|
value,
|
|
86
87
|
...props
|
|
87
88
|
},
|
|
89
|
+
provide: {
|
|
90
|
+
store: createStore({
|
|
91
|
+
getters: {
|
|
92
|
+
currentStore: () => 'current_store',
|
|
93
|
+
'management/paginationEnabled': () => () => false
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
},
|
|
88
97
|
computed: mockComputed,
|
|
89
98
|
global: { mocks },
|
|
90
99
|
};
|
|
@@ -30,7 +30,6 @@ describe('view: kontainerdriver should', () => {
|
|
|
30
30
|
active: true,
|
|
31
31
|
checksum: '',
|
|
32
32
|
url: '',
|
|
33
|
-
uiUrl: '',
|
|
34
33
|
whitelistDomains: []
|
|
35
34
|
}
|
|
36
35
|
},
|
|
@@ -65,32 +64,22 @@ describe('view: kontainerdriver should', () => {
|
|
|
65
64
|
|
|
66
65
|
it('have "Create" button enabled and disabled depending on validation results', async() => {
|
|
67
66
|
const urlField = wrapper.find('[data-testid="driver-create-url-field"]');
|
|
68
|
-
const uiurlField = wrapper.find('[data-testid="driver-create-uiurl-field"]');
|
|
69
67
|
const checksumField = wrapper.find('[data-testid="driver-create-checksum-field"]');
|
|
70
68
|
const saveButton = wrapper.find('[data-testid="kontainer-driver-edit-save"]').element as HTMLInputElement;
|
|
71
69
|
|
|
72
70
|
const testCases = [
|
|
73
71
|
{
|
|
74
72
|
url: '1111',
|
|
75
|
-
uiurl: 'http://test.com',
|
|
76
73
|
checksum: 'aaaaaBBBBdddd',
|
|
77
74
|
result: true
|
|
78
75
|
},
|
|
79
76
|
{
|
|
80
77
|
url: 'http://test.com',
|
|
81
|
-
uiurl: '1111',
|
|
82
|
-
checksum: 'aaaaaBBBBdddd',
|
|
83
|
-
result: true
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
url: 'http://test.com',
|
|
87
|
-
uiurl: 'http://test.com',
|
|
88
78
|
checksum: '!!!',
|
|
89
79
|
result: true
|
|
90
80
|
},
|
|
91
81
|
{
|
|
92
82
|
url: 'http://test.com',
|
|
93
|
-
uiurl: 'http://test.com',
|
|
94
83
|
checksum: 'aaaaaBBBBdddd',
|
|
95
84
|
result: false
|
|
96
85
|
}
|
|
@@ -99,8 +88,6 @@ describe('view: kontainerdriver should', () => {
|
|
|
99
88
|
for (const testCase of testCases) {
|
|
100
89
|
urlField.setValue(testCase.url);
|
|
101
90
|
await nextTick();
|
|
102
|
-
uiurlField.setValue(testCase.uiurl);
|
|
103
|
-
await nextTick();
|
|
104
91
|
checksumField.setValue(testCase.checksum);
|
|
105
92
|
await nextTick();
|
|
106
93
|
|
|
@@ -31,7 +31,6 @@ describe('view: nodedriver should', () => {
|
|
|
31
31
|
active: true,
|
|
32
32
|
checksum: '',
|
|
33
33
|
url: '',
|
|
34
|
-
uiUrl: '',
|
|
35
34
|
whitelistDomains: []
|
|
36
35
|
}
|
|
37
36
|
},
|
|
@@ -65,21 +64,16 @@ describe('view: nodedriver should', () => {
|
|
|
65
64
|
});
|
|
66
65
|
|
|
67
66
|
it.each`
|
|
68
|
-
url |
|
|
69
|
-
${ '1111' } | ${ '
|
|
70
|
-
${ 'http://test.com' } | ${ '
|
|
71
|
-
${ 'http://test.com' } | ${ '
|
|
72
|
-
|
|
73
|
-
`('have "Create" button enabled and disabled depending on validation results', async({
|
|
74
|
-
url, uiurl, checksum, expected
|
|
75
|
-
}) => {
|
|
67
|
+
url | checksum | expected
|
|
68
|
+
${ '1111' } | ${ 'aaaaaBBBBdddd' } | ${ true }
|
|
69
|
+
${ 'http://test.com' } | ${ '!!!' } | ${ true }
|
|
70
|
+
${ 'http://test.com' } | ${ 'aaaaaBBBBdddd' } | ${ false }
|
|
71
|
+
`('have "Create" button enabled and disabled depending on validation results', async({ url, checksum, expected }) => {
|
|
76
72
|
const urlField = wrapper.find('[data-testid="driver-create-url-field"]');
|
|
77
|
-
const uiurlField = wrapper.find('[data-testid="driver-create-uiurl-field"]');
|
|
78
73
|
const checksumField = wrapper.find('[data-testid="driver-create-checksum-field"]');
|
|
79
74
|
const saveButton = wrapper.find('[data-testid="node-driver-edit-save"]').element as HTMLInputElement;
|
|
80
75
|
|
|
81
76
|
urlField.setValue(url);
|
|
82
|
-
uiurlField.setValue(uiurl);
|
|
83
77
|
checksumField.setValue(checksum);
|
|
84
78
|
|
|
85
79
|
await nextTick();
|
|
@@ -2,6 +2,7 @@ import { nextTick } from 'vue';
|
|
|
2
2
|
import { mount } from '@vue/test-utils';
|
|
3
3
|
import RestoreComponent from '@shell/edit/resources.cattle.io.restore.vue';
|
|
4
4
|
import { _CREATE } from '@shell/config/query-params';
|
|
5
|
+
import { createStore } from 'vuex';
|
|
5
6
|
|
|
6
7
|
describe('view: restore storage source switching', () => {
|
|
7
8
|
let wrapper: any;
|
|
@@ -30,6 +31,14 @@ describe('view: restore storage source switching', () => {
|
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
return mount(RestoreComponent, {
|
|
34
|
+
provide: {
|
|
35
|
+
store: createStore({
|
|
36
|
+
getters: {
|
|
37
|
+
currentStore: () => 'current_store',
|
|
38
|
+
'cluster/paginationEnabled': () => () => false
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
},
|
|
33
42
|
global: {
|
|
34
43
|
mocks: {
|
|
35
44
|
$store: mockStore,
|
|
@@ -65,7 +65,9 @@ exports[`component: General rendering & initial state should render with default
|
|
|
65
65
|
clearable="false"
|
|
66
66
|
closeonselect="true"
|
|
67
67
|
disabled="false"
|
|
68
|
+
filterable="true"
|
|
68
69
|
hovertooltip="true"
|
|
70
|
+
instore="cluster"
|
|
69
71
|
label="auditPolicy.general.verbosity.level.label"
|
|
70
72
|
loading="false"
|
|
71
73
|
localizedlabel="false"
|
|
@@ -73,8 +75,12 @@ exports[`component: General rendering & initial state should render with default
|
|
|
73
75
|
nooptionslabelkey="labelSelect.noOptions.empty"
|
|
74
76
|
optionlabel="label"
|
|
75
77
|
options="[object Object],[object Object],[object Object],[object Object]"
|
|
78
|
+
placeholder=""
|
|
76
79
|
reduce="[Function]"
|
|
77
80
|
required="false"
|
|
81
|
+
requiredirty="true"
|
|
82
|
+
rules=""
|
|
83
|
+
searchable="false"
|
|
78
84
|
selectable="[Function]"
|
|
79
85
|
value="0"
|
|
80
86
|
/>
|
|
@@ -297,5 +297,59 @@ describe('oidc.vue', () => {
|
|
|
297
297
|
expect(groupsClaim.exists()).toBe(false);
|
|
298
298
|
expect(emailClaim.exists()).toBe(false);
|
|
299
299
|
});
|
|
300
|
+
|
|
301
|
+
describe('clientAuthenticatedSearch checkbox', () => {
|
|
302
|
+
it('is not rendered for genericoidc', async() => {
|
|
303
|
+
const checkbox = wrapper.find('[data-testid="input-client-authenticated-group-search"]');
|
|
304
|
+
|
|
305
|
+
expect(checkbox.exists()).toBe(false);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('is not rendered for cognito', async() => {
|
|
309
|
+
await wrapper.setData({ model: { ...mockModel, id: 'cognito' } });
|
|
310
|
+
|
|
311
|
+
const checkbox = wrapper.find('[data-testid="input-client-authenticated-group-search"]');
|
|
312
|
+
|
|
313
|
+
expect(checkbox.exists()).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('is rendered for keycloakoidc', async() => {
|
|
317
|
+
await wrapper.setData({ model: { ...mockModel, id: 'keycloakoidc' } });
|
|
318
|
+
|
|
319
|
+
const checkbox = wrapper.find('[data-testid="input-client-authenticated-group-search"]');
|
|
320
|
+
|
|
321
|
+
expect(checkbox.exists()).toBe(true);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('defaults to falsy when not set on keycloakoidc', async() => {
|
|
325
|
+
await wrapper.setData({ model: { ...mockModel, id: 'keycloakoidc' } });
|
|
326
|
+
|
|
327
|
+
expect(wrapper.vm.model.clientAuthenticatedSearch).toBeFalsy();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('updates model when checkbox is clicked', async() => {
|
|
331
|
+
await wrapper.setData({
|
|
332
|
+
model: {
|
|
333
|
+
...mockModel, id: 'keycloakoidc', clientAuthenticatedSearch: false
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const checkbox = wrapper.getComponent('[data-testid="input-client-authenticated-group-search"]');
|
|
338
|
+
|
|
339
|
+
await checkbox.find('[role="checkbox"]').trigger('click');
|
|
340
|
+
|
|
341
|
+
expect(wrapper.vm.model.clientAuthenticatedSearch).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('reflects a pre-existing true value from the model', async() => {
|
|
345
|
+
await wrapper.setData({
|
|
346
|
+
model: {
|
|
347
|
+
...mockModel, id: 'keycloakoidc', clientAuthenticatedSearch: true
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(wrapper.vm.model.clientAuthenticatedSearch).toBe(true);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
300
354
|
});
|
|
301
355
|
});
|