mirror of https://github.com/rancher/dashboard.git
383 lines
12 KiB
Vue
383 lines
12 KiB
Vue
<script>
|
|
|
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
import { _EDIT } from '@shell/config/query-params';
|
|
import MatchExpressions from '@shell/components/form/MatchExpressions';
|
|
import { convert, matching, simplify } from '@shell/utils/selector';
|
|
import { POD } from '@shell/config/types';
|
|
import ArrayList from '@shell/components/form/ArrayList';
|
|
import { Banner } from '@components/Banner';
|
|
import throttle from 'lodash/throttle';
|
|
import { isValidCIDR } from '@shell/utils/validators/cidr';
|
|
|
|
const TARGET_OPTIONS = {
|
|
IP_BLOCK: 'ipBlock',
|
|
NAMESPACE_SELECTOR: 'namespaceSelector',
|
|
POD_SELECTOR: 'podSelector',
|
|
NAMESPACE_AND_POD_SELECTOR: 'namespaceAndPodSelector',
|
|
};
|
|
|
|
export default {
|
|
components: {
|
|
ArrayList, Banner, LabeledInput, LabeledSelect, MatchExpressions
|
|
},
|
|
props: {
|
|
value: {
|
|
type: Object,
|
|
default: () => {
|
|
return {};
|
|
},
|
|
},
|
|
mode: {
|
|
type: String,
|
|
default: _EDIT,
|
|
},
|
|
type: {
|
|
type: String,
|
|
default: 'ingress'
|
|
},
|
|
namespace: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
allPods: {
|
|
type: Array,
|
|
default: () => {
|
|
return [];
|
|
},
|
|
},
|
|
allNamespaces: {
|
|
type: Array,
|
|
default: () => {
|
|
return [];
|
|
},
|
|
},
|
|
},
|
|
data() {
|
|
if (!this.value[TARGET_OPTIONS.IP_BLOCK] &&
|
|
!this.value[TARGET_OPTIONS.POD_SELECTOR] &&
|
|
!this.value[TARGET_OPTIONS.NAMESPACE_SELECTOR] &&
|
|
!this.value[TARGET_OPTIONS.NAMESPACE_AND_POD_SELECTOR]
|
|
) {
|
|
this.$nextTick(() => {
|
|
this.$set(this.value, TARGET_OPTIONS.IP_BLOCK, {});
|
|
});
|
|
}
|
|
|
|
return {
|
|
portOptions: ['TCP', 'UDP'],
|
|
matchingPods: {},
|
|
matchingNamespaces: {},
|
|
invalidCidr: null,
|
|
invalidCidrs: [],
|
|
POD,
|
|
TARGET_OPTIONS,
|
|
targetOptions: Object.values(TARGET_OPTIONS),
|
|
throttleTime: 250,
|
|
};
|
|
},
|
|
computed: {
|
|
podSelectorExpressions: {
|
|
get() {
|
|
return convert(
|
|
this.value[TARGET_OPTIONS.POD_SELECTOR]?.matchLabels || {},
|
|
this.value[TARGET_OPTIONS.POD_SELECTOR]?.matchExpressions || []
|
|
);
|
|
},
|
|
set(podSelectorExpressions) {
|
|
this.$set(this.value, TARGET_OPTIONS.POD_SELECTOR, simplify(podSelectorExpressions));
|
|
}
|
|
},
|
|
namespaceSelectorExpressions: {
|
|
get() {
|
|
return convert(
|
|
this.value[TARGET_OPTIONS.NAMESPACE_SELECTOR]?.matchLabels || {},
|
|
this.value[TARGET_OPTIONS.NAMESPACE_SELECTOR]?.matchExpressions || []
|
|
);
|
|
},
|
|
set(namespaceSelectorExpressions) {
|
|
this.$set(this.value, TARGET_OPTIONS.NAMESPACE_SELECTOR, simplify(namespaceSelectorExpressions));
|
|
}
|
|
},
|
|
selectTargetOptions() {
|
|
const selectTargetOptions = [];
|
|
|
|
for (const option of this.targetOptions) {
|
|
selectTargetOptions.push({
|
|
label: this.t(`networkpolicy.rules.${ option }.label`),
|
|
value: option,
|
|
});
|
|
}
|
|
|
|
return selectTargetOptions;
|
|
},
|
|
targetType: {
|
|
get() {
|
|
for (const option of this.targetOptions) {
|
|
if (this.value[TARGET_OPTIONS.NAMESPACE_AND_POD_SELECTOR] || (this.value[TARGET_OPTIONS.NAMESPACE_SELECTOR] && this.value[TARGET_OPTIONS.POD_SELECTOR])) {
|
|
return TARGET_OPTIONS.NAMESPACE_AND_POD_SELECTOR;
|
|
}
|
|
if (this.value[option]) {
|
|
return option;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
set(targetType) {
|
|
this.$delete(this.value, TARGET_OPTIONS.IP_BLOCK);
|
|
this.$delete(this.value, TARGET_OPTIONS.NAMESPACE_SELECTOR);
|
|
this.$delete(this.value, TARGET_OPTIONS.POD_SELECTOR);
|
|
this.$delete(this.value, TARGET_OPTIONS.NAMESPACE_AND_POD_SELECTOR);
|
|
this.$nextTick(() => {
|
|
this.$set(this.value, targetType, {});
|
|
});
|
|
}
|
|
},
|
|
updateMatches() {
|
|
return {
|
|
handler: throttle(function() {
|
|
this.matchingNamespaces = this.getMatchingNamespaces();
|
|
this.matchingPods = this.getMatchingPods();
|
|
}, this.throttle, { leading: true }),
|
|
immediate: true
|
|
};
|
|
},
|
|
matchingNamespacesAndPods() {
|
|
return {
|
|
policyNamespace: this.namespace,
|
|
...Object.keys(this.matchingNamespaces).reduce((acc, k) => ({ ...acc, [`${ k }Namespaces`]: this.matchingNamespaces[k] }), {}),
|
|
...Object.keys(this.matchingPods).reduce((acc, k) => ({ ...acc, [`${ k }Pods`]: this.matchingPods[k] }), {}),
|
|
};
|
|
}
|
|
},
|
|
watch: {
|
|
namespace: 'updateMatches',
|
|
'value.podSelector': 'updateMatches',
|
|
'value.namespaceSelector': 'updateMatches',
|
|
'value.ipBlock.cidr': 'validateCIDR',
|
|
'value.ipBlock.except': 'validateCIDR',
|
|
podSelectorExpressions: 'updateMatches',
|
|
namespaceSelectorExpressions: 'updateMatches',
|
|
},
|
|
methods: {
|
|
validateCIDR() {
|
|
const exceptCidrs = this.value[TARGET_OPTIONS.IP_BLOCK]?.except || [];
|
|
|
|
this.invalidCidrs = exceptCidrs
|
|
.filter((cidr) => !isValidCIDR(cidr))
|
|
.map((invalidCidr) => invalidCidr || '<blank>');
|
|
|
|
if (this.value[TARGET_OPTIONS.IP_BLOCK]?.cidr && !isValidCIDR(this.value[TARGET_OPTIONS.IP_BLOCK].cidr)) {
|
|
this.invalidCidr = this.value[TARGET_OPTIONS.IP_BLOCK].cidr;
|
|
} else {
|
|
this.invalidCidr = null;
|
|
}
|
|
},
|
|
getMatchingPods() {
|
|
const namespaces = this.targetType === TARGET_OPTIONS.NAMESPACE_AND_POD_SELECTOR ? this.matchingNamespaces.matches : [{ id: this.namespace }];
|
|
const allInNamespace = this.allPods.filter((pod) => namespaces.some((ns) => ns.id === pod.metadata.namespace));
|
|
const match = matching(allInNamespace, this.podSelectorExpressions);
|
|
const matched = match.length || 0;
|
|
const sample = match[0]?.nameDisplay;
|
|
|
|
return {
|
|
matched,
|
|
matches: match,
|
|
none: matched === 0,
|
|
sample,
|
|
total: allInNamespace.length,
|
|
};
|
|
},
|
|
getMatchingNamespaces() {
|
|
const allNamespaces = this.allNamespaces;
|
|
const match = matching(allNamespaces, this.namespaceSelectorExpressions);
|
|
const matched = match.length || 0;
|
|
const sample = match[0]?.nameDisplay;
|
|
|
|
return {
|
|
matched,
|
|
matches: match,
|
|
none: matched === 0,
|
|
sample,
|
|
total: allNamespaces.length,
|
|
};
|
|
},
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="rule">
|
|
<div class="row mb-20">
|
|
<div class="col span-6">
|
|
<LabeledSelect
|
|
v-model="targetType"
|
|
data-testid="labeled-select-type-selector"
|
|
:mode="mode"
|
|
:tooltip="targetType === TARGET_OPTIONS.NAMESPACE_AND_POD_SELECTOR ? t('networkpolicy.selectors.matchingNamespacesAndPods.tooltip') : null"
|
|
:options="selectTargetOptions"
|
|
:multiple="false"
|
|
:label="t('networkpolicy.rules.type')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div v-if="targetType === TARGET_OPTIONS.IP_BLOCK">
|
|
<div class="row">
|
|
<div class="col span-6">
|
|
<LabeledInput
|
|
v-model="value[TARGET_OPTIONS.IP_BLOCK].cidr"
|
|
data-testid="labeled-input-ip-block-selector"
|
|
:mode="mode"
|
|
:placeholder="t('networkpolicy.rules.ipBlock.cidr.placeholder')"
|
|
:label="t('networkpolicy.rules.ipBlock.cidr.label')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="invalidCidr"
|
|
class="row"
|
|
>
|
|
<div class="col span-12">
|
|
<Banner color="error">
|
|
<t k="networkpolicy.rules.ipBlock.invalidCidr" />
|
|
</Banner>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-20">
|
|
<div class="col span-12">
|
|
<ArrayList
|
|
v-model="value[TARGET_OPTIONS.IP_BLOCK].except"
|
|
:add-label="t('networkpolicy.rules.ipBlock.addExcept')"
|
|
:mode="mode"
|
|
:show-header="true"
|
|
:value-label="t('networkpolicy.rules.ipBlock.exceptions')"
|
|
:value-placeholder="t('networkpolicy.rules.ipBlock.cidr.placeholder')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="invalidCidrs.length"
|
|
class="row mb-10"
|
|
>
|
|
<div class="col span-12">
|
|
<Banner color="error">
|
|
<t k="networkpolicy.rules.ipBlock.invalidExceptionCidrs" />{{ invalidCidrs.join(', ') }}
|
|
</Banner>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="targetType === TARGET_OPTIONS.POD_SELECTOR">
|
|
<div class="row">
|
|
<div class="col span-12">
|
|
<Banner color="success">
|
|
<span v-clean-html="t('networkpolicy.selectors.matchingPods.matchesSome', matchingPods)" />
|
|
</Banner>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-0">
|
|
<div class="col span-12">
|
|
<MatchExpressions
|
|
v-model="podSelectorExpressions"
|
|
data-testid="match-expression-pod-selector"
|
|
:mode="mode"
|
|
:show-remove="false"
|
|
:initial-empty-row="true"
|
|
:type="POD"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="targetType === TARGET_OPTIONS.NAMESPACE_SELECTOR">
|
|
<div class="row">
|
|
<div class="col span-12">
|
|
<Banner color="success">
|
|
<span v-clean-html="t('networkpolicy.selectors.matchingNamespaces.matchesSome', matchingNamespaces)" />
|
|
</Banner>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-0">
|
|
<div class="col span-12">
|
|
<MatchExpressions
|
|
v-model="namespaceSelectorExpressions"
|
|
data-testid="match-expression-namespace-selector"
|
|
:mode="mode"
|
|
:show-remove="false"
|
|
:initial-empty-row="true"
|
|
:type="POD"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="targetType === TARGET_OPTIONS.NAMESPACE_AND_POD_SELECTOR">
|
|
<div class="row">
|
|
<div class="col span-12">
|
|
<Banner color="success">
|
|
<span
|
|
v-if="!namespaceSelectorExpressions.length"
|
|
v-clean-html="t('networkpolicy.selectors.matchingPods.matchesSome', matchingPods)"
|
|
/>
|
|
<span
|
|
v-else
|
|
v-clean-html="t('networkpolicy.selectors.matchingNamespacesAndPods.matchesSome', matchingNamespacesAndPods)"
|
|
/>
|
|
</Banner>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-0">
|
|
<div class="col span-1 namespace-pod-rule">
|
|
<span class="label">
|
|
{{ t('networkpolicy.rules.namespace') }}
|
|
</span>
|
|
</div>
|
|
<div class="col span-11">
|
|
<MatchExpressions
|
|
v-model="namespaceSelectorExpressions"
|
|
data-testid="match-expression-namespace-and-pod-selector-ns-rule"
|
|
:mode="mode"
|
|
:show-add-button="false"
|
|
:show-remove-button="false"
|
|
:show-remove="false"
|
|
:initial-empty-row="true"
|
|
:type="POD"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-0">
|
|
<div class="col span-1 namespace-pod-rule">
|
|
<span class="label">
|
|
{{ t('networkpolicy.rules.pod') }}
|
|
</span>
|
|
</div>
|
|
<div class="col span-11">
|
|
<MatchExpressions
|
|
v-model="podSelectorExpressions"
|
|
data-testid="match-expression-namespace-and-pod-selector-pod-rule"
|
|
:mode="mode"
|
|
:show-add-button="false"
|
|
:show-remove-button="false"
|
|
:show-remove="false"
|
|
:initial-empty-row="true"
|
|
:type="POD"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang='scss' scoped>
|
|
.namespace-pod-rule {
|
|
display: table;
|
|
width: 100px;
|
|
padding: 0, 10px, 0, 0;
|
|
text-align: center;
|
|
|
|
.label {
|
|
display:table-cell;
|
|
vertical-align:middle;
|
|
}
|
|
}
|
|
</style>
|