admin(machines): fix machine detail not loading by switching to server-side fetch by ID
- Add Convex query machines.getById with full payload (metrics/inventory) - Update AdminMachineDetailsClient to use getById instead of listByTenant+find - Update MachineBreadcrumbs to fetch hostname by ID This prevents the empty state when the list query hasn’t loaded or filtered out the machine.
This commit is contained in:
parent
6333a3fc07
commit
5ff37195f5
3 changed files with 127 additions and 16 deletions
|
|
@ -850,6 +850,118 @@ export const listByTenant = query({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getById = query({
|
||||||
|
args: {
|
||||||
|
id: v.id("machines"),
|
||||||
|
includeMetadata: v.optional(v.boolean()),
|
||||||
|
},
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const includeMetadata = Boolean(args.includeMetadata)
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
const machine = await ctx.db.get(args.id)
|
||||||
|
if (!machine) return null
|
||||||
|
|
||||||
|
const tokens = await ctx.db
|
||||||
|
.query("machineTokens")
|
||||||
|
.withIndex("by_machine", (q) => q.eq("machineId", machine._id))
|
||||||
|
.collect()
|
||||||
|
|
||||||
|
const activeToken = tokens.find((token) => !token.revoked && token.expiresAt > now) ?? null
|
||||||
|
|
||||||
|
const offlineThresholdMs = getOfflineThresholdMs()
|
||||||
|
const staleThresholdMs = getStaleThresholdMs(offlineThresholdMs)
|
||||||
|
const manualStatus = (machine.status ?? "").toLowerCase()
|
||||||
|
let derivedStatus: string
|
||||||
|
if (machine.isActive === false) {
|
||||||
|
derivedStatus = "deactivated"
|
||||||
|
} else if (["maintenance", "blocked"].includes(manualStatus)) {
|
||||||
|
derivedStatus = manualStatus
|
||||||
|
} else if (machine.lastHeartbeatAt) {
|
||||||
|
const age = now - machine.lastHeartbeatAt
|
||||||
|
if (age <= offlineThresholdMs) {
|
||||||
|
derivedStatus = "online"
|
||||||
|
} else if (age <= staleThresholdMs) {
|
||||||
|
derivedStatus = "offline"
|
||||||
|
} else {
|
||||||
|
derivedStatus = "stale"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
derivedStatus = machine.status ?? "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = includeMetadata ? (machine.metadata ?? null) : null
|
||||||
|
let metrics: Record<string, unknown> | null = null
|
||||||
|
let inventory: Record<string, unknown> | null = null
|
||||||
|
let postureAlerts: Array<Record<string, unknown>> | null = null
|
||||||
|
let lastPostureAt: number | null = null
|
||||||
|
if (meta && typeof meta === "object") {
|
||||||
|
const metaRecord = meta as Record<string, unknown>
|
||||||
|
if (metaRecord.metrics && typeof metaRecord.metrics === "object") {
|
||||||
|
metrics = metaRecord.metrics as Record<string, unknown>
|
||||||
|
}
|
||||||
|
if (metaRecord.inventory && typeof metaRecord.inventory === "object") {
|
||||||
|
inventory = metaRecord.inventory as Record<string, unknown>
|
||||||
|
}
|
||||||
|
if (Array.isArray(metaRecord.postureAlerts)) {
|
||||||
|
postureAlerts = metaRecord.postureAlerts as Array<Record<string, unknown>>
|
||||||
|
}
|
||||||
|
if (typeof metaRecord.lastPostureAt === "number") {
|
||||||
|
lastPostureAt = metaRecord.lastPostureAt as number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkedUserIds = machine.linkedUserIds ?? []
|
||||||
|
const linkedUsers = await Promise.all(
|
||||||
|
linkedUserIds.map(async (id) => {
|
||||||
|
const u = await ctx.db.get(id)
|
||||||
|
if (!u) return null
|
||||||
|
return { id: u._id, email: u.email, name: u.name }
|
||||||
|
})
|
||||||
|
).then((arr) => arr.filter(Boolean) as Array<{ id: string; email: string; name: string }>)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: machine._id,
|
||||||
|
tenantId: machine.tenantId,
|
||||||
|
hostname: machine.hostname,
|
||||||
|
companyId: machine.companyId ?? null,
|
||||||
|
companySlug: machine.companySlug ?? null,
|
||||||
|
osName: machine.osName,
|
||||||
|
osVersion: machine.osVersion ?? null,
|
||||||
|
architecture: machine.architecture ?? null,
|
||||||
|
macAddresses: machine.macAddresses,
|
||||||
|
serialNumbers: machine.serialNumbers,
|
||||||
|
authUserId: machine.authUserId ?? null,
|
||||||
|
authEmail: machine.authEmail ?? null,
|
||||||
|
persona: machine.persona ?? null,
|
||||||
|
assignedUserId: machine.assignedUserId ?? null,
|
||||||
|
assignedUserEmail: machine.assignedUserEmail ?? null,
|
||||||
|
assignedUserName: machine.assignedUserName ?? null,
|
||||||
|
assignedUserRole: machine.assignedUserRole ?? null,
|
||||||
|
linkedUsers,
|
||||||
|
status: derivedStatus,
|
||||||
|
isActive: machine.isActive ?? true,
|
||||||
|
lastHeartbeatAt: machine.lastHeartbeatAt ?? null,
|
||||||
|
heartbeatAgeMs: machine.lastHeartbeatAt ? now - machine.lastHeartbeatAt : null,
|
||||||
|
registeredBy: machine.registeredBy ?? null,
|
||||||
|
createdAt: machine.createdAt,
|
||||||
|
updatedAt: machine.updatedAt,
|
||||||
|
token: activeToken
|
||||||
|
? {
|
||||||
|
expiresAt: activeToken.expiresAt,
|
||||||
|
lastUsedAt: activeToken.lastUsedAt ?? null,
|
||||||
|
usageCount: activeToken.usageCount ?? 0,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
metrics,
|
||||||
|
inventory,
|
||||||
|
postureAlerts,
|
||||||
|
lastPostureAt,
|
||||||
|
remoteAccess: machine.remoteAccess ?? null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const listAlerts = query({
|
export const listAlerts = query({
|
||||||
args: {
|
args: {
|
||||||
machineId: v.id("machines"),
|
machineId: v.id("machines"),
|
||||||
|
|
|
||||||
|
|
@ -10,18 +10,18 @@ import {
|
||||||
} from "@/components/admin/machines/admin-machines-overview"
|
} from "@/components/admin/machines/admin-machines-overview"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
|
import type { Id } from "@/convex/_generated/dataModel"
|
||||||
|
|
||||||
export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||||
const rawResult = useQuery(api.machines.listByTenant, { tenantId, includeMetadata: true }) as Array<Record<string, unknown>> | undefined
|
const single = useQuery((api as any).machines.getById, { id: machineId as Id<"machines">, includeMetadata: true }) as
|
||||||
const machines: MachinesQueryItem[] | undefined = useMemo(() => {
|
| Record<string, unknown>
|
||||||
if (!rawResult) return undefined
|
| null
|
||||||
return rawResult.map((item) => normalizeMachineItem(item))
|
| undefined
|
||||||
}, [rawResult])
|
const machine: MachinesQueryItem | null = useMemo(() => {
|
||||||
const isLoading = rawResult === undefined
|
if (single === undefined || single === null) return single as null
|
||||||
const machine = useMemo(() => {
|
return normalizeMachineItem(single)
|
||||||
if (!machines) return null
|
}, [single])
|
||||||
return machines.find((m) => m.id === machineId) ?? null
|
const isLoading = single === undefined
|
||||||
}, [machines, machineId])
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ import { useAuth } from "@/lib/auth-client"
|
||||||
|
|
||||||
export function MachineBreadcrumbs({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
export function MachineBreadcrumbs({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||||
const { convexUserId } = useAuth()
|
const { convexUserId } = useAuth()
|
||||||
const list = useQuery(
|
const item = useQuery((api as any).machines.getById, { id: machineId as Id<"machines">, includeMetadata: false }) as
|
||||||
convexUserId ? api.machines.listByTenant : "skip",
|
| { hostname: string }
|
||||||
convexUserId ? { tenantId, includeMetadata: false } : ("skip" as const)
|
| null
|
||||||
) as Array<{ id: Id<"machines">; hostname: string }> | undefined
|
| undefined
|
||||||
const hostname = useMemo(() => list?.find((m) => m.id === (machineId as unknown as Id<"machines">))?.hostname ?? "Detalhe", [list, machineId])
|
const hostname = useMemo(() => item?.hostname ?? "Detalhe", [item])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="mb-4 text-sm text-neutral-600">
|
<nav className="mb-4 text-sm text-neutral-600">
|
||||||
|
|
@ -27,4 +27,3 @@ export function MachineBreadcrumbs({ tenantId, machineId }: { tenantId: string;
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue