@rancher/shell 3.0.12-rc.1 → 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.
- package/assets/images/providers/entraid-black.svg +4 -0
- package/assets/images/providers/entraid.svg +9 -0
- package/assets/images/vendor/entraid.svg +9 -0
- package/assets/styles/app.scss +0 -1
- package/assets/translations/en-us.yaml +19 -17
- package/assets/translations/zh-hans.yaml +4 -8
- package/chart/__tests__/S3.test.ts +10 -3
- package/components/CountBox.vue +20 -0
- package/components/CreateDriver.vue +0 -12
- package/components/DetailText.vue +12 -3
- package/components/SelectIconGrid.vue +5 -0
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/fleet/FleetClusterTargets/index.vue +18 -1
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +82 -24
- package/components/form/Select.vue +73 -56
- package/components/form/ServiceNameSelect.vue +13 -11
- package/components/form/__tests__/KeyValue.test.ts +66 -0
- package/components/form/__tests__/NodeScheduling.test.ts +9 -0
- package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/nav/Group.vue +7 -6
- package/components/nav/Header.vue +24 -3
- package/components/nav/NotificationCenter/Notification.vue +4 -1
- package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
- package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
- package/components/nav/Type.vue +8 -7
- package/components/nav/WindowManager/index.vue +2 -1
- package/components/nav/WorkspaceSwitcher.vue +13 -0
- package/components/nav/__tests__/Group.test.ts +67 -0
- package/components/nav/__tests__/Header.test.ts +235 -0
- package/components/nav/__tests__/Type.test.ts +20 -3
- package/components/templates/default.vue +34 -4
- package/components/templates/home.vue +12 -25
- package/components/templates/plain.vue +13 -26
- package/composables/useLabeledFormElement.ts +10 -2
- package/composables/useLabeledSelect.ts +60 -0
- package/composables/useUserRetentionValidation.ts +1 -49
- package/config/cookies.js +0 -1
- package/config/labels-annotations.js +1 -0
- package/config/query-params.js +1 -0
- package/config/router/routes.js +0 -8
- package/core/__tests__/plugin-products.test.ts +616 -25
- package/core/plugin-products-base.ts +31 -14
- package/core/plugin-products-helpers.ts +5 -4
- package/core/plugin-types.ts +18 -3
- package/core/types.ts +3 -1
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/management.cattle.io.fleetworkspace.vue +49 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
- package/edit/__tests__/kontainerDriver.test.ts +0 -13
- package/edit/__tests__/nodeDriver.test.ts +5 -11
- package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auth/__tests__/oidc.test.ts +54 -0
- package/edit/auth/azuread.vue +1 -1
- package/edit/auth/oidc.vue +8 -0
- package/edit/kontainerDriver.vue +1 -2
- package/edit/nodeDriver.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
- package/initialize/App.vue +29 -2
- package/initialize/install-plugins.js +0 -2
- package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
- package/list/catalog.cattle.io.app.vue +25 -5
- package/list/management.cattle.io.feature.vue +1 -1
- package/list/management.cattle.io.fleetworkspace.vue +8 -0
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/chart.js +40 -9
- package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
- package/models/__tests__/chart.test.ts +99 -6
- package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
- package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
- package/models/catalog.cattle.io.app.js +21 -17
- package/models/catalog.cattle.io.clusterrepo.js +39 -11
- package/models/chart.js +33 -19
- package/models/fleet-application.js +1 -1
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/kontainerdriver.js +11 -0
- package/models/management.cattle.io.authconfig.js +5 -1
- package/models/management.cattle.io.cluster.js +0 -53
- package/models/management.cattle.io.feature.js +3 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -26
- package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
- package/models/nodedriver.js +7 -0
- package/package.json +13 -12
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
- package/pages/c/_cluster/apps/charts/chart.vue +217 -33
- package/pages/c/_cluster/apps/charts/index.vue +2 -2
- package/pages/c/_cluster/apps/charts/install.vue +8 -3
- package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +15 -10
- package/pages/c/_cluster/uiplugins/index.vue +23 -25
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
- package/scripts/test-plugins-build.sh +5 -2
- package/server/server-middleware.js +2 -2
- package/static/humans.txt +1 -0
- package/static/robots.txt +34 -0
- package/static/welcome-cow.svg +18 -0
- package/store/__tests__/catalog.test.ts +161 -11
- package/store/auth.js +0 -3
- package/store/catalog.js +60 -8
- package/types/shell/index.d.ts +26 -22
- package/utils/__tests__/git.test.ts +270 -0
- package/utils/__tests__/inactivity.test.ts +316 -0
- package/utils/__tests__/object.test.ts +77 -0
- package/utils/__tests__/time.test.ts +14 -1
- package/utils/__tests__/url.test.ts +246 -0
- package/utils/object.js +33 -2
- package/utils/time.ts +5 -0
- package/vue.config.js +0 -9
- package/assets/images/providers/azuread-black.svg +0 -22
- package/assets/images/providers/azuread.svg +0 -25
- package/assets/images/vendor/azuread.svg +0 -18
- package/assets/styles/fonts/_dots.scss +0 -18
- package/components/EmberPage.vue +0 -622
- package/components/EmberPageView.vue +0 -39
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
- package/mixins/labeled-form-element.ts +0 -225
- package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
- package/pages/c/_cluster/manager/pages/_page.vue +0 -22
- package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
- package/plugins/ember-cookie.js +0 -17
- 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
|
|
606
|
-
|
|
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 {
|
|
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<
|
|
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
|
-
|
|
93
|
-
.
|
|
113
|
+
const skippedIds: readonly UserRetentionSettingId[] = [
|
|
114
|
+
SETTING.DISABLE_INACTIVE_USER_AFTER,
|
|
115
|
+
SETTING.DELETE_INACTIVE_USER_AFTER,
|
|
116
|
+
];
|
|
94
117
|
|
|
95
|
-
|
|
118
|
+
ids.filter((id) => !skippedIds.includes(id))
|
|
119
|
+
.forEach(assignSettings);
|
|
96
120
|
});
|
|
97
121
|
|
|
98
|
-
const assignSettings = (key:
|
|
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="
|
|
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="
|
|
77
|
+
v-if="hasEmberUiDrivers"
|
|
79
78
|
color="warning"
|
|
80
|
-
label-key="drivers.kontainer.
|
|
81
|
-
data-testid="kontainer-driver-ember-deprecation-banner"
|
|
79
|
+
label-key="drivers.kontainer.emberRemovalMessage"
|
|
82
80
|
/>
|
|
83
81
|
<ResourceTable
|
|
84
82
|
:schema="schema"
|
|
@@ -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
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { shallowMount, VueWrapper } from '@vue/test-utils';
|
|
2
2
|
import UiPluginsPage from '@shell/pages/c/_cluster/uiplugins/index.vue';
|
|
3
3
|
import { UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
|
|
4
|
+
import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
4
5
|
|
|
5
6
|
const t = (key: string, args: Object) => {
|
|
6
7
|
if (args) {
|
|
@@ -292,10 +293,11 @@ describe('page: UI plugins/Extensions', () => {
|
|
|
292
293
|
});
|
|
293
294
|
|
|
294
295
|
it('should return "deprecated" status for deprecated plugins', () => {
|
|
295
|
-
const plugin = { chart: {
|
|
296
|
+
const plugin = { chart: { versions: [{ annotations: { [CATALOG_ANNOTATIONS.DEPRECATED]: 'true' } }] } };
|
|
296
297
|
const statuses = wrapper.vm.getStatuses(plugin);
|
|
297
298
|
|
|
298
|
-
expect(statuses[0].tooltip.
|
|
299
|
+
expect(statuses[0].tooltip.text).toBe('generic.deprecated');
|
|
300
|
+
expect(statuses[0].color).toBe('error');
|
|
299
301
|
});
|
|
300
302
|
|
|
301
303
|
it('should return error status for installedError', () => {
|
|
@@ -303,15 +305,17 @@ describe('page: UI plugins/Extensions', () => {
|
|
|
303
305
|
const statuses = wrapper.vm.getStatuses(plugin);
|
|
304
306
|
|
|
305
307
|
expect(statuses[0].icon).toBe('icon-alert-alt');
|
|
306
|
-
expect(statuses[0].
|
|
308
|
+
expect(statuses[0].color).toBe('error');
|
|
309
|
+
expect(statuses[0].tooltip.text).toBe('An error occurred');
|
|
307
310
|
});
|
|
308
311
|
|
|
309
|
-
it('should return
|
|
312
|
+
it('should return warning status for incompatibilityMessage', () => {
|
|
310
313
|
const plugin = { incompatibilityMessage: 'Incompatible version' };
|
|
311
314
|
const statuses = wrapper.vm.getStatuses(plugin);
|
|
312
315
|
|
|
313
316
|
expect(statuses[0].icon).toBe('icon-alert-alt');
|
|
314
|
-
expect(statuses[0].
|
|
317
|
+
expect(statuses[0].color).toBe('error');
|
|
318
|
+
expect(statuses[0].tooltip.text).toBe('Incompatible version');
|
|
315
319
|
});
|
|
316
320
|
|
|
317
321
|
it('should return error status for helmError', () => {
|
|
@@ -319,15 +323,16 @@ describe('page: UI plugins/Extensions', () => {
|
|
|
319
323
|
const statuses = wrapper.vm.getStatuses(plugin);
|
|
320
324
|
|
|
321
325
|
expect(statuses[0].icon).toBe('icon-alert-alt');
|
|
322
|
-
expect(statuses[0].
|
|
326
|
+
expect(statuses[0].color).toBe('error');
|
|
327
|
+
expect(statuses[0].tooltip.text).toBe('plugins.helmError');
|
|
323
328
|
});
|
|
324
329
|
|
|
325
|
-
it('should combine deprecated and
|
|
326
|
-
const plugin = { chart: {
|
|
330
|
+
it('should combine deprecated and error messages in a single tooltip', () => {
|
|
331
|
+
const plugin = { chart: { versions: [{ annotations: { [CATALOG_ANNOTATIONS.DEPRECATED]: 'true' } }] }, helmError: true };
|
|
327
332
|
const statuses = wrapper.vm.getStatuses(plugin);
|
|
328
|
-
const
|
|
333
|
+
const errorStatus = statuses.find((status: any) => status.color === 'error');
|
|
329
334
|
|
|
330
|
-
expect(
|
|
335
|
+
expect(errorStatus.tooltip.text).toBe('generic.deprecated<br/>plugins.helmError');
|
|
331
336
|
});
|
|
332
337
|
});
|
|
333
338
|
|
|
@@ -5,7 +5,8 @@ import { mapPref, PLUGIN_DEVELOPER } from '@shell/store/prefs';
|
|
|
5
5
|
import { sortBy } from '@shell/utils/sort';
|
|
6
6
|
import genericPluginSvg from '~shell/assets/images/generic-plugin.svg';
|
|
7
7
|
import { allHash } from '@shell/utils/promise';
|
|
8
|
-
import { CATALOG, UI_PLUGIN, MANAGEMENT
|
|
8
|
+
import { CATALOG, UI_PLUGIN, MANAGEMENT } from '@shell/config/types';
|
|
9
|
+
import { isMissingDate } from '@shell/utils/time';
|
|
9
10
|
import { SETTING } from '@shell/config/settings';
|
|
10
11
|
import { fetchOrCreateSetting } from '@shell/utils/settings';
|
|
11
12
|
import { getVersionData, isRancherPrime } from '@shell/config/version';
|
|
@@ -896,18 +897,12 @@ export default {
|
|
|
896
897
|
label: plugin.displayVersionLabel,
|
|
897
898
|
}];
|
|
898
899
|
|
|
899
|
-
if (plugin.created) {
|
|
900
|
-
|
|
901
|
-
const lastUpdatedItem = {
|
|
900
|
+
if (!isMissingDate(plugin.created)) {
|
|
901
|
+
items.push({
|
|
902
902
|
icon: 'icon-refresh-alt',
|
|
903
903
|
iconTooltip: { key: 'tableHeaders.lastUpdated' },
|
|
904
|
-
label:
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
if (hasZeroTime) {
|
|
908
|
-
lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
|
|
909
|
-
}
|
|
910
|
-
items.push(lastUpdatedItem);
|
|
904
|
+
label: day(plugin.created).format('MMM D, YYYY')
|
|
905
|
+
});
|
|
911
906
|
}
|
|
912
907
|
|
|
913
908
|
if (plugin.installing) {
|
|
@@ -989,24 +984,27 @@ export default {
|
|
|
989
984
|
getStatuses(plugin) {
|
|
990
985
|
const statuses = [];
|
|
991
986
|
|
|
992
|
-
const
|
|
993
|
-
const
|
|
987
|
+
const errorMsg = plugin.installedError || (plugin.helmError ? this.t('plugins.helmError') : null);
|
|
988
|
+
const incompatibilityMsg = plugin.incompatibilityMessage;
|
|
989
|
+
const isDeprecated = uiPluginHasAnnotation(plugin?.chart, CATALOG_ANNOTATIONS.DEPRECATED, 'true');
|
|
994
990
|
|
|
995
|
-
|
|
996
|
-
let tooltip;
|
|
991
|
+
const tooltipMsgs = [];
|
|
997
992
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
993
|
+
if (isDeprecated) {
|
|
994
|
+
tooltipMsgs.push(this.t('generic.deprecated'));
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (errorMsg) {
|
|
998
|
+
tooltipMsgs.push(errorMsg);
|
|
999
|
+
} else if (incompatibilityMsg) {
|
|
1000
|
+
tooltipMsgs.push(incompatibilityMsg);
|
|
1001
|
+
}
|
|
1005
1002
|
|
|
1003
|
+
if (tooltipMsgs.length) {
|
|
1006
1004
|
statuses.push({
|
|
1007
|
-
icon:
|
|
1008
|
-
color:
|
|
1009
|
-
tooltip
|
|
1005
|
+
icon: 'icon-alert-alt',
|
|
1006
|
+
color: 'error',
|
|
1007
|
+
tooltip: { text: tooltipMsgs.join('<br/>') }
|
|
1010
1008
|
});
|
|
1011
1009
|
}
|
|
1012
1010
|
|