修复了一些已知问题

This commit is contained in:
MengMengCode
2026-06-11 23:09:04 +08:00
parent c303fe6d17
commit 2f94498df2
7 changed files with 63 additions and 60 deletions
@@ -321,7 +321,12 @@ export default function CreateContainerModal({ isOpen, onClose, onSuccess, exist
type="checkbox"
checked={!!form.assign_ipv4}
disabled={!ipv4Available}
onChange={(event) => setForm({ ...form, assign_ipv4: event.target.checked, public_ipv4s: event.target.checked ? form.public_ipv4s : [] })}
onChange={(event) => setForm({
...form,
assign_ipv4: event.target.checked,
public_ipv4s: event.target.checked ? form.public_ipv4s : [],
...(event.target.checked ? { assign_nat: false, port_mapping_count: 0, extra_ports: [] } : {}),
})}
className="mt-1"
/>
<span className="min-w-0">
@@ -429,6 +434,7 @@ export default function CreateContainerModal({ isOpen, onClose, onSuccess, exist
assign_nat: checked,
port_mapping_count: checked ? Math.max(2, form.port_mapping_count || 2) : 0,
extra_ports: [],
...(checked ? { assign_ipv4: false, public_ipv4s: [], ipv4_count: 0 } : {}),
})
}}
className="mt-1"
@@ -680,9 +686,10 @@ function validateResourceInputs(form: CreateContainerRequest, maxVCPU: number, m
function normalizeCreateForm(form: CreateContainerRequest): CreateContainerRequest {
const normalized = applyTemplateDefaults(form)
const wantsNAT = normalized.assign_nat !== false
const wantsIPv4 = !!normalized.assign_ipv4
const wantsIPv6 = !!normalized.assign_ipv6
// IPv4 and NAT are mutually exclusive
const wantsNAT = wantsIPv4 ? false : normalized.assign_nat !== false
const linuxTemplate = !isWindowsTemplate(normalized.template_id)
const sshAuthMode = linuxTemplate ? (normalized.ssh_auth_mode || 'auto_password') : 'auto_password'
return {
+26 -17
View File
@@ -773,23 +773,26 @@ export default function ContainerDetail() {
const maxRAMMB = hostInfo?.ram.total_mb ? Number(hostInfo.ram.total_mb) : undefined
const hasIndependentIPv4 = assignedIPv4List.length > 0
const hasIndependentIPv6 = ipv6List.length > 0
const hasIndependentIP = hasIndependentIPv4 || hasIndependentIPv6
const defaultConnPort = isWindows ? 3389 : 22
let publicEndpoint = '-'
let sshCommand = ''
if (container.ssh_port > 0) {
if (hasIndependentIPv4) {
// Direct connection via independent IPv4 — all ports forwarded
publicEndpoint = `${assignedIPv4List[0]}:${defaultConnPort}`
if (!isWindows) {
sshCommand = `ssh root@${assignedIPv4List[0]}`
}
} else if (hasIndependentIPv6) {
publicEndpoint = `[${ipv6List[0]}]:${defaultConnPort}`
if (!isWindows) {
sshCommand = `ssh root@[${ipv6List[0]}]`
}
} else if (container.ssh_port > 0) {
// NAT port mapping mode
publicEndpoint = `${publicHost}:${container.ssh_port}`
sshCommand = `ssh -p ${container.ssh_port} root@${publicHost}`
} else if (hasIndependentIP) {
// Direct connection via independent IPv4 or IPv6
const connIP = hasIndependentIPv4 ? assignedIPv4List[0] : `[${ipv6List[0]}]`
publicEndpoint = `${connIP}:${defaultConnPort}`
if (!isWindows) {
sshCommand = `ssh root@${connIP}`
}
}
const editingSSH = draft.index !== null && !!container.port_mappings?.[draft.index] && (
container.port_mappings[draft.index].description === 'SSH' || container.port_mappings[draft.index].container_port === 22 ||
@@ -879,7 +882,11 @@ export default function ContainerDetail() {
<InfoTag color="blue"> {container.template}</InfoTag>
<InfoTag color="slate"> {(container.virtualization || 'lxc').toUpperCase()}</InfoTag>
<InfoTag color="emerald"> {container.ip || '-'}</InfoTag>
<InfoTag color="amber">IPv4 NAT {hasNATQuota ? `${mappingCount}` : '未分配'}</InfoTag>
{hasIndependentIPv4 ? (
<InfoTag color="amber"> IPv4 {assignedIPv4List[0]}</InfoTag>
) : (
<InfoTag color="amber">IPv4 NAT {hasNATQuota ? `${mappingCount}` : '未分配'}</InfoTag>
)}
<InfoTag color="violet">{isWindows ? 'RDP' : 'SSH'} {publicEndpoint}</InfoTag>
{isPolicyBlocked && <InfoTag color="red"></InfoTag>}
</div>
@@ -922,12 +929,14 @@ export default function ContainerDetail() {
</ActionButton>
)}
<>
<ActionButton disabled={isSubUserPolicyBlocked} onClick={() => setShowNat(true)}>
<Settings className="w-3.5 h-3.5" />
IPv4 NAT
</ActionButton>
</>
{!hasIndependentIPv4 && (
<>
<ActionButton disabled={isSubUserPolicyBlocked} onClick={() => setShowNat(true)}>
<Settings className="w-3.5 h-3.5" />
IPv4 NAT
</ActionButton>
</>
)}
<ActionButton onClick={() => setShowSnapshots(true)} disabled={!!taskStatus || !!snapshotBusy || isSubUserPolicyBlocked}>
<Camera className="w-3.5 h-3.5" />
@@ -1437,7 +1446,7 @@ export default function ContainerDetail() {
</Modal>
)}
{showNat && (
{showNat && !hasIndependentIPv4 && (
<Modal title="IPv4 NAT 端口管理" onClose={() => { setShowNat(false); setDraft(emptyDraft); setShowMappingEditor(false) }} wide extra={
!isSubUser && canAddMapping && (
<button onClick={openAddMapping} className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-black text-white rounded-md text-xs hover:bg-gray-800">
+10 -8
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
import { Globe2, Network, Pencil, Plus, RefreshCw, Router, Save, Search, Server, Trash2, X } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { useLanguage, type Language } from '../contexts/LanguageContext'
@@ -23,7 +23,8 @@ export default function Routing() {
const [ipv4EditMode, setIPv4EditMode] = useState<'pool' | 'address'>('pool')
const [editingIPv4Address, setEditingIPv4Address] = useState('')
const [savingIPv4, setSavingIPv4] = useState(false)
const [ipv4Draft, setIPv4Draft] = useState<PublicIPv4Info[]>([])
const [ipv4Draft, setIPv4Draft] = useState<(PublicIPv4Info & { _id: number })[]>([])
const nextDraftId = useRef(0)
const [nat4Page, setNat4Page] = useState(1)
const [ipv6Page, setIPv6Page] = useState(1)
const [nat4Search, setNat4Search] = useState('')
@@ -54,7 +55,7 @@ export default function Routing() {
useEffect(() => {
if (!editingIPv4) {
setIPv4Draft(publicIPv4s.map((ip) => ({ ...ip })))
setIPv4Draft(publicIPv4s.map((ip) => ({ ...ip, _id: nextDraftId.current++ })))
}
}, [editingIPv4, publicIPv4s])
@@ -65,14 +66,14 @@ export default function Routing() {
}, [ipv4Assignments])
const startEditIPv4 = () => {
setIPv4Draft(publicIPv4s.map((ip) => ({ ...ip })))
setIPv4Draft(publicIPv4s.map((ip) => ({ ...ip, _id: nextDraftId.current++ })))
setIPv4EditMode('pool')
setEditingIPv4Address('')
setEditingIPv4(true)
}
const startEditIPv4Address = (ip: PublicIPv4Info) => {
setIPv4Draft([{ ...ip }])
setIPv4Draft([{ ...ip, _id: nextDraftId.current++ }])
setIPv4EditMode('address')
setEditingIPv4Address(ip.address)
setEditingIPv4(true)
@@ -82,13 +83,14 @@ export default function Routing() {
setEditingIPv4(false)
setIPv4EditMode('pool')
setEditingIPv4Address('')
setIPv4Draft(publicIPv4s.map((ip) => ({ ...ip })))
setIPv4Draft([])
}
const addIPv4Row = () => {
setIPv4Draft((items) => [
...items,
{
_id: nextDraftId.current++,
address: '',
interface: defaultIPv4Interface,
prefix: '',
@@ -108,7 +110,7 @@ export default function Routing() {
setSavingIPv4(true)
try {
const draftItems = ipv4Draft
.map((item) => ({
.map(({ _id, ...item }) => ({
...item,
address: (item.address || '').trim(),
interface: (item.interface || defaultIPv4Interface).trim(),
@@ -285,7 +287,7 @@ export default function Routing() {
</thead>
<tbody className="divide-y divide-gray-100">
{ipv4Draft.map((item, index) => (
<tr key={`${item.address}-${index}`}>
<tr key={item._id}>
<td className="px-3 py-2"><input value={item.address || ''} onChange={(e) => updateIPv4Draft(index, { address: e.target.value })} placeholder={text.ipv4CIDR} className={smallInputClass} /></td>
<td className="px-3 py-2"><input value={item.gateway || ''} onChange={(e) => updateIPv4Draft(index, { gateway: e.target.value })} placeholder={defaultIPv4Gateway || text.gateway} className={smallInputClass} /></td>
<td className="px-3 py-2"><input value={item.interface || ''} onChange={(e) => updateIPv4Draft(index, { interface: e.target.value })} placeholder={defaultIPv4Interface} className={smallInputClass} /></td>