@rancher/shell 3.0.8-rc.9 → 3.0.8
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/impl/apis.ts +61 -0
- package/apis/index.ts +40 -0
- package/apis/intf/modal.ts +90 -0
- package/apis/intf/shell.ts +36 -0
- package/apis/intf/slide-in.ts +98 -0
- package/apis/intf/system.ts +41 -0
- package/apis/shell/__tests__/modal.test.ts +80 -0
- package/apis/shell/__tests__/notifications.test.ts +71 -0
- package/apis/shell/__tests__/slide-in.test.ts +54 -0
- package/apis/shell/__tests__/system.test.ts +129 -0
- package/apis/shell/index.ts +38 -0
- package/apis/shell/modal.ts +41 -0
- package/apis/shell/notifications.ts +65 -0
- package/apis/shell/slide-in.ts +33 -0
- package/apis/shell/system.ts +65 -0
- package/apis/vue-shim.d.ts +11 -0
- package/assets/styles/global/_tooltip.scss +6 -1
- package/assets/translations/en-us.yaml +5 -0
- package/components/ActionMenuShell.vue +3 -1
- package/components/CruResource.vue +8 -1
- package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
- package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
- package/components/LocaleSelector.vue +2 -2
- package/components/ModalManager.vue +11 -1
- package/components/Questions/__tests__/Yaml.test.ts +1 -1
- package/components/RelatedResources.vue +5 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
- package/components/ResourceDetail/Masthead/latest.vue +23 -21
- package/components/ResourceDetail/index.vue +3 -0
- package/components/ResourceTable.vue +54 -21
- package/components/SlideInPanelManager.vue +16 -11
- package/components/SortableTable/THead.vue +2 -1
- package/components/SortableTable/index.vue +20 -2
- package/components/Tabbed/index.vue +37 -2
- package/components/__tests__/NamespaceFilter.test.ts +49 -0
- package/components/auth/SelectPrincipal.vue +4 -0
- package/components/auth/login/ldap.vue +3 -3
- package/components/fleet/FleetSecretSelector.vue +1 -1
- package/components/form/KeyValue.vue +1 -1
- package/components/form/NameNsDescription.vue +1 -1
- package/components/form/NodeScheduling.vue +2 -2
- package/components/form/ResourceTabs/composable.ts +2 -2
- package/components/form/ResourceTabs/index.vue +0 -2
- package/components/form/__tests__/NameNsDescription.test.ts +42 -0
- package/components/formatter/LinkName.vue +5 -0
- package/components/nav/Group.vue +25 -7
- package/components/nav/Header.vue +1 -1
- package/components/nav/NamespaceFilter.vue +1 -0
- package/components/nav/Type.vue +17 -6
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
- package/components/nav/__tests__/Type.test.ts +59 -0
- package/composables/cruResource.ts +27 -0
- package/composables/focusTrap.ts +3 -1
- package/composables/resourceDetail.ts +15 -0
- package/composables/useLabeledFormElement.ts +3 -4
- package/config/product/fleet.js +1 -1
- package/config/router/navigation-guards/clusters.js +3 -3
- package/config/router/navigation-guards/products.js +1 -1
- package/config/router/routes.js +1 -5
- package/core/__tests__/extension-manager-impl.test.js +437 -0
- package/core/extension-manager-impl.js +6 -27
- package/core/plugin-helpers.ts +2 -2
- package/core/plugin.ts +9 -1
- package/core/plugins-loader.js +2 -2
- package/core/types-provisioning.ts +4 -0
- package/core/types.ts +35 -0
- package/detail/provisioning.cattle.io.cluster.vue +8 -6
- package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
- package/dialog/MoveNamespaceDialog.vue +20 -4
- package/dialog/SearchDialog.vue +1 -0
- package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
- package/directives/__tests__/clean-tooltip.test.ts +298 -0
- package/directives/clean-tooltip.ts +234 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +98 -1
- package/edit/fleet.cattle.io.helmop.vue +5 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
- package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
- package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
- package/edit/resources.cattle.io.restore.vue +1 -1
- package/edit/workload/Job.vue +2 -2
- package/edit/workload/index.vue +1 -1
- package/initialize/install-plugins.js +4 -5
- package/machine-config/azure.vue +1 -1
- package/machine-config/components/GCEImage.vue +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +16 -0
- package/models/chart.js +70 -74
- package/models/management.cattle.io.cluster.js +1 -1
- package/models/provisioning.cattle.io.cluster.js +11 -3
- package/package.json +7 -7
- package/pages/auth/login.vue +3 -3
- package/pages/auth/setup.vue +1 -1
- package/pages/auth/verify.vue +3 -3
- package/pages/c/_cluster/apps/charts/index.vue +122 -24
- package/pages/c/_cluster/apps/charts/install.vue +33 -0
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
- package/pages/c/_cluster/fleet/index.vue +4 -7
- package/pages/c/_cluster/settings/index.vue +5 -0
- package/pkg/auto-import.js +3 -3
- package/pkg/dynamic-importer.lib.js +1 -1
- package/pkg/import.js +1 -1
- package/plugins/__tests__/mutations.tests.ts +179 -0
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/dashboard-store/model-loader.js +1 -1
- package/plugins/dashboard-store/mutations.js +23 -2
- package/plugins/dashboard-store/resource-class.js +8 -3
- package/plugins/plugin.js +2 -2
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
- package/plugins/steve/steve-class.js +1 -1
- package/plugins/steve/steve-pagination-utils.ts +108 -43
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
- package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
- package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
- package/scripts/publish-shell.sh +25 -0
- package/store/__tests__/catalog.test.ts +1 -1
- package/store/__tests__/type-map.test.ts +164 -2
- package/store/auth.js +23 -11
- package/store/i18n.js +3 -3
- package/store/index.js +5 -3
- package/store/notifications.ts +2 -0
- package/store/prefs.js +2 -2
- package/store/type-map.js +17 -7
- package/types/internal-api/shell/modal.d.ts +6 -6
- package/types/notifications/index.ts +126 -15
- package/types/rancher/index.d.ts +9 -0
- package/types/shell/index.d.ts +16 -1
- package/types/vue-shim.d.ts +5 -4
- package/utils/__tests__/router.test.js +238 -0
- package/utils/cluster.js +4 -1
- package/utils/fleet.ts +8 -1
- package/utils/pagination-utils.ts +2 -2
- package/utils/pagination-wrapper.ts +1 -1
- package/utils/router.js +50 -0
- package/utils/unit-tests/pagination-utils.spec.ts +8 -8
- package/vue.config.js +3 -3
- package/composables/useExtensionManager.ts +0 -17
- package/core/__test__/extension-manager-impl.test.js +0 -236
- package/core/plugins.js +0 -38
- package/directives/clean-tooltip.js +0 -32
- package/plugins/internal-api/index.ts +0 -37
- package/plugins/internal-api/shared/base-api.ts +0 -13
- package/plugins/internal-api/shell/shell.api.ts +0 -108
- package/types/internal-api/shell/growl.d.ts +0 -25
- package/types/internal-api/shell/slideIn.d.ts +0 -15
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// system.test.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
describe, it, expect, jest, beforeEach
|
|
5
|
+
} from '@jest/globals';
|
|
6
|
+
import { SystemApiImpl } from '../system';
|
|
7
|
+
import { Store } from 'vuex';
|
|
8
|
+
|
|
9
|
+
// --- Mock the external modules ---
|
|
10
|
+
|
|
11
|
+
// 1. Import the functions we need to mock
|
|
12
|
+
import { getVersionData, getKubeVersionData, isRancherPrime } from '@shell/config/version';
|
|
13
|
+
import { isDevBuild, isPrerelease } from '@shell/utils/version';
|
|
14
|
+
|
|
15
|
+
// 2. Tell Jest to mock the entire module
|
|
16
|
+
// We provide a factory function () => ({ ... }) to define the mock implementation.
|
|
17
|
+
jest.mock('@shell/config/version', () => ({
|
|
18
|
+
getVersionData: jest.fn(),
|
|
19
|
+
getKubeVersionData: jest.fn(),
|
|
20
|
+
isRancherPrime: jest.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
jest.mock('@shell/utils/version', () => ({
|
|
24
|
+
isDevBuild: jest.fn(),
|
|
25
|
+
isPrerelease: jest.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// 3. Cast the imported functions to Jest Mocks for type-safety
|
|
29
|
+
const mockGetVersionData = getVersionData as jest.Mock;
|
|
30
|
+
const mockGetKubeVersionData = getKubeVersionData as jest.Mock;
|
|
31
|
+
const mockIsRancherPrime = isRancherPrime as jest.Mock;
|
|
32
|
+
const mockIsDevBuild = isDevBuild as jest.Mock;
|
|
33
|
+
const mockIsPrerelease = isPrerelease as jest.Mock;
|
|
34
|
+
|
|
35
|
+
// --- End of Mocking Setup ---
|
|
36
|
+
|
|
37
|
+
describe('systemApiImpl', () => {
|
|
38
|
+
let mockStore: Store<any>;
|
|
39
|
+
let systemApi: SystemApiImpl;
|
|
40
|
+
let mockGetter: jest.Mock;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
// Reset all mocks before each test
|
|
44
|
+
mockGetVersionData.mockClear();
|
|
45
|
+
mockGetKubeVersionData.mockClear();
|
|
46
|
+
mockIsRancherPrime.mockClear();
|
|
47
|
+
mockIsDevBuild.mockClear();
|
|
48
|
+
mockIsPrerelease.mockClear();
|
|
49
|
+
|
|
50
|
+
// Arrange: Mock the store
|
|
51
|
+
mockGetter = jest.fn() as any;
|
|
52
|
+
mockStore = {
|
|
53
|
+
getters: { 'management/byId': mockGetter },
|
|
54
|
+
$config: { dashboardVersion: 'v2.9-test' }
|
|
55
|
+
} as any;
|
|
56
|
+
|
|
57
|
+
// Arrange: Instantiate the class
|
|
58
|
+
systemApi = new SystemApiImpl(mockStore);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// --- Tests for Store-based Getters ---
|
|
62
|
+
|
|
63
|
+
it('should get uiVersion from store.$config', () => {
|
|
64
|
+
expect(systemApi.uiVersion).toBe('v2.9-test');
|
|
65
|
+
});
|
|
66
|
+
// --- Tests for Mocked Module Getters ---
|
|
67
|
+
|
|
68
|
+
it('should get rancherVersion and gitCommit from getVersionData', () => {
|
|
69
|
+
// Arrange: Define the return value for this test
|
|
70
|
+
mockGetVersionData.mockReturnValue({ Version: 'v2.8.0', GitCommit: 'abc1234' });
|
|
71
|
+
|
|
72
|
+
// Act & Assert
|
|
73
|
+
expect(systemApi.rancherVersion).toBe('v2.8.0');
|
|
74
|
+
expect(systemApi.gitCommit).toBe('abc1234');
|
|
75
|
+
expect(mockGetVersionData).toHaveBeenCalledTimes(2); // Called for both getters
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should get kubernetesVersion and buildPlatform from getKubeVersionData', () => {
|
|
79
|
+
// Arrange
|
|
80
|
+
mockGetKubeVersionData.mockReturnValue({ gitVersion: 'v1.25.0', platform: 'linux/amd64' });
|
|
81
|
+
|
|
82
|
+
// Act & Assert
|
|
83
|
+
expect(systemApi.kubernetesVersion).toBe('v1.25.0');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should handle undefined kubeData gracefully', () => {
|
|
87
|
+
// Arrange: Simulate getKubeVersionData returning nothing
|
|
88
|
+
mockGetKubeVersionData.mockReturnValue(undefined);
|
|
89
|
+
|
|
90
|
+
// Act & Assert (The code's `|| {}` handles this)
|
|
91
|
+
expect(systemApi.kubernetesVersion).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should get isRancherPrime from isRancherPrime', () => {
|
|
95
|
+
// Arrange
|
|
96
|
+
mockIsRancherPrime.mockReturnValue(true);
|
|
97
|
+
|
|
98
|
+
// Act & Assert
|
|
99
|
+
expect(systemApi.isRancherPrime).toBe(true);
|
|
100
|
+
expect(mockIsRancherPrime).toHaveBeenCalledTimes(1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should call isDevBuild with the correct rancherVersion', () => {
|
|
104
|
+
// Arrange
|
|
105
|
+
mockGetVersionData.mockReturnValue({ Version: 'v2.8.0-dev', GitCommit: '...' });
|
|
106
|
+
mockIsDevBuild.mockReturnValue(true); // The logic we are testing
|
|
107
|
+
|
|
108
|
+
// Act
|
|
109
|
+
const result = systemApi.isDevBuild;
|
|
110
|
+
|
|
111
|
+
// Assert
|
|
112
|
+
expect(result).toBe(true);
|
|
113
|
+
// This is the key check: verify it passed its OWN version to the util
|
|
114
|
+
expect(mockIsDevBuild).toHaveBeenCalledWith('v2.8.0-dev');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should call isPrereleaseVersion with the correct rancherVersion', () => {
|
|
118
|
+
// Arrange
|
|
119
|
+
mockGetVersionData.mockReturnValue({ Version: 'v2.8.0-rc1', GitCommit: '...' });
|
|
120
|
+
mockIsPrerelease.mockReturnValue(true);
|
|
121
|
+
|
|
122
|
+
// Act
|
|
123
|
+
const result = systemApi.isPrereleaseVersion;
|
|
124
|
+
|
|
125
|
+
// Assert
|
|
126
|
+
expect(result).toBe(true);
|
|
127
|
+
expect(mockIsPrerelease).toHaveBeenCalledWith('v2.8.0-rc1');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Store } from 'vuex';
|
|
2
|
+
import {
|
|
3
|
+
ModalApi, ShellApi, SlideInApi, NotificationApi, SystemApi
|
|
4
|
+
} from '@shell/apis/intf/shell';
|
|
5
|
+
import { ModalApiImpl } from './modal';
|
|
6
|
+
import { SlideInApiImpl } from './slide-in';
|
|
7
|
+
import { NotificationApiImpl } from './notifications';
|
|
8
|
+
import { SystemApiImpl } from './system';
|
|
9
|
+
|
|
10
|
+
export class ShellApiImpl implements ShellApi {
|
|
11
|
+
private modalApi: ModalApi;
|
|
12
|
+
private slideInApi: SlideInApi;
|
|
13
|
+
private notificationApi: NotificationApi;
|
|
14
|
+
private systemApi: SystemApi;
|
|
15
|
+
|
|
16
|
+
constructor(store: Store<any>) {
|
|
17
|
+
this.modalApi = new ModalApiImpl(store);
|
|
18
|
+
this.slideInApi = new SlideInApiImpl(store);
|
|
19
|
+
this.notificationApi = new NotificationApiImpl(store);
|
|
20
|
+
this.systemApi = new SystemApiImpl(store);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get modal(): ModalApi {
|
|
24
|
+
return this.modalApi;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get slideIn(): SlideInApi {
|
|
28
|
+
return this.slideInApi;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get notification(): NotificationApi {
|
|
32
|
+
return this.notificationApi;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get system(): SystemApi {
|
|
36
|
+
return this.systemApi;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Component } from 'vue';
|
|
2
|
+
import { ModalApi, ModalConfig } from '@shell/apis/intf/shell';
|
|
3
|
+
import { Store } from 'vuex';
|
|
4
|
+
|
|
5
|
+
export class ModalApiImpl implements ModalApi {
|
|
6
|
+
private store: Store<any>;
|
|
7
|
+
|
|
8
|
+
constructor(store: Store<any>) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Opens a modal by committing to the Vuex store.
|
|
14
|
+
*
|
|
15
|
+
* Example:
|
|
16
|
+
* ```ts
|
|
17
|
+
* import MyCustomModal from '@/components/MyCustomModal.vue';
|
|
18
|
+
*
|
|
19
|
+
* this.$shell.modal.open(MyCustomModal, {
|
|
20
|
+
* props: { title: 'Hello Modal' }
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @param component
|
|
25
|
+
* The Vue component to be displayed inside the modal.
|
|
26
|
+
* This can be any SFC (Single-File Component) imported and passed in as a `Component`.
|
|
27
|
+
*
|
|
28
|
+
* @param config Configuration object for opening a modal.
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
31
|
+
public open(component: Component, config?: ModalConfig): void {
|
|
32
|
+
this.store.commit('modal/openModal', {
|
|
33
|
+
component,
|
|
34
|
+
componentProps: config?.props || {},
|
|
35
|
+
resources: config?.resources || [],
|
|
36
|
+
modalWidth: config?.width || '600px',
|
|
37
|
+
closeOnClickOutside: config?.closeOnClickOutside ?? true,
|
|
38
|
+
// modalSticky: config.modalSticky ?? false // Not implemented yet
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Store } from 'vuex';
|
|
2
|
+
import { NotificationApi } from '@shell/apis/intf/shell';
|
|
3
|
+
import { NotificationLevel, NotificationConfig } from '@shell/types/notifications';
|
|
4
|
+
|
|
5
|
+
export class NotificationApiImpl implements NotificationApi {
|
|
6
|
+
private store: Store<any>;
|
|
7
|
+
|
|
8
|
+
constructor(store: Store<any>) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Sends a notification to the Rancher UI Notification Center
|
|
14
|
+
*
|
|
15
|
+
* Example:
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { NotificationLevel } from '@shell/types/notifications';
|
|
18
|
+
*
|
|
19
|
+
* this.$shell.notification.send(NotificationLevel.Success, 'Some notification title', 'Hello world! Success!', {})
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* For usage with the Composition API check usage guide [here](../../shell-api#using-composition-api-in-vue).
|
|
23
|
+
*
|
|
24
|
+
* @param level The `level` specifies the importance of the notification and determines the icon that is shown in the notification
|
|
25
|
+
* @param title The notification title
|
|
26
|
+
* @param message The notification message to be displayed
|
|
27
|
+
* @param config Notifications configuration object
|
|
28
|
+
*
|
|
29
|
+
* * @returns notification ID
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
public async send(level: NotificationLevel, title: string, message?:string, config?: NotificationConfig): Promise<string> {
|
|
33
|
+
const notification = {
|
|
34
|
+
level,
|
|
35
|
+
title,
|
|
36
|
+
message,
|
|
37
|
+
...config
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return await this.store.dispatch('notifications/add', notification, { root: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Update notification progress (Only valid for notifications of type `Task`)
|
|
45
|
+
*
|
|
46
|
+
* Example:
|
|
47
|
+
* ```ts
|
|
48
|
+
* this.$shell.notification.updateProgress('some-notification-id', 80)
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* For usage with the Composition API check usage guide [here](../../shell-api#using-composition-api-in-vue).
|
|
52
|
+
*
|
|
53
|
+
* @param notificationId Unique ID for the notification
|
|
54
|
+
* @param progress Progress (0-100) for notifications of type `Task`
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
public updateProgress(notificationId: string, progress: number): void {
|
|
58
|
+
const notification = {
|
|
59
|
+
id: notificationId,
|
|
60
|
+
progress
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
this.store.dispatch('notifications/update', notification, { root: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Component } from 'vue';
|
|
2
|
+
import { SlideInApi, SlideInConfig } from '@shell/apis/intf/shell';
|
|
3
|
+
import { Store } from 'vuex';
|
|
4
|
+
|
|
5
|
+
export class SlideInApiImpl implements SlideInApi {
|
|
6
|
+
private store: Store<any>;
|
|
7
|
+
|
|
8
|
+
constructor(store: Store<any>) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Opens a slide in panel in Rancher UI
|
|
14
|
+
*
|
|
15
|
+
* Example:
|
|
16
|
+
* ```ts
|
|
17
|
+
* import MyCustomSlideIn from '@/components/MyCustomSlideIn.vue';
|
|
18
|
+
*
|
|
19
|
+
* this.$shell.slideIn.open(MyCustomSlideIn, {
|
|
20
|
+
* title: 'Hello from SlideIn panel!'
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @param component - A Vue component (imported SFC, functional component, etc.) to be rendered in the panel.
|
|
25
|
+
* @param config - Slide-In configuration object
|
|
26
|
+
*/
|
|
27
|
+
public open(component: Component, config?: SlideInConfig): void {
|
|
28
|
+
this.store.commit('slideInPanel/open', {
|
|
29
|
+
component,
|
|
30
|
+
componentProps: { ...config || {} }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SystemApi } from '@shell/apis/intf/shell';
|
|
2
|
+
import { Store } from 'vuex';
|
|
3
|
+
import { isDevBuild, isPrerelease } from '@shell/utils/version';
|
|
4
|
+
import { getVersionData, getKubeVersionData, isRancherPrime } from '@shell/config/version';
|
|
5
|
+
|
|
6
|
+
export class SystemApiImpl implements SystemApi {
|
|
7
|
+
private store: Store<any>;
|
|
8
|
+
|
|
9
|
+
constructor(store: Store<any>) {
|
|
10
|
+
this.store = store;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Rancher version
|
|
15
|
+
*/
|
|
16
|
+
get rancherVersion(): string {
|
|
17
|
+
return getVersionData().Version;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Rancher UI version
|
|
22
|
+
*/
|
|
23
|
+
get uiVersion(): string {
|
|
24
|
+
const storeTyped = this.store as any;
|
|
25
|
+
|
|
26
|
+
return storeTyped.$config.dashboardVersion;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* If Rancher system running is Prime
|
|
31
|
+
*/
|
|
32
|
+
get isRancherPrime(): boolean {
|
|
33
|
+
return isRancherPrime();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Git Commit for Rancher system running
|
|
38
|
+
*/
|
|
39
|
+
get gitCommit(): string {
|
|
40
|
+
return getVersionData().GitCommit;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Rancher Kubernetes version
|
|
45
|
+
*/
|
|
46
|
+
get kubernetesVersion(): string {
|
|
47
|
+
const kubeData = getKubeVersionData() as any || {};
|
|
48
|
+
|
|
49
|
+
return kubeData.gitVersion;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* If Rancher system is a Dev build
|
|
54
|
+
*/
|
|
55
|
+
get isDevBuild(): boolean {
|
|
56
|
+
return isDevBuild(this.rancherVersion);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* If Rancher system is a Pre-Release build/version
|
|
61
|
+
*/
|
|
62
|
+
get isPrereleaseVersion(): boolean {
|
|
63
|
+
return isPrerelease(this.rancherVersion);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -162,7 +162,12 @@
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
//icon tooltip
|
|
165
|
-
.icon-info.
|
|
165
|
+
.icon-info.has-clean-tooltip {
|
|
166
166
|
font-size: 14px;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// making sure hovering on the tooltip's arrow part does not cause flickering
|
|
170
|
+
.v-popper__arrow-container {
|
|
171
|
+
pointer-events: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
@@ -1731,6 +1731,7 @@ cluster:
|
|
|
1731
1731
|
addVolume: Add Volume
|
|
1732
1732
|
addVMImage: Add VM Image
|
|
1733
1733
|
storageClass: Storage Class
|
|
1734
|
+
reservedMemory: Reserved Memory
|
|
1734
1735
|
sshUser: SSH User
|
|
1735
1736
|
userData:
|
|
1736
1737
|
label: User Data Template
|
|
@@ -1776,6 +1777,8 @@ cluster:
|
|
|
1776
1777
|
placeholder: e.g. 2
|
|
1777
1778
|
memory:
|
|
1778
1779
|
placeholder: e.g. 4
|
|
1780
|
+
reservedMemory:
|
|
1781
|
+
placeholder: e.g. 256
|
|
1779
1782
|
disk:
|
|
1780
1783
|
placeholder: e.g. 4
|
|
1781
1784
|
namespace:
|
|
@@ -1805,6 +1808,7 @@ cluster:
|
|
|
1805
1808
|
current: (current)
|
|
1806
1809
|
experimental: (experimental)
|
|
1807
1810
|
deprecated: (deprecated)
|
|
1811
|
+
manual: (manually upgraded)
|
|
1808
1812
|
deprecatedPatches: Show deprecated Kubernetes patch versions
|
|
1809
1813
|
deprecatedPatchWarning: We recommend using the latest patch version for each minor Kubernetes version. Deprecated patch versions can be useful for migration purposes.
|
|
1810
1814
|
legacyWarning: The legacy feature flag is enabled and not all legacy features are supported in Kubernetes 1.21+.
|
|
@@ -4617,6 +4621,7 @@ moveModal:
|
|
|
4617
4621
|
description: 'You are moving the following namespaces:'
|
|
4618
4622
|
moveButtonLabel: Move
|
|
4619
4623
|
targetProject: Target Project
|
|
4624
|
+
noProject: None (remove from any project)
|
|
4620
4625
|
|
|
4621
4626
|
nameNsDescription:
|
|
4622
4627
|
name:
|
|
@@ -8,6 +8,8 @@ import { RcDropdownMenu } from '@components/RcDropdown';
|
|
|
8
8
|
import { ButtonRoleProps, ButtonSizeProps } from '@components/RcButton/types';
|
|
9
9
|
import { DropdownOption } from '@components/RcDropdown/types';
|
|
10
10
|
|
|
11
|
+
defineOptions({ inheritAttrs: false });
|
|
12
|
+
|
|
11
13
|
const store = useStore();
|
|
12
14
|
|
|
13
15
|
type RcDropdownMenuComponentProps = {
|
|
@@ -16,7 +18,7 @@ type RcDropdownMenuComponentProps = {
|
|
|
16
18
|
buttonAriaLabel?: string;
|
|
17
19
|
dropdownAriaLabel?: string;
|
|
18
20
|
dataTestid?: string;
|
|
19
|
-
resource
|
|
21
|
+
resource?: Object;
|
|
20
22
|
customActions?: DropdownOption[];
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -9,9 +9,10 @@ import AsyncButton from '@shell/components/AsyncButton';
|
|
|
9
9
|
import { mapGetters, mapState, mapActions } from 'vuex';
|
|
10
10
|
import { stringify, exceptionToErrorsArray } from '@shell/utils/error';
|
|
11
11
|
import CruResourceFooter from '@shell/components/CruResourceFooter';
|
|
12
|
+
import { useResourceCreatePageProvider, useResourceEditPageProvider } from '@shell/composables/cruResource';
|
|
12
13
|
|
|
13
14
|
import {
|
|
14
|
-
_EDIT, _VIEW, AS, _YAML, _UNFLAG, SUB_TYPE
|
|
15
|
+
_EDIT, _VIEW, AS, _YAML, _UNFLAG, SUB_TYPE, _CREATE
|
|
15
16
|
} from '@shell/config/query-params';
|
|
16
17
|
|
|
17
18
|
import { BEFORE_SAVE_HOOKS } from '@shell/mixins/child-hook';
|
|
@@ -168,6 +169,12 @@ export default {
|
|
|
168
169
|
const inStore = this.$store.getters['currentStore'](this.resource);
|
|
169
170
|
const schema = this.$store.getters[`${ inStore }/schemaFor`](this.resource.type);
|
|
170
171
|
|
|
172
|
+
if (this.mode === _CREATE) {
|
|
173
|
+
useResourceCreatePageProvider();
|
|
174
|
+
} else if (this.mode === _EDIT) {
|
|
175
|
+
useResourceEditPageProvider();
|
|
176
|
+
}
|
|
177
|
+
|
|
171
178
|
return {
|
|
172
179
|
isCancelModal: false,
|
|
173
180
|
showAsForm: this.$route.query[AS] !== _YAML,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { provide, inject } from 'vue';
|
|
2
|
+
import { useDefaultConfigTabProps, useDefaultYamlTabProps, useResourceDetailDrawerProvider, useIsInResourceDetailDrawer } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
|
|
2
3
|
import * as helpers from '@shell/components/Drawer/ResourceDetailDrawer/helpers';
|
|
3
4
|
import * as vuex from 'vuex';
|
|
4
5
|
|
|
@@ -6,6 +7,11 @@ jest.mock('@shell/components/Drawer/ResourceDetailDrawer/helpers');
|
|
|
6
7
|
jest.mock('vuex');
|
|
7
8
|
jest.mock('@shell/composables/drawer');
|
|
8
9
|
jest.mock('@shell/components/Drawer/ResourceDetailDrawer/index.vue', () => ({ name: 'ResourceDetailDrawer' } as any));
|
|
10
|
+
jest.mock('vue', () => ({
|
|
11
|
+
...jest.requireActual('vue'),
|
|
12
|
+
provide: jest.fn(),
|
|
13
|
+
inject: jest.fn()
|
|
14
|
+
}));
|
|
9
15
|
|
|
10
16
|
describe('composables: ResourceDetailDrawer', () => {
|
|
11
17
|
const resource = { type: 'RESOURCE' };
|
|
@@ -78,4 +84,47 @@ describe('composables: ResourceDetailDrawer', () => {
|
|
|
78
84
|
expect(props?.resource).toStrictEqual(resource);
|
|
79
85
|
});
|
|
80
86
|
});
|
|
87
|
+
|
|
88
|
+
describe('useResourceDetailDrawerProvider', () => {
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
jest.clearAllMocks();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should call provide with the correct key and value', () => {
|
|
94
|
+
useResourceDetailDrawerProvider();
|
|
95
|
+
|
|
96
|
+
expect(provide).toHaveBeenCalledWith('isInResourceDetailDrawerKey', true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('useIsInResourceDetailDrawer', () => {
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
jest.clearAllMocks();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should call inject with the correct key and default value', () => {
|
|
106
|
+
(inject as jest.Mock).mockReturnValue(false);
|
|
107
|
+
|
|
108
|
+
const result = useIsInResourceDetailDrawer();
|
|
109
|
+
|
|
110
|
+
expect(inject).toHaveBeenCalledWith('isInResourceDetailDrawerKey', false);
|
|
111
|
+
expect(result).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should return true when inside a ResourceDetailDrawer', () => {
|
|
115
|
+
(inject as jest.Mock).mockReturnValue(true);
|
|
116
|
+
|
|
117
|
+
const result = useIsInResourceDetailDrawer();
|
|
118
|
+
|
|
119
|
+
expect(result).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should return false when not inside a ResourceDetailDrawer', () => {
|
|
123
|
+
(inject as jest.Mock).mockReturnValue(false);
|
|
124
|
+
|
|
125
|
+
const result = useIsInResourceDetailDrawer();
|
|
126
|
+
|
|
127
|
+
expect(result).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
81
130
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useStore } from 'vuex';
|
|
2
2
|
import { getYaml } from '@shell/components/Drawer/ResourceDetailDrawer/helpers';
|
|
3
3
|
import { ConfigProps, YamlProps } from '@shell/components/Drawer/ResourceDetailDrawer/types';
|
|
4
|
+
import { inject, provide } from 'vue';
|
|
4
5
|
|
|
5
6
|
export async function useDefaultYamlTabProps(resource: any): Promise<YamlProps> {
|
|
6
7
|
const yaml = await getYaml(resource);
|
|
@@ -27,3 +28,21 @@ export function useDefaultConfigTabProps(resource: any): ConfigProps | undefined
|
|
|
27
28
|
resourceType: resource.type
|
|
28
29
|
};
|
|
29
30
|
}
|
|
31
|
+
|
|
32
|
+
const IS_IN_RESOURCE_DETAIL_DRAWER_KEY = 'isInResourceDetailDrawerKey';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Used to add a provide method which will indicate to all ancestors that they're inside the ResourceDetailDrawer. This is useful because we show
|
|
36
|
+
* config page components both independently and within the ResourceDetailDrawer and we sometimes want to distinguish between the two use cases.
|
|
37
|
+
*/
|
|
38
|
+
export function useResourceDetailDrawerProvider() {
|
|
39
|
+
provide(IS_IN_RESOURCE_DETAIL_DRAWER_KEY, true);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A composable used to determine if the current component was instantiated as an ancestor of a ResourceDetailDrawer.
|
|
44
|
+
* @returns true if the component is an ancestor of ResourceDetailDrawer, otherwise false
|
|
45
|
+
*/
|
|
46
|
+
export function useIsInResourceDetailDrawer() {
|
|
47
|
+
return inject(IS_IN_RESOURCE_DETAIL_DRAWER_KEY, false);
|
|
48
|
+
}
|
|
@@ -4,7 +4,7 @@ import { useI18n } from '@shell/composables/useI18n';
|
|
|
4
4
|
import { useStore } from 'vuex';
|
|
5
5
|
import Tabbed from '@shell/components/Tabbed/index.vue';
|
|
6
6
|
import YamlTab, { Props as YamlProps } from '@shell/components/Drawer/ResourceDetailDrawer/YamlTab.vue';
|
|
7
|
-
import { useDefaultConfigTabProps, useDefaultYamlTabProps } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
|
|
7
|
+
import { useDefaultConfigTabProps, useDefaultYamlTabProps, useResourceDetailDrawerProvider } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
|
|
8
8
|
import ConfigTab from '@shell/components/Drawer/ResourceDetailDrawer/ConfigTab.vue';
|
|
9
9
|
import { computed, ref } from 'vue';
|
|
10
10
|
import RcButton from '@components/RcButton/RcButton.vue';
|
|
@@ -54,6 +54,8 @@ const canEdit = computed(() => {
|
|
|
54
54
|
return isConfig.value ? props.resource.canEdit : props.resource.canEditYaml;
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
useResourceDetailDrawerProvider();
|
|
58
|
+
|
|
57
59
|
</script>
|
|
58
60
|
<template>
|
|
59
61
|
<Drawer
|
|
@@ -45,11 +45,11 @@ export default {
|
|
|
45
45
|
},
|
|
46
46
|
|
|
47
47
|
showLocale() {
|
|
48
|
-
return (this.availableLocales && Object.keys(this.availableLocales).length > 1) || this.
|
|
48
|
+
return (this.availableLocales && Object.keys(this.availableLocales).length > 1) || this.showNone;
|
|
49
49
|
},
|
|
50
50
|
|
|
51
51
|
showNone() {
|
|
52
|
-
return !!process.env.dev
|
|
52
|
+
return !!process.env.dev;
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
|
|
@@ -5,6 +5,7 @@ import { useStore } from 'vuex';
|
|
|
5
5
|
import AppModal from '@shell/components/AppModal.vue';
|
|
6
6
|
|
|
7
7
|
const store = useStore();
|
|
8
|
+
const componentRendered = ref(false);
|
|
8
9
|
|
|
9
10
|
const isOpen = computed(() => store.getters['modal/isOpen']);
|
|
10
11
|
const component = computed(() => store.getters['modal/component']);
|
|
@@ -23,12 +24,20 @@ function close() {
|
|
|
23
24
|
backgroundClosing.value();
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
componentRendered.value = false;
|
|
26
28
|
store.commit('modal/closeModal');
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function registerBackgroundClosing(fn: Function) {
|
|
30
32
|
backgroundClosing.value = fn;
|
|
31
33
|
}
|
|
34
|
+
|
|
35
|
+
function onSlotComponentMounted() {
|
|
36
|
+
// variable for the watcher based focus-trap
|
|
37
|
+
// so that we know when the component is rendered
|
|
38
|
+
// works in tandem with trigger-focus-trap="true"
|
|
39
|
+
componentRendered.value = true;
|
|
40
|
+
}
|
|
32
41
|
</script>
|
|
33
42
|
|
|
34
43
|
<template>
|
|
@@ -39,7 +48,7 @@ function registerBackgroundClosing(fn: Function) {
|
|
|
39
48
|
:width="modalWidth"
|
|
40
49
|
:style="{ '--prompt-modal-width': modalWidth }"
|
|
41
50
|
:trigger-focus-trap="true"
|
|
42
|
-
|
|
51
|
+
:focus-trap-watcher-based-variable="componentRendered"
|
|
43
52
|
@close="close"
|
|
44
53
|
>
|
|
45
54
|
<component
|
|
@@ -48,6 +57,7 @@ function registerBackgroundClosing(fn: Function) {
|
|
|
48
57
|
data-testid="modal-manager-component"
|
|
49
58
|
:resources="resources"
|
|
50
59
|
:register-background-closing="registerBackgroundClosing"
|
|
60
|
+
@vue:mounted="onSlotComponentMounted"
|
|
51
61
|
@close="close"
|
|
52
62
|
/>
|
|
53
63
|
</app-modal>
|
|
@@ -118,7 +118,7 @@ describe('the yaml Component', () => {
|
|
|
118
118
|
|
|
119
119
|
expect(inputFields).toHaveLength(1);
|
|
120
120
|
|
|
121
|
-
const labelFields = wrapper.findAll('[data-testid="yaml-row-var_name"] .
|
|
121
|
+
const labelFields = wrapper.findAll('[data-testid="yaml-row-var_name"] .has-clean-tooltip');
|
|
122
122
|
|
|
123
123
|
expect(labelFields).toHaveLength(1);
|
|
124
124
|
});
|
|
@@ -102,6 +102,11 @@ export default {
|
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
// Having an undefined param can yield a console warning like [Vue Router warn]: Discarded invalid param(s) "namespace" when navigating
|
|
106
|
+
if (!detailLocation.params.namespace) {
|
|
107
|
+
delete detailLocation.params.namespace;
|
|
108
|
+
}
|
|
109
|
+
|
|
105
110
|
out.push({
|
|
106
111
|
type,
|
|
107
112
|
id: r[`${ this.direction }Id`],
|