@rancher/shell 3.0.9-rc.5 → 3.0.9
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/oci-open-containers.svg +22 -0
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/themes/_dark.scss +2 -0
- package/assets/styles/themes/_light.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -0
- package/assets/translations/en-us.yaml +129 -25
- package/components/CruResource.vue +3 -1
- package/components/ExplorerProjectsNamespaces.vue +12 -12
- package/components/IconOrSvg.vue +61 -42
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
- package/components/Resource/Detail/ResourceRow.vue +2 -2
- package/components/ResourceList/index.vue +7 -4
- package/components/SortableTable/index.vue +2 -2
- package/components/Window/ContainerLogs.vue +48 -37
- package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
- package/components/fleet/FleetClusterTargets/index.vue +6 -1
- package/components/fleet/GitRepoAdvancedTab.vue +333 -0
- package/components/fleet/GitRepoMetadataTab.vue +43 -0
- package/components/fleet/GitRepoRepositoryTab.vue +101 -0
- package/components/fleet/GitRepoTargetTab.vue +77 -0
- package/components/fleet/HelmOpAdvancedTab.vue +247 -0
- package/components/fleet/HelmOpChartTab.vue +158 -0
- package/components/fleet/HelmOpMetadataTab.vue +46 -0
- package/components/fleet/HelmOpTargetTab.vue +84 -0
- package/components/fleet/HelmOpValuesTab.vue +147 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
- package/components/form/BannerSettings.vue +2 -2
- package/components/form/NodeScheduling.vue +81 -7
- package/components/form/NotificationSettings.vue +2 -2
- package/components/form/PodAffinity.vue +1 -36
- package/components/form/ResourceLabeledSelect.vue +8 -4
- package/components/form/ResourceQuota/Namespace.vue +30 -9
- package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
- package/components/form/ResourceQuota/Project.vue +140 -82
- package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
- package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
- package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
- package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
- package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
- package/components/form/SchedulingCustomization.vue +14 -6
- package/components/form/SelectOrCreateAuthSecret.vue +107 -18
- package/components/form/__tests__/NodeScheduling.test.ts +12 -9
- package/components/form/__tests__/PodAffinity.test.ts +21 -2
- package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
- package/components/formatter/ClusterLink.vue +8 -0
- package/components/formatter/SecretOrigin.vue +79 -0
- package/config/labels-annotations.js +7 -6
- package/config/pagination-table-headers.js +6 -4
- package/config/product/explorer.js +1 -11
- package/config/product/manager.js +0 -1
- package/config/query-params.js +3 -0
- package/config/settings.ts +15 -2
- package/config/table-headers.js +21 -17
- package/config/types.js +23 -8
- package/detail/fleet.cattle.io.cluster.vue +1 -1
- package/detail/workload/index.vue +11 -16
- package/dialog/DeactivateDriverDialog.vue +1 -1
- package/dialog/FeatureFlagListDialog.vue +1 -1
- package/dialog/Ipv6NetworkingDialog.vue +156 -0
- package/dialog/ScalePoolDownDialog.vue +2 -2
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -128
- package/edit/auth/oidc.vue +1 -1
- package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
- package/edit/fleet.cattle.io.gitrepo.vue +153 -283
- package/edit/fleet.cattle.io.helmop.vue +190 -332
- package/edit/management.cattle.io.project.vue +5 -42
- package/edit/management.cattle.io.setting.vue +6 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
- package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
- package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
- package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +114 -0
- package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +167 -69
- package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +70 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +343 -0
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
- package/edit/secret/index.vue +1 -1
- package/edit/token.vue +68 -29
- package/edit/workload/__tests__/index.test.ts +2 -37
- package/edit/workload/index.vue +6 -2
- package/edit/workload/mixins/workload.js +0 -32
- package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
- package/list/management.cattle.io.setting.vue +13 -0
- package/list/provisioning.cattle.io.cluster.vue +50 -1
- package/list/secret.vue +4 -9
- package/list/service.vue +6 -8
- package/machine-config/amazonec2.vue +11 -4
- package/machine-config/components/EC2Networking.vue +46 -30
- package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
- package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
- package/machine-config/digitalocean.vue +3 -3
- package/models/__tests__/chart.test.ts +2 -2
- package/models/__tests__/namespace.test.ts +11 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
- package/models/__tests__/workload.test.ts +42 -1
- package/models/catalog.cattle.io.clusterrepo.js +30 -4
- package/models/chart.js +3 -3
- package/models/ext.cattle.io.token.js +48 -0
- package/models/kontainerdriver.js +2 -2
- package/models/namespace.js +7 -1
- package/models/nodedriver.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +28 -7
- package/models/secret.js +0 -17
- package/models/service.js +44 -1
- package/models/token.js +4 -0
- package/models/workload.js +12 -6
- package/package.json +1 -1
- package/pages/account/index.vue +96 -67
- package/pages/auth/setup.vue +5 -14
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +45 -18
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
- package/pages/c/_cluster/apps/charts/index.vue +82 -3
- package/pages/c/_cluster/apps/charts/install.vue +317 -42
- package/pages/c/_cluster/explorer/tools/index.vue +1 -1
- package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
- package/pages/c/_cluster/settings/index.vue +3 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
- package/plugins/dashboard-store/actions.js +3 -8
- package/plugins/dashboard-store/getters.js +7 -5
- package/plugins/dashboard-store/mutations.js +4 -1
- package/plugins/dashboard-store/resource-class.js +3 -3
- package/plugins/steve/__tests__/steve-class.test.ts +102 -141
- package/plugins/steve/steve-class.js +12 -3
- package/plugins/steve/steve-pagination-utils.ts +6 -2
- package/rancher-components/RcIcon/types.ts +2 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +72 -20
- package/store/prefs.js +3 -0
- package/types/aws-sdk.d.ts +121 -0
- package/types/resources/node.ts +15 -0
- package/types/shell/index.d.ts +537 -506
- package/types/store/pagination.types.ts +5 -5
- package/utils/__tests__/array.test.ts +1 -29
- package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
- package/utils/array.ts +0 -11
- package/utils/aws.ts +21 -0
- package/utils/cluster.js +22 -2
- package/utils/selector-typed.ts +1 -1
- package/utils/svg-filter.js +4 -3
- package/components/__tests__/ProjectRow.test.ts +0 -206
- package/components/form/ResourceQuota/ProjectRow.vue +0 -277
|
@@ -162,6 +162,12 @@ export default {
|
|
|
162
162
|
class="edit-help"
|
|
163
163
|
/>
|
|
164
164
|
|
|
165
|
+
<Banner
|
|
166
|
+
v-if="setting.agent"
|
|
167
|
+
color="info"
|
|
168
|
+
:label="t('advancedSettings.edit.agentConfigBanner.text', { agent: t('advancedSettings.edit.agentConfigBanner.' + setting.agent) })"
|
|
169
|
+
data-testid="setting-agent-config-banner"
|
|
170
|
+
/>
|
|
165
171
|
<div class="edit-change mt-20">
|
|
166
172
|
<h5 v-t="'advancedSettings.edit.changeSetting'" />
|
|
167
173
|
<button
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import Auth from '@shell/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue';
|
|
3
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
4
|
+
import SimpleSecretSelector from '@shell/components/form/SimpleSecretSelector';
|
|
5
|
+
|
|
6
|
+
describe('component: Auth.vue', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
mode: 'edit',
|
|
9
|
+
value: {},
|
|
10
|
+
namespace: 'test-namespace'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it('should render correctly with initial props', () => {
|
|
14
|
+
const wrapper = shallowMount(Auth, {
|
|
15
|
+
props: defaultProps,
|
|
16
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(wrapper.find('h3').text()).toBe('%monitoringReceiver.auth.label%');
|
|
20
|
+
expect(wrapper.findComponent(LabeledSelect).exists()).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should initialize with basic auth type if present', () => {
|
|
24
|
+
const wrapper = shallowMount(Auth, {
|
|
25
|
+
props: { ...defaultProps, value: { basicAuth: { username: { name: 'test' } } } },
|
|
26
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(wrapper.vm.authType).toBe('basicAuth');
|
|
30
|
+
expect(wrapper.findAllComponents(SimpleSecretSelector)).toHaveLength(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should initialize with bearer token auth type if present', () => {
|
|
34
|
+
const wrapper = shallowMount(Auth, {
|
|
35
|
+
props: { ...defaultProps, value: { bearerTokenSecret: { name: 'test' } } },
|
|
36
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(wrapper.vm.authType).toBe('bearerTokenSecret');
|
|
40
|
+
expect(wrapper.findAllComponents(SimpleSecretSelector)).toHaveLength(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should switch to basic auth and clear other types', async() => {
|
|
44
|
+
const value = { bearerTokenSecret: { name: 'b', key: 'k' } };
|
|
45
|
+
const wrapper = shallowMount(Auth, {
|
|
46
|
+
props: { ...defaultProps, value },
|
|
47
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'basicAuth');
|
|
51
|
+
expect(wrapper.vm.authType).toBe('basicAuth');
|
|
52
|
+
expect(wrapper.props('value').bearerTokenSecret).toBeUndefined();
|
|
53
|
+
expect(wrapper.props('value').basicAuth).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should switch to bearer token and clear other types', async() => {
|
|
57
|
+
const value = { basicAuth: { username: { name: 'u' } } };
|
|
58
|
+
const wrapper = shallowMount(Auth, {
|
|
59
|
+
props: { ...defaultProps, value },
|
|
60
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'bearerTokenSecret');
|
|
64
|
+
expect(wrapper.vm.authType).toBe('bearerTokenSecret');
|
|
65
|
+
expect(wrapper.props('value').basicAuth).toBeUndefined();
|
|
66
|
+
expect(wrapper.props('value').bearerTokenSecret).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should switch to none and clear other types', async() => {
|
|
70
|
+
const value = { basicAuth: { username: { name: 'u' } } };
|
|
71
|
+
const wrapper = shallowMount(Auth, {
|
|
72
|
+
props: { ...defaultProps, value },
|
|
73
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'none');
|
|
77
|
+
expect(wrapper.vm.authType).toBe('none');
|
|
78
|
+
expect(wrapper.props('value').basicAuth).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should update basic auth username and password', async() => {
|
|
82
|
+
const wrapper = shallowMount(Auth, {
|
|
83
|
+
props: { ...defaultProps, value: { basicAuth: { username: { name: 'test' } } } },
|
|
84
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await wrapper.vm.$nextTick();
|
|
88
|
+
|
|
89
|
+
const selectors = wrapper.findAllComponents(SimpleSecretSelector);
|
|
90
|
+
|
|
91
|
+
expect(selectors).toHaveLength(2);
|
|
92
|
+
|
|
93
|
+
const usernameSelector = selectors[0];
|
|
94
|
+
const passwordSelector = selectors[1];
|
|
95
|
+
|
|
96
|
+
await usernameSelector.vm.$emit('updateSecretName', 'user-secret');
|
|
97
|
+
await usernameSelector.vm.$emit('updateSecretKey', 'user-key');
|
|
98
|
+
await passwordSelector.vm.$emit('updateSecretName', 'pass-secret');
|
|
99
|
+
await passwordSelector.vm.$emit('updateSecretKey', 'pass-key');
|
|
100
|
+
|
|
101
|
+
const basicAuth = wrapper.props('value').basicAuth;
|
|
102
|
+
|
|
103
|
+
expect(basicAuth.username.name).toBe('user-secret');
|
|
104
|
+
expect(basicAuth.username.key).toBe('user-key');
|
|
105
|
+
expect(basicAuth.password.name).toBe('pass-secret');
|
|
106
|
+
expect(basicAuth.password.key).toBe('pass-key');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should update bearer token secret', async() => {
|
|
110
|
+
const wrapper = shallowMount(Auth, {
|
|
111
|
+
props: { ...defaultProps, value: { bearerTokenSecret: { name: 'test' } } },
|
|
112
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await wrapper.vm.$nextTick();
|
|
116
|
+
|
|
117
|
+
const selector = wrapper.findComponent(SimpleSecretSelector);
|
|
118
|
+
|
|
119
|
+
expect(selector.exists()).toBe(true);
|
|
120
|
+
|
|
121
|
+
await selector.vm.$emit('updateSecretName', 'bearer-name');
|
|
122
|
+
await selector.vm.$emit('updateSecretKey', 'bearer-key');
|
|
123
|
+
|
|
124
|
+
const bearerToken = wrapper.props('value').bearerTokenSecret;
|
|
125
|
+
|
|
126
|
+
expect(bearerToken.name).toBe('bearer-name');
|
|
127
|
+
expect(bearerToken.key).toBe('bearer-key');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should render in view mode', () => {
|
|
131
|
+
const wrapper = shallowMount(Auth, {
|
|
132
|
+
props: {
|
|
133
|
+
mode: 'view', value: { basicAuth: {} }, namespace: 'ns'
|
|
134
|
+
},
|
|
135
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(wrapper.findComponent(LabeledSelect).attributes('disabled')).toBe('true');
|
|
139
|
+
const selectors = wrapper.findAllComponents(SimpleSecretSelector);
|
|
140
|
+
|
|
141
|
+
selectors.forEach((selector) => {
|
|
142
|
+
expect(selector.props('disabled')).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/* eslint-disable import/first */
|
|
2
|
+
global.PointerEvent = class PointerEvent extends Event {};
|
|
3
|
+
|
|
4
|
+
import { shallowMount } from '@vue/test-utils';
|
|
5
|
+
import { EDITOR_MODES } from '@shell/components/YamlEditor.vue';
|
|
6
|
+
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
7
|
+
import Index from '../index.vue';
|
|
8
|
+
|
|
9
|
+
describe('monitoring.coreos.com.alertmanagerconfig/index.vue', () => {
|
|
10
|
+
it('should render correctly', () => {
|
|
11
|
+
const valueMock = {
|
|
12
|
+
applyDefaults: jest.fn(),
|
|
13
|
+
spec: {
|
|
14
|
+
receivers: [
|
|
15
|
+
{ name: 'receiver1', type: 'webhook' },
|
|
16
|
+
{ name: 'receiver2', type: 'email' },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
getCreateReceiverRoute: jest.fn(() => ({ name: 'create-receiver' })),
|
|
20
|
+
getReceiverDetailLink: jest.fn((name) => `detail-${ name }`),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const wrapper = shallowMount(Index, {
|
|
24
|
+
propsData: {
|
|
25
|
+
value: valueMock,
|
|
26
|
+
mode: 'create',
|
|
27
|
+
},
|
|
28
|
+
global: {
|
|
29
|
+
mocks: {
|
|
30
|
+
$store: {
|
|
31
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
32
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
33
|
+
},
|
|
34
|
+
$router: { push: jest.fn() },
|
|
35
|
+
$route: { name: 'test-route' },
|
|
36
|
+
$fetchState: { pending: false },
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
stubs: {
|
|
40
|
+
CruResource: true,
|
|
41
|
+
NameNsDescription: true,
|
|
42
|
+
Tabbed: true,
|
|
43
|
+
Tab: true,
|
|
44
|
+
RouteConfig: true,
|
|
45
|
+
ResourceTable: true,
|
|
46
|
+
ActionMenu: true,
|
|
47
|
+
Loading: true,
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Ensure the component is rendered
|
|
52
|
+
expect(wrapper.exists()).toBe(true);
|
|
53
|
+
|
|
54
|
+
// Assert that applyDefaults is called
|
|
55
|
+
expect(valueMock.applyDefaults).toHaveBeenCalledTimes(1);
|
|
56
|
+
|
|
57
|
+
// Assert receiverOptions are correctly populated from value.spec.receivers
|
|
58
|
+
expect(wrapper.vm.receiverOptions).toStrictEqual(['receiver1', 'receiver2']);
|
|
59
|
+
|
|
60
|
+
// Assert createReceiverLink is correctly set
|
|
61
|
+
expect(wrapper.vm.createReceiverLink).toStrictEqual({ name: 'create-receiver' });
|
|
62
|
+
|
|
63
|
+
// Assert that receiverTableHeaders has the expected structure (basic check)
|
|
64
|
+
expect(wrapper.vm.receiverTableHeaders.length).toBeGreaterThan(0);
|
|
65
|
+
expect(wrapper.vm.receiverTableHeaders[0].name).toBe('name');
|
|
66
|
+
expect(wrapper.vm.receiverTableHeaders[1].name).toBe('type');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Additional test for editorMode computed property
|
|
70
|
+
it('editorMode should return VIEW_CODE when mode is _VIEW', () => {
|
|
71
|
+
const valueMock = {
|
|
72
|
+
applyDefaults: jest.fn(),
|
|
73
|
+
spec: { receivers: [] },
|
|
74
|
+
getCreateReceiverRoute: jest.fn(),
|
|
75
|
+
getReceiverDetailLink: jest.fn(),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const wrapper = shallowMount(Index, {
|
|
79
|
+
propsData: {
|
|
80
|
+
value: valueMock,
|
|
81
|
+
mode: _VIEW, // Set mode to _VIEW
|
|
82
|
+
},
|
|
83
|
+
global: {
|
|
84
|
+
mocks: {
|
|
85
|
+
$store: {
|
|
86
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
87
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
88
|
+
},
|
|
89
|
+
$router: { push: jest.fn() },
|
|
90
|
+
$route: { name: 'test-route' },
|
|
91
|
+
$fetchState: { pending: false },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
stubs: {
|
|
95
|
+
CruResource: true,
|
|
96
|
+
NameNsDescription: true,
|
|
97
|
+
Tabbed: true,
|
|
98
|
+
Tab: true,
|
|
99
|
+
RouteConfig: true,
|
|
100
|
+
ResourceTable: true,
|
|
101
|
+
ActionMenu: true,
|
|
102
|
+
Loading: true,
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(wrapper.vm.editorMode).toBe(EDITOR_MODES.VIEW_CODE);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('editorMode should return EDIT_CODE when mode is not _VIEW', () => {
|
|
110
|
+
const valueMock = {
|
|
111
|
+
applyDefaults: jest.fn(),
|
|
112
|
+
spec: { receivers: [] },
|
|
113
|
+
getCreateReceiverRoute: jest.fn(),
|
|
114
|
+
getReceiverDetailLink: jest.fn(),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const wrapper = shallowMount(Index, {
|
|
118
|
+
propsData: {
|
|
119
|
+
value: valueMock,
|
|
120
|
+
mode: _EDIT, // Set mode to _EDIT
|
|
121
|
+
},
|
|
122
|
+
global: {
|
|
123
|
+
mocks: {
|
|
124
|
+
$store: {
|
|
125
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
126
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
127
|
+
},
|
|
128
|
+
$router: { push: jest.fn() },
|
|
129
|
+
$route: { name: 'test-route' },
|
|
130
|
+
$fetchState: { pending: false },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
stubs: {
|
|
134
|
+
CruResource: true,
|
|
135
|
+
NameNsDescription: true,
|
|
136
|
+
Tabbed: true,
|
|
137
|
+
Tab: true,
|
|
138
|
+
RouteConfig: true,
|
|
139
|
+
ResourceTable: true,
|
|
140
|
+
ActionMenu: true,
|
|
141
|
+
Loading: true,
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(wrapper.vm.editorMode).toBe(EDITOR_MODES.EDIT_CODE);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('translateReceiverTypes method translates receiver types', () => {
|
|
149
|
+
const valueMock = {
|
|
150
|
+
applyDefaults: jest.fn(),
|
|
151
|
+
spec: { receivers: [] },
|
|
152
|
+
getCreateReceiverRoute: jest.fn(),
|
|
153
|
+
getReceiverDetailLink: jest.fn(),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const wrapper = shallowMount(Index, {
|
|
157
|
+
propsData: {
|
|
158
|
+
value: valueMock,
|
|
159
|
+
mode: 'create',
|
|
160
|
+
},
|
|
161
|
+
global: {
|
|
162
|
+
mocks: {
|
|
163
|
+
$store: {
|
|
164
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
165
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
166
|
+
},
|
|
167
|
+
$router: { push: jest.fn() },
|
|
168
|
+
$route: { name: 'test-route' },
|
|
169
|
+
$fetchState: { pending: false },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
stubs: {
|
|
173
|
+
CruResource: true,
|
|
174
|
+
NameNsDescription: true,
|
|
175
|
+
Tabbed: true,
|
|
176
|
+
Tab: true,
|
|
177
|
+
RouteConfig: true,
|
|
178
|
+
ResourceTable: true,
|
|
179
|
+
ActionMenu: true,
|
|
180
|
+
Loading: true,
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Ensure receiverTypes is initialized, then call the method
|
|
185
|
+
const originalReceiverTypes = [
|
|
186
|
+
{ label: 'alertmanagerConfigReceiver.alerta', value: 'alerta' },
|
|
187
|
+
{ label: 'alertmanagerConfigReceiver.dingtalk', value: 'dingtalk' }
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
// Manually set receiverTypes for the test (it's normally populated from an import)
|
|
191
|
+
// In a real scenario, you'd mock the import or ensure it's loaded before the test.
|
|
192
|
+
// For this test, we'll directly manipulate the vm.
|
|
193
|
+
wrapper.setData({ receiverTypes: originalReceiverTypes });
|
|
194
|
+
|
|
195
|
+
const translatedTypes = wrapper.vm.translateReceiverTypes();
|
|
196
|
+
|
|
197
|
+
expect(translatedTypes).toStrictEqual([
|
|
198
|
+
{ label: '%alertmanagerConfigReceiver.alerta%', value: 'alerta' },
|
|
199
|
+
{ label: '%alertmanagerConfigReceiver.dingtalk%', value: 'dingtalk' }
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
|
|
2
|
+
import Tls from '@shell/edit/monitoring.coreos.com.alertmanagerconfig/tls.vue';
|
|
3
|
+
import { shallowMount } from '@vue/test-utils';
|
|
4
|
+
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
5
|
+
import SimpleSecretSelector from '@shell/components/form/SimpleSecretSelector.vue';
|
|
6
|
+
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
7
|
+
import { Banner } from '@components/Banner';
|
|
8
|
+
|
|
9
|
+
describe('component: Tls', () => {
|
|
10
|
+
const mockStore = {
|
|
11
|
+
getters: {
|
|
12
|
+
'i18n/t': (key: string) => key,
|
|
13
|
+
'i18n/exists': () => true,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const requiredProps = {
|
|
18
|
+
value: { tlsConfig: {} },
|
|
19
|
+
namespace: 'test-namespace',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
it('should render all child components', () => {
|
|
23
|
+
const wrapper = shallowMount(Tls, {
|
|
24
|
+
props: { ...requiredProps, mode: _EDIT },
|
|
25
|
+
global: { mocks: mockStore },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const secretSelectors = wrapper.findAllComponents(SimpleSecretSelector);
|
|
29
|
+
const labeledInput = wrapper.findComponent(LabeledInput);
|
|
30
|
+
|
|
31
|
+
expect(secretSelectors).toHaveLength(3);
|
|
32
|
+
expect(labeledInput.exists()).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should show a banner if namespace is not provided', () => {
|
|
36
|
+
const wrapper = shallowMount(Tls, {
|
|
37
|
+
props: {
|
|
38
|
+
...requiredProps,
|
|
39
|
+
namespace: undefined,
|
|
40
|
+
mode: _EDIT,
|
|
41
|
+
},
|
|
42
|
+
global: { mocks: mockStore },
|
|
43
|
+
});
|
|
44
|
+
const banner = wrapper.findComponent(Banner);
|
|
45
|
+
|
|
46
|
+
expect(banner.exists()).toBe(true);
|
|
47
|
+
expect(banner.props().color).toBe('error');
|
|
48
|
+
expect(banner.vm.$slots.default()[0].children).toBe('%alertmanagerConfigReceiver.namespaceWarning%');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should not show a banner if namespace is provided', () => {
|
|
52
|
+
const wrapper = shallowMount(Tls, {
|
|
53
|
+
props: { ...requiredProps, mode: _EDIT },
|
|
54
|
+
global: { mocks: mockStore },
|
|
55
|
+
});
|
|
56
|
+
const banner = wrapper.findComponent(Banner);
|
|
57
|
+
|
|
58
|
+
expect(banner.exists()).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should disable inputs when in view mode', () => {
|
|
62
|
+
const wrapper = shallowMount(Tls, {
|
|
63
|
+
props: { ...requiredProps, mode: _VIEW },
|
|
64
|
+
global: { mocks: mockStore },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const secretSelectors = wrapper.findAllComponents(SimpleSecretSelector);
|
|
68
|
+
const labeledInput = wrapper.findComponent(LabeledInput);
|
|
69
|
+
|
|
70
|
+
secretSelectors.forEach((s) => expect(s.props().disabled).toBe(true));
|
|
71
|
+
expect(labeledInput.props().mode).toBe(_VIEW);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should initialize tlsConfig if not present', () => {
|
|
75
|
+
const value = {};
|
|
76
|
+
const wrapper = shallowMount(Tls, {
|
|
77
|
+
props: {
|
|
78
|
+
...requiredProps,
|
|
79
|
+
value,
|
|
80
|
+
mode: _EDIT,
|
|
81
|
+
},
|
|
82
|
+
global: { mocks: mockStore },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(wrapper.props().value.tlsConfig).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should correctly initialize data from props', () => {
|
|
89
|
+
const value = {
|
|
90
|
+
tlsConfig: {
|
|
91
|
+
ca: {
|
|
92
|
+
secret: {
|
|
93
|
+
key: 'ca-key',
|
|
94
|
+
name: 'ca-name'
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
cert: {
|
|
98
|
+
secret: {
|
|
99
|
+
key: 'cert-key',
|
|
100
|
+
name: 'cert-name'
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
keySecret: {
|
|
104
|
+
key: 'key-key',
|
|
105
|
+
name: 'key-name'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const wrapper = shallowMount(Tls, {
|
|
111
|
+
props: {
|
|
112
|
+
...requiredProps,
|
|
113
|
+
value,
|
|
114
|
+
mode: _EDIT,
|
|
115
|
+
},
|
|
116
|
+
global: { mocks: mockStore },
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const data = wrapper.vm.$data;
|
|
120
|
+
|
|
121
|
+
expect(data.initialCaSecretKey).toBe('ca-key');
|
|
122
|
+
expect(data.initialCaSecretName).toBe('ca-name');
|
|
123
|
+
expect(data.initialClientCertSecretKey).toBe('cert-key');
|
|
124
|
+
expect(data.initialClientCertSecretName).toBe('cert-name');
|
|
125
|
+
expect(data.initialClientKeySecretKey).toBe('key-key');
|
|
126
|
+
expect(data.initialClientKeySecretName).toBe('key-name');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('event handling', () => {
|
|
130
|
+
it.each([
|
|
131
|
+
['ca', 0, 'ca', 'updateCaSecretName', 'updateCaSecretKey'],
|
|
132
|
+
['cert', 1, 'cert', 'updateClientCertSecretName', 'updateClientCertSecretKey'],
|
|
133
|
+
['key', 2, 'keySecret', 'updateClientKeySecretName', 'updateClientKeySecretKey'],
|
|
134
|
+
])('should handle %p secret selector events', async(secretType, index, tlsConfigKey, nameHandler, keyHandler) => {
|
|
135
|
+
const value = { tlsConfig: {} };
|
|
136
|
+
const wrapper = shallowMount(Tls, {
|
|
137
|
+
props: {
|
|
138
|
+
...requiredProps, value, mode: _EDIT
|
|
139
|
+
},
|
|
140
|
+
global: { mocks: mockStore },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const secretSelector = wrapper.findAllComponents(SimpleSecretSelector)[index];
|
|
144
|
+
const name = `${ secretType }-secret-name`;
|
|
145
|
+
const key = `${ secretType }-secret-key`;
|
|
146
|
+
|
|
147
|
+
await secretSelector.vm.$emit('updateSecretName', name);
|
|
148
|
+
await wrapper.vm.$nextTick();
|
|
149
|
+
|
|
150
|
+
const nameValue = tlsConfigKey === 'keySecret' ? value.tlsConfig[tlsConfigKey].name : value.tlsConfig[tlsConfigKey].secret.name;
|
|
151
|
+
|
|
152
|
+
expect(nameValue).toBe(name);
|
|
153
|
+
|
|
154
|
+
await secretSelector.vm.$emit('updateSecretKey', key);
|
|
155
|
+
await wrapper.vm.$nextTick();
|
|
156
|
+
|
|
157
|
+
const keyValue = tlsConfigKey === 'keySecret' ? value.tlsConfig[tlsConfigKey].key : value.tlsConfig[tlsConfigKey].secret.key;
|
|
158
|
+
|
|
159
|
+
expect(keyValue).toBe(key);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('secret updates', () => {
|
|
164
|
+
it.each([
|
|
165
|
+
['Ca', 'updateCaSecretName', 'ca', 'name'],
|
|
166
|
+
['Ca', 'updateCaSecretKey', 'ca', 'key'],
|
|
167
|
+
['ClientCert', 'updateClientCertSecretName', 'cert', 'name'],
|
|
168
|
+
['ClientCert', 'updateClientCertSecretKey', 'cert', 'key'],
|
|
169
|
+
['ClientKey', 'updateClientKeySecretName', 'keySecret', 'name'],
|
|
170
|
+
['ClientKey', 'updateClientKeySecretKey', 'keySecret', 'key'],
|
|
171
|
+
])('should update %p secret %p', (secret, handler, obj, field) => {
|
|
172
|
+
const value = { tlsConfig: {} };
|
|
173
|
+
const wrapper = shallowMount(Tls, {
|
|
174
|
+
props: {
|
|
175
|
+
...requiredProps,
|
|
176
|
+
value,
|
|
177
|
+
mode: _EDIT
|
|
178
|
+
},
|
|
179
|
+
global: { mocks: mockStore },
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const data = 'test-data';
|
|
183
|
+
|
|
184
|
+
(wrapper.vm as any)[handler](data);
|
|
185
|
+
|
|
186
|
+
const dataValue = obj === 'keySecret' ? value.tlsConfig[obj][field] : value.tlsConfig[obj].secret[field];
|
|
187
|
+
|
|
188
|
+
expect(dataValue).toBe(data);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it.each([
|
|
192
|
+
['Ca', 'updateCaSecretName', 'ca'],
|
|
193
|
+
['ClientCert', 'updateClientCertSecretName', 'cert'],
|
|
194
|
+
])('should remove %p secret', (secret, handler, obj) => {
|
|
195
|
+
const value = { tlsConfig: { [obj]: { secret: { name: 'test-name', key: 'test-key' } } } };
|
|
196
|
+
const wrapper = shallowMount(Tls, {
|
|
197
|
+
props: {
|
|
198
|
+
...requiredProps,
|
|
199
|
+
value,
|
|
200
|
+
mode: _EDIT
|
|
201
|
+
},
|
|
202
|
+
global: { mocks: mockStore },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
(wrapper.vm as any)[handler]('__[[NONE]]__');
|
|
206
|
+
|
|
207
|
+
expect(value.tlsConfig[obj]).toStrictEqual({});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should remove "ClientKey" secret', () => {
|
|
211
|
+
const value = { tlsConfig: { keySecret: { name: 'test-name', key: 'test-key' } } };
|
|
212
|
+
const wrapper = shallowMount(Tls, {
|
|
213
|
+
props: {
|
|
214
|
+
...requiredProps,
|
|
215
|
+
value,
|
|
216
|
+
mode: _EDIT
|
|
217
|
+
},
|
|
218
|
+
global: { mocks: mockStore },
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
(wrapper.vm as any).updateClientKeySecretName('__[[NONE]]__');
|
|
222
|
+
|
|
223
|
+
expect(value.tlsConfig.keySecret).toStrictEqual({});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
3
3
|
import SimpleSecretSelector from '@shell/components/form/SimpleSecretSelector';
|
|
4
|
+
import Banner from '@components/Banner/Banner.vue';
|
|
4
5
|
import isEmpty from 'lodash/isEmpty';
|
|
5
6
|
import { _VIEW } from '@shell/config/query-params';
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
|
-
components: {
|
|
9
|
-
|
|
9
|
+
components: {
|
|
10
|
+
Banner, LabeledSelect, SimpleSecretSelector
|
|
11
|
+
},
|
|
12
|
+
props: {
|
|
10
13
|
mode: {
|
|
11
14
|
type: String,
|
|
12
15
|
required: true,
|
|
@@ -64,7 +67,7 @@ export default {
|
|
|
64
67
|
initializeType(authOptions, type) {
|
|
65
68
|
authOptions.forEach((authOption) => {
|
|
66
69
|
if (authOption.value === type && type !== 'none') {
|
|
67
|
-
this.value
|
|
70
|
+
this.value[authOption.value] = this.value[authOption.value] || authOption.default;
|
|
68
71
|
} else if (typeof this.value[authOption.value] !== 'undefined') {
|
|
69
72
|
delete this.value[authOption.value];
|
|
70
73
|
}
|
|
@@ -248,9 +251,9 @@ export default {
|
|
|
248
251
|
/>
|
|
249
252
|
</div>
|
|
250
253
|
</div>
|
|
251
|
-
<div
|
|
252
|
-
v-if="authType === 'basicAuth'"
|
|
253
|
-
class="row mb-20"
|
|
254
|
+
<div
|
|
255
|
+
v-if="authType === 'basicAuth'"
|
|
256
|
+
class="row mb-20"
|
|
254
257
|
>
|
|
255
258
|
<SimpleSecretSelector
|
|
256
259
|
v-if="namespace"
|
|
@@ -265,16 +268,16 @@ export default {
|
|
|
265
268
|
@updateSecretName="updateBasicAuthUsernameSecretName"
|
|
266
269
|
@updateSecretKey="updateBasicAuthUsernameSecretKey"
|
|
267
270
|
/>
|
|
268
|
-
<Banner
|
|
269
|
-
v-else
|
|
270
|
-
color="error"
|
|
271
|
+
<Banner
|
|
272
|
+
v-else
|
|
273
|
+
color="error"
|
|
271
274
|
>
|
|
272
275
|
{{ t("alertmanagerConfigReceiver.namespaceWarning") }}
|
|
273
276
|
</Banner>
|
|
274
277
|
</div>
|
|
275
|
-
<div
|
|
276
|
-
v-if="authType === 'basicAuth'"
|
|
277
|
-
class="row mb-20"
|
|
278
|
+
<div
|
|
279
|
+
v-if="authType === 'basicAuth'"
|
|
280
|
+
class="row mb-20"
|
|
278
281
|
>
|
|
279
282
|
<SimpleSecretSelector
|
|
280
283
|
v-if="namespace"
|
|
@@ -289,16 +292,16 @@ export default {
|
|
|
289
292
|
@updateSecretName="updateBasicAuthPasswordSecretName"
|
|
290
293
|
@updateSecretKey="updateBasicAuthPasswordSecretKey"
|
|
291
294
|
/>
|
|
292
|
-
<Banner
|
|
293
|
-
v-else
|
|
294
|
-
color="error"
|
|
295
|
+
<Banner
|
|
296
|
+
v-else
|
|
297
|
+
color="error"
|
|
295
298
|
>
|
|
296
299
|
{{ t("alertmanagerConfigReceiver.namespaceWarning") }}
|
|
297
300
|
</Banner>
|
|
298
301
|
</div>
|
|
299
|
-
<div
|
|
300
|
-
v-if="authType === 'bearerTokenSecret'"
|
|
301
|
-
class="row mb-20"
|
|
302
|
+
<div
|
|
303
|
+
v-if="authType === 'bearerTokenSecret'"
|
|
304
|
+
class="row mb-20"
|
|
302
305
|
>
|
|
303
306
|
<SimpleSecretSelector
|
|
304
307
|
v-if="namespace"
|
|
@@ -313,9 +316,9 @@ export default {
|
|
|
313
316
|
@updateSecretName="updateBearerTokenSecretName"
|
|
314
317
|
@updateSecretKey="updateBearerTokenSecretKey"
|
|
315
318
|
/>
|
|
316
|
-
<Banner
|
|
317
|
-
v-else
|
|
318
|
-
color="error"
|
|
319
|
+
<Banner
|
|
320
|
+
v-else
|
|
321
|
+
color="error"
|
|
319
322
|
>
|
|
320
323
|
{{ t("alertmanagerConfigReceiver.namespaceWarning") }}
|
|
321
324
|
</Banner>
|