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

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 (258) hide show
  1. package/assets/styles/global/_layout.scss +4 -0
  2. package/assets/translations/en-us.yaml +144 -41
  3. package/assets/translations/zh-hans.yaml +1 -7
  4. package/chart/monitoring/ClusterSelector.vue +0 -21
  5. package/chart/monitoring/prometheus/index.vue +6 -3
  6. package/components/CruResource.vue +161 -14
  7. package/components/ExplorerMembers.vue +8 -4
  8. package/components/ExplorerProjectsNamespaces.vue +10 -6
  9. package/components/GrowlManager.vue +4 -0
  10. package/components/MgmtNodeList.vue +184 -0
  11. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  12. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  13. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  14. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  15. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  16. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  17. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  18. package/components/ResourceDetail/index.vue +1 -1
  19. package/components/ResourceList/Masthead.vue +7 -1
  20. package/components/ResourceList/index.vue +82 -1
  21. package/components/RichTranslation.vue +5 -2
  22. package/components/Setting.vue +1 -0
  23. package/components/SubtleLink.vue +31 -6
  24. package/components/Tabbed/Tab.vue +29 -3
  25. package/components/Tabbed/index.vue +25 -3
  26. package/components/TableOfContents/TableOfContents.vue +109 -0
  27. package/components/TableOfContents/composables.ts +258 -0
  28. package/components/Window/ContainerShell.vue +21 -11
  29. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  30. package/components/Wizard.vue +9 -4
  31. package/components/fleet/AppCoChartGrid.vue +401 -0
  32. package/components/fleet/AppCoEmptyState.vue +127 -0
  33. package/components/fleet/AppCoPageHeader.vue +119 -0
  34. package/components/fleet/AppCoVersionSelect.vue +70 -0
  35. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  36. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  37. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  38. package/components/fleet/FleetIntro.vue +7 -3
  39. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  40. package/components/fleet/FleetSecretSelector.vue +5 -3
  41. package/components/fleet/FleetValuesFrom.vue +8 -2
  42. package/components/fleet/GitRepoTargetTab.vue +0 -2
  43. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  44. package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
  45. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  46. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  47. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  48. package/components/fleet/HelmOpTargetTab.vue +64 -60
  49. package/components/fleet/HelmOpValuesTab.vue +129 -105
  50. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  51. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  52. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  53. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  54. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  55. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  56. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  57. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  58. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  59. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  60. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  61. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  62. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  63. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  64. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  65. package/components/fleet/dashboard/Empty.vue +8 -4
  66. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  67. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  68. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  69. package/components/form/ArrayList.vue +61 -4
  70. package/components/form/KeyValue.vue +23 -2
  71. package/components/form/LabeledSelect.vue +39 -1
  72. package/components/form/Labels.vue +22 -3
  73. package/components/form/NameNsDescription.vue +13 -5
  74. package/components/form/ResourceTabs/index.vue +1 -0
  75. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  76. package/components/formatter/InternalExternalIP.vue +10 -4
  77. package/components/formatter/ServiceTargets.vue +26 -7
  78. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  79. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  80. package/components/nav/Header.vue +4 -0
  81. package/components/nav/TopLevelMenu.vue +7 -2
  82. package/components/nav/__tests__/Header.test.ts +15 -0
  83. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  84. package/components/templates/default.vue +9 -4
  85. package/components/templates/home.vue +9 -4
  86. package/components/templates/plain.vue +9 -4
  87. package/composables/useHelmOpResources.test.ts +56 -0
  88. package/composables/useHelmOpResources.ts +32 -0
  89. package/composables/useStateColor.test.ts +325 -0
  90. package/composables/useStateColor.ts +128 -0
  91. package/config/home-links.js +1 -1
  92. package/config/labels-annotations.js +1 -0
  93. package/config/product/explorer.js +17 -4
  94. package/config/product/manager.js +2 -0
  95. package/config/router/index.js +16 -0
  96. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  97. package/config/router/navigation-guards/authentication.js +10 -4
  98. package/config/router/routes.js +20 -6
  99. package/config/settings.ts +0 -2
  100. package/config/table-headers.js +3 -4
  101. package/config/types.js +9 -0
  102. package/core/plugin-products-base.ts +3 -3
  103. package/core/plugin-types.ts +83 -30
  104. package/core/plugin.ts +3 -0
  105. package/core/types-provisioning.ts +34 -1
  106. package/core/types.ts +15 -2
  107. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  108. package/detail/__tests__/workload.test.ts +3 -152
  109. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  110. package/detail/provisioning.cattle.io.cluster.vue +30 -4
  111. package/detail/workload/index.vue +12 -55
  112. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  113. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
  114. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  115. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  116. package/edit/auth/__tests__/azuread.test.ts +34 -9
  117. package/edit/auth/__tests__/github.test.ts +234 -0
  118. package/edit/auth/__tests__/oidc.test.ts +26 -6
  119. package/edit/auth/__tests__/saml.test.ts +196 -0
  120. package/edit/auth/azuread.vue +128 -95
  121. package/edit/auth/github.vue +72 -13
  122. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  123. package/edit/auth/ldap/config.vue +8 -0
  124. package/edit/auth/ldap/index.vue +75 -1
  125. package/edit/auth/oidc.vue +119 -73
  126. package/edit/auth/saml.vue +76 -12
  127. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  128. package/edit/fleet.cattle.io.helmop.vue +491 -136
  129. package/edit/management.cattle.io.user.vue +5 -2
  130. package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
  131. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  132. package/list/group.principal.vue +5 -4
  133. package/list/harvesterhci.io.management.cluster.vue +8 -9
  134. package/list/management.cattle.io.user.vue +12 -9
  135. package/list/provisioning.cattle.io.cluster.vue +16 -10
  136. package/mixins/__tests__/auth-config.test.ts +90 -0
  137. package/mixins/__tests__/chart.test.ts +94 -0
  138. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  139. package/mixins/auth-config.js +7 -0
  140. package/mixins/chart.js +11 -2
  141. package/mixins/child-hook.js +12 -6
  142. package/mixins/create-edit-view/impl.js +5 -3
  143. package/mixins/resource-fetch-api-pagination.js +21 -1
  144. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  145. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  146. package/models/__tests__/fleet-application.test.ts +175 -0
  147. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  148. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  149. package/models/__tests__/management.cattle.io.node.ts +22 -0
  150. package/models/__tests__/namespace.test.ts +36 -0
  151. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
  152. package/models/__tests__/workload.test.ts +401 -26
  153. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  154. package/models/compliance.cattle.io.clusterscan.js +39 -4
  155. package/models/fleet-application.js +4 -0
  156. package/models/fleet.cattle.io.helmop.js +20 -1
  157. package/models/management.cattle.io.cluster.js +18 -2
  158. package/models/management.cattle.io.node.js +44 -3
  159. package/models/namespace.js +1 -1
  160. package/models/pod.js +33 -1
  161. package/models/provisioning.cattle.io.cluster.js +5 -5
  162. package/models/workload.js +108 -13
  163. package/models/workload.service.js +5 -0
  164. package/package.json +14 -13
  165. package/pages/about.vue +5 -6
  166. package/pages/auth/login.vue +0 -35
  167. package/pages/auth/setup.vue +11 -0
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  172. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  173. package/pages/c/_cluster/apps/charts/install.vue +122 -116
  174. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  175. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  176. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  177. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  178. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  179. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  180. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  181. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  182. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  183. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  184. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  185. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  186. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  187. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  188. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  189. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  190. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  191. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  192. package/pages/c/_cluster/fleet/index.vue +2 -2
  193. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  194. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  195. package/pages/fail-whale.vue +16 -11
  196. package/pages/home.vue +16 -46
  197. package/plugins/clean-html.d.ts +9 -0
  198. package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
  199. package/plugins/dashboard-store/resource-class.js +62 -7
  200. package/plugins/steve/__tests__/actions.test.ts +212 -0
  201. package/plugins/steve/actions.js +96 -0
  202. package/plugins/steve/steve-pagination-utils.ts +1 -1
  203. package/rancher-components/Accordion/Accordion.vue +53 -9
  204. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  205. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  206. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  207. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  208. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  209. package/rancher-components/RcButton/RcButton.vue +94 -15
  210. package/rancher-components/RcButton/types.ts +3 -0
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  213. package/rancher-components/RcSection/RcSection.vue +28 -3
  214. package/scripts/extension/helm/package/Dockerfile +1 -1
  215. package/scripts/test-plugins-build.sh +2 -1
  216. package/store/__tests__/notifications.test.ts +434 -0
  217. package/store/catalog.js +57 -0
  218. package/store/plugins.js +7 -4
  219. package/types/components/buttonGroup.ts +5 -0
  220. package/types/shell/index.d.ts +104 -70
  221. package/utils/__tests__/auth.test.ts +273 -0
  222. package/utils/__tests__/computed.test.ts +193 -0
  223. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  224. package/utils/__tests__/dom.test.ts +81 -0
  225. package/utils/__tests__/duration.test.ts +37 -1
  226. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  227. package/utils/__tests__/fleet-appco.test.ts +312 -0
  228. package/utils/__tests__/monitoring.test.ts +130 -0
  229. package/utils/__tests__/object.test.ts +22 -0
  230. package/utils/__tests__/platform.test.ts +91 -0
  231. package/utils/__tests__/position.test.ts +237 -0
  232. package/utils/__tests__/provider.test.ts +51 -1
  233. package/utils/__tests__/queue.test.ts +232 -0
  234. package/utils/__tests__/release-notes.test.ts +221 -0
  235. package/utils/__tests__/router.test.js +254 -1
  236. package/utils/__tests__/select.test.ts +208 -0
  237. package/utils/__tests__/time.test.ts +265 -1
  238. package/utils/__tests__/title.test.ts +47 -0
  239. package/utils/__tests__/width.test.ts +53 -0
  240. package/utils/__tests__/window.test.ts +158 -0
  241. package/utils/__tests__/xccdf.test.ts +126 -6
  242. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  243. package/utils/crypto/__tests__/index.test.ts +144 -0
  244. package/utils/duration.ts +104 -0
  245. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  246. package/utils/dynamic-content/info.ts +2 -1
  247. package/utils/error.js +13 -0
  248. package/utils/fleet-appco.ts +323 -0
  249. package/utils/object.js +22 -2
  250. package/utils/provider.ts +12 -0
  251. package/utils/validators/__tests__/container-images.test.ts +104 -0
  252. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  253. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  254. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  255. package/utils/xccdf.ts +39 -42
  256. package/vue.config.js +1 -1
  257. package/pages/support/index.vue +0 -264
  258. package/utils/duration.js +0 -43
@@ -0,0 +1,310 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, onMounted } from 'vue';
3
+ import { useStore } from 'vuex';
4
+ import { useRoute, useRouter } from 'vue-router';
5
+ import { useI18n } from '@shell/composables/useI18n';
6
+ import { AUTH_TYPE, SECRET } from '@shell/config/types';
7
+ import {
8
+ FLEET_APPCO_AUTH_GENERATE_NAME,
9
+ createAppCoAuthSecret,
10
+ ensureAppCoResources,
11
+ } from '@shell/utils/fleet-appco';
12
+ import { CATALOG } from '@shell/config/labels-annotations';
13
+ import { SECRET_TYPES } from '@shell/config/secret';
14
+ import LabeledSelect from '@shell/components/form/LabeledSelect';
15
+ import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
16
+ import Banner from '@components/Banner/Banner.vue';
17
+ import Loading from '@shell/components/Loading';
18
+ import { RcButton } from '@components/RcButton';
19
+ import AppCoPageHeader from '@shell/components/fleet/AppCoPageHeader.vue';
20
+
21
+ const ADD_NEW_TOKEN = '__add_new__';
22
+
23
+ interface SelectOption {
24
+ label: string;
25
+ value?: string;
26
+ disabled?: boolean;
27
+ kind?: string;
28
+ }
29
+
30
+ const store = useStore();
31
+ const route = useRoute();
32
+ const router = useRouter();
33
+ const { t } = useI18n(store);
34
+
35
+ const loading = ref(true);
36
+ const saving = ref(false);
37
+ const createErrors = ref<string[]>([]);
38
+ const selectedSecret = ref('');
39
+ const username = ref('');
40
+ const accessToken = ref('');
41
+
42
+ const namespace = computed(() => store.getters.workspace);
43
+
44
+ const existingSecrets = computed(() => {
45
+ const ns = namespace.value;
46
+ const allSecrets = store.getters[`${ CATALOG._MANAGEMENT }/all`](SECRET) || [];
47
+
48
+ return allSecrets.filter((s: any) => {
49
+ return s.metadata?.namespace === ns &&
50
+ s.metadata?.name?.startsWith(FLEET_APPCO_AUTH_GENERATE_NAME) &&
51
+ s._type === SECRET_TYPES.BASIC;
52
+ });
53
+ });
54
+
55
+ const secretOptions = computed(() => {
56
+ const existing = existingSecrets.value.map((s: any) => {
57
+ const name = s.metadata.name;
58
+ const user = s.decodedData?.username || '';
59
+ const label = user ? `${ name } (HTTP Basic Auth: ${ user })` : `${ name } (HTTP Basic Auth)`;
60
+
61
+ return { label, value: name };
62
+ });
63
+
64
+ const out: SelectOption[] = [
65
+ {
66
+ label: t('fleet.appCo.credentials.addNewToken'),
67
+ value: ADD_NEW_TOKEN,
68
+ kind: 'highlighted',
69
+ },
70
+ ];
71
+
72
+ if (existing.length) {
73
+ out.push({
74
+ label: 'divider',
75
+ disabled: true,
76
+ kind: 'divider',
77
+ });
78
+ out.push({
79
+ label: t('fleet.appCo.credentials.chooseExisting'),
80
+ disabled: true,
81
+ kind: 'title',
82
+ });
83
+ out.push(...existing);
84
+ }
85
+
86
+ return out;
87
+ });
88
+
89
+ const isAddNew = computed(() => selectedSecret.value === ADD_NEW_TOKEN);
90
+ const hasNoSecrets = computed(() => !loading.value && existingSecrets.value.length === 0 && !route.query.secret);
91
+
92
+ const canSave = computed(() => {
93
+ if (isAddNew.value || hasNoSecrets.value) {
94
+ return !!username.value && !!accessToken.value;
95
+ }
96
+
97
+ return !!selectedSecret.value;
98
+ });
99
+
100
+ const originalSecret = (route.query.secret as string) || '';
101
+
102
+ const cancel = () => {
103
+ if (originalSecret) {
104
+ router.push({
105
+ name: 'c-cluster-fleet-application-appco-charts',
106
+ params: { cluster: route.params.cluster as string },
107
+ query: { secret: originalSecret },
108
+ });
109
+ } else {
110
+ router.push({
111
+ name: 'c-cluster-fleet-application-create',
112
+ params: { cluster: route.params.cluster as string },
113
+ });
114
+ }
115
+ };
116
+
117
+ const save = async() => {
118
+ createErrors.value = [];
119
+ saving.value = true;
120
+
121
+ try {
122
+ let secretName = selectedSecret.value;
123
+
124
+ if (isAddNew.value) {
125
+ const credentials = {
126
+ selected: AUTH_TYPE._BASIC,
127
+ publicKey: username.value,
128
+ privateKey: accessToken.value,
129
+ };
130
+
131
+ const secret = await createAppCoAuthSecret(store, credentials, namespace.value);
132
+
133
+ secretName = secret.metadata.name;
134
+ }
135
+
136
+ await ensureAppCoResources(store, secretName, namespace.value, t);
137
+
138
+ router.push({
139
+ name: 'c-cluster-fleet-application-appco-charts',
140
+ params: { cluster: route.params.cluster as string },
141
+ query: { secret: secretName },
142
+ });
143
+ } catch (e: unknown) {
144
+ const msg = e instanceof Error ? e.message : String(e);
145
+
146
+ createErrors.value = [msg];
147
+ } finally {
148
+ saving.value = false;
149
+ }
150
+ };
151
+
152
+ onMounted(async() => {
153
+ try {
154
+ if (store.getters[`${ CATALOG._MANAGEMENT }/schemaFor`](SECRET)) {
155
+ await store.dispatch(`${ CATALOG._MANAGEMENT }/findAll`, { type: SECRET });
156
+ }
157
+ } finally {
158
+ loading.value = false;
159
+ }
160
+
161
+ const querySecret = route.query.secret as string;
162
+
163
+ if (querySecret && existingSecrets.value.find((s: any) => s.metadata.name === querySecret)) {
164
+ selectedSecret.value = querySecret;
165
+ } else if (existingSecrets.value.length && !querySecret) {
166
+ router.replace({
167
+ name: 'c-cluster-fleet-application-appco-charts',
168
+ params: { cluster: route.params.cluster as string },
169
+ query: { secret: existingSecrets.value[0].metadata.name },
170
+ });
171
+ } else if (!existingSecrets.value.length) {
172
+ selectedSecret.value = ADD_NEW_TOKEN;
173
+ }
174
+ });
175
+ </script>
176
+
177
+ <template>
178
+ <Loading v-if="loading" />
179
+
180
+ <div
181
+ v-else
182
+ class="appco-credentials-page"
183
+ >
184
+ <div>
185
+ <AppCoPageHeader :subtitle="true" />
186
+
187
+ <div class="credentials-content">
188
+ <h2 class="subtitle">
189
+ {{ t('fleet.appCo.credentials.subtitle') }}
190
+ </h2>
191
+
192
+ <Banner
193
+ v-for="(err, i) in createErrors"
194
+ :key="i"
195
+ color="error"
196
+ :label="err"
197
+ />
198
+
199
+ <p v-if="hasNoSecrets">
200
+ {{ t('fleet.appCo.credentials.noTokensYet', {}, true) }}
201
+ </p>
202
+
203
+ <div
204
+ v-else
205
+ class="row"
206
+ >
207
+ <div class="col span-6">
208
+ <LabeledSelect
209
+ v-model:value="selectedSecret"
210
+ :label="t('fleet.appCo.credentials.accessTokenLabel')"
211
+ :options="secretOptions"
212
+ :required="true"
213
+ :selectable="(option: any) => !option.disabled"
214
+ data-testid="appco-credentials-select"
215
+ />
216
+ </div>
217
+ </div>
218
+
219
+ <template v-if="isAddNew || hasNoSecrets">
220
+ <div class="row">
221
+ <div class="col span-6">
222
+ <LabeledInput
223
+ v-model:value="username"
224
+ :label="t('fleet.appCo.credentials.username')"
225
+ :required="true"
226
+ :placeholder="t('fleet.appCo.credentials.usernamePlaceholder')"
227
+ data-testid="appco-credentials-username"
228
+ />
229
+ </div>
230
+ </div>
231
+ <div class="row">
232
+ <div class="col span-6">
233
+ <LabeledInput
234
+ v-model:value="accessToken"
235
+ :label="t('fleet.appCo.credentials.accessTokenField')"
236
+ :required="true"
237
+ type="multiline"
238
+ :placeholder="t('fleet.appCo.credentials.accessTokenPlaceholder')"
239
+ data-testid="appco-credentials-token"
240
+ />
241
+ </div>
242
+ </div>
243
+ <Banner
244
+ color="info"
245
+ :label="t('fleet.helmOp.add.steps.selection.authBanner')"
246
+ />
247
+ </template>
248
+ </div>
249
+ </div>
250
+
251
+ <div class="appco-credentials-page-footer">
252
+ <hr class="footer-divider">
253
+ <div class="footer">
254
+ <RcButton
255
+ variant="secondary"
256
+ data-testid="appco-credentials-cancel"
257
+ size="large"
258
+ @click="cancel"
259
+ >
260
+ {{ t('generic.cancel') }}
261
+ </RcButton>
262
+ <RcButton
263
+ :disabled="!canSave || saving"
264
+ :loading="saving"
265
+ data-testid="appco-credentials-save"
266
+ size="large"
267
+ @click="save"
268
+ >
269
+ {{ t('generic.save') }}
270
+ </RcButton>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </template>
275
+
276
+ <style lang="scss" scoped>
277
+ .appco-credentials-page {
278
+ display: flex;
279
+ flex-direction: column;
280
+ min-height: 100%;
281
+ justify-content: space-between;
282
+ }
283
+
284
+ .credentials-content {
285
+ flex: 1;
286
+ display: flex;
287
+ flex-direction: column;
288
+ gap: var(--gap-md);
289
+ }
290
+
291
+ .subtitle {
292
+ margin: 0;
293
+ }
294
+
295
+ .footer-divider {
296
+ border: none;
297
+ border-top: 1px solid var(--border);
298
+ }
299
+
300
+ .appco-credentials-page-footer {
301
+ margin: (-$space-m);
302
+ }
303
+
304
+ .footer {
305
+ display: flex;
306
+ justify-content: flex-end;
307
+ gap: 20px;
308
+ padding: $space-s $space-m;
309
+ }
310
+ </style>
@@ -775,7 +775,7 @@ export default {
775
775
  display: flex;
776
776
  align-items: center;
777
777
  justify-content: end;
778
- gap: 16px;
778
+ gap: var(--gap-md);
779
779
 
780
780
  .collapse-all-btn {
781
781
  width: 105px;
@@ -977,7 +977,7 @@ export default {
977
977
  .resource-cards-container {
978
978
  display: grid;
979
979
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
980
- gap: 16px;
980
+ gap: var(--gap-md);
981
981
  min-height: 100%;
982
982
 
983
983
  .resource-card {
@@ -1201,6 +1201,102 @@ describe('page: UI plugins/Extensions', () => {
1201
1201
  expect(all[1].installed).toBe(true);
1202
1202
  expect(all[1].uiplugin).toBe(pluginCR);
1203
1203
  });
1204
+
1205
+ it('should use install-time repo fallback when originalRepoName is undefined', () => {
1206
+ const apps = [{
1207
+ metadata: {
1208
+ name: 'my-plugin',
1209
+ namespace: UI_PLUGIN_NAMESPACE,
1210
+ labels: {}
1211
+ },
1212
+ spec: { chart: { metadata: { annotations: {} } } }
1213
+ }];
1214
+
1215
+ wireWrapper = createWireWrapper(apps);
1216
+ wireWrapper.vm.installedFromRepo = { 'my-plugin': 'rancher-charts' };
1217
+
1218
+ const all: any[] = [{
1219
+ name: 'my-plugin',
1220
+ chart: { repoName: 'rancher-charts' },
1221
+ installableVersions: [],
1222
+ }];
1223
+
1224
+ const pluginCR = { name: 'my-plugin', version: '1.0.0' };
1225
+
1226
+ wireWrapper.vm.wirePluginCRToChart(pluginCR, all);
1227
+
1228
+ expect(all).toHaveLength(1);
1229
+ expect(all[0].installed).toBe(true);
1230
+ expect(all[0].uiplugin).toBe(pluginCR);
1231
+ expect(all[0].installedVersion).toBe('1.0.0');
1232
+ });
1233
+
1234
+ it('should preserve certification flags when using install-time repo fallback', () => {
1235
+ const apps = [{
1236
+ metadata: {
1237
+ name: 'my-plugin',
1238
+ namespace: UI_PLUGIN_NAMESPACE,
1239
+ labels: {}
1240
+ },
1241
+ spec: { chart: { metadata: { annotations: {} } } }
1242
+ }];
1243
+
1244
+ wireWrapper = createWireWrapper(apps);
1245
+ wireWrapper.vm.installedFromRepo = { 'my-plugin': 'rancher-charts' };
1246
+
1247
+ const all: any[] = [{
1248
+ name: 'my-plugin',
1249
+ chart: { repoName: 'rancher-charts' },
1250
+ certified: true,
1251
+ primeOnly: false,
1252
+ experimental: false,
1253
+ installableVersions: [],
1254
+ }];
1255
+
1256
+ const pluginCR = { name: 'my-plugin', version: '1.0.0' };
1257
+
1258
+ wireWrapper.vm.wirePluginCRToChart(pluginCR, all);
1259
+
1260
+ expect(all).toHaveLength(1);
1261
+ expect(all[0].installed).toBe(true);
1262
+ expect(all[0].certified).toBe(true);
1263
+ });
1264
+
1265
+ it('should not cross-match to wrong repo when using install-time repo fallback with multiple repos', () => {
1266
+ const apps = [{
1267
+ metadata: {
1268
+ name: 'my-plugin',
1269
+ namespace: UI_PLUGIN_NAMESPACE,
1270
+ labels: {}
1271
+ },
1272
+ spec: { chart: { metadata: { annotations: {} } } }
1273
+ }];
1274
+
1275
+ wireWrapper = createWireWrapper(apps);
1276
+ wireWrapper.vm.installedFromRepo = { 'my-plugin': 'repo-a' };
1277
+
1278
+ const all: any[] = [
1279
+ {
1280
+ name: 'my-plugin',
1281
+ chart: { repoName: 'repo-a' },
1282
+ installableVersions: [],
1283
+ },
1284
+ {
1285
+ name: 'my-plugin',
1286
+ chart: { repoName: 'repo-b' },
1287
+ installableVersions: [],
1288
+ }
1289
+ ];
1290
+
1291
+ const pluginCR = { name: 'my-plugin', version: '1.0.0' };
1292
+
1293
+ wireWrapper.vm.wirePluginCRToChart(pluginCR, all);
1294
+
1295
+ expect(all).toHaveLength(2);
1296
+ expect(all[0].installed).toBe(true);
1297
+ expect(all[0].uiplugin).toBe(pluginCR);
1298
+ expect(all[1].installed).toBeUndefined();
1299
+ });
1204
1300
  });
1205
1301
 
1206
1302
  describe('mergePluginErrors', () => {
@@ -66,6 +66,7 @@ export default {
66
66
  kubeVersion: null,
67
67
  activeTab: '',
68
68
  installing: {},
69
+ installedFromRepo: {},
69
70
  errors: {},
70
71
  plugins: [], // The installed plugins
71
72
  helmOps: [], // Helm operations
@@ -528,6 +529,9 @@ export default {
528
529
  this.updatePluginInstallStatus(pluginId, type);
529
530
  },
530
531
  closed: (res) => {
532
+ if (res && plugin?.chart?.repoName) {
533
+ this.installedFromRepo[plugin.name] = plugin.chart.repoName;
534
+ }
531
535
  this.didInstall(res);
532
536
  }
533
537
  }
@@ -962,6 +966,17 @@ export default {
962
966
  chart = all.find((c) => c.name === pluginCR.name && c.chart?.repoName === originalRepoName);
963
967
  }
964
968
 
969
+ // fallback to try and prevent https://github.com/rancher/dashboard/issues/17956
970
+ // which we believe is due to a race condition
971
+ // we fall back to plugin name which should be present when going through the "installed plugins" (uiplugins) and should be enough to find the correct chart in the majority of cases, as long as there are not multiple charts with the same name across different repos
972
+ if (!chart) {
973
+ const fallbackRepoName = this.installedFromRepo?.[pluginCR.name];
974
+
975
+ if (fallbackRepoName) {
976
+ chart = all.find((c) => c.name === pluginCR.name && c.chart?.repoName === fallbackRepoName);
977
+ }
978
+ }
979
+
965
980
  if (chart) {
966
981
  chart.installed = true;
967
982
  chart.uiplugin = pluginCR;
@@ -8,11 +8,12 @@ import FixedBanner from '@shell/components/FixedBanner';
8
8
  import GrowlManager from '@shell/components/GrowlManager';
9
9
  import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
10
10
  import PromptModal from '@shell/components/PromptModal';
11
+ import { RcButton } from '@components/RcButton';
11
12
 
12
13
  export default {
13
14
 
14
15
  components: {
15
- BrandImage, FixedBanner, GrowlManager, Header, PromptModal
16
+ BrandImage, FixedBanner, GrowlManager, Header, PromptModal, RcButton
16
17
  },
17
18
  mixins: [Brand, BrowserTabVisibility],
18
19
 
@@ -61,10 +62,13 @@ export default {
61
62
 
62
63
  <template>
63
64
  <div class="dashboard-root">
64
- <a
65
- href="#main-content"
66
- class="skip-to-content btn role-primary"
67
- >{{ t('nav.skipToContent') }}</a>
65
+ <rc-button
66
+ size="large"
67
+ class="skip-to-content"
68
+ :to="{ hash: '#main-content' }"
69
+ >
70
+ {{ t('nav.skipToContent') }}
71
+ </rc-button>
68
72
  <FixedBanner :header="true" />
69
73
  <PromptModal />
70
74
  <div
@@ -106,12 +110,12 @@ export default {
106
110
  {{ displayError }}
107
111
  </h2>
108
112
  <p class="mt-20">
109
- <a
113
+ <rc-button
114
+ size="large"
110
115
  :href="home"
111
- class="btn role-primary"
112
116
  >
113
117
  {{ t('nav.home') }}
114
- </a>
118
+ </rc-button>
115
119
  </p>
116
120
  <hr
117
121
  class="custom-content"
@@ -119,12 +123,13 @@ export default {
119
123
  role="none"
120
124
  >
121
125
  <p class="mt-20">
122
- <a
123
- class="btn role-secondary"
126
+ <rc-button
127
+ size="large"
128
+ variant="secondary"
124
129
  @click="$router.push(previousRoute.fullPath)"
125
130
  >
126
131
  {{ t('nav.failWhale.reload') }}
127
- </a>
132
+ </rc-button>
128
133
  </p>
129
134
  </div>
130
135
  </div>
package/pages/home.vue CHANGED
@@ -35,6 +35,7 @@ import Preset from '@shell/mixins/preset';
35
35
  import { PaginationFeatureHomePageClusterConfig } from '@shell/types/resources/settings';
36
36
  import MgmtCluster from '@shell/models/management.cattle.io.cluster';
37
37
  import ManagementClusterUtils from '@shell/list/utils/management.cattle.io.cluster.utils';
38
+ import { RcButton } from '@components/RcButton';
38
39
 
39
40
  export default defineComponent({
40
41
  name: 'Home',
@@ -50,6 +51,7 @@ export default defineComponent({
50
51
  ResourceTable,
51
52
  DynamicContentBanner,
52
53
  DynamicContentPanel,
54
+ RcButton
53
55
  },
54
56
 
55
57
  mixins: [PageHeaderActions, Preset],
@@ -504,39 +506,31 @@ export default defineComponent({
504
506
  #header-middle
505
507
  >
506
508
  <div class="table-heading">
507
- <router-link
509
+ <rc-button
508
510
  v-if="!!provClusterSchema"
511
+ variant="secondary"
509
512
  :to="manageLocation"
510
- class="btn btn-sm role-secondary"
511
513
  data-testid="cluster-management-manage-button"
512
- role="button"
513
514
  :aria-label="t('cluster.manageAction')"
514
- @keyup.space="$router.push(manageLocation)"
515
515
  >
516
516
  {{ t('cluster.manageAction') }}
517
- </router-link>
518
- <router-link
517
+ </rc-button>
518
+ <rc-button
519
519
  v-if="canCreateCluster"
520
520
  :to="importLocation"
521
- class="btn btn-sm role-primary"
522
521
  data-testid="cluster-create-import-button"
523
- role="button"
524
522
  :aria-label="t('cluster.importAction')"
525
- @keyup.space="$router.push(importLocation)"
526
523
  >
527
524
  {{ t('cluster.importAction') }}
528
- </router-link>
529
- <router-link
525
+ </rc-button>
526
+ <rc-button
530
527
  v-if="canCreateCluster"
531
528
  :to="createLocation"
532
- class="btn btn-sm role-primary"
533
529
  data-testid="cluster-create-button"
534
- role="button"
535
530
  :aria-label="t('generic.create')"
536
- @keyup.space="$router.push(createLocation)"
537
531
  >
538
532
  {{ t('generic.create') }}
539
- </router-link>
533
+ </rc-button>
540
534
  </div>
541
535
  </template>
542
536
  <template #col:name="{row}">
@@ -604,14 +598,6 @@ export default defineComponent({
604
598
  &mdash;
605
599
  </td>
606
600
  </template>
607
- <!-- <template #cell:explorer="{row}">
608
- <router-link v-if="row && row.isReady" class="btn btn-sm role-primary" :to="{name: 'c-cluster', params: {cluster: row.id}}">
609
- {{ t('landing.clusters.explore') }}
610
- </router-link>
611
- <button v-else :disabled="true" class="btn btn-sm role-primary">
612
- {{ t('landing.clusters.explore') }}
613
- </button>
614
- </template> -->
615
601
  </ResourceTable>
616
602
  </div>
617
603
  <div
@@ -665,39 +651,31 @@ export default defineComponent({
665
651
  #header-middle
666
652
  >
667
653
  <div class="table-heading">
668
- <router-link
654
+ <rc-button
669
655
  v-if="!!provClusterSchema"
656
+ variant="secondary"
670
657
  :to="manageLocation"
671
- class="btn btn-sm role-secondary"
672
658
  data-testid="cluster-management-manage-button"
673
- role="button"
674
659
  :aria-label="t('cluster.manageAction')"
675
- @keyup.space="$router.push(manageLocation)"
676
660
  >
677
661
  {{ t('cluster.manageAction') }}
678
- </router-link>
679
- <router-link
662
+ </rc-button>
663
+ <rc-button
680
664
  v-if="canCreateCluster"
681
665
  :to="importLocation"
682
- class="btn btn-sm role-primary"
683
666
  data-testid="cluster-create-import-button"
684
- role="button"
685
667
  :aria-label="t('cluster.importAction')"
686
- @keyup.space="$router.push(importLocation)"
687
668
  >
688
669
  {{ t('cluster.importAction') }}
689
- </router-link>
690
- <router-link
670
+ </rc-button>
671
+ <rc-button
691
672
  v-if="canCreateCluster"
692
673
  :to="createLocation"
693
- class="btn btn-sm role-primary"
694
674
  data-testid="cluster-create-button"
695
- role="button"
696
675
  :aria-label="t('generic.create')"
697
- @keyup.space="$router.push(createLocation)"
698
676
  >
699
677
  {{ t('generic.create') }}
700
- </router-link>
678
+ </rc-button>
701
679
  </div>
702
680
  </template>
703
681
  <template #col:name="{row}">
@@ -748,14 +726,6 @@ export default defineComponent({
748
726
  &mdash;
749
727
  </td>
750
728
  </template>
751
- <!-- <template #cell:explorer="{row}">
752
- <router-link v-if="row && row.isReady" class="btn btn-sm role-primary" :to="{name: 'c-cluster', params: {cluster: row.id}}">
753
- {{ t('landing.clusters.explore') }}
754
- </router-link>
755
- <button v-else :disabled="true" class="btn btn-sm role-primary">
756
- {{ t('landing.clusters.explore') }}
757
- </button>
758
- </template> -->
759
729
  </PaginatedResourceTable>
760
730
  </div>
761
731
  <div
@@ -0,0 +1,9 @@
1
+ import { Config } from 'dompurify';
2
+
3
+ export function purifyHTML(value: string, options?: Config): string;
4
+
5
+ export function addLinkInterceptor(fn: (link: string) => string | undefined | void, name?: string): void;
6
+
7
+ export function removeLinkInterceptor(fn: (link: string) => string | undefined | void): void;
8
+
9
+ export function processLink(link: string): string;