@rancher/shell 3.0.9-rc.3 → 3.0.9-rc.5
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/brand/suse/metadata.json +2 -1
- package/assets/translations/en-us.yaml +105 -5
- package/components/ActionMenuShell.vue +1 -1
- package/components/Inactivity.vue +2 -2
- package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
- package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
- package/components/Resource/Detail/Masthead/index.vue +11 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
- package/components/Resource/Detail/Metadata/index.vue +1 -1
- package/components/Resource/Detail/ResourceRow.vue +1 -1
- package/components/ResourceDetail/Masthead/latest.vue +12 -2
- package/components/ResourceList/index.vue +9 -0
- package/components/ResourceTable.vue +38 -4
- package/components/Tabbed/Tab.vue +4 -0
- package/components/Tabbed/index.vue +4 -1
- package/components/__tests__/ProjectRow.test.ts +60 -0
- package/components/form/ChangePassword.vue +41 -35
- package/components/form/ResourceQuota/Project.vue +42 -1
- package/components/form/ResourceQuota/ProjectRow.vue +71 -4
- package/components/form/ResourceQuota/__tests__/Project.test.ts +63 -0
- package/components/form/SelectOrCreateAuthSecret.vue +6 -1
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
- package/components/formatter/KubeconfigClusters.vue +74 -0
- package/components/formatter/MachineSummaryGraph.vue +10 -2
- package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
- package/components/nav/TopLevelMenu.helper.ts +50 -2
- package/components/nav/TopLevelMenu.vue +14 -0
- package/components/nav/Type.vue +5 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
- package/components/nav/__tests__/Type.test.ts +6 -4
- package/config/product/explorer.js +4 -3
- package/config/product/manager.js +47 -3
- package/config/router/navigation-guards/authentication.js +8 -9
- package/config/router/routes.js +4 -1
- package/config/types.js +10 -2
- package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
- package/detail/management.cattle.io.user.vue +1 -2
- package/detail/node.vue +0 -1
- package/detail/provisioning.cattle.io.cluster.vue +2 -1
- package/dialog/ChangePasswordDialog.vue +8 -0
- package/dialog/GenericPrompt.vue +20 -3
- package/dialog/ScaleMachineDownDialog.vue +65 -15
- package/dialog/SearchDialog.vue +10 -2
- package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -1
- package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
- package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
- package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
- package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
- package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
- package/edit/fleet.cattle.io.gitrepo.vue +16 -1
- package/edit/management.cattle.io.project.vue +8 -2
- package/edit/management.cattle.io.user.vue +29 -34
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -2
- package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
- package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
- package/list/ext.cattle.io.kubeconfig.vue +118 -0
- package/list/group.principal.vue +11 -15
- package/list/management.cattle.io.user.vue +11 -21
- package/machine-config/azure.vue +14 -0
- package/mixins/__tests__/chart.test.ts +147 -0
- package/mixins/browser-tab-visibility.js +5 -4
- package/mixins/chart.js +10 -8
- package/mixins/fetch.client.js +6 -0
- package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
- package/models/__tests__/secret.test.ts +55 -0
- package/models/__tests__/workload.test.ts +49 -6
- package/models/auditlog.cattle.io.auditpolicy.js +46 -0
- package/models/cluster.x-k8s.io.machine.js +1 -1
- package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
- package/models/event.js +5 -0
- package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
- package/models/ext.cattle.io.kubeconfig.ts +97 -0
- package/models/ext.cattle.io.passwordchangerequest.js +15 -0
- package/models/ext.cattle.io.selfuser.js +15 -0
- package/models/fleet-application.js +17 -7
- package/models/management.cattle.io.user.js +28 -31
- package/models/schema.js +18 -0
- package/models/secret.js +28 -25
- package/models/steve-schema.ts +39 -2
- package/models/workload.js +3 -2
- package/package.json +2 -2
- package/pages/about.vue +3 -2
- package/pages/account/index.vue +23 -16
- package/pages/auth/login.vue +15 -8
- package/pages/auth/setup.vue +52 -15
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
- package/pages/c/_cluster/apps/charts/index.vue +1 -0
- package/pages/home.vue +9 -3
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
- package/plugins/dashboard-store/actions.js +7 -0
- package/plugins/dashboard-store/getters.js +23 -1
- package/plugins/dashboard-store/index.js +3 -2
- package/plugins/dashboard-store/mutations.js +4 -0
- package/plugins/dashboard-store/resource-class.js +12 -5
- package/plugins/steve/__tests__/steve-class.test.ts +167 -0
- package/plugins/steve/schema.d.ts +5 -0
- package/plugins/steve/steve-class.js +19 -0
- package/plugins/steve/steve-pagination-utils.ts +2 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +4 -2
- package/rancher-components/RcItemCard/RcItemCard.vue +27 -10
- package/store/auth.js +57 -19
- package/store/notifications.ts +1 -1
- package/store/type-map.js +12 -1
- package/types/shell/index.d.ts +24 -15
- package/types/store/dashboard-store.types.ts +7 -0
- package/utils/__tests__/chart.test.ts +96 -0
- package/utils/__tests__/version.test.ts +1 -19
- package/utils/chart.js +64 -0
- package/utils/pagination-wrapper.ts +11 -3
- package/utils/version.js +5 -17
- package/vue.config.js +26 -13
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { useStore } from 'vuex';
|
|
4
|
+
import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
|
|
5
|
+
import { CAPI, MANAGEMENT } from '@shell/config/types';
|
|
6
|
+
import { ActionFindPageArgs } from '@shell/types/store/dashboard-store.types';
|
|
7
|
+
import { FilterArgs, PaginationFilterField, PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
8
|
+
import { PagTableFetchPageSecondaryResourcesOpts, PagTableFetchSecondaryResourcesOpts, PagTableFetchSecondaryResourcesReturns } from '@shell/types/components/paginatedResourceTable';
|
|
9
|
+
|
|
10
|
+
defineProps({
|
|
11
|
+
schema: {
|
|
12
|
+
type: Object,
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
useQueryParamsForSimpleFiltering: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
default: false
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const store = useStore();
|
|
23
|
+
|
|
24
|
+
const canViewProvClusters = computed<boolean>(() => {
|
|
25
|
+
return !!store.getters['management/canList'](CAPI.RANCHER_CLUSTER);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const canViewMgmtClusters = computed<boolean>(() => {
|
|
29
|
+
return !!store.getters['management/canList'](MANAGEMENT.CLUSTER);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Fetch all clusters when not using pagination
|
|
34
|
+
*/
|
|
35
|
+
async function fetchSecondaryResources({ canPaginate }: PagTableFetchSecondaryResourcesOpts): PagTableFetchSecondaryResourcesReturns {
|
|
36
|
+
if (canPaginate) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const promises = [];
|
|
41
|
+
|
|
42
|
+
if (canViewProvClusters.value) {
|
|
43
|
+
promises.push(store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER }));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (canViewMgmtClusters.value) {
|
|
47
|
+
promises.push(store.dispatch('management/findAll', { type: MANAGEMENT.CLUSTER }));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await Promise.all(promises);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fetch only the clusters referenced by kubeconfigs on the current page
|
|
55
|
+
*
|
|
56
|
+
* NOTE: For the time being this isn't validated because ext.cattle.io.kubeconfig is not one of the indexed resources. I'm putting this in for future support since secondary resources are needed.
|
|
57
|
+
*/
|
|
58
|
+
async function fetchPageSecondaryResources({ force, page }: PagTableFetchPageSecondaryResourcesOpts) {
|
|
59
|
+
if (!page?.length) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const uniqueClusterIds = new Set<string>();
|
|
64
|
+
|
|
65
|
+
page.forEach((kubeconfig: any) => {
|
|
66
|
+
const ids = kubeconfig.spec?.clusters || [];
|
|
67
|
+
|
|
68
|
+
ids.forEach((id: string) => uniqueClusterIds.add(id));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (uniqueClusterIds.size === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const clusterIdArray = Array.from(uniqueClusterIds);
|
|
76
|
+
|
|
77
|
+
if (canViewProvClusters.value) {
|
|
78
|
+
const opt: ActionFindPageArgs = {
|
|
79
|
+
force,
|
|
80
|
+
pagination: new FilterArgs({
|
|
81
|
+
filters: PaginationParamFilter.createMultipleFields(
|
|
82
|
+
clusterIdArray.map((id) => new PaginationFilterField({
|
|
83
|
+
field: 'status.clusterName', // Verified it's one of the attribute fields listed in the schema, according to steve-pagination-utils that means it should be filterable
|
|
84
|
+
value: id
|
|
85
|
+
}))
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
store.dispatch('management/findPage', { type: CAPI.RANCHER_CLUSTER, opt });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (canViewMgmtClusters.value) {
|
|
94
|
+
const opt: ActionFindPageArgs = {
|
|
95
|
+
force,
|
|
96
|
+
pagination: new FilterArgs({
|
|
97
|
+
filters: PaginationParamFilter.createMultipleFields(
|
|
98
|
+
clusterIdArray.map((id) => new PaginationFilterField({
|
|
99
|
+
field: 'metadata.name', // Verified it's one of the attribute fields listed in the schema, according to steve-pagination-utils that means it should be filterable
|
|
100
|
+
value: id
|
|
101
|
+
}))
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
store.dispatch('management/findPage', { type: MANAGEMENT.CLUSTER, opt });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<template>
|
|
112
|
+
<PaginatedResourceTable
|
|
113
|
+
:schema="schema"
|
|
114
|
+
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
|
115
|
+
:fetch-secondary-resources="fetchSecondaryResources"
|
|
116
|
+
:fetch-page-secondary-resources="fetchPageSecondaryResources"
|
|
117
|
+
/>
|
|
118
|
+
</template>
|
package/list/group.principal.vue
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
import ResourceTable from '@shell/components/ResourceTable';
|
|
3
3
|
import Loading from '@shell/components/Loading';
|
|
4
4
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
|
5
|
-
import { NORMAN, MANAGEMENT } from '@shell/config/types';
|
|
5
|
+
import { NORMAN, MANAGEMENT, EXT } from '@shell/config/types';
|
|
6
6
|
import AsyncButton from '@shell/components/AsyncButton';
|
|
7
7
|
import { applyProducts } from '@shell/store/type-map';
|
|
8
8
|
import { NAME } from '@shell/config/product/auth';
|
|
9
9
|
import { MODE, _EDIT } from '@shell/config/query-params';
|
|
10
10
|
import { mapState } from 'vuex';
|
|
11
11
|
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
12
|
-
import { allHash } from '@shell/utils/promise';
|
|
13
12
|
|
|
14
13
|
export default {
|
|
15
14
|
components: {
|
|
@@ -37,21 +36,20 @@ export default {
|
|
|
37
36
|
const authConfigSchema = this.$store.getters[`management/schemaFor`](MANAGEMENT.AUTH_CONFIG);
|
|
38
37
|
const grbSchema = this.$store.getters['rancher/schemaFor'](NORMAN.GLOBAL_ROLE_BINDING);
|
|
39
38
|
|
|
40
|
-
const
|
|
41
|
-
user: this.$store.dispatch('rancher/request', { url: '/v3/users?limit=0' }),
|
|
42
|
-
providers: authConfigSchema ? this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.AUTH_CONFIG }) : Promise.resolve([])
|
|
43
|
-
});
|
|
39
|
+
const providers = authConfigSchema ? await this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.AUTH_CONFIG }) : [];
|
|
44
40
|
|
|
45
|
-
const nonLocalAuthProvider = !!
|
|
41
|
+
const nonLocalAuthProvider = !!providers.find((p) => p.name !== 'local' && p.enabled === true);
|
|
46
42
|
|
|
47
|
-
this.
|
|
43
|
+
this.membershipRefreshRequests = await this.$store.dispatch('management/create', { type: EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS });
|
|
44
|
+
this.canRefreshMemberships = !!this.membershipRefreshRequests?.canRefreshMemberships;
|
|
48
45
|
this.canCreateGlobalRoleBinding = nonLocalAuthProvider && grbSchema?.collectionMethods?.includes('POST');
|
|
49
46
|
},
|
|
50
47
|
data() {
|
|
51
48
|
return {
|
|
52
49
|
rows: [],
|
|
50
|
+
membershipRefreshRequests: undefined,
|
|
53
51
|
canCreateGlobalRoleBinding: false,
|
|
54
|
-
|
|
52
|
+
canRefreshMemberships: false,
|
|
55
53
|
assignLocation: {
|
|
56
54
|
path: `/c/${ BLANK_CLUSTER }/${ NAME }/${ NORMAN.SPOOFED.GROUP_PRINCIPAL }/assign-edit`,
|
|
57
55
|
query: { [MODE]: _EDIT }
|
|
@@ -86,11 +84,9 @@ export default {
|
|
|
86
84
|
},
|
|
87
85
|
async refreshGroupMemberships(buttonDone) {
|
|
88
86
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
data: { },
|
|
93
|
-
});
|
|
87
|
+
// userId specifies the user ID. Use '*' for all users. Check the schemaDefinition for more details.
|
|
88
|
+
this.membershipRefreshRequests.spec = { userId: '*' };
|
|
89
|
+
await this.membershipRefreshRequests.save();
|
|
94
90
|
|
|
95
91
|
await this.updateGroupPrincipals(true);
|
|
96
92
|
|
|
@@ -125,7 +121,7 @@ export default {
|
|
|
125
121
|
>
|
|
126
122
|
<template #extraActions>
|
|
127
123
|
<AsyncButton
|
|
128
|
-
v-if="
|
|
124
|
+
v-if="canRefreshMemberships"
|
|
129
125
|
mode="refresh"
|
|
130
126
|
:action-label="t('authGroups.actions.refresh')"
|
|
131
127
|
:waiting-label="t('authGroups.actions.refresh')"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import AsyncButton from '@shell/components/AsyncButton';
|
|
3
|
-
import {
|
|
3
|
+
import { EXT } from '@shell/config/types';
|
|
4
4
|
import { NAME } from '@shell/config/product/auth';
|
|
5
5
|
import ResourceTable from '@shell/components/ResourceTable';
|
|
6
6
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
|
@@ -38,14 +38,10 @@ export default {
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
async fetch() {
|
|
41
|
-
const store = this.$store;
|
|
42
|
-
|
|
43
|
-
await store.dispatch(`rancher/findAll`, { type: NORMAN.USER });
|
|
44
|
-
|
|
45
41
|
await this.$fetchType(this.resource);
|
|
46
42
|
|
|
47
|
-
this.
|
|
48
|
-
|
|
43
|
+
this.membershipRefreshRequests = await this.$store.dispatch('management/create', { type: EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS });
|
|
44
|
+
this.canRefreshMemberships = !!this.membershipRefreshRequests?.canRefreshMemberships;
|
|
49
45
|
},
|
|
50
46
|
|
|
51
47
|
data() {
|
|
@@ -55,7 +51,8 @@ export default {
|
|
|
55
51
|
|
|
56
52
|
return {
|
|
57
53
|
schema,
|
|
58
|
-
|
|
54
|
+
membershipRefreshRequests: undefined,
|
|
55
|
+
canRefreshMemberships: false
|
|
59
56
|
};
|
|
60
57
|
},
|
|
61
58
|
|
|
@@ -82,28 +79,21 @@ export default {
|
|
|
82
79
|
// 1) Only show system users in explorer/users and not in auth/users
|
|
83
80
|
// 2) Supplement user with info to enable/disable the refresh group membership action (this is not persisted on save)
|
|
84
81
|
const params = { ...this.$route.params };
|
|
85
|
-
const requiredUsers = params.product === NAME ? this.rows.filter((a) => !a.isSystem) : this.rows;
|
|
86
|
-
|
|
87
|
-
requiredUsers.forEach((r) => {
|
|
88
|
-
r.canRefreshAccess = this.canRefreshAccess;
|
|
89
|
-
});
|
|
90
82
|
|
|
91
|
-
return
|
|
83
|
+
return params.product === NAME ? this.rows.filter((a) => !a.isSystem) : this.rows;
|
|
92
84
|
},
|
|
93
85
|
|
|
94
86
|
isAdmin() {
|
|
95
87
|
return isAdminUser(this.$store.getters);
|
|
96
|
-
}
|
|
88
|
+
}
|
|
97
89
|
},
|
|
98
90
|
|
|
99
91
|
methods: {
|
|
100
92
|
async refreshGroupMemberships(buttonDone) {
|
|
101
93
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
});
|
|
106
|
-
|
|
94
|
+
// userId specifies the user ID. Use '*' for all users. Check the schemaDefinition for more details.
|
|
95
|
+
this.membershipRefreshRequests.spec = { userId: '*' };
|
|
96
|
+
await this.membershipRefreshRequests.save();
|
|
107
97
|
buttonDone(true);
|
|
108
98
|
} catch (err) {
|
|
109
99
|
this.$store.dispatch('growl/fromError', { title: this.t('user.list.errorRefreshingGroupMemberships'), err }, { root: true });
|
|
@@ -125,7 +115,7 @@ export default {
|
|
|
125
115
|
>
|
|
126
116
|
<template #extraActions>
|
|
127
117
|
<AsyncButton
|
|
128
|
-
v-if="
|
|
118
|
+
v-if="canRefreshMemberships"
|
|
129
119
|
mode="refresh"
|
|
130
120
|
:action-label="t('authGroups.actions.refresh')"
|
|
131
121
|
:waiting-label="t('authGroups.actions.refresh')"
|
package/machine-config/azure.vue
CHANGED
|
@@ -209,6 +209,10 @@ export default {
|
|
|
209
209
|
}
|
|
210
210
|
},
|
|
211
211
|
|
|
212
|
+
setup() {
|
|
213
|
+
return { _EDIT };
|
|
214
|
+
},
|
|
215
|
+
|
|
212
216
|
data() {
|
|
213
217
|
return {
|
|
214
218
|
azureEnvironments,
|
|
@@ -518,6 +522,11 @@ export default {
|
|
|
518
522
|
</div>
|
|
519
523
|
</div>
|
|
520
524
|
<div v-else>
|
|
525
|
+
<Banner
|
|
526
|
+
v-if="mode === _EDIT && !value.managedDisks"
|
|
527
|
+
color="warning"
|
|
528
|
+
:label="t('cluster.machineConfig.azure.managedDisks.deprecationWarning', {}, true)"
|
|
529
|
+
/>
|
|
521
530
|
<div class="row mt-20">
|
|
522
531
|
<div class="col span-6">
|
|
523
532
|
<LabeledSelect
|
|
@@ -843,6 +852,11 @@ export default {
|
|
|
843
852
|
:label="t('cluster.machineConfig.azure.managedDisks.label')"
|
|
844
853
|
:disabled="disabled"
|
|
845
854
|
/>
|
|
855
|
+
<Banner
|
|
856
|
+
v-if="!value.managedDisks"
|
|
857
|
+
color="warning"
|
|
858
|
+
:label="t('cluster.machineConfig.azure.managedDisks.deprecationWarning', {}, true)"
|
|
859
|
+
/>
|
|
846
860
|
<Banner
|
|
847
861
|
v-if="value.availabilityZone && !value.managedDisks"
|
|
848
862
|
color="error"
|
|
@@ -322,5 +322,152 @@ describe('chartMixin', () => {
|
|
|
322
322
|
icon: 'icon-upgrade-alt',
|
|
323
323
|
});
|
|
324
324
|
});
|
|
325
|
+
|
|
326
|
+
it('should return "upgrade" action when upgrading from a pre-release to a stable version with "up" build metadata', () => {
|
|
327
|
+
const wrapper = mount(DummyComponent, {
|
|
328
|
+
data: () => ({
|
|
329
|
+
existing: { spec: { chart: { metadata: { version: '108.0.0+up0.25.0-rc.4' } } } },
|
|
330
|
+
version: { version: '108.0.0+up0.25.0' }
|
|
331
|
+
}),
|
|
332
|
+
global: {
|
|
333
|
+
mocks: {
|
|
334
|
+
$store: mockStore,
|
|
335
|
+
$route: { query: {} }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
expect(wrapper.vm.action).toStrictEqual({
|
|
341
|
+
name: 'upgrade',
|
|
342
|
+
tKey: 'upgrade',
|
|
343
|
+
icon: 'icon-upgrade-alt',
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should return "upgrade" action when upgrading with build metadata change', () => {
|
|
348
|
+
const wrapper = mount(DummyComponent, {
|
|
349
|
+
data: () => ({
|
|
350
|
+
existing: { spec: { chart: { metadata: { version: '1.0.0+1' } } } },
|
|
351
|
+
version: { version: '1.0.0+2' }
|
|
352
|
+
}),
|
|
353
|
+
global: {
|
|
354
|
+
mocks: {
|
|
355
|
+
$store: mockStore,
|
|
356
|
+
$route: { query: {} }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
expect(wrapper.vm.action).toStrictEqual({
|
|
362
|
+
name: 'upgrade',
|
|
363
|
+
tKey: 'upgrade',
|
|
364
|
+
icon: 'icon-upgrade-alt',
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('mappedVersions', () => {
|
|
370
|
+
it('should return versions sorted by semver (descending)', () => {
|
|
371
|
+
const versions = [
|
|
372
|
+
{ version: '0.1.0', created: '2026-01-01' },
|
|
373
|
+
{ version: '0.2.0-rc1', created: '2026-01-01' },
|
|
374
|
+
{ version: '0.2.0', created: '2026-01-01' },
|
|
375
|
+
{ version: '1.2.3', created: '2026-01-01' },
|
|
376
|
+
{ version: '1.2.3-dev', created: '2026-01-01' },
|
|
377
|
+
{ version: '10.0.0', created: '2026-01-01' },
|
|
378
|
+
{ version: '2.0.0', created: '2026-01-01' },
|
|
379
|
+
{ version: '2.0.0-rc2', created: '2026-01-01' },
|
|
380
|
+
{ version: '2.0.0-rc1', created: '2026-01-01' },
|
|
381
|
+
{ version: '2.0.0-beta.1', created: '2026-01-01' },
|
|
382
|
+
{ version: '2.0.0-alpha', created: '2026-01-01' },
|
|
383
|
+
{ version: '3.0.0-rc.3', created: '2026-01-01' },
|
|
384
|
+
{ version: '3.0.0-rc.2', created: '2026-01-01' },
|
|
385
|
+
{ version: '3.0.0-rc.10', created: '2026-01-01' },
|
|
386
|
+
{ version: '108.0.0+up0.25.0-rc.4', created: '2026-01-01' },
|
|
387
|
+
{ version: '108.0.0+up0.25.0', created: '2026-01-01' },
|
|
388
|
+
{ version: '1.0.0-alpha.beta', created: '2026-01-01' },
|
|
389
|
+
{ version: '1.0.0-alpha.1', created: '2026-01-01' },
|
|
390
|
+
{ version: '1.0.0-alpha.2', created: '2026-01-01' },
|
|
391
|
+
{ version: '1.0.0-alpha', created: '2026-01-01' },
|
|
392
|
+
{ version: '1.0.0-beta.11', created: '2026-01-01' },
|
|
393
|
+
{ version: '1.0.0-beta.2', created: '2026-01-01' },
|
|
394
|
+
{ version: '1.0.0-beta', created: '2026-01-01' },
|
|
395
|
+
{ version: '1.0.0+build.1', created: '2026-01-01' },
|
|
396
|
+
{ version: '1.0.0+build.2', created: '2026-01-01' },
|
|
397
|
+
{ version: '1.0.0+up1.0.0', created: '2026-01-01' },
|
|
398
|
+
{ version: '1.0.0+upFoo', created: '2026-01-01' },
|
|
399
|
+
{ version: '108.0.0+up0.25.0-rc.5', created: '2026-01-01' },
|
|
400
|
+
{ version: '108.0.0+up0.25.1', created: '2026-01-01' },
|
|
401
|
+
{ version: '0.0.1', created: '2026-01-01' }
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const mockStore = {
|
|
405
|
+
dispatch: jest.fn(() => Promise.resolve()),
|
|
406
|
+
getters: {
|
|
407
|
+
currentCluster: () => {},
|
|
408
|
+
isRancher: () => true,
|
|
409
|
+
'catalog/repo': () => () => 'repo',
|
|
410
|
+
'catalog/chart': () => ({ versions }),
|
|
411
|
+
'prefs/get': () => (key: string) => true,
|
|
412
|
+
'i18n/t': () => jest.fn()
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const DummyComponent = {
|
|
417
|
+
mixins: [ChartMixin],
|
|
418
|
+
template: '<div></div>',
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const wrapper = mount(
|
|
422
|
+
DummyComponent,
|
|
423
|
+
{
|
|
424
|
+
data() {
|
|
425
|
+
return { chart: { versions } };
|
|
426
|
+
},
|
|
427
|
+
global: {
|
|
428
|
+
mocks: {
|
|
429
|
+
$store: mockStore,
|
|
430
|
+
$route: { query: { version: '10.0.0' } }
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// mappedVersions is a computed property, so we access it directly
|
|
436
|
+
const result = wrapper.vm.mappedVersions;
|
|
437
|
+
const resultVersions = result.map((v: any) => v.version);
|
|
438
|
+
|
|
439
|
+
expect(resultVersions).toStrictEqual([
|
|
440
|
+
'108.0.0+up0.25.1',
|
|
441
|
+
'108.0.0+up0.25.0',
|
|
442
|
+
'108.0.0+up0.25.0-rc.5',
|
|
443
|
+
'108.0.0+up0.25.0-rc.4',
|
|
444
|
+
'10.0.0',
|
|
445
|
+
'3.0.0-rc.10',
|
|
446
|
+
'3.0.0-rc.3',
|
|
447
|
+
'3.0.0-rc.2',
|
|
448
|
+
'2.0.0',
|
|
449
|
+
'2.0.0-rc2',
|
|
450
|
+
'2.0.0-rc1',
|
|
451
|
+
'2.0.0-beta.1',
|
|
452
|
+
'2.0.0-alpha',
|
|
453
|
+
'1.2.3',
|
|
454
|
+
'1.2.3-dev',
|
|
455
|
+
'1.0.0+up1.0.0',
|
|
456
|
+
'1.0.0+upFoo',
|
|
457
|
+
'1.0.0+build.2',
|
|
458
|
+
'1.0.0+build.1',
|
|
459
|
+
'1.0.0-beta.11',
|
|
460
|
+
'1.0.0-beta.2',
|
|
461
|
+
'1.0.0-beta',
|
|
462
|
+
'1.0.0-alpha.beta',
|
|
463
|
+
'1.0.0-alpha.2',
|
|
464
|
+
'1.0.0-alpha.1',
|
|
465
|
+
'1.0.0-alpha',
|
|
466
|
+
'0.2.0',
|
|
467
|
+
'0.2.0-rc1',
|
|
468
|
+
'0.1.0',
|
|
469
|
+
'0.0.1'
|
|
470
|
+
]);
|
|
471
|
+
});
|
|
325
472
|
});
|
|
326
473
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EXT } from '@shell/config/types';
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
|
|
4
4
|
export default {
|
|
@@ -14,9 +14,10 @@ export default {
|
|
|
14
14
|
|
|
15
15
|
async visibilityChange() {
|
|
16
16
|
if (!document.hidden) {
|
|
17
|
-
await this.$store.dispatch('
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
await this.$store.dispatch('management/request', {
|
|
18
|
+
url: `/v1/${ EXT.SELFUSER }`,
|
|
19
|
+
method: 'POST',
|
|
20
|
+
data: {}
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
23
|
},
|
package/mixins/chart.js
CHANGED
|
@@ -10,7 +10,8 @@ import { NAME as MANAGER } from '@shell/config/product/manager';
|
|
|
10
10
|
import { OPA_GATE_KEEPER_ID } from '@shell/pages/c/_cluster/gatekeeper/index.vue';
|
|
11
11
|
import { formatSi, parseSi } from '@shell/utils/units';
|
|
12
12
|
import { CAPI, CATALOG } from '@shell/config/types';
|
|
13
|
-
import { isPrerelease
|
|
13
|
+
import { isPrerelease } from '@shell/utils/version';
|
|
14
|
+
import { compareChartVersions } from '@shell/utils/chart';
|
|
14
15
|
import difference from 'lodash/difference';
|
|
15
16
|
import { LINUX, APP_UPGRADE_STATUS } from '@shell/store/catalog';
|
|
16
17
|
import { clone } from '@shell/utils/object';
|
|
@@ -51,7 +52,12 @@ export default {
|
|
|
51
52
|
},
|
|
52
53
|
|
|
53
54
|
mappedVersions() {
|
|
54
|
-
const versions = this.chart?.versions || [];
|
|
55
|
+
const versions = (this.chart?.versions || []).slice();
|
|
56
|
+
|
|
57
|
+
versions.sort((a, b) => {
|
|
58
|
+
return compareChartVersions(b.version, a.version);
|
|
59
|
+
});
|
|
60
|
+
|
|
55
61
|
const selectedVersion = this.targetVersion;
|
|
56
62
|
const OSs = this.currentCluster?.workerOSs;
|
|
57
63
|
const out = [];
|
|
@@ -240,13 +246,9 @@ export default {
|
|
|
240
246
|
};
|
|
241
247
|
}
|
|
242
248
|
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
name: 'upgrade', tKey: 'upgrade', icon: 'icon-upgrade-alt'
|
|
246
|
-
};
|
|
247
|
-
}
|
|
249
|
+
const diff = compareChartVersions(this.currentVersion, this.targetVersion);
|
|
248
250
|
|
|
249
|
-
if (
|
|
251
|
+
if (diff < 0) {
|
|
250
252
|
return {
|
|
251
253
|
name: 'upgrade', tKey: 'upgrade', icon: 'icon-upgrade-alt'
|
|
252
254
|
};
|
package/mixins/fetch.client.js
CHANGED
|
@@ -5,6 +5,12 @@ const hasFetch = (component) => component.$options && typeof component.$options.
|
|
|
5
5
|
export const addLifecycleHook = (vm, hook, fn) => {
|
|
6
6
|
if (!vm.$options[hook]) {
|
|
7
7
|
vm.$options[hook] = [];
|
|
8
|
+
} else if (!Array.isArray(vm.$options[hook]) && typeof vm.$options[hook] === 'function' ) {
|
|
9
|
+
// This caters for when....
|
|
10
|
+
// - component has mixins, but they have no hooks of this type (vm.$options[hook] is then not an array)
|
|
11
|
+
// - component has the hook (vm.$options[hook] is then a function)
|
|
12
|
+
// - component has both fetch and beforeMount (the component beforeMount replaces this files beforeMount with $fetch call)
|
|
13
|
+
vm.$options[hook] = [vm.$options[hook]];
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
if (Array.isArray(vm.$options[hook]) && !vm.$options[hook].includes(fn)) {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import AuditPolicy from '@shell/models/auditlog.cattle.io.auditpolicy';
|
|
2
|
+
|
|
3
|
+
describe('auditPolicy Model', () => {
|
|
4
|
+
let mockDispatch: jest.Mock;
|
|
5
|
+
let mockT: jest.Mock;
|
|
6
|
+
let auditPolicy: any;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockDispatch = jest.fn();
|
|
10
|
+
mockT = jest.fn();
|
|
11
|
+
|
|
12
|
+
const mockResource = {
|
|
13
|
+
id: 'test-policy',
|
|
14
|
+
spec: { enabled: false },
|
|
15
|
+
metadata: { name: 'test-policy' }
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
auditPolicy = new AuditPolicy(mockResource, {
|
|
19
|
+
dispatch: mockDispatch,
|
|
20
|
+
rootGetters: { 'i18n/t': mockT },
|
|
21
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) }
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('enable method', () => {
|
|
26
|
+
it('should call enableOrDisable with "enable"', () => {
|
|
27
|
+
const spy = jest.spyOn(auditPolicy, 'enableOrDisable').mockImplementation();
|
|
28
|
+
|
|
29
|
+
auditPolicy.enable();
|
|
30
|
+
|
|
31
|
+
expect(spy).toHaveBeenCalledWith('enable');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('disable method', () => {
|
|
36
|
+
it('should call enableOrDisable with "disable"', () => {
|
|
37
|
+
const spy = jest.spyOn(auditPolicy, 'enableOrDisable').mockImplementation();
|
|
38
|
+
|
|
39
|
+
auditPolicy.disable();
|
|
40
|
+
|
|
41
|
+
expect(spy).toHaveBeenCalledWith('disable');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('enableOrDisable method', () => {
|
|
46
|
+
let mockClone: any;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
mockClone = {
|
|
50
|
+
spec: { enabled: false },
|
|
51
|
+
save: jest.fn()
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
mockDispatch.mockImplementation((action: string) => {
|
|
55
|
+
if (action === 'rancher/clone') {
|
|
56
|
+
return Promise.resolve(mockClone);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should enable policy when flag is "enable"', async() => {
|
|
64
|
+
mockClone.save.mockResolvedValue({});
|
|
65
|
+
|
|
66
|
+
await auditPolicy.enableOrDisable('enable');
|
|
67
|
+
|
|
68
|
+
expect(mockClone.spec.enabled).toBe(true);
|
|
69
|
+
expect(mockClone.save).toHaveBeenCalledWith();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should disable policy when flag is "disable"', async() => {
|
|
73
|
+
mockClone.save.mockResolvedValue({});
|
|
74
|
+
|
|
75
|
+
await auditPolicy.enableOrDisable('disable');
|
|
76
|
+
|
|
77
|
+
expect(mockClone.spec.enabled).toBe(false);
|
|
78
|
+
expect(mockClone.save).toHaveBeenCalledWith();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should handle save errors and show growl notification', async() => {
|
|
82
|
+
const saveError = new Error('Save failed');
|
|
83
|
+
|
|
84
|
+
mockClone.save.mockRejectedValue(saveError);
|
|
85
|
+
mockT.mockReturnValue('Error when enabling - test-policy');
|
|
86
|
+
|
|
87
|
+
await auditPolicy.enableOrDisable('enable');
|
|
88
|
+
|
|
89
|
+
expect(mockDispatch).toHaveBeenCalledWith('growl/fromError', {
|
|
90
|
+
title: 'Error when enabling - test-policy',
|
|
91
|
+
err: saveError,
|
|
92
|
+
timeout: 5000
|
|
93
|
+
}, { root: true });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should call translation with correct parameters', async() => {
|
|
97
|
+
const saveError = new Error('Save failed');
|
|
98
|
+
|
|
99
|
+
mockClone.save.mockRejectedValue(saveError);
|
|
100
|
+
|
|
101
|
+
await auditPolicy.enableOrDisable('enable');
|
|
102
|
+
|
|
103
|
+
expect(mockT).toHaveBeenCalledWith('auditPolicy.error.enableOrDisable', {
|
|
104
|
+
flag: 'enable',
|
|
105
|
+
id: 'test-policy'
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should dispatch rancher/clone with correct parameters', async() => {
|
|
110
|
+
mockClone.save.mockResolvedValue({});
|
|
111
|
+
|
|
112
|
+
await auditPolicy.enableOrDisable('enable');
|
|
113
|
+
|
|
114
|
+
expect(mockDispatch).toHaveBeenCalledWith('rancher/clone', { resource: auditPolicy }, { root: true });
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|