@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,140 @@
1
+ import { isIpv4Network, isIpv6Network, getVpcDisplayName, getSubnetDisplayName } from '@shell/utils/aws';
2
+
3
+ describe('aws utils', () => {
4
+ describe('isIpv4Network', () => {
5
+ it.each([
6
+ {
7
+ desc: 'returns true when CidrBlock is set',
8
+ network: { CidrBlock: '10.0.0.0/16' },
9
+ expected: true,
10
+ },
11
+ {
12
+ desc: 'returns false when CidrBlock is absent',
13
+ network: {},
14
+ expected: false,
15
+ },
16
+ {
17
+ desc: 'returns false when CidrBlock is an empty string',
18
+ network: { CidrBlock: '' },
19
+ expected: false,
20
+ },
21
+ {
22
+ desc: 'returns true when CidrBlock is a non-empty string',
23
+ network: { CidrBlock: '0.0.0.0/0' },
24
+ expected: true,
25
+ },
26
+ ])('$desc', ({ network, expected }) => {
27
+ expect(isIpv4Network(network as any)).toStrictEqual(expected);
28
+ });
29
+ });
30
+
31
+ describe('isIpv6Network', () => {
32
+ it.each([
33
+ {
34
+ desc: 'returns true when Ipv6CidrBlockAssociationSet has entries',
35
+ network: { Ipv6CidrBlockAssociationSet: [{ Ipv6CidrBlock: '::/0' }] },
36
+ expected: true,
37
+ },
38
+ {
39
+ desc: 'returns false when Ipv6CidrBlockAssociationSet is an empty array',
40
+ network: { Ipv6CidrBlockAssociationSet: [] },
41
+ expected: false,
42
+ },
43
+ {
44
+ desc: 'returns false when Ipv6CidrBlockAssociationSet is absent',
45
+ network: {},
46
+ expected: false,
47
+ },
48
+ {
49
+ desc: 'returns false when Ipv6CidrBlockAssociationSet is undefined',
50
+ network: { Ipv6CidrBlockAssociationSet: undefined },
51
+ expected: false,
52
+ },
53
+ ])('$desc', ({ network, expected }) => {
54
+ expect(isIpv6Network(network as any)).toStrictEqual(expected);
55
+ });
56
+ });
57
+
58
+ describe('getVpcDisplayName', () => {
59
+ it.each([
60
+ {
61
+ desc: 'returns "Name (VpcId)" when Name tag is present',
62
+ vpc: {
63
+ VpcId: 'vpc-abc123',
64
+ Tags: [{ Key: 'Name', Value: 'my-vpc' }],
65
+ },
66
+ expected: 'my-vpc (vpc-abc123)',
67
+ },
68
+ {
69
+ desc: 'returns VpcId alone when Tags array is empty',
70
+ vpc: {
71
+ VpcId: 'vpc-abc123',
72
+ Tags: [],
73
+ },
74
+ expected: 'vpc-abc123',
75
+ },
76
+ {
77
+ desc: 'returns VpcId alone when Tags is absent',
78
+ vpc: { VpcId: 'vpc-abc123' },
79
+ expected: 'vpc-abc123',
80
+ },
81
+ {
82
+ desc: 'returns VpcId alone when no tag has Key "Name"',
83
+ vpc: {
84
+ VpcId: 'vpc-abc123',
85
+ Tags: [{ Key: 'Env', Value: 'prod' }],
86
+ },
87
+ expected: 'vpc-abc123',
88
+ },
89
+ {
90
+ desc: 'uses the first Name tag when multiple tags exist',
91
+ vpc: {
92
+ VpcId: 'vpc-abc123',
93
+ Tags: [
94
+ { Key: 'Env', Value: 'prod' },
95
+ { Key: 'Name', Value: 'primary-vpc' },
96
+ ],
97
+ },
98
+ expected: 'primary-vpc (vpc-abc123)',
99
+ },
100
+ ])('$desc', ({ vpc, expected }) => {
101
+ expect(getVpcDisplayName(vpc as any)).toStrictEqual(expected);
102
+ });
103
+ });
104
+
105
+ describe('getSubnetDisplayName', () => {
106
+ it.each([
107
+ {
108
+ desc: 'returns "Name (SubnetId)" when Name tag is present',
109
+ subnet: {
110
+ SubnetId: 'subnet-xyz789',
111
+ Tags: [{ Key: 'Name', Value: 'my-subnet' }],
112
+ },
113
+ expected: 'my-subnet (subnet-xyz789)',
114
+ },
115
+ {
116
+ desc: 'returns SubnetId alone when Tags array is empty',
117
+ subnet: {
118
+ SubnetId: 'subnet-xyz789',
119
+ Tags: [],
120
+ },
121
+ expected: 'subnet-xyz789',
122
+ },
123
+ {
124
+ desc: 'returns SubnetId alone when Tags is absent',
125
+ subnet: { SubnetId: 'subnet-xyz789' },
126
+ expected: 'subnet-xyz789',
127
+ },
128
+ {
129
+ desc: 'returns SubnetId alone when no tag has Key "Name"',
130
+ subnet: {
131
+ SubnetId: 'subnet-xyz789',
132
+ Tags: [{ Key: 'Zone', Value: 'us-east-1a' }],
133
+ },
134
+ expected: 'subnet-xyz789',
135
+ },
136
+ ])('$desc', ({ subnet, expected }) => {
137
+ expect(getSubnetDisplayName(subnet as any)).toStrictEqual(expected);
138
+ });
139
+ });
140
+ });
@@ -0,0 +1,176 @@
1
+ import { getIndividualBanners, overlayIndividualBanners } from '@shell/utils/banners';
2
+
3
+ describe('banners', () => {
4
+ describe('getIndividualBanners', () => {
5
+ it('returns an empty object when no settings match', () => {
6
+ const store = { getters: { 'management/all': () => [] } };
7
+
8
+ expect(getIndividualBanners(store)).toStrictEqual({});
9
+ });
10
+
11
+ it('ignores settings without a value', () => {
12
+ const store = {
13
+ getters: {
14
+ 'management/all': () => [
15
+ {
16
+ id: 'ui-banner-header',
17
+ value: '',
18
+ },
19
+ ],
20
+ },
21
+ };
22
+
23
+ expect(getIndividualBanners(store)).toStrictEqual({});
24
+ });
25
+
26
+ it('ignores settings not in the known banner id list', () => {
27
+ const store = {
28
+ getters: {
29
+ 'management/all': () => [
30
+ {
31
+ id: 'ui-some-other-setting',
32
+ value: 'some-value',
33
+ },
34
+ ],
35
+ },
36
+ };
37
+
38
+ expect(getIndividualBanners(store)).toStrictEqual({});
39
+ });
40
+
41
+ it.each([
42
+ {
43
+ desc: 'maps ui-banner-header to bannerHeader',
44
+ id: 'ui-banner-header',
45
+ expectedKey: 'bannerHeader',
46
+ },
47
+ {
48
+ desc: 'maps ui-banner-footer to bannerFooter',
49
+ id: 'ui-banner-footer',
50
+ expectedKey: 'bannerFooter',
51
+ },
52
+ {
53
+ desc: 'maps ui-banner-login-consent to bannerConsent',
54
+ id: 'ui-banner-login-consent',
55
+ expectedKey: 'bannerConsent',
56
+ },
57
+ ])('$desc', ({ id, expectedKey }) => {
58
+ const setting = {
59
+ id,
60
+ value: '{"text":"hello"}',
61
+ };
62
+ const store = { getters: { 'management/all': () => [setting] } };
63
+ const result = getIndividualBanners(store);
64
+
65
+ expect(Object.keys(result)).toStrictEqual([expectedKey]);
66
+ expect(result[expectedKey]).toStrictEqual(setting);
67
+ });
68
+
69
+ it('collects multiple matching banner settings', () => {
70
+ const headerSetting = {
71
+ id: 'ui-banner-header',
72
+ value: '{"text":"header"}',
73
+ };
74
+ const footerSetting = {
75
+ id: 'ui-banner-footer',
76
+ value: '{"text":"footer"}',
77
+ };
78
+ const store = { getters: { 'management/all': () => [headerSetting, footerSetting] } };
79
+ const result = getIndividualBanners(store);
80
+
81
+ expect(result['bannerHeader']).toStrictEqual(headerSetting);
82
+ expect(result['bannerFooter']).toStrictEqual(footerSetting);
83
+ });
84
+ });
85
+
86
+ describe('overlayIndividualBanners', () => {
87
+ it('does nothing when banners object is empty', () => {
88
+ const parsedBanner = { showHeader: 'false' };
89
+
90
+ overlayIndividualBanners(parsedBanner, {});
91
+ expect(parsedBanner).toStrictEqual({ showHeader: 'false' });
92
+ });
93
+
94
+ it('overlays bannerHeader and sets showHeader to true', () => {
95
+ const parsedBanner: Record<string, unknown> = {};
96
+ const banners = { bannerHeader: { value: '{"text":"Top banner","color":"#fff"}' } };
97
+
98
+ overlayIndividualBanners(parsedBanner, banners);
99
+
100
+ expect(parsedBanner['bannerHeader']).toStrictEqual({ text: 'Top banner', color: '#fff' });
101
+ expect(parsedBanner['showHeader']).toStrictEqual('true');
102
+ });
103
+
104
+ it('overlays bannerFooter and sets showFooter to true', () => {
105
+ const parsedBanner: Record<string, unknown> = {};
106
+ const banners = { bannerFooter: { value: '{"text":"Footer"}' } };
107
+
108
+ overlayIndividualBanners(parsedBanner, banners);
109
+
110
+ expect(parsedBanner['bannerFooter']).toStrictEqual({ text: 'Footer' });
111
+ expect(parsedBanner['showFooter']).toStrictEqual('true');
112
+ });
113
+
114
+ it('overlays bannerConsent and sets showConsent to true', () => {
115
+ const parsedBanner: Record<string, unknown> = {};
116
+ const banners = { bannerConsent: { value: '{"text":"Consent"}' } };
117
+
118
+ overlayIndividualBanners(parsedBanner, banners);
119
+
120
+ expect(parsedBanner['bannerConsent']).toStrictEqual({ text: 'Consent' });
121
+ expect(parsedBanner['showConsent']).toStrictEqual('true');
122
+ });
123
+
124
+ it('overlays multiple banners at once', () => {
125
+ const parsedBanner: Record<string, unknown> = {};
126
+ const banners = {
127
+ bannerHeader: { value: '{"text":"Header"}' },
128
+ bannerFooter: { value: '{"text":"Footer"}' },
129
+ bannerConsent: { value: '{"text":"Consent"}' },
130
+ };
131
+
132
+ overlayIndividualBanners(parsedBanner, banners);
133
+
134
+ expect(parsedBanner['bannerHeader']).toStrictEqual({ text: 'Header' });
135
+ expect(parsedBanner['bannerFooter']).toStrictEqual({ text: 'Footer' });
136
+ expect(parsedBanner['bannerConsent']).toStrictEqual({ text: 'Consent' });
137
+ expect(parsedBanner['showHeader']).toStrictEqual('true');
138
+ expect(parsedBanner['showFooter']).toStrictEqual('true');
139
+ expect(parsedBanner['showConsent']).toStrictEqual('true');
140
+ });
141
+
142
+ it('silently skips a banner with invalid JSON value', () => {
143
+ const parsedBanner: Record<string, unknown> = { existing: 'value' };
144
+ const banners = { bannerHeader: { value: 'not-valid-json{{{' } };
145
+
146
+ overlayIndividualBanners(parsedBanner, banners);
147
+
148
+ expect(parsedBanner).toStrictEqual({ existing: 'value' });
149
+ });
150
+
151
+ it('skips invalid banner but still processes valid ones', () => {
152
+ const parsedBanner: Record<string, unknown> = {};
153
+ const banners = {
154
+ bannerHeader: { value: 'bad json' },
155
+ bannerFooter: { value: '{"text":"Footer"}' },
156
+ };
157
+
158
+ overlayIndividualBanners(parsedBanner, banners);
159
+
160
+ expect(parsedBanner['bannerHeader']).toBeUndefined();
161
+ expect(parsedBanner['showHeader']).toBeUndefined();
162
+ expect(parsedBanner['bannerFooter']).toStrictEqual({ text: 'Footer' });
163
+ expect(parsedBanner['showFooter']).toStrictEqual('true');
164
+ });
165
+
166
+ it('overwrites existing parsedBanner field when overlaying', () => {
167
+ const parsedBanner: Record<string, unknown> = { bannerHeader: { text: 'Old' }, showHeader: 'false' };
168
+ const banners = { bannerHeader: { value: '{"text":"New"}' } };
169
+
170
+ overlayIndividualBanners(parsedBanner, banners);
171
+
172
+ expect(parsedBanner['bannerHeader']).toStrictEqual({ text: 'New' });
173
+ expect(parsedBanner['showHeader']).toStrictEqual('true');
174
+ });
175
+ });
176
+ });
@@ -1,4 +1,7 @@
1
- import { compareChartVersions } from '@shell/utils/chart';
1
+ import { compareChartVersions, getStandaloneReadmeUrl } from '@shell/utils/chart';
2
+ import {
3
+ CHART, REPO, REPO_TYPE, VERSION, DEPRECATED
4
+ } from '@shell/config/query-params';
2
5
 
3
6
  describe('compareChartVersions', () => {
4
7
  describe('standard SemVer Comparison', () => {
@@ -94,3 +97,63 @@ describe('compareChartVersions', () => {
94
97
  });
95
98
  });
96
99
  });
100
+
101
+ describe('getStandaloneReadmeUrl', () => {
102
+ it('builds readme route with defaults', () => {
103
+ const router = { resolve: jest.fn(() => ({ href: '/c/local/readme' })) };
104
+
105
+ const href = getStandaloneReadmeUrl(router as any, {
106
+ cluster: 'local',
107
+ repoType: 'cluster',
108
+ repoName: 'rancher-charts',
109
+ chartName: 'my-chart',
110
+ versionName: '1.2.3',
111
+ deprecated: 'false',
112
+ });
113
+
114
+ expect(href).toBe('/c/local/readme');
115
+ expect(router.resolve).toHaveBeenCalledWith({
116
+ name: 'readme',
117
+ params: { cluster: 'local' },
118
+ query: {
119
+ [REPO_TYPE]: 'cluster',
120
+ [REPO]: 'rancher-charts',
121
+ [CHART]: 'my-chart',
122
+ [VERSION]: '1.2.3',
123
+ [DEPRECATED]: 'false',
124
+ showAppReadme: 'true',
125
+ hideReadmeFirstTitle: 'true'
126
+ }
127
+ });
128
+ });
129
+
130
+ it('builds readme route with explicit options', () => {
131
+ const router = { resolve: jest.fn(() => ({ href: '/c/c-abc/readme' })) };
132
+
133
+ const href = getStandaloneReadmeUrl(router as any, {
134
+ cluster: 'c-abc',
135
+ repoType: 'cluster',
136
+ repoName: 'partner-charts',
137
+ chartName: 'other-chart',
138
+ versionName: '9.9.9',
139
+ deprecated: 'true',
140
+ showAppReadme: false,
141
+ hideReadmeFirstTitle: false,
142
+ });
143
+
144
+ expect(href).toBe('/c/c-abc/readme');
145
+ expect(router.resolve).toHaveBeenCalledWith({
146
+ name: 'readme',
147
+ params: { cluster: 'c-abc' },
148
+ query: {
149
+ [REPO_TYPE]: 'cluster',
150
+ [REPO]: 'partner-charts',
151
+ [CHART]: 'other-chart',
152
+ [VERSION]: '9.9.9',
153
+ [DEPRECATED]: 'true',
154
+ showAppReadme: 'false',
155
+ hideReadmeFirstTitle: 'false'
156
+ }
157
+ });
158
+ });
159
+ });
@@ -0,0 +1,226 @@
1
+ import {
2
+ contrastColor,
3
+ parseColor,
4
+ textColor,
5
+ hexToRgb,
6
+ mapStandardColors,
7
+ rgbToRgb,
8
+ colorToRgb,
9
+ normalizeHex,
10
+ createCssVars,
11
+ } from '@shell/utils/color';
12
+
13
+ describe('shell/utils/color', () => {
14
+ describe('hexToRgb', () => {
15
+ it.each([
16
+ {
17
+ desc: 'a lowercase hex color',
18
+ input: '#ff0080',
19
+ expected: {
20
+ r: 255, g: 0, b: 128
21
+ },
22
+ },
23
+ {
24
+ desc: 'a hex color without # prefix',
25
+ input: 'ff0080',
26
+ expected: {
27
+ r: 255, g: 0, b: 128
28
+ },
29
+ },
30
+ {
31
+ desc: 'an uppercase hex color',
32
+ input: '#AABBCC',
33
+ expected: {
34
+ r: 170, g: 187, b: 204
35
+ },
36
+ },
37
+ ])('parses $desc', ({ input, expected }) => {
38
+ expect(hexToRgb(input)).toStrictEqual(expected);
39
+ });
40
+
41
+ it.each([
42
+ { desc: 'an invalid hex string', input: 'not-a-color' },
43
+ { desc: 'a short 3-character hex (not supported by hexToRgb)', input: '#abc' },
44
+ ])('returns null for $desc', ({ input }) => {
45
+ expect(hexToRgb(input)).toBeNull();
46
+ });
47
+ });
48
+
49
+ describe('rgbToRgb', () => {
50
+ it.each([
51
+ {
52
+ desc: 'a valid rgb() string with spaces',
53
+ input: 'rgb(10, 20, 30)',
54
+ expected: {
55
+ r: 10, g: 20, b: 30
56
+ },
57
+ },
58
+ {
59
+ desc: 'an rgb() string without spaces',
60
+ input: 'rgb(0,128,255)',
61
+ expected: {
62
+ r: 0, g: 128, b: 255
63
+ },
64
+ },
65
+ ])('parses $desc', ({ input, expected }) => {
66
+ expect(rgbToRgb(input)).toStrictEqual(expected);
67
+ });
68
+
69
+ it.each([
70
+ { desc: 'an rgba string', input: 'rgba(10,20,30,0.5)' },
71
+ { desc: 'a plain color name', input: 'red' },
72
+ ])('returns null for $desc', ({ input }) => {
73
+ expect(rgbToRgb(input)).toBeNull();
74
+ });
75
+ });
76
+
77
+ describe('colorToRgb', () => {
78
+ it('converts a hex color to rgb object', () => {
79
+ expect(colorToRgb('#ff0000')).toStrictEqual({
80
+ r: 255, g: 0, b: 0
81
+ });
82
+ });
83
+
84
+ it('converts an rgb() string to rgb object', () => {
85
+ expect(colorToRgb('rgb(0, 0, 255)')).toStrictEqual({
86
+ r: 0, g: 0, b: 255
87
+ });
88
+ });
89
+
90
+ it('returns { r:0, g:0, b:0 } for an unrecognized format', () => {
91
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
92
+
93
+ expect(colorToRgb('hsl(0,100%,50%)')).toStrictEqual({
94
+ r: 0, g: 0, b: 0
95
+ });
96
+ consoleSpy.mockRestore();
97
+ });
98
+
99
+ it('warns for unrecognized color formats', () => {
100
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
101
+
102
+ colorToRgb('blue');
103
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Unable to parse color: blue'));
104
+ consoleSpy.mockRestore();
105
+ });
106
+ });
107
+
108
+ describe('normalizeHex', () => {
109
+ it.each([
110
+ {
111
+ desc: '3-char hex with # to 6-char', input: '#abc', expected: '#aabbcc'
112
+ },
113
+ {
114
+ desc: '3-char hex without # to 6-char', input: 'abc', expected: 'aabbcc'
115
+ },
116
+ {
117
+ desc: '6-char hex with # unchanged', input: '#aabbcc', expected: '#aabbcc'
118
+ },
119
+ {
120
+ desc: '6-char hex without # unchanged', input: 'aabbcc', expected: 'aabbcc'
121
+ },
122
+ ])('handles $desc', ({ input, expected }) => {
123
+ expect(normalizeHex(input)).toStrictEqual(expected);
124
+ });
125
+ });
126
+
127
+ describe('mapStandardColors', () => {
128
+ it.each([
129
+ {
130
+ desc: '"black" to its hex value', input: 'black', expected: '#000000'
131
+ },
132
+ {
133
+ desc: '"white" to its hex value', input: 'white', expected: '#ffffff'
134
+ },
135
+ {
136
+ desc: 'an unknown color string unchanged', input: '#123456', expected: '#123456'
137
+ },
138
+ ])('maps $desc', ({ input, expected }) => {
139
+ expect(mapStandardColors(input)).toStrictEqual(expected);
140
+ });
141
+ });
142
+
143
+ describe('textColor', () => {
144
+ it.each([
145
+ {
146
+ desc: '"black" for a light color (#ffffff)', input: '#ffffff', expected: 'black'
147
+ },
148
+ {
149
+ desc: '"white" for a dark color (#000000)', input: '#000000', expected: 'white'
150
+ },
151
+ {
152
+ desc: '"black" for brightness above threshold (rgb 200,200,200)', input: 'rgb(200, 200, 200)', expected: 'black'
153
+ },
154
+ {
155
+ desc: '"white" for brightness below threshold (rgb 50,50,50)', input: 'rgb(50, 50, 50)', expected: 'white'
156
+ },
157
+ ])('returns $desc', ({ input, expected }) => {
158
+ const color = parseColor(input);
159
+
160
+ expect(textColor(color)).toStrictEqual(expected);
161
+ });
162
+ });
163
+
164
+ describe('contrastColor', () => {
165
+ it('returns light text on a very dark background (light theme)', () => {
166
+ expect(contrastColor('#000000')).toStrictEqual('rgb(255, 255, 255)');
167
+ });
168
+
169
+ it('returns dark text on a very light background (light theme)', () => {
170
+ expect(contrastColor('#ffffff')).toStrictEqual('rgb(20, 20, 25)');
171
+ });
172
+
173
+ it('uses custom contrast options when provided', () => {
174
+ const opts = { dark: '#000000', light: '#ffffff' };
175
+
176
+ // white background → dark text has higher contrast
177
+ expect(contrastColor('#ffffff', opts)).toStrictEqual('#000000');
178
+ });
179
+ });
180
+
181
+ describe('createCssVars', () => {
182
+ it('returns an object with expected CSS variable keys for light theme', () => {
183
+ const vars = createCssVars('#4a90d9', 'light', 'primary');
184
+ const expectedKeys = [
185
+ '--primary',
186
+ '--primary-text ',
187
+ '--primary-hover-bg',
188
+ '--primary-active-bg',
189
+ '--primary-active-text',
190
+ '--primary-border',
191
+ '--primary-banner-bg',
192
+ '--primary-light-bg',
193
+ '--primary-keyboard-focus',
194
+ ];
195
+
196
+ expect(Object.keys(vars)).toStrictEqual(expectedKeys);
197
+ });
198
+
199
+ it('sets --primary to the input color', () => {
200
+ const vars = createCssVars('#4a90d9');
201
+
202
+ expect(vars['--primary']).toStrictEqual('#4a90d9');
203
+ });
204
+
205
+ it('sets --primary-border to the input color', () => {
206
+ const vars = createCssVars('#4a90d9');
207
+
208
+ expect(vars['--primary-border']).toStrictEqual('#4a90d9');
209
+ });
210
+
211
+ it('uses a custom name prefix', () => {
212
+ const vars = createCssVars('#ff0000', 'light', 'accent');
213
+
214
+ expect(vars).toHaveProperty('--accent');
215
+ expect(vars).toHaveProperty('--accent-hover-bg');
216
+ });
217
+
218
+ it('produces opacity-based banner-bg and light-bg values', () => {
219
+ const vars = createCssVars('#ff0000');
220
+
221
+ // opacity(color, 0.15) and opacity(color, 0.05) produce rgba strings
222
+ expect(vars['--primary-banner-bg']).toContain('rgba');
223
+ expect(vars['--primary-light-bg']).toContain('rgba');
224
+ });
225
+ });
226
+ });