mirror of
https://github.com/fatedier/frp.git
synced 2026-03-19 00:09:14 +08:00
web/frpc: redesign frpc dashboard with sidebar nav, proxy/visitor list and detail views
This commit is contained in:
40
web/frpc/src/components/proxy-form/ProxyAuthSection.vue
Normal file
40
web/frpc/src/components/proxy-form/ProxyAuthSection.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<ConfigSection title="Authentication" :readonly="readonly">
|
||||
<template v-if="['http', 'tcpmux'].includes(form.type)">
|
||||
<div class="field-row three-col">
|
||||
<ConfigField label="HTTP User" type="text" v-model="form.httpUser" :readonly="readonly" />
|
||||
<ConfigField label="HTTP Password" type="password" v-model="form.httpPassword" :readonly="readonly" />
|
||||
<ConfigField label="Route By HTTP User" type="text" v-model="form.routeByHTTPUser" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="['stcp', 'sudp', 'xtcp'].includes(form.type)">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Secret Key" type="password" v-model="form.secretKey" prop="secretKey" :readonly="readonly" />
|
||||
<ConfigField label="Allow Users" type="tags" v-model="form.allowUsers" placeholder="username" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
</ConfigSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
149
web/frpc/src/components/proxy-form/ProxyBackendSection.vue
Normal file
149
web/frpc/src/components/proxy-form/ProxyBackendSection.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<!-- Backend Mode -->
|
||||
<template v-if="!readonly">
|
||||
<el-form-item label="Backend Mode">
|
||||
<el-radio-group v-model="backendMode">
|
||||
<el-radio value="direct">Direct</el-radio>
|
||||
<el-radio value="plugin">Plugin</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Direct mode -->
|
||||
<template v-if="backendMode === 'direct'">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Local IP" type="text" v-model="form.localIP" placeholder="127.0.0.1" :readonly="readonly" />
|
||||
<ConfigField label="Local Port" type="number" v-model="form.localPort" :min="0" :max="65535" prop="localPort" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Plugin mode -->
|
||||
<template v-else>
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Plugin Type" type="select" v-model="form.pluginType"
|
||||
:options="PLUGIN_LIST.map((p) => ({ label: p, value: p }))" :readonly="readonly" />
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<template v-if="['http2https', 'https2http', 'https2https', 'http2http', 'tls2raw'].includes(form.pluginType)">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Local Address" type="text" v-model="form.pluginConfig.localAddr" placeholder="127.0.0.1:8080" :readonly="readonly" />
|
||||
<ConfigField v-if="['http2https', 'https2http', 'https2https', 'http2http'].includes(form.pluginType)"
|
||||
label="Host Header Rewrite" type="text" v-model="form.pluginConfig.hostHeaderRewrite" :readonly="readonly" />
|
||||
<div v-else></div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="['http2https', 'https2http', 'https2https', 'http2http'].includes(form.pluginType)">
|
||||
<ConfigField label="Request Headers" type="kv" v-model="pluginRequestHeaders"
|
||||
key-placeholder="Header" value-placeholder="Value" :readonly="readonly" />
|
||||
</template>
|
||||
<template v-if="['https2http', 'https2https', 'tls2raw'].includes(form.pluginType)">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Certificate Path" type="text" v-model="form.pluginConfig.crtPath" placeholder="/path/to/cert.pem" :readonly="readonly" />
|
||||
<ConfigField label="Key Path" type="text" v-model="form.pluginConfig.keyPath" placeholder="/path/to/key.pem" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="['https2http', 'https2https'].includes(form.pluginType)">
|
||||
<ConfigField label="Enable HTTP/2" type="switch" v-model="form.pluginConfig.enableHTTP2" :readonly="readonly" />
|
||||
</template>
|
||||
<template v-if="form.pluginType === 'http_proxy'">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="HTTP User" type="text" v-model="form.pluginConfig.httpUser" :readonly="readonly" />
|
||||
<ConfigField label="HTTP Password" type="password" v-model="form.pluginConfig.httpPassword" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="form.pluginType === 'socks5'">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Username" type="text" v-model="form.pluginConfig.username" :readonly="readonly" />
|
||||
<ConfigField label="Password" type="password" v-model="form.pluginConfig.password" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="form.pluginType === 'static_file'">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Local Path" type="text" v-model="form.pluginConfig.localPath" placeholder="/path/to/files" :readonly="readonly" />
|
||||
<ConfigField label="Strip Prefix" type="text" v-model="form.pluginConfig.stripPrefix" :readonly="readonly" />
|
||||
</div>
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="HTTP User" type="text" v-model="form.pluginConfig.httpUser" :readonly="readonly" />
|
||||
<ConfigField label="HTTP Password" type="password" v-model="form.pluginConfig.httpPassword" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="form.pluginType === 'unix_domain_socket'">
|
||||
<ConfigField label="Unix Socket Path" type="text" v-model="form.pluginConfig.unixPath" placeholder="/tmp/socket.sock" :readonly="readonly" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick, onMounted } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const PLUGIN_LIST = [
|
||||
'http2https', 'http_proxy', 'https2http', 'https2https', 'http2http',
|
||||
'socks5', 'static_file', 'unix_domain_socket', 'tls2raw', 'virtual_net',
|
||||
]
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
const backendMode = ref<'direct' | 'plugin'>(form.value.pluginType ? 'plugin' : 'direct')
|
||||
const isHydrating = ref(false)
|
||||
|
||||
const pluginRequestHeaders = computed({
|
||||
get() {
|
||||
const set = form.value.pluginConfig?.requestHeaders?.set
|
||||
if (!set || typeof set !== 'object') return []
|
||||
return Object.entries(set).map(([key, value]) => ({ key, value: String(value) }))
|
||||
},
|
||||
set(val: Array<{ key: string; value: string }>) {
|
||||
if (!form.value.pluginConfig) form.value.pluginConfig = {}
|
||||
if (val.length === 0) {
|
||||
delete form.value.pluginConfig.requestHeaders
|
||||
} else {
|
||||
form.value.pluginConfig.requestHeaders = {
|
||||
set: Object.fromEntries(val.map((e) => [e.key, e.value])),
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
watch(() => form.value.pluginType, (newType, oldType) => {
|
||||
if (isHydrating.value) return
|
||||
if (!oldType || !newType || newType === oldType) return
|
||||
if (form.value.pluginConfig && Object.keys(form.value.pluginConfig).length > 0) {
|
||||
form.value.pluginConfig = {}
|
||||
}
|
||||
})
|
||||
|
||||
watch(backendMode, (mode) => {
|
||||
if (mode === 'direct') {
|
||||
form.value.pluginType = ''
|
||||
form.value.pluginConfig = {}
|
||||
} else if (!form.value.pluginType) {
|
||||
form.value.pluginType = 'http2https'
|
||||
}
|
||||
})
|
||||
|
||||
const hydrate = () => {
|
||||
isHydrating.value = true
|
||||
backendMode.value = form.value.pluginType ? 'plugin' : 'direct'
|
||||
nextTick(() => { isHydrating.value = false })
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => { hydrate() })
|
||||
onMounted(() => { hydrate() })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
51
web/frpc/src/components/proxy-form/ProxyBaseSection.vue
Normal file
51
web/frpc/src/components/proxy-form/ProxyBaseSection.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<!-- Name / Type / Enabled -->
|
||||
<div v-if="!readonly" class="field-row three-col">
|
||||
<el-form-item label="Name" prop="name" class="field-grow">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
:disabled="editing || readonly"
|
||||
placeholder="my-proxy"
|
||||
/>
|
||||
</el-form-item>
|
||||
<ConfigField
|
||||
label="Type"
|
||||
type="select"
|
||||
v-model="form.type"
|
||||
:disabled="editing"
|
||||
:options="PROXY_TYPES.map((t) => ({ label: t.toUpperCase(), value: t }))"
|
||||
prop="type"
|
||||
/>
|
||||
<el-form-item label="Enabled" class="switch-field">
|
||||
<el-switch v-model="form.enabled" size="small" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-else class="field-row three-col">
|
||||
<ConfigField label="Name" type="text" :model-value="form.name" readonly class="field-grow" />
|
||||
<ConfigField label="Type" type="text" :model-value="form.type.toUpperCase()" readonly />
|
||||
<ConfigField label="Enabled" type="switch" :model-value="form.enabled" readonly />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { PROXY_TYPES, type ProxyFormData } from '../../types'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
editing?: boolean
|
||||
}>(), { readonly: false, editing: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
50
web/frpc/src/components/proxy-form/ProxyFormLayout.vue
Normal file
50
web/frpc/src/components/proxy-form/ProxyFormLayout.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="proxy-form-layout">
|
||||
<ConfigSection :readonly="readonly">
|
||||
<ProxyBaseSection v-model="form" :readonly="readonly" :editing="editing" />
|
||||
<ProxyRemoteSection
|
||||
v-if="['tcp', 'udp', 'http', 'https', 'tcpmux'].includes(form.type)"
|
||||
v-model="form" :readonly="readonly" />
|
||||
<ProxyBackendSection v-model="form" :readonly="readonly" />
|
||||
</ConfigSection>
|
||||
|
||||
<ProxyAuthSection
|
||||
v-if="['http', 'tcpmux', 'stcp', 'sudp', 'xtcp'].includes(form.type)"
|
||||
v-model="form" :readonly="readonly" />
|
||||
<ProxyHttpSection v-if="form.type === 'http'" v-model="form" :readonly="readonly" />
|
||||
<ProxyTransportSection v-model="form" :readonly="readonly" />
|
||||
<ProxyHealthSection v-model="form" :readonly="readonly" />
|
||||
<ProxyLoadBalanceSection v-model="form" :readonly="readonly" />
|
||||
<ProxyNatSection v-if="form.type === 'xtcp'" v-model="form" :readonly="readonly" />
|
||||
<ProxyMetadataSection v-model="form" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ProxyBaseSection from './ProxyBaseSection.vue'
|
||||
import ProxyRemoteSection from './ProxyRemoteSection.vue'
|
||||
import ProxyBackendSection from './ProxyBackendSection.vue'
|
||||
import ProxyAuthSection from './ProxyAuthSection.vue'
|
||||
import ProxyHttpSection from './ProxyHttpSection.vue'
|
||||
import ProxyTransportSection from './ProxyTransportSection.vue'
|
||||
import ProxyHealthSection from './ProxyHealthSection.vue'
|
||||
import ProxyLoadBalanceSection from './ProxyLoadBalanceSection.vue'
|
||||
import ProxyNatSection from './ProxyNatSection.vue'
|
||||
import ProxyMetadataSection from './ProxyMetadataSection.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
editing?: boolean
|
||||
}>(), { readonly: false, editing: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
52
web/frpc/src/components/proxy-form/ProxyHealthSection.vue
Normal file
52
web/frpc/src/components/proxy-form/ProxyHealthSection.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<ConfigSection title="Health Check" collapsible :readonly="readonly" :has-value="!!form.healthCheckType">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Type" type="select" v-model="form.healthCheckType"
|
||||
:options="[{ label: 'Disabled', value: '' }, { label: 'TCP', value: 'tcp' }, { label: 'HTTP', value: 'http' }]" :readonly="readonly" />
|
||||
<div></div>
|
||||
</div>
|
||||
<template v-if="form.healthCheckType">
|
||||
<div class="field-row three-col">
|
||||
<ConfigField label="Timeout (s)" type="number" v-model="form.healthCheckTimeoutSeconds" :min="1" :readonly="readonly" />
|
||||
<ConfigField label="Max Failed" type="number" v-model="form.healthCheckMaxFailed" :min="1" :readonly="readonly" />
|
||||
<ConfigField label="Interval (s)" type="number" v-model="form.healthCheckIntervalSeconds" :min="1" :readonly="readonly" />
|
||||
</div>
|
||||
<template v-if="form.healthCheckType === 'http'">
|
||||
<ConfigField label="Path" type="text" v-model="form.healthCheckPath" prop="healthCheckPath" placeholder="/health" :readonly="readonly" />
|
||||
<ConfigField label="HTTP Headers" type="kv" v-model="healthCheckHeaders" key-placeholder="Header" value-placeholder="Value" :readonly="readonly" />
|
||||
</template>
|
||||
</template>
|
||||
</ConfigSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
const healthCheckHeaders = computed({
|
||||
get() {
|
||||
return form.value.healthCheckHTTPHeaders.map((h) => ({ key: h.name, value: h.value }))
|
||||
},
|
||||
set(val: Array<{ key: string; value: string }>) {
|
||||
form.value.healthCheckHTTPHeaders = val.map((h) => ({ name: h.key, value: h.value }))
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
32
web/frpc/src/components/proxy-form/ProxyHttpSection.vue
Normal file
32
web/frpc/src/components/proxy-form/ProxyHttpSection.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<ConfigSection title="HTTP Options" collapsible :readonly="readonly"
|
||||
:has-value="form.locations.length > 0 || !!form.hostHeaderRewrite || form.requestHeaders.length > 0 || form.responseHeaders.length > 0">
|
||||
<ConfigField label="Locations" type="tags" v-model="form.locations" placeholder="/path" :readonly="readonly" />
|
||||
<ConfigField label="Host Header Rewrite" type="text" v-model="form.hostHeaderRewrite" :readonly="readonly" />
|
||||
<ConfigField label="Request Headers" type="kv" v-model="form.requestHeaders" key-placeholder="Header" value-placeholder="Value" :readonly="readonly" />
|
||||
<ConfigField label="Response Headers" type="kv" v-model="form.responseHeaders" key-placeholder="Header" value-placeholder="Value" :readonly="readonly" />
|
||||
</ConfigSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<ConfigSection title="Load Balancer" collapsible :readonly="readonly" :has-value="!!form.loadBalancerGroup">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Group" type="text" v-model="form.loadBalancerGroup" placeholder="Group name" :readonly="readonly" />
|
||||
<ConfigField label="Group Key" type="text" v-model="form.loadBalancerGroupKey" :readonly="readonly" />
|
||||
</div>
|
||||
</ConfigSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
29
web/frpc/src/components/proxy-form/ProxyMetadataSection.vue
Normal file
29
web/frpc/src/components/proxy-form/ProxyMetadataSection.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<ConfigSection title="Metadata" collapsible :readonly="readonly" :has-value="form.metadatas.length > 0 || form.annotations.length > 0">
|
||||
<ConfigField label="Metadatas" type="kv" v-model="form.metadatas" :readonly="readonly" />
|
||||
<ConfigField label="Annotations" type="kv" v-model="form.annotations" :readonly="readonly" />
|
||||
</ConfigSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
29
web/frpc/src/components/proxy-form/ProxyNatSection.vue
Normal file
29
web/frpc/src/components/proxy-form/ProxyNatSection.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<ConfigSection title="NAT Traversal" collapsible :readonly="readonly" :has-value="form.natTraversalDisableAssistedAddrs">
|
||||
<ConfigField label="Disable Assisted Addresses" type="switch" v-model="form.natTraversalDisableAssistedAddrs"
|
||||
tip="Only use STUN-discovered public addresses" :readonly="readonly" />
|
||||
</ConfigSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
41
web/frpc/src/components/proxy-form/ProxyRemoteSection.vue
Normal file
41
web/frpc/src/components/proxy-form/ProxyRemoteSection.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<template v-if="['tcp', 'udp'].includes(form.type)">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Remote Port" type="number" v-model="form.remotePort"
|
||||
:min="0" :max="65535" prop="remotePort" tip="Use 0 for random port assignment" :readonly="readonly" />
|
||||
<div></div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="['http', 'https', 'tcpmux'].includes(form.type)">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Custom Domains" type="tags" v-model="form.customDomains"
|
||||
prop="customDomains" placeholder="example.com" :readonly="readonly" />
|
||||
<ConfigField v-if="form.type !== 'tcpmux'" label="Subdomain" type="text"
|
||||
v-model="form.subdomain" placeholder="test" :readonly="readonly" />
|
||||
<ConfigField v-if="form.type === 'tcpmux'" label="Multiplexer" type="select"
|
||||
v-model="form.multiplexer" :options="[{ label: 'HTTP CONNECT', value: 'httpconnect' }]" :readonly="readonly" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
39
web/frpc/src/components/proxy-form/ProxyTransportSection.vue
Normal file
39
web/frpc/src/components/proxy-form/ProxyTransportSection.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<ConfigSection title="Transport" collapsible :readonly="readonly"
|
||||
:has-value="form.useEncryption || form.useCompression || !!form.bandwidthLimit || (!!form.bandwidthLimitMode && form.bandwidthLimitMode !== 'client') || !!form.proxyProtocolVersion">
|
||||
<div class="field-row two-col">
|
||||
<ConfigField label="Use Encryption" type="switch" v-model="form.useEncryption" :readonly="readonly" />
|
||||
<ConfigField label="Use Compression" type="switch" v-model="form.useCompression" :readonly="readonly" />
|
||||
</div>
|
||||
<div class="field-row three-col">
|
||||
<ConfigField label="Bandwidth Limit" type="text" v-model="form.bandwidthLimit" placeholder="1MB" tip="e.g., 1MB, 500KB" :readonly="readonly" />
|
||||
<ConfigField label="Bandwidth Limit Mode" type="select" v-model="form.bandwidthLimitMode"
|
||||
:options="[{ label: 'Client', value: 'client' }, { label: 'Server', value: 'server' }]" :readonly="readonly" />
|
||||
<ConfigField label="Proxy Protocol Version" type="select" v-model="form.proxyProtocolVersion"
|
||||
:options="[{ label: 'None', value: '' }, { label: 'v1', value: 'v1' }, { label: 'v2', value: 'v2' }]" :readonly="readonly" />
|
||||
</div>
|
||||
</ConfigSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProxyFormData } from '../../types'
|
||||
import ConfigSection from '../ConfigSection.vue'
|
||||
import ConfigField from '../ConfigField.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: ProxyFormData
|
||||
readonly?: boolean
|
||||
}>(), { readonly: false })
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: ProxyFormData] }>()
|
||||
|
||||
const form = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/assets/css/form-layout';
|
||||
</style>
|
||||
Reference in New Issue
Block a user