mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2025-12-15 14:11:46 +00:00
feat: add ACL tab in create subnet page (#527)
* feat: add ACL tab in create subnet page Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: typo Signed-off-by: Andy Lee <andy.lee@suse.com> --------- Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
f652ed9d4b
commit
9fdbe9c58f
196
pkg/harvester/edit/kubeovn.io.subnet/AccessControlList.vue
Normal file
196
pkg/harvester/edit/kubeovn.io.subnet/AccessControlList.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<script>
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
||||||
|
import { removeAt } from '@shell/utils/array';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import InfoBox from '@shell/components/InfoBox';
|
||||||
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AccessControlList',
|
||||||
|
|
||||||
|
emits: ['update:value'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Banner, InfoBox, LabeledSelect, LabeledInput
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: _EDIT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const rows = (this.value || []).map((row) => {
|
||||||
|
return {
|
||||||
|
action: row.action || '',
|
||||||
|
direction: row.direction || '',
|
||||||
|
priority: row.priority || 0,
|
||||||
|
match: row.match || '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { rows };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
isView() {
|
||||||
|
return this.mode === _VIEW;
|
||||||
|
},
|
||||||
|
|
||||||
|
actionOptions() {
|
||||||
|
return [
|
||||||
|
{ label: 'Allow', value: 'allow' },
|
||||||
|
{ label: 'Drop', value: 'drop' },
|
||||||
|
{ label: 'Pass', value: 'pass' },
|
||||||
|
{ label: 'Reject', value: 'reject' },
|
||||||
|
{ label: 'Allow-related', value: 'allow-related' },
|
||||||
|
{ label: 'Allow-stateless', value: 'allow-stateless' },
|
||||||
|
];
|
||||||
|
},
|
||||||
|
directionOptions() {
|
||||||
|
return [
|
||||||
|
{ label: 'To-lport', value: 'to-lport' },
|
||||||
|
{ label: 'From-lport', value: 'from-lport' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.queueUpdate = debounce(this.update, 100);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
this.rows.push({
|
||||||
|
action: '',
|
||||||
|
direction: '',
|
||||||
|
priority: this.rows.length,
|
||||||
|
match: '',
|
||||||
|
});
|
||||||
|
this.queueUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
removeRule(idx) {
|
||||||
|
removeAt(this.rows, idx);
|
||||||
|
this.queueUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if (this.isView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('update:value', this.rows);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Banner
|
||||||
|
v-if="rows.length > 0"
|
||||||
|
color="info"
|
||||||
|
>
|
||||||
|
<t
|
||||||
|
k="harvester.subnet.acl.banner"
|
||||||
|
:raw="true"
|
||||||
|
/>
|
||||||
|
</Banner>
|
||||||
|
<div
|
||||||
|
v-for="(row, idx) in rows"
|
||||||
|
:key="idx"
|
||||||
|
>
|
||||||
|
<InfoBox class="box">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="role-link btn btn-sm removeBtn"
|
||||||
|
:disabled="isView"
|
||||||
|
@click="removeRule(idx)"
|
||||||
|
>
|
||||||
|
<i class="icon icon-x" />
|
||||||
|
</button>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col span-4">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="row.action"
|
||||||
|
:mode="mode"
|
||||||
|
class="mt-5 ml-5"
|
||||||
|
:options="actionOptions"
|
||||||
|
:required="true"
|
||||||
|
:label="t('harvester.subnet.acl.action.label')"
|
||||||
|
:placeholder="t('harvester.subnet.acl.action.placeholder')"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-4">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="row.direction"
|
||||||
|
:mode="mode"
|
||||||
|
:options="directionOptions"
|
||||||
|
class="mt-5"
|
||||||
|
:required="true"
|
||||||
|
:label="t('harvester.subnet.acl.direction.label')"
|
||||||
|
:placeholder="t('harvester.subnet.acl.direction.placeholder')"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-3">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value.number="row.priority"
|
||||||
|
type="number"
|
||||||
|
class="mb-20 mt-5"
|
||||||
|
:max="32767"
|
||||||
|
:min="0"
|
||||||
|
:mode="mode"
|
||||||
|
required
|
||||||
|
label-key="harvester.subnet.acl.priority.label"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col span-11">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="row.match"
|
||||||
|
class="mb-5 ml-5"
|
||||||
|
:mode="mode"
|
||||||
|
required
|
||||||
|
:placeholder="t('harvester.subnet.acl.match.placeholder')"
|
||||||
|
label-key="harvester.subnet.acl.match.label"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</InfoBox>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn role-tertiary add"
|
||||||
|
:disabled="isView"
|
||||||
|
@click="add()"
|
||||||
|
>
|
||||||
|
<t k="harvester.subnet.acl.addRule" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.box {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.removeBtn {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -15,6 +15,7 @@ import { allHash } from '@shell/utils/promise';
|
|||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
import ResourceTabs from '@shell/components/form/ResourceTabs/index';
|
import ResourceTabs from '@shell/components/form/ResourceTabs/index';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
|
import AccessControlList from './AccessControlList';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditSubnet',
|
name: 'EditSubnet',
|
||||||
@ -32,6 +33,7 @@ export default {
|
|||||||
ArrayList,
|
ArrayList,
|
||||||
ResourceTabs,
|
ResourceTabs,
|
||||||
Loading,
|
Loading,
|
||||||
|
AccessControlList
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView],
|
||||||
@ -51,7 +53,8 @@ export default {
|
|||||||
gatewayIP: '',
|
gatewayIP: '',
|
||||||
excludeIps: [],
|
excludeIps: [],
|
||||||
private: false,
|
private: false,
|
||||||
enableDHCP
|
enableDHCP,
|
||||||
|
acls: []
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -143,6 +146,7 @@ export default {
|
|||||||
async saveSubnet(buttonCb) {
|
async saveSubnet(buttonCb) {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
const name = this.value?.metadata?.name;
|
const name = this.value?.metadata?.name;
|
||||||
|
const hasEmptyAcls = this.value?.spec?.acls?.some((acl) => !acl.match || !acl.action || acl.priority === undefined || acl.priority === null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@ -153,6 +157,8 @@ export default {
|
|||||||
errors.push(this.t('validation.required', { key: this.t('harvester.subnet.provider.label') }, true));
|
errors.push(this.t('validation.required', { key: this.t('harvester.subnet.provider.label') }, true));
|
||||||
} else if (this.value.spec.excludeIps.includes('')) {
|
} else if (this.value.spec.excludeIps.includes('')) {
|
||||||
errors.push(this.t('harvester.validation.subnet.excludeIps'));
|
errors.push(this.t('harvester.validation.subnet.excludeIps'));
|
||||||
|
} else if (hasEmptyAcls) {
|
||||||
|
errors.push(this.t('harvester.validation.subnet.aclEmptyError'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
@ -371,6 +377,17 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
</ArrayList>
|
</ArrayList>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
name="ACL"
|
||||||
|
:label="t('harvester.subnet.acl.label')"
|
||||||
|
:weight="-2"
|
||||||
|
class="bordered-table"
|
||||||
|
>
|
||||||
|
<AccessControlList
|
||||||
|
v-model:value="value.spec.acls"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
</ResourceTabs>
|
</ResourceTabs>
|
||||||
</CruResource>
|
</CruResource>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -404,7 +404,7 @@ harvester:
|
|||||||
sha512: 'Invalid SHA512 checksum.'
|
sha512: 'Invalid SHA512 checksum.'
|
||||||
subnet:
|
subnet:
|
||||||
excludeIps: 'Exclude IPs cannot be empty. Please remove or fill in the exclude IPs.'
|
excludeIps: 'Exclude IPs cannot be empty. Please remove or fill in the exclude IPs.'
|
||||||
|
aclEmptyError: The fields in subnet access control list rule can not be empty.
|
||||||
dashboard:
|
dashboard:
|
||||||
label: Dashboard
|
label: Dashboard
|
||||||
header: "Harvester Cluster: {cluster}"
|
header: "Harvester Cluster: {cluster}"
|
||||||
@ -1062,7 +1062,24 @@ harvester:
|
|||||||
placeholder: e.g. 172.16.0.0/16
|
placeholder: e.g. 172.16.0.0/16
|
||||||
excludeIPs:
|
excludeIPs:
|
||||||
tooltip: The IP address list to reserve from automatic assignment. The gateway IP address is always excluded and will be automatically added to the list.
|
tooltip: The IP address list to reserve from automatic assignment. The gateway IP address is always excluded and will be automatically added to the list.
|
||||||
|
acl:
|
||||||
|
label: Access Control List
|
||||||
|
tooltip: The ACL to apply to this Subnet. Must be one of the ACLs in the same namespace.
|
||||||
|
action:
|
||||||
|
label: Action
|
||||||
|
placeholder: Please select an action
|
||||||
|
direction:
|
||||||
|
label: Direction
|
||||||
|
placeholder: Please select a direction
|
||||||
|
addRule: Add Rule
|
||||||
|
priority:
|
||||||
|
label: Priority
|
||||||
|
placeholder: Please select a priority
|
||||||
|
match:
|
||||||
|
label: Match
|
||||||
|
placeholder: e.g. ip4.dst == 10.10.0.2
|
||||||
|
banner: The supported field in ACL match can refer to <a href="https://kubeovn.github.io/docs/v1.14.x/en/guide/subnet/#subnet-acl" target="_blank">KubeOvn Subnet ACL document</a>
|
||||||
|
|
||||||
vpc:
|
vpc:
|
||||||
noAddonEnabled:
|
noAddonEnabled:
|
||||||
prefix: The kubeovn-operator add-on is not enabled, click
|
prefix: The kubeovn-operator add-on is not enabled, click
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user