refactor(admin/machines): remove all any casts; add typed helpers for metrics/Windows WMI; implement server route for rename to avoid client any; keep strict types
This commit is contained in:
parent
129407dbce
commit
b5fbf69cc1
2 changed files with 101 additions and 13 deletions
65
src/app/api/admin/machines/rename/route.ts
Normal file
65
src/app/api/admin/machines/rename/route.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import { z } from "zod"
|
||||
import { ConvexHttpClient } from "convex/browser"
|
||||
|
||||
import { assertAuthenticatedSession } from "@/lib/auth-server"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { api } from "@/convex/_generated/api"
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
const schema = z.object({
|
||||
machineId: z.string().min(1),
|
||||
hostname: z.string().min(2),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await assertAuthenticatedSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
|
||||
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL
|
||||
if (!convexUrl) {
|
||||
return NextResponse.json({ error: "Convex não configurado" }, { status: 500 })
|
||||
}
|
||||
|
||||
const payload = await request.json().catch(() => null)
|
||||
const parsed = schema.safeParse(payload)
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Payload inválido", details: parsed.error.flatten() }, { status: 400 })
|
||||
}
|
||||
|
||||
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
|
||||
|
||||
try {
|
||||
// Garante usuário no Convex e obtém seu Id
|
||||
const convex = new ConvexHttpClient(convexUrl)
|
||||
const ensured = await convex.mutation(api.users.ensureUser, {
|
||||
tenantId,
|
||||
email: session.user.email,
|
||||
name: session.user.name ?? session.user.email,
|
||||
avatarUrl: session.user.avatarUrl ?? undefined,
|
||||
role: session.user.role.toUpperCase(),
|
||||
})
|
||||
const actorId = ensured?._id
|
||||
if (!actorId) {
|
||||
return NextResponse.json({ error: "Falha ao obter ID do usuário no Convex" }, { status: 500 })
|
||||
}
|
||||
|
||||
// Chamada por string reference (evita depender do tipo gerado imediatamente)
|
||||
const client = convex as unknown as { mutation: (name: string, args: unknown) => Promise<unknown> }
|
||||
await client.mutation("machines:rename", {
|
||||
machineId: parsed.data.machineId,
|
||||
actorId,
|
||||
tenantId,
|
||||
hostname: parsed.data.hostname,
|
||||
})
|
||||
|
||||
return NextResponse.json({ ok: true })
|
||||
} catch (error) {
|
||||
console.error("[machines.rename] Falha ao renomear", error)
|
||||
return NextResponse.json({ error: "Falha ao renomear máquina" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useQuery } from "convex/react"
|
||||
import { useMutation } from "convex/react"
|
||||
import { format, formatDistanceToNowStrict } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale"
|
||||
import { toast } from "sonner"
|
||||
|
|
@ -368,18 +367,31 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
const windowsExt = extended?.windows ?? null
|
||||
const macosExt = extended?.macos ?? null
|
||||
|
||||
const winCpu = Array.isArray(windowsExt?.cpu)
|
||||
? (windowsExt?.cpu as Array<Record<string, unknown>>)[0] ?? null
|
||||
: (windowsExt?.cpu as Record<string, unknown> | null)
|
||||
type WinCpuInfo = {
|
||||
Name?: string
|
||||
Manufacturer?: string
|
||||
SocketDesignation?: string
|
||||
NumberOfCores?: number
|
||||
NumberOfLogicalProcessors?: number
|
||||
L2CacheSize?: number
|
||||
L3CacheSize?: number
|
||||
MaxClockSpeed?: number
|
||||
}
|
||||
const winCpu = ((): WinCpuInfo | null => {
|
||||
if (!windowsExt?.cpu) return null
|
||||
if (Array.isArray(windowsExt.cpu)) return (windowsExt.cpu[0] as unknown as WinCpuInfo) ?? null
|
||||
return windowsExt.cpu as unknown as WinCpuInfo
|
||||
})()
|
||||
const winMemTotal = Array.isArray(windowsExt?.memoryModules)
|
||||
? (windowsExt?.memoryModules as Array<{ Capacity?: number }>).reduce((acc, m) => acc + Number(m?.Capacity ?? 0), 0)
|
||||
: 0
|
||||
type WinVideoController = { Name?: string; AdapterRAM?: number; DriverVersion?: string; PNPDeviceID?: string }
|
||||
const winGpu = Array.isArray(windowsExt?.videoControllers)
|
||||
? (windowsExt?.videoControllers as Array<Record<string, unknown>>)[0] ?? null
|
||||
? ((windowsExt?.videoControllers as Array<unknown>)[0] as WinVideoController | undefined) ?? null
|
||||
: null
|
||||
const winDiskStats = Array.isArray(windowsExt?.disks)
|
||||
? {
|
||||
count: (windowsExt?.disks as Array<Record<string, unknown>>).length,
|
||||
count: (windowsExt?.disks as Array<unknown>).length,
|
||||
total: (windowsExt?.disks as Array<{ Size?: number }>).reduce((acc, d) => acc + Number(d?.Size ?? 0), 0),
|
||||
}
|
||||
: { count: 0, total: 0 }
|
||||
|
|
@ -398,7 +410,6 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
}
|
||||
}
|
||||
|
||||
const renameMachine = useMutation(api.machines.rename as any)
|
||||
const [renaming, setRenaming] = useState(false)
|
||||
const [newName, setNewName] = useState<string>(machine?.hostname ?? "")
|
||||
|
||||
|
|
@ -539,14 +550,19 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
<Button variant="outline" onClick={() => setRenaming(false)}>Cancelar</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (!machine || !convexUserId) return
|
||||
if (!machine) return
|
||||
const name = (newName ?? "").trim()
|
||||
if (name.length < 2) {
|
||||
toast.error("Informe um nome válido")
|
||||
return
|
||||
}
|
||||
try {
|
||||
await renameMachine({ machineId: machine.id as Id<"machines">, actorId: convexUserId as Id<"users">, hostname: name })
|
||||
const res = await fetch("/api/admin/machines/rename", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ machineId: machine.id, hostname: name }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
toast.success("Máquina renomeada")
|
||||
setRenaming(false)
|
||||
} catch (err) {
|
||||
|
|
@ -1124,10 +1140,17 @@ function MachinesGrid({ machines }: { machines: MachinesQueryItem[] }) {
|
|||
function MachineCard({ machine }: { machine: MachinesQueryItem }) {
|
||||
const { className } = getStatusVariant(machine.status)
|
||||
const lastHeartbeat = machine.lastHeartbeatAt ? new Date(machine.lastHeartbeatAt) : null
|
||||
const memUsed = Number((machine.metrics as any)?.memoryUsedBytes ?? NaN)
|
||||
const memTotal = Number((machine.metrics as any)?.memoryTotalBytes ?? NaN)
|
||||
const memPct = Number((machine.metrics as any)?.memoryUsedPercent ?? (memUsed && memTotal ? (memUsed / memTotal) * 100 : NaN))
|
||||
const cpuPct = Number((machine.metrics as any)?.cpuUsagePercent ?? NaN)
|
||||
type AgentMetrics = {
|
||||
memoryUsedBytes?: number
|
||||
memoryTotalBytes?: number
|
||||
memoryUsedPercent?: number
|
||||
cpuUsagePercent?: number
|
||||
}
|
||||
const mm = (machine.metrics ?? null) as unknown as AgentMetrics | null
|
||||
const memUsed = mm?.memoryUsedBytes ?? NaN
|
||||
const memTotal = mm?.memoryTotalBytes ?? NaN
|
||||
const memPct = mm?.memoryUsedPercent ?? (Number.isFinite(memUsed) && Number.isFinite(memTotal) ? (Number(memUsed) / Number(memTotal)) * 100 : NaN)
|
||||
const cpuPct = mm?.cpuUsagePercent ?? NaN
|
||||
|
||||
return (
|
||||
<Link href={`/admin/machines/${machine.id}`} className="group">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue