all adjustments
This commit is contained in:
Generated
+360
-350
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@
|
||||
:label="$t('in.port')"
|
||||
hide-details
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
required
|
||||
v-model.number="inbound.listen_port"></v-text-field>
|
||||
</v-col>
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
hide-details
|
||||
:items="['4','4a','5']"
|
||||
:label="$t('version')"
|
||||
v-model="inData.outJson.version">
|
||||
v-model="inData.out_json.version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="needNetwork">
|
||||
<Network :data="inData.outJson" />
|
||||
<Network :data="inData.out_json" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="needUot">
|
||||
<UoT :data="inData.outJson" />
|
||||
<UoT :data="inData.out_json" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.HTTP">
|
||||
<v-text-field
|
||||
:label="$t('transport.path')"
|
||||
hide-details
|
||||
v-model="inData.outJson.path">
|
||||
v-model="inData.out_json.path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.VMess || type == inTypes.VLESS">
|
||||
@@ -36,14 +36,14 @@
|
||||
hide-details
|
||||
:label="$t('types.vmess.security')"
|
||||
:items="vmessSecurities"
|
||||
v-model="inData.outJson.security">
|
||||
v-model="inData.out_json.security">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="inData.outJson.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
||||
<v-switch v-model="inData.out_json.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="inData.outJson.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
||||
<v-switch v-model="inData.out_json.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</template>
|
||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.Hysteria">
|
||||
@@ -52,7 +52,7 @@
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="inData.outJson.recv_window">
|
||||
v-model.number="inData.out_json.recv_window">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<template v-if="type == inTypes.TUIC">
|
||||
@@ -62,16 +62,16 @@
|
||||
label="UDP Relay Mode"
|
||||
:items="['native', 'quic']"
|
||||
clearable
|
||||
@click:clear="delete inData.outJson.udp_relay_mode"
|
||||
v-model="inData.outJson.udp_relay_mode">
|
||||
@click:clear="delete inData.out_json.udp_relay_mode"
|
||||
v-model="inData.out_json.udp_relay_mode">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="UDP Over Stream" v-model="inData.outJson.udp_over_stream" hide-details></v-switch>
|
||||
<v-switch color="primary" label="UDP Over Stream" v-model="inData.out_json.udp_over_stream" hide-details></v-switch>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
<Headers :data="inData.outJson" v-if="type == inTypes.HTTP" />
|
||||
<Headers :data="inData.out_json" v-if="type == inTypes.HTTP" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
@@ -114,8 +114,8 @@ export default {
|
||||
needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) },
|
||||
needUot():boolean { return this.havUoT.includes(this.$props.type) },
|
||||
packet_encoding: {
|
||||
get() { return this.$props.inData.outJson.packet_encoding != undefined ? this.$props.inData.outJson.packet_encoding : 'none'; },
|
||||
set(v:string) { this.$props.inData.outJson.packet_encoding = v != "none" ? v : undefined }
|
||||
get() { return this.$props.inData.out_json.packet_encoding != undefined ? this.$props.inData.out_json.packet_encoding : 'none'; },
|
||||
set(v:string) { this.$props.inData.out_json.packet_encoding = v != "none" ? v : undefined }
|
||||
},
|
||||
},
|
||||
components: { Network, UoT, Headers }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<v-text-field
|
||||
:label="$t('out.addr')"
|
||||
hide-details
|
||||
v-model="data.server">
|
||||
v-model="address">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
@@ -13,7 +13,16 @@
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="data.server_port">
|
||||
v-model="port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="KeepAlive"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="data.persistent_keepalive_interval">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -36,6 +45,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { KeepAlive } from 'vue';
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
@@ -54,6 +65,14 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
address: {
|
||||
get() { return this.$props.data.address },
|
||||
set(v:string) { this.$props.data.address = v.length > 0 ? v : undefined }
|
||||
},
|
||||
port: {
|
||||
get() { return this.$props.data.port },
|
||||
set(v:number) { this.$props.data.port = v > 0 ? v : undefined }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,22 +2,40 @@
|
||||
<v-card subtitle="Wireguard">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="data.private_key" :label="$t('types.wg.privKey')" hide-details></v-text-field>
|
||||
<v-text-field
|
||||
v-model="data.private_key"
|
||||
:label="$t('types.wg.privKey')"
|
||||
append-icon="mdi-key-star"
|
||||
@click:append="newKey()"
|
||||
hide-details>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="data.peer_public_key" :label="$t('types.wg.pubKey')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8" v-if="data.pre_shared_key != undefined">
|
||||
<v-text-field v-model="data.pre_shared_key" :label="$t('types.wg.psk')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="local_ips" :label="$t('types.wg.localIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||
<v-text-field v-model="address" :label="$t('types.wg.localIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.reserved != undefined">
|
||||
<v-text-field v-model="reserved" :label="'Reserved ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('in.port')"
|
||||
hide-details
|
||||
type="number"
|
||||
min=1
|
||||
v-model.number="data.listen_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.udp_timeout != undefined">
|
||||
<v-text-field
|
||||
label="UDP Timeout"
|
||||
hide-details
|
||||
type="number"
|
||||
min=0
|
||||
:suffix="$t('date.m')"
|
||||
v-model.number="udp_timeout">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.workers != undefined">
|
||||
<v-text-field
|
||||
:label="$t('types.wg.worker')"
|
||||
@@ -39,24 +57,16 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
<v-switch v-model="data.system" color="primary" :label="$t('types.wg.sysIf')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.interface_name != undefined">
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.name != undefined">
|
||||
<v-text-field
|
||||
:label="$t('types.wg.ifName')"
|
||||
hide-details
|
||||
v-model.number="data.interface_name">
|
||||
v-model="data.name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.system_interface" color="primary" :label="$t('types.wg.sysIf')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.gso" color="primary" :label="$t('types.wg.gso')" 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">
|
||||
@@ -66,10 +76,7 @@
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionPsk" color="primary" :label="$t('types.wg.psk')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionRsrv" color="primary" label="Reserved" hide-details></v-switch>
|
||||
<v-switch v-model="optionUdp" color="primary" label="UDP Timeout" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionWorker" color="primary" :label="$t('types.wg.worker')" hide-details></v-switch>
|
||||
@@ -80,9 +87,6 @@
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionInterface" color="primary" :label="$t('types.wg.ifName')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionPeers" color="primary" :label="$t('types.wg.multiPeer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
@@ -95,7 +99,7 @@
|
||||
<template v-for="(p, index) in data.peers">
|
||||
<v-card style="margin-top: 1rem;">
|
||||
<v-card-subtitle>
|
||||
{{ $t('types.wg.peer') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" />
|
||||
{{ $t('types.wg.peer') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" v-if="data.peers.length > 1" />
|
||||
</v-card-subtitle>
|
||||
<Peer :data="p" />
|
||||
</v-card>
|
||||
@@ -104,9 +108,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
import Peer from '@/components/WgPeer.vue'
|
||||
import { WgPeer } from '@/types/outbounds'
|
||||
import WgUtil from '@/plugins/wgUtil'
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
@@ -117,13 +120,19 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
addPeer() {
|
||||
this.$props.data.peers.push({server: '', port: ''})
|
||||
this.$props.data.peers.push({
|
||||
address: '',
|
||||
port: this.$props.data.listen_port
|
||||
})
|
||||
},
|
||||
newKey() {
|
||||
this.$props.data.private_key = WgUtil.generateKeypair().privateKey
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionPsk: {
|
||||
get(): boolean { return this.$props.data.pre_shared_key != undefined },
|
||||
set(v:boolean) { this.$props.data.pre_shared_key = v ? "" : undefined }
|
||||
optionUdp: {
|
||||
get(): boolean { return this.$props.data.udp_timeout != undefined },
|
||||
set(v:boolean) { this.$props.data.udp_timeout = v ? "5m" : undefined }
|
||||
},
|
||||
optionRsrv: {
|
||||
get(): boolean { return this.$props.data.reserved != undefined },
|
||||
@@ -138,16 +147,12 @@ export default {
|
||||
set(v:boolean) { this.$props.data.mtu = v ? 1408 : undefined }
|
||||
},
|
||||
optionInterface: {
|
||||
get(): boolean { return this.$props.data.interface_name != undefined },
|
||||
set(v:boolean) { this.$props.data.interface_name = v ? "" : undefined }
|
||||
get(): boolean { return this.$props.data.name != undefined },
|
||||
set(v:boolean) { this.$props.data.name = v ? "" : undefined }
|
||||
},
|
||||
optionPeers: {
|
||||
get(): boolean { return this.$props.data.peers != undefined },
|
||||
set(v:boolean) { this.$props.data.peers = v ? <WgPeer[]>[] : undefined }
|
||||
},
|
||||
local_ips: {
|
||||
get() { return this.$props.data.local_address?.join(',') },
|
||||
set(v:string) { this.$props.data.local_address = v.length > 0 ? v.split(',') : undefined }
|
||||
address: {
|
||||
get() { return this.$props.data.address?.join(',') },
|
||||
set(v:string) { this.$props.data.address = v.length > 0 ? v.split(',') : undefined }
|
||||
},
|
||||
reserved: {
|
||||
get() { return this.$props.data.reserved?.join(',') },
|
||||
@@ -157,7 +162,11 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
udp_timeout: {
|
||||
get() { return this.$props.data.udp_timeout ? parseInt(this.$props.data.udp_timeout.replace('m','')) : 5 },
|
||||
set(v:number) { this.$props.data.udp_timeout = v > 0 ? v + 'm' : '5m' }
|
||||
}
|
||||
},
|
||||
components: { Network, Peer }
|
||||
components: { Peer }
|
||||
}
|
||||
</script>
|
||||
@@ -1,242 +1,25 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('objects.tls')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tlsOptional">
|
||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.enabled">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('template')"
|
||||
:items="tlsItems"
|
||||
@update:model-value="changeTlsItem($event)"
|
||||
v-model="tlsId">
|
||||
v-model="inbound.tls_id">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="tls.enabled && tlsId == 0">
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-btn-toggle v-model="usePath"
|
||||
class="rounded-xl"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
shaped
|
||||
mandatory>
|
||||
<v-btn
|
||||
@click="tls.key=undefined; tls.certificate=undefined"
|
||||
>{{ $t('tls.usePath') }}</v-btn>
|
||||
<v-btn
|
||||
@click="tls.key_path=undefined; tls.certificate_path=undefined"
|
||||
>{{ $t('tls.useText') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="usePath == 0">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('tls.certPath')"
|
||||
hide-details
|
||||
v-model="tls.certificate_path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('tls.keyPath')"
|
||||
hide-details
|
||||
v-model="tls.key_path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-else>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.cert')"
|
||||
hide-details
|
||||
v-model="certText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.key')"
|
||||
hide-details
|
||||
v-model="keyText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.server_name != undefined">
|
||||
<v-text-field
|
||||
label="SNI"
|
||||
hide-details
|
||||
v-model="tls.server_name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.alpn">
|
||||
<v-select
|
||||
hide-details
|
||||
label="ALPN"
|
||||
multiple
|
||||
:items="alpn"
|
||||
v-model="tls.alpn">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.minVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="tls.min_version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.maxVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="tls.max_version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" md="8" v-if="tls.cipher_suites != undefined">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.cs')"
|
||||
multiple
|
||||
:items="cipher_suites"
|
||||
v-model="tls.cipher_suites">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<v-card-actions v-if="tls.enabled && tlsId == 0">
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { i18n } from '@/locales'
|
||||
import { iTls, defaultInTls } from '@/types/inTls'
|
||||
export default {
|
||||
props: ['inbound', 'tlsConfigs', 'tls_id'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
usePath: this.$props.inbound.tls.key == undefined ? 0 : 1,
|
||||
defaults: defaultInTls,
|
||||
alpn: [
|
||||
{ title: "H3", value: 'h3' },
|
||||
{ title: "H2", value: 'h2' },
|
||||
{ title: "Http/1.1", value: 'http/1.1' },
|
||||
],
|
||||
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
||||
cipher_suites: [
|
||||
{ title: "RSA-AES128-CBC-SHA", value: "TLS_RSA_WITH_AES_128_CBC_SHA" },
|
||||
{ title: "RSA-AES256-CBC-SHA", value: "TLS_RSA_WITH_AES_256_CBC_SHA" },
|
||||
{ title: "RSA-AES128-GCM-SHA256", value: "TLS_RSA_WITH_AES_128_GCM_SHA256" },
|
||||
{ title: "RSA-AES256-GCM-SHA384", value: "TLS_RSA_WITH_AES_256_GCM_SHA384" },
|
||||
{ title: "AES128-GCM-SHA256", value: "TLS_AES_128_GCM_SHA256" },
|
||||
{ title: "AES256-GCM-SHA384", value: "TLS_AES_256_GCM_SHA384" },
|
||||
{ title: "CHACHA20-POLY1305-SHA256", value: "TLS_CHACHA20_POLY1305_SHA256" },
|
||||
{ title: "ECDHE-ECDSA-AES128-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
|
||||
{ title: "ECDHE-ECDSA-AES256-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
|
||||
{ title: "ECDHE-RSA-AES128-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
|
||||
{ title: "ECDHE-RSA-AES256-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
|
||||
{ title: "ECDHE-ECDSA-AES128-GCM-SHA256", value: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
|
||||
{ title: "ECDHE-ECDSA-AES256-GCM-SHA384", value: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
|
||||
{ title: "ECDHE-RSA-AES128-GCM-SHA256", value: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
|
||||
{ title: "ECDHE-RSA-AES256-GCM-SHA384", value: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
|
||||
{ title: "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
|
||||
{ title: "ECDHE-RSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" }
|
||||
]
|
||||
}
|
||||
},
|
||||
props: ['inbound', 'tlsConfigs'],
|
||||
computed: {
|
||||
tls(): iTls {
|
||||
return <iTls> this.$props.inbound.tls
|
||||
},
|
||||
tlsItems(): any[] {
|
||||
return [ { title: i18n.global.t('none'), value: 0 }, ...this.$props.tlsConfigs?.map((t:any) => { return { title: t.name, value: t.id } } )]
|
||||
},
|
||||
tlsId: {
|
||||
get() { return this.tls_id.value?? 0 },
|
||||
set(newValue: boolean) { this.$props.tls_id.value = newValue }
|
||||
},
|
||||
tlsEnable: {
|
||||
get() { return this.tls.enabled?? false },
|
||||
set(newValue: boolean) {
|
||||
this.$props.inbound.tls = newValue ? { enabled: true } : {}
|
||||
this.$props.tls_id.value = 0
|
||||
}
|
||||
},
|
||||
tlsOptional(): boolean {
|
||||
return !['hysteria','hysteria2','tuic','naive'].includes(this.$props.inbound.type)
|
||||
},
|
||||
certText: {
|
||||
get(): string { return this.tls.certificate ? this.tls.certificate.join('\n') : '' },
|
||||
set(newValue:string) { this.tls.certificate = newValue.split('\n') }
|
||||
},
|
||||
keyText: {
|
||||
get(): string { return this.tls.key ? this.tls.key.join('\n') : '' },
|
||||
set(newValue:string) { this.tls.key = newValue.split('\n') }
|
||||
},
|
||||
optionSNI: {
|
||||
get(): boolean { return this.tls.server_name != undefined },
|
||||
set(v:boolean) { this.tls.server_name = v ? '' : undefined }
|
||||
},
|
||||
optionALPN: {
|
||||
get(): boolean { return this.tls.alpn != undefined },
|
||||
set(v:boolean) { this.tls.alpn = v ? defaultInTls.alpn : undefined }
|
||||
},
|
||||
optionMinV: {
|
||||
get(): boolean { return this.tls.min_version != undefined },
|
||||
set(v:boolean) { this.tls.min_version = v ? defaultInTls.min_version : undefined }
|
||||
},
|
||||
optionMaxV: {
|
||||
get(): boolean { return this.tls.max_version != undefined },
|
||||
set(v:boolean) { this.tls.max_version = v ? defaultInTls.max_version : undefined }
|
||||
},
|
||||
optionCS: {
|
||||
get(): boolean { return this.tls.cipher_suites != undefined },
|
||||
set(v:boolean) { this.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTlsItem(id: number){
|
||||
if (id>0) {
|
||||
const tlsConfig = this.$props.tlsConfigs?.findLast((t:any) => t.id == id)
|
||||
if (tlsConfig) this.$props.inbound.tls = tlsConfig.server
|
||||
} else {
|
||||
this.$props.inbound.tls = { enabled: this.tls.enabled }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
<v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" />
|
||||
<span v-else style="width: 24px"></span>
|
||||
<v-app-bar-title :text="$t(<string>route.name)" class="align-center text-center " />
|
||||
<v-btn prepend-icon="mdi-content-save" v-if="stateChange" :text="$t('actions.save')" @click="saveChanges"></v-btn>
|
||||
<v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue"
|
||||
import { ref } from "vue"
|
||||
import { useTheme } from "vuetify"
|
||||
import { FindDiff } from "@/plugins/utils"
|
||||
import Data from "@/store/modules/data"
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
defineProps(['isMobile'])
|
||||
@@ -21,27 +18,9 @@ const route = useRoute();
|
||||
const theme = useTheme()
|
||||
const darkMode = ref(localStorage.getItem('theme') == "dark")
|
||||
|
||||
const store = Data()
|
||||
|
||||
const toggleTheme = () => {
|
||||
darkMode.value = !darkMode.value
|
||||
theme.global.name.value = darkMode.value ? "dark" : "light"
|
||||
localStorage.setItem('theme', theme.global.name.value)
|
||||
}
|
||||
|
||||
const saveChanges = () => {
|
||||
store.pushData()
|
||||
}
|
||||
|
||||
const oldData = computed((): any => {
|
||||
return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs, inData: store.oldData.inData}
|
||||
})
|
||||
|
||||
const newData = computed((): any => {
|
||||
return {config: store.config, clients: store.clients, tls: store.tlsConfigs, inData: store.inData}
|
||||
})
|
||||
|
||||
const stateChange = computed((): any => {
|
||||
return !FindDiff.deepCompare(newData.value,oldData.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -53,6 +53,7 @@ const menu = [
|
||||
{ title: 'pages.inbounds', icon: 'mdi-cloud-download', path: '/inbounds' },
|
||||
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
|
||||
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
|
||||
{ title: 'pages.endpoints', icon: 'mdi-cloud-tags', path: '/endpoints' },
|
||||
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
|
||||
{ title: 'pages.tls', icon: 'mdi-certificate', path: '/tls' },
|
||||
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<DatePick :expiry="expDate" @submit="setDate" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="index != -1">
|
||||
<v-row v-if="id > 0">
|
||||
<v-col cols="12" sm="6" md="4" class="d-flex flex-column">
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
@@ -80,11 +80,6 @@
|
||||
></v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-switch v-model="clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
<v-window-item value="t2">
|
||||
<v-row v-for="(value, key) in clientConfig" :key="key">
|
||||
@@ -189,7 +184,7 @@ import DatePick from '@/components/DateTime.vue'
|
||||
import { HumanReadable } from '@/plugins/utils'
|
||||
|
||||
export default {
|
||||
props: ['visible', 'data', 'index', 'inboundTags', 'groups', 'stats'],
|
||||
props: ['visible', 'data', 'id', 'inboundTags', 'groups'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
@@ -206,7 +201,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.index != -1) {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.client = createClient(newData)
|
||||
this.title = "edit"
|
||||
@@ -217,7 +212,6 @@ export default {
|
||||
this.title = "add"
|
||||
this.clientConfig = randomConfigs('client')
|
||||
}
|
||||
this.clientStats = this.$props.stats
|
||||
this.links = this.client.links.filter(l => l.type == 'local')
|
||||
this.extLinks = this.client.links.filter(l => l.type == 'external')
|
||||
this.subLinks = this.client.links.filter(l => l.type == 'sub')
|
||||
@@ -243,8 +237,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
clientInbounds: {
|
||||
get() { return this.client.inbounds.length>0 ? this.client.inbounds.filter(i => this.inboundTags.includes(i)) : [] },
|
||||
set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? [] : newValue }
|
||||
get() { return this.client.inbounds.length>0 ? this.client.inbounds : [] },
|
||||
set(v:any[]) { this.client.inbounds = v.length == 0 ? [] : v.map(i => i.value) }
|
||||
},
|
||||
expDate: {
|
||||
get() { return this.client.expiry},
|
||||
|
||||
@@ -59,11 +59,6 @@
|
||||
></v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-switch v-model="bulkData.clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
@@ -109,7 +104,6 @@ export default {
|
||||
clientInbounds: [],
|
||||
expiry: 0,
|
||||
Volume: 0,
|
||||
clientStats: false,
|
||||
},
|
||||
patterns: [
|
||||
{ title: i18n.global.t("bulk.random"), value: "random" },
|
||||
@@ -129,7 +123,6 @@ export default {
|
||||
clientInbounds: [],
|
||||
expiry: 0,
|
||||
Volume: 0,
|
||||
clientStats: false,
|
||||
}
|
||||
},
|
||||
closeModal() {
|
||||
@@ -157,7 +150,7 @@ export default {
|
||||
group: this.bulkData.group
|
||||
}))
|
||||
}
|
||||
this.$emit('save', this.clients, this.bulkData.clientInbounds, this.bulkData.clientStats)
|
||||
this.$emit('save', this.clients, this.bulkData.clientInbounds)
|
||||
this.resetData() // reset to default
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-card-title>
|
||||
{{ $t('actions.' + title) + " " + $t('objects.endpoint') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||
<v-container style="padding: 0;">
|
||||
<v-tabs
|
||||
v-model="tab"
|
||||
align-tabs="center"
|
||||
>
|
||||
<v-tab value="t1">{{ $t('client.basics') }}</v-tab>
|
||||
<v-tab value="t2">{{ $t('client.external') }}</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="t1">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('type')"
|
||||
:items="Object.keys(epTypes).map((key,index) => ({title: key, value: Object.values(epTypes)[index]}))"
|
||||
v-model="endpoint.type"
|
||||
@update:modelValue="changeType">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="endpoint.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Wireguard v-if="endpoint.type == epTypes.Wireguard" :data="endpoint" />
|
||||
<Dial :dial="endpoint" :outTags="tags" />
|
||||
</v-window-item>
|
||||
<v-window-item value="t2">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field v-model="link" :label="$t('client.external')" hide-details />
|
||||
</v-col>
|
||||
<v-col cols="12" align="center">
|
||||
<v-btn hide-details variant="tonal" :loading="loading" @click="linkConvert">{{ $t('submit') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="text"
|
||||
@click="closeModal"
|
||||
>
|
||||
{{ $t('actions.close') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="text"
|
||||
:loading="loading"
|
||||
@click="saveChanges"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { EpTypes, createEndpoint } from '@/types/endpoints'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
import Dial from '@/components/Dial.vue'
|
||||
import Wireguard from '@/components/protocols/Wireguard.vue'
|
||||
import HttpUtils from '@/plugins/httputil'
|
||||
import WgUtil from '@/plugins/wgUtil'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'tags'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
endpoint: createEndpoint("wireguard",{ "tag": "" }),
|
||||
title: "add",
|
||||
tab: "t1",
|
||||
link: "",
|
||||
loading: false,
|
||||
epTypes: EpTypes,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.endpoint = createEndpoint(newData.type, newData)
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||
const randomIPoctet = RandomUtil.randomIntRange(1, 255)
|
||||
this.endpoint = createEndpoint("wireguard",{
|
||||
tag: "wireguard-" + RandomUtil.randomSeq(3),
|
||||
address: ['10.0.0.'+ randomIPoctet.toString() +'/32','fe80::'+ randomIPoctet.toString(16) +'/128'],
|
||||
listen_port: port,
|
||||
private_key: WgUtil.generateKeypair().privateKey,
|
||||
peers: [{
|
||||
public_key: WgUtil.generateKeypair().publicKey,
|
||||
allowed_ips: ['0.0.0.0/0', '::/0']
|
||||
}]
|
||||
})
|
||||
this.title = "add"
|
||||
}
|
||||
this.tab = "t1"
|
||||
},
|
||||
changeType() {
|
||||
// Tag change only in add endpoint
|
||||
const tag = this.$props.id > 0 ? this.endpoint.tag : this.endpoint.type + "-" + RandomUtil.randomSeq(3)
|
||||
// Use previous data
|
||||
const prevConfig = { id: this.endpoint.id, tag: tag ,listen: this.endpoint.listen, listen_port: this.endpoint.listen_port }
|
||||
this.endpoint = createEndpoint(this.endpoint.type, prevConfig)
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.endpoint)
|
||||
this.loading = false
|
||||
},
|
||||
async linkConvert() {
|
||||
if (this.link.length>0){
|
||||
this.loading = true
|
||||
const msg = await HttpUtils.post('api/linkConvert', { link: this.link })
|
||||
this.loading = false
|
||||
if (msg.success) {
|
||||
this.endpoint = msg.obj
|
||||
this.tab = "t1"
|
||||
this.link = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { Dial, Wireguard }
|
||||
}
|
||||
</script>
|
||||
@@ -1,12 +1,18 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-dialog transition="dialog-bottom-transition" width="800" @after-enter="updateData">
|
||||
<v-card class="rounded-lg" :loading="loading">
|
||||
<v-card-title>
|
||||
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-skeleton-loader
|
||||
class="mx-auto border"
|
||||
width="95%"
|
||||
type="card, text, divider, list-item-two-line"
|
||||
v-if="loading"
|
||||
></v-skeleton-loader>
|
||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||
<v-container style="padding: 0;">
|
||||
<v-container style="padding: 0;" :hidden="loading">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
@@ -45,19 +51,18 @@
|
||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" />
|
||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
||||
<InTls v-if="HasTls.includes(inbound.type)" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="inbound.tls_id" />
|
||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
||||
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-window-item>
|
||||
<v-window-item value="c">
|
||||
<OutJsonVue :inData="inData" :type="inbound.type" />
|
||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="out" :data="inData.outJson" />
|
||||
<OutJsonVue :inData="inbound" :type="inbound.type" />
|
||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="out" :data="inbound.out_json" />
|
||||
<v-card>
|
||||
<v-card-subtitle>{{ $t('in.multiDomain') }}
|
||||
<v-icon @click="add_addr" icon="mdi-plus"></v-icon>
|
||||
</v-card-subtitle>
|
||||
<template v-for="addr,index in inData.addrs">
|
||||
{{ $t('in.addr') }} #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inData.addrs.splice(index,1)" />
|
||||
<template v-for="addr,index in inbound.addrs">
|
||||
{{ $t('in.addr') }} #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inbound.addrs?.splice(index,1)" />
|
||||
<v-divider></v-divider>
|
||||
<AddrVue :addr="addr" :hasTls="Object.hasOwn(inbound,'tls')" />
|
||||
</template>
|
||||
@@ -79,6 +84,7 @@
|
||||
color="blue-darken-1"
|
||||
variant="text"
|
||||
:loading="loading"
|
||||
:disabled="!validate"
|
||||
@click="saveChanges"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
@@ -89,8 +95,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { InTypes, createInbound } from '@/types/inbounds'
|
||||
import { Addr, InData } from '@/plugins/inData'
|
||||
import { InTypes, createInbound, Addr } from '@/types/inbounds'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
|
||||
import Listen from '@/components/Listen.vue'
|
||||
@@ -109,19 +114,17 @@ import Multiplex from '@/components/Multiplex.vue'
|
||||
import Transport from '@/components/Transport.vue'
|
||||
import AddrVue from '@/components/Addr.vue'
|
||||
import OutJsonVue from '@/components/OutJson.vue'
|
||||
import Data from '@/store/modules/data'
|
||||
export default {
|
||||
props: ['visible', 'data', 'cData', 'index', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
||||
props: ['visible', 'id', 'inTags', 'outTags', 'tlsConfigs'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
inbound: createInbound("direct",{ "tag": "" }),
|
||||
inData: <InData>{},
|
||||
inbound: createInbound("direct",{ id:0, "tag": "" }),
|
||||
title: "add",
|
||||
loading: false,
|
||||
side: "s",
|
||||
inTypes: InTypes,
|
||||
inboundStats: false,
|
||||
tls_id: { value: 0 },
|
||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||
HasInData: [
|
||||
InTypes.SOCKS,
|
||||
@@ -136,56 +139,65 @@ export default {
|
||||
InTypes.TUIC,
|
||||
InTypes.Hysteria2,
|
||||
InTypes.Naive,
|
||||
]
|
||||
],
|
||||
HasTls: [
|
||||
InTypes.HTTP,
|
||||
InTypes.VMess,
|
||||
InTypes.Trojan,
|
||||
InTypes.Naive,
|
||||
InTypes.Hysteria,
|
||||
InTypes.TUIC,
|
||||
InTypes.Hysteria2,
|
||||
InTypes.VLESS,
|
||||
],
|
||||
OnlyTLS: [InTypes.Hysteria, InTypes.Hysteria2, InTypes.TUIC, InTypes.Naive ],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
const inboundArray = await Data().loadInbounds([this.$props.id])
|
||||
this.inbound = inboundArray[0]
|
||||
this.loading = false
|
||||
},
|
||||
updateData() {
|
||||
if (this.$props.index != -1) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.inbound = createInbound(newData.type, newData)
|
||||
this.tls_id.value = this.$props.tlsConfigs?.findLast((t:any) => t.inbounds?.includes(this.inbound.tag))?.id?? 0
|
||||
if (this.HasInData.includes(this.inbound.type)){
|
||||
this.inData = this.$props.cData?.length> 0 ? <InData>JSON.parse(this.$props.cData) : <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||
} else {
|
||||
this.inData = <InData>{id: -1}
|
||||
}
|
||||
if (this.$props.id > 0) {
|
||||
this.loadData()
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||
this.tls_id.value = 0
|
||||
this.inbound = createInbound("direct",{ id: 0, tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||
if (this.HasInData.includes(this.inbound.type)){
|
||||
this.inData = <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||
this.inbound.addrs = []
|
||||
this.inbound.out_json = {}
|
||||
} else {
|
||||
this.inData = <InData>{id: -1}
|
||||
delete this.inbound.addrs
|
||||
delete this.inbound.out_json
|
||||
}
|
||||
this.title = "add"
|
||||
this.loading = false
|
||||
}
|
||||
this.inboundStats = this.$props.stats
|
||||
this.side = "s"
|
||||
},
|
||||
changeType() {
|
||||
if (!this.inbound.listen_port) this.inbound.listen_port = RandomUtil.randomIntRange(10000, 60000)
|
||||
// Tag change only in add inbound
|
||||
const tag = this.$props.index != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||
const tag = this.$props.id > 0 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||
// Use previous data
|
||||
const prevConfig = { tag: tag ,listen: this.inbound.listen?? "::", listen_port: this.inbound.listen_port }
|
||||
const prevConfig = { id: this.inbound.id, tag: tag ,listen: this.inbound.listen?? "::", listen_port: this.inbound.listen_port }
|
||||
this.inbound = createInbound(this.inbound.type, this.inbound.type != this.inTypes.Tun ? prevConfig : { tag: tag })
|
||||
if (this.HasInData.includes(this.inbound.type)){
|
||||
if (this.inData.id == -1) this.inData.id = 0
|
||||
this.inData.addrs = []
|
||||
this.inData.outJson = {}
|
||||
this.inData.tag = tag
|
||||
this.inbound.addrs = []
|
||||
this.inbound.out_json = {}
|
||||
} else {
|
||||
this.inData = <InData>{id: -1}
|
||||
delete this.inbound.addrs
|
||||
delete this.inbound.out_json
|
||||
}
|
||||
this.tls_id.value = 0
|
||||
this.side = "s"
|
||||
},
|
||||
add_addr() {
|
||||
this.inData.addrs.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
||||
this.inbound.addrs?.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
@@ -193,14 +205,23 @@ export default {
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value, this.inData)
|
||||
this.$emit('save', this.inbound)
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
validate() {
|
||||
if (this.inbound == undefined) return false
|
||||
if (this.inbound.tag == "") return false
|
||||
if (this.inbound.listen_port > 65535 || this.inbound.listen_port < 1) return false
|
||||
if (this.OnlyTLS.includes(this.inbound.type) && this.inbound.tls_id == 0) return false
|
||||
return true
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
this.loading = true
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
<Shadowsocks v-if="outbound.type == outTypes.Shadowsocks" direction="out" :data="outbound" />
|
||||
<Vmess v-if="outbound.type == outTypes.VMess" :data="outbound" />
|
||||
<Trojan v-if="outbound.type == outTypes.Trojan" :data="outbound" />
|
||||
<Wireguard v-if="outbound.type == outTypes.Wireguard" :data="outbound" />
|
||||
<Hysteria v-if="outbound.type == outTypes.Hysteria" direction="out" :data="outbound" />
|
||||
<ShadowTls v-if="outbound.type == outTypes.ShadowTLS" :data="outbound" />
|
||||
<Vless v-if="outbound.type == outTypes.VLESS" :data="outbound" />
|
||||
@@ -69,7 +68,6 @@
|
||||
<OutTLS v-if="Object.hasOwn(outbound,'tls')" :outbound="outbound" />
|
||||
<Multiplex v-if="Object.hasOwn(outbound,'multiplex')" direction="out" :data="outbound" />
|
||||
<Dial v-if="!NoDial.includes(outbound.type)" :dial="outbound" :outTags="tags" />
|
||||
<v-switch v-model="outboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-window-item>
|
||||
<v-window-item value="t2">
|
||||
<v-row>
|
||||
@@ -131,7 +129,7 @@ import Selector from '@/components/protocols/Selector.vue'
|
||||
import UrlTest from '@/components/protocols/UrlTest.vue'
|
||||
import HttpUtils from '@/plugins/httputil'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'stats', 'tags'],
|
||||
props: ['visible', 'data', 'id', 'tags'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
@@ -141,14 +139,13 @@ export default {
|
||||
link: "",
|
||||
loading: false,
|
||||
outTypes: OutTypes,
|
||||
outboundStats: false,
|
||||
NoDial: [OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest],
|
||||
NoServer: [OutTypes.Direct, OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest, OutTypes.Tor],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.id != -1) {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.outbound = createOutbound(newData.type, newData)
|
||||
this.title = "edit"
|
||||
@@ -158,13 +155,12 @@ export default {
|
||||
this.title = "add"
|
||||
}
|
||||
this.tab = "t1"
|
||||
this.outboundStats = this.$props.stats
|
||||
},
|
||||
changeType() {
|
||||
// Tag change only in add outbound
|
||||
const tag = this.$props.id != -1 ? this.outbound.tag : this.outbound.type + "-" + RandomUtil.randomSeq(3)
|
||||
const tag = this.$props.id > 0 ? this.outbound.tag : this.outbound.type + "-" + RandomUtil.randomSeq(3)
|
||||
// Use previous data
|
||||
const prevConfig = { tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
||||
const prevConfig = { id: this.outbound.id, tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
||||
this.outbound = createOutbound(this.outbound.type, prevConfig)
|
||||
},
|
||||
closeModal() {
|
||||
@@ -173,7 +169,7 @@ export default {
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.outbound, this.outboundStats)
|
||||
this.$emit('save', this.outbound)
|
||||
this.loading = false
|
||||
},
|
||||
async linkConvert() {
|
||||
|
||||
@@ -298,11 +298,11 @@ import { push } from 'notivue'
|
||||
import { i18n } from '@/locales'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
export default {
|
||||
props: ['visible', 'data', 'index'],
|
||||
props: ['visible', 'data', 'id'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
tls: { id: -1, name: '', inbounds: [], server: <iTls>{ enabled: true }, client: <oTls>{} },
|
||||
tls: { id: 0, name: '', server: <iTls>{ enabled: true }, client: <oTls>{} },
|
||||
title: "add",
|
||||
loading: false,
|
||||
menu: false,
|
||||
@@ -354,15 +354,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.index != -1) {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.tls = newData
|
||||
if (this.tls.server == null) this.tls.server = {}
|
||||
if (this.tls.client == null) this.tls.client = {}
|
||||
this.tlsType = newData.server?.reality == undefined ? 0 : 1
|
||||
this.usePath = newData.server?.key == undefined ? 0 : 1
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
this.tls = { id: 0, name: '', inbounds: [], server: {enabled: true}, client: {} }
|
||||
this.tls = { id: 0, name: '', server: {enabled: true}, client: {} }
|
||||
this.tlsType = 0
|
||||
this.usePath = 0
|
||||
this.title = "add"
|
||||
@@ -461,10 +463,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
inTls(): iTls {
|
||||
return <iTls> this.tls.server
|
||||
return this.tls.server
|
||||
},
|
||||
outTls(): oTls {
|
||||
return <oTls> this.tls.client
|
||||
return this.tls.client
|
||||
},
|
||||
certText: {
|
||||
get(): string { return this.inTls.certificate ? this.inTls.certificate.join('\n') : '' },
|
||||
@@ -476,11 +478,11 @@ export default {
|
||||
},
|
||||
disableSni: {
|
||||
get() { return this.outTls.disable_sni ?? false },
|
||||
set(v: boolean) { this.outTls.disable_sni = v ? true : undefined }
|
||||
set(v: boolean) { this.tls.client.disable_sni = v ? true : undefined }
|
||||
},
|
||||
insecure: {
|
||||
get() { return this.outTls.insecure ?? false },
|
||||
set(v: boolean) { this.outTls.insecure = v ? true : undefined }
|
||||
set(v: boolean) { this.tls.client.insecure = v ? true : undefined }
|
||||
},
|
||||
server_port: {
|
||||
get() { return this.inTls.reality?.handshake?.server_port ? this.inTls.reality.handshake.server_port : 443 },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { en } from "vuetify/lib/locale/index.mjs";
|
||||
|
||||
export default {
|
||||
message: "Welcome",
|
||||
success: "success",
|
||||
@@ -37,6 +39,7 @@ export default {
|
||||
home: "Home",
|
||||
inbounds: "Inbounds",
|
||||
outbounds: "Outbounds",
|
||||
endpoints: "Endpoints",
|
||||
clients: "Clients",
|
||||
rules: "Rules",
|
||||
tls: "TLS Settings",
|
||||
@@ -75,6 +78,8 @@ export default {
|
||||
inbound: "Inbound",
|
||||
client: "Client",
|
||||
outbound: "Outbound",
|
||||
endpoint: "Endpoint",
|
||||
config: "Config",
|
||||
rule: "Rule",
|
||||
user: "User",
|
||||
tag: "Tag",
|
||||
@@ -228,7 +233,6 @@ export default {
|
||||
worker: "Workers",
|
||||
ifName: "Interface Name",
|
||||
sysIf: "System Interface",
|
||||
gso: "Segmentation Offload",
|
||||
options: "Wireguard Options",
|
||||
multiPeer: "Multi Peer",
|
||||
allowedIp: "Allowed IPs",
|
||||
@@ -394,7 +398,6 @@ export default {
|
||||
download: "Download",
|
||||
volume: "Volume",
|
||||
usage: "Usage",
|
||||
enable: "Enable Statistics",
|
||||
graphTitle: "Traffic Chart",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { config } from "process";
|
||||
|
||||
export default {
|
||||
message: "خوش آمدید",
|
||||
success: "موفق",
|
||||
@@ -37,6 +39,7 @@ export default {
|
||||
home: "خانه",
|
||||
inbounds: "ورودیها",
|
||||
outbounds: "خروجیها",
|
||||
endpoints: "درگاهها",
|
||||
clients: "کاربران",
|
||||
rules: "قوانین",
|
||||
tls: "رمزنگاریها",
|
||||
@@ -75,6 +78,8 @@ export default {
|
||||
inbound: "ورودی",
|
||||
client: "کاربر",
|
||||
outbound: "خروجی",
|
||||
endpoint: "درگاه",
|
||||
config: "پیکربندی",
|
||||
rule: "قانون",
|
||||
user: "کاربر",
|
||||
tag: "برچسب",
|
||||
@@ -227,7 +232,6 @@ export default {
|
||||
worker: "عملگرها",
|
||||
ifName: "نام اینترفیس",
|
||||
sysIf: "استفاده از اینترفیس سیستم",
|
||||
gso: "بارگذاری تقسیمبندی عمومی",
|
||||
options: "گزینههای Wireguard",
|
||||
multiPeer: "چند همتایی",
|
||||
allowedIp: "آدرسهای مجاز",
|
||||
@@ -393,7 +397,6 @@ export default {
|
||||
download: "دانلود",
|
||||
volume: "حجم",
|
||||
usage: "استفاده",
|
||||
enable: "فعال سازی کنترل ترافیک",
|
||||
graphTitle: "نمودار ترافیک",
|
||||
B: "ب",
|
||||
KB: "کب",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { config } from "process";
|
||||
|
||||
export default {
|
||||
message: "Добро пожаловать",
|
||||
success: "успех",
|
||||
@@ -37,6 +39,7 @@ export default {
|
||||
home: "Главная",
|
||||
inbounds: "Входящие",
|
||||
outbounds: "Исходящие",
|
||||
endpoints: "Эндпоинты",
|
||||
clients: "Клиенты",
|
||||
rules: "Правила",
|
||||
tls: "Настройки TLS",
|
||||
@@ -75,6 +78,8 @@ export default {
|
||||
inbound: "Входящий",
|
||||
client: "Клиент",
|
||||
outbound: "Исходящий",
|
||||
endpoint: "Точка входа",
|
||||
config: "Настройки",
|
||||
rule: "Правило",
|
||||
user: "Пользователь",
|
||||
tag: "Тег",
|
||||
@@ -228,7 +233,6 @@ export default {
|
||||
worker: "Работники",
|
||||
ifName: "Имя интерфейса",
|
||||
sysIf: "Системный интерфейс",
|
||||
gso: "Отключение сегментации",
|
||||
options: "Параметры Wireguard",
|
||||
multiPeer: "Множественный пир",
|
||||
allowedIp: "Разрешенные IP",
|
||||
@@ -394,7 +398,6 @@ export default {
|
||||
download: "Скачивание",
|
||||
volume: "Объем",
|
||||
usage: "Использование",
|
||||
enable: "Включить статистику",
|
||||
graphTitle: "График трафика",
|
||||
B: "Б",
|
||||
KB: "КБ",
|
||||
|
||||
@@ -37,6 +37,7 @@ export default {
|
||||
home: "Trang chủ",
|
||||
inbounds: "Đầu Vào",
|
||||
outbounds: "Đầu ra",
|
||||
endpoints: "Câu hình",
|
||||
clients: "Khách hàng",
|
||||
rules: "Quy tắc",
|
||||
tls: "Cài đặt TLS",
|
||||
@@ -75,6 +76,8 @@ export default {
|
||||
inbound: "Đầu Vào",
|
||||
client: "Máy Khách hàng",
|
||||
outbound: "Đầu Ra",
|
||||
endpoint: "Điểm cuối",
|
||||
config: "Câu hình",
|
||||
rule: "Quy tắc",
|
||||
user: "Người dùng",
|
||||
tag: "Thẻ",
|
||||
@@ -228,7 +231,6 @@ export default {
|
||||
worker: "Công nhân",
|
||||
ifName: "Tên Giao diện",
|
||||
sysIf: "Giao diện Hệ thống",
|
||||
gso: "Giao Thức GSO",
|
||||
options: "Tùy chọn Wireguard",
|
||||
multiPeer: "Nhiều Đối tác",
|
||||
allowedIp: "IPs được Phép",
|
||||
@@ -395,7 +397,6 @@ export default {
|
||||
download: "Tải xuống",
|
||||
volume: "Thể tích",
|
||||
usage: "Sử dụng",
|
||||
enable: "Kích hoạt thống kê",
|
||||
graphTitle: "Biểu đồ lưu lượng",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -37,6 +37,7 @@ export default {
|
||||
home: "主页",
|
||||
inbounds: "入站管理",
|
||||
outbounds: "出站管理",
|
||||
endpoints: "节点管理",
|
||||
clients: "用户管理",
|
||||
rules: "路由列表",
|
||||
tls: "TLS 设置",
|
||||
@@ -75,6 +76,8 @@ export default {
|
||||
inbound: "入站",
|
||||
client: "客户端",
|
||||
outbound: "出站",
|
||||
endpoint: "节点",
|
||||
config: "配置",
|
||||
rule: "规则",
|
||||
user: "用户",
|
||||
tag: "标签",
|
||||
@@ -228,7 +231,6 @@ export default {
|
||||
worker: "工作线程",
|
||||
ifName: "接口名称",
|
||||
sysIf: "系统接口",
|
||||
gso: "分段卸载",
|
||||
options: "WireGuard 选项",
|
||||
multiPeer: "多对等体",
|
||||
allowedIp: "允许的 IP 地址",
|
||||
@@ -395,7 +397,6 @@ export default {
|
||||
download: "下载",
|
||||
volume: "流量",
|
||||
usage: "已用",
|
||||
enable: "启用统计",
|
||||
graphTitle: "流量图表",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
home: "主頁",
|
||||
inbounds: "入站管理",
|
||||
outbounds: "出站管理",
|
||||
endpoints: "端點管理",
|
||||
clients: "用戶管理",
|
||||
rules: "路由列表",
|
||||
tls: "TLS 設置",
|
||||
@@ -76,6 +77,8 @@ export default {
|
||||
inbound: "入站",
|
||||
client: "客戶端",
|
||||
outbound: "出站",
|
||||
endpoint: "端點",
|
||||
config: "配置",
|
||||
rule: "規則",
|
||||
user: "用戶",
|
||||
tag: "標簽",
|
||||
@@ -229,7 +232,6 @@ export default {
|
||||
worker: "工作線程",
|
||||
ifName: "介面名稱",
|
||||
sysIf: "系統介面",
|
||||
gso: "分段卸載",
|
||||
options: "Wireguard 選項",
|
||||
multiPeer: "多對等方",
|
||||
allowedIp: "允許的 IP",
|
||||
@@ -396,7 +398,6 @@ export default {
|
||||
download: "下載",
|
||||
volume: "流量",
|
||||
usage: "已用",
|
||||
enable: "啟用統計",
|
||||
graphTitle: "流量圖表",
|
||||
B: "B",
|
||||
KB: "KB",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export interface Addr {
|
||||
server: string
|
||||
server_port: number
|
||||
tls?: boolean
|
||||
insecure?: boolean
|
||||
server_name?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface InData {
|
||||
id: number
|
||||
tag: string
|
||||
addrs: Addr[]
|
||||
outJson: any
|
||||
}
|
||||
@@ -15,24 +15,24 @@ function utf8ToBase64(utf8String: string): string {
|
||||
}
|
||||
|
||||
export namespace LinkUtil {
|
||||
export function linkGenerator(user: Client, inbound: Inbound, tlsClient: any = {}, addrs: any[] = []): string[] {
|
||||
export function linkGenerator(user: Client, inbound: Inbound, tls: any = {}, addrs: any[] = []): string[] {
|
||||
switch(inbound.type){
|
||||
case InTypes.Shadowsocks:
|
||||
return shadowsocksLink(user,<Shadowsocks>inbound, addrs)
|
||||
case InTypes.Naive:
|
||||
return naiveLink(user,<Naive>inbound, addrs, tlsClient)
|
||||
return naiveLink(user,<Naive>inbound, addrs, tls)
|
||||
case InTypes.Hysteria:
|
||||
return hysteriaLink(user,<Hysteria>inbound, addrs, tlsClient)
|
||||
return hysteriaLink(user,<Hysteria>inbound, addrs, tls)
|
||||
case InTypes.Hysteria2:
|
||||
return hysteria2Link(user,<Hysteria2>inbound, addrs, tlsClient)
|
||||
return hysteria2Link(user,<Hysteria2>inbound, addrs, tls)
|
||||
case InTypes.TUIC:
|
||||
return tuicLink(user,<TUIC>inbound, addrs, tlsClient)
|
||||
return tuicLink(user,<TUIC>inbound, addrs, tls)
|
||||
case InTypes.VLESS:
|
||||
return vlessLink(user,<VLESS>inbound, addrs, tlsClient)
|
||||
return vlessLink(user,<VLESS>inbound, addrs, tls)
|
||||
case InTypes.Trojan:
|
||||
return trojanLink(user,<Trojan>inbound, addrs, tlsClient)
|
||||
return trojanLink(user,<Trojan>inbound, addrs, tls)
|
||||
case InTypes.VMess:
|
||||
return vmessLink(user,<VMess>inbound, addrs, tlsClient)
|
||||
return vmessLink(user,<VMess>inbound, addrs, tls)
|
||||
}
|
||||
return []
|
||||
}
|
||||
@@ -71,17 +71,17 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function hysteriaLink(user: Client, inbound: Hysteria, addrs: any[], tlsClient: any): string[] {
|
||||
function hysteriaLink(user: Client, inbound: Hysteria, addrs: any[], tls: any): string[] {
|
||||
const auth = user.config.hysteria.auth_str
|
||||
const params = {
|
||||
upmbps: inbound.up_mbps?? null,
|
||||
downmbps: inbound.down_mbps?? null,
|
||||
auth: auth?? null,
|
||||
peer: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
peer: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
obfsParam: inbound.obfs?? null,
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||
insecure: tlsClient?.insecure ? 1 : null
|
||||
insecure: tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -105,12 +105,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('peer', a.server_name)
|
||||
} else {
|
||||
inbound.tls.server_name ? uri.searchParams.set('peer', inbound.tls.server_name) : uri.searchParams.delete('peer')
|
||||
tls?.server?.server_name ? uri.searchParams.set('peer', tls?.server?.server_name) : uri.searchParams.delete('peer')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('insecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -119,17 +119,17 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function hysteria2Link(user: Client, inbound: Hysteria2, addrs: any[], tlsClient: any): string[] {
|
||||
function hysteria2Link(user: Client, inbound: Hysteria2, addrs: any[], tls: any): string[] {
|
||||
const password = user.config.hysteria2.password
|
||||
const params = {
|
||||
upmbps: inbound.up_mbps?? null,
|
||||
downmbps: inbound.down_mbps?? null,
|
||||
sni: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
obfs: inbound.obfs?.type?? null,
|
||||
'obfs-password': inbound.obfs?.password?? null,
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||
insecure: tlsClient?.insecure ? 1 : null
|
||||
insecure: tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -153,12 +153,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('insecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -167,17 +167,17 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function naiveLink(user: Client, inbound: Naive, addrs: any[], tlsClient: any): string[] {
|
||||
function naiveLink(user: Client, inbound: Naive, addrs: any[], tls: any): string[] {
|
||||
const password = user.config.naive.password
|
||||
|
||||
let links = <string[]>[]
|
||||
if (addrs.length == 0) {
|
||||
const params = {
|
||||
padding: 1,
|
||||
peer: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
peer: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
const uri = `http2://${utf8ToBase64(user.name + ":" + password + "@" + location.hostname + ":" + inbound.listen_port)}`
|
||||
const paramsArray = []
|
||||
@@ -191,10 +191,10 @@ export namespace LinkUtil {
|
||||
addrs.forEach(a => {
|
||||
const params = {
|
||||
padding: 1,
|
||||
peer: a.server_name?.length>0 ? a.server_name : inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
peer: a.server_name?.length>0 ? a.server_name : tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||
allowInsecure: a.insecure ? 1 : tlsClient?.insecure ? 1 : null
|
||||
allowInsecure: a.insecure ? 1 : tls?.client?.insecure ? 1 : null
|
||||
}
|
||||
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + a.server + ":" + a.server_port)}`
|
||||
const paramsArray = []
|
||||
@@ -209,14 +209,14 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function tuicLink(user: Client, inbound: TUIC, addrs: any[], tlsClient: any): string[] {
|
||||
function tuicLink(user: Client, inbound: TUIC, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.tuic
|
||||
const params = {
|
||||
sni: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
congestion_control: inbound.congestion_control?? null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
disable_sni: tlsClient?.disable_sni ? 1 : null
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null,
|
||||
disable_sni: tls?.client?.disable_sni ? 1 : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -240,12 +240,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('allowInsecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -287,7 +287,7 @@ export namespace LinkUtil {
|
||||
return params
|
||||
}
|
||||
|
||||
function vlessLink(user: Client, inbound: VLESS, addrs: any[], tlsClient: any): string[] {
|
||||
function vlessLink(user: Client, inbound: VLESS, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.vless
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -295,14 +295,14 @@ export namespace LinkUtil {
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
flow: inbound.tls?.enabled ? u?.flow?? null : null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||
pbk: tlsClient?.reality?.public_key?? null,
|
||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||
security: tls?.server?.enabled? tls?.server?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
flow: tls?.server?.enabled ? u?.flow?? null : null,
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null,
|
||||
fp: tls?.client?.utls?.enabled ? tls.client.utls.fingerprint : null,
|
||||
pbk: tls?.client?.reality?.public_key?? null,
|
||||
sid: tls?.server?.reality?.enabled ? (tls?.server?.reality?.short_id?.length>0 ? tls.server.reality.short_id[RandomUtil.randomInt(tls.server.reality.short_id.length)] : null) : null
|
||||
}
|
||||
let links = <string[]>[]
|
||||
if (addrs.length == 0) {
|
||||
@@ -335,12 +335,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('allowInsecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -349,7 +349,7 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function trojanLink(user: Client, inbound: Trojan, addrs: any[], tlsClient: any): string[] {
|
||||
function trojanLink(user: Client, inbound: Trojan, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.trojan
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -357,13 +357,13 @@ export namespace LinkUtil {
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||
pbk: tlsClient?.reality?.public_key?? null,
|
||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||
security: tls?.server?.enabled? tls?.server?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: tls?.server?.alpn?.join(',')?? null,
|
||||
sni: tls?.server?.server_name?? null,
|
||||
allowInsecure: tls?.client?.insecure ? 1 : null,
|
||||
fp: tls?.client?.utls?.enabled ? tls.client.utls.fingerprint : null,
|
||||
pbk: tls?.client?.reality?.public_key?? null,
|
||||
sid: tls?.server?.reality?.enabled ? (tls?.server?.reality?.short_id?.length>0 ? tls?.server?.reality.short_id[RandomUtil.randomInt(tls?.server?.reality.short_id.length)] : null) : null
|
||||
}
|
||||
|
||||
let links = <string[]>[]
|
||||
@@ -397,12 +397,12 @@ export namespace LinkUtil {
|
||||
if (a.server_name?.length>0) {
|
||||
uri.searchParams.set('sni', a.server_name)
|
||||
} else {
|
||||
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||
tls?.server?.server_name ? uri.searchParams.set('sni', tls?.server?.server_name) : uri.searchParams.delete('sni')
|
||||
}
|
||||
if (a.insecure) {
|
||||
uri.searchParams.set('allowInsecure', '1')
|
||||
} else {
|
||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
tls?.client?.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||
}
|
||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||
links.push(uri.toString())
|
||||
@@ -411,7 +411,7 @@ export namespace LinkUtil {
|
||||
return links
|
||||
}
|
||||
|
||||
function vmessLink(user: Client, inbound: VMess, addrs: any[], tlsClient: any): string[] {
|
||||
function vmessLink(user: Client, inbound: VMess, addrs: any[], tls: any): string[] {
|
||||
const u = user.config.vmess
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -429,9 +429,9 @@ export namespace LinkUtil {
|
||||
path: tParams.path?? undefined,
|
||||
port: inbound.listen_port,
|
||||
ps: inbound.tag,
|
||||
sni: inbound.tls.server_name?? undefined,
|
||||
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
|
||||
allowInsecure: tlsClient?.insecure ? 1 : undefined
|
||||
sni: tls?.server?.server_name?? undefined,
|
||||
tls: tls?.server && Object.keys(tls.server).length>0? 'tls' : 'none',
|
||||
allowInsecure: tls?.client?.insecure ? 1 : undefined
|
||||
}
|
||||
let links = <string[]>[]
|
||||
if (addrs.length == 0) {
|
||||
|
||||
@@ -3,50 +3,49 @@ import { iTls } from "@/types/inTls"
|
||||
import { oTls } from "@/types/outTls"
|
||||
import RandomUtil from "./randomUtil"
|
||||
|
||||
export function fillData(out: any, inbound: Inbound, tlsClient: any) {
|
||||
if (Object.hasOwn(inbound, 'tls')) {
|
||||
const inb = <any>inbound
|
||||
addTls(out,inb.tls,tlsClient)
|
||||
export function fillData(inbound: Inbound, tls: any | null = null) {
|
||||
if (tls != null) {
|
||||
addTls(inbound.out_json, tls.server, tls.client)
|
||||
} else {
|
||||
delete out.tls
|
||||
delete inbound.out_json.tls
|
||||
}
|
||||
out.type = inbound.type
|
||||
out.tag = inbound.tag
|
||||
out.server = location.hostname
|
||||
out.server_port = inbound.listen_port
|
||||
inbound.out_json.type = inbound.type
|
||||
inbound.out_json.tag = inbound.tag
|
||||
inbound.out_json.server = location.hostname
|
||||
inbound.out_json.server_port = inbound.listen_port
|
||||
switch(inbound.type){
|
||||
case InTypes.HTTP: case InTypes.SOCKS: case InTypes.Mixed:
|
||||
return
|
||||
case InTypes.Shadowsocks:
|
||||
shadowsocksOut(out, <Shadowsocks>inbound)
|
||||
shadowsocksOut(inbound.out_json, <Shadowsocks>inbound)
|
||||
return
|
||||
case InTypes.ShadowTLS:
|
||||
shadowTlsOut(out, <ShadowTLS>inbound)
|
||||
shadowTlsOut(inbound.out_json, <ShadowTLS>inbound)
|
||||
return
|
||||
case InTypes.Hysteria:
|
||||
hysteriaOut(out, <Hysteria>inbound)
|
||||
hysteriaOut(inbound.out_json, <Hysteria>inbound)
|
||||
return
|
||||
case InTypes.Hysteria2:
|
||||
hysteria2Out(out, <Hysteria2>inbound)
|
||||
hysteria2Out(inbound.out_json, <Hysteria2>inbound)
|
||||
return
|
||||
case InTypes.TUIC:
|
||||
tuicOut(out, <TUIC>inbound)
|
||||
tuicOut(inbound.out_json, <TUIC>inbound)
|
||||
return
|
||||
case InTypes.VLESS:
|
||||
vlessOut(out, <VLESS>inbound)
|
||||
vlessOut(inbound.out_json, <VLESS>inbound)
|
||||
return
|
||||
case InTypes.Trojan:
|
||||
trojanOut(out, <Trojan>inbound)
|
||||
trojanOut(inbound.out_json, <Trojan>inbound)
|
||||
return
|
||||
case InTypes.VMess:
|
||||
vmessOut(out, <VMess>inbound)
|
||||
vmessOut(inbound.out_json, <VMess>inbound)
|
||||
return
|
||||
}
|
||||
Object.keys(out).forEach(key => delete out[key])
|
||||
Object.keys(inbound.out_json).forEach(key => delete inbound.out_json[key])
|
||||
}
|
||||
|
||||
function addTls(out: any, tls: iTls, tlsClient: oTls){
|
||||
out.tls = tlsClient
|
||||
out.tls = tlsClient?? {}
|
||||
if(tls.enabled) out.tls.enabled = tls.enabled
|
||||
if(tls.server_name) out.tls.server_name = tls.server_name
|
||||
if(tls.alpn) out.tls.alpn = tls.alpn
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
const WgUtil = {
|
||||
gf(init?: Float64Array): Float64Array {
|
||||
var r = new Float64Array(16)
|
||||
if (init) {
|
||||
for (var i = 0; i < init.length; ++i)
|
||||
r[i] = init[i]
|
||||
}
|
||||
return r
|
||||
},
|
||||
|
||||
pack(o: Float64Array, n: Float64Array): Float64Array {
|
||||
var b, m = this.gf(), t = this.gf()
|
||||
for (var i = 0; i < 16; ++i)
|
||||
t[i] = n[i]
|
||||
this.carry(t)
|
||||
this.carry(t)
|
||||
this.carry(t)
|
||||
for (var j = 0; j < 2; ++j) {
|
||||
m[0] = t[0] - 0xffed
|
||||
for (var i = 1; i < 15; ++i) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1)
|
||||
m[i - 1] &= 0xffff
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1)
|
||||
b = (m[15] >> 16) & 1
|
||||
m[14] &= 0xffff
|
||||
this.cswap(t, m, 1 - b)
|
||||
}
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[2 * i] = t[i] & 0xff
|
||||
o[2 * i + 1] = t[i] >> 8
|
||||
}
|
||||
},
|
||||
|
||||
carry(o: Float64Array) {
|
||||
var c
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536)
|
||||
o[i] &= 0xffff
|
||||
}
|
||||
},
|
||||
|
||||
cswap(p: Float64Array, q: Float64Array, b: number) {
|
||||
var t, c = ~(b - 1)
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
t = c & (p[i] ^ q[i])
|
||||
p[i] ^= t
|
||||
q[i] ^= t
|
||||
}
|
||||
},
|
||||
|
||||
add(o: Float64Array, a: Float64Array, b: Float64Array) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] + b[i]) | 0
|
||||
},
|
||||
|
||||
subtract(o: Float64Array, a: Float64Array, b: Float64Array) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] - b[i]) | 0
|
||||
},
|
||||
|
||||
multmod(o: Float64Array, a: Float64Array, b: Float64Array) {
|
||||
var t = new Float64Array(31)
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
for (var j = 0; j < 16; ++j)
|
||||
t[i + j] += a[i] * b[j]
|
||||
}
|
||||
for (var i = 0; i < 15; ++i)
|
||||
t[i] += 38 * t[i + 16]
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = t[i]
|
||||
this.carry(o)
|
||||
this.carry(o)
|
||||
},
|
||||
|
||||
invert(o: Float64Array, i: Float64Array) {
|
||||
var c = this.gf()
|
||||
for (var a = 0; a < 16; ++a)
|
||||
c[a] = i[a]
|
||||
for (var a = 253; a >= 0; --a) {
|
||||
this.multmod(c, c, c)
|
||||
if (a !== 2 && a !== 4)
|
||||
this.multmod(c, c, i)
|
||||
}
|
||||
for (var a = 0; a < 16; ++a)
|
||||
o[a] = c[a]
|
||||
},
|
||||
|
||||
clamp(z) {
|
||||
z[31] = (z[31] & 127) | 64
|
||||
z[0] &= 248
|
||||
},
|
||||
|
||||
generatePublicKey(privateKey: Uint8Array): Uint8Array {
|
||||
var r, z = new Uint8Array(32)
|
||||
var a = this.gf([1]),
|
||||
b = this.gf([9]),
|
||||
c = this.gf(),
|
||||
d = this.gf([1]),
|
||||
e = this.gf(),
|
||||
f = this.gf(),
|
||||
_121665 = this.gf([0xdb41, 1]),
|
||||
_9 = this.gf([9])
|
||||
for (var i = 0; i < 32; ++i)
|
||||
z[i] = privateKey[i]
|
||||
this.clamp(z)
|
||||
for (var i = 254; i >= 0; --i) {
|
||||
r = (z[i >>> 3] >>> (i & 7)) & 1
|
||||
this.cswap(a, b, r)
|
||||
this.cswap(c, d, r)
|
||||
this.add(e, a, c)
|
||||
this.subtract(a, a, c)
|
||||
this.add(c, b, d)
|
||||
this.subtract(b, b, d)
|
||||
this.multmod(d, e, e)
|
||||
this.multmod(f, a, a)
|
||||
this.multmod(a, c, a)
|
||||
this.multmod(c, b, e)
|
||||
this.add(e, a, c)
|
||||
this.subtract(a, a, c)
|
||||
this.multmod(b, a, a)
|
||||
this.subtract(c, d, f)
|
||||
this.multmod(a, c, _121665)
|
||||
this.add(a, a, d)
|
||||
this.multmod(c, c, a)
|
||||
this.multmod(a, d, f)
|
||||
this.multmod(d, b, _9)
|
||||
this.multmod(b, e, e)
|
||||
this.cswap(a, b, r)
|
||||
this.cswap(c, d, r)
|
||||
}
|
||||
this.invert(c, c)
|
||||
this.multmod(a, a, c)
|
||||
this.pack(z, a)
|
||||
return z
|
||||
},
|
||||
|
||||
generatePresharedKey(): Uint8Array {
|
||||
var privateKey = new Uint8Array(32)
|
||||
window.crypto.getRandomValues(privateKey)
|
||||
return privateKey
|
||||
},
|
||||
|
||||
generatePrivateKey(): Uint8Array {
|
||||
var privateKey = this.generatePresharedKey()
|
||||
this.clamp(privateKey)
|
||||
return privateKey
|
||||
},
|
||||
|
||||
encodeBase64(dest: Uint8Array, src: Uint8Array) {
|
||||
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63])
|
||||
for (var i = 0; i < 4; ++i)
|
||||
dest[i] = input[i] + 65 +
|
||||
(((25 - input[i]) >> 8) & 6) -
|
||||
(((51 - input[i]) >> 8) & 75) -
|
||||
(((61 - input[i]) >> 8) & 15) +
|
||||
(((62 - input[i]) >> 8) & 3)
|
||||
},
|
||||
|
||||
keyToBase64(key: Uint8Array): string {
|
||||
var i, base64 = new Uint8Array(44)
|
||||
for (i = 0; i < 32 / 3; ++i)
|
||||
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3))
|
||||
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]))
|
||||
base64[43] = 61
|
||||
return String.fromCharCode.apply(null, base64)
|
||||
},
|
||||
|
||||
keyFromBase64(encoded: string): Uint8Array {
|
||||
const binaryStr = atob(encoded)
|
||||
const bytes = new Uint8Array(binaryStr.length)
|
||||
for (let i = 0; i < binaryStr.length; i++) {
|
||||
bytes[i] = binaryStr.charCodeAt(i)
|
||||
}
|
||||
return bytes
|
||||
},
|
||||
|
||||
generateKeypair(secretKey?: string ='') {
|
||||
var privateKey = secretKey.length>0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey()
|
||||
var publicKey = this.generatePublicKey(privateKey)
|
||||
return {
|
||||
publicKey: this.keyToBase64(publicKey),
|
||||
privateKey: secretKey.length>0 ? secretKey : this.keyToBase64(privateKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default WgUtil
|
||||
@@ -34,6 +34,11 @@ const routes = [
|
||||
name: 'pages.outbounds',
|
||||
component: () => import('@/views/Outbounds.vue'),
|
||||
},
|
||||
{
|
||||
path: '/endpoints',
|
||||
name: 'pages.endpoints',
|
||||
component: () => import('@/views/Endpoints.vue'),
|
||||
},
|
||||
{
|
||||
path: '/rules',
|
||||
name: 'pages.rules',
|
||||
|
||||
@@ -3,6 +3,9 @@ import HttpUtils from '@/plugins/httputil'
|
||||
import { defineStore } from 'pinia'
|
||||
import { push } from 'notivue'
|
||||
import { i18n } from '@/locales'
|
||||
import { Inbound } from '@/types/inbounds'
|
||||
import { Outbound } from '@/types/outbounds'
|
||||
import { Endpoint } from '@/types/endpoints'
|
||||
|
||||
const Data = defineStore('Data', {
|
||||
state: () => ({
|
||||
@@ -10,23 +13,17 @@ const Data = defineStore('Data', {
|
||||
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
||||
subURI: "",
|
||||
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
||||
oldData: <{config: any, clients: any[], tlsConfigs: any[], inData: any[]}>{},
|
||||
config: <any>{},
|
||||
inbounds: <Inbound[]>[],
|
||||
outbounds: <Outbound[]>[],
|
||||
endpoints: <Endpoint[]>[],
|
||||
clients: [],
|
||||
tlsConfigs: [],
|
||||
inData: [],
|
||||
tlsConfigs: <any[]>[],
|
||||
}),
|
||||
actions: {
|
||||
async loadData() {
|
||||
const msg = await HttpUtils.get('api/load', this.lastLoad >0 ? {lu: this.lastLoad} : {} )
|
||||
if(msg.success) {
|
||||
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
||||
|
||||
// Set new data
|
||||
if (msg.obj.config) this.oldData.config = msg.obj.config
|
||||
if (msg.obj.clients) this.oldData.clients = msg.obj.clients
|
||||
if (msg.obj.tls) this.oldData.tlsConfigs = msg.obj.tls
|
||||
if (msg.obj.inData) this.oldData.inData = msg.obj.inData
|
||||
this.onlines = msg.obj.onlines
|
||||
if (msg.obj.lastLog) {
|
||||
push.error({
|
||||
@@ -37,84 +34,50 @@ const Data = defineStore('Data', {
|
||||
}
|
||||
|
||||
if (msg.obj.config) {
|
||||
// To avoid ref copy
|
||||
const data = JSON.parse(JSON.stringify(msg.obj))
|
||||
if (data.subURI) this.subURI = data.subURI
|
||||
if (data.config) this.config = data.config
|
||||
if (data.clients) this.clients = data.clients
|
||||
if (data.tls) this.tlsConfigs = data.tls
|
||||
if (data.inData) this.inData = data.inData
|
||||
this.setNewData(msg.obj)
|
||||
}
|
||||
}
|
||||
},
|
||||
async pushData() {
|
||||
const diff = {
|
||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config), null, 2),
|
||||
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||
inData: JSON.stringify(FindDiff.ArrObj(this.inData,this.oldData.inData, "inData"), null, 2),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.lastLoad = 0
|
||||
this.loadData()
|
||||
}
|
||||
setNewData(data: any) {
|
||||
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
||||
if (data.subURI) this.subURI = data.subURI
|
||||
if (data.config) this.config = data.config
|
||||
if (data.clients) this.clients = data.clients
|
||||
if (data.inbounds) this.inbounds = data.inbounds
|
||||
if (data.outbounds) this.outbounds = data.outbounds
|
||||
if (data.endpoints) this.endpoints = data.endpoints
|
||||
if (data.tls) this.tlsConfigs = data.tls
|
||||
},
|
||||
async delInbound(index: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
||||
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||
inData: <string|undefined> undefined,
|
||||
}
|
||||
|
||||
// Validate inData
|
||||
let invalidInData = <any[]>[]
|
||||
this.inData.forEach((d:any) => {
|
||||
const inboundIndex = this.config.inbounds.findIndex((i:any) => i.tag == d.tag)
|
||||
if (inboundIndex == -1) invalidInData.push({key: "inData", action: "del", index: d.id, obj: null})
|
||||
})
|
||||
if (invalidInData.length>0) {
|
||||
diff.inData = JSON.stringify(invalidInData)
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
async loadInbounds(ids: number[]): Promise<Inbound[]> {
|
||||
const options = ids.length > 0 ? {id: ids.join(",")} : {}
|
||||
const msg = await HttpUtils.get('api/inbounds', options)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
return msg.obj.inbounds
|
||||
}
|
||||
return <Inbound[]>[]
|
||||
},
|
||||
async delInData(id: number) {
|
||||
const diff = {
|
||||
inData: JSON.stringify([{key: "inData", action: "del", index: id, obj: null}])
|
||||
async save (object: string, action: string, data: any, userLinks: any[] | null = null, outJsons: any[] | null = null): Promise<boolean> {
|
||||
let postData = {
|
||||
object: object,
|
||||
action: action,
|
||||
data: JSON.stringify(data),
|
||||
userLinks: userLinks == null ? undefined : JSON.stringify(userLinks),
|
||||
outJsons: outJsons == null ? undefined : JSON.stringify(outJsons),
|
||||
}
|
||||
await HttpUtils.post('api/save',diff)
|
||||
},
|
||||
async delOutbound(index: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
||||
if (userLinks == null) {
|
||||
delete postData.userLinks
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delClient(id: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
|
||||
clients:JSON.stringify([{key: "clients", action: "del", index: id, obj: null}]),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delTls(id: number) {
|
||||
const diff = {
|
||||
tls:JSON.stringify([{key: "tls", action: "del", index: id, obj: null}]),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
const msg = await HttpUtils.post('api/save', postData)
|
||||
if (msg.success) {
|
||||
const objectName = ['tls', 'config'].includes(object) ? object : object.substring(0, object.length - 1)
|
||||
push.success({
|
||||
title: i18n.global.t('success'),
|
||||
duration: 5000,
|
||||
message: i18n.global.t('actions.' + action) + " " + i18n.global.t('objects.' + objectName)
|
||||
})
|
||||
this.setNewData(msg.obj)
|
||||
}
|
||||
return msg.success
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface Client {
|
||||
enable: boolean
|
||||
name: string
|
||||
config: Config
|
||||
inbounds: string[]
|
||||
inbounds: number[]
|
||||
links: Link[]
|
||||
volume: number
|
||||
expiry: number
|
||||
|
||||
@@ -83,8 +83,6 @@ interface RouteRuleSet {
|
||||
|
||||
interface Experimental {
|
||||
cache_file?: CacheFile
|
||||
clash_api?: ClashApi
|
||||
v2ray_api: V2rayApi
|
||||
}
|
||||
|
||||
interface CacheFile {
|
||||
@@ -94,27 +92,6 @@ interface CacheFile {
|
||||
store_fakeip?: boolean
|
||||
}
|
||||
|
||||
interface V2rayApi {
|
||||
listen: string
|
||||
stats: V2rayApiStats
|
||||
}
|
||||
|
||||
export interface V2rayApiStats {
|
||||
enabled: boolean
|
||||
inbounds: string[]
|
||||
outbounds: string[]
|
||||
users: string[]
|
||||
}
|
||||
|
||||
interface ClashApi {
|
||||
external_controller?: string
|
||||
external_ui?: string
|
||||
external_ui_download_url?: string
|
||||
external_ui_download_detour?: string
|
||||
secret?: string
|
||||
default_mode?: string
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
log: Log
|
||||
dns: Dns
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Dial } from "./outbounds"
|
||||
|
||||
export const EpTypes = {
|
||||
Wireguard: 'wireguard',
|
||||
}
|
||||
|
||||
type EpType = typeof EpTypes[keyof typeof EpTypes]
|
||||
|
||||
|
||||
|
||||
interface EndpointBasics {
|
||||
id: number
|
||||
type: EpType
|
||||
tag: string
|
||||
}
|
||||
|
||||
export interface WgPeer {
|
||||
address: string
|
||||
port: number
|
||||
public_key: string
|
||||
pre_shared_key?: string
|
||||
allowed_ips?: string[]
|
||||
persistent_keepalive_interval?: number
|
||||
reserved?: number[]
|
||||
}
|
||||
|
||||
export interface WireGuard extends EndpointBasics, Dial {
|
||||
system?: boolean
|
||||
name?: string
|
||||
mtu?: number
|
||||
address: string[]
|
||||
private_key: string
|
||||
listen_port: number,
|
||||
peers: WgPeer[]
|
||||
udp_timeout?: string,
|
||||
workers?: number
|
||||
}
|
||||
|
||||
// Create interfaces dynamically based on EpTypes keys
|
||||
type InterfaceMap = {
|
||||
[Key in keyof typeof EpTypes]: {
|
||||
type: string
|
||||
[otherProperties: string]: any; // You can add other properties as needed
|
||||
}
|
||||
}
|
||||
|
||||
// Create union type from InterfaceMap
|
||||
export type Endpoint = InterfaceMap[keyof InterfaceMap]
|
||||
|
||||
// Create defaultValues object dynamically
|
||||
const defaultValues: Record<EpType, Endpoint> = {
|
||||
wireguard: { type: EpTypes.Wireguard, address: ['10.0.0.2/32','fe80::2/128'], private_key: '', listen_port: 0, peers: [{ address: '', port: 0, public_key: ''}] },
|
||||
}
|
||||
|
||||
export function createEndpoint<T extends Endpoint>(type: string,json?: Partial<T>): Endpoint {
|
||||
const defaultObject: Endpoint = { ...defaultValues[type], ...(json || {}) }
|
||||
return defaultObject
|
||||
}
|
||||
@@ -24,6 +24,15 @@ export const InTypes = {
|
||||
|
||||
type InType = typeof InTypes[keyof typeof InTypes]
|
||||
|
||||
export interface Addr {
|
||||
server: string
|
||||
server_port: number
|
||||
tls?: boolean
|
||||
insecure?: boolean
|
||||
server_name?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface Listen {
|
||||
listen: string
|
||||
listen_port: number
|
||||
@@ -39,8 +48,12 @@ export interface Listen {
|
||||
}
|
||||
|
||||
interface InboundBasics extends Listen {
|
||||
id: number
|
||||
type: InType
|
||||
tag: string
|
||||
tls_id: number
|
||||
addrs?: Addr[]
|
||||
out_json?: any
|
||||
}
|
||||
|
||||
interface UsernamePass {
|
||||
@@ -87,7 +100,6 @@ export interface SOCKS extends InboundBasics {
|
||||
}
|
||||
export interface HTTP extends InboundBasics {
|
||||
users?: UsernamePass[]
|
||||
tls?: iTls,
|
||||
}
|
||||
export interface Shadowsocks extends InboundBasics {
|
||||
method: string
|
||||
@@ -125,7 +137,6 @@ export interface Hysteria extends InboundBasics {
|
||||
recv_window_client?: number
|
||||
max_conn_client?: number
|
||||
disable_mtu_discovery?: boolean
|
||||
tls: iTls
|
||||
}
|
||||
export interface ShadowTLS extends InboundBasics {
|
||||
version: 1|2|3
|
||||
@@ -139,7 +150,6 @@ export interface ShadowTLS extends InboundBasics {
|
||||
}
|
||||
export interface VLESS extends InboundBasics {
|
||||
users: VlessUser[]
|
||||
tls?: iTls
|
||||
multiplex?: iMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
@@ -149,7 +159,6 @@ export interface TUIC extends InboundBasics {
|
||||
auth_timeout?: string
|
||||
zero_rtt_handshake?: boolean
|
||||
heartbeat?: string
|
||||
tls: iTls
|
||||
}
|
||||
export interface Hysteria2 extends InboundBasics {
|
||||
up_mbps?: number
|
||||
@@ -160,7 +169,6 @@ export interface Hysteria2 extends InboundBasics {
|
||||
}
|
||||
users: NamePass[]
|
||||
ignore_client_bandwidth?: boolean
|
||||
tls: iTls
|
||||
masquerade?: string
|
||||
brutal_debug?: boolean
|
||||
}
|
||||
@@ -234,24 +242,26 @@ type userEnabledTypes = {
|
||||
vless: VLESS
|
||||
}
|
||||
|
||||
export const inboundWithUsers = ['mixed', 'socks:', 'http', 'shadowsocks', 'vmess', 'trojan', 'naive', 'hysteria', 'shadowtls', 'tuic', 'hysteria2', 'vless']
|
||||
|
||||
// Create union type from userEnabledTypes
|
||||
export type InboundWithUser = userEnabledTypes[keyof userEnabledTypes]
|
||||
type InboundWithUser = userEnabledTypes[keyof userEnabledTypes]
|
||||
|
||||
// Create defaultValues object dynamically
|
||||
const defaultValues: Record<InType, Inbound> = {
|
||||
direct: <Direct>{ type: InTypes.Direct },
|
||||
mixed: <Mixed>{ type: InTypes.Mixed },
|
||||
socks: <SOCKS>{ type: InTypes.SOCKS },
|
||||
http: <HTTP>{ type: InTypes.HTTP, tls: {} },
|
||||
http: <HTTP>{ type: InTypes.HTTP, tls_id: 0 },
|
||||
shadowsocks: <Shadowsocks>{ type: InTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||
vmess: <VMess>{ type: InTypes.VMess, users: <VmessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
||||
trojan: <Trojan>{ type: InTypes.Trojan, users: <NamePass[]>[], tls: {}, multiplex: {}, transport: {} },
|
||||
naive: <Naive>{ type: InTypes.Naive, users: <UsernamePass[]>[], tls: { enabled: true } },
|
||||
hysteria: <Hysteria>{ type: InTypes.Hysteria, users: <NameAuth[]>[], up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
||||
vmess: <VMess>{ type: InTypes.VMess, users: <VmessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
trojan: <Trojan>{ type: InTypes.Trojan, users: <NamePass[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
naive: <Naive>{ type: InTypes.Naive, users: <UsernamePass[]>[], tls_id: 0 },
|
||||
hysteria: <Hysteria>{ type: InTypes.Hysteria, users: <NameAuth[]>[], up_mbps: 100, down_mbps: 100, tls_id: 0 },
|
||||
shadowtls: <ShadowTLS>{ type: InTypes.ShadowTLS, version: 3, users: <NamePass[]>[], handshake: {}, handshake_for_server_name: {} },
|
||||
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls: { enabled: true } },
|
||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls: { enabled: true } },
|
||||
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
||||
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls_id: 0 },
|
||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls_id: 0 },
|
||||
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
tun: <Tun>{ type: InTypes.Tun, mtu: 9000, stack: 'system', udp_timeout: '5m', auto_route: false },
|
||||
redirect: <Redirect>{ type: InTypes.Redirect },
|
||||
tproxy: <TProxy>{ type: InTypes.TProxy },
|
||||
|
||||
@@ -10,7 +10,6 @@ export const OutTypes = {
|
||||
Shadowsocks: 'shadowsocks',
|
||||
VMess: 'vmess',
|
||||
Trojan: 'trojan',
|
||||
Wireguard: 'wireguard',
|
||||
Hysteria: 'hysteria',
|
||||
VLESS: 'vless',
|
||||
ShadowTLS: 'shadowtls',
|
||||
@@ -41,6 +40,7 @@ export interface Dial {
|
||||
}
|
||||
|
||||
interface OutboundBasics {
|
||||
id: number
|
||||
type: OutType
|
||||
tag: string
|
||||
}
|
||||
@@ -124,23 +124,6 @@ export interface Trojan extends OutboundBasics, Dial {
|
||||
transport?: Transport
|
||||
}
|
||||
|
||||
export interface WireGuard extends OutboundBasics, Dial {
|
||||
server?: string
|
||||
server_port?: number
|
||||
system_interface?: boolean
|
||||
gso?: boolean
|
||||
interface_name?: string
|
||||
local_address: string[]
|
||||
private_key: string
|
||||
peers?: WgPeer[]
|
||||
peer_public_key?: string
|
||||
pre_shared_key?: string
|
||||
reserved?: number[]
|
||||
workers?: number
|
||||
mtu?: number
|
||||
network?: "udp" | "tcp"
|
||||
}
|
||||
|
||||
export interface Hysteria extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
@@ -263,7 +246,6 @@ const defaultValues: Record<OutType, Outbound> = {
|
||||
shadowsocks: { type: OutTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||
vmess: { type: OutTypes.VMess, tls: {}, multiplex: {}, transport: {}, security: 'auto', global_padding: false },
|
||||
trojan: { type: OutTypes.Trojan, tls: {}, multiplex: {}, transport: {} },
|
||||
wireguard: { type: OutTypes.Wireguard, local_address: ['10.0.0.2/32','fe80::2/128'], private_key: '' },
|
||||
hysteria: { type: OutTypes.Hysteria, up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
||||
shadowtls: { type: OutTypes.ShadowTLS, version: 3, tls: { enabled: true } },
|
||||
vless: { type: OutTypes.VLESS, tls: {}, multiplex: {}, transport: {} },
|
||||
|
||||
+35
-109
@@ -1,4 +1,11 @@
|
||||
<template>
|
||||
<v-row style="margin-bottom: 10px;">
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn variant="outlined" color="warning" @click="saveConfig" :loading="loading" :disabled="stateChange">
|
||||
{{ $t('actions.save') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel :title="$t('basic.log.title')">
|
||||
<v-expansion-panel-text>
|
||||
@@ -11,6 +18,8 @@
|
||||
hide-details
|
||||
:label="$t('basic.log.level')"
|
||||
:items="levels"
|
||||
clearable
|
||||
@click:clear="delete appConfig.log.level"
|
||||
v-model="appConfig.log.level">
|
||||
</v-select>
|
||||
</v-col>
|
||||
@@ -215,128 +224,49 @@
|
||||
hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
Clash API
|
||||
<v-divider></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="3" lg="2">
|
||||
<v-switch v-model="enableClashApi" color="primary" :label="$t('enable')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_controller"
|
||||
hide-details
|
||||
label="External Controller"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_ui"
|
||||
hide-details
|
||||
label="External UI"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_ui_download_url"
|
||||
hide-details
|
||||
label="UI Download URL"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.external_ui_download_detour"
|
||||
hide-details
|
||||
label="UI Download detour"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.secret"
|
||||
hide-details
|
||||
label="Secret"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.clash_api.default_mode"
|
||||
hide-details
|
||||
label="Default Mode"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
V2Ray API
|
||||
<v-divider></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="3" lg="2">
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.v2ray_api.listen"
|
||||
hide-details
|
||||
:label="$t('objects.listen')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" lg="2">
|
||||
<v-switch v-model="appConfig.experimental.v2ray_api.stats.enabled"
|
||||
color="primary"
|
||||
:label="$t('stats.enable')"
|
||||
hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="appConfig.experimental.v2ray_api.stats.enabled">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('pages.inbounds')"
|
||||
multiple chips closable-chips
|
||||
:items="inboundTags"
|
||||
v-model="appConfig.experimental.v2ray_api.stats.inbounds">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('pages.outbounds')"
|
||||
multiple chips closable-chips
|
||||
:items="outboundTags"
|
||||
v-model="appConfig.experimental.v2ray_api.stats.outbounds">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('pages.clients')"
|
||||
multiple chips closable-chips
|
||||
:items="clientNames"
|
||||
v-model="appConfig.experimental.v2ray_api.stats.users">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data';
|
||||
import Dial from '@/components/Dial.vue';
|
||||
import { computed } from 'vue';
|
||||
import { Config, Ntp } from '@/types/config';
|
||||
import { Client } from '@/types/clients';
|
||||
import Data from '@/store/modules/data'
|
||||
import Dial from '@/components/Dial.vue'
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { Config, Ntp } from '@/types/config'
|
||||
import { Client } from '@/types/clients'
|
||||
import { FindDiff } from '@/plugins/utils'
|
||||
|
||||
const oldConfig = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
return appConfig.value.inbounds.map(i => i.tag)
|
||||
onMounted(async () => {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
})
|
||||
|
||||
const stateChange = computed(() => {
|
||||
return FindDiff.deepCompare(appConfig.value,oldConfig.value)
|
||||
})
|
||||
|
||||
const saveConfig = async () => {
|
||||
loading.value = true
|
||||
const success = await Data().save("config", "set", appConfig.value)
|
||||
if (success) {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
return appConfig.value.outbounds.map(o => o.tag)
|
||||
return [...Data().outbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||
})
|
||||
|
||||
const clientNames = computed((): string[] => {
|
||||
const clients = <Client[]>Data().clients
|
||||
return clients?.map(c => c.name)
|
||||
return Data().clients.map((c:any) => c.name)
|
||||
})
|
||||
|
||||
const levels = ["trace", "debug", "info", "warn", "error", "fatal", "panic"]
|
||||
@@ -384,8 +314,4 @@ const enableCacheFile = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const enableClashApi = computed({
|
||||
get() { return appConfig.value.experimental.clash_api != undefined },
|
||||
set(v:boolean) { v ? appConfig.value.experimental.clash_api = {} : delete appConfig.value.experimental.clash_api }
|
||||
})
|
||||
</script>
|
||||
+30
-112
@@ -3,10 +3,9 @@
|
||||
<ClientModal
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:index="modal.index"
|
||||
:id="modal.id"
|
||||
:data="modal.data"
|
||||
:groups="groups"
|
||||
:stats="modal.stats"
|
||||
:inboundTags="inboundTags"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
@@ -34,7 +33,7 @@
|
||||
/>
|
||||
<v-row justify="center" align="center">
|
||||
<v-col cols="auto">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-menu v-model="actionMenu" :close-on-content-click="false" location="bottom center">
|
||||
@@ -147,7 +146,6 @@
|
||||
<v-col cols="auto">
|
||||
<v-switch color="primary"
|
||||
v-model="item.enable"
|
||||
@update:model-value="buildInboundsUsers(item.inbounds)"
|
||||
hideDetails density="compact" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -162,7 +160,7 @@
|
||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds != ''">
|
||||
<span v-for="i in item.inbounds">{{ i }}<br /></span>
|
||||
<span v-for="i in item.inbounds">{{ inbounds.find(inb => inb.id == i)?.tag }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds.length }}
|
||||
</v-col>
|
||||
@@ -230,7 +228,7 @@
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" text="QR-Code"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.name)" v-if="v2rayStats.users.includes(item.name)">
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.name)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -324,7 +322,7 @@
|
||||
>
|
||||
mdi-qrcode
|
||||
</v-icon>
|
||||
<v-icon icon="mdi-chart-line" @click="showStats(item.name)" v-if="v2rayStats.users.includes(item.name)">
|
||||
<v-icon icon="mdi-chart-line" @click="showStats(item.name)">
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-icon>
|
||||
</template>
|
||||
@@ -349,8 +347,7 @@ import QrCode from '@/layouts/modals/QrCode.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Client, createClient } from '@/types/clients'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Config, V2rayApiStats } from '@/types/config'
|
||||
import { InTypes, Inbound,InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
||||
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import { HumanReadable } from '@/plugins/utils'
|
||||
import { i18n } from '@/locales'
|
||||
@@ -367,21 +364,13 @@ const isOnline = (cname: string) => computed(() => {
|
||||
return Data().onlines?.user ? Data().onlines.user.includes(cname) : false
|
||||
})
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental.v2ray_api.stats
|
||||
})
|
||||
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> appConfig.value?.inbounds
|
||||
return <Inbound[]> Data().inbounds?? []
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
const inboundTags = computed((): any[] => {
|
||||
if (!inbounds.value) return []
|
||||
return inbounds.value?.filter(i => i.tag != "" && Object.hasOwn(i,'users')).map(i => i.tag)
|
||||
return inbounds.value?.filter(i => i.tag != "" && inboundWithUsers.includes(i.type)).map(i => { return { title: i.tag, value: i.id } })
|
||||
})
|
||||
|
||||
const groups = computed((): string[] => {
|
||||
@@ -430,102 +419,44 @@ const groupBy = [
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
id: 0,
|
||||
data: "",
|
||||
stats: false,
|
||||
})
|
||||
|
||||
const delOverlay = ref(new Array<boolean>(clients.value.length).fill(false))
|
||||
|
||||
const showModal = (id: number) => {
|
||||
const index = id == -1 ? -1 : clients.value.findIndex(c => c.id == id)
|
||||
modal.value.index = index
|
||||
modal.value.data = index == -1 ? '' : JSON.stringify(clients.value[index])
|
||||
modal.value.stats = index == -1 ? false : v2rayStats.value.users.includes(clients.value[index].name)
|
||||
const showModal = async (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == 0 ? '' : JSON.stringify(clients.value.findLast(o => o.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:any, stats:boolean) => {
|
||||
const saveModal = async (data:any) => {
|
||||
// Check duplicate name
|
||||
const oldName = modal.value.index != -1 ? clients.value[modal.value.index].name : null
|
||||
const oldName = modal.value.id > 0 ? clients.value.findLast(i => i.id == modal.value.id)?.name : null
|
||||
if (data.name != oldName && clients.value.findIndex(c => c.name == data.name) != -1) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
|
||||
})
|
||||
return
|
||||
}
|
||||
if(modal.value.index == -1) {
|
||||
clients.value.push(data)
|
||||
} else {
|
||||
clients.value[modal.value.index] = data
|
||||
}
|
||||
|
||||
// Rebuild affected inbounds
|
||||
buildInboundsUsers(data.inbounds)
|
||||
|
||||
// Rebuild links
|
||||
data.links = updateLinks(data)
|
||||
const clientInbounds = data.inbounds.length == 0 ? [] : await Data().loadInbounds(data.inbounds)
|
||||
data.links = updateLinks(data, clientInbounds)
|
||||
|
||||
// Set Client Stats
|
||||
const sIndex = v2rayStats.value.users.findIndex(i => i == data.name) // Find if new user exists
|
||||
|
||||
if (oldName != data.name) {
|
||||
v2rayStats.value.users = v2rayStats.value.users.filter(item => item != oldName)
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
// Add if dos not exist
|
||||
if (data.name.length>0 && sIndex == -1) v2rayStats.value.users.push(data.name)
|
||||
} else {
|
||||
// Delete if exists
|
||||
if (sIndex != -1) v2rayStats.value.users.splice(sIndex,1)
|
||||
}
|
||||
|
||||
modal.value.visible = false
|
||||
// save data
|
||||
const success = await Data().save("clients", modal.value.id == 0 ? "new" : "edit", data)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
const buildInboundsUsers = (inboundTags:string[]) => {
|
||||
inboundTags.forEach(tag => {
|
||||
const inbound_index = inbounds.value.findIndex(i => i.tag == tag)
|
||||
if (inbound_index != -1){
|
||||
const users = <any>[]
|
||||
const newInbound = <InboundWithUser>inbounds.value[inbound_index]
|
||||
const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(tag))
|
||||
inboundClients.forEach(c => {
|
||||
// Remove flow in non tls VLESS
|
||||
if (newInbound.type == InTypes.VLESS) {
|
||||
const vlessInbound = <VLESS>newInbound
|
||||
if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
|
||||
}
|
||||
users.push(c.config[newInbound.type])
|
||||
})
|
||||
newInbound.users = users
|
||||
|
||||
// Exceptions for Naive and ShadowTLSv3
|
||||
if (users.length == 0){
|
||||
if (newInbound.type == InTypes.Naive) {
|
||||
newInbound.users = <any>[{}]
|
||||
} else {
|
||||
if (newInbound.type == InTypes.ShadowTLS){
|
||||
const ssTls = <ShadowTLS>newInbound
|
||||
if (ssTls.version == 3) newInbound.users = <any>[{}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inbounds.value[inbound_index] = newInbound
|
||||
}
|
||||
})
|
||||
}
|
||||
const updateLinks = (c:Client):Link[] => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(i => c.inbounds.includes(i.tag))
|
||||
const updateLinks = (c:Client, clientInbounds:Inbound[]):Link[] => {
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const tlsConfig = <any>Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
|
||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||
const addrs = cData ? <any[]>cData.addrs : []
|
||||
const uris = LinkUtil.linkGenerator(c,i, tlsConfig?.client?? {}, addrs)
|
||||
const tls = i.tls_id && i.tls_id>0 ? Data().tlsConfigs?.findLast((t:any) => t.id == i.tls_id) : undefined
|
||||
const uris = LinkUtil.linkGenerator(c,i, tls, i.addrs)
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
@@ -537,21 +468,10 @@ const updateLinks = (c:Client):Link[] => {
|
||||
|
||||
return links
|
||||
}
|
||||
const delClient = (id: number) => {
|
||||
const clientIndex = clients.value.findIndex(c => c.id === id)
|
||||
const oldData = createClient(clients.value[clientIndex])
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = clients.value.filter(i => i.name == oldData.name).length
|
||||
const sIndex = v2rayStats.value.users.findIndex(i => i == oldData.name)
|
||||
if (tagCounts == 1 && sIndex != -1){
|
||||
v2rayStats.value.users.splice(sIndex,1)
|
||||
}
|
||||
|
||||
clients.value.splice(clientIndex,1)
|
||||
buildInboundsUsers(oldData.inbounds)
|
||||
if (id>0) Data().delClient(id)
|
||||
delOverlay.value[clientIndex] = false
|
||||
const delClient = async (id: number) => {
|
||||
const index = clients.value.findIndex(c => c.id === id)
|
||||
const success = await Data().save("clients", "del", id)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const qrcode = ref({
|
||||
@@ -636,14 +556,12 @@ const closeBulk = () => {
|
||||
addBulkModal.value = false
|
||||
}
|
||||
|
||||
const saveBulk = (bulkClients: Client[], clientInbounds: string[], clientStats: boolean) => {
|
||||
const saveBulk = async (bulkClients: Client[], clientInbounds: number[]) => {
|
||||
const inboundData = clientInbounds.length == 0 ? [] : await Data().loadInbounds(clientInbounds)
|
||||
bulkClients.forEach((c,c_index) => {
|
||||
bulkClients[c_index].links = updateLinks(c)
|
||||
bulkClients[c_index].links = updateLinks(c, inboundData)
|
||||
})
|
||||
clients.value.push(...bulkClients)
|
||||
buildInboundsUsers(clientInbounds)
|
||||
// Stats
|
||||
if (clientStats) v2rayStats.value.users.push(...bulkClients.map(bc => bc.name))
|
||||
closeBulk()
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<EndpointVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:id="modal.id"
|
||||
:data="modal.data"
|
||||
:tags="endpointTags"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
<Stats
|
||||
v-model="stats.visible"
|
||||
:visible="stats.visible"
|
||||
:resource="stats.resource"
|
||||
:tag="stats.tag"
|
||||
@close="closeStats"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>endpoints" :key="item.tag">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.tag">
|
||||
<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>{{ $t('in.addr') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.address?.length>0 ? item.address[0] : '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('in.port') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.listen_port?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('types.wg.peers') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.peers.length?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<template v-if="onlines.includes(item.tag)">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<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="delOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-overlay
|
||||
v-model="delOverlay[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="delEndpoint(item.tag)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import EndpointVue from '@/layouts/modals/Endpoint.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Endpoint } from '@/types/endpoints';
|
||||
import { computed, ref } from 'vue'
|
||||
import { i18n } from '@/locales';
|
||||
import { push } from 'notivue';
|
||||
|
||||
const endpoints = computed((): Endpoint[] => {
|
||||
return <Endpoint[]> Data().endpoints
|
||||
})
|
||||
|
||||
const endpointTags = computed((): any[] => {
|
||||
return endpoints.value?.map((o:Endpoint) => o.tag)
|
||||
})
|
||||
|
||||
const onlines = computed(() => {
|
||||
return [...Data().onlines.inbound?? [], ...Data().onlines.outbound??[] ]
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: 0,
|
||||
data: "",
|
||||
})
|
||||
|
||||
let delOverlay = ref(new Array<boolean>)
|
||||
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == 0 ? '' : JSON.stringify(endpoints.value.findLast(o => o.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = async (data:Endpoint) => {
|
||||
// Check duplicate tag
|
||||
const oldTag = modal.value.id > 0 ? endpoints.value.findLast(i => i.id == modal.value.id)?.tag : null
|
||||
if (data.tag != oldTag && endpointTags.value.includes(data.tag)) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// save data
|
||||
const success = await Data().save("endpoints", modal.value.id == 0 ? "new" : "edit", data)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
visible: false,
|
||||
resource: "endpoint",
|
||||
tag: "",
|
||||
})
|
||||
|
||||
const delEndpoint = async (tag: string) => {
|
||||
const index = endpoints.value.findIndex(i => i.tag == tag)
|
||||
const success = await Data().save("endpoints", "del", tag)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const showStats = (tag: string) => {
|
||||
stats.value.tag = tag
|
||||
stats.value.visible = true
|
||||
}
|
||||
const closeStats = () => {
|
||||
stats.value.visible = false
|
||||
}
|
||||
</script>
|
||||
+84
-203
@@ -2,10 +2,7 @@
|
||||
<InboundVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:index="modal.index"
|
||||
:stats="modal.stats"
|
||||
:data="modal.data"
|
||||
:cData="modal.cData"
|
||||
:id="modal.id"
|
||||
:inTags="inTags"
|
||||
:outTags="outTags"
|
||||
:tlsConfigs="tlsConfigs"
|
||||
@@ -21,7 +18,7 @@
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -48,22 +45,25 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('objects.tls') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
|
||||
{{ item.tls_id > 0 ? $t('enable') : $t('disable') }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.clients') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="Object.hasOwn(item,'users')">
|
||||
<span v-for="u in findInbounsUsers(item)">{{ u }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ Array.isArray(item.users) ? item.users.length : '-' }}
|
||||
<template v-if="inboundWithUsers.includes(item.tag)">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="findInboundUsers(item.tag).length > 0">
|
||||
<span v-for="u in findInboundUsers(item.tag)">{{ u }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ findInboundUsers(item.tag).length }}
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<template v-if="onlines[index]">
|
||||
<template v-if="onlines.includes(item.tag)">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
@@ -72,7 +72,7 @@
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -89,12 +89,12 @@
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delInbound(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="error" variant="outlined" @click="delInbound(item.id)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)" v-if="v2rayStats.inbounds.includes(item.tag)">
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -108,9 +108,9 @@
|
||||
import Data from '@/store/modules/data'
|
||||
import InboundVue from '@/layouts/modals/Inbound.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Config, V2rayApiStats } from '@/types/config'
|
||||
import { computed, ref } from 'vue'
|
||||
import { InTypes, Inbound, InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
||||
import { Config } from '@/types/config'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||
import { Client } from '@/types/clients'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import { i18n } from '@/locales'
|
||||
@@ -122,17 +122,13 @@ const appConfig = computed((): Config => {
|
||||
})
|
||||
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> appConfig.value.inbounds
|
||||
return <Inbound[]> Data().inbounds
|
||||
})
|
||||
|
||||
const tlsConfigs = computed((): any[] => {
|
||||
return <any[]> Data().tlsConfigs
|
||||
})
|
||||
|
||||
const inData = computed((): any[] => {
|
||||
return <any[]> Data().inData
|
||||
})
|
||||
|
||||
const inTags = computed((): string[] => {
|
||||
return inbounds.value?.map(i => i.tag)
|
||||
})
|
||||
@@ -149,216 +145,101 @@ const onlines = computed(() => {
|
||||
return Data().onlines.inbound ? inbounds.value.map(i => Data().onlines.inbound.includes(i.tag)) : []
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
data: "",
|
||||
cData: "",
|
||||
stats: false,
|
||||
id: 0,
|
||||
})
|
||||
|
||||
let delOverlay = ref(new Array<boolean>)
|
||||
|
||||
const showModal = (index: number) => {
|
||||
modal.value.index = index
|
||||
if (index == -1){
|
||||
modal.value.data = ''
|
||||
modal.value.cData = ''
|
||||
modal.value.stats = false
|
||||
} else {
|
||||
modal.value.data = JSON.stringify(inbounds.value[index])
|
||||
modal.value.stats = v2rayStats.value.inbounds.includes(inbounds.value[index].tag)
|
||||
const inDataIndex = inData.value.findIndex(d => d.tag == inbounds.value[index].tag)
|
||||
modal.value.cData = inDataIndex == -1 ? '' : JSON.stringify(inData.value[inDataIndex])
|
||||
}
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.visible = true
|
||||
}
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Inbound, stats: boolean, tls_id: number, cData: any) => {
|
||||
const saveModal = async (data:Inbound) => {
|
||||
// Check duplicate tag
|
||||
const oldTag = modal.value.index != -1 ? inbounds.value[modal.value.index].tag : null
|
||||
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
|
||||
const oldInbound = modal.value.id > 0 ? inbounds.value.findLast(i => i.id == modal.value.id) : null
|
||||
if (data.tag != oldInbound?.tag && inTags.value.includes(data.tag)) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||
})
|
||||
return
|
||||
}
|
||||
if (cData.id != -1) {
|
||||
cData.tag = data.tag
|
||||
fillData(cData.outJson, data,tls_id>0 ? tlsConfigs.value.findLast(t => t.id == tls_id).client : {})
|
||||
|
||||
// Fill outjson
|
||||
if (data.out_json){
|
||||
fillData(data, data.tls_id > 0 ? tlsConfigs?.value.findLast((t:any) => t.id == data.tls_id) : null)
|
||||
}
|
||||
|
||||
let userLinkDiff = []
|
||||
// Update links
|
||||
if (data.id > 0 && oldInbound != null) {
|
||||
userLinkDiff = updateLinks(data,oldInbound)
|
||||
}
|
||||
|
||||
// New or Edit
|
||||
if (modal.value.index == -1) {
|
||||
inbounds.value.push(data)
|
||||
if (stats && data.tag.length>0) {
|
||||
v2rayStats.value.inbounds.push(data.tag)
|
||||
}
|
||||
if (cData.id != -1){
|
||||
inData.value.push(cData)
|
||||
}
|
||||
} else {
|
||||
const oldTag = inbounds.value[modal.value.index].tag
|
||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
||||
|
||||
// Update tls preset
|
||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(oldTag))
|
||||
if (oldTlsConfigIndex != -1){
|
||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
|
||||
}
|
||||
|
||||
if (oldTag != data.tag) {
|
||||
v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
|
||||
changeClientInboundsTag(oldTag,data.tag)
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
// Add if dos not exist
|
||||
if (data.tag.length>0 && sIndex == -1) v2rayStats.value.inbounds.push(data.tag)
|
||||
} else {
|
||||
// Delete if exists
|
||||
if (sIndex != -1) v2rayStats.value.inbounds.splice(sIndex,1)
|
||||
}
|
||||
|
||||
inbounds.value[modal.value.index] = data
|
||||
const inDataIndex = inData.value.findIndex(indata => indata.tag == oldTag)
|
||||
if (cData.id != -1) {
|
||||
if (inDataIndex == -1){
|
||||
inData.value.push(cData)
|
||||
} else {
|
||||
inData.value[inDataIndex] = cData
|
||||
}
|
||||
} else if (inDataIndex != -1) {
|
||||
Data().delInData(inData.value[inDataIndex].id)
|
||||
inData.value.splice(inDataIndex,1)
|
||||
}
|
||||
}
|
||||
// Update tls preset
|
||||
if (tls_id>0) {
|
||||
tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
|
||||
tlsConfigs.value.sort()
|
||||
}
|
||||
|
||||
if (Object.hasOwn(data,'users')) {
|
||||
// Set users
|
||||
data = buildInboundsUsers(data)
|
||||
// Update links
|
||||
updateLinks(data)
|
||||
}
|
||||
modal.value.visible = false
|
||||
// save data
|
||||
const success = await Data().save("inbounds", modal.value.id == 0 ? "new" : "edit", data, userLinkDiff)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
const updateLinks = (i: any) => {
|
||||
if(i.users){
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
|
||||
uClients.forEach((u:Client) => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => u.inbounds.includes(inb.tag))
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? {}
|
||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||
const addrs = cData ? <any[]>cData.addrs : []
|
||||
const uris = LinkUtil.linkGenerator(u,i, tlsClient, addrs)
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
}
|
||||
})
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...newLinks, ...links.filter(l => l.type != 'local')]
|
||||
const updateLinks = (i: Inbound, o: Inbound): any[] => {
|
||||
let diff = <any[]>[]
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.id))
|
||||
if (uClients.length == 0) return diff
|
||||
|
||||
u.links = links
|
||||
if (inboundWithUsers.includes(o.type) && !inboundWithUsers.includes(i.type)){
|
||||
// Remove old inbound links if new type does not support users
|
||||
uClients.forEach((u:Client) => {
|
||||
u.inbounds = u.inbounds.filter(i => i != o.id)
|
||||
const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != o.tag)
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...otherLocalLinks, ...links.filter(l => l.type != 'local')]
|
||||
|
||||
diff.push({ id: u.id, links: links, inbounds: u.inbounds })
|
||||
})
|
||||
} else if(inboundWithUsers.includes(i.type)){
|
||||
// Add new inbound links if new type supports users
|
||||
const tls = tlsConfigs?.value.findLast((t:any) => t.id == i.tls_id)
|
||||
uClients.forEach((u:Client) => {
|
||||
const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != i.tag)
|
||||
const uris = LinkUtil.linkGenerator(u,i, tls, i.addrs)
|
||||
let newLinks = <Link[]>[]
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
}
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...otherLocalLinks, ...newLinks, ...links.filter(l => l.type != 'local')]
|
||||
|
||||
diff.push({ id: u.id, links: links, inbounds: u.inbounds })
|
||||
})
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
const delInbound = (index: number) => {
|
||||
const delInbound = async (id: number) => {
|
||||
const index = inbounds.value.findIndex(i => i.id == id)
|
||||
const inb = inbounds.value[index]
|
||||
inbounds.value.splice(index,1)
|
||||
const tag = inb.tag
|
||||
|
||||
if (Object.hasOwn(inb,'users')) {
|
||||
const inbU = <InboundWithUser>inb
|
||||
if (inbU.users && inbU.users.length>0){
|
||||
inbU.users.forEach((u:any) => {
|
||||
const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.name == c.name)
|
||||
if (c_index != -1) {
|
||||
clients.value[c_index].inbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag)
|
||||
clients.value[c_index].links = clients.value[c_index].links.filter((x:any) => x.remark!=tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Delete binded tls if exists
|
||||
if (Object.hasOwn(inb,'tls')) {
|
||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(inb.tag))
|
||||
if (oldTlsConfigIndex != -1){
|
||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != inb.tag)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = inbounds.value.filter(i => i.tag == inb.tag).length
|
||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == inb.tag)
|
||||
if (tagCounts == 1 && sIndex != -1){
|
||||
v2rayStats.value.inbounds.splice(sIndex,1)
|
||||
}
|
||||
if (index < Data().oldData.config.inbounds.length){
|
||||
Data().delInbound(index)
|
||||
} else {
|
||||
// Delete new inbound's inData if exists
|
||||
const inDataIndex = Data().inData.findIndex((d:any) => d.tag == tag)
|
||||
if (inDataIndex != -1) Data().inData.splice(inDataIndex, 1)
|
||||
}
|
||||
delOverlay.value[index] = false
|
||||
}
|
||||
const buildInboundsUsers = (inbound:any):Inbound => {
|
||||
const users = <any>[]
|
||||
const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(inbound.tag))
|
||||
inboundClients.forEach(c => {
|
||||
// Remove flow in non tls VLESS
|
||||
if (inbound.type == InTypes.VLESS) {
|
||||
const vlessInbound = <VLESS>inbound
|
||||
if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
|
||||
}
|
||||
users.push(c.config[inbound.type])
|
||||
})
|
||||
inbound.users = users
|
||||
|
||||
// Exceptions for Naive and ShadowTLSv3
|
||||
if (users.length == 0){
|
||||
if (inbound.type == InTypes.Naive){
|
||||
inbound.users = <any>[{}]
|
||||
} else {
|
||||
if (inbound.type == InTypes.ShadowTLS){
|
||||
const ssTls = <ShadowTLS>inbound
|
||||
if (ssTls.version == 3) inbound.users = <any>[{}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <Inbound>inbound
|
||||
}
|
||||
const changeClientInboundsTag = (oldtag: string, newTag:string) => {
|
||||
clients.value.forEach((c, c_index) => {
|
||||
const inbound_index = c.inbounds.findIndex(i => i == oldtag)
|
||||
if (inbound_index != -1) {
|
||||
c.inbounds[inbound_index] = newTag
|
||||
clients.value[c_index].inbounds = c.inbounds
|
||||
}
|
||||
let diff = <any[]>[]
|
||||
// delete inbound in client table
|
||||
const inboundClients = clients.value.filter(c => c.inbounds.includes(id))
|
||||
inboundClients.forEach((c:Client) => {
|
||||
c.inbounds = c.inbounds.filter((x:number) => x!=id)
|
||||
c.links = c.links.filter((x:any) => x.remark!=tag)
|
||||
diff.push({ id: c.id, links: c.links, inbounds: c.inbounds })
|
||||
})
|
||||
}
|
||||
const findInbounsUsers = (inbound: InboundWithUser): string[] => {
|
||||
if (inbound.users === null || !Array.isArray(inbound.users) || inbound.users.length == 0) return []
|
||||
|
||||
const users = inbound.users.map(user => "username" in user ? user.username : user.name)
|
||||
return users
|
||||
const success = await Data().save("inbounds", "del", tag, diff)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const findInboundUsers = (i: Inbound): string[] => {
|
||||
return clients.value.filter(c => c.inbounds.includes(i.id)).map(c => c.name)
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:id="modal.id"
|
||||
:stats="modal.stats"
|
||||
:data="modal.data"
|
||||
:tags="outboundTags"
|
||||
@close="closeModal"
|
||||
@@ -18,7 +17,7 @@
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -51,7 +50,7 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<template v-if="onlines[index]">
|
||||
<template v-if="onlines.includes(item.tag)">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
@@ -60,7 +59,7 @@
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -77,12 +76,12 @@
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delOutbound(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="error" variant="outlined" @click="delOutbound(item.tag)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)" v-if="v2rayStats.outbounds.includes(item.tag)">
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -96,79 +95,56 @@
|
||||
import Data from '@/store/modules/data'
|
||||
import OutboundVue from '@/layouts/modals/Outbound.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Config, V2rayApiStats } from '@/types/config';
|
||||
import { Outbound } from '@/types/outbounds';
|
||||
import { computed, ref } from 'vue'
|
||||
import { i18n } from '@/locales';
|
||||
import { push } from 'notivue';
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const outbounds = computed((): Outbound[] => {
|
||||
return <Outbound[]> appConfig.value.outbounds
|
||||
return <Outbound[]> Data().outbounds
|
||||
})
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
const outboundTags = computed((): any[] => {
|
||||
return outbounds.value?.map((o:Outbound) => o.tag)
|
||||
})
|
||||
|
||||
const onlines = computed(() => {
|
||||
return Data().onlines.outbound ? outbounds.value.map(i => Data().onlines.outbound.includes(i.tag)) : []
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
||||
return Data().onlines.outbound?? []
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: -1,
|
||||
id: 0,
|
||||
data: "",
|
||||
stats: false,
|
||||
})
|
||||
|
||||
let delOverlay = ref(new Array<boolean>)
|
||||
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == -1 ? '' : JSON.stringify(outbounds.value[id])
|
||||
modal.value.stats = id == -1 ? false : v2rayStats.value.outbounds.includes(outbounds.value[id].tag)
|
||||
modal.value.data = id == 0 ? '' : JSON.stringify(outbounds.value.findLast(o => o.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Outbound, stats: boolean) => {
|
||||
const saveModal = async (data:Outbound) => {
|
||||
// Check duplicate tag
|
||||
const oldTag = modal.value.id != -1 ? outbounds.value[modal.value.id].tag : null
|
||||
const oldTag = modal.value.id > 0 ? outbounds.value.findLast(i => i.id == modal.value.id)?.tag : null
|
||||
if (data.tag != oldTag && outboundTags.value.includes(data.tag)) {
|
||||
push.error({
|
||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||
})
|
||||
return
|
||||
}
|
||||
// New or Edit
|
||||
if (modal.value.id == -1) {
|
||||
outbounds.value.push(data)
|
||||
if (stats && data.tag.length>0) {
|
||||
v2rayStats.value.outbounds.push(data.tag)
|
||||
}
|
||||
} else {
|
||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
||||
|
||||
if (stats) {
|
||||
// Add if dos not exist
|
||||
if (data.tag.length>0 && sIndex == -1) v2rayStats.value.outbounds.push(data.tag)
|
||||
} else {
|
||||
// Delete if exists
|
||||
if (sIndex != -1) v2rayStats.value.outbounds.splice(sIndex,1)
|
||||
}
|
||||
|
||||
outbounds.value[modal.value.id] = data
|
||||
// save data
|
||||
const success = await Data().save("outbounds", modal.value.id == 0 ? "new" : "edit", data)
|
||||
if (!success) {
|
||||
return
|
||||
}
|
||||
|
||||
modal.value.visible = false
|
||||
}
|
||||
|
||||
@@ -178,21 +154,10 @@ const stats = ref({
|
||||
tag: "",
|
||||
})
|
||||
|
||||
const delOutbound = (index: number) => {
|
||||
const inb = outbounds.value[index]
|
||||
outbounds.value.splice(index,1)
|
||||
const tag = inb.tag
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = outbounds.value.filter(i => i.tag == inb.tag).length
|
||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == inb.tag)
|
||||
if (tagCounts == 1 && sIndex != -1){
|
||||
v2rayStats.value.outbounds.splice(sIndex,1)
|
||||
}
|
||||
if (index < Data().oldData.config.outbounds.length){
|
||||
Data().delOutbound(index)
|
||||
}
|
||||
delOverlay.value[index] = false
|
||||
const delOutbound = async (tag: string) => {
|
||||
const index = outbounds.value.findIndex(i => i.tag == tag)
|
||||
const success = await Data().save("outbounds", "del", tag)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const showStats = (tag: string) => {
|
||||
@@ -202,4 +167,8 @@ const showStats = (tag: string) => {
|
||||
const closeStats = () => {
|
||||
stats.value.visible = false
|
||||
}
|
||||
|
||||
function awaitData() {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
</script>
|
||||
@@ -24,6 +24,9 @@
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showRuleModal(-1)" style="margin: 0 5px;">{{ $t('rule.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showRulesetModal(-1)" style="margin: 0 5px;">{{ $t('ruleset.add') }}</v-btn>
|
||||
<v-btn variant="outlined" color="warning" @click="saveConfig" :loading="loading" :disabled="stateChange">
|
||||
{{ $t('actions.save') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -144,16 +147,37 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, onMounted } 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'
|
||||
import { FindDiff } from '@/plugins/utils'
|
||||
|
||||
const oldConfig = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
})
|
||||
|
||||
const stateChange = computed(() => {
|
||||
return FindDiff.deepCompare(appConfig.value,oldConfig.value)
|
||||
})
|
||||
|
||||
const saveConfig = async () => {
|
||||
loading.value = true
|
||||
const success = await Data().save("config", "set", appConfig.value)
|
||||
if (success) {
|
||||
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const clients = computed((): string[] => {
|
||||
return Data().clients.map((c:any) => c.name)
|
||||
})
|
||||
@@ -189,11 +213,11 @@ const rulesetTags = computed((): any[] => {
|
||||
})
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
return appConfig.value.outbounds?.map((o:any) => o.tag)
|
||||
return [...Data().outbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
return appConfig.value.inbounds?.map((i:any) => i.tag)
|
||||
return [...Data().inbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||
})
|
||||
|
||||
let delRuleOverlay = ref(new Array<boolean>)
|
||||
@@ -268,7 +292,7 @@ const draggedItemIndex = ref(null);
|
||||
|
||||
const onDragStart = (index: any) => {
|
||||
draggedItemIndex.value = index;
|
||||
};
|
||||
}
|
||||
|
||||
const onDrop = (index: any) => {
|
||||
if (draggedItemIndex.value !== null) {
|
||||
@@ -278,5 +302,5 @@ const onDrop = (index: any) => {
|
||||
rules.value.splice(index, 0, draggedItem);
|
||||
draggedItemIndex.value = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
+82
-71
@@ -2,19 +2,19 @@
|
||||
<TlsVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:index="modal.index"
|
||||
:id="modal.id"
|
||||
:data="modal.data"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>tlsConfigs" :key="item.id">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="(item.id? item.id + '. ' : '*') + item.name">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.name">
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
{{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
|
||||
</v-card-subtitle>
|
||||
@@ -22,10 +22,13 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds?.length>0">
|
||||
<span v-for="i in item.inbounds">{{ i }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds?.length }}
|
||||
<template v-if="tlsInbounds(item.id).length>0">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom">
|
||||
<span v-for="i in tlsInbounds(item.id)">{{ i }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ tlsInbounds.length }}
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -49,11 +52,11 @@
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn v-if="item.inbounds?.length == 0" icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||
<v-btn v-if="tlsInbounds(item.id).length == 0" icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -66,12 +69,12 @@
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delTls(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="error" variant="outlined" @click="delTls(item.id)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-content-duplicate" @click="clone(index)">
|
||||
<v-btn icon="mdi-content-duplicate" @click="clone(item)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.clone')"></v-tooltip>
|
||||
</v-btn>
|
||||
@@ -85,8 +88,7 @@
|
||||
import TlsVue from '@/layouts/modals/Tls.vue'
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Config } from '@/types/config'
|
||||
import { Inbound } from '@/types/inbounds'
|
||||
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||
import { Client } from '@/types/clients'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import { fillData } from '@/plugins/outJson'
|
||||
@@ -95,13 +97,13 @@ const tlsConfigs = computed((): any[] => {
|
||||
return Data().tlsConfigs
|
||||
})
|
||||
|
||||
const inbounds = computed((): any[] => {
|
||||
return <any[]>(<Config>Data().config)?.inbounds
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return Data().inbounds
|
||||
})
|
||||
|
||||
const inData = computed((): any[] => {
|
||||
return <any[]> Data().inData
|
||||
})
|
||||
const tlsInbounds = (id: number): string[] => {
|
||||
return inbounds.value.filter(i => i.tls_id == id).map(i => i.tag)
|
||||
}
|
||||
|
||||
const clients = computed((): any[] => {
|
||||
return <Client[]>Data().clients
|
||||
@@ -109,21 +111,20 @@ const clients = computed((): any[] => {
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
id: 0,
|
||||
data: "",
|
||||
})
|
||||
|
||||
const delOverlay = ref(new Array<boolean>(tlsConfigs.value.length).fill(false))
|
||||
|
||||
const showModal = (index: number) => {
|
||||
modal.value.index = index
|
||||
modal.value.data = index == -1 ? '{}' : JSON.stringify(tlsConfigs.value[index])
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == 0 ? '{}' : JSON.stringify(tlsConfigs.value.findLast(t => t.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
const clone = (index: number) => {
|
||||
let data = JSON.parse(JSON.stringify(tlsConfigs.value[index]))
|
||||
const clone = (obj: any) => {
|
||||
let data = JSON.parse(JSON.stringify(obj))
|
||||
data.id = 0
|
||||
data.inbounds = []
|
||||
while (tlsConfigs.value.findIndex(t => t.name == data.name) != -1){
|
||||
data.name += "-copy"
|
||||
}
|
||||
@@ -132,57 +133,67 @@ const clone = (index: number) => {
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:any) => {
|
||||
const saveModal = async (data:any) => {
|
||||
let outJsons = <any[]>[]
|
||||
let userLinks = <any[]>[]
|
||||
// New or Edit
|
||||
if (modal.value.index == -1) {
|
||||
tlsConfigs.value.push(data)
|
||||
} else {
|
||||
tlsConfigs.value[modal.value.index] = data
|
||||
inbounds?.value.filter(i => tlsConfigs.value[modal.value.index].inbounds.includes(i.tag)).forEach(i =>{
|
||||
if (i.tls != undefined) i.tls = data.server
|
||||
updateInData(i,data.client)
|
||||
updateLinks(i,data.client)
|
||||
})
|
||||
}
|
||||
modal.value.visible = false
|
||||
}
|
||||
|
||||
const delTls = (index: number) => {
|
||||
if (index < Data().oldData.tlsConfigs.length){
|
||||
Data().delTls(tlsConfigs.value[index].id)
|
||||
}
|
||||
tlsConfigs.value.splice(index,1)
|
||||
delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const updateLinks = (i:any,tlsClient:any) => {
|
||||
if(i.users){
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
|
||||
uClients.forEach((client:any) => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||
const addrs = cData ? <any[]>cData.addrs : []
|
||||
const uris = LinkUtil.linkGenerator(client,i, tlsClient, addrs)
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
if (modal.value.id > 0) {
|
||||
const inboundIds = inbounds.value.filter(i => i.tls_id == modal.value.id).map(i => i.id)
|
||||
if (inboundIds.length > 0) {
|
||||
const tlsInbounds = inboundIds.length == 0 ? [] : await Data().loadInbounds(inboundIds)
|
||||
for (const inbound of tlsInbounds) {
|
||||
// Fill outjson
|
||||
if (inbound.out_json) {
|
||||
fillData(inbound, data)
|
||||
}
|
||||
})
|
||||
let links = client.links && client.links.length>0? client.links : <Link[]>[]
|
||||
links = [...newLinks, ...links.filter((l:Link) => l.type != 'local')]
|
||||
outJsons.push({tag: inbound.tag,out_jsons: inbound.out_json})
|
||||
// Update links
|
||||
const diff = updateLinks(inbound)
|
||||
diff.forEach((d: any) => {
|
||||
if (userLinks.findIndex(l => l.id == d.id) == -1) {
|
||||
userLinks.push(d)
|
||||
} else {
|
||||
const index = userLinks.findIndex(l => l.id == d.id)
|
||||
userLinks[index].links = d.links
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
client.links = links
|
||||
}
|
||||
const success = await Data().save("tls", data.id == 0 ? "new" : "edit", data, userLinks.length > 0 ? null: userLinks, outJsons.length > 0 ? null: outJsons)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
|
||||
const delTls = async (id: number) => {
|
||||
const index = tlsConfigs.value.findIndex(t => t.id == id)
|
||||
const success = await Data().save("tls", "del", id)
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const updateLinks = (i: Inbound): any[] => {
|
||||
let diff = <any[]>[]
|
||||
if(inboundWithUsers.includes(i.type) && i.id != 0){
|
||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.id))
|
||||
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.id == i.tls_id)
|
||||
uClients.forEach((u:Client) => {
|
||||
const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != i.tag)
|
||||
const uris = LinkUtil.linkGenerator(u,i, tlsClient, i.addrs)
|
||||
let newLinks = <Link[]>[]
|
||||
if (uris.length>0){
|
||||
uris.forEach(uri => {
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
})
|
||||
}
|
||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
||||
links = [...otherLocalLinks, ...newLinks, ...links.filter(l => l.type != 'local')]
|
||||
|
||||
u.links = links
|
||||
diff.push({ id: u.id, links: links })
|
||||
})
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
const updateInData = (i:any, c:any) => {
|
||||
const inDataIndex = inData.value.findIndex(d => d.tag == i.tag)
|
||||
if (inDataIndex != -1) {
|
||||
fillData(inData.value[inDataIndex].outJson, i, c)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user