@rancher/shell 3.0.6 → 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/pl/dark/rancher-logo.svg +131 -44
- package/assets/images/pl/rancher-logo.svg +120 -44
- package/assets/images/vendor/githubapp.svg +13 -0
- package/assets/styles/base/_basic.scss +2 -2
- package/assets/styles/base/_color-classic.scss +51 -0
- package/assets/styles/base/_color.scss +3 -3
- package/assets/styles/base/_mixins.scss +1 -1
- package/assets/styles/base/_typography.scss +1 -1
- package/assets/styles/base/_variables-classic.scss +47 -0
- package/assets/styles/global/_button.scss +49 -17
- package/assets/styles/global/_form.scss +1 -1
- package/assets/styles/themes/_dark.scss +4 -0
- package/assets/styles/themes/_light.scss +3 -69
- package/assets/styles/themes/_modern.scss +194 -50
- package/assets/styles/vendor/vue-select.scss +1 -2
- package/assets/translations/en-us.yaml +124 -32
- package/assets/translations/zh-hans.yaml +0 -4
- package/components/ClusterIconMenu.vue +1 -1
- package/components/ClusterProviderIcon.vue +1 -1
- package/components/CodeMirror.vue +1 -1
- package/components/IconOrSvg.vue +40 -29
- package/components/Inactivity.vue +222 -106
- package/components/InstallHelmCharts.vue +2 -2
- package/components/ResourceDetail/index.vue +2 -1
- package/components/SortableTable/index.vue +17 -2
- package/components/SortableTable/sorting.js +3 -1
- package/components/Tabbed/index.vue +5 -5
- 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/ResourceTabs/index.vue +37 -18
- package/components/form/SecretSelector.vue +6 -2
- package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
- package/components/nav/Group.vue +29 -9
- package/components/nav/Header.vue +7 -8
- package/components/nav/NamespaceFilter.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +47 -20
- package/components/nav/TopLevelMenu.vue +44 -14
- package/components/nav/Type.vue +0 -5
- package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
- package/config/pagination-table-headers.js +10 -2
- package/config/product/auth.js +1 -0
- package/config/product/explorer.js +4 -3
- package/config/query-params.js +1 -0
- package/config/settings.ts +8 -1
- package/config/table-headers.js +9 -0
- package/config/types.js +2 -0
- package/core/plugin.ts +18 -6
- package/core/types.ts +8 -0
- package/detail/provisioning.cattle.io.cluster.vue +1 -0
- package/dialog/AddonConfigConfirmationDialog.vue +45 -1
- package/dialog/InstallExtensionDialog.vue +71 -45
- package/dialog/UninstallExtensionDialog.vue +2 -1
- package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
- 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/auth/oidc.vue +86 -16
- 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/__tests__/chart.test.ts +1 -1
- package/mixins/chart.js +2 -2
- 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/event.js +7 -0
- package/models/management.cattle.io.authconfig.js +1 -0
- package/models/provisioning.cattle.io.cluster.js +9 -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 +92 -9
- package/pages/c/_cluster/explorer/tools/index.vue +3 -3
- package/pages/c/_cluster/settings/performance.vue +13 -26
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
- package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
- package/pages/c/_cluster/uiplugins/index.vue +110 -94
- package/pages/home.vue +313 -12
- package/plugins/__tests__/subscribe.events.test.ts +194 -0
- package/plugins/axios.js +2 -1
- package/plugins/dashboard-store/actions.js +4 -1
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/dashboard-store/resource-class.js +20 -5
- package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
- package/plugins/steve/index.js +18 -10
- package/plugins/steve/mutations.js +2 -2
- package/plugins/steve/resourceWatcher.js +2 -2
- package/plugins/steve/steve-pagination-utils.ts +12 -9
- package/plugins/steve/subscribe.js +113 -85
- package/plugins/subscribe-events.ts +211 -0
- package/rancher-components/BadgeState/BadgeState.vue +8 -6
- package/rancher-components/Banner/Banner.vue +2 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
- package/rancher-components/Form/Radio/RadioButton.vue +3 -3
- 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 +21 -25
- package/store/prefs.js +6 -0
- package/types/extension-manager.ts +8 -1
- package/types/kube/kube-api.ts +2 -1
- package/types/rancher/index.d.ts +1 -0
- package/types/resources/settings.d.ts +52 -23
- package/types/shell/index.d.ts +412 -336
- package/types/store/subscribe-events.types.ts +70 -0
- package/types/store/subscribe.types.ts +6 -22
- 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 +105 -31
- package/utils/pagination-wrapper.ts +6 -8
- package/utils/release-notes.ts +1 -1
- package/utils/sort.js +5 -0
- package/utils/unit-tests/pagination-utils.spec.ts +283 -0
- package/utils/validators/formRules/__tests__/index.test.ts +7 -0
- package/utils/validators/formRules/index.ts +2 -2
|
@@ -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
|
>
|
|
@@ -7,14 +7,14 @@ import Tabbed from '@shell/components/Tabbed';
|
|
|
7
7
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
8
8
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
9
9
|
import Conditions from '@shell/components/form/Conditions';
|
|
10
|
-
import { EVENT } from '@shell/config/types';
|
|
10
|
+
import { EVENT, NAMESPACE } from '@shell/config/types';
|
|
11
11
|
import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
|
|
12
12
|
import { _VIEW } from '@shell/config/query-params';
|
|
13
13
|
import RelatedResources from '@shell/components/RelatedResources';
|
|
14
14
|
import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
|
|
15
15
|
import { PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
16
16
|
import { MESSAGE, REASON } from '@shell/config/table-headers';
|
|
17
|
-
import { STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
|
|
17
|
+
import { STEVE_EVENT_FIRST_SEEN, STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
|
|
18
18
|
import { headerFromSchemaColString } from '@shell/store/type-map.utils';
|
|
19
19
|
import { useIndicateUseCounts } from '@shell/components/form/ResourceTabs/composable';
|
|
20
20
|
|
|
@@ -93,7 +93,7 @@ export default {
|
|
|
93
93
|
headerFromSchemaColString('Subobject', eventSchema, this.$store.getters, true),
|
|
94
94
|
headerFromSchemaColString('Source', eventSchema, this.$store.getters, true),
|
|
95
95
|
MESSAGE,
|
|
96
|
-
|
|
96
|
+
STEVE_EVENT_FIRST_SEEN,
|
|
97
97
|
headerFromSchemaColString('Count', eventSchema, this.$store.getters, true),
|
|
98
98
|
STEVE_NAME_COL,
|
|
99
99
|
] : [];
|
|
@@ -119,6 +119,9 @@ export default {
|
|
|
119
119
|
},
|
|
120
120
|
|
|
121
121
|
computed: {
|
|
122
|
+
isNamespace() {
|
|
123
|
+
return this.value?.type === NAMESPACE;
|
|
124
|
+
},
|
|
122
125
|
showEvents() {
|
|
123
126
|
return this.isView && this.needEvents && this.eventSchema;
|
|
124
127
|
},
|
|
@@ -190,7 +193,9 @@ export default {
|
|
|
190
193
|
* Filter out hidden repos from list of all repos
|
|
191
194
|
*/
|
|
192
195
|
filterEventsLocal(rows) {
|
|
193
|
-
return rows.filter((event) =>
|
|
196
|
+
return rows.filter((event) => {
|
|
197
|
+
return this.isNamespace ? event.metadata?.namespace === this.value?.metadata?.name : event.involvedObject?.uid === this.value?.metadata?.uid;
|
|
198
|
+
});
|
|
194
199
|
},
|
|
195
200
|
|
|
196
201
|
/**
|
|
@@ -204,27 +209,22 @@ export default {
|
|
|
204
209
|
pagination.filters = [];
|
|
205
210
|
}
|
|
206
211
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
let existing = null;
|
|
211
|
-
|
|
212
|
-
for (let i = 0; i < pagination.filters.length; i++) {
|
|
213
|
-
const filter = pagination.filters[i];
|
|
212
|
+
// Determine the field and value based on type
|
|
213
|
+
const field = this.isNamespace ? 'metadata.namespace' : 'involvedObject.uid';
|
|
214
|
+
const value = this.isNamespace ? this.value.metadata.name : this.value.metadata.uid;
|
|
214
215
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
216
|
+
// Check if a filter for this field already exists
|
|
217
|
+
const existing = pagination.filters.find((f) => f.fields.some((ff) => ff.field === field));
|
|
220
218
|
|
|
219
|
+
// Create the required filter
|
|
221
220
|
const required = PaginationParamFilter.createSingleField({
|
|
222
221
|
field,
|
|
223
222
|
exact: true,
|
|
224
|
-
value
|
|
223
|
+
value,
|
|
225
224
|
equals: true
|
|
226
225
|
});
|
|
227
226
|
|
|
227
|
+
// Merge or add the filter
|
|
228
228
|
if (!!existing) {
|
|
229
229
|
Object.assign(existing, required);
|
|
230
230
|
} else {
|
|
@@ -260,10 +260,16 @@ export default {
|
|
|
260
260
|
|
|
261
261
|
<Tab
|
|
262
262
|
v-if="showEvents"
|
|
263
|
-
label
|
|
263
|
+
:label="isNamespace ? t('resourceTabs.events.namespaceTab') : t('resourceTabs.events.tab')"
|
|
264
264
|
name="events"
|
|
265
265
|
:weight="-2"
|
|
266
266
|
>
|
|
267
|
+
<!-- Caption for namespace pages -->
|
|
268
|
+
<div
|
|
269
|
+
v-if="isNamespace"
|
|
270
|
+
v-clean-html="t('resourceTabs.events.namespaceCaption', { namespace: value.metadata.name }, true)"
|
|
271
|
+
class="tab-caption"
|
|
272
|
+
/>
|
|
267
273
|
<!-- namespaced: false given we don't want the default handling of namespaced resource (apply header filter) -->
|
|
268
274
|
<PaginatedResourceTable
|
|
269
275
|
:schema="eventSchema"
|
|
@@ -318,4 +324,17 @@ export default {
|
|
|
318
324
|
}
|
|
319
325
|
}
|
|
320
326
|
}
|
|
327
|
+
/* Caption for namespace events tab */
|
|
328
|
+
.tab-caption {
|
|
329
|
+
align-items: center;
|
|
330
|
+
font-size: 16px;
|
|
331
|
+
margin-bottom: 24px;
|
|
332
|
+
|
|
333
|
+
.namespace-name {
|
|
334
|
+
display: inline;
|
|
335
|
+
font-weight: bold;
|
|
336
|
+
margin-right: 0 4px;
|
|
337
|
+
white-space: nowrap;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
321
340
|
</style>
|
|
@@ -61,7 +61,7 @@ export default {
|
|
|
61
61
|
},
|
|
62
62
|
inStore: {
|
|
63
63
|
type: String,
|
|
64
|
-
default:
|
|
64
|
+
default: undefined,
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
|
|
@@ -129,6 +129,10 @@ export default {
|
|
|
129
129
|
}));
|
|
130
130
|
},
|
|
131
131
|
|
|
132
|
+
validInStore() {
|
|
133
|
+
return this.inStore || this.$store.getters['currentStore']() || 'cluster';
|
|
134
|
+
},
|
|
135
|
+
|
|
132
136
|
isView() {
|
|
133
137
|
return this.mode === _VIEW;
|
|
134
138
|
},
|
|
@@ -221,7 +225,7 @@ export default {
|
|
|
221
225
|
:label="secretNameLabel"
|
|
222
226
|
:mode="mode"
|
|
223
227
|
:resource-type="SECRET"
|
|
224
|
-
:in-store="
|
|
228
|
+
:in-store="validInStore"
|
|
225
229
|
:paginated-resource-settings="paginateSecretsSetting"
|
|
226
230
|
:all-resources-settings="allSecretsSettings"
|
|
227
231
|
/>
|
|
@@ -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
|
+
});
|