feat: dispositivos e ajustes de csat e relatórios

This commit is contained in:
codex-bot 2025-11-03 19:29:50 -03:00
parent 25d2a9b062
commit e0ef66555d
86 changed files with 5811 additions and 992 deletions

View file

@ -1,11 +1,11 @@
# Sistema de Chamados — App Desktop (Tauri)
Cliente desktop (Tauri v2 + Vite) que:
- Coleta perfil/métricas da máquina via comandos Rust.
- Registra a máquina com um código de provisionamento.
- Coleta perfil/métricas da dispositivo via comandos Rust.
- Registra a dispositivo com um código de provisionamento.
- Envia heartbeat periódico ao backend (`/api/machines/heartbeat`).
- Redireciona para a UI web do sistema após provisionamento.
- Armazena o token da máquina com segurança no cofre do SO (Keyring).
- Armazena o token da dispositivo com segurança no cofre do SO (Keyring).
- Exibe abas de Resumo, Inventário, Diagnóstico e Configurações; permite “Enviar inventário agora”.
## URLs e ambiente
@ -65,7 +65,7 @@ pnpm -C apps/desktop tauri build --bundles nsis
Consulte https://tauri.app/start/prerequisites/
## Fluxo (resumo)
1) Ao abrir, o app coleta o perfil da máquina e exibe um resumo.
1) Ao abrir, o app coleta o perfil da dispositivo e exibe um resumo.
2) Informe o “código de provisionamento” (chave definida no servidor) e confirme.
3) O servidor retorna um `machineToken`; o app salva e inicia o heartbeat.
4) O app abre `APP_URL/machines/handshake?token=...` no WebView para autenticar a sessão na UI.

View file

@ -159,7 +159,7 @@
## 5. Gerar chaves do updater Tauri
1. Em qualquer máquina com Node/pnpm (pode ser seu computador local):
1. Em qualquer dispositivo com Node/pnpm (pode ser seu computador local):
```bash
pnpm install
pnpm --filter appsdesktop tauri signer generate
@ -267,10 +267,10 @@
.\svc start
```
6. Confirme no GitHub que o runner aparece como `online`.
7. Mantenha a máquina ligada e conectada durante o período em que o workflow precisa rodar:
7. Mantenha a dispositivo ligada e conectada durante o período em que o workflow precisa rodar:
- Para releases desktop, o runner só precisa estar ligado enquanto o job `desktop_release` estiver em execução (crie a tag e aguarde o workflow terminar).
- Após a conclusão, você pode desligar o computador até a próxima release.
8. Observação importante: o runner Windows pode ser sua máquina pessoal. Garanta apenas que:
8. Observação importante: o runner Windows pode ser sua dispositivo pessoal. Garanta apenas que:
- Você confia no código que será executado (o runner processa os jobs do repositório).
- O serviço do runner esteja ativo enquanto o workflow rodar (caso desligue o PC, as releases ficam na fila).
- Há espaço em disco suficiente e nenhuma política corporativa bloqueando a instalação dos pré-requisitos.
@ -429,7 +429,7 @@
- Garanta que o certificado TLS usado pelo Nginx é renovado (p. ex. `certbot renew`).
4. Manter runners:
- VPS: monitore serviço `actions.runner.*`. Reinicie se necessário (`sudo ./svc.sh restart`).
- Windows: mantenha máquina ligada e atualizada. Se o serviço parar, abra `services.msc``GitHub Actions Runner` → Start.
- Windows: mantenha dispositivo ligada e atualizada. Se o serviço parar, abra `services.msc``GitHub Actions Runner` → Start.
---
@ -451,7 +451,7 @@
| Job `desktop_release` falha na etapa `tauri-action` | Toolchain incompleto no Windows | Reinstale Rust, WebView2 e componentes C++ do Visual Studio. |
| Artefatos não chegam à VPS | Caminho incorreto ou chave SSH inválida | Verifique `VPS_HOST`, `VPS_USER`, `VPS_SSH_KEY` e se a pasta `/var/www/updates` existe. |
| App não encontra update | URL ou chave pública divergente no `tauri.conf.json` | Confirme que `endpoints` bate com o domínio HTTPS e que `pubkey` é exatamente a chave pública gerada. |
| Runner aparece offline no GitHub | Serviço parado ou máquina desligada | VPS: `sudo ./svc.sh status`; Windows: abra `Services` e reinicie o `GitHub Actions Runner`. |
| Runner aparece offline no GitHub | Serviço parado ou dispositivo desligada | VPS: `sudo ./svc.sh status`; Windows: abra `Services` e reinicie o `GitHub Actions Runner`. |
---

View file

@ -13,7 +13,7 @@ use tokio::sync::Notify;
#[derive(thiserror::Error, Debug)]
pub enum AgentError {
#[error("Falha ao obter hostname da máquina")]
#[error("Falha ao obter hostname da dispositivo")]
Hostname,
#[error("Nenhum identificador de hardware disponível (MAC/serial)")]
MissingIdentifiers,

View file

@ -8,9 +8,9 @@ export function DeactivationScreen({ companyName }: { companyName?: string | nul
<span className="inline-flex items-center gap-2 rounded-full border border-rose-200 bg-rose-50 px-3 py-1 text-xs font-semibold text-rose-700">
<ShieldAlert className="size-4" /> Acesso bloqueado
</span>
<h1 className="text-2xl font-semibold text-neutral-900">Máquina desativada</h1>
<h1 className="text-2xl font-semibold text-neutral-900">Dispositivo desativada</h1>
<p className="max-w-md text-sm text-neutral-600">
Esta máquina foi desativada temporariamente pelos administradores. Enquanto isso, o acesso ao portal e o
Esta dispositivo foi desativada temporariamente pelos administradores. Enquanto isso, o acesso ao portal e o
envio de informações ficam indisponíveis.
</p>
{companyName ? (

View file

@ -273,9 +273,9 @@ function App() {
const text = await res.text()
const msg = text.toLowerCase()
const isInvalid =
msg.includes("token de máquina inválido") ||
msg.includes("token de máquina revogado") ||
msg.includes("token de máquina expirado")
msg.includes("token de dispositivo inválido") ||
msg.includes("token de dispositivo revogado") ||
msg.includes("token de dispositivo expirado")
if (isInvalid) {
try {
await store.delete("token"); await store.delete("config"); await store.save()
@ -293,7 +293,7 @@ function App() {
} catch {}
} else {
// Não limpa token em falhas genéricas (ex.: rede); apenas informa
setError("Falha ao validar sessão da máquina. Tente novamente.")
setError("Falha ao validar sessão da dispositivo. Tente novamente.")
tokenVerifiedRef.current = true
setTokenValidationTick((tick) => tick + 1)
}
@ -443,12 +443,12 @@ function App() {
return
}
if (!validatedCompany) {
setError("Valide o código de provisionamento antes de registrar a máquina.")
setError("Valide o código de provisionamento antes de registrar a dispositivo.")
return
}
const normalizedEmail = collabEmail.trim().toLowerCase()
if (!normalizedEmail) {
setError("Informe o e-mail do colaborador vinculado a esta máquina.")
setError("Informe o e-mail do colaborador vinculado a esta dispositivo.")
return
}
if (!emailRegex.current.test(normalizedEmail)) {
@ -575,7 +575,7 @@ function App() {
setError(null)
}
if (!currentActive) {
setError("Esta máquina está desativada. Entre em contato com o suporte da Rever para reativar o acesso.")
setError("Esta dispositivo está desativada. Entre em contato com o suporte da Rever para reativar o acesso.")
setIsLaunchingSystem(false)
return
}
@ -590,7 +590,7 @@ function App() {
: ""
setIsMachineActive(false)
setIsLaunchingSystem(false)
setError(message.length > 0 ? message : "Esta máquina está desativada. Entre em contato com o suporte da Rever.")
setError(message.length > 0 ? message : "Esta dispositivo está desativada. Entre em contato com o suporte da Rever.")
return
}
// Se sessão falhar, tenta identificar token inválido/expirado
@ -604,9 +604,9 @@ function App() {
const text = await hb.text()
const low = text.toLowerCase()
const invalid =
low.includes("token de máquina inválido") ||
low.includes("token de máquina revogado") ||
low.includes("token de máquina expirado")
low.includes("token de dispositivo inválido") ||
low.includes("token de dispositivo revogado") ||
low.includes("token de dispositivo expirado")
if (invalid) {
// Força onboarding
await store?.delete("token"); await store?.delete("config"); await store?.save()
@ -616,7 +616,7 @@ function App() {
setConfig(null)
setStatus(null)
setIsMachineActive(true)
setError("Sessão expirada. Reprovisione a máquina para continuar.")
setError("Sessão expirada. Reprovisione a dispositivo para continuar.")
setIsLaunchingSystem(false)
const p = await invoke<MachineProfile>("collect_machine_profile")
setProfile(p)
@ -787,7 +787,7 @@ function App() {
{error ? <p className="mt-3 rounded-md bg-rose-50 p-2 text-sm text-rose-700">{error}</p> : null}
{!token ? (
<div className="mt-4 space-y-3">
<p className="text-sm text-slate-600">Informe os dados para registrar esta máquina.</p>
<p className="text-sm text-slate-600">Informe os dados para registrar esta dispositivo.</p>
<div className="grid gap-2">
<label className="text-sm font-medium">Código de provisionamento</label>
<div className="relative">
@ -822,7 +822,7 @@ function App() {
</p>
) : (
<p className="text-xs text-slate-500">
Informe o código único fornecido pela equipe para vincular esta máquina a uma empresa.
Informe o código único fornecido pela equipe para vincular esta dispositivo a uma empresa.
</p>
)}
</div>
@ -832,7 +832,7 @@ function App() {
<div className="space-y-1">
<span className="block text-sm font-semibold text-emerald-800">{validatedCompany.name}</span>
<span className="text-xs text-emerald-700/80">
Código reconhecido. Esta máquina será vinculada automaticamente à empresa informada.
Código reconhecido. Esta dispositivo será vinculada automaticamente à empresa informada.
</span>
</div>
</div>
@ -884,7 +884,7 @@ function App() {
</div>
) : null}
<div className="mt-2 flex gap-2">
<button disabled={busy || !validatedCompany || !isEmailValid || !collabName.trim() || provisioningCode.trim().length < 32} onClick={register} className="rounded-lg border border-black bg-black px-3 py-2 text-sm font-semibold text-white hover:bg-black/90 disabled:opacity-60">Registrar máquina</button>
<button disabled={busy || !validatedCompany || !isEmailValid || !collabName.trim() || provisioningCode.trim().length < 32} onClick={register} className="rounded-lg border border-black bg-black px-3 py-2 text-sm font-semibold text-white hover:bg-black/90 disabled:opacity-60">Registrar dispositivo</button>
</div>
</div>
) : (