@rancher/shell 3.0.11 → 3.0.12-rc.2

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 (219) hide show
  1. package/assets/images/providers/entraid-black.svg +4 -0
  2. package/assets/images/providers/entraid.svg +9 -0
  3. package/assets/images/vendor/entraid.svg +9 -0
  4. package/assets/styles/app.scss +0 -1
  5. package/assets/styles/base/_mixins.scss +31 -0
  6. package/assets/styles/base/_variables.scss +2 -0
  7. package/assets/styles/themes/_modern.scss +6 -5
  8. package/assets/translations/en-us.yaml +24 -21
  9. package/assets/translations/zh-hans.yaml +4 -11
  10. package/chart/__tests__/S3.test.ts +10 -3
  11. package/components/CountBox.vue +20 -0
  12. package/components/CreateDriver.vue +0 -12
  13. package/components/DetailText.vue +12 -3
  14. package/components/EmptyProductPage.vue +76 -0
  15. package/components/Resource/Detail/CopyToClipboard.vue +1 -2
  16. package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
  17. package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
  18. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
  19. package/components/Resource/Detail/TitleBar/index.vue +1 -1
  20. package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
  21. package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
  22. package/components/Resource/Detail/ViewOptions/index.vue +2 -1
  23. package/components/ResourceList/Masthead.vue +25 -2
  24. package/components/SelectIconGrid.vue +5 -0
  25. package/components/SideNav.vue +13 -0
  26. package/components/__tests__/CountBox.test.ts +72 -0
  27. package/components/__tests__/DetailText.test.ts +113 -0
  28. package/components/__tests__/PromptModal.test.ts +2 -0
  29. package/components/fleet/FleetClusterTargets/index.vue +18 -1
  30. package/components/fleet/FleetClusters.vue +1 -0
  31. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  32. package/components/form/InputWithSelect.vue +18 -10
  33. package/components/form/KeyValue.vue +17 -1
  34. package/components/form/LabeledSelect.vue +82 -24
  35. package/components/form/NodeScheduling.vue +17 -3
  36. package/components/form/PrivateRegistry.vue +69 -0
  37. package/components/form/Select.vue +73 -56
  38. package/components/form/ServiceNameSelect.vue +13 -11
  39. package/components/form/__tests__/KeyValue.test.ts +66 -0
  40. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  41. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  42. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  43. package/components/formatter/WorkloadHealthScale.vue +3 -1
  44. package/components/nav/Group.vue +33 -9
  45. package/components/nav/Header.vue +56 -10
  46. package/components/nav/NotificationCenter/Notification.vue +4 -1
  47. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  48. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  49. package/components/nav/TopLevelMenu.vue +15 -1
  50. package/components/nav/Type.vue +8 -7
  51. package/components/nav/WindowManager/index.vue +2 -1
  52. package/components/nav/WorkspaceSwitcher.vue +13 -0
  53. package/components/nav/__tests__/Group.test.ts +67 -0
  54. package/components/nav/__tests__/Header.test.ts +235 -0
  55. package/components/nav/__tests__/Type.test.ts +20 -3
  56. package/components/templates/default.vue +34 -4
  57. package/components/templates/home.vue +12 -25
  58. package/components/templates/plain.vue +13 -26
  59. package/composables/useLabeledFormElement.ts +10 -2
  60. package/composables/useLabeledSelect.ts +60 -0
  61. package/composables/useUserRetentionValidation.ts +1 -49
  62. package/config/cookies.js +0 -1
  63. package/config/labels-annotations.js +1 -0
  64. package/config/pagination-table-headers.js +8 -1
  65. package/config/product/apps.js +2 -1
  66. package/config/product/auth.js +1 -0
  67. package/config/product/backup.js +1 -0
  68. package/config/product/compliance.js +1 -1
  69. package/config/product/explorer.js +25 -6
  70. package/config/product/fleet.js +1 -0
  71. package/config/product/gatekeeper.js +1 -0
  72. package/config/product/istio.js +1 -0
  73. package/config/product/logging.js +1 -0
  74. package/config/product/longhorn.js +2 -1
  75. package/config/product/manager.js +1 -0
  76. package/config/product/monitoring.js +1 -0
  77. package/config/product/navlinks.js +1 -0
  78. package/config/product/neuvector.js +2 -1
  79. package/config/product/settings.js +1 -0
  80. package/config/product/uiplugins.js +1 -0
  81. package/config/query-params.js +1 -0
  82. package/config/router/routes.js +0 -8
  83. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  84. package/core/__tests__/plugin-products.test.ts +3810 -0
  85. package/core/extension-manager-impl.js +30 -1
  86. package/core/plugin-products-base.ts +392 -0
  87. package/core/plugin-products-extending.ts +44 -0
  88. package/core/plugin-products-helpers.ts +263 -0
  89. package/core/plugin-products-top-level.ts +66 -0
  90. package/core/plugin-products-type-guards.ts +33 -0
  91. package/core/plugin-products.ts +50 -0
  92. package/core/plugin-types.ts +237 -0
  93. package/core/plugin.ts +45 -10
  94. package/core/productDebugger.js +48 -0
  95. package/core/types.ts +97 -11
  96. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  97. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  98. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  99. package/detail/fleet.cattle.io.bundle.vue +21 -34
  100. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  101. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  102. package/dialog/InstallExtensionDialog.vue +6 -27
  103. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  104. package/dialog/UninstallExtensionDialog.vue +4 -26
  105. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  106. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  107. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  108. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  109. package/edit/__tests__/nodeDriver.test.ts +5 -11
  110. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  111. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  112. package/edit/auth/__tests__/oidc.test.ts +54 -0
  113. package/edit/auth/azuread.vue +1 -1
  114. package/edit/auth/oidc.vue +8 -0
  115. package/edit/kontainerDriver.vue +1 -2
  116. package/edit/nodeDriver.vue +0 -2
  117. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  119. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  120. package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  122. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  124. package/initialize/App.vue +29 -2
  125. package/initialize/install-plugins.js +0 -2
  126. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  127. package/list/catalog.cattle.io.app.vue +25 -5
  128. package/list/management.cattle.io.feature.vue +1 -1
  129. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  130. package/list/provisioning.cattle.io.cluster.vue +0 -1
  131. package/list/workload.vue +11 -4
  132. package/machine-config/amazonec2.vue +1 -0
  133. package/mixins/chart.js +40 -9
  134. package/mixins/resource-fetch.js +12 -3
  135. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  136. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  137. package/models/__tests__/chart.test.ts +99 -6
  138. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  139. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  140. package/models/catalog.cattle.io.app.js +21 -17
  141. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  142. package/models/chart.js +33 -19
  143. package/models/fleet-application.js +1 -1
  144. package/models/fleet.cattle.io.bundle.js +1 -1
  145. package/models/kontainerdriver.js +11 -0
  146. package/models/management.cattle.io.authconfig.js +5 -1
  147. package/models/management.cattle.io.cluster.js +0 -53
  148. package/models/management.cattle.io.feature.js +3 -3
  149. package/models/management.cattle.io.kontainerdriver.js +1 -26
  150. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  151. package/models/nodedriver.js +7 -0
  152. package/models/pod.js +18 -0
  153. package/models/workload.js +20 -2
  154. package/package.json +13 -13
  155. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  156. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
  157. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  158. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  159. package/pages/c/_cluster/apps/charts/chart.vue +217 -33
  160. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  161. package/pages/c/_cluster/apps/charts/install.vue +8 -3
  162. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  163. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  164. package/pages/c/_cluster/settings/brand.vue +4 -4
  165. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
  166. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  167. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
  168. package/pages/c/_cluster/uiplugins/index.vue +166 -62
  169. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  170. package/plugins/dashboard-store/actions.js +3 -2
  171. package/plugins/dashboard-store/resource-class.js +62 -6
  172. package/plugins/plugin.js +16 -0
  173. package/plugins/steve/steve-pagination-utils.ts +7 -0
  174. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
  175. package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
  176. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  177. package/scripts/test-plugins-build.sh +5 -2
  178. package/scripts/typegen.sh +13 -1
  179. package/server/server-middleware.js +2 -2
  180. package/static/humans.txt +1 -0
  181. package/static/robots.txt +34 -0
  182. package/static/welcome-cow.svg +18 -0
  183. package/store/__tests__/catalog.test.ts +161 -11
  184. package/store/__tests__/type-map.test.ts +84 -24
  185. package/store/auth.js +0 -3
  186. package/store/catalog.js +60 -8
  187. package/store/type-map.js +42 -3
  188. package/tsconfig.paths.json +1 -0
  189. package/types/resources/pod.ts +18 -0
  190. package/types/shell/index.d.ts +8539 -2938
  191. package/types/store/dashboard-store.types.ts +5 -0
  192. package/types/store/pagination.types.ts +6 -0
  193. package/utils/__tests__/git.test.ts +270 -0
  194. package/utils/__tests__/inactivity.test.ts +316 -0
  195. package/utils/__tests__/object.test.ts +77 -0
  196. package/utils/__tests__/time.test.ts +14 -1
  197. package/utils/__tests__/url.test.ts +246 -0
  198. package/utils/axios.js +1 -4
  199. package/utils/dynamic-importer.js +3 -2
  200. package/utils/object.js +33 -2
  201. package/utils/pagination-utils.ts +1 -1
  202. package/utils/time.ts +5 -0
  203. package/utils/uiplugins.ts +12 -16
  204. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  205. package/utils/validators/private-registry.ts +28 -0
  206. package/vue.config.js +0 -9
  207. package/assets/images/providers/azuread-black.svg +0 -22
  208. package/assets/images/providers/azuread.svg +0 -25
  209. package/assets/images/vendor/azuread.svg +0 -18
  210. package/assets/styles/fonts/_dots.scss +0 -18
  211. package/components/EmberPage.vue +0 -622
  212. package/components/EmberPageView.vue +0 -39
  213. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  214. package/mixins/labeled-form-element.ts +0 -225
  215. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  216. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  217. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  218. package/plugins/ember-cookie.js +0 -17
  219. package/utils/ember-page.js +0 -30
@@ -30,7 +30,7 @@ import {
30
30
  AUTH_TYPE, NAMESPACE as NAMESPACE_TYPE
31
31
  } from '@shell/config/types';
32
32
  import {
33
- CHART, FROM_CLUSTER, FROM_TOOLS, HIDE_SIDE_NAV, NAMESPACE, REPO, REPO_TYPE, VERSION, _FLAGGED
33
+ CHART, FROM_CLUSTER, FROM_TOOLS, HIDE_SIDE_NAV, NEW_APP_INSTANCE, NAMESPACE, REPO, REPO_TYPE, VERSION, _FLAGGED
34
34
  } from '@shell/config/query-params';
35
35
  import { CATALOG as CATALOG_ANNOTATIONS, PROJECT } from '@shell/config/labels-annotations';
36
36
 
@@ -602,8 +602,11 @@ export default {
602
602
  },
603
603
 
604
604
  showSelectVersionOrChart() {
605
- // Allow the user to choose a version if the app exists OR they've come from tools
606
- return this.existing || (FROM_TOOLS in this.$route.query);
605
+ // Allow the user to choose a version if:
606
+ // - the app exists (editing/upgrading)
607
+ // - OR they've come from tools
608
+ // - OR they're installing a new instance of an already-installed chart
609
+ return this.existing || (FROM_TOOLS in this.$route.query) || (NEW_APP_INSTANCE in this.$route.query);
607
610
  },
608
611
 
609
612
  showNameEditor() {
@@ -1110,6 +1113,8 @@ export default {
1110
1113
  this.$router.replace(this.clusterToolsLocation());
1111
1114
  } else if (this.$route.query[FROM_CLUSTER] === _FLAGGED) {
1112
1115
  this.$router.replace(this.clustersLocation());
1116
+ } else if (!this.chart) {
1117
+ this.$router.replace(this.appLocation());
1113
1118
  } else {
1114
1119
  this.$router.replace(this.chartLocation(false));
1115
1120
  }
@@ -1,6 +1,9 @@
1
1
  <script lang="ts" setup>
2
- import { ref, reactive, watch, onMounted } from 'vue';
2
+ import {
3
+ ref, reactive, watch, onMounted, computed
4
+ } from 'vue';
3
5
  import { useRouter, onBeforeRouteUpdate } from 'vue-router';
6
+ import { useForm } from 'vee-validate';
4
7
 
5
8
  import UserRetentionHeader from '@shell/components/user.retention/user-retention-header.vue';
6
9
  import Footer from '@shell/components/form/Footer.vue';
@@ -20,8 +23,15 @@ import { ToggleSwitch } from '@components/Form/ToggleSwitch';
20
23
 
21
24
  import dayjs from 'dayjs';
22
25
 
26
+ type UserRetentionSettingId =
27
+ | typeof SETTING.DISABLE_INACTIVE_USER_AFTER
28
+ | typeof SETTING.DELETE_INACTIVE_USER_AFTER
29
+ | typeof SETTING.USER_RETENTION_CRON
30
+ | typeof SETTING.USER_RETENTION_DRY_RUN
31
+ | typeof SETTING.USER_LAST_LOGIN_DEFAULT;
32
+
23
33
  const store = useStore();
24
- const userRetentionSettings = reactive<{[id: string]: string | null }>({
34
+ const userRetentionSettings = reactive<Record<UserRetentionSettingId, string | null>>({
25
35
  [SETTING.DISABLE_INACTIVE_USER_AFTER]: null,
26
36
  [SETTING.DELETE_INACTIVE_USER_AFTER]: null,
27
37
  [SETTING.USER_RETENTION_CRON]: null,
@@ -38,18 +48,28 @@ const {
38
48
  validateDeleteInactiveUserAfterDuration,
39
49
  validateDeleteInactiveUserAfter,
40
50
  validateDurationAgainstAuthUserSession,
41
- setValidation,
42
- removeValidation,
43
- addValidation,
44
- isFormValid,
45
51
  } = useUserRetentionValidation(disableAfterPeriod, deleteAfterPeriod, authUserSessionTtlMinutes);
52
+
53
+ const { errors, validate: validateForm, validateField } = useForm({
54
+ validationSchema: {
55
+ [SETTING.DISABLE_INACTIVE_USER_AFTER]: (value: string) => validateDisableInactiveUserAfterDuration(value) ??
56
+ validateDurationAgainstAuthUserSession(value) ??
57
+ true,
58
+ [SETTING.DELETE_INACTIVE_USER_AFTER]: (value: string) => validateDeleteInactiveUserAfterDuration(value) ??
59
+ validateDurationAgainstAuthUserSession(value) ??
60
+ validateDeleteInactiveUserAfter(value) ??
61
+ true,
62
+ [SETTING.USER_RETENTION_CRON]: (value: string) => validateUserRetentionCron(value) ?? true,
63
+ },
64
+ });
65
+
46
66
  let settings: { [id: string]: Setting } = {};
47
67
 
48
68
  /**
49
69
  * Watches the disable after period and removes the value if the checkbox is
50
70
  * not selected. Lookup the value when the checkbox is selected.
51
71
  */
52
- watch(disableAfterPeriod, (newVal) => {
72
+ watch(disableAfterPeriod, async(newVal) => {
53
73
  if (!newVal) {
54
74
  userRetentionSettings[SETTING.DISABLE_INACTIVE_USER_AFTER] = null;
55
75
 
@@ -57,13 +77,14 @@ watch(disableAfterPeriod, (newVal) => {
57
77
  }
58
78
 
59
79
  userRetentionSettings[SETTING.DISABLE_INACTIVE_USER_AFTER] = settings[SETTING.DISABLE_INACTIVE_USER_AFTER].value;
80
+ await validateField(SETTING.DISABLE_INACTIVE_USER_AFTER);
60
81
  });
61
82
 
62
83
  /**
63
84
  * Watches the delete after period and removes the value if the checkbox is
64
85
  * not selected. Lookup the value when the checkbox is selected.
65
86
  */
66
- watch(deleteAfterPeriod, (newVal) => {
87
+ watch(deleteAfterPeriod, async(newVal) => {
67
88
  if (!newVal) {
68
89
  userRetentionSettings[SETTING.DELETE_INACTIVE_USER_AFTER] = null;
69
90
 
@@ -71,6 +92,8 @@ watch(deleteAfterPeriod, (newVal) => {
71
92
  }
72
93
 
73
94
  userRetentionSettings[SETTING.DELETE_INACTIVE_USER_AFTER] = settings[SETTING.DELETE_INACTIVE_USER_AFTER].value;
95
+ await validateField(SETTING.DELETE_INACTIVE_USER_AFTER);
96
+ await validateField(SETTING.USER_RETENTION_CRON);
74
97
  });
75
98
 
76
99
  /**
@@ -84,18 +107,19 @@ watch([disableAfterPeriod, deleteAfterPeriod], ([newDisableAfterPeriod, newDelet
84
107
  userRetentionSettings[key] = null;
85
108
  });
86
109
 
87
- removeValidation(SETTING.USER_RETENTION_CRON);
88
-
89
110
  return;
90
111
  }
91
112
 
92
- ids.filter((id) => ![SETTING.DISABLE_INACTIVE_USER_AFTER, SETTING.DELETE_INACTIVE_USER_AFTER].includes(id))
93
- .forEach(assignSettings);
113
+ const skippedIds: readonly UserRetentionSettingId[] = [
114
+ SETTING.DISABLE_INACTIVE_USER_AFTER,
115
+ SETTING.DELETE_INACTIVE_USER_AFTER,
116
+ ];
94
117
 
95
- addValidation(SETTING.USER_RETENTION_CRON);
118
+ ids.filter((id) => !skippedIds.includes(id))
119
+ .forEach(assignSettings);
96
120
  });
97
121
 
98
- const assignSettings = (key: string) => {
122
+ const assignSettings = (key: UserRetentionSettingId) => {
99
123
  if (settings[key].id === SETTING.USER_LAST_LOGIN_DEFAULT && settings[key].value && typeof settings[key].value === 'string') {
100
124
  const value = settings[key].value as string;
101
125
 
@@ -111,7 +135,7 @@ const fetchSetting = async(id: string) => {
111
135
  return await store.dispatch('management/find', { type: MANAGEMENT.SETTING, id });
112
136
  };
113
137
 
114
- const ids = Object.keys(userRetentionSettings);
138
+ const ids = Object.keys(userRetentionSettings) as UserRetentionSettingId[];
115
139
  const settingPromises = ids.map((id) => fetchSetting(id));
116
140
 
117
141
  onMounted(async() => {
@@ -136,6 +160,14 @@ onMounted(async() => {
136
160
  const { t } = useI18n(store);
137
161
  const error = ref<string | null>(null);
138
162
  const save = async(btnCB: (arg: boolean) => void) => {
163
+ const { valid } = await validateForm();
164
+
165
+ if (!valid) {
166
+ btnCB(false);
167
+
168
+ return;
169
+ }
170
+
139
171
  try {
140
172
  error.value = null;
141
173
  ids.forEach((key) => {
@@ -164,6 +196,10 @@ const save = async(btnCB: (arg: boolean) => void) => {
164
196
  }
165
197
  };
166
198
 
199
+ const isFormInvalid = computed(() => {
200
+ return Object.keys(errors.value).length > 0;
201
+ });
202
+
167
203
  const router = useRouter();
168
204
  const routeBack = () => {
169
205
  router.back();
@@ -199,12 +235,11 @@ onBeforeRouteUpdate((_to: unknown, _from: unknown) => {
199
235
  <labeled-input
200
236
  v-model:value="userRetentionSettings[SETTING.DISABLE_INACTIVE_USER_AFTER]"
201
237
  data-testid="disableAfterPeriodInput"
238
+ :name="SETTING.DISABLE_INACTIVE_USER_AFTER"
202
239
  :tooltip="t('user.retention.edit.form.disableAfter.input.tooltip')"
203
240
  class="input-field"
204
241
  :label="t('user.retention.edit.form.disableAfter.input.label')"
205
242
  :disabled="!disableAfterPeriod"
206
- :rules="[validateDisableInactiveUserAfterDuration, validateDurationAgainstAuthUserSession]"
207
- @update:validation="e => setValidation(SETTING.DISABLE_INACTIVE_USER_AFTER, e)"
208
243
  />
209
244
  </div>
210
245
  <div class="input-fieldset">
@@ -216,13 +251,12 @@ onBeforeRouteUpdate((_to: unknown, _from: unknown) => {
216
251
  <labeled-input
217
252
  v-model:value="userRetentionSettings[SETTING.DELETE_INACTIVE_USER_AFTER]"
218
253
  data-testid="deleteAfterPeriodInput"
254
+ :name="SETTING.DELETE_INACTIVE_USER_AFTER"
219
255
  :tooltip="t('user.retention.edit.form.deleteAfter.input.tooltip')"
220
256
  class="input-field"
221
257
  :label="t('user.retention.edit.form.deleteAfter.input.label')"
222
258
  :sub-label="t('user.retention.edit.form.deleteAfter.input.subLabel')"
223
259
  :disabled="!deleteAfterPeriod"
224
- :rules="[validateDeleteInactiveUserAfterDuration, validateDurationAgainstAuthUserSession, validateDeleteInactiveUserAfter]"
225
- @update:validation="e => setValidation(SETTING.DELETE_INACTIVE_USER_AFTER, e)"
226
260
  />
227
261
  </div>
228
262
  <template
@@ -232,14 +266,13 @@ onBeforeRouteUpdate((_to: unknown, _from: unknown) => {
232
266
  <labeled-input
233
267
  v-model:value="userRetentionSettings[SETTING.USER_RETENTION_CRON]"
234
268
  data-testid="userRetentionCron"
269
+ :name="SETTING.USER_RETENTION_CRON"
235
270
  class="input-field"
236
271
  required
237
272
  type="cron"
238
273
  :tooltip="t('user.retention.edit.form.cron.subLabel')"
239
- :rules="[validateUserRetentionCron]"
240
274
  :label="t('user.retention.edit.form.cron.label')"
241
275
  :require-dirty="false"
242
- @update:validation="e => setValidation(SETTING.USER_RETENTION_CRON, e)"
243
276
  />
244
277
  </div>
245
278
  <div class="input-fieldset condensed pt-12">
@@ -268,7 +301,7 @@ onBeforeRouteUpdate((_to: unknown, _from: unknown) => {
268
301
  <Footer
269
302
  class="footer-user-retention"
270
303
  mode="edit"
271
- :disable-save="!isFormValid"
304
+ :disable-save="isFormInvalid"
272
305
  @save="save"
273
306
  @done="routeBack"
274
307
  />
@@ -1,6 +1,5 @@
1
1
  <script>
2
2
  import { NORMAN } from '@shell/config/types';
3
- import { isAdminUser } from '@shell/store/type-map';
4
3
  import ResourceTable from '@shell/components/ResourceTable';
5
4
  import AsyncButton from '@shell/components/AsyncButton';
6
5
  import Loading from '@shell/components/Loading';
@@ -20,18 +19,19 @@ export default {
20
19
  data() {
21
20
  return {
22
21
  allDrivers: null,
23
- canRefreshK8sMetadata: true,
24
22
  resource: NORMAN.KONTAINER_DRIVER,
25
23
  schema: this.$store.getters['rancher/schemaFor'](NORMAN.KONTAINER_DRIVER),
26
24
  useQueryParamsForSimpleFiltering: false,
27
25
  forceUpdateLiveAndDelayed: 10,
28
- showDeprecationBanner: isAdminUser(this.$store.getters),
29
26
  };
30
27
  },
31
28
  computed: {
32
29
  rows() {
33
30
  return this.allDrivers || [];
34
31
  },
32
+ hasEmberUiDrivers() {
33
+ return this.rows.some((driver) => driver.active && driver.isEmber);
34
+ },
35
35
  },
36
36
  methods: {
37
37
  async refreshK8sMetadata(buttonDone) {
@@ -63,7 +63,6 @@ export default {
63
63
  >
64
64
  <template #extraActions>
65
65
  <AsyncButton
66
- v-if="canRefreshK8sMetadata"
67
66
  mode="refresh"
68
67
  :action-label="t('drivers.actions.refresh')"
69
68
  :waiting-label="t('drivers.actions.refresh')"
@@ -75,10 +74,9 @@ export default {
75
74
  </template>
76
75
  </Masthead>
77
76
  <Banner
78
- v-if="showDeprecationBanner"
77
+ v-if="hasEmberUiDrivers"
79
78
  color="warning"
80
- label-key="drivers.kontainer.emberDeprecationMessage"
81
- data-testid="kontainer-driver-ember-deprecation-banner"
79
+ label-key="drivers.kontainer.emberRemovalMessage"
82
80
  />
83
81
  <ResourceTable
84
82
  :schema="schema"
@@ -440,14 +440,14 @@ export default {
440
440
  />
441
441
  </div>
442
442
  <SimpleBox
443
- v-if="uiLoginBackgroundLight || uiLoginBackgroundDark"
443
+ v-if="uiLoginBackgroundLight"
444
444
  class="theme-light mb-10"
445
445
  >
446
446
  <label class="text-muted">{{ t('branding.loginBackground.lightPreview') }}</label>
447
447
  <img
448
448
  class="img-preview"
449
449
  data-testid="branding-login-background-light-preview"
450
- :src="uiLoginBackgroundLight ? uiLoginBackgroundLight : uiLoginBackgroundDark"
450
+ :src="uiLoginBackgroundLight"
451
451
  >
452
452
  </SimpleBox>
453
453
  </div>
@@ -465,14 +465,14 @@ export default {
465
465
  />
466
466
  </div>
467
467
  <SimpleBox
468
- v-if="uiLoginBackgroundDark || uiLoginBackgroundLight"
468
+ v-if="uiLoginBackgroundDark"
469
469
  class="theme-dark mb-10"
470
470
  >
471
471
  <label class="text-muted">{{ t('branding.loginBackground.darkPreview') }}</label>
472
472
  <img
473
473
  class="img-preview"
474
474
  data-testid="branding-login-background-dark-preview"
475
- :src="uiLoginBackgroundDark ? uiLoginBackgroundDark : uiLoginBackgroundLight"
475
+ :src="uiLoginBackgroundDark"
476
476
  >
477
477
  </SimpleBox>
478
478
  </div>
@@ -7,7 +7,9 @@ import genericPluginSvg from '~shell/assets/images/generic-plugin.svg';
7
7
  import { SETTING } from '@shell/config/settings';
8
8
  import { useWatcherBasedSetupFocusTrapWithDestroyIncluded } from '@shell/composables/focusTrap';
9
9
  import { getPluginChartVersionLabel, getPluginChartVersion } from '@shell/utils/uiplugins';
10
- import { isChartVersionHigher } from '@shell/config/uiplugins';
10
+ import { isChartVersionHigher, uiPluginHasAnnotation } from '@shell/config/uiplugins';
11
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
12
+ import Banner from '@components/Banner/Banner.vue';
11
13
  import RcButton from '@components/RcButton/RcButton.vue';
12
14
  import AppChartCardFooter from '@shell/pages/c/_cluster/apps/charts/AppChartCardFooter.vue';
13
15
 
@@ -25,6 +27,7 @@ export default {
25
27
  }
26
28
  },
27
29
  components: {
30
+ Banner,
28
31
  ChartReadme,
29
32
  LazyImage,
30
33
  RcButton,
@@ -48,6 +51,24 @@ export default {
48
51
  computed: {
49
52
  ...mapGetters({ theme: 'prefs/theme' }),
50
53
 
54
+ errorMessage() {
55
+ return this.info?.installedError || (this.info?.helmError ? this.t('plugins.helmError') : null);
56
+ },
57
+
58
+ warningMessages() {
59
+ const warnings = [];
60
+
61
+ if (uiPluginHasAnnotation(this.info?.chart, CATALOG_ANNOTATIONS.DEPRECATED, 'true')) {
62
+ warnings.push(this.t('plugins.deprecatedExtension'));
63
+ }
64
+
65
+ if (this.info?.incompatibilityMessage) {
66
+ warnings.push(this.info.incompatibilityMessage);
67
+ }
68
+
69
+ return warnings;
70
+ },
71
+
51
72
  applyDarkModeBg() {
52
73
  if (this.theme === 'dark') {
53
74
  return { 'dark-mode': true };
@@ -307,6 +328,20 @@ export default {
307
328
  :items="info.tags"
308
329
  class="plugin-tags-container"
309
330
  />
331
+ <Banner
332
+ v-for="(msg, i) in warningMessages"
333
+ :key="i"
334
+ color="warning"
335
+ >
336
+ {{ msg }}
337
+ </Banner>
338
+
339
+ <Banner
340
+ v-if="errorMessage"
341
+ color="error"
342
+ >
343
+ {{ errorMessage }}
344
+ </Banner>
310
345
 
311
346
  <div class="plugin-versions-container">
312
347
  <h3>
@@ -452,8 +487,10 @@ export default {
452
487
  flex-direction: column;
453
488
  overflow: hidden;
454
489
 
455
- .banner.warning {
490
+ .banner.warning,
491
+ .banner.error {
456
492
  margin-top: 0;
493
+ margin-bottom: 32px;
457
494
  }
458
495
 
459
496
  .plugin-info-detail {
@@ -1,5 +1,6 @@
1
1
  import { shallowMount, VueWrapper } from '@vue/test-utils';
2
2
  import PluginInfoPanel from '@shell/pages/c/_cluster/uiplugins/PluginInfoPanel.vue';
3
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
3
4
 
4
5
  jest.mock('@shell/config/uiplugins', () => ({
5
6
  ...jest.requireActual('@shell/config/uiplugins'),
@@ -99,4 +100,64 @@ describe('component: PluginInfoPanel', () => {
99
100
  expect(label).toBe('1.0.0 (plugins.labels.current)');
100
101
  });
101
102
  });
103
+
104
+ describe('errorMessage', () => {
105
+ beforeEach(() => {
106
+ wrapper = mountComponent();
107
+ });
108
+
109
+ it('should return installedError if present', () => {
110
+ wrapper.vm.info = { installedError: 'install error' };
111
+
112
+ expect(wrapper.vm.errorMessage).toBe('install error');
113
+ });
114
+
115
+ it('should return translated helmError if present', () => {
116
+ wrapper.vm.info = { helmError: true };
117
+
118
+ expect(wrapper.vm.errorMessage).toBe('plugins.helmError');
119
+ });
120
+
121
+ it('should return null if no error', () => {
122
+ wrapper.vm.info = {};
123
+
124
+ expect(wrapper.vm.errorMessage).toBeNull();
125
+ });
126
+ });
127
+
128
+ describe('warningMessages', () => {
129
+ beforeEach(() => {
130
+ wrapper = mountComponent();
131
+ });
132
+
133
+ it('should include deprecated message if the extension chart has the deprecated annotation', () => {
134
+ wrapper.vm.info = { chart: { versions: [{ annotations: { [CATALOG_ANNOTATIONS.DEPRECATED]: 'true' } }] } };
135
+
136
+ expect(wrapper.vm.warningMessages).toContain('plugins.deprecatedExtension');
137
+ });
138
+
139
+ it('should include incompatibilityMessage if present', () => {
140
+ wrapper.vm.info = { incompatibilityMessage: 'incompatibility error' };
141
+
142
+ expect(wrapper.vm.warningMessages).toContain('incompatibility error');
143
+ });
144
+
145
+ it('should include both deprecated and incompatibility messages if both are present', () => {
146
+ wrapper.vm.info = {
147
+ chart: { versions: [{ annotations: { [CATALOG_ANNOTATIONS.DEPRECATED]: 'true' } }] },
148
+ incompatibilityMessage: 'incompatibility error'
149
+ };
150
+
151
+ expect(wrapper.vm.warningMessages).toStrictEqual([
152
+ 'plugins.deprecatedExtension',
153
+ 'incompatibility error'
154
+ ]);
155
+ });
156
+
157
+ it('should return an empty array if neither is present', () => {
158
+ wrapper.vm.info = { chart: { versions: [{ annotations: { [CATALOG_ANNOTATIONS.CERTIFIED]: 'rancher' } }] } };
159
+
160
+ expect(wrapper.vm.warningMessages).toStrictEqual([]);
161
+ });
162
+ });
102
163
  });