Files
Sub-Store/web/src/views/SubEditor.vue
2020-09-01 20:24:32 +08:00

405 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<v-container>
<v-card class="mb-4">
<v-subheader>基本信息</v-subheader>
<v-form class="pl-4 pr-4 pb-0" v-model="formState.basicValid">
<v-text-field
v-model="options.name"
class="mt-2"
:rules="validations.nameRules"
required
label="订阅名称"
placeholder="填入订阅名称,名称需唯一"
/>
<v-textarea
v-model="options.url"
class="mt-2"
rows="2"
:rules="validations.urlRules"
required
label="订阅链接"
placeholder="填入机场原始订阅链接"
/>
</v-form>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn icon @click="save">
<v-icon>save_alt</v-icon>
</v-btn>
<v-btn icon @click="discard">
<v-icon>settings_backup_restore</v-icon>
</v-btn>
</v-card-actions>
</v-card>
<v-card class="mb-4">
<v-subheader>常用选项</v-subheader>
<v-form class="pl-4 pr-4">
<v-item-group>
<v-radio-group
v-model="options.useless"
dense
class="mt-0 mb-0"
>
过滤非法节点
<v-row>
<v-col>
<v-radio label="保留" value="KEEP"/>
</v-col>
<v-col>
<v-radio label="删除" value="REMOVE"/>
</v-col>
<v-col></v-col>
</v-row>
</v-radio-group>
<v-radio-group
v-model="options.flag"
dense
class="mt-0 mb-0"
>
国旗
<v-row>
<v-col>
<v-radio label="默认" value="DEFAULT"/>
</v-col>
<v-col>
<v-radio label="添加国旗" value="ADD"/>
</v-col>
<v-col>
<v-radio label="删除国旗" value="REMOVE"/>
</v-col>
</v-row>
</v-radio-group>
<v-radio-group
v-model="options.udp"
dense
class="mt-0 mb-0"
>
UDP转发
<v-row>
<v-col>
<v-radio label="默认" value="DEFAULT"/>
</v-col>
<v-col>
<v-radio label="强制开启" value="FORCE_OPEN"/>
</v-col>
<v-col>
<v-radio label="强制关闭" value="FORCE_CLOSE"/>
</v-col>
</v-row>
</v-radio-group>
<v-radio-group
v-model="options.scert"
dense
class="mt-0 mb-0"
>
跳过证书验证
<v-row>
<v-col>
<v-radio label="默认" value="DEFAULT"/>
</v-col>
<v-col>
<v-radio label="强制跳过" value="FORCE_OPEN"/>
</v-col>
<v-col>
<v-radio label="强制验证" value="FORCE_CLOSE"/>
</v-col>
</v-row>
</v-radio-group>
<v-radio-group
v-model="options.tfo"
dense
class="mt-0 mb-0"
>
TCP Fast Open
<v-row>
<v-col>
<v-radio label="默认" value="DEFAULT"/>
</v-col>
<v-col>
<v-radio label="强制开启" value="FORCE_OPEN"/>
</v-col>
<v-col>
<v-radio label="强制关闭" value="FORCE_CLOSE"/>
</v-col>
</v-row>
</v-radio-group>
</v-item-group>
</v-form>
</v-card>
<v-card class="mb-4" id="processors">
<v-subheader>
节点操作
<v-spacer></v-spacer>
<v-dialog scrollable v-model="dialog">
<template #activator="{on}">
<v-btn icon v-on="on">
<v-icon color="primary">add_circle</v-icon>
</v-btn>
</template>
<v-card>
<v-card-title>选择节点操作</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-radio-group dense v-model="selectedProcess">
<v-radio v-for="(k, idx) in Object.keys(availableProcessors)" :label="availableProcessors[k].name"
:key="idx" :value="k"></v-radio>
</v-radio-group>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="addProcess(selectedProcess)">确认</v-btn>
<v-btn text @click="dialog = false">取消</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-subheader>
<v-divider></v-divider>
<component v-for="(p, idx) in processors"
:is="p.component"
:key="idx"
:args="p.args"
@dataChanged="dataChanged"
@deleteProcess="deleteProcess"
@up="moveUp"
@down="moveDown"
>
</component>
</v-card>
</v-container>
</template>
<script>
import {showError, showInfo} from "@/utils";
import TypeFilter from "@/components/TypeFilter";
import RegionFilter from "@/components/RegionFilter";
import KeywordFilter from "@/components/KeywordFilter";
import RegexFilter from "@/components/RegexFilter";
import SortOperator from "@/components/SortOperator";
import KeywordRenameOperator from "@/components/KeywordRenameOperator";
import RegexRenameOperator from "@/components/RegexRenameOperator";
import KeywordDeleteOperator from "@/components/KeywordDeleteOperator";
import RegexDeleteOperator from "@/components/RegexDeleteOperator";
const AVAILABLE_PROCESSORS = {
"Type Filter": {
component: "TypeFilter",
name: "类型过滤器"
},
"Region Filter": {
component: "RegionFilter",
name: "区域过滤器"
},
"Keyword Filter": {
component: "KeywordFilter",
name: "关键词过滤器"
},
"Regex Filter": {
component: "RegexFilter",
name: "正则过滤器"
},
"Sort Operator": {
component: "SortOperator",
name: "节点排序"
},
"Keyword Rename Operator": {
component: "KeywordRenameOperator",
name: "关键词重命名"
},
"Regex Rename Operator": {
component: "RegexRenameOperator",
name: "正则重命名"
},
"Keyword Delete Operator": {
component: "KeywordDeleteOperator",
name: "删除关键词"
},
"Regex Delete Operator": {
component: "RegexDeleteOperator",
name: "删除正则"
},
}
export default {
components: {
KeywordFilter,
RegexFilter,
RegionFilter,
TypeFilter,
SortOperator,
KeywordRenameOperator,
RegexRenameOperator,
KeywordDeleteOperator,
RegexDeleteOperator
},
data: function () {
return {
selectedProcess: null,
dialog: false,
validations: {
nameRules: [
v => !!v || "订阅名称不能为空!",
v => /^[\w-_]*$/.test(v) || "订阅名称只能包含英文字符、横杠和下划线!"
],
urlRules: [
v => !!v || "订阅链接不能为空!",
v => /^https?:\/\//.test(v) || "订阅链接不合法!"
]
},
formState: {
basicValid: false
},
options: {
name: "",
url: "",
useless: "KEEP",
udp: "DEFAULT",
flag: "DEFAULT",
scert: "DEFAULT",
tfo: "DEFAULT",
process: [],
}
}
},
created() {
const name = this.$route.params.name;
const sub = (typeof name === 'undefined' || name === 'UNTITLED') ? {} : this.$store.state.subscriptions[name];
this.$store.commit("SET_NAV_TITLE", sub.name ? `订阅编辑 -- ${sub.name}` : "新建订阅");
this.options = loadSubscription(this.options, sub);
},
computed: {
availableProcessors() {
return AVAILABLE_PROCESSORS;
},
processors() {
return this.options.process.map(p => {
return {
component: AVAILABLE_PROCESSORS[p.type].component,
args: p.args
}
});
}
},
methods: {
save() {
if (this.options.name && this.options.url) {
const sub = buildSubscription(this.options);
if (this.$route.params.name !== "UNTITLED") {
this.$store.dispatch("UPDATE_SUBSCRIPTION", {
name: this.$route.params.name,
sub
}).then(() => {
showInfo(`成功保存订阅:${this.options.name}`)
}).catch(() => {
showError(`发生错误,无法保存订阅!`)
});
} else {
this.$store.dispatch("NEW_SUBSCRIPTION", sub).then(() => {
showInfo(`成功创建订阅:${this.options.name}`)
}).catch(() => {
showError(`发生错误,无法创建订阅!`)
});
}
}
},
discard() {
this.$router.back();
},
dataChanged(content) {
this.options.process[content.idx].args = content.args;
},
addProcess(type) {
this.options.process.push({type});
this.dialog = false;
},
deleteProcess(key) {
this.options.process.splice(key, 1);
},
moveUp(index) {
if (index === 0) return;
// otherwise swap with previous one
this.options.process.splice(index - 1, 2, this.options.process[index], this.options.process[index - 1]);
},
moveDown(index) {
if (index === this.options.process.length) return;
// otherwise swap with latter one
this.options.process.splice(index, 2, this.options.process[index + 1], this.options.process[index]);
}
}
}
function loadSubscription(options, sub) {
options = {
...options,
name: sub.name,
url: sub.url,
process: []
}
// flag
for (const p of (sub.process || [])) {
switch (p.type) {
case 'Useless Filter':
options.useless = "REMOVE";
break
case 'Flag Operator':
options.flag = p.args[0] ? "ADD" : "REMOVE";
break
case 'Set Property Operator':
options[p.args[0]] = p.args[1] ? "FORCE_OPEN" : "FORCE_CLOSE";
break
default:
options.process.push(p);
}
}
return options;
}
function buildSubscription(options) {
const sub = {
name: options.name,
url: options.url,
process: []
};
// useless filter
if (options.useless === 'REMOVE') {
sub.process.push({
type: "Useless Filter"
});
}
// flag
if (options.flag !== 'DEFAULT') {
sub.process.push({
type: "Flag Operator",
args: options.flag === 'ADD'
});
}
// udp, tfo, scert
for (const opt of ['udp', 'tfo', 'scert']) {
if (options[opt] !== 'DEFAULT') {
sub.process.push({
type: "Set Property Operator",
args: {key: opt, value: options[opt] === 'FORCE_OPEN'}
});
}
}
for (const p of options.process) {
sub.process.push(p);
}
return sub;
}
</script>
<style>
.v-label {
font-size: small;
}
</style>