@rancher/shell 3.0.12-rc.2 → 3.0.12-rc.3

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 (272) hide show
  1. package/apis/impl/apis.ts +6 -0
  2. package/apis/index.ts +26 -0
  3. package/apis/intf/resources-api/cluster-api.ts +18 -0
  4. package/apis/intf/resources-api/mgmt-api.ts +15 -0
  5. package/apis/intf/resources-api/resource-base.ts +107 -0
  6. package/apis/intf/resources-api/resource-constants.ts +147 -0
  7. package/apis/intf/resources-api/resources-api.ts +143 -0
  8. package/apis/intf/resources.ts +49 -0
  9. package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
  10. package/apis/intf/shell-api/proxy.ts +216 -0
  11. package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
  12. package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
  13. package/apis/intf/shell.ts +12 -6
  14. package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
  15. package/apis/resources/index.ts +22 -0
  16. package/apis/resources/resources-api-class.ts +187 -0
  17. package/apis/shell/__tests__/proxy.test.ts +369 -0
  18. package/apis/shell/index.ts +8 -1
  19. package/apis/shell/modal.ts +4 -1
  20. package/apis/shell/notifications.ts +9 -6
  21. package/apis/shell/proxy.ts +256 -0
  22. package/apis/shell/slide-in.ts +4 -1
  23. package/apis/vue-shim.d.ts +2 -1
  24. package/assets/data/aws-regions.json +4 -0
  25. package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
  26. package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
  27. package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
  28. package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
  29. package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
  30. package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
  31. package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
  32. package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
  33. package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
  34. package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
  35. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
  36. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
  37. package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
  38. package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
  39. package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
  40. package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
  41. package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
  42. package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
  43. package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
  44. package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
  45. package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
  46. package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
  47. package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
  48. package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
  49. package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
  50. package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
  51. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
  52. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
  53. package/assets/styles/base/_variables.scss +2 -0
  54. package/assets/styles/fonts/_fontstack.scss +132 -8
  55. package/assets/translations/en-us.yaml +22 -5
  56. package/chart/monitoring/index.vue +10 -1
  57. package/components/ActionDropdownShell.vue +2 -1
  58. package/components/CruResourceFooter.vue +9 -5
  59. package/components/ExplorerProjectsNamespaces.vue +1 -1
  60. package/components/InstallHelmCharts.vue +2 -2
  61. package/components/LandingPagePreference.vue +14 -5
  62. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  63. package/components/Resource/Detail/Metadata/index.vue +6 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  65. package/components/Resource/Detail/SpacedRow.vue +3 -1
  66. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  67. package/components/ResourceList/Masthead.vue +12 -8
  68. package/components/SelectIconGrid.vue +0 -10
  69. package/components/SingleClusterInfo.vue +1 -0
  70. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  71. package/components/SortableTable/index.vue +6 -9
  72. package/components/SortableTable/selection.js +23 -5
  73. package/components/SortableTable/sorting.js +6 -3
  74. package/components/Wizard.vue +14 -13
  75. package/components/fleet/FleetBundles.vue +100 -12
  76. package/components/fleet/FleetClusterTargets/index.vue +37 -15
  77. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  78. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  79. package/components/form/LabeledSelect.vue +20 -3
  80. package/components/form/NameNsDescription.vue +11 -0
  81. package/components/form/Security.vue +6 -2
  82. package/components/form/WorkloadPorts.vue +2 -7
  83. package/components/form/__tests__/Security.test.ts +76 -0
  84. package/components/formatter/Autoscaler.vue +4 -4
  85. package/components/formatter/ClusterKubeVersion.vue +27 -0
  86. package/components/formatter/ClusterLink.vue +1 -7
  87. package/components/formatter/ClusterProvider.vue +6 -10
  88. package/components/formatter/FleetSummaryGraph.vue +0 -3
  89. package/components/formatter/MachineSummaryGraph.vue +1 -1
  90. package/components/formatter/PodsUsage.vue +2 -2
  91. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  92. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  93. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  94. package/components/nav/NamespaceFilter.vue +2 -2
  95. package/components/nav/TopLevelMenu.helper.ts +15 -3
  96. package/components/nav/TopLevelMenu.vue +16 -5
  97. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  98. package/components/templates/home.vue +18 -0
  99. package/components/templates/plain.vue +18 -0
  100. package/components/templates/standalone.vue +17 -0
  101. package/composables/useFormValidation.ts +93 -0
  102. package/composables/useVeeValidateField.test.ts +159 -0
  103. package/composables/useVeeValidateField.ts +67 -0
  104. package/config/pagination-table-headers.js +18 -1
  105. package/config/product/manager.js +82 -21
  106. package/config/router/routes.js +6 -0
  107. package/config/table-headers.js +20 -1
  108. package/config/types.js +2 -1
  109. package/core/__tests__/plugin-products.test.ts +904 -20
  110. package/core/plugin-products-base.ts +107 -7
  111. package/core/plugin-products.ts +4 -0
  112. package/core/plugin-types.ts +111 -1
  113. package/core/plugin.ts +15 -7
  114. package/core/productDebugger.js +9 -4
  115. package/core/types-provisioning.ts +43 -30
  116. package/core/types.ts +57 -20
  117. package/detail/__tests__/pod.test.ts +41 -0
  118. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  119. package/detail/pod.vue +1 -1
  120. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  121. package/edit/auth/__tests__/azuread.test.ts +217 -34
  122. package/edit/auth/azuread.vue +122 -14
  123. package/edit/auth/oidc.vue +2 -2
  124. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  125. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  126. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  127. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  128. package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
  129. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  130. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  131. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  132. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  133. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  134. package/edit/secret/__tests__/ssh.test.ts +5 -6
  135. package/edit/secret/basic.vue +31 -0
  136. package/edit/secret/index.vue +68 -17
  137. package/edit/secret/registry.vue +38 -0
  138. package/edit/secret/ssh.vue +29 -0
  139. package/edit/secret/tls.vue +30 -0
  140. package/edit/service.vue +4 -4
  141. package/edit/workload/Upgrading.vue +3 -3
  142. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  143. package/edit/workload/mixins/workload.js +2 -1
  144. package/list/fleet.cattle.io.bundle.vue +7 -104
  145. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  146. package/list/provisioning.cattle.io.cluster.vue +262 -180
  147. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  148. package/mixins/__tests__/chart.test.ts +112 -0
  149. package/mixins/brand.js +2 -1
  150. package/mixins/chart.js +12 -8
  151. package/mixins/resource-fetch-api-pagination.js +41 -5
  152. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  153. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  154. package/models/__tests__/management.cattle.io.node.ts +6 -5
  155. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  156. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  157. package/models/base-cluster.x-k8s.io.js +26 -0
  158. package/models/cluster.js +1 -1
  159. package/models/cluster.x-k8s.io.machine.js +4 -22
  160. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  161. package/models/cluster.x-k8s.io.machineset.js +2 -20
  162. package/models/compliance.cattle.io.clusterscan.js +130 -2
  163. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  164. package/models/fleet-application.js +3 -1
  165. package/models/management.cattle.io.cluster.js +417 -40
  166. package/models/management.cattle.io.node.js +6 -4
  167. package/models/management.cattle.io.nodepool.js +1 -1
  168. package/models/networking.k8s.io.ingress.js +12 -4
  169. package/models/provisioning.cattle.io.cluster.js +47 -330
  170. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  171. package/package.json +11 -29
  172. package/pages/__tests__/readme.test.ts +49 -0
  173. package/pages/auth/setup.vue +2 -3
  174. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
  175. package/pages/c/_cluster/apps/charts/chart.vue +60 -8
  176. package/pages/c/_cluster/apps/charts/install.vue +10 -7
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  178. package/pages/c/_cluster/explorer/index.vue +5 -49
  179. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  180. package/pages/c/_cluster/istio/index.vue +21 -6
  181. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
  182. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
  183. package/pages/c/_cluster/uiplugins/index.vue +203 -197
  184. package/pages/diagnostic.vue +13 -17
  185. package/pages/fail-whale.vue +18 -0
  186. package/pages/home.vue +77 -260
  187. package/pages/readme.vue +88 -0
  188. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  189. package/plugins/dashboard-store/actions.js +40 -18
  190. package/plugins/dashboard-store/resource-class.js +5 -2
  191. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  192. package/plugins/steve/steve-pagination-utils.ts +11 -3
  193. package/plugins/steve/subscribe.js +35 -5
  194. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
  195. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
  196. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  197. package/rancher-components/RcButton/RcButton.vue +38 -8
  198. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  199. package/store/__tests__/catalog.test.ts +115 -1
  200. package/store/__tests__/type-map.test.ts +556 -1
  201. package/store/action-menu.js +8 -3
  202. package/store/auth.js +1 -1
  203. package/store/aws.js +27 -16
  204. package/store/catalog.js +27 -3
  205. package/store/digitalocean.js +20 -38
  206. package/store/index.js +2 -0
  207. package/store/linode.js +25 -40
  208. package/store/pnap.js +1 -0
  209. package/store/type-map.js +111 -29
  210. package/tsconfig.paths.json +8 -8
  211. package/types/kube/kube-api.ts +14 -1
  212. package/types/rancher/steve.api.ts +12 -12
  213. package/types/resources/settings.d.ts +2 -1
  214. package/types/shell/index.d.ts +102 -2
  215. package/types/store/dashboard-store.types.ts +108 -11
  216. package/types/store/pagination.types.ts +6 -3
  217. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  218. package/utils/__tests__/async.test.ts +87 -0
  219. package/utils/__tests__/aws.test.ts +140 -0
  220. package/utils/__tests__/banners.test.ts +176 -0
  221. package/utils/__tests__/chart.test.ts +64 -1
  222. package/utils/__tests__/color.test.ts +226 -0
  223. package/utils/__tests__/duration.test.ts +140 -0
  224. package/utils/__tests__/fleet.test.ts +340 -0
  225. package/utils/__tests__/ingress.test.ts +553 -0
  226. package/utils/__tests__/kube.test.ts +68 -0
  227. package/utils/__tests__/namespace-filter.test.ts +109 -0
  228. package/utils/__tests__/pagination-utils.test.ts +361 -0
  229. package/utils/__tests__/parse-externalid.test.ts +137 -0
  230. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  231. package/utils/__tests__/poller-sequential.test.ts +177 -0
  232. package/utils/__tests__/poller.test.ts +170 -0
  233. package/utils/__tests__/promise.test.ts +346 -0
  234. package/utils/__tests__/settings.test.ts +140 -0
  235. package/utils/__tests__/sort-utils.test.ts +301 -0
  236. package/utils/__tests__/string-utils.test.ts +798 -0
  237. package/utils/__tests__/string.test.ts +23 -1
  238. package/utils/__tests__/style.test.ts +154 -0
  239. package/utils/__tests__/svg-filter.test.ts +184 -0
  240. package/utils/__tests__/units.test.ts +417 -0
  241. package/utils/__tests__/versions.test.ts +128 -0
  242. package/utils/__tests__/xccdf.test.ts +391 -0
  243. package/utils/chart.js +36 -0
  244. package/utils/fleet.ts +13 -3
  245. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  246. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  247. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  248. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  249. package/utils/gc/__tests__/gc.test.ts +487 -0
  250. package/utils/ingress.ts +9 -1
  251. package/utils/pagination-utils.ts +2 -1
  252. package/utils/string.js +25 -2
  253. package/utils/uiplugins.ts +5 -5
  254. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  255. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  256. package/utils/validators/__tests__/index.test.ts +481 -0
  257. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  258. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  259. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  260. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  261. package/utils/validators/__tests__/role-template.test.ts +149 -0
  262. package/utils/validators/__tests__/service.test.ts +283 -0
  263. package/utils/validators/__tests__/setting.test.js +32 -0
  264. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  265. package/utils/validators/formRules/index.ts +5 -5
  266. package/utils/validators/machine-pool.ts +1 -1
  267. package/utils/validators/setting.js +18 -3
  268. package/utils/xccdf.ts +418 -0
  269. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  270. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  271. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  272. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
@@ -0,0 +1,187 @@
1
+ import {
2
+ ResourceType, FindMethodOptions, FindAllMethodOptions, FindFilteredPageOptions, FindFilteredLabelSelectorOptions,
3
+ FindFilteredPageResponse, FindFilteredLabelSelectorResponse
4
+ } from '@shell/apis/intf/resources-api/resource-base';
5
+ import { ResourcesApi } from '@shell/apis/intf/resources-api/resources-api';
6
+ import { SteveListResponse, SteveGetResponse } from '@shell/types/rancher/steve.api';
7
+ import { Store } from 'vuex';
8
+
9
+ export class ResourcesApiClassImpl implements ResourcesApi {
10
+ private store: Store<any>;
11
+
12
+ private storeType: 'cluster' | 'management' | string;
13
+
14
+ private surfaceError(message: string, e?: any): never {
15
+ console.error(`Resource API error - ${ this.storeType } - ${ message }`); // eslint-disable-line no-console
16
+ throw new Error(`Resource API error - ${ this.storeType } - ${ message }`, { cause: e });
17
+ }
18
+
19
+ private isNamespaced(resourceType: ResourceType): boolean {
20
+ const schema = this.store.getters[`${ this.storeType }/schemaFor`]?.(resourceType);
21
+
22
+ return !!schema?.attributes?.namespaced;
23
+ }
24
+
25
+ constructor(store: Store<any>, storeType: 'cluster' | 'management' | string) {
26
+ this.store = store;
27
+ this.storeType = storeType;
28
+ }
29
+
30
+ /**
31
+ * Finds a specific resource by its type and ID.
32
+ *
33
+ * @template T - The type of the resource (defaults to SteveGetResponse)
34
+ * @param resourceType - The type of the resource to find (use **{@link K8S}** constant). See also {@link ResourceType}.
35
+ * @param resourceId - The unique identifier of the resource to find. If the resource is namespaced, this should be in the format `namespace/name`.
36
+ * @param options - Optional find arguments
37
+ * @returns The found resource item or null if not found.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { useResources, K8S } from '@shell/apis';
42
+ *
43
+ * const resources = useResources();
44
+ *
45
+ * // Namespaced resource - ID must be in "namespace/name" format
46
+ * const pod = await resources.cluster.find(K8S.POD, 'default/my-pod-123');
47
+ *
48
+ * // Cluster-scoped resource - ID is just the name
49
+ * const node = await resources.cluster.find(K8S.NODE, 'worker-1');
50
+ * ```
51
+ */
52
+ async find<T = SteveGetResponse>(
53
+ resourceType: ResourceType,
54
+ resourceId: string,
55
+ options?: FindMethodOptions
56
+ ): Promise<T | null> {
57
+ if (this.isNamespaced(resourceType) && !resourceId.includes('/')) {
58
+ this.surfaceError(`Resource "${ resourceType }" is namespaced. The resourceId must be in "namespace/name" format, but received "${ resourceId }"`);
59
+ }
60
+
61
+ try {
62
+ const resource = await this.store.dispatch(`${ this.storeType }/find`, {
63
+ type: resourceType,
64
+ id: resourceId,
65
+ opt: options || {}
66
+ });
67
+
68
+ return (resource as T) ?? null;
69
+ } catch (e: unknown) {
70
+ this.surfaceError(`Failed to find resource ${ resourceType }/${ resourceId }: ${ (e as Error).message }`, e);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Finds resources using pagination mode with server-side filtering, sorting, and pagination.
76
+ *
77
+ * Requires `ui-sql-cache` to be enabled.
78
+ *
79
+ * @template T - The type of the resources (defaults to SteveListResponse)
80
+ * @param resourceType - The type of the resources to find
81
+ * @param options - Pagination options with server-side filtering and sorting
82
+ * @returns Response containing resource items (may be transient if requested, otherwise cached array)
83
+ * @throws If pagination mode is requested but `ui-sql-cache` is not enabled
84
+ */
85
+ findFiltered<T = SteveListResponse>(
86
+ resourceType: ResourceType,
87
+ options: FindFilteredPageOptions
88
+ ): Promise<FindFilteredPageResponse<T>>;
89
+
90
+ /**
91
+ * Finds resources using label selector matching.
92
+ *
93
+ * Filters resources by Kubernetes labels. The store automatically handles pagination:
94
+ * - If `ui-sql-cache` is enabled: uses server-side pagination
95
+ * - Otherwise: uses native Kubernetes API pagination
96
+ *
97
+ * @template T - The type of the resources (defaults to SteveListResponse)
98
+ * @param resourceType - The type of the resources to find
99
+ * @param options - Label selector options for filtering
100
+ * @returns Response containing resource items (may be transient if requested, otherwise cached array)
101
+ */
102
+ findFiltered<T = SteveListResponse>(
103
+ resourceType: ResourceType,
104
+ options: FindFilteredLabelSelectorOptions
105
+ ): Promise<FindFilteredLabelSelectorResponse<T>>;
106
+
107
+ /**
108
+ * @internal Implementation - use one of the overloads above
109
+ */
110
+ async findFiltered<T = SteveListResponse>(
111
+ resourceType: ResourceType,
112
+ options: FindFilteredPageOptions | FindFilteredLabelSelectorOptions
113
+ ): Promise<FindFilteredPageResponse<T> | FindFilteredLabelSelectorResponse<T>> {
114
+ try {
115
+ if ('pagination' in options) { // pagination mode
116
+ const canPaginate = this.store.getters[`${ this.storeType }/paginationEnabled`]?.(resourceType);
117
+
118
+ if (!canPaginate) {
119
+ return this.surfaceError('findFiltered requests with FindFilteredPageOptions are only supported when ui-sql-cache is enabled');
120
+ }
121
+
122
+ const safeOption = options as FindFilteredPageOptions;
123
+ const response = await this.store.dispatch(`${ this.storeType }/findPage`, {
124
+ type: resourceType,
125
+ opt: safeOption
126
+ });
127
+
128
+ return response as FindFilteredPageResponse<T>;
129
+ } else if ('labelSelector' in options) { // label selector mode
130
+ const safeOption = options as FindFilteredLabelSelectorOptions;
131
+ const { labelSelector, namespaced, ...rest } = safeOption;
132
+ const resources = await this.store.dispatch(`${ this.storeType }/findLabelSelector`, {
133
+ type: resourceType,
134
+ matching: {
135
+ namespace: namespaced,
136
+ labelSelector
137
+ },
138
+ opt: rest
139
+ });
140
+
141
+ return resources as FindFilteredLabelSelectorResponse<T>;
142
+ } else {
143
+ return this.surfaceError('findFiltered request was made with unknown options');
144
+ }
145
+ } catch (e: unknown) {
146
+ this.surfaceError(`Failed to find filtered resources ${ resourceType }: ${ (e as Error).message }`, e);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Fetches all resources of a specific type with advanced options.
152
+ * This method provides additional capabilities like incremental loading and namespace filtering.
153
+ *
154
+ * @template T - The type of the resources (defaults to SteveListResponse)
155
+ * @param resourceType - The type of the resources to find (use **{@link K8S}** constant). See also {@link ResourceType}.
156
+ * @param options - Optional advanced fetch options (incremental loading, namespace filtering, etc.)
157
+ * @returns An array of resource items or an empty array if none are found.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * import { useResources, K8S } from '@shell/apis';
162
+ * import type { Pod } from '@shell/types/resources';
163
+ *
164
+ * const resources = useResources();
165
+ *
166
+ * // Fetch all pods in specific namespaces
167
+ * const pods = await resources.cluster.findAll<Pod>(K8S.POD, {
168
+ * namespaced: ['default', 'kube-system']
169
+ * });
170
+ * ```
171
+ */
172
+ async findAll<T = SteveListResponse>(
173
+ resourceType: ResourceType,
174
+ options?: FindAllMethodOptions
175
+ ): Promise<T[]> {
176
+ try {
177
+ const resources = await this.store.dispatch(`${ this.storeType }/findAll`, {
178
+ type: resourceType,
179
+ opt: options || {}
180
+ });
181
+
182
+ return resources as T[];
183
+ } catch (e: unknown) {
184
+ this.surfaceError(`Failed to find all resources ${ resourceType }: ${ (e as Error).message }`, e);
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,369 @@
1
+ // proxy.test.ts
2
+
3
+ import {
4
+ describe, it, expect, jest, beforeEach
5
+ } from '@jest/globals';
6
+ import { ProxyApiImpl, createDepaginator } from '../proxy';
7
+ import { Store } from 'vuex';
8
+
9
+ const PROXY_PREFIX = '/meta/proxy/';
10
+
11
+ describe('proxyApiImpl', () => {
12
+ let mockStore: Store<any>;
13
+ let mockDispatch: jest.Mock;
14
+ let proxyApi: ProxyApiImpl;
15
+
16
+ beforeEach(() => {
17
+ mockDispatch = jest.fn() as any;
18
+ mockStore = { dispatch: mockDispatch } as any;
19
+ proxyApi = new ProxyApiImpl(mockStore);
20
+ });
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // prepareRequest — URL building
24
+ // ---------------------------------------------------------------------------
25
+
26
+ describe('prepareRequest: URL building', () => {
27
+ it('should prepend /meta/proxy/ to a url, stripping the https scheme', () => {
28
+ const { url } = proxyApi.prepareRequest({ url: new URL('https://api.example.com/v1/things') });
29
+
30
+ expect(url).toBe(`${ PROXY_PREFIX }api.example.com/v1/things`);
31
+ });
32
+
33
+ it('should preserve http:/ (one slash) for plain-HTTP urls', () => {
34
+ const { url } = proxyApi.prepareRequest({ url: new URL('http://api.example.com:1234/v1/things') });
35
+
36
+ expect(url).toBe(`${ PROXY_PREFIX }http:/api.example.com:1234/v1/things`);
37
+ });
38
+
39
+ it('should preserve query params', () => {
40
+ const input = new URL('https://api.example.com/v1/things');
41
+
42
+ input.searchParams.set('region', 'us-east-1');
43
+ input.searchParams.set('page', '2');
44
+
45
+ const { url } = proxyApi.prepareRequest({ url: input });
46
+
47
+ expect(url).toBe(`${ PROXY_PREFIX }api.example.com/v1/things?region=us-east-1&page=2`);
48
+ });
49
+ });
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // prepareRequest — header building
53
+ // ---------------------------------------------------------------------------
54
+
55
+ describe('prepareRequest: header building', () => {
56
+ it('should set Accept to application/json by default', () => {
57
+ const { headers } = proxyApi.prepareRequest({ url: new URL('https://api.example.com') });
58
+
59
+ expect(headers['Accept']).toBe('application/json');
60
+ });
61
+
62
+ it('should override Accept when accept option is provided', () => {
63
+ const { headers } = proxyApi.prepareRequest({ url: new URL('https://api.example.com'), accept: 'text/plain' });
64
+
65
+ expect(headers['Accept']).toBe('text/plain');
66
+ });
67
+
68
+ it('should merge caller-supplied headers', () => {
69
+ const { headers } = proxyApi.prepareRequest({
70
+ url: new URL('https://api.example.com'),
71
+ headers: { 'X-Custom': 'value' },
72
+ });
73
+
74
+ expect(headers['X-Custom']).toBe('value');
75
+ });
76
+
77
+ it('should set x-api-auth-header with Bearer scheme for a token by default', () => {
78
+ const { headers } = proxyApi.prepareRequest({
79
+ url: new URL('https://api.example.com'),
80
+ authentication: { token: 'my-token' },
81
+ });
82
+
83
+ expect(headers['x-api-auth-header']).toBe('Bearer my-token');
84
+ });
85
+
86
+ it('should use the supplied authSigner as the scheme for a token', () => {
87
+ const { headers } = proxyApi.prepareRequest({
88
+ url: new URL('https://api.example.com'),
89
+ authentication: { token: 'encoded==', authSigner: 'basic' },
90
+ });
91
+
92
+ expect(headers['x-api-auth-header']).toBe('basic encoded==');
93
+ });
94
+
95
+ it('should set x-api-cattleauth-header with signer and credID', () => {
96
+ const { headers } = proxyApi.prepareRequest({
97
+ url: new URL('https://api.example.com'),
98
+ authentication: {
99
+ id: 'cattle-global-data:my-cred',
100
+ authSigner: 'bearer',
101
+ passwordField: 'token',
102
+ },
103
+ });
104
+
105
+ expect(headers['x-api-cattleauth-header']).toContain('bearer ');
106
+ expect(headers['x-api-cattleauth-header']).toContain('credID=cattle-global-data:my-cred');
107
+ expect(headers['x-api-cattleauth-header']).toContain('passwordField=token');
108
+ });
109
+
110
+ it('should include usernameField in x-api-cattleauth-header when provided', () => {
111
+ const { headers } = proxyApi.prepareRequest({
112
+ url: new URL('https://api.example.com'),
113
+ authentication: {
114
+ id: 'cattle-global-data:my-cred',
115
+ authSigner: 'basic',
116
+ usernameField: 'username',
117
+ passwordField: 'password',
118
+ },
119
+ });
120
+
121
+ expect(headers['x-api-cattleauth-header']).toContain('usernameField=username');
122
+ expect(headers['x-api-cattleauth-header']).toContain('passwordField=password');
123
+ });
124
+
125
+ it('should not set x-api-cattleauth-header when no authentication is provided', () => {
126
+ const { headers } = proxyApi.prepareRequest({ url: new URL('https://api.example.com') });
127
+
128
+ expect(headers['x-api-cattleauth-header']).toBeUndefined();
129
+ });
130
+
131
+ it('should not set x-api-auth-header when no authentication is provided', () => {
132
+ const { headers } = proxyApi.prepareRequest({ url: new URL('https://api.example.com') });
133
+
134
+ expect(headers['x-api-auth-header']).toBeUndefined();
135
+ });
136
+ });
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // request
140
+ // ---------------------------------------------------------------------------
141
+
142
+ describe('request', () => {
143
+ it('should dispatch management/request with the built url and headers', async() => {
144
+ mockDispatch.mockResolvedValue({ data: [] });
145
+
146
+ await proxyApi.request({ url: new URL('https://api.example.com/v1/items') });
147
+
148
+ expect(mockDispatch).toHaveBeenCalledWith(
149
+ 'management/request',
150
+ expect.objectContaining({
151
+ url: `${ PROXY_PREFIX }api.example.com/v1/items`,
152
+ redirectUnauthorized: false,
153
+ }),
154
+ { root: true }
155
+ );
156
+ });
157
+
158
+ it('should pass method through to management/request', async() => {
159
+ mockDispatch.mockResolvedValue({});
160
+
161
+ await proxyApi.request({ url: new URL('https://api.example.com/token'), method: 'POST' });
162
+
163
+ expect(mockDispatch).toHaveBeenCalledWith(
164
+ 'management/request',
165
+ expect.objectContaining({ method: 'POST' }),
166
+ { root: true }
167
+ );
168
+ });
169
+
170
+ it('should return the response directly when no postProcess is set', async() => {
171
+ const mockResponse = { items: ['a', 'b'] };
172
+
173
+ mockDispatch.mockResolvedValue(mockResponse);
174
+
175
+ const result = await proxyApi.request({ url: new URL('https://api.example.com/items') });
176
+
177
+ expect(result).toStrictEqual(mockResponse);
178
+ });
179
+
180
+ it('should call postProcess with the response and return its result', async() => {
181
+ const raw = { items: ['a'] };
182
+ const processed = { items: ['a', 'b'] };
183
+
184
+ mockDispatch.mockResolvedValue(raw);
185
+ const postProcess = jest.fn().mockResolvedValue(processed) as jest.Mock;
186
+
187
+ const result = await proxyApi.request({ url: new URL('https://api.example.com/items'), postProcess });
188
+
189
+ expect(postProcess).toHaveBeenCalledWith(raw);
190
+ expect(result).toStrictEqual(processed);
191
+ });
192
+
193
+ it('createDepaginator: should follow next links and merge results', async() => {
194
+ const page1 = {
195
+ items: ['a', 'b'],
196
+ links: { pages: { next: 'https://api.example.com/v1/items?page=2' } },
197
+ };
198
+ const page2 = {
199
+ items: ['c'],
200
+ links: { pages: { next: null } },
201
+ };
202
+
203
+ mockDispatch
204
+ .mockResolvedValueOnce(page1)
205
+ .mockResolvedValueOnce(page2);
206
+
207
+ const baseOptions = { url: new URL('https://api.example.com/v1/items') };
208
+ const result = await proxyApi.request({
209
+ ...baseOptions,
210
+ postProcess: createDepaginator(proxyApi, baseOptions, { mergeKey: 'items' }),
211
+ });
212
+
213
+ expect(result.items).toStrictEqual(['a', 'b', 'c']);
214
+ expect(mockDispatch).toHaveBeenCalledTimes(2);
215
+ });
216
+
217
+ it('createDepaginator: should use a custom nextUrlPath when provided', async() => {
218
+ const page1 = {
219
+ data: ['x'],
220
+ paging: { next: 'https://api.example.com/v1/things?cursor=abc' },
221
+ };
222
+ const page2 = {
223
+ data: ['y'],
224
+ paging: { next: null },
225
+ };
226
+
227
+ mockDispatch
228
+ .mockResolvedValueOnce(page1)
229
+ .mockResolvedValueOnce(page2);
230
+
231
+ const baseOptions = { url: new URL('https://api.example.com/v1/things') };
232
+ const result = await proxyApi.request({
233
+ ...baseOptions,
234
+ postProcess: createDepaginator(proxyApi, baseOptions, {
235
+ nextUrlPath: 'paging.next',
236
+ mergeKey: 'data',
237
+ }),
238
+ });
239
+
240
+ expect(result.data).toStrictEqual(['x', 'y']);
241
+ });
242
+ });
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // allowDomains
246
+ // ---------------------------------------------------------------------------
247
+
248
+ describe('allowDomains', () => {
249
+ it('should dispatch management/create with the correct type and routes', async() => {
250
+ const mockSave = jest.fn().mockResolvedValue({ spec: { routes: [] } });
251
+
252
+ mockDispatch.mockResolvedValue({ save: mockSave });
253
+
254
+ await proxyApi.allowDomains(['api.example.com', '%.amazonaws.com'], 'my-cr');
255
+
256
+ expect(mockDispatch).toHaveBeenCalledWith('management/create', {
257
+ type: 'management.cattle.io.proxyEndpoint',
258
+ metadata: { name: 'my-cr' },
259
+ spec: {
260
+ routes: [
261
+ { domain: 'api.example.com' },
262
+ { domain: '%.amazonaws.com' },
263
+ ],
264
+ },
265
+ });
266
+
267
+ expect(mockSave).toHaveBeenCalledTimes(1);
268
+ });
269
+
270
+ it('should use an generateName when none is supplied', async() => {
271
+ const mockSave = jest.fn().mockResolvedValue({});
272
+
273
+ mockDispatch.mockResolvedValue({ save: mockSave });
274
+
275
+ await proxyApi.allowDomains(['api.example.com']);
276
+
277
+ expect(mockDispatch).toHaveBeenCalledWith('management/create', expect.objectContaining({
278
+ metadata: { generateName: 'endpoints-' }, spec: { routes: [{ domain: 'api.example.com' }] }, type: 'management.cattle.io.proxyEndpoint'
279
+ }));
280
+ });
281
+ });
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // isDomainAllowed
285
+ // ---------------------------------------------------------------------------
286
+
287
+ describe('isDomainAllowed', () => {
288
+ it('should return true when the domain is found in a ProxyEndpoint CR', async() => {
289
+ mockDispatch.mockResolvedValue([
290
+ { spec: { routes: [{ domain: 'api.example.com' }, { domain: 'other.example.com' }] } },
291
+ ]);
292
+
293
+ const result = await proxyApi.isDomainAllowed('api.example.com');
294
+
295
+ expect(result).toBe(true);
296
+ });
297
+
298
+ it('should match case-insensitively', async() => {
299
+ mockDispatch.mockResolvedValue([
300
+ { spec: { routes: [{ domain: 'API.EXAMPLE.COM' }] } },
301
+ ]);
302
+
303
+ const result = await proxyApi.isDomainAllowed('api.example.com');
304
+
305
+ expect(result).toBe(true);
306
+ });
307
+
308
+ it('should return false when the domain is not found in any CR', async() => {
309
+ mockDispatch.mockResolvedValue([
310
+ { spec: { routes: [{ domain: 'other.example.com' }] } },
311
+ ]);
312
+
313
+ const result = await proxyApi.isDomainAllowed('api.example.com');
314
+
315
+ expect(result).toBe(false);
316
+ });
317
+
318
+ it('should return false when there are no ProxyEndpoint CRs', async() => {
319
+ mockDispatch.mockResolvedValue([]);
320
+
321
+ const result = await proxyApi.isDomainAllowed('api.example.com');
322
+
323
+ expect(result).toBe(false);
324
+ });
325
+
326
+ it('should return false when a CR has no routes', async() => {
327
+ mockDispatch.mockResolvedValue([{ spec: {} }]);
328
+
329
+ const result = await proxyApi.isDomainAllowed('api.example.com');
330
+
331
+ expect(result).toBe(false);
332
+ });
333
+ });
334
+
335
+ // ---------------------------------------------------------------------------
336
+ // hasProxyEndpoint
337
+ // ---------------------------------------------------------------------------
338
+
339
+ describe('hasProxyEndpoint', () => {
340
+ it('should return true when management/find resolves', async() => {
341
+ mockDispatch.mockResolvedValue({ metadata: { name: 'my-cr' } });
342
+
343
+ const result = await proxyApi.hasProxyEndpoint('my-cr');
344
+
345
+ expect(result).toBe(true);
346
+ expect(mockDispatch).toHaveBeenCalledWith('management/find', {
347
+ type: 'management.cattle.io.proxyEndpoint',
348
+ id: 'my-cr',
349
+ opt: { force: false },
350
+ });
351
+ });
352
+
353
+ it('should return false when management/find rejects with status 404', async() => {
354
+ mockDispatch.mockRejectedValue({ status: 404 });
355
+
356
+ const result = await proxyApi.hasProxyEndpoint('missing-cr');
357
+
358
+ expect(result).toBe(false);
359
+ });
360
+
361
+ it('should re-throw errors that are not 404', async() => {
362
+ const serverError = { status: 500, message: 'Internal Server Error' };
363
+
364
+ mockDispatch.mockRejectedValue(serverError);
365
+
366
+ await expect(proxyApi.hasProxyEndpoint('my-cr')).rejects.toStrictEqual(serverError);
367
+ });
368
+ });
369
+ });
@@ -1,23 +1,26 @@
1
1
  import { Store } from 'vuex';
2
2
  import {
3
- ModalApi, ShellApi, SlideInApi, NotificationApi, SystemApi
3
+ ModalApi, ShellApi, SlideInApi, NotificationApi, SystemApi, ProxyApi
4
4
  } from '@shell/apis/intf/shell';
5
5
  import { ModalApiImpl } from './modal';
6
6
  import { SlideInApiImpl } from './slide-in';
7
7
  import { NotificationApiImpl } from './notifications';
8
8
  import { SystemApiImpl } from './system';
9
+ import { ProxyApiImpl } from './proxy';
9
10
 
10
11
  export class ShellApiImpl implements ShellApi {
11
12
  private modalApi: ModalApi;
12
13
  private slideInApi: SlideInApi;
13
14
  private notificationApi: NotificationApi;
14
15
  private systemApi: SystemApi;
16
+ private proxyApi: ProxyApi;
15
17
 
16
18
  constructor(store: Store<any>) {
17
19
  this.modalApi = new ModalApiImpl(store);
18
20
  this.slideInApi = new SlideInApiImpl(store);
19
21
  this.notificationApi = new NotificationApiImpl(store);
20
22
  this.systemApi = new SystemApiImpl(store);
23
+ this.proxyApi = new ProxyApiImpl(store);
21
24
  }
22
25
 
23
26
  get modal(): ModalApi {
@@ -35,4 +38,8 @@ export class ShellApiImpl implements ShellApi {
35
38
  get system(): SystemApi {
36
39
  return this.systemApi;
37
40
  }
41
+
42
+ get proxy(): ProxyApi {
43
+ return this.proxyApi;
44
+ }
38
45
  }
@@ -14,9 +14,12 @@ export class ModalApiImpl implements ModalApi {
14
14
  *
15
15
  * Example:
16
16
  * ```ts
17
+ * import { useShell } from '@shell/apis';
17
18
  * import MyCustomModal from '@/components/MyCustomModal.vue';
18
19
  *
19
- * this.$shell.modal.open(MyCustomModal, {
20
+ * const shell = useShell();
21
+ *
22
+ * shell.modal.open(MyCustomModal, {
20
23
  * props: { title: 'Hello Modal' }
21
24
  * });
22
25
  * ```
@@ -14,12 +14,13 @@ export class NotificationApiImpl implements NotificationApi {
14
14
  *
15
15
  * Example:
16
16
  * ```ts
17
+ * import { useShell } from '@shell/apis';
17
18
  * import { NotificationLevel } from '@shell/types/notifications';
18
19
  *
19
- * this.$shell.notification.send(NotificationLevel.Success, 'Some notification title', 'Hello world! Success!', {})
20
- * ```
20
+ * const shell = useShell();
21
21
  *
22
- * For usage with the Composition API check usage guide [here](../../shell-api#using-composition-api-in-vue).
22
+ * shell.notification.send(NotificationLevel.Success, 'Some notification title', 'Hello world! Success!', {})
23
+ * ```
23
24
  *
24
25
  * @param level The `level` specifies the importance of the notification and determines the icon that is shown in the notification
25
26
  * @param title The notification title
@@ -45,10 +46,12 @@ export class NotificationApiImpl implements NotificationApi {
45
46
  *
46
47
  * Example:
47
48
  * ```ts
48
- * this.$shell.notification.updateProgress('some-notification-id', 80)
49
- * ```
49
+ * import { useShell } from '@shell/apis';
50
50
  *
51
- * For usage with the Composition API check usage guide [here](../../shell-api#using-composition-api-in-vue).
51
+ * const shell = useShell();
52
+ *
53
+ * shell.notification.updateProgress('some-notification-id', 80)
54
+ * ```
52
55
  *
53
56
  * @param notificationId Unique ID for the notification
54
57
  * @param progress Progress (0-100) for notifications of type `Task`