Rule and Ruleset modals
This commit is contained in:
@@ -0,0 +1,382 @@
|
|||||||
|
<template>
|
||||||
|
<v-card style="background-color: inherit;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" v-if="optionInbound">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.inbound"
|
||||||
|
:items="inTags"
|
||||||
|
:label="$t('pages.inbounds')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" v-if="optionClient">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.auth_user"
|
||||||
|
:items="clients"
|
||||||
|
:label="$t('pages.clients')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionIPver">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="IP Version"
|
||||||
|
:items="[4,6]"
|
||||||
|
v-model.number="rule.ip_version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="optionProtocol">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.protocol"
|
||||||
|
:items="['http','tls', 'quic', 'stun', 'dns']"
|
||||||
|
label="Protocols"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionDomain">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="domainKeys"
|
||||||
|
@update:model-value="updateDomainOption($event)"
|
||||||
|
v-model="domainOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Domains (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="domain"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain_suffix != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Domain Suffixes (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="domain_suffix"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain_keyword != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Domain Keywords (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="domain_keyword"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain_regex != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Domain Regexes (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="domain_regex"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.ip_cidr != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="IP CIDRs (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="ip_cidr"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.ip_is_private != undefined">
|
||||||
|
<v-switch v-model="rule.ip_is_private" color="primary" label="Private IP" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionPort">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="portKeys"
|
||||||
|
@update:model-value="updatePortOption($event)"
|
||||||
|
v-model="portOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.port != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Ports (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="port"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.port_range != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Port Ranges (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="port_range"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionSrcIP">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="srcIPKeys"
|
||||||
|
@update:model-value="updateSrcIPOption($event)"
|
||||||
|
v-model="srcIPOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_ip_cidr != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Source IP CIDRs (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="source_ip_cidr"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_ip_is_private != undefined">
|
||||||
|
<v-switch v-model="rule.source_ip_is_private" color="primary" label="Private Source IP" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionSrcPort">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="srcPortKeys"
|
||||||
|
@update:model-value="updateSrcPortOption($event)"
|
||||||
|
v-model="srcPortOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_port != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Source Ports (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="source_port"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_port_range != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Source Port Ranges (comma separated)"
|
||||||
|
hide-details
|
||||||
|
v-model="source_port_range"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionRuleSet">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.rule_set"
|
||||||
|
:items="rsTags"
|
||||||
|
label="Rulesets"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-switch v-model="rule.rule_set_ipcidr_match_source" color="primary" label="Ruleset IPcidr Match Source" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" hide-details variant="tonal">Rule Options</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionInbound" color="primary" :label="$t('pages.inbounds')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionClient" color="primary" :label="$t('pages.clients')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionIPver" color="primary" label="IP Version" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionProtocol" color="primary" label="Protocol" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionDomain" color="primary" label="Domain/IP" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionPort" color="primary" label="Port" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionSrcIP" color="primary" label="Source IP" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionSrcPort" color="primary" label="Source Port" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionRuleSet" color="primary" label="Rule Set" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
props: ['rule', 'clients', 'inTags', 'rsTags', 'deleteable'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
domainKeys: ['domain', 'domain_suffix', 'domain_keyword', 'domain_regex', 'ip_cidr', 'ip_is_private'],
|
||||||
|
portKeys: ['port', 'port_range'],
|
||||||
|
srcIPKeys: ['source_ip_cidr', 'source_ip_is_private'],
|
||||||
|
srcPortKeys: ['source_port', 'source_port_range'],
|
||||||
|
domainOption: 'domain',
|
||||||
|
portOption: 'port',
|
||||||
|
srcIPOption: 'source_ip_cidr',
|
||||||
|
srcPortOption: 'source_port',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateDomainOption(option:string) {
|
||||||
|
this.domainKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = option == 'ip_is_private' ? false : []
|
||||||
|
},
|
||||||
|
updatePortOption(option:string) {
|
||||||
|
this.portKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = []
|
||||||
|
},
|
||||||
|
updateSrcIPOption(option:string) {
|
||||||
|
this.srcIPKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = option == 'source_ip_is_private' ? false : []
|
||||||
|
},
|
||||||
|
updateSrcPortOption(option:string) {
|
||||||
|
this.srcPortKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
optionInbound: {
|
||||||
|
get() { return this.$props.rule.inbound != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.inbound = v ? [] : undefined }
|
||||||
|
},
|
||||||
|
optionClient: {
|
||||||
|
get() { return this.$props.rule.auth_user != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.auth_user = v ? [] : undefined }
|
||||||
|
},
|
||||||
|
optionIPver: {
|
||||||
|
get() { return this.$props.rule.ip_version != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.ip_version = v ? 4 : undefined }
|
||||||
|
},
|
||||||
|
optionProtocol: {
|
||||||
|
get() { return this.$props.rule.protocol != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.protocol = v ? ['http'] : undefined }
|
||||||
|
},
|
||||||
|
optionDomain: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.domainKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.domain = []
|
||||||
|
} else {
|
||||||
|
this.domainKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.domainOption = 'domain'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionPort: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.portKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.port = []
|
||||||
|
} else {
|
||||||
|
this.portKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.portOption = 'port'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionSrcIP: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.srcIPKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.source_ip_cidr = []
|
||||||
|
} else {
|
||||||
|
this.srcIPKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.srcIPOption = 'source_ip_cidr'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionSrcPort: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.srcPortKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.source_port = []
|
||||||
|
} else {
|
||||||
|
this.srcPortKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.srcPortOption = 'source_port'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionRuleSet: {
|
||||||
|
get() { return this.$props.rule.rule_set != undefined },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.rule_set = []
|
||||||
|
this.$props.rule.rule_set_ipcidr_match_source = false
|
||||||
|
} else {
|
||||||
|
delete this.$props.rule.rule_set
|
||||||
|
delete this.$props.rule.rule_set_ipcidr_match_source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
get() { return this.$props.rule.domain?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
domain_suffix: {
|
||||||
|
get() { return this.$props.rule.domain_suffix?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain_suffix = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
domain_keyword: {
|
||||||
|
get() { return this.$props.rule.domain_keyword?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain_keyword = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
domain_regex: {
|
||||||
|
get() { return this.$props.rule.domain_regex?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain_regex = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
ip_cidr: {
|
||||||
|
get() { return this.$props.rule.ip_cidr?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.ip_cidr = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
get() { return this.$props.rule.port?.join(',') },
|
||||||
|
set(v:string) {
|
||||||
|
if(!v.endsWith(',')) {
|
||||||
|
this.$props.rule.port = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
port_range: {
|
||||||
|
get() { return this.$props.rule.port_range?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.port_range = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
source_ip_cidr: {
|
||||||
|
get() { return this.$props.rule.source_ip_cidr?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.source_ip_cidr = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
source_port: {
|
||||||
|
get() { return this.$props.rule.source_port?.join(',') },
|
||||||
|
set(v:string) {
|
||||||
|
if(!v.endsWith(',')) {
|
||||||
|
this.$props.rule.source_port = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
source_port_range: {
|
||||||
|
get() { return this.$props.rule.source_port_range?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.source_port_range = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const ruleKeys = Object.keys(this.$props.rule)
|
||||||
|
if (this.optionDomain) {
|
||||||
|
const enabledOption = this.domainKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.domainOption = enabledOption.length>0 ? enabledOption[0] : 'domain'
|
||||||
|
}
|
||||||
|
if (this.optionPort) {
|
||||||
|
const enabledOption = this.portKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.portOption = enabledOption.length>0 ? enabledOption[0] : 'port'
|
||||||
|
}
|
||||||
|
if (this.optionSrcIP) {
|
||||||
|
const enabledOption = this.srcIPKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.srcIPOption = enabledOption.length>0 ? enabledOption[0] : 'source_ip_cidr'
|
||||||
|
}
|
||||||
|
if (this.optionSrcPort) {
|
||||||
|
const enabledOption = this.srcPortKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.srcPortOption = enabledOption.length>0 ? enabledOption[0] : 'source_port'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('actions.' + title) + " " + $t('objects.rule') }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text style="padding: 0 16px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" v-model="logical" label="Logical Rule" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto" v-if="logical" justify="center" align="center">
|
||||||
|
<v-btn color="primary" @click="ruleData.rules.push({})" hide-details>{{ $t('actions.add') + " " + $t('objects.rule') }}</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card style="background-color: inherit; margin-bottom: 5px;" v-for="(r, index) in ruleData.rules" v-if="ruleData.type == 'logical'">
|
||||||
|
<v-card-subtitle>{{ $t('objects.rule') + ' ' + (index+1) }}
|
||||||
|
<v-icon @click="ruleData.rules.splice(index,1)" icon="mdi-delete" v-if="ruleData.rules.length>1" />
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text style="padding: 0;">
|
||||||
|
<RuleOptions
|
||||||
|
:rule="r"
|
||||||
|
:clients="clients"
|
||||||
|
:inTags="inTags"
|
||||||
|
:rsTags="rsTags" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<RuleOptions
|
||||||
|
v-else
|
||||||
|
:rule="ruleData.rules[0]"
|
||||||
|
:clients="clients"
|
||||||
|
:inTags="inTags"
|
||||||
|
:rsTags="rsTags" />
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-combobox
|
||||||
|
v-model="ruleData.outbound"
|
||||||
|
:items="outTags"
|
||||||
|
:label="$t('objects.outbound')"
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="logical">
|
||||||
|
<v-combobox
|
||||||
|
v-model="ruleData.mode"
|
||||||
|
:items="['and', 'or']"
|
||||||
|
label="Mode"
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" v-model="ruleData.invert" label="Invert" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="outlined"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
{{ $t('actions.close') }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="tonal"
|
||||||
|
:loading="loading"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { logicalRule, rule } from '@/types/rules'
|
||||||
|
import RuleOptions from '@/components/Rule.vue'
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'data', 'index', 'clients', 'inTags', 'outTags', 'rsTags'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: 'add',
|
||||||
|
loading: false,
|
||||||
|
ruleData: <logicalRule>{
|
||||||
|
type: 'logical',
|
||||||
|
mode: 'and',
|
||||||
|
rules: <rule[]>[{}],
|
||||||
|
invert: false,
|
||||||
|
outbound: 'direct',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData() {
|
||||||
|
if (this.$props.index != -1) {
|
||||||
|
const newData = JSON.parse(this.$props.data)
|
||||||
|
if (newData.type) {
|
||||||
|
this.ruleData = newData
|
||||||
|
} else {
|
||||||
|
this.ruleData = <logicalRule>{
|
||||||
|
type: 'simple',
|
||||||
|
mode: 'and',
|
||||||
|
rules: <rule[]>[{...newData}],
|
||||||
|
invert: newData.invert,
|
||||||
|
outbound: newData.outbound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.title = 'edit'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.ruleData = <logicalRule>{
|
||||||
|
type: 'simple',
|
||||||
|
mode: 'and',
|
||||||
|
rules: <rule[]>[{}],
|
||||||
|
invert: false,
|
||||||
|
outbound: this.$props.outTags[0]?? 'direct',
|
||||||
|
}
|
||||||
|
this.title = 'add'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.updateData() // reset
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
if (this.ruleData.type == 'simple'){
|
||||||
|
this.ruleData.rules[0].outbound = this.ruleData.outbound
|
||||||
|
this.ruleData.rules[0].invert = this.ruleData.invert
|
||||||
|
}
|
||||||
|
this.$emit('save', this.ruleData)
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
deleteRule(index:number) {
|
||||||
|
this.ruleData.rules.splice(index,1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
logical: {
|
||||||
|
get() { return this.ruleData.type == 'logical' },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.ruleData.type = 'logical'
|
||||||
|
this.ruleData.outbound = this.ruleData.rules[0].outbound?? this.$props.outTags[0]
|
||||||
|
delete this.ruleData.rules[0].outbound
|
||||||
|
} else {
|
||||||
|
this.ruleData.type = 'simple'
|
||||||
|
this.ruleData.rules[0].outbound = this.ruleData.outbound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { RuleOptions }
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('actions.' + title) + " Ruleset" }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text style="padding: 0 16px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="Type"
|
||||||
|
:items="['local', 'remote']"
|
||||||
|
@update:model-value="updateType($event)"
|
||||||
|
v-model="rule_set.type">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="rule_set.tag" :label="$t('in.tag')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="Format"
|
||||||
|
:items="['source', 'binary']"
|
||||||
|
v-model="rule_set.format">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="rule_set.type == 'local'">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field v-model="rule_set.path" label="Path" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field v-model="rule_set.url" label="URL" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('objects.outbound')"
|
||||||
|
:items="outTags"
|
||||||
|
clearable
|
||||||
|
@click:clear="delete rule_set.download_detour"
|
||||||
|
v-model="rule_set.download_detour">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model.number="update_intervals" :suffix="$t('date.d')" type="number" min="0" label="Update Intervals" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="outlined"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
{{ $t('actions.close') }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="tonal"
|
||||||
|
:loading="loading"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
import { ruleset } from '@/types/rules'
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'data', 'index', 'outTags'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: "add",
|
||||||
|
loading: false,
|
||||||
|
rule_set: <ruleset>{},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData() {
|
||||||
|
if (this.$props.index != -1) {
|
||||||
|
this.title = "edit"
|
||||||
|
this.rule_set = <ruleset>JSON.parse(this.$props.data)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.title = "add"
|
||||||
|
this.rule_set = <ruleset>{type: 'local', tag: "rs-" + RandomUtil.randomSeq(3), format: 'binary'}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateType(t:string) {
|
||||||
|
if (t == 'local') {
|
||||||
|
delete this.rule_set.url
|
||||||
|
delete this.rule_set.download_detour
|
||||||
|
delete this.rule_set.update_interval
|
||||||
|
} else {
|
||||||
|
delete this.rule_set.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
this.$emit('save', this.rule_set)
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
update_intervals: {
|
||||||
|
get() { return this.rule_set.update_interval != undefined ? parseInt(this.rule_set.update_interval.replace('d','')) : 0 },
|
||||||
|
set(v:number) { this.rule_set.update_interval = v>0 ? v + 'd' : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
export interface logicalRule {
|
||||||
|
type: 'logical' | 'simple'
|
||||||
|
mode: 'and' | 'or'
|
||||||
|
rules: rule[]
|
||||||
|
invert: boolean
|
||||||
|
outbound: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface rule {
|
||||||
|
inbound?: string[]
|
||||||
|
ip_version?: 4 | 6
|
||||||
|
network?: string[]
|
||||||
|
auth_user?: string[]
|
||||||
|
protocol?: string[]
|
||||||
|
domain?: string[]
|
||||||
|
domain_suffix?: string[]
|
||||||
|
domain_keyword?: string[]
|
||||||
|
domain_regex?: string[]
|
||||||
|
source_ip_cidr?: string[]
|
||||||
|
source_ip_is_private?: boolean
|
||||||
|
ip_cidr?: string[]
|
||||||
|
ip_is_private?: boolean
|
||||||
|
source_port?: number[]
|
||||||
|
source_port_range?: string[]
|
||||||
|
port?: number[]
|
||||||
|
port_range?: string[]
|
||||||
|
process_name?: string[]
|
||||||
|
process_path?: string[]
|
||||||
|
package_name?: string[]
|
||||||
|
user?: string[]
|
||||||
|
user_id?: number[]
|
||||||
|
clash_mode?: string
|
||||||
|
rule_set?: string[]
|
||||||
|
rule_set_ipcidr_match_source?: boolean
|
||||||
|
invert?: boolean
|
||||||
|
outbound?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ruleset {
|
||||||
|
type: 'local' | 'remote'
|
||||||
|
tag: string
|
||||||
|
format: 'source' | 'binary'
|
||||||
|
path?: string
|
||||||
|
url?: string
|
||||||
|
download_detour?: string
|
||||||
|
update_interval?: string
|
||||||
|
}
|
||||||
+210
-12
@@ -1,20 +1,98 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<RuleVue
|
||||||
|
v-model="ruleModal.visible"
|
||||||
|
:visible="ruleModal.visible"
|
||||||
|
:index="ruleModal.index"
|
||||||
|
:data="ruleModal.data"
|
||||||
|
:clients="clients"
|
||||||
|
:inTags="inboundTags"
|
||||||
|
:outTags="outboundTags"
|
||||||
|
:rsTags="rulesetTags"
|
||||||
|
@close="closeRuleModal"
|
||||||
|
@save="saveRuleModal"
|
||||||
|
/>
|
||||||
|
<RulesetVue
|
||||||
|
v-model="rulesetModal.visible"
|
||||||
|
:visible="rulesetModal.visible"
|
||||||
|
:index="rulesetModal.index"
|
||||||
|
:data="rulesetModal.data"
|
||||||
|
:outTags="outboundTags"
|
||||||
|
@close="closeRulesetModal"
|
||||||
|
@save="saveRulesetModal"
|
||||||
|
/>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>rules" :key="item.name">
|
<v-col cols="12" justify="center" align="center">
|
||||||
|
<v-btn color="primary" @click="showRuleModal(-1)" style="margin: 0 5px;">{{ $t('actions.add') + " " + $t('objects.rule') }}</v-btn>
|
||||||
|
<v-btn color="primary" @click="showRulesetModal(-1)" style="margin: 0 5px;">{{ $t('actions.add') + " Ruleset" }}</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">Rulesets</v-col>
|
||||||
|
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>rulesets" :key="item.tag">
|
||||||
<v-card rounded="xl" elevation="5" min-width="200" :title="index">
|
<v-card rounded="xl" elevation="5" min-width="200" :title="index">
|
||||||
|
<v-card-subtitle style="margin-top: -20px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ item.type }}</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-subtitle>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>Type</v-col>
|
<v-col>{{ $t('in.tag') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col dir="ltr">
|
||||||
{{ item.type }}
|
{{ item.tag }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>Mode</v-col>
|
<v-col>Format</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col dir="ltr">
|
||||||
{{ item.mode }}
|
{{ item.format }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>Update</v-col>
|
||||||
|
<v-col dir="ltr">
|
||||||
|
{{ item.update_interval?? '-' }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-actions style="padding: 0;">
|
||||||
|
<v-btn icon="mdi-file-edit" @click="showRulesetModal(index)">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delRulesetOverlay[index] = true">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-overlay
|
||||||
|
v-model="delRulesetOverlay[index]"
|
||||||
|
contained
|
||||||
|
class="align-center justify-center"
|
||||||
|
>
|
||||||
|
<v-card :title="$t('actions.del')" rounded="lg">
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="error" variant="outlined" @click="delRuleset(index)">{{ $t('yes') }}</v-btn>
|
||||||
|
<v-btn color="success" variant="outlined" @click="delRulesetOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-overlay>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">{{ $t('pages.rules') }}</v-col>
|
||||||
|
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>rules">
|
||||||
|
<v-card rounded="xl" elevation="5" min-width="200" :title="index">
|
||||||
|
<v-card-subtitle style="margin-top: -20px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ item.type != undefined ? 'Logical (' + item.mode + ')' : 'Simple' }}</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('objects.outbound') }}</v-col>
|
<v-col>{{ $t('objects.outbound') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col dir="ltr">
|
||||||
@@ -24,16 +102,41 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('pages.rules') }}</v-col>
|
<v-col>{{ $t('pages.rules') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col dir="ltr">
|
||||||
{{ item.rules ? item.rules.length : 0 }}
|
{{ item.rules ? item.rules.length : Object.keys(item).filter(r => !["rule_set_ipcidr_match_source","invert","outbound"].includes(r)).length }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>Invert</v-col>
|
<v-col>Invert</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col dir="ltr">
|
||||||
{{ item.invert }}
|
{{ $t( (item.invert?? false)? 'yes' : 'no') }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-actions style="padding: 0;">
|
||||||
|
<v-btn icon="mdi-file-edit" @click="showRuleModal(index)">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delRuleOverlay[index] = true">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-overlay
|
||||||
|
v-model="delRuleOverlay[index]"
|
||||||
|
contained
|
||||||
|
class="align-center justify-center"
|
||||||
|
>
|
||||||
|
<v-card :title="$t('actions.del')" rounded="lg">
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="error" variant="outlined" @click="delRule(index)">{{ $t('yes') }}</v-btn>
|
||||||
|
<v-btn color="success" variant="outlined" @click="delRuleOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-overlay>
|
||||||
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -42,14 +145,21 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Data from '@/store/modules/data'
|
import Data from '@/store/modules/data'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import RuleVue from '@/layouts/modals/Rule.vue'
|
||||||
|
import RulesetVue from '@/layouts/modals/Ruleset.vue'
|
||||||
|
import { Config } from '@/types/config'
|
||||||
|
import { logicalRule, ruleset } from '@/types/rules'
|
||||||
|
|
||||||
const appConfig = Data().config
|
const appConfig = computed((): Config => {
|
||||||
|
return <Config> Data().config
|
||||||
|
})
|
||||||
|
|
||||||
|
const clients = computed((): string[] => {
|
||||||
|
return Data().clients.map((c:any) => c.name)
|
||||||
|
})
|
||||||
|
|
||||||
const route = computed((): any => {
|
const route = computed((): any => {
|
||||||
if (!appConfig || !('route' in appConfig)) {
|
return appConfig.value.route
|
||||||
return []
|
|
||||||
}
|
|
||||||
return appConfig.route
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = computed((): any[] => {
|
const rules = computed((): any[] => {
|
||||||
@@ -59,4 +169,92 @@ const rules = computed((): any[] => {
|
|||||||
}
|
}
|
||||||
return data.rules
|
return data.rules
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rulesets = computed((): any[] => {
|
||||||
|
const data = route.value
|
||||||
|
if (!route || !('rule_set' in data) || !Array.isArray(data.rule_set)){
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return data.rule_set
|
||||||
|
})
|
||||||
|
|
||||||
|
const rulesetTags = computed((): any[] => {
|
||||||
|
return rulesets.value.map((rs:any) => rs.tag)
|
||||||
|
})
|
||||||
|
|
||||||
|
const outboundTags = computed((): string[] => {
|
||||||
|
return appConfig.value.outbounds.map((o:any) => o.tag)
|
||||||
|
})
|
||||||
|
|
||||||
|
const inboundTags = computed((): string[] => {
|
||||||
|
return appConfig.value.inbounds.map((i:any) => i.tag)
|
||||||
|
})
|
||||||
|
|
||||||
|
let delRuleOverlay = ref(new Array<boolean>)
|
||||||
|
let delRulesetOverlay = ref(new Array<boolean>)
|
||||||
|
|
||||||
|
const ruleModal = ref({
|
||||||
|
visible: false,
|
||||||
|
index: -1,
|
||||||
|
data: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const showRuleModal = (index: number) => {
|
||||||
|
ruleModal.value.index = index
|
||||||
|
ruleModal.value.data = index == -1 ? '' : JSON.stringify(rules.value[index])
|
||||||
|
ruleModal.value.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeRuleModal = () => {
|
||||||
|
ruleModal.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveRuleModal = (data:logicalRule) => {
|
||||||
|
// Logical or simple
|
||||||
|
const ruleData = data.type == 'logical' ? data : data.rules[0]
|
||||||
|
|
||||||
|
// New or Edit
|
||||||
|
if (ruleModal.value.index == -1) {
|
||||||
|
rules.value.push(ruleData)
|
||||||
|
} else {
|
||||||
|
rules.value[ruleModal.value.index] = ruleData
|
||||||
|
}
|
||||||
|
ruleModal.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const delRule = (index: number) => {
|
||||||
|
rules.value.splice(index,1)
|
||||||
|
delRuleOverlay.value[index] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const rulesetModal = ref({
|
||||||
|
visible: false,
|
||||||
|
index: -1,
|
||||||
|
data: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const showRulesetModal = (index: number) => {
|
||||||
|
rulesetModal.value.index = index
|
||||||
|
rulesetModal.value.data = index == -1 ? '' : JSON.stringify(rulesets.value[index])
|
||||||
|
rulesetModal.value.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeRulesetModal = () => {
|
||||||
|
rulesetModal.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveRulesetModal = (data:ruleset) => {
|
||||||
|
// New or Edit
|
||||||
|
if (rulesetModal.value.index == -1) {
|
||||||
|
rulesets.value.push(data)
|
||||||
|
} else {
|
||||||
|
rulesets.value[rulesetModal.value.index] = data
|
||||||
|
}
|
||||||
|
rulesetModal.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const delRuleset = (index: number) => {
|
||||||
|
rulesets.value.splice(index,1)
|
||||||
|
delRulesetOverlay.value[index] = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
Reference in New Issue
Block a user