9a5de83fee
CI / docker (push) Successful in 1m36s
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。 Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。 Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
2.8 KiB
Vue
105 lines
2.8 KiB
Vue
<template>
|
|
<div class="proxy-headers-fields">
|
|
<el-form-item :label="t('ipForward.ipForward')">
|
|
<el-radio-group v-model="ipMode">
|
|
<el-radio label="replace">{{ ipForwardModeLabel('replace', t) }}</el-radio>
|
|
<el-radio label="append">{{ ipForwardModeLabel('append', t) }}</el-radio>
|
|
</el-radio-group>
|
|
<div class="form-hint">{{ modeHint }}</div>
|
|
</el-form-item>
|
|
<el-form-item :label="t('ipForward.customHeaders')">
|
|
<div class="header-list">
|
|
<div v-for="(row, idx) in headerRows" :key="idx" class="header-row">
|
|
<el-input v-model="row.key" :placeholder="t('ipForward.headerName')" @input="emitHeaders" />
|
|
<el-input v-model="row.value" :placeholder="t('ipForward.headerValue')" @input="emitHeaders" />
|
|
<el-button link type="danger" @click="removeRow(idx)">{{ t('common.delete') }}</el-button>
|
|
</div>
|
|
<el-button size="small" @click="addRow">{{ t('ipForward.addHeader') }}</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { ipForwardModeHint, ipForwardModeLabel, normalizeIPForwardMode } from '@/utils/ipForward'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps({
|
|
ipForwardMode: { type: String, default: 'replace' },
|
|
customHeaders: { type: String, default: '' },
|
|
})
|
|
|
|
const emit = defineEmits(['update:ipForwardMode', 'update:customHeaders'])
|
|
|
|
const headerRows = ref([])
|
|
|
|
function parseHeaders(raw) {
|
|
if (!raw || raw === '{}') return []
|
|
try {
|
|
const obj = JSON.parse(raw)
|
|
return Object.entries(obj).map(([key, value]) => ({ key, value: String(value) }))
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
function serializeHeaders(rows) {
|
|
const obj = {}
|
|
for (const row of rows) {
|
|
const key = row.key?.trim()
|
|
if (!key) continue
|
|
obj[key] = row.value ?? ''
|
|
}
|
|
return Object.keys(obj).length ? JSON.stringify(obj) : ''
|
|
}
|
|
|
|
function syncFromProps() {
|
|
headerRows.value = parseHeaders(props.customHeaders)
|
|
}
|
|
|
|
watch(() => props.customHeaders, syncFromProps, { immediate: true })
|
|
|
|
const ipMode = computed({
|
|
get() {
|
|
return normalizeIPForwardMode(props.ipForwardMode)
|
|
},
|
|
set(v) {
|
|
emit('update:ipForwardMode', v)
|
|
},
|
|
})
|
|
|
|
const modeHint = computed(() => ipForwardModeHint(ipMode.value, t))
|
|
|
|
function emitHeaders() {
|
|
emit('update:customHeaders', serializeHeaders(headerRows.value))
|
|
}
|
|
|
|
function addRow() {
|
|
headerRows.value.push({ key: '', value: '' })
|
|
emitHeaders()
|
|
}
|
|
|
|
function removeRow(idx) {
|
|
headerRows.value.splice(idx, 1)
|
|
emitHeaders()
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.header-list {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-2);
|
|
}
|
|
.header-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr auto;
|
|
gap: var(--space-2);
|
|
align-items: center;
|
|
}
|
|
</style>
|