@rancher/shell 3.0.5-rc.8 → 3.0.5

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 (199) hide show
  1. package/assets/styles/base/_color.scss +4 -1
  2. package/assets/styles/global/_tooltip.scss +7 -4
  3. package/assets/styles/themes/_dark.scss +11 -0
  4. package/assets/styles/themes/_light.scss +13 -1
  5. package/assets/styles/themes/_modern.scss +22 -0
  6. package/assets/translations/en-us.yaml +147 -19
  7. package/assets/translations/zh-hans.yaml +0 -1
  8. package/chart/monitoring/grafana/index.vue +8 -2
  9. package/components/ActionMenuShell.vue +3 -1
  10. package/components/Cron/CronExpressionEditor.vue +299 -0
  11. package/components/Cron/CronExpressionEditorModal.vue +247 -0
  12. package/components/Cron/CronTooltip.vue +87 -0
  13. package/components/Cron/types.ts +13 -0
  14. package/components/ForceDirectedTreeChart/composable.ts +11 -0
  15. package/components/PodSecurityAdmission.vue +2 -0
  16. package/components/PromptModal.vue +1 -1
  17. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
  18. package/components/Resource/Detail/CopyToClipboard.vue +78 -0
  19. package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
  20. package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
  21. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
  22. package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
  23. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
  24. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
  25. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +24 -38
  26. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
  27. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
  28. package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
  29. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
  30. package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
  31. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
  32. package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
  33. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +0 -3
  34. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
  35. package/components/Resource/Detail/Metadata/composables.ts +1 -4
  36. package/components/Resource/Detail/Metadata/index.vue +1 -0
  37. package/components/Resource/Detail/Preview/Content.vue +63 -0
  38. package/components/Resource/Detail/Preview/Preview.vue +128 -0
  39. package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
  40. package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
  41. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
  42. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
  43. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
  44. package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
  45. package/components/Resource/Detail/SpacedRow.vue +1 -0
  46. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +0 -5
  47. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
  48. package/components/Resource/Detail/TitleBar/composables.ts +1 -3
  49. package/components/Resource/Detail/TitleBar/index.vue +2 -29
  50. package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
  51. package/components/Resource/Detail/ViewOptions/index.vue +41 -0
  52. package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
  53. package/components/ResourceDetail/Masthead/legacy.vue +0 -19
  54. package/components/ResourceDetail/index.vue +1 -26
  55. package/components/ResourceTable.vue +24 -0
  56. package/components/SortableTable/index.vue +7 -1
  57. package/components/SortableTable/paging.js +3 -0
  58. package/components/Tabbed/Tab.vue +43 -1
  59. package/components/Tabbed/index.vue +3 -1
  60. package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
  61. package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
  62. package/components/auth/login/saml.vue +86 -0
  63. package/components/form/LabeledSelect.vue +8 -8
  64. package/components/form/ProjectMemberEditor.vue +2 -0
  65. package/components/form/ResourceTabs/composable.ts +54 -0
  66. package/components/form/ResourceTabs/index.vue +10 -7
  67. package/components/form/Select.vue +13 -10
  68. package/components/form/__tests__/LabeledSelect.test.ts +133 -0
  69. package/components/form/__tests__/Select.test.ts +134 -0
  70. package/components/nav/Header.vue +6 -5
  71. package/composables/useExtensionManager.ts +17 -0
  72. package/config/home-links.js +12 -0
  73. package/config/labels-annotations.js +0 -1
  74. package/config/page-actions.js +0 -1
  75. package/config/product/explorer.js +3 -1
  76. package/config/product/fleet.js +2 -7
  77. package/config/product/manager.js +0 -5
  78. package/config/query-params.js +1 -0
  79. package/config/router/navigation-guards/clusters.js +2 -1
  80. package/config/router/navigation-guards/products.js +1 -1
  81. package/config/store.js +2 -0
  82. package/core/extension-manager-impl.js +518 -0
  83. package/core/plugins.js +35 -468
  84. package/core/types.ts +8 -2
  85. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
  86. package/detail/catalog.cattle.io.app.vue +7 -4
  87. package/detail/fleet.cattle.io.bundle.vue +1 -5
  88. package/detail/fleet.cattle.io.cluster.vue +3 -2
  89. package/detail/fleet.cattle.io.gitrepo.vue +76 -49
  90. package/detail/fleet.cattle.io.helmop.vue +78 -49
  91. package/dialog/AddonConfigConfirmationDialog.vue +1 -1
  92. package/dialog/GenericPrompt.vue +1 -1
  93. package/dialog/ImportDialog.vue +9 -2
  94. package/dialog/InstallExtensionDialog.vue +18 -10
  95. package/dialog/SloDialog.vue +1 -1
  96. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
  97. package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
  98. package/edit/auth/oidc.vue +106 -6
  99. package/edit/auth/saml.vue +5 -5
  100. package/edit/cloudcredential.vue +31 -17
  101. package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
  102. package/edit/fleet.cattle.io.cluster.vue +19 -0
  103. package/edit/fleet.cattle.io.gitrepo.vue +23 -16
  104. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
  105. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
  106. package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
  107. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -3
  108. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
  109. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
  110. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  111. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
  112. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
  113. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
  114. package/edit/resources.cattle.io.restore.vue +5 -8
  115. package/initialize/install-plugins.js +1 -3
  116. package/list/__tests__/workload.test.ts +1 -0
  117. package/list/workload.vue +8 -1
  118. package/machine-config/components/GCEImage.vue +6 -5
  119. package/machine-config/google.vue +11 -6
  120. package/mixins/__tests__/auth-config.test.ts +4 -6
  121. package/mixins/__tests__/chart.test.ts +139 -1
  122. package/mixins/auth-config.js +33 -10
  123. package/mixins/chart.js +58 -18
  124. package/models/__tests__/namespace.test.ts +69 -0
  125. package/models/apps.statefulset.js +8 -10
  126. package/models/chart.js +5 -1
  127. package/models/fleet-application.js +16 -46
  128. package/models/fleet.cattle.io.bundle.js +1 -38
  129. package/models/fleet.cattle.io.gitrepo.js +4 -0
  130. package/models/fleet.cattle.io.helmop.js +4 -0
  131. package/models/management.cattle.io.cluster.js +1 -1
  132. package/models/management.cattle.io.project.js +12 -0
  133. package/models/namespace.js +30 -0
  134. package/models/workload.js +4 -1
  135. package/package.json +10 -10
  136. package/pages/auth/login.vue +8 -3
  137. package/pages/auth/logout.vue +6 -5
  138. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -11
  139. package/pages/c/_cluster/apps/charts/chart.vue +29 -20
  140. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  141. package/pages/c/_cluster/apps/charts/install.vue +6 -5
  142. package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
  143. package/pages/c/_cluster/explorer/tools/index.vue +145 -254
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +18 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
  146. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  147. package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
  148. package/pages/c/_cluster/uiplugins/index.vue +221 -363
  149. package/pages/home.vue +1 -9
  150. package/plugins/axios.js +3 -2
  151. package/plugins/dashboard-store/resource-class.js +49 -0
  152. package/plugins/ember-cookie.js +7 -3
  153. package/plugins/steve/subscribe.js +4 -2
  154. package/public/index.html +2 -1
  155. package/rancher-components/Card/Card.vue +1 -1
  156. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  157. package/rancher-components/Form/Radio/RadioButton.vue +1 -1
  158. package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
  159. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
  160. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
  161. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
  162. package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
  163. package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
  164. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +1 -1
  165. package/rancher-components/Pill/RcStatusBadge/index.ts +1 -1
  166. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -3
  167. package/rancher-components/Pill/RcStatusIndicator/types.ts +1 -1
  168. package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
  169. package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
  170. package/rancher-components/Pill/RcTag/index.ts +1 -0
  171. package/rancher-components/Pill/RcTag/types.ts +9 -0
  172. package/rancher-components/Pill/types.ts +1 -0
  173. package/rancher-components/RcItemCard/RcItemCard.vue +1 -0
  174. package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
  175. package/scripts/test-plugins-build.sh +0 -1
  176. package/store/__tests__/catalog.test.ts +63 -0
  177. package/store/__tests__/cookies.test.ts +72 -0
  178. package/store/auth.js +33 -10
  179. package/store/catalog.js +2 -2
  180. package/store/cookies.ts +30 -0
  181. package/store/prefs.js +10 -5
  182. package/store/type-map.js +3 -15
  183. package/types/extension-manager.ts +26 -0
  184. package/types/shell/index.d.ts +123 -27
  185. package/utils/__tests__/product.test.ts +129 -0
  186. package/utils/__tests__/resource.test.ts +87 -0
  187. package/utils/alertmanagerconfig.js +2 -2
  188. package/utils/auth.js +4 -77
  189. package/utils/product.ts +39 -0
  190. package/utils/resource.ts +35 -0
  191. package/utils/select.js +0 -24
  192. package/utils/validators/formRules/__tests__/index.test.ts +3 -0
  193. package/utils/validators/formRules/index.ts +2 -1
  194. package/vue.config.js +1 -1
  195. package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
  196. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
  197. package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
  198. package/utils/cookie-universal.js +0 -10
  199. /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
@@ -0,0 +1,129 @@
1
+ import { setProduct } from '@shell/utils/product';
2
+ import { getProductFromRoute } from '@shell/utils/router';
3
+ import { NAME as EXPLORER } from '@shell/config/product/explorer';
4
+ import { RouteLocation } from 'vue-router';
5
+
6
+ // Mock dependencies
7
+ jest.mock('@shell/utils/router', () => ({ getProductFromRoute: jest.fn() }));
8
+
9
+ const mockGetProductFromRoute = getProductFromRoute as jest.Mock;
10
+
11
+ describe('utils/product', () => {
12
+ describe('setProduct', () => {
13
+ let mockStore: any;
14
+ let mockTo: RouteLocation;
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+
19
+ // A stateful mock store to better simulate Vuex behavior
20
+ mockStore = {
21
+ productId: 'some-old-product',
22
+ products: {
23
+ 'some-old-product': { name: 'some-old-product', inStore: 'store_a' },
24
+ 'new-product-same-store': { name: 'new-product-same-store', inStore: 'store_a' },
25
+ 'new-product-diff-store': { name: 'new-product-diff-store', inStore: 'store_b' },
26
+ [EXPLORER]: { name: EXPLORER, inStore: 'explorer_store' },
27
+ },
28
+ getters: {
29
+ 'type-map/isProductRegistered': jest.fn().mockReturnValue(true),
30
+ 'i18n/t': jest.fn((key, args) => `${ key } ${ JSON.stringify(args) || '' }`),
31
+ get currentProduct() {
32
+ return mockStore.products[mockStore.productId];
33
+ },
34
+ get productId() {
35
+ return mockStore.productId;
36
+ }
37
+ },
38
+ commit: jest.fn((type, payload) => {
39
+ if (type === 'setProduct') {
40
+ mockStore.productId = payload;
41
+ }
42
+ }),
43
+ dispatch: jest.fn(),
44
+ };
45
+
46
+ mockTo = { matched: [{ path: '/c/:cluster/some-product' }] } as unknown as RouteLocation;
47
+ });
48
+
49
+ it('should dispatch loadingError if product is not found (wildcard route)', () => {
50
+ const product = 'invalid-product';
51
+
52
+ mockGetProductFromRoute.mockReturnValue(product);
53
+ mockTo.matched = [{ path: '/c/:cluster/:product' }] as any;
54
+
55
+ setProduct(mockStore, mockTo);
56
+
57
+ expect(mockStore.dispatch).toHaveBeenCalledWith('loadingError', expect.any(Error));
58
+ expect(mockStore.getters['i18n/t']).toHaveBeenCalledWith('nav.failWhale.productNotFound', { productNotFound: product }, true);
59
+ expect(mockStore.commit).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it('should dispatch loadingError if product exists but route has no matches', () => {
63
+ const product = 'invalid-product';
64
+
65
+ mockGetProductFromRoute.mockReturnValue(product);
66
+ mockTo.matched = [] as any; // No matched routes
67
+
68
+ setProduct(mockStore, mockTo);
69
+
70
+ expect(mockStore.dispatch).toHaveBeenCalledWith('loadingError', expect.any(Error));
71
+ expect(mockStore.getters['i18n/t']).toHaveBeenCalledWith('nav.failWhale.productNotFound', { productNotFound: product }, true);
72
+ expect(mockStore.commit).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it('should dispatch loadingError if product is not registered', () => {
76
+ const product = 'unregistered-product';
77
+
78
+ mockGetProductFromRoute.mockReturnValue(product);
79
+ mockStore.getters['type-map/isProductRegistered'].mockReturnValue(false);
80
+
81
+ setProduct(mockStore, mockTo);
82
+
83
+ expect(mockStore.dispatch).toHaveBeenCalledWith('loadingError', expect.any(Error));
84
+ expect(mockStore.getters['i18n/t']).toHaveBeenCalledWith('nav.failWhale.productNotFound', { productNotFound: product }, true);
85
+ expect(mockStore.commit).not.toHaveBeenCalled();
86
+ });
87
+
88
+ it('should default to EXPLORER product and reset catalog if store changes', () => {
89
+ mockGetProductFromRoute.mockReturnValue(null);
90
+
91
+ setProduct(mockStore, mockTo);
92
+
93
+ expect(mockStore.commit).toHaveBeenCalledWith('setProduct', EXPLORER);
94
+ expect(mockStore.commit).toHaveBeenCalledWith('catalog/reset');
95
+ });
96
+
97
+ it('should not call any commits if the product has not changed', () => {
98
+ const product = 'some-old-product';
99
+
100
+ mockGetProductFromRoute.mockReturnValue(product);
101
+
102
+ setProduct(mockStore, mockTo);
103
+
104
+ expect(mockStore.commit).not.toHaveBeenCalled();
105
+ });
106
+
107
+ it('should change product but not reset catalog if inStore is the same', () => {
108
+ const newProduct = 'new-product-same-store';
109
+
110
+ mockGetProductFromRoute.mockReturnValue(newProduct);
111
+
112
+ setProduct(mockStore, mockTo);
113
+
114
+ expect(mockStore.commit).toHaveBeenCalledWith('setProduct', newProduct);
115
+ expect(mockStore.commit).not.toHaveBeenCalledWith('catalog/reset');
116
+ });
117
+
118
+ it('should change product and reset catalog if inStore is different', () => {
119
+ const newProduct = 'new-product-diff-store';
120
+
121
+ mockGetProductFromRoute.mockReturnValue(newProduct);
122
+
123
+ setProduct(mockStore, mockTo);
124
+
125
+ expect(mockStore.commit).toHaveBeenCalledWith('setProduct', newProduct);
126
+ expect(mockStore.commit).toHaveBeenCalledWith('catalog/reset');
127
+ });
128
+ });
129
+ });
@@ -0,0 +1,87 @@
1
+ import { validateResource } from '@shell/utils/resource';
2
+ import { canViewResource } from '@shell/utils/auth';
3
+ import { getResourceFromRoute } from '@shell/utils/router';
4
+ import { RouteLocation } from 'vue-router';
5
+
6
+ // Mock dependencies from other modules
7
+ jest.mock('@shell/utils/auth', () => ({ canViewResource: jest.fn() }));
8
+
9
+ jest.mock('@shell/utils/router', () => ({ getResourceFromRoute: jest.fn() }));
10
+
11
+ // Typed mocks for better intellisense and type-checking
12
+ const mockCanViewResource = canViewResource as jest.Mock;
13
+ const mockGetResourceFromRoute = getResourceFromRoute as jest.Mock;
14
+
15
+ describe('validateResource', () => {
16
+ let mockStore: any;
17
+ let mockTo: RouteLocation;
18
+
19
+ beforeEach(() => {
20
+ // Reset mocks before each test to ensure test isolation
21
+ jest.clearAllMocks();
22
+
23
+ // A basic mock for the Vuex store
24
+ mockStore = {
25
+ getters: {
26
+ currentProduct: { name: 'explorer' },
27
+ 'i18n/t': jest.fn((key, args) => `i18n: ${ key } ${ JSON.stringify(args) || '' }`),
28
+ },
29
+ dispatch: jest.fn(),
30
+ };
31
+
32
+ // A mock for the route location object
33
+ mockTo = {
34
+ name: 'c-cluster-product-resource-id',
35
+ params: {
36
+ cluster: 'local',
37
+ product: 'explorer',
38
+ resource: 'pod',
39
+ id: 'my-pod'
40
+ }
41
+ } as unknown as RouteLocation;
42
+ });
43
+
44
+ it('should return false if no product is set in the store', () => {
45
+ mockStore.getters.currentProduct = null;
46
+ mockGetResourceFromRoute.mockReturnValue('pod');
47
+
48
+ const result = validateResource(mockStore, mockTo);
49
+
50
+ expect(result).toBe(false);
51
+ expect(mockStore.dispatch).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it('should return false if no resource is found in the route', () => {
55
+ mockGetResourceFromRoute.mockReturnValue(null);
56
+
57
+ const result = validateResource(mockStore, mockTo);
58
+
59
+ expect(result).toBe(false);
60
+ expect(mockStore.dispatch).not.toHaveBeenCalled();
61
+ });
62
+
63
+ it('should return false if the user is authorized to view the resource', () => {
64
+ mockGetResourceFromRoute.mockReturnValue('pod');
65
+ mockCanViewResource.mockReturnValue(true);
66
+
67
+ const result = validateResource(mockStore, mockTo);
68
+
69
+ expect(result).toBe(false);
70
+ expect(mockStore.dispatch).not.toHaveBeenCalled();
71
+ });
72
+
73
+ it('should throw an error and dispatch loadingError if user cannot view the resource', () => {
74
+ const resource = 'pod';
75
+
76
+ mockGetResourceFromRoute.mockReturnValue(resource);
77
+ mockCanViewResource.mockReturnValue(false);
78
+
79
+ const errorMessage = `i18n: nav.failWhale.resourceNotFound {"resource":"${ resource }"}`;
80
+
81
+ mockStore.getters['i18n/t'].mockReturnValue(errorMessage);
82
+
83
+ expect(() => validateResource(mockStore, mockTo)).toThrow(errorMessage);
84
+ expect(mockStore.dispatch).toHaveBeenCalledWith('loadingError', expect.any(Error));
85
+ expect(mockStore.getters['i18n/t']).toHaveBeenCalledWith('nav.failWhale.resourceNotFound', { resource }, true);
86
+ });
87
+ });
@@ -19,8 +19,8 @@ export const fetchAlertManagerConfigSpecs = async($store) => {
19
19
  await schema.fetchResourceFields();
20
20
 
21
21
  return {
22
- receiverSchema: schema.schemaDefinitions?.[`${ schema.schemaDefinition.id }.spec.receivers`],
23
- routeSchema: schema.schemaDefinitions?.[`${ schema.schemaDefinition.id }.spec.route`],
22
+ receiverSchema: schema.schemaDefinitions?.[`${ schema.schemaDefinition.type }.spec.receivers`],
23
+ routeSchema: schema.schemaDefinitions?.[`${ schema.schemaDefinition.type }.spec.route`],
24
24
  };
25
25
  };
26
26
 
package/utils/auth.js CHANGED
@@ -5,8 +5,6 @@ import {
5
5
  } from '@shell/config/query-params';
6
6
  import { MANAGEMENT, NORMAN } from '@shell/config/types';
7
7
  import { allHash } from '@shell/utils/promise';
8
- import { getProductFromRoute, getResourceFromRoute } from '@shell/utils/router';
9
- import { NAME as EXPLORER } from '@shell/config/product/explorer';
10
8
  import { findBy } from '@shell/utils/array';
11
9
  import { onExtensionsReady } from '@shell/utils/uiplugins';
12
10
 
@@ -201,6 +199,9 @@ export const checkPermissions = (types, getters) => {
201
199
  return allHash(hash);
202
200
  };
203
201
 
202
+ /**
203
+ * Checks if the current user has access to the specified resource type
204
+ */
204
205
  export const canViewResource = (store, resource) => {
205
206
  // Note - don't use the current products store... because products can override stores for resources with `typeStoreMap`
206
207
  const inStore = store.getters['currentStore'](resource);
@@ -218,80 +219,6 @@ export const canViewResource = (store, resource) => {
218
219
  return !!validResource;
219
220
  };
220
221
 
221
- // ************************************************************
222
- //
223
- // BELOW ARE METHODS THAT ARE A PART OF THE AUTHENTICATED MIDDLEWARE REMOVAL. THIS IS A TEMPORARY HOME FOR THESE UTILS AND SHOULD BE REWRITTEN, MOVED OR DELETED.
224
- //
225
- // TODO: Remove and refactor everything below for more clarity and better organization. https://github.com/rancher/dashboard/issues/11111
226
- //
227
- // ************************************************************
228
-
229
- /**
230
- * Attempt to set the product in our datastore if the route matches a known product. Otherwise show an error page instead.
231
- */
232
- export function setProduct(store, to) {
233
- let product = getProductFromRoute(to);
234
-
235
- // since all products are hardcoded as routes (ex: c-local-explorer), if we match the wildcard route it means that the product does not exist
236
- if ((product && (!to.matched.length || (to.matched.length && to.matched[0].path === '/c/:cluster/:product'))) ||
237
- // if the product grabbed from the route is not registered, then we don't have it!
238
- (product && !store.getters['type-map/isProductRegistered'](product))) {
239
- const error = new Error(store.getters['i18n/t']('nav.failWhale.productNotFound', { productNotFound: product }, true));
240
-
241
- return store.dispatch('loadingError', error);
242
- }
243
-
244
- if ( !product ) {
245
- product = EXPLORER;
246
- }
247
-
248
- const oldProduct = store.getters['productId'];
249
- const oldStore = store.getters['currentProduct']?.inStore;
250
-
251
- if ( product !== oldProduct ) {
252
- store.commit('setProduct', product);
253
- }
254
-
255
- const neuStore = store.getters['currentProduct']?.inStore;
256
-
257
- if ( neuStore !== oldStore ) {
258
- // If the product store changes, clear the catalog.
259
- // There might be management catalog items in it vs cluster.
260
- store.commit('catalog/reset');
261
- }
262
- }
263
-
264
- /**
265
- * Check that the resource is valid, if not redirect to fail whale
266
- *
267
- * This requires that
268
- * - product is set
269
- * - product's store is set and setup (so we can check schema's within it)
270
- * - product's store has the schemaFor getter (extension stores might not have it)
271
- * - there's a resource associated with route (meta or param)
272
- */
273
- export function validateResource(store, to) {
274
- const product = store.getters['currentProduct'];
275
- const resource = getResourceFromRoute(to);
276
-
277
- // In order to check a resource is valid we need these
278
- if (!product || !resource) {
279
- return false;
280
- }
281
-
282
- if (canViewResource(store, resource)) {
283
- return false;
284
- }
285
-
286
- // Unknown resource, redirect to fail whale
287
-
288
- const error = new Error(store.getters['i18n/t']('nav.failWhale.resourceNotFound', { resource }, true));
289
-
290
- store.dispatch('loadingError', error);
291
-
292
- throw error;
293
- }
294
-
295
222
  /**
296
223
  * Attempt to load the current user's principal
297
224
  */
@@ -336,7 +263,7 @@ export async function tryInitialSetup(store, password = 'admin') {
336
263
  */
337
264
  export async function isLoggedIn(store, userData) {
338
265
  store.commit('auth/hasAuth', true);
339
- store.commit('auth/loggedInAs', userData.id);
266
+ store.dispatch('auth/loggedInAs', userData.id);
340
267
 
341
268
  // Init the notification center now that we know who the user is
342
269
  await store.dispatch('notifications/init', userData);
@@ -0,0 +1,39 @@
1
+ import { getProductFromRoute } from '@shell/utils/router';
2
+ import { NAME as EXPLORER } from '@shell/config/product/explorer';
3
+ import { RouteLocation } from 'vue-router';
4
+ import { Store } from 'vuex';
5
+
6
+ /**
7
+ * Attempt to set the product in our datastore if the route matches a known product. Otherwise show an error page instead.
8
+ */
9
+ export function setProduct(store: Store<any>, to: RouteLocation) {
10
+ let product = getProductFromRoute(to);
11
+
12
+ // since all products are hardcoded as routes (ex: c-local-explorer), if we match the wildcard route it means that the product does not exist
13
+ if ((product && (!to.matched.length || (to.matched.length && to.matched[0].path === '/c/:cluster/:product'))) ||
14
+ // if the product grabbed from the route is not registered, then we don't have it!
15
+ (product && !store.getters['type-map/isProductRegistered'](product))) {
16
+ const error = new Error(store.getters['i18n/t']('nav.failWhale.productNotFound', { productNotFound: product }, true));
17
+
18
+ return store.dispatch('loadingError', error);
19
+ }
20
+
21
+ if ( !product ) {
22
+ product = EXPLORER;
23
+ }
24
+
25
+ const oldProduct = store.getters['productId'];
26
+ const oldStore = store.getters['currentProduct']?.inStore;
27
+
28
+ if ( product !== oldProduct ) {
29
+ store.commit('setProduct', product);
30
+ }
31
+
32
+ const neuStore = store.getters['currentProduct']?.inStore;
33
+
34
+ if ( neuStore !== oldStore ) {
35
+ // If the product store changes, clear the catalog.
36
+ // There might be management catalog items in it vs cluster.
37
+ store.commit('catalog/reset');
38
+ }
39
+ }
@@ -0,0 +1,35 @@
1
+ import { canViewResource } from '@shell/utils/auth';
2
+ import { getResourceFromRoute } from '@shell/utils/router';
3
+ import { RouteLocation } from 'vue-router';
4
+ import { Store } from 'vuex';
5
+
6
+ /**
7
+ * Check that the resource is valid, if not redirect to fail whale
8
+ *
9
+ * This requires that
10
+ * - product is set
11
+ * - product's store is set and setup (so we can check schema's within it)
12
+ * - product's store has the schemaFor getter (extension stores might not have it)
13
+ * - there's a resource associated with route (meta or param)
14
+ */
15
+ export function validateResource(store: Store<any>, to: RouteLocation) {
16
+ const product = store.getters['currentProduct'];
17
+ const resource = getResourceFromRoute(to);
18
+
19
+ // In order to check a resource is valid we need these
20
+ if (!product || !resource) {
21
+ return false;
22
+ }
23
+
24
+ if (canViewResource(store, resource)) {
25
+ return false;
26
+ }
27
+
28
+ // Unknown resource, redirect to fail whale
29
+
30
+ const error = new Error(store.getters['i18n/t']('nav.failWhale.resourceNotFound', { resource }, true));
31
+
32
+ store.dispatch('loadingError', error);
33
+
34
+ throw error;
35
+ }
package/utils/select.js CHANGED
@@ -1,27 +1,3 @@
1
- export function onClickOption(option, e) {
2
- if (!this.$attrs.multiple) {
3
- return;
4
- }
5
-
6
- const getValue = (opt) => (this.optionKey ? this.get(opt, this.optionKey) : this.getOptionLabel(opt));
7
- const optionValue = getValue(option);
8
- const value = this.value || [];
9
- const optionIndex = value.findIndex((option) => getValue(option) === optionValue);
10
-
11
- if (optionIndex < 0) {
12
- return;
13
- }
14
-
15
- this.value.splice(optionIndex, 1);
16
-
17
- this.$emit('update:value', this.value);
18
- e.preventDefault();
19
- e.stopPropagation();
20
-
21
- if (this.closeOnSelect) {
22
- this.$refs['select-input'].closeSearchOptions();
23
- }
24
- }
25
1
 
26
2
  // This is a simpler positionner for the dropdown for a select control
27
3
  // We used to use popper for these, but it does not suppotr fractional pixel placements which
@@ -114,6 +114,8 @@ describe('formRules', () => {
114
114
  ['git@github.com:rancher/dashboard/', undefined],
115
115
  ['git@github.com:rancher/%20dashboard/', undefined],
116
116
  ['git@github.com:rancher/dashboard/%20', undefined],
117
+ ['git@git.apps.local:fleet/fleet-local.git', undefined],
118
+ ['git@git.apps.local:33333/fleet/fleet-local.git', undefined],
117
119
 
118
120
  // Not valid HTTP(s)
119
121
  ['https://github.com/rancher/ dashboard.git', message],
@@ -140,6 +142,7 @@ describe('formRules', () => {
140
142
  ['git@githubcomrancher/dashboard', message],
141
143
  ['%20git@github.comrancher/dashboard', message],
142
144
  ['git@git%20hub.comrancher/dashboard', message],
145
+ ['git@git.apps.local:/fleet/fleet-local.git', message],
143
146
  ['git@.git', message],
144
147
  ['git@', message],
145
148
 
@@ -191,6 +191,7 @@ export default function(
191
191
  protocol,
192
192
  authority,
193
193
  host,
194
+ port,
194
195
  path
195
196
  } = parse(url);
196
197
 
@@ -205,7 +206,7 @@ export default function(
205
206
  }
206
207
 
207
208
  // Test ssh, authority must be valid (SSH user + host)
208
- if (!protocol && !authority.endsWith(':')) {
209
+ if (!protocol && !port && (!authority.endsWith(':') || path.startsWith('/'))) {
209
210
  return message;
210
211
  }
211
212
 
package/vue.config.js CHANGED
@@ -546,7 +546,7 @@ module.exports = function(dir, appConfig = {}) {
546
546
  config.plugins.push(getVirtualModulesAutoImport(dir));
547
547
  config.plugins.push(getPackageImport(dir));
548
548
  config.plugins.push(createEnvVariablesPlugin(routerBasePath, rancherEnv));
549
- config.plugins.push(new NodePolyfillPlugin()); // required from Webpack 5 to polyfill node modules
549
+ config.plugins.push(new NodePolyfillPlugin({ additionalAliases: ['process'] })); // required from Webpack 5 to polyfill node modules
550
550
 
551
551
  // The static assets need to be in the built assets directory in order to get served (primarily the favicon)
552
552
  config.plugins.push(new CopyWebpackPlugin({ patterns: [{ from: path.join(SHELL_ABS, 'static'), to: '.' }] }));
@@ -1,34 +0,0 @@
1
- <script setup lang="ts">
2
- export interface RectangleProps {
3
- outline?: boolean;
4
- }
5
-
6
- const props = withDefaults(
7
- defineProps<RectangleProps>(),
8
- { outline: false }
9
- );
10
-
11
- </script>
12
-
13
- <template>
14
- <div
15
- class="rectangle"
16
- :class="{outline: props.outline}"
17
- >
18
- <slot />
19
- </div>
20
- </template>
21
-
22
- <style lang="scss" scoped>
23
- .rectangle {
24
- border: 1px solid var(--tag-bg);
25
- border-radius: 4px;
26
- padding: 0 8px;
27
- height: 23px;
28
- line-height: 23px;
29
-
30
- &:not(.outline) {
31
- background-color: var(--tag-bg);
32
- }
33
- }
34
- </style>
@@ -1,24 +0,0 @@
1
- import { mount } from '@vue/test-utils';
2
- import Rectangle from '@shell/components/Resource/Detail/Metadata/Rectangle.vue';
3
-
4
- describe('component: Rectangle', () => {
5
- it('should render container with class title and missing outline when passed outline:false', async() => {
6
- const wrapper = mount(Rectangle, { props: { outline: false } });
7
-
8
- expect(wrapper.find('.rectangle').exists()).toBeTruthy();
9
- expect(wrapper.find('.rectangle.outline').exists()).toBeFalsy();
10
- });
11
-
12
- it('should render outline class when passed outline:true', async() => {
13
- const wrapper = mount(Rectangle, { props: { outline: true } });
14
-
15
- expect(wrapper.find('.rectangle.outline').exists()).toBeTruthy();
16
- });
17
-
18
- it('should render default slot', async() => {
19
- const content = 'CONTENT';
20
- const wrapper = mount(Rectangle, { slots: { default: content } });
21
-
22
- expect(wrapper.find('.rectangle').element.innerHTML).toStrictEqual(content);
23
- });
24
- });
@@ -1,65 +0,0 @@
1
- import { mount, RouterLinkStub } from '@vue/test-utils';
2
- import { _VIEW } from '@shell/config/query-params';
3
- import Legacy from '@shell/components/ResourceDetail/Masthead/legacy.vue';
4
- import { createStore } from 'vuex';
5
-
6
- const mockedStore = () => {
7
- return {
8
- getters: {
9
- currentStore: () => 'current_store',
10
- currentProduct: { inStore: 'cluster' },
11
- isExplorer: false,
12
- currentCluster: {},
13
- 'type-map/labelFor': jest.fn(),
14
- 'type-map/optionsFor': jest.fn(),
15
- 'current_store/schemaFor': jest.fn(),
16
- },
17
- };
18
- };
19
-
20
- const requiredSetup = () => {
21
- const store = createStore({ getters: { 'management/byId': () => jest.fn() } });
22
-
23
- return {
24
- stubs: {
25
- 'router-link': RouterLinkStub,
26
- LiveDate: true
27
- },
28
- provide: { store },
29
- mocks: { $store: mockedStore() }
30
- };
31
- };
32
-
33
- describe('component: Masthead/legacy', () => {
34
- it.each([
35
- ['hidden', '', false, { displayName: 'admin', location: { id: 'resource-id' } }, false, false],
36
- ['plain-text', 'admin', true, { displayName: 'admin', location: null }, false, true],
37
- ['link', 'foo', true, { displayName: 'foo', location: { id: 'resource-id' } }, true, false],
38
- ])('"Created By" should be %p, with text: %p', (
39
- _,
40
- text,
41
- showCreatedBy,
42
- createdBy,
43
- showLink,
44
- showPlainText,
45
- ) => {
46
- const wrapper = mount(Legacy, {
47
- props: {
48
- mode: _VIEW,
49
- value: {
50
- showCreatedBy,
51
- createdBy,
52
- },
53
- },
54
- global: { ...requiredSetup() }
55
- });
56
-
57
- const container = wrapper.find('[data-testid="masthead-subheader-createdBy"]');
58
- const link = wrapper.find('[data-testid="masthead-subheader-createdBy-link"]');
59
- const plainText = wrapper.find('[data-testid="masthead-subheader-createdBy_plain-text"]');
60
-
61
- expect(link.exists()).toBe(showLink);
62
- expect(plainText.exists()).toBe(showPlainText);
63
- expect(showLink || showPlainText ? container.element.textContent : '').toContain(text);
64
- });
65
- });
@@ -1,10 +0,0 @@
1
- import cookieUniversal from 'cookie-universal';
2
-
3
- export default ({ req, res }, inject) => {
4
- const options = {
5
- alias: 'cookies',
6
- parseJSON: true
7
- };
8
-
9
- inject(options.alias, cookieUniversal(req, res, options.parseJSON));
10
- };