@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.
Files changed (146) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +90 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +98 -0
  6. package/apis/intf/system.ts +41 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +54 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +33 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/assets/styles/global/_tooltip.scss +6 -1
  18. package/assets/translations/en-us.yaml +5 -0
  19. package/components/ActionMenuShell.vue +3 -1
  20. package/components/CruResource.vue +8 -1
  21. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  22. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  23. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
  24. package/components/LocaleSelector.vue +2 -2
  25. package/components/ModalManager.vue +11 -1
  26. package/components/Questions/__tests__/Yaml.test.ts +1 -1
  27. package/components/RelatedResources.vue +5 -0
  28. package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
  29. package/components/ResourceDetail/Masthead/latest.vue +23 -21
  30. package/components/ResourceDetail/index.vue +3 -0
  31. package/components/ResourceTable.vue +54 -21
  32. package/components/SlideInPanelManager.vue +16 -11
  33. package/components/SortableTable/THead.vue +2 -1
  34. package/components/SortableTable/index.vue +20 -2
  35. package/components/Tabbed/index.vue +37 -2
  36. package/components/__tests__/NamespaceFilter.test.ts +49 -0
  37. package/components/auth/SelectPrincipal.vue +4 -0
  38. package/components/auth/login/ldap.vue +3 -3
  39. package/components/fleet/FleetSecretSelector.vue +1 -1
  40. package/components/form/KeyValue.vue +1 -1
  41. package/components/form/NameNsDescription.vue +1 -1
  42. package/components/form/NodeScheduling.vue +2 -2
  43. package/components/form/ResourceTabs/composable.ts +2 -2
  44. package/components/form/ResourceTabs/index.vue +0 -2
  45. package/components/form/__tests__/NameNsDescription.test.ts +42 -0
  46. package/components/formatter/LinkName.vue +5 -0
  47. package/components/nav/Group.vue +25 -7
  48. package/components/nav/Header.vue +1 -1
  49. package/components/nav/NamespaceFilter.vue +1 -0
  50. package/components/nav/Type.vue +17 -6
  51. package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
  52. package/components/nav/__tests__/Type.test.ts +59 -0
  53. package/composables/cruResource.ts +27 -0
  54. package/composables/focusTrap.ts +3 -1
  55. package/composables/resourceDetail.ts +15 -0
  56. package/composables/useLabeledFormElement.ts +3 -4
  57. package/config/product/fleet.js +1 -1
  58. package/config/router/navigation-guards/clusters.js +3 -3
  59. package/config/router/navigation-guards/products.js +1 -1
  60. package/config/router/routes.js +1 -5
  61. package/core/__tests__/extension-manager-impl.test.js +437 -0
  62. package/core/extension-manager-impl.js +6 -27
  63. package/core/plugin-helpers.ts +2 -2
  64. package/core/plugin.ts +9 -1
  65. package/core/plugins-loader.js +2 -2
  66. package/core/types-provisioning.ts +4 -0
  67. package/core/types.ts +35 -0
  68. package/detail/provisioning.cattle.io.cluster.vue +8 -6
  69. package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
  70. package/dialog/MoveNamespaceDialog.vue +20 -4
  71. package/dialog/SearchDialog.vue +1 -0
  72. package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
  73. package/directives/__tests__/clean-tooltip.test.ts +298 -0
  74. package/directives/clean-tooltip.ts +234 -0
  75. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
  76. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +98 -1
  77. package/edit/fleet.cattle.io.helmop.vue +5 -0
  78. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  79. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  80. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
  81. package/edit/resources.cattle.io.restore.vue +1 -1
  82. package/edit/workload/Job.vue +2 -2
  83. package/edit/workload/index.vue +1 -1
  84. package/initialize/install-plugins.js +4 -5
  85. package/machine-config/azure.vue +1 -1
  86. package/machine-config/components/GCEImage.vue +1 -1
  87. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +16 -0
  88. package/models/chart.js +70 -74
  89. package/models/management.cattle.io.cluster.js +1 -1
  90. package/models/provisioning.cattle.io.cluster.js +11 -3
  91. package/package.json +7 -7
  92. package/pages/auth/login.vue +3 -3
  93. package/pages/auth/setup.vue +1 -1
  94. package/pages/auth/verify.vue +3 -3
  95. package/pages/c/_cluster/apps/charts/index.vue +122 -24
  96. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  97. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  98. package/pages/c/_cluster/fleet/index.vue +4 -7
  99. package/pages/c/_cluster/settings/index.vue +5 -0
  100. package/pkg/auto-import.js +3 -3
  101. package/pkg/dynamic-importer.lib.js +1 -1
  102. package/pkg/import.js +1 -1
  103. package/plugins/__tests__/mutations.tests.ts +179 -0
  104. package/plugins/dashboard-store/getters.js +1 -1
  105. package/plugins/dashboard-store/model-loader.js +1 -1
  106. package/plugins/dashboard-store/mutations.js +23 -2
  107. package/plugins/dashboard-store/resource-class.js +8 -3
  108. package/plugins/plugin.js +2 -2
  109. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
  110. package/plugins/steve/steve-class.js +1 -1
  111. package/plugins/steve/steve-pagination-utils.ts +108 -43
  112. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  113. package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
  114. package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
  115. package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
  116. package/scripts/publish-shell.sh +25 -0
  117. package/store/__tests__/catalog.test.ts +1 -1
  118. package/store/__tests__/type-map.test.ts +164 -2
  119. package/store/auth.js +23 -11
  120. package/store/i18n.js +3 -3
  121. package/store/index.js +5 -3
  122. package/store/notifications.ts +2 -0
  123. package/store/prefs.js +2 -2
  124. package/store/type-map.js +17 -7
  125. package/types/internal-api/shell/modal.d.ts +6 -6
  126. package/types/notifications/index.ts +126 -15
  127. package/types/rancher/index.d.ts +9 -0
  128. package/types/shell/index.d.ts +16 -1
  129. package/types/vue-shim.d.ts +5 -4
  130. package/utils/__tests__/router.test.js +238 -0
  131. package/utils/cluster.js +4 -1
  132. package/utils/fleet.ts +8 -1
  133. package/utils/pagination-utils.ts +2 -2
  134. package/utils/pagination-wrapper.ts +1 -1
  135. package/utils/router.js +50 -0
  136. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  137. package/vue.config.js +3 -3
  138. package/composables/useExtensionManager.ts +0 -17
  139. package/core/__test__/extension-manager-impl.test.js +0 -236
  140. package/core/plugins.js +0 -38
  141. package/directives/clean-tooltip.js +0 -32
  142. package/plugins/internal-api/index.ts +0 -37
  143. package/plugins/internal-api/shared/base-api.ts +0 -13
  144. package/plugins/internal-api/shell/shell.api.ts +0 -108
  145. package/types/internal-api/shell/growl.d.ts +0 -25
  146. 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
+ }
@@ -0,0 +1,11 @@
1
+ /* eslint-disable */
2
+ import { ShellApi, ExtensionManagerApi } from '@shell/apis';
3
+
4
+ export {};
5
+
6
+ declare module 'vue' {
7
+ interface ComponentCustomProperties {
8
+ $shell: ShellApi,
9
+ $extension: ExtensionManagerApi,
10
+ }
11
+ }
@@ -162,7 +162,12 @@
162
162
  }
163
163
 
164
164
  //icon tooltip
165
- .icon-info.v-popper--has-tooltip {
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: Object;
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 { useDefaultConfigTabProps, useDefaultYamlTabProps } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
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.dev;
48
+ return (this.availableLocales && Object.keys(this.availableLocales).length > 1) || this.showNone;
49
49
  },
50
50
 
51
51
  showNone() {
52
- return !!process.env.dev && this.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
- tabindex="0"
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"] .v-popper--has-tooltip');
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`],