add tls
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<v-card subtitle="ACME" style="background-color: inherit;">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('enable')" v-model="enabled" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" md="8" v-if="enabled">
|
||||
<v-text-field
|
||||
:label="$t('rule.domain') + ' ' + $t('commaSeparated')"
|
||||
hide-details
|
||||
v-model="domains">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="enabled">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionDir">
|
||||
<v-text-field
|
||||
:label="$t('tls.acme.dataDir')"
|
||||
hide-details
|
||||
v-model="acme.data_directory">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionDefault">
|
||||
<v-combobox
|
||||
v-model="acme.default_server_name"
|
||||
:items="acme.domain"
|
||||
:label="$t('tls.acme.defaultDomain')"
|
||||
hide-details
|
||||
></v-combobox>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionEmail">
|
||||
<v-text-field
|
||||
:label="$t('email')"
|
||||
hide-details
|
||||
v-model="acme.email">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="optionChallenge">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('tls.acme.httpChallenge')" v-model="acme.disable_http_challenge" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('tls.acme.tlsChallenge')" v-model="acme.disable_tls_alpn_challenge" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="optionPorts">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('tls.acme.altHport')"
|
||||
hide-details
|
||||
type="number"
|
||||
min=1
|
||||
max="65532"
|
||||
v-model.number="acme.alternative_http_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('tls.acme.altTport')"
|
||||
hide-details
|
||||
type="number"
|
||||
min=1
|
||||
max="65532"
|
||||
v-model.number="acme.alternative_tls_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="optionProvider">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
v-model="caProvider"
|
||||
:items="providerList"
|
||||
:label="$t('tls.acme.caProvider')"
|
||||
hide-details
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="8" v-if="caProvider == ''">
|
||||
<v-text-field
|
||||
:label="$t('tls.acme.customCa')"
|
||||
hide-details
|
||||
v-model="acme.provider">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="acme.external_account != undefined">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Key ID"
|
||||
hide-details
|
||||
v-model="acme.external_account.key_id">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="MAC Key"
|
||||
hide-details
|
||||
v-model="acme.external_account.mac_key">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="acme.dns01_challenge != undefined">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
:label="$t('tls.acme.dns01Provider')"
|
||||
hide-details
|
||||
:items="dnsProviders.map(d => d.provider)"
|
||||
@update:model-value="acme.dns01_challenge = { provider: $event }"
|
||||
v-model="acme.dns01_challenge.provider">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4"
|
||||
v-for="item in dnsProviders.filter(d => d.provider == acme.dns01_challenge?.provider)[0]?.params"
|
||||
:key="item">
|
||||
<v-text-field
|
||||
:label="item"
|
||||
hide-details
|
||||
v-model="acme.dns01_challenge[item]">
|
||||
</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>{{ $t('tls.acme.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionDir" color="primary" :label="$t('tls.acme.dataDir')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionDefault" color="primary" :label="$t('tls.acme.defaultDomain')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionEmail" color="primary" :label="$t('email')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionChallenge" color="primary" :label="$t('tls.acme.disableChallenges')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionPorts" color="primary" :label="$t('tls.acme.altPorts')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionProvider" color="primary" :label="$t('tls.acme.caProvider')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionExt" color="primary" :label="$t('tls.acme.extAcc')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionDns01" color="primary" :label="$t('tls.acme.dns01')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</template>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { acme } from '@/types/inTls'
|
||||
|
||||
export default {
|
||||
props: ['tls'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
providerList: [
|
||||
{ title: "Let's Encrypt", value: "letsencrypt" },
|
||||
{ title: "ZeroSSL", value: "zerossl" },
|
||||
{ title: "Custom", value: "" }
|
||||
],
|
||||
dnsProviders: [
|
||||
{ provider: "cloudflare", params: [ "api_token" ] },
|
||||
{ provider: "alidns", params: [ "access_key_id","access_key_secret","region_id" ] }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
acme() {
|
||||
return <acme>this.$props.tls.acme
|
||||
},
|
||||
enabled: {
|
||||
get() { return this.acme != undefined },
|
||||
set(v: boolean) { this.$props.tls.acme = v ? { domain: [] } : undefined }
|
||||
},
|
||||
domains: {
|
||||
get() { return this.acme?.domain ? this.acme.domain.join(',') : "" },
|
||||
set(v: string) {
|
||||
if(!v.endsWith(',')) {
|
||||
this.acme.domain = v.length > 0 ? v.split(',') : []
|
||||
}
|
||||
}
|
||||
},
|
||||
caProvider: {
|
||||
get() { return this.acme?.provider && ['letsencrypt','zerossl'].includes(this.acme.provider) ? this.acme?.provider : '' },
|
||||
set(v: string) { this.acme.provider = ['letsencrypt','zerossl'].includes(v) ? v : 'https://' }
|
||||
},
|
||||
optionDir: {
|
||||
get(): boolean { return this.acme?.data_directory != undefined },
|
||||
set(v:boolean) { this.acme.data_directory = v ? '' : undefined }
|
||||
},
|
||||
optionDefault: {
|
||||
get(): boolean { return this.acme?.default_server_name != undefined },
|
||||
set(v:boolean) { this.acme.default_server_name = v ? this.domains.length>0 ? this.domains[0] : '' : undefined }
|
||||
},
|
||||
optionEmail: {
|
||||
get(): boolean { return this.acme?.email != undefined },
|
||||
set(v:boolean) { this.acme.email = v ? '' : undefined }
|
||||
},
|
||||
optionChallenge: {
|
||||
get(): boolean { return this.acme?.disable_http_challenge != undefined || this.acme?.disable_tls_alpn_challenge != undefined },
|
||||
set(v:boolean) {
|
||||
if (v) {
|
||||
this.acme.disable_http_challenge = false
|
||||
this.acme.disable_tls_alpn_challenge = false
|
||||
} else {
|
||||
delete this.acme.disable_http_challenge
|
||||
delete this.acme.disable_tls_alpn_challenge
|
||||
}
|
||||
}
|
||||
},
|
||||
optionPorts: {
|
||||
get(): boolean { return this.acme?.alternative_http_port != undefined || this.acme?.alternative_tls_port != undefined },
|
||||
set(v:boolean) {
|
||||
if (v) {
|
||||
this.acme.alternative_http_port = 80
|
||||
this.acme.alternative_tls_port = 443
|
||||
} else {
|
||||
delete this.acme.alternative_http_port
|
||||
delete this.acme.alternative_tls_port
|
||||
}
|
||||
}
|
||||
},
|
||||
optionProvider: {
|
||||
get(): boolean { return this.acme?.provider != undefined },
|
||||
set(v:boolean) { this.acme.provider = v ? 'letsencrypt' : undefined }
|
||||
},
|
||||
optionExt: {
|
||||
get(): boolean { return this.acme?.external_account != undefined },
|
||||
set(v:boolean) { this.acme.external_account = v ? { key_id: '', mac_key: '' } : undefined }
|
||||
},
|
||||
optionDns01: {
|
||||
get(): boolean { return this.acme?.dns01_challenge != undefined },
|
||||
set(v:boolean) { this.acme.dns01_challenge = v ? { provider: 'cloudflare' } : undefined }
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<v-card subtitle="ECH" style="background-color: inherit;">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('enable')" v-model="enabled" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="enabled">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Post-Quantum Schemes" v-model="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="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 ech.key"
|
||||
>{{ $t('tls.usePath') }}</v-btn>
|
||||
<v-btn
|
||||
@click="delete ech.key_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.keyPath')"
|
||||
hide-details
|
||||
v-model="ech.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="echKeyText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.cert')"
|
||||
hide-details
|
||||
v-model="echConfigText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ech } from '@/types/inTls'
|
||||
|
||||
export default {
|
||||
props: ['iTls','oTls'],
|
||||
data() {
|
||||
return {
|
||||
useEchPath: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ech() {
|
||||
return <ech>this.$props.iTls.ech
|
||||
},
|
||||
enabled: {
|
||||
get() { return this.ech?.enabled?? false },
|
||||
set(v: boolean) {
|
||||
this.$props.iTls.ech = v ? { enabled: true } : undefined
|
||||
this.$props.oTls.ech = v ? {} : undefined
|
||||
}
|
||||
},
|
||||
echKeyText: {
|
||||
get(): string { return this.ech?.key ? this.ech.key.join('\n') : '' },
|
||||
set(newValue:string) { this.ech.key = newValue.split('\n') }
|
||||
},
|
||||
echConfigText: {
|
||||
get(): string { return this.oTls.ech?.config ? this.oTls.ech.config.join('\n') : '' },
|
||||
set(newValue:string) { this.oTls.ech.config = newValue.split('\n') }
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +1,20 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('objects.tls')">
|
||||
<v-row v-if="tlsOptional">
|
||||
<v-col cols="auto">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tlsOptional">
|
||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.enabled">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Preset"
|
||||
:items="tlsItems"
|
||||
@update:model-value="changeTlsItem($event)"
|
||||
v-model="tlsId">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-if="tls.enabled">
|
||||
<template v-if="tls.enabled && tlsId == 0">
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-btn-toggle v-model="usePath"
|
||||
@@ -103,32 +112,32 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<v-card-actions v-if="tls.enabled">
|
||||
<v-card-actions v-if="tls.enabled && tlsId == 0">
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>{{ $t('tls.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>{{ $t('tls.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -136,11 +145,11 @@
|
||||
<script lang="ts">
|
||||
import { iTls, defaultInTls } from '@/types/inTls'
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['inbound', 'tlsConfigs', 'tls_id'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
usePath: 0,
|
||||
usePath: this.$props.inbound.tls.key == undefined ? 0 : 1,
|
||||
defaults: defaultInTls,
|
||||
alpn: [
|
||||
{ title: "H3", value: 'h3' },
|
||||
@@ -173,8 +182,15 @@ export default {
|
||||
tls(): iTls {
|
||||
return <iTls> this.$props.inbound.tls
|
||||
},
|
||||
tlsItems(): any[] {
|
||||
return [ { title: '', value: 0 }, ...this.$props.tlsConfigs?.map((t:any) => { return { title: t.name, value: t.id } } )]
|
||||
},
|
||||
tlsId: {
|
||||
get() { return this.tls_id.value?? 0 },
|
||||
set(newValue: boolean) { this.$props.tls_id.value = newValue }
|
||||
},
|
||||
tlsEnable: {
|
||||
get() { return Object.hasOwn(this.$props.inbound.tls, 'enabled') ? this.tls.enabled : false },
|
||||
get() { return this.tls.enabled?? false },
|
||||
set(newValue: boolean) { this.$props.inbound.tls = newValue ? { enabled: true } : {} }
|
||||
},
|
||||
tlsOptional(): boolean {
|
||||
@@ -190,23 +206,33 @@ export default {
|
||||
},
|
||||
optionSNI: {
|
||||
get(): boolean { return this.tls.server_name != undefined },
|
||||
set(v:boolean) { this.$props.inbound.tls.server_name = v ? '' : undefined }
|
||||
set(v:boolean) { this.tls.server_name = v ? '' : undefined }
|
||||
},
|
||||
optionALPN: {
|
||||
get(): boolean { return this.tls.alpn != undefined },
|
||||
set(v:boolean) { this.$props.inbound.tls.alpn = v ? defaultInTls.alpn : undefined }
|
||||
set(v:boolean) { this.tls.alpn = v ? defaultInTls.alpn : undefined }
|
||||
},
|
||||
optionMinV: {
|
||||
get(): boolean { return this.tls.min_version != undefined },
|
||||
set(v:boolean) { this.$props.inbound.tls.min_version = v ? defaultInTls.min_version : undefined }
|
||||
set(v:boolean) { this.tls.min_version = v ? defaultInTls.min_version : undefined }
|
||||
},
|
||||
optionMaxV: {
|
||||
get(): boolean { return this.tls.max_version != undefined },
|
||||
set(v:boolean) { this.$props.inbound.tls.max_version = v ? defaultInTls.max_version : undefined }
|
||||
set(v:boolean) { this.tls.max_version = v ? defaultInTls.max_version : undefined }
|
||||
},
|
||||
optionCS: {
|
||||
get(): boolean { return this.tls.cipher_suites != undefined },
|
||||
set(v:boolean) { this.$props.inbound.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
||||
set(v:boolean) { this.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTlsItem(id: number){
|
||||
if (id>0) {
|
||||
const tlsConfig = this.$props.tlsConfigs?.findLast((t:any) => t.id == id)
|
||||
if (tlsConfig) this.$props.inbound.tls = tlsConfig.server
|
||||
} else {
|
||||
this.$props.inbound.tls = { enabled: this.tls.enabled }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<v-row v-if="Inbound.handshake_for_server_name != undefined">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('types.shdwTls.adHS')"
|
||||
:label="$t('types.shdwTls.addHS')"
|
||||
hide-details
|
||||
append-icon="mdi-plus"
|
||||
@click:append="addHandshakeServer()"
|
||||
|
||||
@@ -32,11 +32,11 @@ const saveChanges = () => {
|
||||
}
|
||||
|
||||
const oldData = computed((): any => {
|
||||
return {config: store.oldData.config, clients: store.oldData.clients}
|
||||
return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs}
|
||||
})
|
||||
|
||||
const newData = computed((): any => {
|
||||
return {config: store.config, clients: store.clients}
|
||||
return {config: store.config, clients: store.clients, tls: store.tlsConfigs}
|
||||
})
|
||||
|
||||
const stateChange = computed((): any => {
|
||||
|
||||
@@ -54,6 +54,7 @@ const menu = [
|
||||
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
|
||||
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
|
||||
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
|
||||
{ title: 'pages.tls', icon: 'mdi-certificate', path: '/tls' },
|
||||
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
|
||||
{ title: 'pages.admins', icon: 'mdi-account-tie', path: '/admins' },
|
||||
{ title: 'pages.settings', icon: 'mdi-cog', path: '/settings' },
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" :id="id" />
|
||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" />
|
||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
||||
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-card-text>
|
||||
@@ -57,7 +57,6 @@
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { InTypes, createInbound } from '@/types/inbounds'
|
||||
import Listen from '@/components/Listen.vue'
|
||||
@@ -75,7 +74,7 @@ import RandomUtil from '@/plugins/randomUtil'
|
||||
import Multiplex from '@/components/Multiplex.vue'
|
||||
import Transport from '@/components/Transport.vue'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'stats', 'inTags', 'outTags'],
|
||||
props: ['visible', 'data', 'id', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
@@ -84,6 +83,7 @@ export default {
|
||||
loading: false,
|
||||
inTypes: InTypes,
|
||||
inboundStats: false,
|
||||
tls_id: { value: 0 },
|
||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||
}
|
||||
},
|
||||
@@ -92,6 +92,7 @@ export default {
|
||||
if (this.$props.id != -1) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.inbound = createInbound(newData.type, newData)
|
||||
this.tls_id.value = this.$props.tlsConfigs?.findLast((t:any) => t.inbounds?.includes(this.inbound.tag))?.id?? 0
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
@@ -114,7 +115,7 @@ export default {
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.inbound, this.inboundStats)
|
||||
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value)
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</v-row>
|
||||
<v-row v-for="l in clientLinks">
|
||||
<v-col style="text-align: center;" @click="copyToClipboard(l.uri)">
|
||||
<v-chip>{{ l.remark }}</v-chip><br />
|
||||
<v-chip>{{ l.remark?? "-" }}</v-chip><br />
|
||||
<QrcodeVue :value="l.uri" :size="300" :margin="1" style="border-radius: 1rem;" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-card-title>
|
||||
{{ $t('actions.' + title) + " " + $t('objects.tls') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-card class="rounded-lg">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('client.name')"
|
||||
hide-details
|
||||
v-model="tls.name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col align="end">
|
||||
<v-btn-toggle v-model="tlsType"
|
||||
class="rounded-xl"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
@update:model-value="changeTlsType"
|
||||
shaped
|
||||
mandatory>
|
||||
<v-btn>TLS</v-btn>
|
||||
<v-btn>Reality</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="inTls.server_name != undefined">
|
||||
<v-text-field
|
||||
label="SNI"
|
||||
hide-details
|
||||
v-model="inTls.server_name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<template v-if="tlsType == 0">
|
||||
<v-col cols="12" sm="6" md="4" v-if="inTls.min_version">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.minVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="inTls.min_version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="inTls.max_version">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.maxVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="inTls.max_version">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="inTls.alpn">
|
||||
<v-select
|
||||
hide-details
|
||||
label="ALPN"
|
||||
multiple
|
||||
:items="alpn"
|
||||
v-model="inTls.alpn">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="8" v-if="inTls.cipher_suites != undefined">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('tls.cs')"
|
||||
multiple
|
||||
:items="cipher_suites"
|
||||
v-model="inTls.cipher_suites">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
<template v-if="tlsType == 0">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn-toggle v-model="usePath"
|
||||
class="rounded-xl"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
shaped
|
||||
mandatory>
|
||||
<v-btn
|
||||
@click="inTls.key=undefined; inTls.certificate=undefined"
|
||||
>{{ $t('tls.usePath') }}</v-btn>
|
||||
<v-btn
|
||||
@click="inTls.key_path=undefined; inTls.certificate_path=undefined"
|
||||
>{{ $t('tls.useText') }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="usePath == 0">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
:label="$t('tls.certPath')"
|
||||
hide-details
|
||||
v-model="inTls.certificate_path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
:label="$t('tls.keyPath')"
|
||||
hide-details
|
||||
v-model="inTls.key_path">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-else>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.cert')"
|
||||
hide-details
|
||||
v-model="certText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-textarea
|
||||
:label="$t('tls.key')"
|
||||
hide-details
|
||||
v-model="keyText">
|
||||
</v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="outTls.utls != undefined">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Fingerprint"
|
||||
:items="fingerprints"
|
||||
v-model="outTls.utls.fingerprint">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('tls.disableSni')" v-model="disableSni" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('tls.insecure')" v-model="insecure" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template v-if="outTls.reality && inTls.reality">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('types.shdwTls.hs')"
|
||||
hide-details
|
||||
v-model="inTls.reality.handshake.server">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('out.port')"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="server_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
:label="$t('tls.privKey')"
|
||||
hide-details
|
||||
v-model="inTls.reality.private_key">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
:label="$t('tls.pubKey')"
|
||||
hide-details
|
||||
v-model="outTls.reality.public_key">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field
|
||||
label="Short IDs"
|
||||
hide-details
|
||||
v-model="short_id">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionTime">
|
||||
<v-text-field
|
||||
label="Max Time Diference"
|
||||
type="number"
|
||||
min="1"
|
||||
:suffix="$t('date.m')"
|
||||
hide-details
|
||||
v-model="max_time">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<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>{{ $t('tls.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<template v-if="tlsType == 0">
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionFP" color="primary" label="UTLS" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionTime" color="primary" label="Max Time Difference" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<AcmeVue :tls="inTls" />
|
||||
<EchVue :iTls="inTls" :oTls="outTls" />
|
||||
</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 { iTls, defaultInTls } from '@/types/inTls'
|
||||
import { oTls, defaultOutTls } from '@/types/outTls'
|
||||
import AcmeVue from '@/components/Acme.vue'
|
||||
import EchVue from '@/components/Ech.vue'
|
||||
export default {
|
||||
props: ['visible', 'data', 'index'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
tls: { id: -1, name: '', inbounds: [], server: <iTls>{ enabled: true }, client: <oTls>{} },
|
||||
title: "add",
|
||||
loading: false,
|
||||
menu: false,
|
||||
tlsType: 0,
|
||||
usePath: 0,
|
||||
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" },
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
if (this.$props.index != -1) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.tls = newData
|
||||
this.tlsType = newData.server?.reality == undefined ? 0 : 1
|
||||
this.usePath = newData.server?.key == undefined ? 0 : 1
|
||||
this.title = "edit"
|
||||
}
|
||||
else {
|
||||
this.tls = { id: 0, name: '', inbounds: [], server: {enabled: true}, client: {} }
|
||||
this.usePath = 0
|
||||
this.title = "add"
|
||||
}
|
||||
},
|
||||
changeTlsType(){
|
||||
if (this.tlsType) {
|
||||
this.tls.server = <iTls>{ enabled: true, reality: { enabled: true, handshake: { server_port: 443 } }, server_name: "" }
|
||||
this.tls.client = <oTls>{ reality: { public_key: "" } }
|
||||
} else {
|
||||
this.tls.server = <iTls>{ enabled: true }
|
||||
this.tls.client = <oTls>{}
|
||||
}
|
||||
},
|
||||
closeModal() {
|
||||
this.updateData() // reset
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.tls)
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
inTls(): iTls {
|
||||
return <iTls> this.tls.server
|
||||
},
|
||||
outTls(): oTls {
|
||||
return <oTls> this.tls.client
|
||||
},
|
||||
certText: {
|
||||
get(): string { return this.inTls.certificate ? this.inTls.certificate.join('\n') : '' },
|
||||
set(v:string) { this.inTls.certificate = v.split('\n') }
|
||||
},
|
||||
keyText: {
|
||||
get(): string { return this.inTls.key ? this.inTls.key.join('\n') : '' },
|
||||
set(v:string) { this.inTls.key = v.split('\n') }
|
||||
},
|
||||
disableSni: {
|
||||
get() { return this.outTls.disable_sni ?? false },
|
||||
set(v: boolean) { this.outTls.disable_sni = v ? true : undefined }
|
||||
},
|
||||
insecure: {
|
||||
get() { return this.outTls.insecure ?? false },
|
||||
set(v: boolean) { this.outTls.insecure = v ? true : undefined }
|
||||
},
|
||||
server_port: {
|
||||
get() { return this.inTls.reality?.handshake?.server_port ? this.inTls.reality.handshake.server_port : 443 },
|
||||
set(v: any) {
|
||||
if (this.inTls.reality){
|
||||
this.inTls.reality.handshake.server_port = v.length == 0 || v == 0 ? 443 : parseInt(v)
|
||||
}
|
||||
}
|
||||
},
|
||||
short_id: {
|
||||
get() { return this.inTls.reality?.short_id ? this.inTls.reality.short_id.join(',') : undefined },
|
||||
set(v: string) {
|
||||
if (this.inTls.reality){
|
||||
this.inTls.reality.short_id = v.length > 0 ? v.split(',') : []
|
||||
}
|
||||
}
|
||||
},
|
||||
max_time: {
|
||||
get() { return this.inTls?.reality?.max_time_difference ? this.inTls.reality.max_time_difference.replace('m','') : 1 },
|
||||
set(v: number) {
|
||||
if (this.inTls.reality){
|
||||
this.inTls.reality.max_time_difference = v > 0 ? v + 'm' : '1m'
|
||||
}
|
||||
}
|
||||
},
|
||||
optionSNI: {
|
||||
get(): boolean { return this.inTls.server_name != undefined },
|
||||
set(v:boolean) { this.inTls.server_name = v ? '' : undefined }
|
||||
},
|
||||
optionALPN: {
|
||||
get(): boolean { return this.inTls.alpn != undefined },
|
||||
set(v:boolean) { this.inTls.alpn = v ? defaultInTls.alpn : undefined }
|
||||
},
|
||||
optionMinV: {
|
||||
get(): boolean { return this.inTls.min_version != undefined },
|
||||
set(v:boolean) { this.inTls.min_version = v ? defaultInTls.min_version : undefined }
|
||||
},
|
||||
optionMaxV: {
|
||||
get(): boolean { return this.inTls.max_version != undefined },
|
||||
set(v:boolean) { this.inTls.max_version = v ? defaultInTls.max_version : undefined }
|
||||
},
|
||||
optionCS: {
|
||||
get(): boolean { return this.inTls.cipher_suites != undefined },
|
||||
set(v:boolean) { this.inTls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
||||
},
|
||||
optionFP: {
|
||||
get(): boolean { return this.outTls.utls != undefined },
|
||||
set(v:boolean) { this.outTls.utls = v ? defaultOutTls.utls : undefined }
|
||||
},
|
||||
optionEch: {
|
||||
get(): boolean { return this.outTls.ech != undefined },
|
||||
set(v:boolean) { this.outTls.ech = v ? defaultOutTls.ech : undefined }
|
||||
},
|
||||
optionTime: {
|
||||
get(): boolean { return this.inTls?.reality?.max_time_difference != undefined },
|
||||
set(v:boolean) { if (this.inTls.reality) this.inTls.reality.max_time_difference = v ? "1m" : undefined }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(v) {
|
||||
if (v) {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { AcmeVue, EchVue }
|
||||
}
|
||||
</script>
|
||||
@@ -21,6 +21,7 @@ export default {
|
||||
invalidLogin: "Invalid Login!",
|
||||
online: "Online",
|
||||
version: "Version",
|
||||
email: "Email",
|
||||
commaSeparated: "(comma separated)",
|
||||
error: {
|
||||
dplData: "Duplicate Data",
|
||||
@@ -32,6 +33,7 @@ export default {
|
||||
outbounds: "Outbounds",
|
||||
clients: "Clients",
|
||||
rules: "Rules",
|
||||
tls: "TLS Settings",
|
||||
basics: "Basics",
|
||||
admins: "Admins",
|
||||
settings: "Settings",
|
||||
@@ -326,9 +328,26 @@ export default {
|
||||
minVer: "Minimum Version",
|
||||
maxVer: "Maximum Version",
|
||||
cs: "Cipher suits",
|
||||
privKey: "Private Key",
|
||||
pubKey: "Public Key",
|
||||
disableSni: "Disable SNI",
|
||||
insecure: "Allow Insecure",
|
||||
acme: {
|
||||
options: "ACME Options",
|
||||
dataDir: "Data Directory",
|
||||
defaultDomain: "Default Domain",
|
||||
disableChallenges: "Disable Challenges",
|
||||
httpChallenge: "Disable HTTP Challenge",
|
||||
tlsChallenge: "Disable TLS Challenge",
|
||||
altPorts: "Alternative Ports",
|
||||
altHport: "Alternative HTTP Port",
|
||||
altTport: "Alternative TLS Port",
|
||||
caProvider: "CA Provider",
|
||||
customCa: "Custom CA Provider",
|
||||
extAcc: "External Account",
|
||||
dns01: "DNS01 Challenge",
|
||||
dns01Provider: "DNS01 Challenge Provider",
|
||||
},
|
||||
},
|
||||
stats: {
|
||||
upload: "Upload",
|
||||
|
||||
@@ -21,6 +21,7 @@ export default {
|
||||
invalidLogin: "ورود نامعتبر!",
|
||||
online: "آنلاین",
|
||||
version: "نسخه",
|
||||
email: "ایمیل",
|
||||
commaSeparated: "(جداشده با کاما)",
|
||||
error: {
|
||||
dplData: "داده تکراری",
|
||||
@@ -32,6 +33,7 @@ export default {
|
||||
outbounds: "خروجیها",
|
||||
clients: "کاربران",
|
||||
rules: "قوانین",
|
||||
tls: "رمزنگاریها",
|
||||
basics: "ترازها",
|
||||
admins: "ادمینها",
|
||||
settings: "پیکربندی",
|
||||
@@ -325,9 +327,26 @@ export default {
|
||||
minVer: "کمینه نسخه",
|
||||
maxVer: "بیشینه نسخه",
|
||||
cs: "مدلهای رمزنگاری",
|
||||
privKey: "کلید خصوصی",
|
||||
pubKey: "کلید عمومی",
|
||||
disableSni: "غیرفعالسازی SNI",
|
||||
insecure: "تایید ارتباط ناامن",
|
||||
acme: {
|
||||
options: "گزینههای ACME",
|
||||
dataDir: "مسیر دادهها",
|
||||
defaultDomain: "دامنه پیشفرض",
|
||||
disableChallenges: "بستن چالشها",
|
||||
httpChallenge: "بستن چالش HTTP",
|
||||
tlsChallenge: "بستن چالش TLS",
|
||||
altPorts: "پورتهای جایگزین",
|
||||
altHport: "پورت جایگزین HTTP",
|
||||
altTport: "پورت جایگزین TLS",
|
||||
caProvider: "فراهم کننده گواهی",
|
||||
customCa: "فراهم کننده دیگر",
|
||||
extAcc: "حساب خارجی",
|
||||
dns01: "چالش DNS01",
|
||||
dns01Provider: "فراهم کننده چالش DNS01",
|
||||
},
|
||||
},
|
||||
stats: {
|
||||
upload: "آپلود",
|
||||
|
||||
@@ -21,6 +21,7 @@ export default {
|
||||
invalidLogin: "Đăng nhập không hợp lệ!",
|
||||
online: "Trực tuyến",
|
||||
version: "Phiên bản",
|
||||
email: "Email",
|
||||
commaSeparated: "(được phân tách bằng dấu phẩy)",
|
||||
error: {
|
||||
dplData: "Dữ liệu trùng lặp",
|
||||
@@ -32,6 +33,7 @@ export default {
|
||||
outbounds: "Đầu ra",
|
||||
clients: "Khách hàng",
|
||||
rules: "Quy tắc",
|
||||
tls: "Cài đặt TLS",
|
||||
basics: "Cơ bản",
|
||||
admins: "Quản trị viên",
|
||||
settings: "Cài đặt",
|
||||
@@ -327,9 +329,26 @@ export default {
|
||||
minVer: "Phiên bản Tối thiểu",
|
||||
maxVer: "Phiên bản Tối đa",
|
||||
cs: "Các bộ mã hóa",
|
||||
privKey: "Khóa riêng",
|
||||
pubKey: "Khóa Công khai",
|
||||
disableSni: "Tắt SNI",
|
||||
insecure: "Cho phép Không an toàn",
|
||||
acme: {
|
||||
options: "Tùy chọn ACME",
|
||||
dataDir: "Thư mục Dữ liệu",
|
||||
defaultDomain: "Tên miền Mặc định",
|
||||
disableChallenges: "Vô hiệu hóa Thách thức",
|
||||
httpChallenge: "Vô hiệu hóa Thách thức HTTP",
|
||||
tlsChallenge: "Vô hiệu hóa Thách thức TLS",
|
||||
altPorts: "Cổng Thay thế",
|
||||
altHport: "Cổng HTTP Thay thế",
|
||||
altTport: "Cổng TLS Thay thế",
|
||||
caProvider: "Nhà cung cấp CA",
|
||||
customCa: "Nhà cung cấp CA Tùy chỉnh",
|
||||
extAcc: "Tài khoản Bên ngoài",
|
||||
dns01: "Thách thức DNS01",
|
||||
dns01Provider: "Nhà cung cấp Thách thức DNS01"
|
||||
},
|
||||
},
|
||||
stats: {
|
||||
upload: "Tải lên",
|
||||
|
||||
@@ -21,6 +21,7 @@ export default {
|
||||
invalidLogin: "登录无效!",
|
||||
online: "在线",
|
||||
version: "版本",
|
||||
email: "电子邮件",
|
||||
commaSeparated: "(逗号分隔)",
|
||||
error: {
|
||||
dplData: "重复数据",
|
||||
@@ -32,6 +33,7 @@ export default {
|
||||
outbounds: "出站管理",
|
||||
clients: "用户管理",
|
||||
rules: "路由列表",
|
||||
tls: "TLS 设置",
|
||||
basics: "基础信息",
|
||||
admins: "管理员",
|
||||
settings: "设置",
|
||||
@@ -327,9 +329,26 @@ export default {
|
||||
minVer: "最低版本",
|
||||
maxVer: "最高版本",
|
||||
cs: "密码套件",
|
||||
privKey: "私钥",
|
||||
pubKey: "公钥",
|
||||
disableSni: "禁用SNI",
|
||||
insecure: "允许不安全",
|
||||
acme: {
|
||||
options: "ACME 选项",
|
||||
dataDir: "数据目录",
|
||||
defaultDomain: "默认域名",
|
||||
disableChallenges: "禁用挑战",
|
||||
httpChallenge: "禁用 HTTP 挑战",
|
||||
tlsChallenge: "禁用 TLS 挑战",
|
||||
altPorts: "替代端口",
|
||||
altHport: "替代 HTTP 端口",
|
||||
altTport: "替代 TLS 端口",
|
||||
caProvider: "CA 提供商",
|
||||
customCa: "自定义 CA 提供商",
|
||||
extAcc: "外部账户",
|
||||
dns01: "DNS01 挑战",
|
||||
dns01Provider: "DNS01 挑战提供商"
|
||||
},
|
||||
},
|
||||
stats: {
|
||||
upload: "上传",
|
||||
|
||||
@@ -22,6 +22,7 @@ export default {
|
||||
invalidLogin: "登錄無效!",
|
||||
online: "在線",
|
||||
version: "版本",
|
||||
email: "電子郵件",
|
||||
commaSeparated: "(逗號分隔)",
|
||||
error: {
|
||||
dplData: "重複數據",
|
||||
@@ -33,6 +34,7 @@ export default {
|
||||
outbounds: "出站管理",
|
||||
clients: "用戶管理",
|
||||
rules: "路由列表",
|
||||
tls: "TLS 設置",
|
||||
basics: "基礎信息",
|
||||
admins: "管理員",
|
||||
settings: "設置",
|
||||
@@ -328,9 +330,26 @@ export default {
|
||||
minVer: "最低版本",
|
||||
maxVer: "最高版本",
|
||||
cs: "加密套件",
|
||||
privKey: "私鑰",
|
||||
pubKey: "公鑰",
|
||||
disableSni: "停用 SNI",
|
||||
insecure: "允許不安全連線",
|
||||
acme: {
|
||||
options: "ACME 選項",
|
||||
dataDir: "數據目錄",
|
||||
defaultDomain: "默認域名",
|
||||
disableChallenges: "禁用挑戰",
|
||||
httpChallenge: "禁用 HTTP 挑戰",
|
||||
tlsChallenge: "禁用 TLS 挑戰",
|
||||
altPorts: "替代端口",
|
||||
altHport: "替代 HTTP 端口",
|
||||
altTport: "替代 TLS 端口",
|
||||
caProvider: "CA 提供商",
|
||||
customCa: "自定義 CA 提供商",
|
||||
extAcc: "外部賬戶",
|
||||
dns01: "DNS01 挑戰",
|
||||
dns01Provider: "DNS01 挑戰提供商"
|
||||
},
|
||||
},
|
||||
stats: {
|
||||
upload: "上傳",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Hysteria, Hysteria2, InTypes, Inbound, Naive, Shadowsocks, TUIC, Trojan, VLESS, VMess } from "@/types/inbounds"
|
||||
import { HTTP, WebSocket, QUIC, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport";
|
||||
import { HTTP, WebSocket, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport";
|
||||
|
||||
export interface Link {
|
||||
type: "local" | "external" | "sub"
|
||||
@@ -13,25 +13,25 @@ function utf8ToBase64(utf8String: string): string {
|
||||
}
|
||||
|
||||
export namespace LinkUtil {
|
||||
export function linkGenerator(user: string, inbound: Inbound): string {
|
||||
export function linkGenerator(user: string, inbound: Inbound, tlsClient: any = null): string {
|
||||
const addr = location.hostname
|
||||
switch(inbound.type){
|
||||
case InTypes.Shadowsocks:
|
||||
return shadowsocksLink(user,<Shadowsocks>inbound,addr)
|
||||
return shadowsocksLink(user,<Shadowsocks>inbound, addr)
|
||||
case InTypes.Naive:
|
||||
return naiveLink(user,<Naive>inbound,addr)
|
||||
return naiveLink(user,<Naive>inbound, addr, tlsClient)
|
||||
case InTypes.Hysteria:
|
||||
return hysteriaLink(user,<Hysteria>inbound,addr)
|
||||
return hysteriaLink(user,<Hysteria>inbound, addr, tlsClient)
|
||||
case InTypes.Hysteria2:
|
||||
return hysteria2Link(user,<Hysteria2>inbound,addr)
|
||||
return hysteria2Link(user,<Hysteria2>inbound, addr, tlsClient)
|
||||
case InTypes.TUIC:
|
||||
return tuicLink(user,<TUIC>inbound,addr)
|
||||
return tuicLink(user,<TUIC>inbound, addr, tlsClient)
|
||||
case InTypes.VLESS:
|
||||
return vlessLink(user,<VLESS>inbound,addr)
|
||||
return vlessLink(user,<VLESS>inbound, addr, tlsClient)
|
||||
case InTypes.Trojan:
|
||||
return trojanLink(user,<Trojan>inbound,addr)
|
||||
return trojanLink(user,<Trojan>inbound, addr, tlsClient)
|
||||
case InTypes.VMess:
|
||||
return vmessLink(user,<VMess>inbound,addr)
|
||||
return vmessLink(user,<VMess>inbound, addr, tlsClient)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -40,7 +40,6 @@ export namespace LinkUtil {
|
||||
const userPass = inbound.users?.find(i => i.name == user)?.password
|
||||
const password = [userPass]
|
||||
if (inbound.method.startsWith('2022')) password.push(inbound.password)
|
||||
|
||||
const params = {
|
||||
tfo: inbound.tcp_fast_open? 1 : null,
|
||||
network: inbound.network?? null
|
||||
@@ -56,7 +55,7 @@ export namespace LinkUtil {
|
||||
return uri.toString()
|
||||
}
|
||||
|
||||
function hysteriaLink(user: string, inbound: Hysteria, addr: string): string {
|
||||
function hysteriaLink(user: string, inbound: Hysteria, addr: string, tlsClient: any): string {
|
||||
const auth = inbound.users.find(i => i.name == user)?.auth_str
|
||||
const params = {
|
||||
upmbps: inbound.up_mbps?? null,
|
||||
@@ -65,7 +64,8 @@ export namespace LinkUtil {
|
||||
peer: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
obfsParam: inbound.obfs?? null,
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||
insecure: tlsClient?.insecure ? 1 : null
|
||||
}
|
||||
const uri = new URL(`hysteria://${addr}:${inbound.listen_port}`)
|
||||
for (const [key, value] of Object.entries(params)){
|
||||
@@ -77,7 +77,7 @@ export namespace LinkUtil {
|
||||
return uri.toString()
|
||||
}
|
||||
|
||||
function hysteria2Link(user: string, inbound: Hysteria2, addr: string): string {
|
||||
function hysteria2Link(user: string, inbound: Hysteria2, addr: string, tlsClient: any): string {
|
||||
const password = inbound.users.find(i => i.name == user)?.password
|
||||
const params = {
|
||||
upmbps: inbound.up_mbps?? null,
|
||||
@@ -86,7 +86,8 @@ export namespace LinkUtil {
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
obfs: inbound.obfs?.type?? null,
|
||||
'obfs-password': inbound.obfs?.password?? null,
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0
|
||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||
insecure: tlsClient?.insecure ? 1 : null
|
||||
}
|
||||
const uri = new URL(`hysteria2://${password}@${addr}:${inbound.listen_port}`)
|
||||
for (const [key, value] of Object.entries(params)){
|
||||
@@ -98,13 +99,14 @@ export namespace LinkUtil {
|
||||
return uri.toString()
|
||||
}
|
||||
|
||||
function naiveLink(user: string, inbound: Naive, addr: string): string {
|
||||
function naiveLink(user: string, inbound: Naive, addr: string, tlsClient: any): string {
|
||||
const password = inbound.users.find(i => i.username == user)?.password
|
||||
const params = {
|
||||
padding: 1,
|
||||
peer: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
tfo: inbound.tcp_fast_open? 1 : 0
|
||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null
|
||||
}
|
||||
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + addr + ":" + inbound.listen_port)}`
|
||||
const paramsArray = []
|
||||
@@ -116,12 +118,14 @@ export namespace LinkUtil {
|
||||
return uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag
|
||||
}
|
||||
|
||||
function tuicLink(user: string, inbound: TUIC, addr: string): string {
|
||||
function tuicLink(user: string, inbound: TUIC, addr: string, tlsClient: any): string {
|
||||
const u = inbound.users.find(i => i.name == user)
|
||||
const params = {
|
||||
sni: inbound.tls.server_name?? null,
|
||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||
congestion_control: inbound.congestion_control?? null
|
||||
congestion_control: inbound.congestion_control?? null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
disable_sni: tlsClient?.disable_sni ? 1 : null
|
||||
}
|
||||
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${addr}:${inbound.listen_port}`)
|
||||
for (const [key, value] of Object.entries(params)){
|
||||
@@ -166,7 +170,7 @@ export namespace LinkUtil {
|
||||
return params
|
||||
}
|
||||
|
||||
function vlessLink(user: string, inbound: VLESS, addr: string): string {
|
||||
function vlessLink(user: string, inbound: VLESS, addr: string, tlsClient: any): string {
|
||||
const u = inbound.users.find(i => i.name == user)
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -174,10 +178,14 @@ export namespace LinkUtil {
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? 'tls' : null,
|
||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
flow: inbound.tls?.enabled ? u?.flow?? null : null
|
||||
flow: inbound.tls?.enabled ? u?.flow?? null : null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||
pbk: tlsClient?.reality?.public_key?? null,
|
||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[0] : null) : null
|
||||
}
|
||||
const uri = new URL(`vless://${u?.uuid}@${addr}:${inbound.listen_port}`)
|
||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||
@@ -189,7 +197,7 @@ export namespace LinkUtil {
|
||||
return uri.toString()
|
||||
}
|
||||
|
||||
function trojanLink(user: string, inbound: Trojan, addr: string): string {
|
||||
function trojanLink(user: string, inbound: Trojan, addr: string, tlsClient: any): string {
|
||||
const u = inbound.users.find(i => i.name == user)
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -197,9 +205,13 @@ export namespace LinkUtil {
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? 'tls' : null,
|
||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||
pbk: tlsClient?.reality?.public_key?? null,
|
||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[0] : null) : null
|
||||
}
|
||||
const uri = new URL(`trojan://${u?.password}@${addr}:${inbound.listen_port}`)
|
||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||
@@ -211,7 +223,7 @@ export namespace LinkUtil {
|
||||
return uri.toString()
|
||||
}
|
||||
|
||||
function vmessLink(user: string, inbound: VMess, addr: string): string {
|
||||
function vmessLink(user: string, inbound: VMess, addr: string, tlsClient: any): string {
|
||||
const u = inbound.users.find(i => i.name == user)
|
||||
const transport = <Transport>inbound.transport
|
||||
|
||||
@@ -224,13 +236,14 @@ export namespace LinkUtil {
|
||||
aid: u?.alterId,
|
||||
host: tParams.host?? undefined,
|
||||
id: u?.uuid,
|
||||
net: transport?.type == undefined || transport?.type == 'http' ? 'tcp' : transport.type,
|
||||
net: transport?.type == undefined || transport?.type == 'http' ? 'tcp' : transport.type,
|
||||
type: transport?.type == 'http' ? 'http' : undefined,
|
||||
path: tParams.path?? undefined,
|
||||
port: inbound.listen_port,
|
||||
ps: inbound.tag,
|
||||
sni: inbound.tls.server_name?? undefined,
|
||||
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none'
|
||||
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
|
||||
allowInsecure: tlsClient?.insecure ? 1 : undefined
|
||||
}
|
||||
return 'vmess://' + utf8ToBase64(JSON.stringify(params, null, 2))
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ const routes = [
|
||||
name: 'pages.rules',
|
||||
component: () => import('@/views/Rules.vue'),
|
||||
},
|
||||
{
|
||||
path: '/tls',
|
||||
name: 'pages.tls',
|
||||
component: () => import('@/views/Tls.vue'),
|
||||
},
|
||||
{
|
||||
path: '/basics',
|
||||
name: 'pages.basics',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { FindDiff } from '@/plugins/utils'
|
||||
import HttpUtils from '@/plugins/httputil'
|
||||
import { defineStore } from 'pinia'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const Data = defineStore('Data', {
|
||||
state: () => ({
|
||||
@@ -9,9 +8,10 @@ const Data = defineStore('Data', {
|
||||
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
||||
subURI: "",
|
||||
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
||||
oldData: <{config: any, clients: any[]}>{},
|
||||
oldData: <{config: any, clients: any[], tlsConfigs: any[]}>{},
|
||||
config: {},
|
||||
clients: [],
|
||||
tlsConfigs: [],
|
||||
}),
|
||||
actions: {
|
||||
async loadData() {
|
||||
@@ -21,20 +21,23 @@ const Data = defineStore('Data', {
|
||||
|
||||
// Set new data
|
||||
const data = JSON.parse(msg.obj)
|
||||
if (data.subURI) this.subURI = data.subURI
|
||||
if (data.config) this.config = data.config
|
||||
if (data.clients) this.clients = data.clients
|
||||
if (data.subURI) this.subURI = data.subURI
|
||||
if (data.tls) this.tlsConfigs = data.tls
|
||||
this.onlines = data.onlines
|
||||
|
||||
// To avoid ref copy
|
||||
if (data.config) this.oldData.config = { ...JSON.parse(msg.obj).config }
|
||||
if (data.clients) this.oldData.clients = [ ...JSON.parse(msg.obj).clients ]
|
||||
if (data.tls) this.oldData.tlsConfigs = [ ...JSON.parse(msg.obj).tls ]
|
||||
}
|
||||
},
|
||||
async pushData() {
|
||||
const diff = {
|
||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
|
||||
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
|
||||
tls: JSON.stringify(FindDiff.Clients(this.tlsConfigs,this.oldData.tlsConfigs)),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
@@ -45,6 +48,7 @@ const Data = defineStore('Data', {
|
||||
const diff = {
|
||||
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
||||
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
|
||||
tls: JSON.stringify(FindDiff.Clients(this.tlsConfigs,this.oldData.tlsConfigs)),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
@@ -69,6 +73,15 @@ const Data = defineStore('Data', {
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delTls(id: number) {
|
||||
const diff = {
|
||||
tls:JSON.stringify([{key: "tls", action: "del", index: id, obj: null}]),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Dial } from "./dial"
|
||||
|
||||
export interface iTls {
|
||||
enabled?: boolean
|
||||
server_name?: string
|
||||
@@ -9,6 +11,50 @@ export interface iTls {
|
||||
certificate_path?: string
|
||||
key?: string[]
|
||||
key_path?: string
|
||||
acme?: acme
|
||||
ech?: ech
|
||||
reality?: reality
|
||||
}
|
||||
|
||||
export interface acme {
|
||||
domain: string[]
|
||||
data_directory?: string
|
||||
default_server_name?: string
|
||||
email?: string
|
||||
provider?: string
|
||||
disable_http_challenge?: boolean
|
||||
disable_tls_alpn_challenge?: boolean
|
||||
alternative_http_port?: number
|
||||
alternative_tls_port?: number
|
||||
external_account?: {
|
||||
key_id: string
|
||||
mac_key: string
|
||||
}
|
||||
dns01_challenge?: {
|
||||
provider: string
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ech {
|
||||
enabled: boolean
|
||||
pq_signature_schemes_enabled?: boolean
|
||||
dynamic_record_sizing_disabled?: boolean
|
||||
key?: string[]
|
||||
key_path?: string
|
||||
}
|
||||
|
||||
interface realityHanshake extends Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
}
|
||||
|
||||
export interface reality {
|
||||
enabled: boolean
|
||||
handshake: realityHanshake
|
||||
private_key: string
|
||||
short_id: string[]
|
||||
max_time_difference?: string
|
||||
}
|
||||
|
||||
export const defaultInTls: iTls = {
|
||||
|
||||
@@ -261,7 +261,8 @@ const updateLinks = (c:Client):string => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(i => c.inbounds.split(',').includes(i.tag))
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const uri = LinkUtil.linkGenerator(c.name,i)
|
||||
const tlsConfig = <any>Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
|
||||
const uri = LinkUtil.linkGenerator(c.name,i,tlsConfig?.client)
|
||||
if (uri.length>0){
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:data="modal.data"
|
||||
:inTags="inTags"
|
||||
:outTags="outTags"
|
||||
:tlsConfigs="tlsConfigs"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
@@ -119,6 +120,10 @@ const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> appConfig.value.inbounds
|
||||
})
|
||||
|
||||
const tlsConfigs = computed((): any[] => {
|
||||
return <any[]> Data().tlsConfigs
|
||||
})
|
||||
|
||||
const inTags = computed((): string[] => {
|
||||
return inbounds.value?.map(i => i.tag)
|
||||
})
|
||||
@@ -157,7 +162,7 @@ const showModal = (id: number) => {
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Inbound, stats: boolean) => {
|
||||
const saveModal = (data:Inbound, stats: boolean, tls_id: number) => {
|
||||
// Check duplicate tag
|
||||
const oldTag = modal.value.id != -1 ? inbounds.value[modal.value.id].tag : null
|
||||
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
|
||||
@@ -165,16 +170,30 @@ const saveModal = (data:Inbound, stats: boolean) => {
|
||||
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
|
||||
return
|
||||
}
|
||||
|
||||
// New or Edit
|
||||
if (modal.value.id == -1) {
|
||||
inbounds.value.push(data)
|
||||
if (stats && data.tag.length>0) {
|
||||
v2rayStats.value.inbounds.push(data.tag)
|
||||
}
|
||||
// Update tls preset
|
||||
if (tls_id>0) {
|
||||
tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
|
||||
}
|
||||
} else {
|
||||
const oldTag = inbounds.value[modal.value.id].tag
|
||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
||||
|
||||
// Update tls preset
|
||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(oldTag))
|
||||
if (oldTlsConfigIndex != -1){
|
||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
|
||||
}
|
||||
if (tls_id>0) {
|
||||
tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
|
||||
}
|
||||
|
||||
if (oldTag != data.tag) {
|
||||
v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
|
||||
changeClientInboundsTag(oldTag,data.tag)
|
||||
@@ -206,7 +225,8 @@ const updateLinks = (i: InboundWithUser) => {
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.split(',').includes(inb.tag))
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const uri = LinkUtil.linkGenerator(client.name,i)
|
||||
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? null
|
||||
const uri = LinkUtil.linkGenerator(client.name,i, tlsClient)
|
||||
if (uri.length>0){
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
}
|
||||
@@ -224,7 +244,7 @@ const delInbound = (index: number) => {
|
||||
inbounds.value.splice(index,1)
|
||||
const tag = inb.tag
|
||||
|
||||
if (Object.hasOwn(inb,'users')){
|
||||
if (Object.hasOwn(inb,'users')) {
|
||||
const inbU = <InboundWithUser>inb
|
||||
if (inbU.users && inbU.users.length>0){
|
||||
inbU.users.forEach((u:any) => {
|
||||
@@ -237,6 +257,13 @@ const delInbound = (index: number) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete binded tls if exists
|
||||
if (Object.hasOwn(inb,'tls')) {
|
||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(inb.tag))
|
||||
if (oldTlsConfigIndex != -1){
|
||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != inb.tag)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = inbounds.value.filter(i => i.tag == inb.tag).length
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<TlsVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:index="modal.index"
|
||||
:data="modal.data"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>tlsConfigs" :key="item.id">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.id + '. ' + item.name">
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
{{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds?.length>0">
|
||||
<span v-for="i in item.inbounds">{{ i }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds?.length }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>ACME</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ $t(item.server?.acme == undefined ? 'no' : 'yes') }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>ECH</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ $t(item.server?.ech == undefined ? 'no' : 'yes') }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>Reality</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ $t(item.server?.reality == undefined ? 'no' : 'yes') }}
|
||||
</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 v-if="item.inbounds?.length == 0" icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
<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="delTls(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-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TlsVue from '@/layouts/modals/Tls.vue'
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Config } from '@/types/config';
|
||||
import { Inbound } from '@/types/inbounds';
|
||||
import { Client } from '@/types/clients';
|
||||
import { Link, LinkUtil } from '@/plugins/link';
|
||||
|
||||
const tlsConfigs = computed((): any[] => {
|
||||
return Data().tlsConfigs
|
||||
})
|
||||
|
||||
const inbounds = computed((): any[] => {
|
||||
return <any[]>(<Config>Data().config)?.inbounds
|
||||
})
|
||||
|
||||
const clients = computed((): any[] => {
|
||||
return <Client[]>Data().clients
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
data: "",
|
||||
})
|
||||
|
||||
const delOverlay = ref(new Array<boolean>(tlsConfigs.value.length).fill(false))
|
||||
|
||||
const showModal = (index: number) => {
|
||||
modal.value.index = index
|
||||
modal.value.data = index == -1 ? '{}' : JSON.stringify(tlsConfigs.value[index])
|
||||
modal.value.visible = true
|
||||
}
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:any) => {
|
||||
// New or Edit
|
||||
if (modal.value.index == -1) {
|
||||
tlsConfigs.value.push(data)
|
||||
} else {
|
||||
tlsConfigs.value[modal.value.index] = data
|
||||
inbounds?.value.filter(i => tlsConfigs.value[modal.value.index].inbounds.includes(i.tag)).forEach(i =>{
|
||||
if (i.tls != undefined) i.tls = data.server
|
||||
updateLinks(i,data.client)
|
||||
})
|
||||
}
|
||||
modal.value.visible = false
|
||||
}
|
||||
|
||||
const delTls = (index: number) => {
|
||||
if (index < Data().oldData.tlsConfigs.length){
|
||||
Data().delTls(tlsConfigs.value[index].id)
|
||||
}
|
||||
tlsConfigs.value.splice(index,1)
|
||||
delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const updateLinks = (i:any,tlsClient:any) => {
|
||||
if(i.users && i.users.length>0){
|
||||
i.users.forEach((u:any) => {
|
||||
const client = clients.value.find(c => u.username? c.name == u.username : c.name == u.name)
|
||||
if (client){
|
||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.split(',').includes(inb.tag))
|
||||
const newLinks = <Link[]>[]
|
||||
clientInbounds.forEach(i =>{
|
||||
const uri = LinkUtil.linkGenerator(client.name,i,tlsClient)
|
||||
if (uri.length>0){
|
||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||
}
|
||||
})
|
||||
let links = client.links && client.links.length>0? <Link[]>JSON.parse(client.links) : <Link[]>[]
|
||||
links = [...newLinks, ...links.filter(l => l.type != 'local')]
|
||||
|
||||
client.links = JSON.stringify(links)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user