mirror of
https://github.com/h44z/wg-portal.git
synced 2025-06-28 01:07:03 +00:00
change tagged-input-field component, allow to paste multiple values (#365)
This commit is contained in:
parent
6681dfa96f
commit
1d94f6baaf
54
frontend/package-lock.json
generated
54
frontend/package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@kyvg/vue3-notification": "^3.4.1",
|
"@kyvg/vue3-notification": "^3.4.1",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"@vojtechlanka/vue-tags-input": "^3.1.1",
|
||||||
"bootstrap": "^5.3.5",
|
"bootstrap": "^5.3.5",
|
||||||
"bootswatch": "^5.3.5",
|
"bootswatch": "^5.3.5",
|
||||||
"flag-icons": "^7.3.2",
|
"flag-icons": "^7.3.2",
|
||||||
@ -23,8 +24,7 @@
|
|||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-i18n": "^11.1.3",
|
"vue-i18n": "^11.1.3",
|
||||||
"vue-prism-component": "github:h44z/vue-prism-component",
|
"vue-prism-component": "github:h44z/vue-prism-component",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0"
|
||||||
"vue3-tags-input": "^1.0.12"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
@ -884,6 +884,20 @@
|
|||||||
"vue": "^3.2.25"
|
"vue": "^3.2.25"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vojtechlanka/vue-tags-input": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vojtechlanka/vue-tags-input/-/vue-tags-input-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-GdREECH+k2pQCKdbHHh4/IxRXje3QQ8rXzXd9/6L1kzGYXqHlG1tbRoi1qC7enph67/g2nvGaZfpqLuuW+CX3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"vue": "3.x",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "3.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.5.13",
|
"version": "3.5.13",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
|
||||||
@ -1070,15 +1084,6 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/click-outside-vue3": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/click-outside-vue3/-/click-outside-vue3-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-sbplNecrup5oGqA3o4bo8XmvHRT6q9fvw21Z67aDbTqB9M6LF7CuYLTlLvNtOgKU6W3zst5H5zJuEh4auqA34g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/clone-regexp": {
|
"node_modules/clone-regexp": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz",
|
||||||
@ -1193,6 +1198,12 @@
|
|||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-deep-equal": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.4.4",
|
"version": "6.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
||||||
@ -1893,6 +1904,12 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sortablejs": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@ -2173,19 +2190,16 @@
|
|||||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vue3-tags-input": {
|
"node_modules/vuedraggable": {
|
||||||
"version": "1.0.12",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue3-tags-input/-/vue3-tags-input-1.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
|
||||||
"integrity": "sha512-s5rG+1W3M8+be0nd9H1nv/8WLjJOO6pShgVz8ALAqOiz3tDH5QhGrDH6fzD14ZjJNRWSa3bRBSXQwHEXffPQ6g==",
|
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"click-outside-vue3": "^4.0.1"
|
"sortablejs": "1.14.0"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.0.5"
|
"vue": "^3.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@kyvg/vue3-notification": "^3.4.1",
|
"@kyvg/vue3-notification": "^3.4.1",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"@vojtechlanka/vue-tags-input": "^3.1.1",
|
||||||
"bootstrap": "^5.3.5",
|
"bootstrap": "^5.3.5",
|
||||||
"bootswatch": "^5.3.5",
|
"bootswatch": "^5.3.5",
|
||||||
"flag-icons": "^7.3.2",
|
"flag-icons": "^7.3.2",
|
||||||
@ -23,8 +24,7 @@
|
|||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-i18n": "^11.1.3",
|
"vue-i18n": "^11.1.3",
|
||||||
"vue-prism-component": "github:h44z/vue-prism-component",
|
"vue-prism-component": "github:h44z/vue-prism-component",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0"
|
||||||
"vue3-tags-input": "^1.0.12"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
|
@ -15,3 +15,85 @@ a.disabled {
|
|||||||
.desc::after {
|
.desc::after {
|
||||||
content: " ↓";
|
content: " ↓";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* style the background and the text color of the input ... */
|
||||||
|
.vue-tags-input {
|
||||||
|
max-width: 100% !important;
|
||||||
|
background-color: #f7f7f9 !important;
|
||||||
|
padding: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-tags-input .ti-input {
|
||||||
|
padding: 0 0;
|
||||||
|
border: none !important;
|
||||||
|
transition: border-bottom 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-tags-input .ti-new-tag-input {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
padding: 0.75rem 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* style the placeholders color across all browser */
|
||||||
|
.vue-tags-input ::-webkit-input-placeholder {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
.vue-tags-input .ti-input::placeholder {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-tags-input ::-moz-placeholder {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-tags-input :-ms-input-placeholder {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-tags-input :-moz-placeholder {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* default styles for all the tags */
|
||||||
|
.vue-tags-input .ti-tag {
|
||||||
|
position: relative;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2px solid var(--bs-body-color);
|
||||||
|
margin: 6px;
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the styles if a tag is invalid */
|
||||||
|
.vue-tags-input .ti-tag.ti-invalid {
|
||||||
|
background-color: #e88a74;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the user input is invalid, the input color should be red */
|
||||||
|
.vue-tags-input .ti-new-tag-input.ti-invalid {
|
||||||
|
color: #e88a74;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if a tag or the user input is a duplicate, it should be crossed out */
|
||||||
|
.vue-tags-input .ti-duplicate span,
|
||||||
|
.vue-tags-input .ti-new-tag-input.ti-duplicate {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the user presses backspace, the complete tag should be crossed out, to mark it for deletion */
|
||||||
|
.vue-tags-input .ti-tag:after {
|
||||||
|
transition: transform .2s;
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 2px;
|
||||||
|
width: 108%;
|
||||||
|
left: -4%;
|
||||||
|
top: calc(50% - 1px);
|
||||||
|
background-color: #000;
|
||||||
|
transform: scaleX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-tags-input .ti-deletion-mark:after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
@ -4,7 +4,7 @@ import {interfaceStore} from "@/stores/interfaces";
|
|||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
import Vue3TagsInput from 'vue3-tags-input';
|
import { VueTagsInput } from '@vojtechlanka/vue-tags-input';
|
||||||
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||||
import isCidr from "is-cidr";
|
import isCidr from "is-cidr";
|
||||||
import {isIP} from 'is-ip';
|
import {isIP} from 'is-ip';
|
||||||
@ -38,6 +38,15 @@ const title = computed(() => {
|
|||||||
return t("modals.interface-edit.headline-new")
|
return t("modals.interface-edit.headline-new")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const currentTags = ref({
|
||||||
|
Addresses: "",
|
||||||
|
Dns: "",
|
||||||
|
DnsSearch: "",
|
||||||
|
PeerDefNetwork: "",
|
||||||
|
PeerDefAllowedIPs: "",
|
||||||
|
PeerDefDns: "",
|
||||||
|
PeerDefDnsSearch: ""
|
||||||
|
})
|
||||||
const formData = ref(freshInterface())
|
const formData = ref(freshInterface())
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
@ -137,94 +146,94 @@ function close() {
|
|||||||
function handleChangeAddresses(tags) {
|
function handleChangeAddresses(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(isCidr(tag) === 0) {
|
if(isCidr(tag.text) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if(validInput) {
|
||||||
formData.value.Addresses = tags
|
formData.value.Addresses = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeDns(tags) {
|
function handleChangeDns(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(!isIP(tag)) {
|
if(!isIP(tag.text)) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid IP",
|
title: "Invalid IP",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if(validInput) {
|
||||||
formData.value.Dns = tags
|
formData.value.Dns = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeDnsSearch(tags) {
|
function handleChangeDnsSearch(tags) {
|
||||||
formData.value.DnsSearch = tags
|
formData.value.DnsSearch = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangePeerDefNetwork(tags) {
|
function handleChangePeerDefNetwork(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(isCidr(tag) === 0) {
|
if(isCidr(tag.text) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if(validInput) {
|
||||||
formData.value.PeerDefNetwork = tags
|
formData.value.PeerDefNetwork = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangePeerDefAllowedIPs(tags) {
|
function handleChangePeerDefAllowedIPs(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(isCidr(tag) === 0) {
|
if(isCidr(tag.text) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if(validInput) {
|
||||||
formData.value.PeerDefAllowedIPs = tags
|
formData.value.PeerDefAllowedIPs = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangePeerDefDns(tags) {
|
function handleChangePeerDefDns(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(!isIP(tag)) {
|
if(!isIP(tag.text)) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid IP",
|
title: "Invalid IP",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if(validInput) {
|
||||||
formData.value.PeerDefDns = tags
|
formData.value.PeerDefDns = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangePeerDefDnsSearch(tags) {
|
function handleChangePeerDefDnsSearch(tags) {
|
||||||
formData.value.PeerDefDnsSearch = tags
|
formData.value.PeerDefDnsSearch = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
@ -333,11 +342,15 @@ async function del() {
|
|||||||
<legend class="mt-4">{{ $t('modals.interface-edit.header-network') }}</legend>
|
<legend class="mt-4">{{ $t('modals.interface-edit.header-network') }}</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
<vue-tags-input class="form-control" v-model="currentTags.Addresses"
|
||||||
|
:tags="formData.Addresses.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.interface-edit.ip.placeholder')"
|
:placeholder="$t('modals.interface-edit.ip.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validation="validateCIDR()"
|
||||||
:validate="validateCIDR"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangeAddresses"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeAddresses"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="formData.Mode==='server'" class="form-group">
|
<div v-if="formData.Mode==='server'" class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.listen-port.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.listen-port.label') }}</label>
|
||||||
@ -345,19 +358,27 @@ async function del() {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="formData.Mode!=='server'" class="form-group">
|
<div v-if="formData.Mode!=='server'" class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.Dns"
|
<vue-tags-input class="form-control" v-model="currentTags.Dns"
|
||||||
|
:tags="formData.Dns.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validation="validateIP()"
|
||||||
:validate="validateIP"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangeDns"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeDns"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="formData.Mode!=='server'" class="form-group">
|
<div v-if="formData.Mode!=='server'" class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns-search.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns-search.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.DnsSearch"
|
<vue-tags-input class="form-control" v-model="currentTags.DnsSearch"
|
||||||
|
:tags="formData.DnsSearch.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validation="validateDomain()"
|
||||||
:validate="validateDomain"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangeDnsSearch"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeDnsSearch"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
@ -420,36 +441,52 @@ async function del() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.networks.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.networks.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefNetwork"
|
<vue-tags-input class="form-control" v-model="currentTags.PeerDefNetwork"
|
||||||
|
:tags="formData.PeerDefNetwork.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.interface-edit.defaults.networks.placeholder')"
|
:placeholder="$t('modals.interface-edit.defaults.networks.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validation="validateCIDR()"
|
||||||
:validate="validateCIDR"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangePeerDefNetwork"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangePeerDefNetwork"/>
|
||||||
<small class="form-text text-muted">{{ $t('modals.interface-edit.defaults.networks.description') }}</small>
|
<small class="form-text text-muted">{{ $t('modals.interface-edit.defaults.networks.description') }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.allowed-ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.allowed-ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefAllowedIPs"
|
<vue-tags-input class="form-control" v-model="currentTags.PeerDefAllowedIPs"
|
||||||
|
:tags="formData.PeerDefAllowedIPs.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.interface-edit.defaults.allowed-ip.placeholder')"
|
:placeholder="$t('modals.interface-edit.defaults.allowed-ip.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validation="validateCIDR()"
|
||||||
:validate="validateCIDR"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangePeerDefAllowedIPs"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangePeerDefAllowedIPs"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefDns"
|
<vue-tags-input class="form-control" v-model="currentTags.PeerDefDns"
|
||||||
|
:tags="formData.PeerDefDns.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validation="validateIP()"
|
||||||
:validate="validateIP"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangePeerDefDns"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangePeerDefDns"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns-search.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns-search.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefDnsSearch"
|
<vue-tags-input class="form-control" v-model="currentTags.PeerDefDnsSearch"
|
||||||
|
:tags="formData.PeerDefDnsSearch.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validation="validateDomain()"
|
||||||
:validate="validateDomain"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangePeerDefDnsSearch"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangePeerDefDnsSearch"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
|
@ -5,7 +5,7 @@ import { interfaceStore } from "@/stores/interfaces";
|
|||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
import Vue3TagsInput from "vue3-tags-input";
|
import { VueTagsInput } from '@vojtechlanka/vue-tags-input';
|
||||||
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||||
import isCidr from "is-cidr";
|
import isCidr from "is-cidr";
|
||||||
import { isIP } from 'is-ip';
|
import { isIP } from 'is-ip';
|
||||||
@ -65,6 +65,13 @@ const title = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const currentTags = ref({
|
||||||
|
Addresses: "",
|
||||||
|
AllowedIPs: "",
|
||||||
|
ExtraAllowedIPs: "",
|
||||||
|
Dns: "",
|
||||||
|
DnsSearch: ""
|
||||||
|
})
|
||||||
const formData = ref(freshPeer())
|
const formData = ref(freshPeer())
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
@ -193,73 +200,73 @@ function close() {
|
|||||||
function handleChangeAddresses(tags) {
|
function handleChangeAddresses(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if (isCidr(tag) === 0) {
|
if (isCidr(tag.text) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (validInput) {
|
if (validInput) {
|
||||||
formData.value.Addresses = tags
|
formData.value.Addresses = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeAllowedIPs(tags) {
|
function handleChangeAllowedIPs(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if (isCidr(tag) === 0) {
|
if (isCidr(tag.text) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (validInput) {
|
if (validInput) {
|
||||||
formData.value.AllowedIPs.Value = tags
|
formData.value.AllowedIPs.Value = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeExtraAllowedIPs(tags) {
|
function handleChangeExtraAllowedIPs(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if (isCidr(tag) === 0) {
|
if (isCidr(tag.text) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (validInput) {
|
if (validInput) {
|
||||||
formData.value.ExtraAllowedIPs = tags
|
formData.value.ExtraAllowedIPs = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeDns(tags) {
|
function handleChangeDns(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if (!isIP(tag)) {
|
if (!isIP(tag.text)) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid IP",
|
title: "Invalid IP",
|
||||||
text: tag + " is not a valid IP address",
|
text: tag.text + " is not a valid IP address",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (validInput) {
|
if (validInput) {
|
||||||
formData.value.Dns.Value = tags
|
formData.value.Dns.Value = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeDnsSearch(tags) {
|
function handleChangeDnsSearch(tags) {
|
||||||
formData.value.DnsSearch.Value = tags
|
formData.value.DnsSearch.Value = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
@ -344,34 +351,64 @@ async function del() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
<vue-tags-input class="form-control" v-model="currentTags.Addresses"
|
||||||
:placeholder="$t('modals.peer-edit.ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
:tags="formData.Addresses.map(str => ({ text: str }))"
|
||||||
:validate="validateCIDR" @on-tags-changed="handleChangeAddresses" />
|
:placeholder="$t('modals.peer-edit.ip.placeholder')"
|
||||||
|
:validation="validateCIDR()"
|
||||||
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeAddresses" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.allowed-ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.allowed-ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.AllowedIPs.Value"
|
<vue-tags-input class="form-control" v-model="currentTags.AllowedIPs"
|
||||||
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
:tags="formData.AllowedIPs.Value.map(str => ({ text: str }))"
|
||||||
:validate="validateCIDR" @on-tags-changed="handleChangeAllowedIPs" />
|
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')"
|
||||||
|
:validation="validateCIDR()"
|
||||||
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeAllowedIPs" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.extra-allowed-ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.extra-allowed-ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.ExtraAllowedIPs"
|
<vue-tags-input class="form-control" v-model="currentTags.ExtraAllowedIPs"
|
||||||
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
:tags="formData.ExtraAllowedIPs.map(str => ({ text: str }))"
|
||||||
:validate="validateCIDR" @on-tags-changed="handleChangeExtraAllowedIPs" />
|
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')"
|
||||||
|
:validation="validateCIDR()"
|
||||||
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeExtraAllowedIPs" />
|
||||||
<small class="form-text text-muted">{{ $t('modals.peer-edit.extra-allowed-ip.description') }}</small>
|
<small class="form-text text-muted">{{ $t('modals.peer-edit.extra-allowed-ip.description') }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.Dns.Value"
|
<vue-tags-input class="form-control" v-model="currentTags.Dns"
|
||||||
:placeholder="$t('modals.peer-edit.dns.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
:tags="formData.Dns.Value.map(str => ({ text: str }))"
|
||||||
:validate="validateIP" @on-tags-changed="handleChangeDns" />
|
:placeholder="$t('modals.peer-edit.dns.placeholder')"
|
||||||
|
:validation="validateIP()"
|
||||||
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeDns" />
|
||||||
</div>
|
</div>
|
||||||
<div hidden class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns-search.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns-search.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.DnsSearch.Value"
|
<vue-tags-input class="form-control" v-model="currentTags.DnsSearch"
|
||||||
:placeholder="$t('modals.peer-edit.dns-search.label')" :add-tag-on-keys="[13, 188, 32, 9]"
|
:tags="formData.DnsSearch.Value.map(str => ({ text: str }))"
|
||||||
:validate="validateDomain" @on-tags-changed="handleChangeDnsSearch" />
|
:placeholder="$t('modals.peer-edit.dns-search.label')"
|
||||||
|
:validation="validateDomain()"
|
||||||
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeDnsSearch" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
|
@ -5,7 +5,7 @@ import {interfaceStore} from "@/stores/interfaces";
|
|||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
import Vue3TagsInput from "vue3-tags-input";
|
import { VueTagsInput } from '@vojtechlanka/vue-tags-input';
|
||||||
import { freshInterface } from '@/helpers/models';
|
import { freshInterface } from '@/helpers/models';
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -36,6 +36,7 @@ function freshForm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentTag = ref("")
|
||||||
const formData = ref(freshForm())
|
const formData = ref(freshForm())
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
@ -55,7 +56,7 @@ function close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeUserIdentifiers(tags) {
|
function handleChangeUserIdentifiers(tags) {
|
||||||
formData.value.Identifiers = tags
|
formData.value.Identifiers = tags.map(tag => tag.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
@ -89,10 +90,14 @@ async function save() {
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-multi-create.identifiers.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-multi-create.identifiers.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.Identifiers"
|
<vue-tags-input class="form-control" v-model="currentTag"
|
||||||
|
:tags="formData.Identifiers.map(str => ({ text: str }))"
|
||||||
:placeholder="$t('modals.peer-multi-create.identifiers.placeholder')"
|
:placeholder="$t('modals.peer-multi-create.identifiers.placeholder')"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:add-on-key="[13, 188, 32, 9]"
|
||||||
@on-tags-changed="handleChangeUserIdentifiers"/>
|
:save-on-key="[13, 188, 32, 9]"
|
||||||
|
:allow-edit-tags="true"
|
||||||
|
:separators="[',', ';', ' ']"
|
||||||
|
@tags-changed="handleChangeUserIdentifiers"/>
|
||||||
<small class="form-text text-muted">{{ $t('modals.peer-multi-create.identifiers.description') }}</small>
|
<small class="form-text text-muted">{{ $t('modals.peer-multi-create.identifiers.description') }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -1,14 +1,26 @@
|
|||||||
import isCidr from "is-cidr";
|
import isCidr from "is-cidr";
|
||||||
import {isIP} from 'is-ip';
|
import {isIP} from 'is-ip';
|
||||||
|
|
||||||
export function validateCIDR(value) {
|
export function validateCIDR() {
|
||||||
return isCidr(value) !== 0
|
return [{
|
||||||
|
classes: 'invalid-cidr',
|
||||||
|
rule: ({ text }) => isCidr(text) === 0,
|
||||||
|
disableAdd: true,
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateIP(value) {
|
export function validateIP() {
|
||||||
return isIP(value)
|
return [{
|
||||||
|
classes: 'invalid-ip',
|
||||||
|
rule: ({ text }) => !isIP(text),
|
||||||
|
disableAdd: true,
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateDomain(value) {
|
export function validateDomain() {
|
||||||
return true
|
return [{
|
||||||
|
classes: 'invalid-domain',
|
||||||
|
rule: tag => tag.text.length < 3,
|
||||||
|
disableAdd: true,
|
||||||
|
}]
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user