Support IPv4 HTTP public gateway entries

This commit is contained in:
Codex committed 2026-10-26 14:53:52 +08:00
1 parent 6f01d31f97
commit 6d5973bea3
11 files changed
+146 -32

No files matched your search

+3 -3
View File
@@ -961,13 +961,13 @@
"publicEntryTitle": "Public entry",
"publicEntryDesc": "Scheme, host, and port define the public Base URL used by the OIDC issuer, Feishu identity, and tunnel callbacks.",
"scheme": "Scheme",
"host": "Host",
"host": "Domain / IPv4 address",
"port": "Port",
"publicBase": "Public Base URL",
"publicBaseHint": "OIDC issuer is Base URL + /id; tunnel callbacks inherit the same base.",
"publicBaseHint": "Generated from scheme, address and port; no input needed. Feishu / OIDC callbacks use this address.",
"httpRisk": "HTTP is plaintext. Prefer HTTPS in production; HTTP is for intranet or temporary ICP workarounds.",
"saveEntry": "Save public entry",
"hostRequired": "Host is required",
"hostRequired": "Enter a domain or IPv4 address",
"certListTitle": "Certificates",
"addCert": "Add certificate",
"replaceCert": "Replace",
+3 -3
View File
@@ -961,13 +961,13 @@
"publicEntryTitle": "公网入口",
"publicEntryDesc": "协议、域名与端口决定 OIDC 发行者、飞书身份与专线对外地址;保存后各页只读继承,不再写死 https。",
"scheme": "协议",
"host": "域名",
"host": "域名 / IPv4 地址",
"port": "端口",
"publicBase": "对外 Base URL",
"publicBaseHint": "飞书 / OIDC 发行者为 Base URL + /id;专线回调亦由此派生。",
"publicBaseHint": "自动由协议、地址和端口生成,无需填写;飞书 / OIDC 回调引用此地址。",
"httpRisk": "HTTP 为明文传输,仅建议内网或未备案临时绕行场景;生产环境优先 HTTPS。",
"saveEntry": "保存公网入口",
"hostRequired": "请填写域名",
"hostRequired": "请填写域名或 IPv4 地址",
"certListTitle": "证书",
"addCert": "添加证书",
"replaceCert": "更换",
@@ -13,8 +13,8 @@
<el-form label-position="top" @submit.prevent="savePublicEntry">
<el-form-item :label="$t('tls.scheme')">
<el-radio-group v-model="entry.scheme" class="scheme-group" @change="onSchemeChange">
<el-radio-button value="https">HTTPS</el-radio-button>
<el-radio-button value="http">HTTP</el-radio-button>
<el-radio-button value="https">HTTPS</el-radio-button>
</el-radio-group>
</el-form-item>
<el-alert
@@ -27,7 +27,7 @@
<el-row :gutter="12">
<el-col :xs="24" :sm="16">
<el-form-item :label="$t('tls.host')" required>
<el-input v-model.trim="entry.host" placeholder="vpn.example.com" maxlength="253" />
<el-input v-model.trim="entry.host" placeholder="vpn.example.com / 121.4.111.208" maxlength="253" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8">
@@ -37,7 +37,7 @@
</el-col>
</el-row>
<el-form-item :label="$t('tls.publicBase')">
<el-input :model-value="publicBase" readonly>
<el-input :model-value="publicBase" readonly class="derived-base">
<template #append>
<el-button @click="copyBase">{{ $t('common.copy') }}</el-button>
</template>
@@ -231,10 +231,10 @@ const certDialog = ref(false)
const certSubmitting = ref(false)
const entry = reactive({
scheme: 'https',
scheme: 'http',
host: '',
port: 443,
mode: 'autocert',
port: 80,
mode: 'nginx',
})
const certForm = reactive({
@@ -248,10 +248,7 @@ const certForm = reactive({
})
const publicBase = computed(() => {
const local = buildPublicBase(entry)
const remote = tlsStatus.value?.configured_public_base || tlsStatus.value?.public_base || ''
// Prefer live form preview while editing; fall back to server after load.
return local || remote
return buildPublicBase(entry)
})
const certRows = computed(() => {
@@ -273,6 +270,7 @@ const certRows = computed(() => {
function onSchemeChange() {
entry.port = defaultPort(entry.scheme)
if (entry.scheme === 'http') entry.mode = 'nginx'
}
function resetCertForm() {
@@ -372,11 +370,11 @@ async function loadTLS() {
const { data } = await api.get('/system/tls')
tlsStatus.value = data
entry.host = data.configured_domain || data.domain || data.host || ''
const scheme = data.configured_scheme || data.scheme || 'https'
const scheme = entry.host ? (data.configured_scheme || data.scheme || 'https') : 'http'
entry.scheme = scheme === 'http' ? 'http' : 'https'
const port = Number(data.configured_port || data.port)
entry.port = port > 0 ? port : defaultPort(entry.scheme)
entry.mode = data.configured_mode || data.mode || 'autocert'
entry.mode = entry.host ? (data.configured_mode || data.mode || 'autocert') : 'nginx'
} catch (e) { ElMessage.error(e.response?.data?.error || t('tls.loadFailed')) }
finally { tlsLoading.value = false }
}
@@ -499,6 +497,8 @@ onMounted(() => { if (!auth.mustChange) refreshAll() })
.settings-column :deep(.el-card__header) { background: #f9fbfe; color: #21344e; }
.card-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.tls-hint { color: var(--text-secondary); line-height: 1.6; font-size: 13px; margin-top: 6px; }
.derived-base :deep(.el-input__wrapper) { background: var(--el-fill-color-light); }
.derived-base :deep(.el-input__inner) { color: var(--el-text-color-secondary); cursor: default; }
.nginx-preview { max-height: 280px; overflow: auto; background: #172239; color: #e7eefc; border-radius: 8px; padding: 14px; font-size: 12px; line-height: 1.5; margin-top: 8px; }
.scheme-group { width: 100%; }
.scheme-group :deep(.el-radio-button) { flex: 1; }