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>
|
||||
<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-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-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.type }}</v-col>
|
||||
</v-row>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>Type</v-col>
|
||||
<v-col>{{ $t('in.tag') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.type }}
|
||||
{{ item.tag }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>Mode</v-col>
|
||||
<v-col>Format</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.mode }}
|
||||
{{ item.format }}
|
||||
</v-col>
|
||||
</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-col>{{ $t('objects.outbound') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
@@ -24,16 +102,41 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.rules') }}</v-col>
|
||||
<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-row>
|
||||
<v-row>
|
||||
<v-col>Invert</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.invert }}
|
||||
{{ $t( (item.invert?? false)? 'yes' : 'no') }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</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-col>
|
||||
</v-row>
|
||||
@@ -42,14 +145,21 @@
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
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 => {
|
||||
if (!appConfig || !('route' in appConfig)) {
|
||||
return []
|
||||
}
|
||||
return appConfig.route
|
||||
return appConfig.value.route
|
||||
})
|
||||
|
||||
const rules = computed((): any[] => {
|
||||
@@ -59,4 +169,92 @@ const rules = computed((): any[] => {
|
||||
}
|
||||
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>
|
||||
Reference in New Issue
Block a user