Outbound modal
This commit is contained in:
@@ -146,6 +146,8 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
||||
case "outbounds":
|
||||
if change.Action == "edit" {
|
||||
newConfig.Outbounds[change.Index] = rawObject
|
||||
} else if change.Action == "del" {
|
||||
newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...)
|
||||
} else {
|
||||
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<v-card subtitle="Headers">
|
||||
<v-row v-for="(header, index) in hdrs">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Key"
|
||||
hide-details
|
||||
@input="update_key(index,$event.target.value)"
|
||||
v-model="header.name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Value"
|
||||
hide-details
|
||||
@input="update_value(index,$event.target.value)"
|
||||
append-icon="mdi-delete"
|
||||
@click:append="del_header(index)"
|
||||
v-model="header.value">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn hide-details @click="add_header" density="compact" icon="mdi-plus"></v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
type Header = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
add_header() {
|
||||
this.hdrs = [...this.hdrs, {name: "Host", value: ""}]
|
||||
},
|
||||
del_header(i:number) {
|
||||
let h = this.hdrs
|
||||
h.splice(i,1)
|
||||
this.hdrs = h
|
||||
},
|
||||
update_key(i:number,k:string) {
|
||||
let h = this.hdrs
|
||||
h[i].name = k
|
||||
this.hdrs = h
|
||||
},
|
||||
update_value(i:number,v:string) {
|
||||
let h = this.hdrs
|
||||
h[i].value = v
|
||||
this.hdrs = h
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hdrs: {
|
||||
get() :Header[] {
|
||||
let headers: Header[] = []
|
||||
const h = this.$props.data.headers
|
||||
if (h) {
|
||||
Object.keys(h).forEach(key => {
|
||||
if (Array.isArray(h[key])){
|
||||
h[key].forEach((v:string) => headers.push({ name: key, value: v }))
|
||||
} else {
|
||||
headers.push({ name: key, value: h[key] })
|
||||
}
|
||||
})
|
||||
}
|
||||
return headers
|
||||
},
|
||||
set(v:Header[]) {
|
||||
if (v.length>0) {
|
||||
let headers:any = {}
|
||||
v.forEach((h:Header) => {
|
||||
if (headers[h.name]) {
|
||||
if (Array.isArray(headers[h.name])) {
|
||||
headers[h.name].push(h.value)
|
||||
} else {
|
||||
headers[h.name] = [headers[h.name], h.value]
|
||||
}
|
||||
} else {
|
||||
headers[h.name] = h.value
|
||||
}
|
||||
})
|
||||
this.$props.data.headers = headers
|
||||
} else {
|
||||
this.$props.data.headers = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('in.multiplex')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Enable Multiplex" v-model="muxEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
||||
<v-switch color="primary" label="Reject Non-Padded" v-model="mux.padding" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
||||
<v-switch color="primary" label="Enable Brutal" v-model="burtalEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="mux.brutal?.enabled">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Uplink Bandwidth"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
v-model.number="up_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Downlink Bandwidth"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
min="0"
|
||||
v-model.number="down_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { iMultiplex } from '@/types/inMultiplex'
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
mux(): iMultiplex {
|
||||
return <iMultiplex> this.$props.inbound.multiplex
|
||||
},
|
||||
muxEnable: {
|
||||
get(): boolean { return this.$props.inbound.multiplex ? this.mux.enabled : false },
|
||||
set(newValue:boolean) { this.$props.inbound.multiplex = newValue ? { enabled: newValue } : {} }
|
||||
},
|
||||
burtalEnable: {
|
||||
get(): boolean { return this.mux.brutal ? this.mux.brutal.enabled : false },
|
||||
set(newValue:boolean) { this.mux.brutal = { enabled: newValue, up_mbps: 100, down_mbps: 100 } }
|
||||
},
|
||||
down_mbps: {
|
||||
get() { return this.mux.brutal && this.mux.brutal.down_mbps ? this.mux.brutal.down_mbps : 0 },
|
||||
set(newValue:any) {
|
||||
if (this.mux.brutal){
|
||||
this.mux.brutal.down_mbps = newValue.length != 0 ? newValue : 0
|
||||
}
|
||||
}
|
||||
},
|
||||
up_mbps: {
|
||||
get() { return this.mux.brutal && this.mux.brutal.up_mbps ? this.mux.brutal.up_mbps : 0 },
|
||||
set(newValue:any) {
|
||||
if (this.mux.brutal){
|
||||
this.mux.brutal.up_mbps = newValue.length != 0 ? newValue : 0
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('in.multiplex')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Enable Multiplex" v-model="muxEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
<template v-if="mux.enabled">
|
||||
<template v-if="direction=='out'">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:items="[ 'smux', 'yamux', 'h2mux']"
|
||||
label="Protocol"
|
||||
clearable
|
||||
@click:clear="mux.protocol=undefined"
|
||||
v-model="mux.protocol">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Max Connections"
|
||||
hide-details
|
||||
type="number"
|
||||
min=0
|
||||
v-model.number="max_connections">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Min Streams"
|
||||
hide-details
|
||||
type="number"
|
||||
min=0
|
||||
v-model.number="min_streams">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Max Streams"
|
||||
hide-details
|
||||
type="number"
|
||||
:min="min_streams"
|
||||
v-model.number="max_streams">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</template>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Reject Non-Padded" v-model="mux.padding" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Enable Brutal" v-model="burtalEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
<v-row v-if="mux.brutal?.enabled">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Uplink Bandwidth"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
v-model.number="up_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Downlink Bandwidth"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
min="0"
|
||||
v-model.number="down_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { oMultiplex } from '@/types/multiplex'
|
||||
export default {
|
||||
props: ['data', 'direction'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
mux(): oMultiplex {
|
||||
return <oMultiplex> this.$props.data.multiplex
|
||||
},
|
||||
muxEnable: {
|
||||
get(): boolean { return this.mux ? this.mux.enabled : false },
|
||||
set(newValue:boolean) { this.$props.data.multiplex = newValue ? { enabled: newValue } : {} }
|
||||
},
|
||||
max_connections: {
|
||||
get(): number { return this.mux.max_connections ? this.mux.max_connections : 0 },
|
||||
set(newValue:number) { this.mux.max_connections = newValue > 0 ? newValue : undefined }
|
||||
},
|
||||
min_streams: {
|
||||
get(): number { return this.mux.min_streams ? this.mux.min_streams : 0 },
|
||||
set(newValue:number) { this.mux.min_streams = newValue > 0 ? newValue : undefined }
|
||||
},
|
||||
max_streams: {
|
||||
get(): number { return this.mux.max_streams ? this.mux.max_streams : 0 },
|
||||
set(newValue:number) { this.mux.max_streams = newValue > 0 ? newValue : undefined }
|
||||
},
|
||||
burtalEnable: {
|
||||
get(): boolean { return this.mux.brutal ? this.mux.brutal.enabled : false },
|
||||
set(newValue:boolean) { this.mux.brutal = newValue ? { enabled: newValue, up_mbps: 100, down_mbps: 100 } : undefined }
|
||||
},
|
||||
down_mbps: {
|
||||
get() { return this.mux.brutal && this.mux.brutal.down_mbps ? this.mux.brutal.down_mbps : 0 },
|
||||
set(newValue:any) {
|
||||
if (this.mux.brutal){
|
||||
this.mux.brutal.down_mbps = newValue.length != 0 ? newValue : 0
|
||||
}
|
||||
}
|
||||
},
|
||||
up_mbps: {
|
||||
get() { return this.mux.brutal && this.mux.brutal.up_mbps ? this.mux.brutal.up_mbps : 0 },
|
||||
set(newValue:any) {
|
||||
if (this.mux.brutal){
|
||||
this.mux.brutal.up_mbps = newValue.length != 0 ? newValue : 0
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
networks: [
|
||||
@@ -21,8 +21,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
Network: {
|
||||
get():string { return this.$props.inbound.network?? '' },
|
||||
set(v:string) { this.$props.inbound.network = v != '' ? v : undefined }
|
||||
get():string { return this.$props.data.network?? '' },
|
||||
set(v:string) { this.$props.data.network = v != '' ? v : undefined }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('in.tls')">
|
||||
<v-row v-if="tlsOptional">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="tls.enabled">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Disable SNI" v-model="disable_sni" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Allow Insecure" v-model="insecure" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="optionCert">
|
||||
<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.certificate=undefined; tls.certificate_path=''"
|
||||
>{{ $t('tls.usePath') }}</v-btn>
|
||||
<v-btn
|
||||
@click="tls.certificate_path=undefined; tls.certificate=''"
|
||||
>{{ $t('tls.useText') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="usePath == 0">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
:label="$t('tls.certPath')"
|
||||
hide-details
|
||||
v-model="tls.certificate_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="tls.certificate">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<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="Minimum Version"
|
||||
: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="Maximum Version"
|
||||
:items="tlsVersions"
|
||||
v-model="tls.max_version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="tls.cipher_suites != undefined">
|
||||
<v-col cols="12" md="8">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Cipher Suites"
|
||||
multiple
|
||||
:items="cipher_suites"
|
||||
v-model="tls.cipher_suites">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="tls.utls != undefined">
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Fingerprint"
|
||||
:items="fingerprints"
|
||||
v-model="tls.utls.fingerprint">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="tls.reality != undefined">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
label="Public Key"
|
||||
hide-details
|
||||
v-model="tls.reality.public_key">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field
|
||||
label="Short ID"
|
||||
hide-details
|
||||
v-model="tls.reality.short_id">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="tls.ech != undefined">
|
||||
<v-row>
|
||||
<v-col class="v-card-subtitle">ECH</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Post-Quantum Schemes" v-model="tls.ech.pq_signature_schemes_enabled" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Disable Adaptive Size" v-model="tls.ech.dynamic_record_sizing_disabled" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-btn-toggle v-model="useEchPath"
|
||||
class="rounded-xl"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
shaped
|
||||
mandatory>
|
||||
<v-btn
|
||||
@click="delete tls.ech?.config"
|
||||
>{{ $t('tls.usePath') }}</v-btn>
|
||||
<v-btn
|
||||
@click="delete tls.ech?.config_path"
|
||||
>{{ $t('tls.useText') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="useEchPath == 0">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
:label="$t('tls.certPath')"
|
||||
hide-details
|
||||
v-model="tls.ech.config_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="echConfigText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</template>
|
||||
<v-card-actions v-if="tls.enabled">
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>TLS Options</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionCert" color="primary" label="Certificate" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<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="Min Version" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMaxV" color="primary" label="Max Version" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionCS" color="primary" label="Cipher Suites" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionFP" color="primary" label="UTLS" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionReality" color="primary" label="Reality" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionEch" color="primary" label="ECH" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { oTls, defaultOutTls } from '@/types/outTls'
|
||||
export default {
|
||||
props: ['outbound'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
usePath: 0,
|
||||
useEchPath: 0,
|
||||
defaults: defaultOutTls,
|
||||
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" }
|
||||
],
|
||||
fingerprints: [
|
||||
{ title: "Chrome", value: "chrome" },
|
||||
{ title: "Chrome PSK", value: "chrome_psk" },
|
||||
{ title: "Chrome PSK Shuffle", value: "chrome_psk_shuffle" },
|
||||
{ title: "Chrome Padding PSK Shuffle", value: "chrome_padding_psk_shuffle" },
|
||||
{ title: "Chrome Post-Quantum", value: "chrome_pq" },
|
||||
{ title: "Chrome Post-Quantum PSK", value: "chrome_pq_psk" },
|
||||
{ title: "Firefox", value: "firefox" },
|
||||
{ title: "Microsoft Edge", value: "edge" },
|
||||
{ title: "Apple Safari", value: "safari" },
|
||||
{ title: "360", value: "360" },
|
||||
{ title: "QQ", value: "qq" },
|
||||
{ title: "Apple IOS", value: "ios" },
|
||||
{ title: "Android", value: "android" },
|
||||
{ title: "Random", value: "random" },
|
||||
{ title: "Randomized", value: "randomized" },
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tls(): oTls {
|
||||
return <oTls> this.$props.outbound.tls
|
||||
},
|
||||
tlsEnable: {
|
||||
get() { return Object.hasOwn(this.tls, 'enabled') ? this.tls.enabled : false },
|
||||
set(newValue: boolean) { this.$props.outbound.tls = newValue ? { enabled: true } : {} }
|
||||
},
|
||||
disable_sni: {
|
||||
get() { return this.tls.disable_sni ?? false },
|
||||
set(newValue: boolean) { this.$props.outbound.tls.disable_sni = newValue ? true : undefined }
|
||||
},
|
||||
insecure: {
|
||||
get() { return this.tls.insecure ?? false },
|
||||
set(newValue: boolean) { this.$props.outbound.tls.insecure = newValue ? true : undefined }
|
||||
},
|
||||
tlsOptional(): boolean {
|
||||
return !['hysteria','hysteria2','tuic','shadowtls'].includes(this.$props.outbound.type)
|
||||
},
|
||||
echConfigText: {
|
||||
get(): string { return this.tls.ech?.config ? this.tls.ech.config.join('\n') : '' },
|
||||
set(newValue:string) { if (this.tls.ech) this.tls.ech.config = newValue.split('\n') }
|
||||
},
|
||||
optionCert: {
|
||||
get(): boolean { return this.tls.certificate != undefined || this.tls.certificate_path != undefined },
|
||||
set(v:boolean) {
|
||||
this.usePath = 0
|
||||
if (v) {
|
||||
this.$props.outbound.tls.certificate_path = ""
|
||||
} else {
|
||||
delete this.$props.outbound.tls.certificate_path
|
||||
delete this.$props.outbound.tls.certificate
|
||||
}
|
||||
}
|
||||
},
|
||||
optionSNI: {
|
||||
get(): boolean { return this.tls.server_name != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.server_name = v ? '' : undefined }
|
||||
},
|
||||
optionALPN: {
|
||||
get(): boolean { return this.tls.alpn != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.alpn = v ? defaultOutTls.alpn : undefined }
|
||||
},
|
||||
optionMinV: {
|
||||
get(): boolean { return this.tls.min_version != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.min_version = v ? defaultOutTls.min_version : undefined }
|
||||
},
|
||||
optionMaxV: {
|
||||
get(): boolean { return this.tls.max_version != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.max_version = v ? defaultOutTls.max_version : undefined }
|
||||
},
|
||||
optionCS: {
|
||||
get(): boolean { return this.tls.cipher_suites != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.cipher_suites = v ? defaultOutTls.cipher_suites : undefined }
|
||||
},
|
||||
optionFP: {
|
||||
get(): boolean { return this.tls.utls != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.utls = v ? defaultOutTls.utls : undefined }
|
||||
},
|
||||
optionReality: {
|
||||
get(): boolean { return this.tls.reality != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.reality = v ? defaultOutTls.reality : undefined }
|
||||
},
|
||||
optionEch: {
|
||||
get(): boolean { return this.tls.ech != undefined },
|
||||
set(v:boolean) { this.$props.outbound.tls.ech = v ? defaultOutTls.ech : undefined }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -28,7 +28,7 @@ import WebSocket from './transports/WebSocket.vue'
|
||||
import GRPC from './transports/gRPC.vue'
|
||||
import HttpUpgrade from './transports/HttpUpgrade.vue'
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
trspTypes: TrspTypes
|
||||
@@ -36,15 +36,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
Transport() {
|
||||
return <Transport>this.$props.inbound.transport
|
||||
return <Transport>this.$props.data.transport
|
||||
},
|
||||
tpEnable: {
|
||||
get() { return Object.hasOwn(this.$props.inbound.transport, 'type') },
|
||||
set(newValue: boolean) { this.$props.inbound.transport = newValue ? { type: 'http' } : {} }
|
||||
get() { return Object.hasOwn(this.$props.data.transport, 'type') },
|
||||
set(newValue: boolean) { this.$props.data.transport = newValue ? { type: 'http' } : {} }
|
||||
},
|
||||
transportType: {
|
||||
get() { return this.Transport.type },
|
||||
set(newValue: string) { this.$props.inbound.transport = { type: newValue } }
|
||||
set(newValue: string) { this.$props.data.transport = { type: newValue } }
|
||||
}
|
||||
},
|
||||
components: { Http, WebSocket, GRPC, HttpUpgrade }
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<v-select
|
||||
hide-details
|
||||
label="UDP over TCP"
|
||||
:items="versions"
|
||||
v-model="udp_over_tcp">
|
||||
</v-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
versions: [
|
||||
{ title: this.$t('disable'), value: 0 },
|
||||
{ title: "1", value: 1 },
|
||||
{ title: "2", value: 2 },
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
udp_over_tcp: {
|
||||
get():number { return this.$props.data.udp_over_tcp?.version?? 0 },
|
||||
set(v:number) { this.$props.data.udp_over_tcp = v > 0 ? { enabled: true, version: v } : undefined }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Server Address"
|
||||
hide-details
|
||||
v-model="data.server">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Server Port"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="data.server_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.public_key" label="Public Key" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.pre_shared_key" label="Pre-Shared Key" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="allowed_ips" label="Allowed IPs (comma separated)" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="reserved" label="Reserved (comma separated)" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
allowed_ips: {
|
||||
get() { return this.$props.data.allowed_ips?.join(',') },
|
||||
set(v:string) { this.$props.data.allowed_ips = v.length > 0 ? v.split(',') : undefined }
|
||||
},
|
||||
reserved: {
|
||||
get() { return this.$props.data.reserved?.join(',') },
|
||||
set(v:string) {
|
||||
if(!v.endsWith(',')) {
|
||||
this.$props.data.reserved = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<v-card subtitle="Direct">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :inbound="inbound" />
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Override Address"
|
||||
hide-details
|
||||
v-model="inbound.override_address">
|
||||
v-model="data.override_address">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
@@ -20,6 +20,16 @@
|
||||
v-model="override_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'out'">
|
||||
<v-text-field
|
||||
label="Proxy Protocol"
|
||||
type="number"
|
||||
min="0"
|
||||
max="2"
|
||||
hide-details
|
||||
v-model="proxy_protocol">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -28,16 +38,20 @@
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
data() {
|
||||
return {}
|
||||
props: ['direction','data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
override_port: {
|
||||
get() { return this.$props.data.override_port ? this.$props.data.override_port : ''; },
|
||||
set(newValue: any) { this.$props.data.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
||||
},
|
||||
computed: {
|
||||
override_port: {
|
||||
get() { return this.$props.inbound.override_port ? this.$props.inbound.override_port : ''; },
|
||||
set(newValue: any) { this.$props.inbound.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
||||
},
|
||||
proxy_protocol: {
|
||||
get() { return this.$props.data.proxy_protocol ? this.$props.data.proxy_protocol : ''; },
|
||||
set(newValue: any) { this.$props.data.proxy_protocol = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
||||
},
|
||||
components: { Network }
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<v-card subtitle="HTTP">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Username"
|
||||
hide-details
|
||||
v-model="username">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Password"
|
||||
hide-details
|
||||
v-model="password">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Path"
|
||||
hide-details
|
||||
v-model="data.path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Headers :data="data" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Headers from '@/components/Headers.vue';
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
username: {
|
||||
get(): string { return this.data.username?.length > 0 ? this.data.username : '' },
|
||||
set(v:string) { this.data.username = v.length > 0 ? v : undefined },
|
||||
},
|
||||
password: {
|
||||
get(): string { return this.data.password?.length > 0 ? this.data.password : '' },
|
||||
set(v:string) { this.data.password = v.length > 0 ? v : undefined },
|
||||
},
|
||||
},
|
||||
components: { Headers }
|
||||
}
|
||||
</script>
|
||||
@@ -26,38 +26,134 @@
|
||||
<v-text-field
|
||||
label="obfs Password"
|
||||
hide-details
|
||||
v-model="inbound.obfs">
|
||||
v-model="data.obfs">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction=='out'">
|
||||
<v-text-field
|
||||
label="Authentication String"
|
||||
hide-details
|
||||
v-model="data.auth_str">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction=='out'">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.disable_mtu_discovery" color="primary" label="Disable MTU discovery" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_conn != undefined">
|
||||
<v-text-field
|
||||
label="Recv window conn"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.recv_window_conn">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window != undefined">
|
||||
<v-text-field
|
||||
label="Recv window"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.recv_window">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_client != undefined">
|
||||
<v-text-field
|
||||
label="Recv window client"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.recv_window_client">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.max_conn_client != undefined">
|
||||
<v-text-field
|
||||
label="Max conn client"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.max_conn_client">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>Hysteria Options</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionRsvConn" color="primary" label="Recv window conn" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="direction=='out'">
|
||||
<v-switch v-model="optionRsvWin" color="primary" label="Recv window" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="direction=='in'">
|
||||
<v-switch v-model="optionRsvClnt" color="primary" label="Recv window client" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="direction=='in'">
|
||||
<v-switch v-model="optionMaxConn" color="primary" label="Max connection" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction','data'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionRsvConn: {
|
||||
get(): boolean { return this.$props.data.recv_window_conn != undefined },
|
||||
set(v:boolean) { this.$props.data.recv_window_conn = v ? 15728640 : undefined }
|
||||
},
|
||||
optionRsvWin: {
|
||||
get(): boolean { return this.$props.data.recv_window != undefined },
|
||||
set(v:boolean) { this.$props.data.recv_window = v ? 67108864 : undefined }
|
||||
},
|
||||
optionRsvClnt: {
|
||||
get(): boolean { return this.$props.data.recv_window_client != undefined },
|
||||
set(v:boolean) { this.$props.data.recv_window_client = v ? 67108864 : undefined }
|
||||
},
|
||||
optionMaxConn: {
|
||||
get(): boolean { return this.$props.data.max_conn_client != undefined },
|
||||
set(v:boolean) { this.$props.data.max_conn_client = v ? 1024 : undefined }
|
||||
},
|
||||
down_mbps: {
|
||||
get() { return this.$props.inbound.down_mbps ? this.$props.inbound.down_mbps : 0 },
|
||||
get() { return this.$props.data.down_mbps ? this.$props.data.down_mbps : 0 },
|
||||
set(newValue:any) {
|
||||
if (newValue.length != 0 ){
|
||||
this.$props.inbound.down_mbps = newValue
|
||||
this.$props.inbound.down = "" + newValue + " Mbps"
|
||||
this.$props.data.down_mbps = newValue
|
||||
this.$props.data.down = "" + newValue + " Mbps"
|
||||
} else {
|
||||
this.$props.inbound.down_mbps = 0
|
||||
this.$props.inbound.down = "0 Mbps"
|
||||
this.$props.data.down_mbps = 0
|
||||
this.$props.data.down = "0 Mbps"
|
||||
}
|
||||
}
|
||||
},
|
||||
up_mbps: {
|
||||
get() { return this.$props.inbound.up_mbps ? this.$props.inbound.up_mbps : 0 },
|
||||
set(newValue:number) { this.$props.inbound.up_mbps = newValue > 0 ? newValue : 0 }
|
||||
get() { return this.$props.data.up_mbps ? this.$props.data.up_mbps : 0 },
|
||||
set(newValue:number) { this.$props.data.up_mbps = newValue > 0 ? newValue : 0 }
|
||||
},
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -1,23 +1,37 @@
|
||||
<template>
|
||||
<v-card subtitle="Hysteria2">
|
||||
<v-row>
|
||||
<v-row v-if="direction == 'in'">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Masquerade"
|
||||
hide-details
|
||||
v-model="hysteria2.masquerade"></v-text-field>
|
||||
v-model="data.masquerade">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="hysteria2.ignore_client_bandwidth" color="primary" label="Ignore Client Bandwidth" hide-details></v-switch>
|
||||
<v-switch v-model="data.ignore_client_bandwidth" color="primary" label="Ignore Client Bandwidth" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="!hysteria2.ignore_client_bandwidth">
|
||||
<v-row v-else>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Password"
|
||||
hide-details
|
||||
v-model="data.password">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="!data.ignore_client_bandwidth">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Uplink Limit"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
min="0"
|
||||
v-model.number="up_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
@@ -32,12 +46,12 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="hysteria2.obfs">
|
||||
<v-row v-if="data.obfs">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="obfs Password"
|
||||
hide-details
|
||||
v-model="hysteria2.obfs.password">
|
||||
v-model="data.obfs.password">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -45,7 +59,7 @@
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>Options</v-btn>
|
||||
<v-btn v-bind="props" hide-details>Hysteria2 Options</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
@@ -60,32 +74,29 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Hysteria2, createInbound } from '@/types/inbounds'
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction', 'data'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
hysteria2: <Hysteria2> createInbound("hysteria2",{ "tag": "" }),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
down_mbps: {
|
||||
get() { return this.hysteria2.down_mbps ? this.hysteria2.down_mbps : 0 },
|
||||
set(newValue:number) { this.hysteria2.down_mbps = newValue?? undefined }
|
||||
get() { return this.$props.data.down_mbps?? 0 },
|
||||
set(newValue:number) { this.$props.data.down_mbps = newValue>0 ? newValue : undefined }
|
||||
},
|
||||
up_mbps: {
|
||||
get() { return this.hysteria2.up_mbps ? this.hysteria2.up_mbps : 0 },
|
||||
set(newValue:number) { this.hysteria2.up_mbps = newValue?? undefined }
|
||||
get() { return this.$props.data.up_mbps?? 0 },
|
||||
set(newValue:number) { this.$props.data.up_mbps = newValue>0 ? newValue : undefined }
|
||||
},
|
||||
optionObfs: {
|
||||
get(): boolean { return this.hysteria2.obfs != undefined },
|
||||
set(v:boolean) { this.$props.inbound.obfs = v ? { type: "salamander", password: ""} : undefined }
|
||||
get(): boolean { return this.$props.data.obfs != undefined },
|
||||
set(v:boolean) { this.$props.data.obfs = v ? { type: "salamander", password: "" } : undefined }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.hysteria2 = <Hysteria2> this.$props.inbound
|
||||
}
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<v-card subtitle="Naive">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :inbound="inbound" />
|
||||
<Network :data="inbound" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<v-card subtitle="ShadowTls">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:items="[1,2,3]"
|
||||
label="Version"
|
||||
v-model="version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.version > 1">
|
||||
<v-text-field
|
||||
label="Password"
|
||||
hide-details
|
||||
v-model="data.password">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
version: {
|
||||
get() { return this.$props.data.version ?? 3 },
|
||||
set(v: number) {
|
||||
this.$props.data.version = v
|
||||
if (v==1) {
|
||||
delete this.$props.data.password
|
||||
} else if (this.$props.data.password === undefined ) {
|
||||
this.$props.data.password = ""
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<v-card subtitle="Selector">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field v-model="outbounds" label="Outbounds(comma separated)" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.default" label="Default" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-switch v-model="data.interrupt_exist_connections" color="primary" label="Interrupt exist connections" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
outbounds: {
|
||||
get() { return this.$props.data.outbounds ? this.$props.data.outbounds.join(',') : '' },
|
||||
set(v:string) { this.$props.data.outbounds = v.length > 0 ? v.split(',') : undefined }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -92,62 +92,62 @@ import { ShadowTLS } from '@/types/inbounds'
|
||||
import Dial from '../Dial.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
data() {
|
||||
return {
|
||||
handshake_server: ''
|
||||
props: ['inbound'],
|
||||
data() {
|
||||
return {
|
||||
handshake_server: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addHandshakeServer() {
|
||||
this.inbound.handshake_for_server_name[this.handshake_server] = {}
|
||||
// Clear the input field after adding the server
|
||||
this.handshake_server = ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.version = this.Inbound.version
|
||||
},
|
||||
computed: {
|
||||
version: {
|
||||
get() { this.version = this.Inbound.version; return this.Inbound.version; },
|
||||
set(newValue: any) {
|
||||
switch (newValue) {
|
||||
case 1:
|
||||
this.Inbound.password = undefined
|
||||
this.Inbound.users = undefined
|
||||
this.Inbound.handshake_for_server_name = undefined
|
||||
break;
|
||||
case 2:
|
||||
if (!this.Inbound.password) {
|
||||
this.Inbound.password = ""
|
||||
}
|
||||
this.Inbound.users = undefined
|
||||
if (!this.Inbound.handshake_for_server_name) {
|
||||
this.Inbound.handshake_for_server_name = {}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
this.Inbound.password = undefined
|
||||
if (Object.hasOwn(this.Inbound, 'users')) {
|
||||
this.Inbound.users = []
|
||||
}
|
||||
if (!this.Inbound.handshake_for_server_name) {
|
||||
this.Inbound.handshake_for_server_name = {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addHandshakeServer() {
|
||||
this.inbound.handshake_for_server_name[this.handshake_server] = {}
|
||||
// Clear the input field after adding the server
|
||||
this.handshake_server = ''
|
||||
this.Inbound.version = newValue;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.version = this.Inbound.version
|
||||
Inbound(): ShadowTLS {
|
||||
return <ShadowTLS>this.$props.inbound;
|
||||
},
|
||||
computed: {
|
||||
version: {
|
||||
get() { this.version = this.Inbound.version; return this.Inbound.version; },
|
||||
set(newValue: any) {
|
||||
switch (newValue) {
|
||||
case 1:
|
||||
this.Inbound.password = undefined
|
||||
this.Inbound.users = undefined
|
||||
this.Inbound.handshake_for_server_name = undefined
|
||||
break;
|
||||
case 2:
|
||||
if (!this.Inbound.password) {
|
||||
this.Inbound.password = ""
|
||||
}
|
||||
this.Inbound.users = undefined
|
||||
if (!this.Inbound.handshake_for_server_name) {
|
||||
this.Inbound.handshake_for_server_name = {}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
this.Inbound.password = undefined
|
||||
if (Object.hasOwn(this.Inbound, 'users')) {
|
||||
this.Inbound.users = []
|
||||
}
|
||||
if (!this.Inbound.handshake_for_server_name) {
|
||||
this.Inbound.handshake_for_server_name = {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.Inbound.version = newValue;
|
||||
}
|
||||
},
|
||||
Inbound(): ShadowTLS {
|
||||
return <ShadowTLS>this.$props.inbound;
|
||||
},
|
||||
server_port: {
|
||||
get() { return this.Inbound.handshake.server_port ? this.Inbound.handshake.server_port : 443; },
|
||||
set(newValue: any) { this.Inbound.handshake.server_port = newValue.length == 0 || newValue == 0 ? 443 : parseInt(newValue); }
|
||||
},
|
||||
server_port: {
|
||||
get() { return this.Inbound.handshake.server_port ? this.Inbound.handshake.server_port : 443; },
|
||||
set(newValue: any) { this.Inbound.handshake.server_port = newValue.length == 0 || newValue == 0 ? 443 : parseInt(newValue); }
|
||||
},
|
||||
components: { Dial }
|
||||
},
|
||||
components: { Dial }
|
||||
}
|
||||
</script>
|
||||
@@ -6,14 +6,17 @@
|
||||
hide-details
|
||||
label="Method"
|
||||
:items="ssMethods"
|
||||
v-model="inbound.method">
|
||||
v-model="data.method">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="inbound.password" label="Password" hide-details></v-text-field>
|
||||
<v-text-field v-model="data.password" label="Password" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :inbound="inbound" />
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'out'">
|
||||
<UoT :data="data" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
@@ -21,24 +24,25 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
import UoT from '@/components/UoT.vue';
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
data() {
|
||||
return {
|
||||
ssMethods: [
|
||||
"none",
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
]
|
||||
}
|
||||
},
|
||||
components: { Network }
|
||||
props: ['direction','data'],
|
||||
data() {
|
||||
return {
|
||||
ssMethods: [
|
||||
"none",
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
]
|
||||
}
|
||||
},
|
||||
components: { Network, UoT }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<v-card subtitle="SOCKS">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Username"
|
||||
hide-details
|
||||
v-model="username">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Password"
|
||||
hide-details
|
||||
v-model="password">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:items="['4','4a','5']"
|
||||
label="Version"
|
||||
v-model="data.version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<UoT :data="data" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
import UoT from '@/components/UoT.vue';
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
username: {
|
||||
get(): string { return this.data.username?.length > 0 ? this.data.username : '' },
|
||||
set(v:string) { this.data.username = v.length > 0 ? v : undefined },
|
||||
},
|
||||
password: {
|
||||
get(): string { return this.data.password?.length > 0 ? this.data.password : '' },
|
||||
set(v:string) { this.data.password = v.length > 0 ? v : undefined },
|
||||
},
|
||||
},
|
||||
components: { Network, UoT }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<v-card subtitle="SSH">
|
||||
<template v-if="optionKey">
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-btn-toggle v-model="usePath"
|
||||
class="rounded-xl"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
shaped
|
||||
mandatory>
|
||||
<v-btn
|
||||
@click="data.private_key=undefined; data.private_key_path=''"
|
||||
>{{ $t('tls.usePath') }}</v-btn>
|
||||
<v-btn
|
||||
@click="data.private_key_path=undefined; data.private_key=''"
|
||||
>{{ $t('tls.useText') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="usePath == 0">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
:label="$t('tls.keyPath')"
|
||||
hide-details
|
||||
v-model="data.private_key_path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-else>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.key')"
|
||||
hide-details
|
||||
v-model="data.private_key">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
label="Passphrase"
|
||||
hide-details
|
||||
v-model="data.private_key_passphrase">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.user" label="SSH User" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.password" label="Password" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<v-row v-if="optionHostKey">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
label="Host Keys"
|
||||
hide-details
|
||||
v-model="host_key">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.host_key_algorithms != undefined">
|
||||
<v-text-field v-model="algorithms" label="Key Algorithms (comma separated)" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.client_version != undefined">
|
||||
<v-text-field v-model="data.client_version" label="Client Version" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>SSH Options</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionKey" color="primary" label="SSH Key" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionHostKey" color="primary" label="Host Key" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionAlgorithms" color="primary" label="Key Algorithms" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionVer" color="primary" label="Client Version" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
usePath: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionKey: {
|
||||
get(): boolean { return this.data.private_key != undefined || this.data.private_key_path != undefined },
|
||||
set(v:boolean) {
|
||||
this.usePath = 0
|
||||
if (v) {
|
||||
this.$props.data.private_key_path = ""
|
||||
delete this.$props.data.user
|
||||
delete this.$props.data.password
|
||||
} else {
|
||||
delete this.$props.data.private_key_path
|
||||
delete this.$props.data.private_key
|
||||
delete this.$props.data.private_key_passphrase
|
||||
}
|
||||
}
|
||||
},
|
||||
optionHostKey: {
|
||||
get(): boolean { return this.data.host_key != undefined },
|
||||
set(v:boolean) { this.data.host_key = v ? '' : undefined }
|
||||
},
|
||||
optionAlgorithms: {
|
||||
get(): boolean { return this.data.host_key_algorithms != undefined },
|
||||
set(v:boolean) { this.data.host_key_algorithms = v ? [] : undefined }
|
||||
},
|
||||
optionVer: {
|
||||
get(): boolean { return this.data.client_version != undefined },
|
||||
set(v:boolean) { this.data.client_version = v ? 'SSH-2.0-OpenSSH_7.4p1' : undefined }
|
||||
},
|
||||
host_key: {
|
||||
get(): string { return this.$props.data.host_key ? this.$props.data.host_key.join('\n') : '' },
|
||||
set(v:string) { this.$props.data.host_key = v.split('\n') }
|
||||
},
|
||||
algorithms: {
|
||||
get() { return this.$props.data.host_key_algorithms ? this.$props.data.host_key_algorithms.join(',') : '' },
|
||||
set(v:string) { this.$props.data.host_key_algorithms = v.length > 0 ? v.split(',') : undefined }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<v-card subtitle="TProxy">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :inbound="inbound" />
|
||||
<Network :data="inbound" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<v-card subtitle="Tor">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.executable_path" label="Executable Path" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.data_directory" label="Data Directory" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="extra_args" label="Extra Args (comma separated)" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
extra_args: {
|
||||
get() { return this.$props.data.extra_args?.join(',') },
|
||||
set(v:string) { this.$props.data.extra_args = v.length > 0 ? v.split(',') : undefined }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<v-card subtitle="Trojan">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.password" label="Password" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -1,20 +1,44 @@
|
||||
<template>
|
||||
<v-card subtitle="TUIC">
|
||||
<v-row v-if="direction == 'out'">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field v-model="data.uuid" label="UUID" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="data.password" label="Password" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
label="UDP Relay Mode"
|
||||
:items="['native', 'quic']"
|
||||
clearable
|
||||
@click:clear="delete data.udp_relay_mode"
|
||||
v-model="data.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="data.udp_over_stream" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Congestion Control"
|
||||
:items="congestion_controls"
|
||||
v-model="inbound.congestion_control">
|
||||
v-model="data.congestion_control">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Zero-RTT Handshake" v-model="inbound.zero_rtt_handshake" hide-details></v-switch>
|
||||
<v-switch color="primary" label="Zero-RTT Handshake" v-model="data.zero_rtt_handshake" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
||||
<v-text-field
|
||||
label="Authentication Timeout"
|
||||
hide-details
|
||||
@@ -39,9 +63,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { TUIC } from '@/types/inbounds'
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction', 'data'],
|
||||
data() {
|
||||
return {
|
||||
congestion_controls: [
|
||||
@@ -50,17 +75,15 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
Inbound(): TUIC {
|
||||
return <TUIC> this.$props.inbound
|
||||
},
|
||||
auth_timeout: {
|
||||
get() { return this.Inbound.auth_timeout ? parseInt(this.Inbound.auth_timeout.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.inbound.auth_timeout = newValue ? newValue + 's' : '' }
|
||||
get() { return this.$props.data.auth_timeout ? parseInt(this.$props.data.auth_timeout.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.data.auth_timeout = newValue ? newValue + 's' : '' }
|
||||
},
|
||||
heartbeat: {
|
||||
get() { return this.Inbound.heartbeat ? parseInt(this.Inbound.heartbeat.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.inbound.heartbeat = newValue ? newValue + 's' : '' }
|
||||
get() { return this.$props.data.heartbeat ? parseInt(this.$props.data.heartbeat.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.data.heartbeat = newValue ? newValue + 's' : '' }
|
||||
}
|
||||
}
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<v-card subtitle="URL Test">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field v-model="outbounds" label="Outbounds(comma separated)" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" v-if="optionUrl">
|
||||
<v-text-field v-model="data.url" label="URL" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionInterval">
|
||||
<v-text-field
|
||||
label="Interval"
|
||||
hide-details
|
||||
type="number"
|
||||
min="3"
|
||||
suffix="s"
|
||||
v-model.number="interval"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionTolerance">
|
||||
<v-text-field
|
||||
label="Tolerance"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
suffix="ms"
|
||||
v-model.number="tolerance"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionIdle">
|
||||
<v-text-field
|
||||
label="Idle Timeout"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
suffix="m"
|
||||
v-model.number="idle_timeout"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-switch v-model="data.interrupt_exist_connections" color="primary" label="Interrupt exist connections" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>SSH Options</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionUrl" color="primary" label="Test URL" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionInterval" color="primary" label="Interval" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionTolerance" color="primary" label="Tolerance" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionIdle" color="primary" label="Idle Timeout" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionUrl: {
|
||||
get(): boolean { return this.$props.data.url != undefined },
|
||||
set(v:boolean) { this.$props.data.url = v ? 'https://www.gstatic.com/generate_204' : undefined }
|
||||
},
|
||||
optionInterval: {
|
||||
get(): boolean { return this.$props.data.interval != undefined },
|
||||
set(v:boolean) { this.$props.data.interval = v ? '3s' : undefined }
|
||||
},
|
||||
optionTolerance: {
|
||||
get(): boolean { return this.$props.data.tolerance != undefined },
|
||||
set(v:boolean) { this.$props.data.tolerance = v ? 50 : undefined }
|
||||
},
|
||||
optionIdle: {
|
||||
get(): boolean { return this.$props.data.idle_timeout != undefined },
|
||||
set(v:boolean) { this.$props.data.idle_timeout = v ? '30m' : undefined }
|
||||
},
|
||||
interval: {
|
||||
get() { return this.$props.data.interval ? parseInt(this.$props.data.interval.replace('s','')) : 3 },
|
||||
set(v:number) { this.$props.data.interval = v > 0 ? v + 's' : '3s' }
|
||||
},
|
||||
tolerance: {
|
||||
get() { return this.$props.data.tolerance ? parseInt(this.$props.data.tolerance) : 0 },
|
||||
set(v:number) { this.$props.data.tolerance = v > 0 ? v : 0 }
|
||||
},
|
||||
idle_timeout: {
|
||||
get() { return this.$props.data.idle_timeout ? parseInt(this.$props.data.idle_timeout.replace('m','')) : 30 },
|
||||
set(v:number) { this.$props.data.idle_timeout = v > 0 ? v + 'm' : '0m' }
|
||||
},
|
||||
outbounds: {
|
||||
get() { return this.$props.data.outbounds ? this.$props.data.outbounds.join(',') : '' },
|
||||
set(v:string) { this.$props.data.outbounds = v.length > 0 ? v.split(',') : undefined }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<v-card subtitle="VLESS">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field v-model="data.uuid" label="UUID" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Flow"
|
||||
:items="['','xtls-rprx-vision']"
|
||||
v-model="data.flow">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
label="UDP Packet Encoding"
|
||||
:items="['none','packetaddr','xudp']"
|
||||
v-model="packet_encoding">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
packet_encoding: {
|
||||
get() { return this.$props.data.packet_encoding != undefined ? this.$props.data.packet_encoding : 'none'; },
|
||||
set(newValue:string) { this.$props.data.packet_encoding = newValue != "none" ? newValue : undefined }
|
||||
},
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<v-card subtitle="VMESS">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field v-model="data.uuid" label="UUID" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Alter ID"
|
||||
hide-details
|
||||
type="number"
|
||||
min=0
|
||||
v-model.number="data.alter_id">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Security"
|
||||
:items="securities"
|
||||
v-model="data.security">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
label="UDP Packet Encoding"
|
||||
:items="['none','packetaddr','xudp']"
|
||||
v-model="packet_encoding">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.global_padding" color="primary" label="Global Padding" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.authenticated_length" color="primary" label="Encryptrd Length" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
securities: [
|
||||
"auto",
|
||||
"none",
|
||||
"zero",
|
||||
"aes-128-gcm",
|
||||
"aes-128-ctr",
|
||||
"chacha20-poly1305",
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
packet_encoding: {
|
||||
get() { return this.$props.data.packet_encoding != undefined ? this.$props.data.packet_encoding : 'none'; },
|
||||
set(newValue:string) { this.$props.data.packet_encoding = newValue != "none" ? newValue : undefined }
|
||||
},
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<v-card subtitle="Wireguard">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="data.private_key" label="Private Key" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="data.peer_public_key" label="Peer Public Key" 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="Pre-Shared Key" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field v-model="local_ips" label="Local IPs (comma separated)" 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 (comma separated)" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.workers != undefined">
|
||||
<v-text-field
|
||||
label="Workers"
|
||||
hide-details
|
||||
type="number"
|
||||
min=1
|
||||
v-model.number="data.workers">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.mtu != undefined">
|
||||
<v-text-field
|
||||
label="MTU"
|
||||
hide-details
|
||||
type="number"
|
||||
min=0
|
||||
v-model.number="data.mtu">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.interface_name != undefined">
|
||||
<v-text-field
|
||||
label="Interface Name"
|
||||
hide-details
|
||||
v-model.number="data.interface_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="System Interface" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.gso" color="primary" label="Segmentation Offload" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>Wireguard Options</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionPsk" color="primary" label="Pre-shared Key" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionRsrv" color="primary" label="Reserved" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionWorker" color="primary" label="Worker" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMtu" color="primary" label="MTU" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionInterface" color="primary" label="Interface Name" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionPeers" color="primary" label="Multi Peer" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<v-card subtitle="Peers" v-if="data.peers != undefined">
|
||||
<template v-for="(p, index) in data.peers">
|
||||
Peer {{ index+1 }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" />
|
||||
<v-divider></v-divider>
|
||||
<Peer :data="p" />
|
||||
</template>
|
||||
<v-card-actions class="pt-0">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn @click="addPeer" rounded>
|
||||
<v-icon icon="mdi-plus" />Peer
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
import Peer from '@/components/WgPeer.vue'
|
||||
import { WgPeer } from '@/types/outbounds'
|
||||
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addPeer() {
|
||||
this.$props.data.peers.push({server: '', port: ''})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionPsk: {
|
||||
get(): boolean { return this.$props.data.pre_shared_key != undefined },
|
||||
set(v:boolean) { this.$props.data.pre_shared_key = v ? "" : undefined }
|
||||
},
|
||||
optionRsrv: {
|
||||
get(): boolean { return this.$props.data.reserved != undefined },
|
||||
set(v:boolean) { this.$props.data.reserved = v ? [0,0,0] : undefined }
|
||||
},
|
||||
optionWorker: {
|
||||
get(): boolean { return this.$props.data.workers != undefined },
|
||||
set(v:boolean) { this.$props.data.workers = v ? 2 : undefined }
|
||||
},
|
||||
optionMtu: {
|
||||
get(): boolean { return this.$props.data.mtu != undefined },
|
||||
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 }
|
||||
},
|
||||
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 }
|
||||
},
|
||||
reserved: {
|
||||
get() { return this.$props.data.reserved?.join(',') },
|
||||
set(v:string) {
|
||||
if(!v.endsWith(',')) {
|
||||
this.$props.data.reserved = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { Network, Peer }
|
||||
}
|
||||
</script>
|
||||
@@ -44,10 +44,12 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Headers :data="transport" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { HTTP } from '../../types/transport'
|
||||
import Headers from '../Headers.vue'
|
||||
export default {
|
||||
props: ['transport'],
|
||||
data() {
|
||||
@@ -70,6 +72,7 @@ export default {
|
||||
get() { return this.Http.ping_timeout ? parseInt(this.Http.ping_timeout.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.transport.ping_timeout = newValue ? newValue + 's' : '' }
|
||||
}
|
||||
}
|
||||
},
|
||||
components: { Headers }
|
||||
}
|
||||
</script>
|
||||
@@ -15,14 +15,17 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Headers :data="transport" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Headers from '../Headers.vue';
|
||||
export default {
|
||||
props: ['transport'],
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
}
|
||||
},
|
||||
components: { Headers }
|
||||
}
|
||||
</script>
|
||||
@@ -33,10 +33,12 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Headers :data="transport" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { WebSocket } from '../../types/transport'
|
||||
import Headers from '../Headers.vue'
|
||||
export default {
|
||||
props: ['transport'],
|
||||
data() {
|
||||
@@ -61,6 +63,7 @@ export default {
|
||||
mounted() {
|
||||
this.WS.early_data_header_name ??= 'Sec-WebSocket-Protocol'
|
||||
this.WS.path ??= '/'
|
||||
}
|
||||
},
|
||||
components: { Headers }
|
||||
}
|
||||
</script>
|
||||
@@ -22,18 +22,18 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Listen :inbound="inbound" />
|
||||
<Direct v-if="inbound.type == inTypes.Direct" :inbound="inbound" />
|
||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" :inbound="inbound" />
|
||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" :inbound="inbound" />
|
||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" :inbound="inbound" />
|
||||
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
||||
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
||||
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" :inbound="inbound" />
|
||||
<Tuic v-if="inbound.type == inTypes.TUIC" :inbound="inbound" />
|
||||
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" direction="in" :data="inbound" />
|
||||
<Tuic v-if="inbound.type == inTypes.TUIC" direction="in" :data="inbound" />
|
||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :inbound="inbound" />
|
||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" :id="id" />
|
||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" />
|
||||
<InMulitiplex v-if="Object.hasOwn(inbound,'multiplex')" :inbound="inbound" />
|
||||
<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-card-text>
|
||||
<v-card-actions>
|
||||
@@ -73,56 +73,56 @@ import Tuic from '@/components/protocols/Tuic.vue'
|
||||
import InTls from '@/components/InTLS.vue'
|
||||
import TProxy from '@/components/protocols/TProxy.vue'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
import InMulitiplex from '@/components/InMulitiplex.vue'
|
||||
import Multiplex from '@/components/Multiplex.vue'
|
||||
import Transport from '@/components/Transport.vue'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'stats'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
inbound: createInbound("direct",{ "tag": "" }),
|
||||
title: "add",
|
||||
loading: false,
|
||||
inTypes: InTypes,
|
||||
inboundStats: false,
|
||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||
}
|
||||
props: ['visible', 'data', 'id', 'stats'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
inbound: createInbound("direct",{ "tag": "" }),
|
||||
title: "add",
|
||||
loading: false,
|
||||
inTypes: InTypes,
|
||||
inboundStats: false,
|
||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.id != -1) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.inbound = createInbound(newData.type, newData)
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||
this.inbound = createInbound("mixed",{ tag: "in-"+port ,listen: "::", listen_port: port })
|
||||
this.title = "add"
|
||||
}
|
||||
this.inboundStats = this.$props.stats
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.id != -1) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.inbound = createInbound(newData.type, newData)
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||
this.inbound = createInbound("mixed",{ tag: "in-"+port ,listen: "::", listen_port: port })
|
||||
this.title = "add"
|
||||
}
|
||||
this.inboundStats = this.$props.stats
|
||||
},
|
||||
changeType() {
|
||||
const prevConfig = { tag: this.inbound.tag ,listen: this.inbound.listen, listen_port: this.inbound.listen_port }
|
||||
this.inbound = createInbound(this.inbound.type, prevConfig)
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.inbound, this.inboundStats)
|
||||
this.loading = false
|
||||
},
|
||||
changeType() {
|
||||
const prevConfig = { tag: this.inbound.tag ,listen: this.inbound.listen, listen_port: this.inbound.listen_port }
|
||||
this.inbound = createInbound(this.inbound.type, prevConfig)
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
this.$emit('close')
|
||||
},
|
||||
components: { Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks, Users, Hysteria, ShadowTls, TProxy, InMulitiplex, Tuic, Transport }
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.inbound, this.inboundStats)
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks, Users, Hysteria, ShadowTls, TProxy, Multiplex, Tuic, Transport }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-card-title>
|
||||
{{ $t('actions.' + title) + " " + $t('objects.outbound') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
width="100"
|
||||
:label="$t('type')"
|
||||
:items="Object.keys(outTypes).map((key,index) => ({title: key, value: Object.values(outTypes)[index]}))"
|
||||
v-model="outbound.type"
|
||||
@update:modelValue="changeType">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model="outbound.tag" :label="$t('in.tag')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="!NoServer.includes(outbound.type)">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Server Address"
|
||||
hide-details
|
||||
v-model="outbound.server">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Server Port"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="outbound.server_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Direct v-if="outbound.type == outTypes.Direct" direction="out" :data="outbound" />
|
||||
<Socks v-if="outbound.type == outTypes.SOCKS" :data="outbound" />
|
||||
<Http v-if="outbound.type == outTypes.HTTP" :data="outbound" />
|
||||
<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" />
|
||||
<Tuic v-if="outbound.type == outTypes.TUIC" direction="out" :data="outbound" />
|
||||
<Hysteria2 v-if="outbound.type == outTypes.Hysteria2" direction="out" :data="outbound" />
|
||||
<Tor v-if="outbound.type == outTypes.Tor" :data="outbound" />
|
||||
<Ssh v-if="outbound.type == outTypes.SSH" :data="outbound" />
|
||||
<Selector v-if="outbound.type == outTypes.Selector" :data="outbound" />
|
||||
<UrlTest v-if="outbound.type == outTypes.URLTest" :data="outbound" />
|
||||
|
||||
<Transport v-if="Object.hasOwn(outbound,'transport')" :data="outbound" />
|
||||
<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" />
|
||||
<v-switch v-model="outboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</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 { OutTypes, createOutbound } from '@/types/outbounds'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
import Dial from '@/components/Dial.vue'
|
||||
import Multiplex from '@/components/Multiplex.vue'
|
||||
import Transport from '@/components/Transport.vue'
|
||||
import OutTLS from '@/components/OutTLS.vue'
|
||||
import Direct from '@/components/protocols/Direct.vue'
|
||||
import Socks from '@/components/protocols/Socks.vue'
|
||||
import Http from '@/components/protocols/Http.vue'
|
||||
import Shadowsocks from '@/components/protocols/Shadowsocks.vue'
|
||||
import Vmess from '@/components/protocols/Vmess.vue'
|
||||
import Trojan from '@/components/protocols/Trojan.vue'
|
||||
import Wireguard from '@/components/protocols/Wireguard.vue'
|
||||
import Hysteria from '@/components/protocols/Hysteria.vue'
|
||||
import ShadowTls from '@/components/protocols/OutShadowTls.vue'
|
||||
import Vless from '@/components/protocols/Vless.vue'
|
||||
import Tuic from '@/components/protocols/Tuic.vue'
|
||||
import Hysteria2 from '@/components/protocols/Hysteria2.vue'
|
||||
import Tor from '@/components/protocols/Tor.vue'
|
||||
import Ssh from '@/components/protocols/Ssh.vue'
|
||||
import Selector from '@/components/protocols/Selector.vue'
|
||||
import UrlTest from '@/components/protocols/UrlTest.vue'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'stats'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
outbound: createOutbound("direct",{ "tag": "" }),
|
||||
title: "add",
|
||||
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) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.outbound = createOutbound(newData.type, newData)
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
this.outbound = createOutbound("direct",{ tag: "direct-" + RandomUtil.randomSeq(3) })
|
||||
this.title = "add"
|
||||
}
|
||||
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)
|
||||
// Use previous data
|
||||
const prevConfig = { tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
||||
this.outbound = createOutbound(this.outbound.type, prevConfig)
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.outbound, this.outboundStats)
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { Dial, Multiplex, Transport, OutTLS,
|
||||
Direct, Socks, Http, Shadowsocks, Vmess, Trojan,
|
||||
Wireguard, Hysteria, ShadowTls, Vless, Tuic,
|
||||
Hysteria2, Tor, Ssh, Selector, UrlTest }
|
||||
}
|
||||
</script>
|
||||
@@ -51,6 +51,15 @@ const Data = defineStore('Data', {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delOutbound(index: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
||||
}
|
||||
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)),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface Brutal {
|
||||
enabled: boolean
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
interface Brutal {
|
||||
enabled: boolean
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
}
|
||||
|
||||
export interface iMultiplex{
|
||||
enabled: boolean
|
||||
padding?: boolean
|
||||
brutal?: Brutal
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { iMultiplex } from "./inMultiplex"
|
||||
import { iMultiplex } from "./multiplex"
|
||||
import { iTls } from "./inTls"
|
||||
import { Dial } from "./outbounds"
|
||||
import { Transport } from "./transport"
|
||||
@@ -119,10 +119,7 @@ export interface Naive extends InboundBasics {
|
||||
export interface Hysteria extends InboundBasics {
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
obfs?: {
|
||||
type?: "salamander"
|
||||
password?: string
|
||||
}
|
||||
obfs?: string
|
||||
users: NameAuth[]
|
||||
recv_window_conn?: number
|
||||
recv_window_client?: number
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Brutal } from "./brutal"
|
||||
|
||||
export interface iMultiplex{
|
||||
enabled: boolean
|
||||
padding?: boolean
|
||||
brutal?: Brutal
|
||||
}
|
||||
|
||||
export interface oMultiplex extends iMultiplex{
|
||||
protocol?: "smux" | "yamux" | "h2mux"
|
||||
max_connections?: number
|
||||
min_streams?: number
|
||||
max_streams?: number
|
||||
}
|
||||
@@ -1,3 +1,50 @@
|
||||
export interface oTls {
|
||||
enabled?: boolean
|
||||
disable_sni?: boolean
|
||||
server_name?: string
|
||||
insecure?: boolean
|
||||
alpn?: string[]
|
||||
min_version?: string
|
||||
max_version?: string
|
||||
cipher_suites?: string[]
|
||||
certificate?: string
|
||||
certificate_path?: string
|
||||
ech?: {
|
||||
enabled: boolean
|
||||
pq_signature_schemes_enabled?: boolean
|
||||
dynamic_record_sizing_disabled?: boolean
|
||||
config?: string[],
|
||||
config_path?: string
|
||||
},
|
||||
utls?: {
|
||||
enabled: boolean
|
||||
fingerprint: string
|
||||
},
|
||||
reality?: {
|
||||
enabled: boolean
|
||||
public_key: string
|
||||
short_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultOutTls: oTls = {
|
||||
alpn: ['h3', 'h2', 'http/1.1'],
|
||||
min_version: "1.2",
|
||||
max_version: "1.3",
|
||||
cipher_suites: [],
|
||||
utls: {
|
||||
enabled: true,
|
||||
fingerprint: "chrome",
|
||||
},
|
||||
reality: {
|
||||
enabled: true,
|
||||
public_key: "",
|
||||
short_id: "",
|
||||
},
|
||||
ech: {
|
||||
enabled: true,
|
||||
pq_signature_schemes_enabled: false,
|
||||
dynamic_record_sizing_disabled: false,
|
||||
config_path: "",
|
||||
}
|
||||
}
|
||||
+208
-13
@@ -1,4 +1,6 @@
|
||||
import { oTls } from "./outTls"
|
||||
import { oMultiplex } from "./multiplex"
|
||||
import { Transport } from "./transport"
|
||||
|
||||
export const OutTypes = {
|
||||
Direct: 'direct',
|
||||
@@ -14,7 +16,7 @@ export const OutTypes = {
|
||||
ShadowTLS: 'shadowtls',
|
||||
TUIC: 'tuic',
|
||||
Hysteria2: 'hysteria2',
|
||||
Tur: 'tur',
|
||||
Tor: 'tor',
|
||||
SSH: 'ssh',
|
||||
DNS: 'dns',
|
||||
Selector: 'selector',
|
||||
@@ -43,12 +45,205 @@ interface OutboundBasics {
|
||||
tag: string
|
||||
}
|
||||
|
||||
export interface WgPeer {
|
||||
server: string
|
||||
server_port: number
|
||||
public_key: string
|
||||
pre_shared_key?: string
|
||||
allowed_ips?: string[]
|
||||
reserved?: number[]
|
||||
}
|
||||
|
||||
export interface Direct extends OutboundBasics, Dial {
|
||||
override_address?: string
|
||||
override_port?: number
|
||||
proxy_protocol?: 0 | 1 | 2
|
||||
}
|
||||
|
||||
export interface Block extends OutboundBasics {}
|
||||
|
||||
export interface SOCKS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
version?: "4" | "4a" | "5"
|
||||
username?: string
|
||||
password?: string
|
||||
network?: "udp" | "tcp"
|
||||
udp_over_tcp?: false | {
|
||||
enabled: true
|
||||
version?: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface HTTP extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
username?: string
|
||||
password?: string
|
||||
path?: string
|
||||
headers?: {
|
||||
[key: string]: string
|
||||
}
|
||||
tls?: oTls
|
||||
}
|
||||
|
||||
export interface Shadowsocks extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
method: string
|
||||
password: string
|
||||
network?: "udp" | "tcp"
|
||||
udp_over_tcp?: false | {
|
||||
enabled: true
|
||||
version?: number
|
||||
}
|
||||
multiplex?: oMultiplex
|
||||
}
|
||||
|
||||
export interface VMESS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
uuid: string
|
||||
security?: string
|
||||
alter_id: 0
|
||||
global_padding?: boolean
|
||||
authenticated_length?: boolean
|
||||
network?: "udp" | "tcp"
|
||||
packet_encoding?: string
|
||||
tls?: oTls
|
||||
multiplex?: oMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
|
||||
export interface Trojan extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
password: string
|
||||
network?: "udp" | "tcp"
|
||||
tls?: oTls
|
||||
multiplex?: oMultiplex
|
||||
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
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
obfs?: string
|
||||
auth_str?: string
|
||||
recv_window_conn?: number
|
||||
recv_window?: number
|
||||
disable_mtu_discovery?: boolean
|
||||
network?: "udp" | "tcp"
|
||||
tls: oTls
|
||||
}
|
||||
|
||||
export interface ShadowTLS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
version: 1|2|3
|
||||
password?: string
|
||||
tls: oTls
|
||||
}
|
||||
|
||||
export interface VLESS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
uuid: string
|
||||
flow?: string
|
||||
network?: "udp" | "tcp"
|
||||
packet_encoding?: string
|
||||
tls?: oTls
|
||||
multiplex?: oMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
|
||||
export interface TUIC extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
uuid: string
|
||||
password?: string
|
||||
congestion_control?: "cubic"|"new_reno"|"bbr"
|
||||
udp_relay_mode?: "native" | "quic"
|
||||
udp_over_stream?: boolean
|
||||
zero_rtt_handshake?: boolean
|
||||
heartbeat?: string
|
||||
network?: "udp" | "tcp"
|
||||
tls: oTls
|
||||
}
|
||||
|
||||
export interface Hysteria2 extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
up_mbps?: number
|
||||
down_mbps?: number
|
||||
obfs?: {
|
||||
type?: "salamander"
|
||||
password: string
|
||||
}
|
||||
password?: string
|
||||
network?: "udp" | "tcp"
|
||||
tls: oTls
|
||||
brutal_debug?: boolean
|
||||
}
|
||||
|
||||
export interface Tor extends OutboundBasics, Dial {
|
||||
executable_path?: string
|
||||
extra_args?: string[]
|
||||
data_directory: string
|
||||
torrc?: {
|
||||
ClientOnly: 0 | 1
|
||||
}
|
||||
}
|
||||
|
||||
export interface SSH extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port?: number
|
||||
user?: string
|
||||
password?: string
|
||||
private_key?: string
|
||||
private_key_path?: string
|
||||
private_key_passphrase?: string
|
||||
host_key?: string[]
|
||||
host_key_algorithms?: string[]
|
||||
client_version?: string
|
||||
}
|
||||
|
||||
export interface DNS extends OutboundBasics {}
|
||||
|
||||
export interface Selector extends OutboundBasics {
|
||||
outbounds: string[]
|
||||
url?: string
|
||||
interval?: string
|
||||
tolerance?: number
|
||||
idle_timeout?: string
|
||||
interrupt_exist_connections?: boolean
|
||||
}
|
||||
|
||||
export interface URLTest extends OutboundBasics {
|
||||
outbounds: string[]
|
||||
default?: string
|
||||
interrupt_exist_connections?: boolean
|
||||
}
|
||||
|
||||
// Create interfaces dynamically based on OutTypes keys
|
||||
type InterfaceMap = {
|
||||
[Key in keyof typeof OutTypes]: {
|
||||
@@ -64,18 +259,18 @@ export type Outbound = InterfaceMap[keyof InterfaceMap]
|
||||
const defaultValues: Record<OutType, Outbound> = {
|
||||
direct: { type: OutTypes.Direct },
|
||||
block: { type: OutTypes.Block },
|
||||
socks: { type: OutTypes.SOCKS },
|
||||
http: { type: OutTypes.HTTP },
|
||||
shadowsocks: { type: OutTypes.Shadowsocks },
|
||||
vmess: { type: OutTypes.VMess, tls: { enabled: true } },
|
||||
trojan: { type: OutTypes.Trojan },
|
||||
wireguard: { type: OutTypes.Wireguard },
|
||||
hysteria: { type: OutTypes.Hysteria },
|
||||
vless: { type: OutTypes.VLESS },
|
||||
shadowtls: { type: OutTypes.ShadowTLS },
|
||||
tuic: { type: OutTypes.TUIC },
|
||||
hysteria2: { type: OutTypes.Hysteria2, users: [], tls: {} },
|
||||
tur: { type: OutTypes.Tur },
|
||||
socks: { type: OutTypes.SOCKS, version: "5" },
|
||||
http: { type: OutTypes.HTTP, tls: {} },
|
||||
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: {} },
|
||||
tuic: { type: OutTypes.TUIC, congestion_control: 'cubic', tls: { enabled: true } },
|
||||
hysteria2: { type: OutTypes.Hysteria2, tls: { enabled: true } },
|
||||
tor: { type: OutTypes.Tor, executable_path: './tor', data_directory: '$HOME/.cache/tor', torrc: { ClientOnly: 1 } },
|
||||
ssh: { type: OutTypes.SSH },
|
||||
dns: { type: OutTypes.DNS },
|
||||
selector: { type: OutTypes.Selector },
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>users" :key="item.id">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.username">
|
||||
<v-card-subtitle>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
Last Login
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
hide-details
|
||||
type="number"
|
||||
clearable
|
||||
@click:clear="delete appConfig.ntp.server_port"
|
||||
@click:clear="delete appConfig.ntp?.server_port"
|
||||
label="Server Port"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-title>
|
||||
<v-card-subtitle>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.desc }}</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>inbounds" :key="item.tag">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.tag">
|
||||
<v-card-subtitle>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.type }}</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -1,8 +1,29 @@
|
||||
<template>
|
||||
<OutboundVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:id="modal.id"
|
||||
:stats="modal.stats"
|
||||
:data="modal.data"
|
||||
@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(-1)">{{ $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[]>outbounds" :key="item.tag">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.tag">
|
||||
<v-card-subtitle>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.type }}</v-col>
|
||||
</v-row>
|
||||
@@ -15,6 +36,32 @@
|
||||
</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(index)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="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="delOutbound(index)">{{ $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-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -22,13 +69,94 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed } from 'vue'
|
||||
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'
|
||||
|
||||
const appConfig = Data().config
|
||||
const outbounds = computed((): any[] => {
|
||||
if (!appConfig || !('outbounds' in appConfig) || !Array.isArray(appConfig.outbounds)) {
|
||||
return []
|
||||
}
|
||||
return appConfig.outbounds
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const outbounds = computed((): Outbound[] => {
|
||||
return <Outbound[]> appConfig.value.outbounds
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental.v2ray_api.stats
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: -1,
|
||||
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.visible = true
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Outbound, stats: boolean) => {
|
||||
// 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
|
||||
}
|
||||
modal.value.visible = false
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
visible: false,
|
||||
resource: "outbound",
|
||||
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 showStats = (tag: string) => {
|
||||
stats.value.tag = tag
|
||||
stats.value.visible = true
|
||||
}
|
||||
const closeStats = () => {
|
||||
stats.value.visible = false
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user