mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-29 10:06:41 +00:00
Keep the Mounts tab in place when an LXC modal opens
- Apply the static mount list as soon as it arrives instead of waiting for the df/stat runtime probe. - Refill a guest's mount seed after a lifecycle event rather than leaving it empty until the next modal open. - Retry the bulk modal cache while the server is still warming guests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
53b415b127
commit
c0fa75f404
@@ -1153,6 +1153,7 @@ export function VirtualMachines() {
|
|||||||
cache.details.delete(vm.vmid)
|
cache.details.delete(vm.vmid)
|
||||||
cache.backups.delete(vm.vmid)
|
cache.backups.delete(vm.vmid)
|
||||||
cache.mountPoints.delete(vm.vmid)
|
cache.mountPoints.delete(vm.vmid)
|
||||||
|
if (vm.type === "lxc") reseedMountPoints(vm.vmid)
|
||||||
cache.schedule.delete(vm.vmid)
|
cache.schedule.delete(vm.vmid)
|
||||||
cache.firewall.delete(vm.vmid)
|
cache.firewall.delete(vm.vmid)
|
||||||
dockerInventoryRequestedRef.current.delete(vm.vmid)
|
dockerInventoryRequestedRef.current.delete(vm.vmid)
|
||||||
@@ -1355,7 +1356,8 @@ export function VirtualMachines() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!vmData || vmData.length === 0) return
|
if (!vmData || vmData.length === 0) return
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
fetchApi<{
|
let retryTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
const hydrate = (attempt: number) => fetchApi<{
|
||||||
guests: Array<{
|
guests: Array<{
|
||||||
vmid: number
|
vmid: number
|
||||||
type: "qemu" | "lxc"
|
type: "qemu" | "lxc"
|
||||||
@@ -1370,6 +1372,10 @@ export function VirtualMachines() {
|
|||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
if (cancelled || !payload?.guests) return
|
if (cancelled || !payload?.guests) return
|
||||||
const cache = vmModalCacheRef.current
|
const cache = vmModalCacheRef.current
|
||||||
|
// Guests the server has not warmed yet come back null; ask again
|
||||||
|
// shortly so their mount tab is seeded before the modal opens.
|
||||||
|
const pending = payload.guests.some((g) => g.type === "lxc" && !g.mount_points)
|
||||||
|
if (pending && attempt < 3) retryTimer = setTimeout(() => hydrate(attempt + 1), 20000)
|
||||||
for (const g of payload.guests) {
|
for (const g of payload.guests) {
|
||||||
if (g.details) cache.details.set(g.vmid, g.details)
|
if (g.details) cache.details.set(g.vmid, g.details)
|
||||||
if (g.backups?.backups) {
|
if (g.backups?.backups) {
|
||||||
@@ -1403,7 +1409,11 @@ export function VirtualMachines() {
|
|||||||
// Silent — modal open handlers fall back to individual
|
// Silent — modal open handlers fall back to individual
|
||||||
// fetches if the ref cache is empty.
|
// fetches if the ref cache is empty.
|
||||||
})
|
})
|
||||||
return () => { cancelled = true }
|
hydrate(0)
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
if (retryTimer) clearTimeout(retryTimer)
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [vmidsKey])
|
}, [vmidsKey])
|
||||||
|
|
||||||
@@ -1431,49 +1441,66 @@ export function VirtualMachines() {
|
|||||||
// user perceives no lag.
|
// user perceives no lag.
|
||||||
const hasSeed = mountPoints.length > 0
|
const hasSeed = mountPoints.length > 0
|
||||||
if (!hasSeed) setLoadingMounts(true)
|
if (!hasSeed) setLoadingMounts(true)
|
||||||
try {
|
// Each half is applied as soon as it lands: the static list decides
|
||||||
const [staticResp, runtimeResp] = await Promise.all([
|
// whether the tab exists and must not wait for df/stat.
|
||||||
fetchApi<{
|
const staticReq = fetchApi<{
|
||||||
ok: boolean
|
ok: boolean
|
||||||
mount_points: LxcMountPoint[]
|
mount_points: LxcMountPoint[]
|
||||||
ad_hoc_hint_count?: number
|
ad_hoc_hint_count?: number
|
||||||
}>(`/api/lxc/${vmid}/mount-points`).catch((e) => {
|
}>(`/api/lxc/${vmid}/mount-points`)
|
||||||
console.error("Error fetching static mount points:", e)
|
.then((staticResp) => {
|
||||||
return null
|
if (staticResp?.ok) {
|
||||||
}),
|
const mp = staticResp.mount_points || []
|
||||||
fetchApi<{
|
const hint = staticResp.ad_hoc_hint_count ?? 0
|
||||||
ok: boolean
|
setMountPoints(mp)
|
||||||
running: boolean
|
setMountsAdHocHint(hint)
|
||||||
runtime: Record<string, Partial<LxcMountPoint>>
|
vmModalCacheRef.current.mountPoints.set(vmid, { mount_points: mp, ad_hoc_hint_count: hint })
|
||||||
ad_hoc: LxcMountPoint[]
|
} else if (!hasSeed) {
|
||||||
}>(`/api/lxc/${vmid}/mount-points/runtime`).catch((e) => {
|
setMountPoints([])
|
||||||
console.error("Error fetching runtime mount points:", e)
|
}
|
||||||
return null
|
})
|
||||||
}),
|
.catch((e) => {
|
||||||
])
|
console.error("Error fetching static mount points:", e)
|
||||||
if (staticResp?.ok) {
|
if (!hasSeed) setMountPoints([])
|
||||||
const mp = staticResp.mount_points || []
|
})
|
||||||
const hint = staticResp.ad_hoc_hint_count ?? 0
|
.finally(() => setLoadingMounts(false))
|
||||||
setMountPoints(mp)
|
const runtimeReq = fetchApi<{
|
||||||
setMountsAdHocHint(hint)
|
ok: boolean
|
||||||
vmModalCacheRef.current.mountPoints.set(vmid, { mount_points: mp, ad_hoc_hint_count: hint })
|
running: boolean
|
||||||
} else if (!hasSeed) {
|
runtime: Record<string, Partial<LxcMountPoint>>
|
||||||
setMountPoints([])
|
ad_hoc: LxcMountPoint[]
|
||||||
}
|
}>(`/api/lxc/${vmid}/mount-points/runtime`)
|
||||||
if (runtimeResp?.ok) {
|
.then((runtimeResp) => {
|
||||||
setMountPointsRuntime(runtimeResp.runtime || {})
|
if (runtimeResp?.ok) {
|
||||||
setAdHocMounts(runtimeResp.ad_hoc || [])
|
setMountPointsRuntime(runtimeResp.runtime || {})
|
||||||
} else {
|
setAdHocMounts(runtimeResp.ad_hoc || [])
|
||||||
|
} else {
|
||||||
|
setMountPointsRuntime({})
|
||||||
|
setAdHocMounts([])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Error fetching runtime mount points:", e)
|
||||||
setMountPointsRuntime({})
|
setMountPointsRuntime({})
|
||||||
setAdHocMounts([])
|
setAdHocMounts([])
|
||||||
}
|
})
|
||||||
} catch (error) {
|
await Promise.all([staticReq, runtimeReq])
|
||||||
console.error("Error fetching LXC mount points:", error)
|
}
|
||||||
if (!hasSeed) setMountPoints([])
|
|
||||||
setAdHocMounts([])
|
// Background refill of the static mount list after a lifecycle event,
|
||||||
} finally {
|
// so the next open of that guest renders its tab bar complete.
|
||||||
setLoadingMounts(false)
|
const reseedMountPoints = (vmid: number) => {
|
||||||
}
|
fetchApi<{ ok: boolean; mount_points: LxcMountPoint[]; ad_hoc_hint_count?: number }>(
|
||||||
|
`/api/lxc/${vmid}/mount-points`,
|
||||||
|
)
|
||||||
|
.then((r) => {
|
||||||
|
if (!r?.ok) return
|
||||||
|
vmModalCacheRef.current.mountPoints.set(vmid, {
|
||||||
|
mount_points: r.mount_points || [],
|
||||||
|
ad_hoc_hint_count: r.ad_hoc_hint_count ?? 0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMetricsClick = () => {
|
const handleMetricsClick = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user