@rancher/shell 3.0.7 → 3.0.8-rc.1
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/vendor/githubapp.svg +13 -0
- package/assets/styles/base/_typography.scss +1 -1
- package/assets/styles/themes/_modern.scss +5 -5
- package/assets/translations/en-us.yaml +91 -11
- package/assets/translations/zh-hans.yaml +0 -4
- package/components/Inactivity.vue +222 -106
- package/components/InstallHelmCharts.vue +2 -2
- package/components/ResourceDetail/index.vue +1 -1
- package/components/SortableTable/index.vue +17 -2
- package/components/fleet/FleetConfigMapSelector.vue +117 -0
- package/components/fleet/FleetSecretSelector.vue +127 -0
- package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
- package/components/form/FileImageSelector.vue +13 -4
- package/components/form/FileSelector.vue +11 -2
- package/components/form/ResourceLabeledSelect.vue +1 -0
- package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
- package/components/nav/Header.vue +1 -0
- package/config/product/auth.js +1 -0
- package/config/query-params.js +1 -0
- package/config/settings.ts +8 -1
- package/config/types.js +2 -0
- package/dialog/AddonConfigConfirmationDialog.vue +45 -1
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
- package/edit/auth/AuthProviderWarningBanners.vue +14 -1
- package/edit/auth/github-app-steps.vue +97 -0
- package/edit/auth/github-steps.vue +75 -0
- package/edit/auth/github.vue +94 -65
- package/edit/fleet.cattle.io.helmop.vue +51 -2
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
- package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
- package/list/projectsecret.vue +1 -1
- package/machine-config/azure.vue +1 -1
- package/mixins/chart.js +1 -1
- package/models/__tests__/chart.test.ts +17 -9
- package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
- package/models/catalog.cattle.io.app.js +1 -1
- package/models/chart.js +3 -1
- package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
- package/models/management.cattle.io.authconfig.js +1 -0
- package/package.json +2 -2
- package/pages/auth/login.vue +5 -2
- package/pages/auth/verify.vue +1 -1
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
- package/pages/c/_cluster/apps/charts/chart.vue +2 -2
- package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
- package/pages/c/_cluster/explorer/tools/index.vue +3 -3
- package/pages/c/_cluster/settings/performance.vue +12 -25
- package/pages/home.vue +313 -12
- package/plugins/axios.js +2 -1
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/resource-class.js +17 -2
- package/plugins/steve/steve-pagination-utils.ts +2 -2
- package/scripts/extension/publish +1 -1
- package/store/auth.js +8 -3
- package/store/aws.js +8 -6
- package/store/features.js +1 -0
- package/store/index.js +9 -3
- package/store/prefs.js +6 -0
- package/types/kube/kube-api.ts +2 -1
- package/types/rancher/index.d.ts +1 -0
- package/types/resources/settings.d.ts +29 -7
- package/types/shell/index.d.ts +59 -0
- package/utils/__tests__/cluster.test.ts +379 -1
- package/utils/cluster.js +157 -3
- package/utils/dynamic-content/__tests__/config.test.ts +187 -0
- package/utils/dynamic-content/__tests__/index.test.ts +390 -0
- package/utils/dynamic-content/__tests__/info.test.ts +263 -0
- package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
- package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
- package/utils/dynamic-content/__tests__/util.test.ts +235 -0
- package/utils/dynamic-content/config.ts +55 -0
- package/utils/dynamic-content/index.ts +273 -0
- package/utils/dynamic-content/info.ts +219 -0
- package/utils/dynamic-content/new-release.ts +126 -0
- package/utils/dynamic-content/support-notice.ts +169 -0
- package/utils/dynamic-content/types.d.ts +101 -0
- package/utils/dynamic-content/util.ts +122 -0
- package/utils/inactivity.ts +104 -0
- package/utils/pagination-utils.ts +19 -4
- package/utils/release-notes.ts +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, computed, defineProps, defineEmits } from 'vue';
|
|
3
|
+
import { _EDIT } from '@shell/config/query-params';
|
|
4
|
+
import { TYPES } from '@shell/models/secret';
|
|
5
|
+
import { SECRET } from '@shell/config/types';
|
|
6
|
+
import { PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
7
|
+
import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
|
|
8
|
+
|
|
9
|
+
interface Secret {
|
|
10
|
+
id?: string;
|
|
11
|
+
name: string;
|
|
12
|
+
namespace: string;
|
|
13
|
+
_type: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
value: {
|
|
18
|
+
type: Object,
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
namespace: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
inStore: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: 'management',
|
|
28
|
+
},
|
|
29
|
+
mode: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: _EDIT
|
|
32
|
+
},
|
|
33
|
+
label: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: '',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const emit = defineEmits(['update:value']);
|
|
40
|
+
|
|
41
|
+
const types = computed<string[]>(() => Object.values(TYPES));
|
|
42
|
+
|
|
43
|
+
const secrets = ref<Secret[]>([]);
|
|
44
|
+
|
|
45
|
+
const allSecretsSettings = {
|
|
46
|
+
updateResources: (secretsList: Secret[]) => {
|
|
47
|
+
const allSecretsInNamespace = secretsList.filter((secret) => types.value.includes(secret._type) && secret.namespace === props.namespace);
|
|
48
|
+
const mappedSecrets = mapSecrets(allSecretsInNamespace.sort((a, b) => a.name.localeCompare(b.name)));
|
|
49
|
+
|
|
50
|
+
secrets.value = allSecretsInNamespace;
|
|
51
|
+
|
|
52
|
+
return mappedSecrets;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const paginateSecretsSetting = {
|
|
57
|
+
requestSettings: paginatePageOptions,
|
|
58
|
+
updateResources: (secretsList: Secret[]) => {
|
|
59
|
+
const mappedSecrets = mapSecrets(secretsList);
|
|
60
|
+
|
|
61
|
+
secrets.value = secretsList;
|
|
62
|
+
|
|
63
|
+
return mappedSecrets;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function mapSecrets(secretsList: Secret[]) {
|
|
68
|
+
return secretsList.reduce<{ label: string; value: string }[]>((res, s) => {
|
|
69
|
+
if (s.id) {
|
|
70
|
+
res.push({ label: s.name, value: s.name });
|
|
71
|
+
} else {
|
|
72
|
+
res.push(s as any);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return res;
|
|
76
|
+
}, []);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function update(value: any) {
|
|
80
|
+
emit('update:value', value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function paginatePageOptions(opts: any) {
|
|
84
|
+
const { opts: { filter } } = opts;
|
|
85
|
+
|
|
86
|
+
const filters = !!filter ? [PaginationParamFilter.createSingleField({
|
|
87
|
+
field: 'metadata.name', value: filter, exact: false, equals: true
|
|
88
|
+
})] : [];
|
|
89
|
+
|
|
90
|
+
filters.push(
|
|
91
|
+
PaginationParamFilter.createSingleField({ field: 'metadata.namespace', value: props.namespace }),
|
|
92
|
+
PaginationParamFilter.createMultipleFields(types.value.map((t) => ({
|
|
93
|
+
field: 'metadata.fields.1',
|
|
94
|
+
equals: true,
|
|
95
|
+
exact: true,
|
|
96
|
+
value: t
|
|
97
|
+
})))
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
...opts,
|
|
102
|
+
filters,
|
|
103
|
+
groupByNamespace: false,
|
|
104
|
+
classify: true,
|
|
105
|
+
sort: [{ asc: true, field: 'metadata.name' }],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<template>
|
|
111
|
+
<ResourceLabeledSelect
|
|
112
|
+
:key="namespace"
|
|
113
|
+
:value="value"
|
|
114
|
+
:label="label || t('fleet.secrets.label')"
|
|
115
|
+
:mode="mode"
|
|
116
|
+
:resource-type="SECRET"
|
|
117
|
+
:loading="$fetchState.pending"
|
|
118
|
+
:in-store="inStore"
|
|
119
|
+
:paginated-resource-settings="paginateSecretsSetting"
|
|
120
|
+
:all-resources-settings="allSecretsSettings"
|
|
121
|
+
:multiple="true"
|
|
122
|
+
@update:value="update"
|
|
123
|
+
/>
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<style lang="scss" scoped>
|
|
127
|
+
</style>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { _EDIT } from '@shell/config/query-params';
|
|
3
|
+
import FleetConfigMapSelector from '@shell/components/fleet/FleetConfigMapSelector.vue';
|
|
4
|
+
import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
|
|
5
|
+
|
|
6
|
+
describe('fleetConfigMapSelector.vue', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
value: {},
|
|
9
|
+
namespace: 'fleet-default',
|
|
10
|
+
inStore: 'management',
|
|
11
|
+
mode: _EDIT,
|
|
12
|
+
label: 'Config Map',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const global = {
|
|
16
|
+
stubs: { ResourceLabeledSelect },
|
|
17
|
+
mocks: {
|
|
18
|
+
$fetchState: { pending: false },
|
|
19
|
+
$store: { getters: { 'management/all': () => [] } },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
it('should emit update:value when update is called', async() => {
|
|
24
|
+
const wrapper = shallowMount(FleetConfigMapSelector, {
|
|
25
|
+
props: defaultProps,
|
|
26
|
+
global,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const vm = wrapper.vm as any;
|
|
30
|
+
|
|
31
|
+
await vm.update('cm1');
|
|
32
|
+
|
|
33
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
34
|
+
expect(wrapper.emitted('update:value')?.[0]).toStrictEqual(['cm1']);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should correctly map configMaps', () => {
|
|
38
|
+
const wrapper = shallowMount(FleetConfigMapSelector, {
|
|
39
|
+
props: defaultProps,
|
|
40
|
+
global
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const configMapsList = [
|
|
44
|
+
{
|
|
45
|
+
id: '1', name: 'cm1', namespace: 'fleet-default'
|
|
46
|
+
},
|
|
47
|
+
{ name: 'cm2', namespace: 'fleet-default' }
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const vm = wrapper.vm as any;
|
|
51
|
+
const result = vm.mapConfigMaps(configMapsList);
|
|
52
|
+
|
|
53
|
+
expect(result).toStrictEqual([
|
|
54
|
+
{ label: 'cm1', value: 'cm1' },
|
|
55
|
+
{ name: 'cm2', namespace: 'fleet-default' }
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return correct filter options from paginatePageOptions', () => {
|
|
60
|
+
const wrapper = shallowMount(FleetConfigMapSelector, {
|
|
61
|
+
props: defaultProps,
|
|
62
|
+
global
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const opts = { opts: { filter: 'test' } };
|
|
66
|
+
|
|
67
|
+
const vm = wrapper.vm as any;
|
|
68
|
+
const result = vm.paginatePageOptions(opts);
|
|
69
|
+
|
|
70
|
+
expect(result.filters).toHaveLength(2);
|
|
71
|
+
expect(result.groupByNamespace).toStrictEqual(false);
|
|
72
|
+
expect(result.classify).toStrictEqual(true);
|
|
73
|
+
expect(result.sort).toStrictEqual([{ asc: true, field: 'metadata.name' }]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should correctly filter and map configMaps in allConfigMapsSettings.updateResources', () => {
|
|
77
|
+
const wrapper = shallowMount(FleetConfigMapSelector, {
|
|
78
|
+
props: defaultProps,
|
|
79
|
+
global
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const configMapsList = [
|
|
83
|
+
{
|
|
84
|
+
id: '1', name: 'cm1', namespace: 'fleet-default'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: '2', name: 'cm2', namespace: 'other'
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const vm = wrapper.vm as any;
|
|
92
|
+
|
|
93
|
+
const result = vm.allConfigMapsSettings.updateResources(configMapsList);
|
|
94
|
+
|
|
95
|
+
expect(result).toStrictEqual([{ label: 'cm1', value: 'cm1' }]);
|
|
96
|
+
expect(vm.configMaps).toStrictEqual([{
|
|
97
|
+
id: '1', name: 'cm1', namespace: 'fleet-default'
|
|
98
|
+
}]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should correctly map configMaps in paginateConfigMapsSetting.updateResources', () => {
|
|
102
|
+
const wrapper = shallowMount(FleetConfigMapSelector, {
|
|
103
|
+
props: defaultProps,
|
|
104
|
+
global
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const configMapsList = [
|
|
108
|
+
{
|
|
109
|
+
id: '1', name: 'cm1', namespace: 'fleet-default'
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: '2', name: 'cm2', namespace: 'fleet-default'
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
const vm = wrapper.vm as any;
|
|
117
|
+
const result = vm.paginateConfigMapsSetting.updateResources(configMapsList);
|
|
118
|
+
|
|
119
|
+
expect(result).toStrictEqual([
|
|
120
|
+
{ label: 'cm1', value: 'cm1' },
|
|
121
|
+
{ label: 'cm2', value: 'cm2' }
|
|
122
|
+
]);
|
|
123
|
+
expect(vm.configMaps).toStrictEqual(configMapsList);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import FleetSecretSelector from '@shell/components/fleet/FleetSecretSelector.vue';
|
|
3
|
+
import { _EDIT } from '@shell/config/query-params';
|
|
4
|
+
|
|
5
|
+
describe('component: FleetSecretSelector.vue', () => {
|
|
6
|
+
const secretsMock = [
|
|
7
|
+
{
|
|
8
|
+
id: '1', name: 'secret1', namespace: 'fleet-default', _type: 'Opaque'
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: '2', name: 'secret2', namespace: 'fleet-default', _type: 'Opaque'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: '3', name: 'secret3', namespace: 'other', _type: 'Opaque'
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const props = {
|
|
19
|
+
value: {},
|
|
20
|
+
namespace: 'fleet-default',
|
|
21
|
+
inStore: 'management',
|
|
22
|
+
mode: _EDIT,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const global = { mocks: { $fetchState: { pending: false, error: false } } };
|
|
26
|
+
|
|
27
|
+
it('should emit update:value when update is called', async() => {
|
|
28
|
+
const wrapper = shallowMount(FleetSecretSelector, { props, global });
|
|
29
|
+
|
|
30
|
+
await (wrapper.vm as any).update('secret1');
|
|
31
|
+
|
|
32
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
33
|
+
expect(wrapper.emitted('update:value')?.[0]).toStrictEqual(['secret1']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should correctly map secrets', () => {
|
|
37
|
+
const wrapper = shallowMount(FleetSecretSelector, { props, global });
|
|
38
|
+
const mapped = (wrapper.vm as any).mapSecrets(secretsMock);
|
|
39
|
+
|
|
40
|
+
expect(mapped).toStrictEqual([
|
|
41
|
+
{ label: 'secret1', value: 'secret1' },
|
|
42
|
+
{ label: 'secret2', value: 'secret2' },
|
|
43
|
+
{ label: 'secret3', value: 'secret3' }
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should filter and sort secrets by namespace and type', () => {
|
|
48
|
+
const wrapper = shallowMount(FleetSecretSelector, { props, global });
|
|
49
|
+
|
|
50
|
+
const result = (wrapper.vm as any).allSecretsSettings.updateResources(secretsMock);
|
|
51
|
+
|
|
52
|
+
expect(result).toStrictEqual([
|
|
53
|
+
{ label: 'secret1', value: 'secret1' },
|
|
54
|
+
{ label: 'secret2', value: 'secret2' }
|
|
55
|
+
]);
|
|
56
|
+
expect((wrapper.vm as any).secrets).toStrictEqual([
|
|
57
|
+
{
|
|
58
|
+
id: '1', name: 'secret1', namespace: 'fleet-default', _type: 'Opaque'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: '2', name: 'secret2', namespace: 'fleet-default', _type: 'Opaque'
|
|
62
|
+
}
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should return correct filter structure from paginatePageOptions', () => {
|
|
67
|
+
const wrapper = shallowMount(FleetSecretSelector, { props, global });
|
|
68
|
+
|
|
69
|
+
const opts = { opts: { filter: 'secret' } };
|
|
70
|
+
const result = (wrapper.vm as any).paginatePageOptions(opts);
|
|
71
|
+
|
|
72
|
+
expect(result.filters).toStrictEqual(
|
|
73
|
+
expect.arrayContaining([
|
|
74
|
+
expect.objectContaining({ fields: expect.arrayContaining([expect.objectContaining({ field: 'metadata.name' })]) }),
|
|
75
|
+
expect.objectContaining({ fields: expect.arrayContaining([expect.objectContaining({ field: 'metadata.namespace' })]) }),
|
|
76
|
+
expect.objectContaining({ fields: expect.arrayContaining([expect.objectContaining({ field: 'metadata.fields.1' })]) }),
|
|
77
|
+
])
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(result.sort).toStrictEqual([{ asc: true, field: 'metadata.name' }]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -7,7 +7,8 @@ export default {
|
|
|
7
7
|
emits: ['update:value', 'error'],
|
|
8
8
|
|
|
9
9
|
components: { FileSelector, LazyImage },
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
props: {
|
|
11
12
|
value: {
|
|
12
13
|
type: String,
|
|
13
14
|
default: null,
|
|
@@ -36,12 +37,20 @@ export default {
|
|
|
36
37
|
accept: {
|
|
37
38
|
type: String,
|
|
38
39
|
default: 'image/*'
|
|
39
|
-
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
class: {
|
|
43
|
+
type: [String, Array],
|
|
44
|
+
default: 'role-primary',
|
|
45
|
+
},
|
|
40
46
|
},
|
|
41
47
|
computed: {
|
|
42
48
|
isView() {
|
|
43
49
|
return this.mode === _VIEW;
|
|
44
|
-
}
|
|
50
|
+
},
|
|
51
|
+
customClass() {
|
|
52
|
+
return [...(Array.isArray(this.class) ? this.class : [this.class])];
|
|
53
|
+
},
|
|
45
54
|
},
|
|
46
55
|
methods: {
|
|
47
56
|
/**
|
|
@@ -62,7 +71,7 @@ export default {
|
|
|
62
71
|
<FileSelector
|
|
63
72
|
v-if="!value && !isView"
|
|
64
73
|
:value="value"
|
|
65
|
-
class="
|
|
74
|
+
:class="customClass"
|
|
66
75
|
:mode="mode"
|
|
67
76
|
:read-as-data-url="true"
|
|
68
77
|
:byte-limit="byteLimit"
|
|
@@ -67,12 +67,21 @@ export default {
|
|
|
67
67
|
default: '*'
|
|
68
68
|
},
|
|
69
69
|
|
|
70
|
+
class: {
|
|
71
|
+
type: [String, Array],
|
|
72
|
+
default: () => [],
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
},
|
|
71
76
|
|
|
72
77
|
computed: {
|
|
73
78
|
isView() {
|
|
74
79
|
return this.mode === _VIEW;
|
|
75
|
-
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
customClass() {
|
|
83
|
+
return ['file-selector', 'btn', ...(Array.isArray(this.class) ? this.class : [this.class])];
|
|
84
|
+
},
|
|
76
85
|
},
|
|
77
86
|
|
|
78
87
|
methods: {
|
|
@@ -151,7 +160,7 @@ export default {
|
|
|
151
160
|
:aria-label="label"
|
|
152
161
|
type="button"
|
|
153
162
|
role="button"
|
|
154
|
-
class="
|
|
163
|
+
:class="customClass"
|
|
155
164
|
data-testid="file-selector__uploader-button"
|
|
156
165
|
@click="selectFile"
|
|
157
166
|
>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
|
|
3
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
4
|
+
import { RESOURCE_LABEL_SELECT_MODE } from '@shell/types/components/resourceLabeledSelect';
|
|
5
|
+
|
|
6
|
+
const mockStore = {
|
|
7
|
+
getters: {
|
|
8
|
+
currentStore: jest.fn().mockReturnValue('cluster'),
|
|
9
|
+
'cluster/paginationEnabled': jest.fn().mockReturnValue(true),
|
|
10
|
+
'cluster/all': jest.fn().mockReturnValue([{ id: 'foo', name: 'Foo' }]),
|
|
11
|
+
},
|
|
12
|
+
dispatch: jest.fn().mockResolvedValue(undefined),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const requiredSetup = () => {
|
|
16
|
+
return {
|
|
17
|
+
global: {
|
|
18
|
+
components: { LabeledSelect },
|
|
19
|
+
mocks: { $store: mockStore, $fetchState: {} }
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('component: ResourceLabeledSelect.vue', () => {
|
|
25
|
+
it('should render LabeledSelect', async() => {
|
|
26
|
+
const wrapper = shallowMount(ResourceLabeledSelect, {
|
|
27
|
+
...requiredSetup(),
|
|
28
|
+
props: { resourceType: 'testResource' }
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(wrapper.findComponent(LabeledSelect).exists()).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should call paginateType with overrideRequest if provided', async() => {
|
|
35
|
+
const overrideRequest = jest.fn().mockResolvedValue({ page: [{ id: 'bar', name: 'Bar' }], total: 1 });
|
|
36
|
+
const wrapper = shallowMount(ResourceLabeledSelect, {
|
|
37
|
+
...requiredSetup(),
|
|
38
|
+
props: {
|
|
39
|
+
resourceType: 'testResource',
|
|
40
|
+
paginateMode: RESOURCE_LABEL_SELECT_MODE.DYNAMIC,
|
|
41
|
+
paginatedResourceSettings: { overrideRequest }
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const result = await wrapper.vm.paginateType({
|
|
46
|
+
filter: 'bar',
|
|
47
|
+
page: 1,
|
|
48
|
+
pageSize: 10,
|
|
49
|
+
pageContent: [],
|
|
50
|
+
resetPage: false
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(overrideRequest).toHaveBeenCalledWith({
|
|
54
|
+
filter: 'bar',
|
|
55
|
+
page: 1,
|
|
56
|
+
pageSize: 10,
|
|
57
|
+
pageContent: [],
|
|
58
|
+
resetPage: false
|
|
59
|
+
});
|
|
60
|
+
expect(result.page[0].name).toBe('Bar');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should emit update:value when LabeledSelect emits update:value', async() => {
|
|
64
|
+
const wrapper = shallowMount(ResourceLabeledSelect, {
|
|
65
|
+
...requiredSetup(),
|
|
66
|
+
props: { resourceType: 'testResource' }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'baz');
|
|
70
|
+
|
|
71
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
72
|
+
expect(wrapper.emitted('update:value')?.[0]).toStrictEqual(['baz']);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should pass correct props and attrs to LabeledSelect', async() => {
|
|
76
|
+
const wrapper = shallowMount(ResourceLabeledSelect, {
|
|
77
|
+
...requiredSetup(),
|
|
78
|
+
props: {
|
|
79
|
+
resourceType: 'testResource',
|
|
80
|
+
allResourcesSettings: { labelSelectOptions: { placeholder: 'Select a resource' } }
|
|
81
|
+
},
|
|
82
|
+
attrs: { multiple: true }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const labeledSelect = wrapper.findComponent(LabeledSelect);
|
|
86
|
+
|
|
87
|
+
expect(labeledSelect.attributes('placeholder')).toBe('Select a resource');
|
|
88
|
+
expect(labeledSelect.attributes('multiple')).toBe('true');
|
|
89
|
+
});
|
|
90
|
+
});
|
package/config/product/auth.js
CHANGED
|
@@ -177,6 +177,7 @@ export function init(store) {
|
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/github`, 'auth/github');
|
|
180
|
+
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/githubapp`, 'auth/github');
|
|
180
181
|
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/openldap`, 'auth/ldap/index');
|
|
181
182
|
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/freeipa`, 'auth/ldap/index');
|
|
182
183
|
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/activedirectory`, 'auth/ldap/index');
|
package/config/query-params.js
CHANGED
|
@@ -8,6 +8,7 @@ export const STEP = 'step';
|
|
|
8
8
|
export const LOGGED_OUT = 'logged-out';
|
|
9
9
|
export const IS_SSO = 'is-sso';
|
|
10
10
|
export const IS_SLO = 'is-slo';
|
|
11
|
+
export const IS_SESSION_IDLE = 'is-session-idle';
|
|
11
12
|
export const UPGRADED = 'upgraded';
|
|
12
13
|
export const TIMED_OUT = 'timed-out';
|
|
13
14
|
export const AUTH_TEST = 'test';
|
package/config/settings.ts
CHANGED
|
@@ -52,6 +52,7 @@ export const SETTING = {
|
|
|
52
52
|
RKE_METADATA_CONFIG: 'rke-metadata-config',
|
|
53
53
|
EULA_AGREED: 'eula-agreed',
|
|
54
54
|
AUTH_USER_INFO_MAX_AGE_SECONDS: 'auth-user-info-max-age-seconds',
|
|
55
|
+
AUTH_USER_SESSION_IDLE_TTL_MINUTES: 'auth-user-session-idle-ttl-minutes',
|
|
55
56
|
AUTH_USER_SESSION_TTL_MINUTES: 'auth-user-session-ttl-minutes',
|
|
56
57
|
AUTH_USER_INFO_RESYNC_CRON: 'auth-user-info-resync-cron',
|
|
57
58
|
AUTH_LOCAL_VALIDATE_DESC: 'auth-password-requirements-description',
|
|
@@ -109,7 +110,12 @@ export const SETTING = {
|
|
|
109
110
|
SYSTEM_AGENT_UPGRADER_INSTALL_CONCURRENCY: 'system-agent-upgrader-install-concurrency',
|
|
110
111
|
IMPORTED_CLUSTER_VERSION_MANAGEMENT: 'imported-cluster-version-management',
|
|
111
112
|
CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS: 'cluster-agent-default-priority-class',
|
|
112
|
-
CLUSTER_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET: 'cluster-agent-default-pod-disruption-budget'
|
|
113
|
+
CLUSTER_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET: 'cluster-agent-default-pod-disruption-budget',
|
|
114
|
+
/**
|
|
115
|
+
* Dynamic Content settings
|
|
116
|
+
*/
|
|
117
|
+
DYNAMIC_CONTENT_ENABLED: 'ui-content-enabled',
|
|
118
|
+
DYNAMIC_CONTENT_ENDPOINT: 'ui-content-endpoint',
|
|
113
119
|
} as const;
|
|
114
120
|
|
|
115
121
|
// These are the settings that are allowed to be edited via the UI
|
|
@@ -140,6 +146,7 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
|
|
|
140
146
|
},
|
|
141
147
|
[SETTING.INGRESS_IP_DOMAIN]: {},
|
|
142
148
|
[SETTING.AUTH_USER_INFO_MAX_AGE_SECONDS]: {},
|
|
149
|
+
[SETTING.AUTH_USER_SESSION_IDLE_TTL_MINUTES]: {},
|
|
143
150
|
[SETTING.AUTH_USER_SESSION_TTL_MINUTES]: {},
|
|
144
151
|
[SETTING.AUTH_TOKEN_MAX_TTL_MINUTES]: {},
|
|
145
152
|
[SETTING.KUBECONFIG_GENERATE_TOKEN]: { kind: 'boolean' },
|
package/config/types.js
CHANGED
|
@@ -222,6 +222,8 @@ export const MANAGEMENT = {
|
|
|
222
222
|
OIDC_CLIENT: 'management.cattle.io.oidcclient'
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
+
export const EXT = { USER_ACTIVITY: 'ext.cattle.io.useractivity' };
|
|
226
|
+
|
|
225
227
|
export const CAPI = {
|
|
226
228
|
CAPI_CLUSTER: 'cluster.x-k8s.io.cluster',
|
|
227
229
|
MACHINE_DEPLOYMENT: 'cluster.x-k8s.io.machinedeployment',
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import AsyncButton from '@shell/components/AsyncButton';
|
|
3
3
|
import { Card } from '@components/Card';
|
|
4
|
+
import { mapGetters } from 'vuex';
|
|
5
|
+
|
|
6
|
+
import { labelForAddon } from '@shell/utils/cluster';
|
|
7
|
+
import { resourceNames } from '@shell/utils/string';
|
|
4
8
|
|
|
5
9
|
export default {
|
|
6
10
|
emits: ['close'],
|
|
@@ -17,11 +21,45 @@ export default {
|
|
|
17
21
|
registerBackgroundClosing: {
|
|
18
22
|
type: Function,
|
|
19
23
|
required: true
|
|
24
|
+
},
|
|
25
|
+
/**
|
|
26
|
+
* The names of the addons that have configuration conflicts.
|
|
27
|
+
*/
|
|
28
|
+
addonNames: {
|
|
29
|
+
type: Array,
|
|
30
|
+
default: () => []
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* The Kubernetes version the user is upgrading from.
|
|
34
|
+
*/
|
|
35
|
+
previousKubeVersion: {
|
|
36
|
+
type: String,
|
|
37
|
+
default: ''
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* The Kubernetes version the user is upgrading to.
|
|
41
|
+
*/
|
|
42
|
+
newKubeVersion: {
|
|
43
|
+
type: String,
|
|
44
|
+
default: ''
|
|
20
45
|
}
|
|
21
46
|
},
|
|
22
47
|
created() {
|
|
23
48
|
this.registerBackgroundClosing(this.closing);
|
|
24
49
|
},
|
|
50
|
+
computed: {
|
|
51
|
+
...mapGetters({ t: 'i18n/t' }),
|
|
52
|
+
|
|
53
|
+
formattedAddons() {
|
|
54
|
+
if (!this.addonNames || this.addonNames.length === 0) {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const translatedNames = this.addonNames.map((name) => labelForAddon(this.$store, name, true));
|
|
59
|
+
|
|
60
|
+
return resourceNames(translatedNames, null, this.t, false);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
25
63
|
methods: {
|
|
26
64
|
continue(value) {
|
|
27
65
|
if (this.resources[0]) {
|
|
@@ -59,7 +97,13 @@ export default {
|
|
|
59
97
|
|
|
60
98
|
<template #body>
|
|
61
99
|
<slot name="body">
|
|
62
|
-
|
|
100
|
+
<span
|
|
101
|
+
v-clean-html="t('addonConfigConfirmation.body', {
|
|
102
|
+
addons: formattedAddons,
|
|
103
|
+
previousKubeVersion,
|
|
104
|
+
newKubeVersion
|
|
105
|
+
}, true)"
|
|
106
|
+
/>
|
|
63
107
|
</slot>
|
|
64
108
|
</template>
|
|
65
109
|
|