docs: registrar fluxo do updater e atualizar chaves

This commit is contained in:
Esdras Renan 2025-10-12 04:06:29 -03:00
parent 206d00700e
commit b5fd920efd
50 changed files with 980 additions and 93 deletions

View file

@ -1,4 +1,5 @@
import { useEffect, useMemo, useState } from "react"
/* eslint-disable @next/next/no-img-element */
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { createRoot } from "react-dom/client"
import { invoke } from "@tauri-apps/api/core"
import { Store } from "@tauri-apps/plugin-store"
@ -45,6 +46,12 @@ type MachineRegisterResponse = {
machineToken: string
machineEmail?: string | null
expiresAt?: number | null
persona?: string | null
assignedUserId?: string | null
collaborator?: {
email: string
name?: string | null
} | null
}
type AgentConfig = {
@ -54,6 +61,10 @@ type AgentConfig = {
machineEmail?: string | null
collaboratorEmail?: string | null
collaboratorName?: string | null
accessRole: "collaborator" | "manager"
assignedUserId?: string | null
assignedUserEmail?: string | null
assignedUserName?: string | null
apiBaseUrl: string
appUrl: string
createdAt: number
@ -129,7 +140,14 @@ function App() {
const [company, setCompany] = useState("")
const [collabEmail, setCollabEmail] = useState("")
const [collabName, setCollabName] = useState("")
const [accessRole, setAccessRole] = useState<"collaborator" | "manager">("collaborator")
const [updating, setUpdating] = useState(false)
const [updateInfo, setUpdateInfo] = useState<{ message: string; tone: "info" | "success" | "error" } | null>({
message: "Atualizações automáticas são verificadas a cada inicialização.",
tone: "info",
})
const autoLaunchRef = useRef(false)
const autoUpdateRef = useRef(false)
useEffect(() => {
(async () => {
@ -140,6 +158,7 @@ function App() {
setToken(t)
const cfg = await readConfig(s)
setConfig(cfg)
setAccessRole(cfg?.accessRole ?? "collaborator")
if (cfg?.collaboratorEmail) setCollabEmail(cfg.collaboratorEmail)
if (cfg?.collaboratorName) setCollabName(cfg.collaboratorName)
if (!t) {
@ -147,7 +166,7 @@ function App() {
setProfile(p)
}
setStatus(t ? "online" : null)
} catch (err) {
} catch {
setError("Falha ao carregar estado do agente.")
}
})()
@ -161,7 +180,8 @@ function App() {
const normalizedName = name.length > 0 ? name : null
if (
config.collaboratorEmail === normalizedEmail &&
config.collaboratorName === normalizedName
config.collaboratorName === normalizedName &&
config.accessRole === accessRole
) {
return
}
@ -169,10 +189,11 @@ function App() {
...config,
collaboratorEmail: normalizedEmail,
collaboratorName: normalizedName,
accessRole,
}
setConfig(nextConfig)
writeConfig(store, nextConfig).catch((err) => console.error("Falha ao atualizar colaborador", err))
}, [store, config?.machineId, config?.collaboratorEmail, config?.collaboratorName, collabEmail, collabName])
}, [store, config, config?.collaboratorEmail, config?.collaboratorName, config?.accessRole, collabEmail, collabName, accessRole])
useEffect(() => {
if (!store || !config) return
@ -195,7 +216,7 @@ function App() {
return appUrl
}
return normalized
}, [config?.appUrl, appUrl])
}, [config?.appUrl])
async function register() {
if (!profile) return
@ -205,6 +226,16 @@ function App() {
const collaboratorPayload = collabEmail.trim()
? { email: collabEmail.trim(), name: collabName.trim() || undefined }
: undefined
const collaboratorMetadata = collaboratorPayload
? { ...collaboratorPayload, role: accessRole }
: undefined
const metadataPayload: Record<string, unknown> = {
inventory: profile.inventory,
metrics: profile.metrics,
}
if (collaboratorMetadata) {
metadataPayload.collaborator = collaboratorMetadata
}
const payload = {
provisioningSecret: provisioningSecret.trim(),
tenantId: tenantId.trim() || undefined,
@ -213,7 +244,9 @@ function App() {
os: profile.os,
macAddresses: profile.macAddresses,
serialNumbers: profile.serialNumbers,
metadata: { inventory: profile.inventory, metrics: profile.metrics, collaborator: collaboratorPayload },
metadata: metadataPayload,
accessRole,
collaborator: collaboratorPayload,
registeredBy: "desktop-agent",
}
const res = await fetch(`${apiBaseUrl}/api/machines/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) })
@ -231,6 +264,10 @@ function App() {
machineEmail: data.machineEmail ?? null,
collaboratorEmail: collaboratorPayload?.email ?? null,
collaboratorName: collaboratorPayload?.name ?? null,
accessRole,
assignedUserId: data.assignedUserId ?? null,
assignedUserEmail: data.collaborator?.email ?? collaboratorPayload?.email ?? null,
assignedUserName: data.collaborator?.name ?? collaboratorPayload?.name ?? null,
apiBaseUrl,
appUrl,
createdAt: Date.now(),
@ -248,16 +285,19 @@ function App() {
}
}
async function openSystem() {
if (!token || !config) return
const url = `${resolvedAppUrl}/machines/handshake?token=${encodeURIComponent(token)}`
const openSystem = useCallback(() => {
if (!token) return
const persona = (config?.accessRole ?? accessRole) === "manager" ? "manager" : "collaborator"
const redirectTarget = persona === "manager" ? "/dashboard" : "/portal"
const url = `${resolvedAppUrl}/machines/handshake?token=${encodeURIComponent(token)}&redirect=${encodeURIComponent(redirectTarget)}`
window.location.href = url
}
}, [token, config?.accessRole, accessRole, resolvedAppUrl])
async function reprovision() {
if (!store) return
await store.delete("token"); await store.delete("config"); await store.save()
setToken(null); setConfig(null); setStatus(null)
autoLaunchRef.current = false
setToken(null); setConfig(null); setStatus(null); setAccessRole("collaborator")
const p = await invoke<MachineProfile>("collect_machine_profile")
setProfile(p)
}
@ -269,9 +309,12 @@ function App() {
const collaboratorPayload = collabEmail.trim()
? { email: collabEmail.trim(), name: collabName.trim() || undefined }
: undefined
const collaboratorInventory = collaboratorPayload
? { ...collaboratorPayload, role: accessRole }
: undefined
const inventoryPayload: Record<string, unknown> = { ...profile.inventory }
if (collaboratorPayload) {
inventoryPayload.collaborator = collaboratorPayload
if (collaboratorInventory) {
inventoryPayload.collaborator = collaboratorInventory
}
const payload = {
machineToken: token,
@ -296,27 +339,61 @@ function App() {
}
}
async function checkForUpdates() {
async function checkForUpdates(auto = false) {
try {
setUpdating(true)
if (!auto) {
setUpdating(true)
setUpdateInfo({ tone: "info", message: "Procurando por atualizações..." })
}
const { check } = await import("@tauri-apps/plugin-updater")
const update = await check()
if (update && (update as any).available) {
// download and install then relaunch
await (update as any).downloadAndInstall()
const { relaunch } = await import("@tauri-apps/plugin-process")
await relaunch()
} else {
alert("Nenhuma atualização disponível.")
type UpdateResult = {
available?: boolean
version?: string
downloadAndInstall?: () => Promise<void>
}
const update = (await check()) as UpdateResult | null
if (update?.available) {
setUpdateInfo({
tone: "info",
message: `Atualização ${update.version} disponível. Baixando e aplicando...`,
})
if (typeof update.downloadAndInstall === "function") {
await update.downloadAndInstall()
const { relaunch } = await import("@tauri-apps/plugin-process")
await relaunch()
}
} else if (!auto) {
setUpdateInfo({ tone: "info", message: "Nenhuma atualização disponível no momento." })
}
} catch (error) {
console.error("Falha ao verificar atualizações", error)
alert("Falha ao verificar atualizações.")
if (!auto) {
setUpdateInfo({
tone: "error",
message: "Falha ao verificar atualizações. Tente novamente mais tarde.",
})
}
} finally {
setUpdating(false)
if (!auto) setUpdating(false)
}
}
useEffect(() => {
if (import.meta.env.DEV) return
if (autoUpdateRef.current) return
autoUpdateRef.current = true
checkForUpdates(true).catch((err: unknown) => {
console.error("Falha ao executar atualização automática", err)
})
}, [])
useEffect(() => {
if (!token) return
if (autoLaunchRef.current) return
autoLaunchRef.current = true
openSystem()
}, [token, config?.accessRole, openSystem])
return (
<div className="min-h-screen grid place-items-center p-6">
<div className="w-full max-w-[720px] rounded-2xl border border-slate-200 bg-white p-6 shadow-sm">
@ -345,6 +422,20 @@ function App() {
<label className="text-sm font-medium">Empresa (slug opcional)</label>
<input className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm" placeholder="ex.: atlas-engenharia" value={company} onChange={(e)=>setCompany(e.target.value)} />
</div>
<div className="grid gap-2">
<label className="text-sm font-medium">Perfil de acesso</label>
<select
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm"
value={accessRole}
onChange={(e) => setAccessRole((e.target.value as "collaborator" | "manager") ?? "collaborator")}
>
<option value="collaborator">Colaborador (portal)</option>
<option value="manager">Gestor (painel completo)</option>
</select>
<p className="text-xs text-slate-500">
Colaboradores veem apenas seus chamados. Gestores acompanham todos os tickets da empresa.
</p>
</div>
<div className="grid gap-2">
<label className="text-sm font-medium">Colaborador (e-mail)</label>
<input className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm" placeholder="colaborador@empresa.com" value={collabEmail} onChange={(e)=>setCollabEmail(e.target.value)} />
@ -442,9 +533,25 @@ function App() {
</div>
<div className="grid gap-2">
<label className="label">Atualizações</label>
<button onClick={checkForUpdates} disabled={updating} className={cn("btn btn-outline inline-flex items-center gap-2", updating && "opacity-60")}>
<RefreshCw className="size-4" /> Verificar atualizações
<button
onClick={() => checkForUpdates(false)}
disabled={updating}
className={cn("btn btn-outline inline-flex items-center gap-2", updating && "opacity-60")}
>
<RefreshCw className={cn("size-4", updating && "animate-spin")} /> Verificar atualizações
</button>
{updateInfo ? (
<div
className={cn(
"rounded-lg border px-3 py-2 text-sm leading-snug",
updateInfo.tone === "success" && "border-emerald-200 bg-emerald-50 text-emerald-700",
updateInfo.tone === "error" && "border-rose-200 bg-rose-50 text-rose-700",
updateInfo.tone === "info" && "border-slate-200 bg-slate-50 text-slate-700"
)}
>
{updateInfo.message}
</div>
) : null}
</div>
</TabsContent>
</Tabs>
@ -452,7 +559,7 @@ function App() {
)}
</div>
<div className="mt-6 flex justify-center">
<img src={`${appUrl}/rever-8.png`} alt="Logotipo Rever Tecnologia" width={110} height={110} className="h-[3.45rem] w-auto" />
<img src={`${appUrl}/raven.png`} alt="Logotipo Raven" width={110} height={110} className="h-[3.45rem] w-auto" />
</div>
</div>
)