mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-02-02 19:11:54 +08:00
feat: 支持 ipv4/ipv6 双栈访问
This commit is contained in:
@@ -336,6 +336,7 @@ export const rustdeskConfigApi = {
|
||||
export interface WebConfig {
|
||||
http_port: number
|
||||
https_port: number
|
||||
bind_addresses: string[]
|
||||
bind_address: string
|
||||
https_enabled: boolean
|
||||
}
|
||||
@@ -344,6 +345,7 @@ export interface WebConfig {
|
||||
export interface WebConfigUpdate {
|
||||
http_port?: number
|
||||
https_port?: number
|
||||
bind_addresses?: string[]
|
||||
bind_address?: string
|
||||
https_enabled?: boolean
|
||||
}
|
||||
|
||||
@@ -480,10 +480,22 @@ export default {
|
||||
configureHttpPort: 'Configure HTTP server port',
|
||||
// Web server
|
||||
webServer: 'Access Address',
|
||||
webServerDesc: 'Configure HTTP/HTTPS ports and bind address. Restart required for changes to take effect.',
|
||||
webServerDesc: 'Configure HTTP/HTTPS ports and listening addresses. Restart required for changes to take effect.',
|
||||
httpsPort: 'HTTPS Port',
|
||||
bindAddress: 'Bind Address',
|
||||
bindAddressDesc: 'IP address the server listens on. 0.0.0.0 means all network interfaces.',
|
||||
bindMode: 'Listening Address',
|
||||
bindModeDesc: 'Choose which addresses the web server binds to.',
|
||||
bindModeAll: 'All addresses',
|
||||
bindModeLocal: 'Local only (127.0.0.1)',
|
||||
bindModeCustom: 'Custom address list',
|
||||
bindIpv6: 'Enable IPv6',
|
||||
bindAllDesc: 'Also listen on :: (all IPv6 interfaces).',
|
||||
bindLocalDesc: 'Also listen on ::1 (IPv6 loopback).',
|
||||
bindAddressList: 'Address List',
|
||||
bindAddressListDesc: 'One IP address per line (IPv4 or IPv6).',
|
||||
addBindAddress: 'Add address',
|
||||
bindAddressListEmpty: 'Add at least one IP address.',
|
||||
httpsEnabled: 'Enable HTTPS',
|
||||
httpsEnabledDesc: 'Enable HTTPS encrypted connection (self-signed certificate will be auto-generated)',
|
||||
restartRequired: 'Restart Required',
|
||||
|
||||
@@ -480,10 +480,22 @@ export default {
|
||||
configureHttpPort: '配置 HTTP 服务器端口',
|
||||
// Web server
|
||||
webServer: '访问地址',
|
||||
webServerDesc: '配置 HTTP/HTTPS 端口和绑定地址,修改后需要重启生效',
|
||||
webServerDesc: '配置 HTTP/HTTPS 端口和监听地址,修改后需要重启生效',
|
||||
httpsPort: 'HTTPS 端口',
|
||||
bindAddress: '绑定地址',
|
||||
bindAddressDesc: '服务器监听的 IP 地址,0.0.0.0 表示监听所有网络接口',
|
||||
bindMode: '监听地址',
|
||||
bindModeDesc: '选择 Web 服务监听哪些地址。',
|
||||
bindModeAll: '所有地址',
|
||||
bindModeLocal: '仅本地 (127.0.0.1)',
|
||||
bindModeCustom: '自定义地址列表',
|
||||
bindIpv6: '启用 IPv6',
|
||||
bindAllDesc: '同时监听 ::(所有 IPv6 地址)。',
|
||||
bindLocalDesc: '同时监听 ::1(IPv6 本地回环)。',
|
||||
bindAddressList: '地址列表',
|
||||
bindAddressListDesc: '每行一个 IP(IPv4 或 IPv6)。',
|
||||
addBindAddress: '添加地址',
|
||||
bindAddressListEmpty: '请至少填写一个 IP 地址。',
|
||||
httpsEnabled: '启用 HTTPS',
|
||||
httpsEnabledDesc: '启用 HTTPS 加密连接(将自动生成自签名证书)',
|
||||
restartRequired: '需要重启',
|
||||
|
||||
@@ -282,7 +282,9 @@ export interface WebConfig {
|
||||
http_port: number;
|
||||
/** HTTPS port */
|
||||
https_port: number;
|
||||
/** Bind address */
|
||||
/** Bind addresses (preferred) */
|
||||
bind_addresses: string[];
|
||||
/** Bind address (legacy) */
|
||||
bind_address: string;
|
||||
/** Enable HTTPS */
|
||||
https_enabled: boolean;
|
||||
@@ -625,6 +627,7 @@ export interface VideoConfigUpdate {
|
||||
export interface WebConfigUpdate {
|
||||
http_port?: number;
|
||||
https_port?: number;
|
||||
bind_addresses?: string[];
|
||||
bind_address?: string;
|
||||
https_enabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ import {
|
||||
Square,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
Trash2,
|
||||
ExternalLink,
|
||||
Copy,
|
||||
ScreenShare,
|
||||
@@ -196,11 +197,32 @@ const webServerConfig = ref<WebConfig>({
|
||||
http_port: 8080,
|
||||
https_port: 8443,
|
||||
bind_address: '0.0.0.0',
|
||||
bind_addresses: ['0.0.0.0'],
|
||||
https_enabled: false,
|
||||
})
|
||||
const webServerLoading = ref(false)
|
||||
const showRestartDialog = ref(false)
|
||||
const restarting = ref(false)
|
||||
type BindMode = 'all' | 'loopback' | 'custom'
|
||||
const bindMode = ref<BindMode>('all')
|
||||
const bindAllIpv6 = ref(false)
|
||||
const bindLocalIpv6 = ref(false)
|
||||
const bindAddressList = ref<string[]>([])
|
||||
const bindAddressError = computed(() => {
|
||||
if (bindMode.value !== 'custom') return ''
|
||||
return normalizeBindAddresses(bindAddressList.value).length
|
||||
? ''
|
||||
: t('settings.bindAddressListEmpty')
|
||||
})
|
||||
const effectiveBindAddresses = computed(() => {
|
||||
if (bindMode.value === 'all') {
|
||||
return bindAllIpv6.value ? ['0.0.0.0', '::'] : ['0.0.0.0']
|
||||
}
|
||||
if (bindMode.value === 'loopback') {
|
||||
return bindLocalIpv6.value ? ['127.0.0.1', '::1'] : ['127.0.0.1']
|
||||
}
|
||||
return normalizeBindAddresses(bindAddressList.value)
|
||||
})
|
||||
|
||||
// Config
|
||||
interface DeviceConfig {
|
||||
@@ -320,6 +342,12 @@ watch(() => config.value.msd_enabled, (enabled) => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(bindMode, (mode) => {
|
||||
if (mode === 'custom' && bindAddressList.value.length === 0) {
|
||||
bindAddressList.value = ['']
|
||||
}
|
||||
})
|
||||
|
||||
// ATX config state
|
||||
const atxConfig = ref({
|
||||
enabled: false,
|
||||
@@ -987,20 +1015,72 @@ function normalizeRustdeskServer(value: string, defaultPort: number): string | u
|
||||
return `${trimmed}:${defaultPort}`
|
||||
}
|
||||
|
||||
function normalizeBindAddresses(addresses: string[]): string[] {
|
||||
return addresses.map(addr => addr.trim()).filter(Boolean)
|
||||
}
|
||||
|
||||
function applyBindStateFromConfig(config: WebConfig) {
|
||||
const rawAddrs =
|
||||
config.bind_addresses && config.bind_addresses.length > 0
|
||||
? config.bind_addresses
|
||||
: config.bind_address
|
||||
? [config.bind_address]
|
||||
: []
|
||||
const addrs = normalizeBindAddresses(rawAddrs)
|
||||
const isAll = addrs.length > 0 && addrs.every(addr => addr === '0.0.0.0' || addr === '::') && addrs.includes('0.0.0.0')
|
||||
const isLoopback =
|
||||
addrs.length > 0 &&
|
||||
addrs.every(addr => addr === '127.0.0.1' || addr === '::1') &&
|
||||
addrs.includes('127.0.0.1')
|
||||
if (isAll) {
|
||||
bindMode.value = 'all'
|
||||
bindAllIpv6.value = addrs.includes('::')
|
||||
return
|
||||
}
|
||||
if (isLoopback) {
|
||||
bindMode.value = 'loopback'
|
||||
bindLocalIpv6.value = addrs.includes('::1')
|
||||
return
|
||||
}
|
||||
bindMode.value = 'custom'
|
||||
bindAddressList.value = addrs.length ? [...addrs] : ['']
|
||||
}
|
||||
|
||||
function addBindAddress() {
|
||||
bindAddressList.value.push('')
|
||||
}
|
||||
|
||||
function removeBindAddress(index: number) {
|
||||
bindAddressList.value.splice(index, 1)
|
||||
if (bindAddressList.value.length === 0) {
|
||||
bindAddressList.value.push('')
|
||||
}
|
||||
}
|
||||
|
||||
// Web server config functions
|
||||
async function loadWebServerConfig() {
|
||||
try {
|
||||
const config = await webConfigApi.get()
|
||||
webServerConfig.value = config
|
||||
applyBindStateFromConfig(config)
|
||||
} catch (e) {
|
||||
console.error('Failed to load web server config:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveWebServerConfig() {
|
||||
if (bindAddressError.value) return
|
||||
webServerLoading.value = true
|
||||
try {
|
||||
await webConfigApi.update(webServerConfig.value)
|
||||
const update = {
|
||||
http_port: webServerConfig.value.http_port,
|
||||
https_port: webServerConfig.value.https_port,
|
||||
https_enabled: webServerConfig.value.https_enabled,
|
||||
bind_addresses: effectiveBindAddresses.value,
|
||||
}
|
||||
const updated = await webConfigApi.update(update)
|
||||
webServerConfig.value = updated
|
||||
applyBindStateFromConfig(updated)
|
||||
showRestartDialog.value = true
|
||||
} catch (e) {
|
||||
console.error('Failed to save web server config:', e)
|
||||
@@ -1687,13 +1767,51 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('settings.bindAddress') }}</Label>
|
||||
<Input v-model="webServerConfig.bind_address" placeholder="0.0.0.0" />
|
||||
<p class="text-sm text-muted-foreground">{{ t('settings.bindAddressDesc') }}</p>
|
||||
<Label>{{ t('settings.bindMode') }}</Label>
|
||||
<select v-model="bindMode" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
|
||||
<option value="all">{{ t('settings.bindModeAll') }}</option>
|
||||
<option value="loopback">{{ t('settings.bindModeLocal') }}</option>
|
||||
<option value="custom">{{ t('settings.bindModeCustom') }}</option>
|
||||
</select>
|
||||
<p class="text-sm text-muted-foreground">{{ t('settings.bindModeDesc') }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="bindMode === 'all'" class="flex items-center justify-between">
|
||||
<div class="space-y-0.5">
|
||||
<Label>{{ t('settings.bindIpv6') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.bindAllDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="bindAllIpv6" />
|
||||
</div>
|
||||
|
||||
<div v-if="bindMode === 'loopback'" class="flex items-center justify-between">
|
||||
<div class="space-y-0.5">
|
||||
<Label>{{ t('settings.bindIpv6') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.bindLocalDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="bindLocalIpv6" />
|
||||
</div>
|
||||
|
||||
<div v-if="bindMode === 'custom'" class="space-y-2">
|
||||
<Label>{{ t('settings.bindAddressList') }}</Label>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(_, i) in bindAddressList" :key="`bind-${i}`" class="flex gap-2">
|
||||
<Input v-model="bindAddressList[i]" placeholder="192.168.1.10" />
|
||||
<Button variant="ghost" size="icon" @click="removeBindAddress(i)">
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" @click="addBindAddress">
|
||||
<Plus class="h-4 w-4 mr-1" />
|
||||
{{ t('settings.addBindAddress') }}
|
||||
</Button>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.bindAddressListDesc') }}</p>
|
||||
<p v-if="bindAddressError" class="text-xs text-destructive">{{ bindAddressError }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-4">
|
||||
<Button @click="saveWebServerConfig" :disabled="webServerLoading">
|
||||
<Button @click="saveWebServerConfig" :disabled="webServerLoading || !!bindAddressError">
|
||||
<Save class="h-4 w-4 mr-2" />
|
||||
{{ t('common.save') }}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user