Improve USB policy responsiveness and reliability

- Reduce USB policy polling from 60s to 15s for faster response
- Add retry with exponential backoff (2s, 4s, 8s) on report failures
- Add APPLYING state for real-time progress bar feedback
- Check if policy is already applied locally before re-applying
- Fix API schema to accept APPLYING status
- Update agent to v0.1.9

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-06 17:51:57 -03:00
parent 23e7cf58ae
commit b60255fe03
6 changed files with 115 additions and 14 deletions

View file

@ -1215,11 +1215,9 @@ async fn check_and_apply_usb_policy(base_url: &str, token: &str) {
} }
}; };
eprintln!("[agent] Aplicando politica USB: {}", policy_str);
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
use crate::usb_control::{apply_usb_policy, UsbPolicy}; use crate::usb_control::{apply_usb_policy, get_current_policy, UsbPolicy};
let policy = match UsbPolicy::from_str(&policy_str) { let policy = match UsbPolicy::from_str(&policy_str) {
Some(p) => p, Some(p) => p,
@ -1230,6 +1228,26 @@ async fn check_and_apply_usb_policy(base_url: &str, token: &str) {
} }
}; };
// Verifica se a politica ja esta aplicada localmente
match get_current_policy() {
Ok(current) if current == policy => {
eprintln!("[agent] Politica USB ja esta aplicada localmente: {}", policy_str);
report_usb_policy_status(base_url, token, "APPLIED", None, Some(policy_str)).await;
return;
}
Ok(current) => {
eprintln!("[agent] Politica atual: {:?}, esperada: {:?}", current, policy);
}
Err(e) => {
eprintln!("[agent] Nao foi possivel ler politica atual: {e}");
}
}
eprintln!("[agent] Aplicando politica USB: {}", policy_str);
// Reporta APPLYING para progress bar real no frontend
report_usb_policy_status(base_url, token, "APPLYING", None, None).await;
match apply_usb_policy(policy) { match apply_usb_policy(policy) {
Ok(result) => { Ok(result) => {
eprintln!("[agent] Politica USB aplicada com sucesso: {:?}", result); eprintln!("[agent] Politica USB aplicada com sucesso: {:?}", result);
@ -1265,8 +1283,38 @@ async fn report_usb_policy_status(
current_policy, current_policy,
}; };
if let Err(e) = HTTP_CLIENT.post(&url).json(&report).send().await { // Retry com backoff exponencial: 2s, 4s, 8s
eprintln!("[agent] Falha ao reportar status de politica USB: {e}"); let delays = [2, 4, 8];
let mut last_error = None;
for (attempt, delay_secs) in delays.iter().enumerate() {
match HTTP_CLIENT.post(&url).json(&report).send().await {
Ok(response) if response.status().is_success() => {
if attempt > 0 {
eprintln!("[agent] Report de politica USB enviado na tentativa {}", attempt + 1);
}
return;
}
Ok(response) => {
last_error = Some(format!("HTTP {}", response.status()));
}
Err(e) => {
last_error = Some(e.to_string());
}
}
if attempt < delays.len() - 1 {
eprintln!(
"[agent] Falha ao reportar politica USB (tentativa {}), retentando em {}s...",
attempt + 1,
delay_secs
);
tokio::time::sleep(Duration::from_secs(*delay_secs)).await;
}
}
if let Some(err) = last_error {
eprintln!("[agent] Falha ao reportar status de politica USB apos 3 tentativas: {err}");
} }
} }
@ -1344,7 +1392,7 @@ impl AgentRuntime {
let mut heartbeat_ticker = tokio::time::interval(Duration::from_secs(interval)); let mut heartbeat_ticker = tokio::time::interval(Duration::from_secs(interval));
heartbeat_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); heartbeat_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
let mut usb_ticker = tokio::time::interval(Duration::from_secs(60)); let mut usb_ticker = tokio::time::interval(Duration::from_secs(15));
usb_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); usb_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop { loop {

View file

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Raven", "productName": "Raven",
"version": "0.1.6", "version": "0.1.9",
"identifier": "br.com.esdrasrenan.sistemadechamados", "identifier": "br.com.esdrasrenan.sistemadechamados",
"build": { "build": {
"beforeDevCommand": "bun run dev", "beforeDevCommand": "bun run dev",

View file

@ -18,7 +18,7 @@ function hashToken(token: string) {
export const USB_POLICY_VALUES = ["ALLOW", "BLOCK_ALL", "READONLY"] as const export const USB_POLICY_VALUES = ["ALLOW", "BLOCK_ALL", "READONLY"] as const
export type UsbPolicyValue = (typeof USB_POLICY_VALUES)[number] export type UsbPolicyValue = (typeof USB_POLICY_VALUES)[number]
export const USB_POLICY_STATUS = ["PENDING", "APPLIED", "FAILED"] as const export const USB_POLICY_STATUS = ["PENDING", "APPLYING", "APPLIED", "FAILED"] as const
export type UsbPolicyStatus = (typeof USB_POLICY_STATUS)[number] export type UsbPolicyStatus = (typeof USB_POLICY_STATUS)[number]
export const setUsbPolicy = mutation({ export const setUsbPolicy = mutation({

View file

@ -10,7 +10,7 @@ const getPolicySchema = z.object({
const reportStatusSchema = z.object({ const reportStatusSchema = z.object({
machineToken: z.string().min(1), machineToken: z.string().min(1),
status: z.enum(["PENDING", "APPLIED", "FAILED"]), status: z.enum(["PENDING", "APPLYING", "APPLIED", "FAILED"]),
error: z.string().optional(), error: z.string().optional(),
currentPolicy: z.string().optional(), currentPolicy: z.string().optional(),
}) })

View file

@ -4622,7 +4622,7 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
return ( return (
<li key={`gpu-${idx}`}> <li key={`gpu-${idx}`}>
<span className="font-medium text-foreground">{name ?? "Adaptador de vídeo"}</span> <span className="font-medium text-foreground">{name ?? "Adaptador de vídeo"}</span>
{memoryBytes ? <span className="ml-1 text-muted-foreground">VRAM {formatBytes(memoryBytes)}</span> : null} {memoryBytes ? <span className="ml-1 text-muted-foreground">{formatBytes(memoryBytes)} VRAM</span> : null}
{vendor ? <span className="ml-1 text-muted-foreground">· {vendor}</span> : null} {vendor ? <span className="ml-1 text-muted-foreground">· {vendor}</span> : null}
{driver ? <span className="ml-1 text-muted-foreground">· Driver {driver}</span> : null} {driver ? <span className="ml-1 text-muted-foreground">· Driver {driver}</span> : null}
</li> </li>

View file

@ -73,6 +73,13 @@ function getStatusBadge(status: string | undefined | null) {
Pendente Pendente
</Badge> </Badge>
) )
case "APPLYING":
return (
<Badge variant="outline" className="gap-1 border-blue-200 bg-blue-50 text-blue-700">
<Loader2 className="size-3 animate-spin" />
Aplicando
</Badge>
)
case "APPLIED": case "APPLIED":
return ( return (
<Badge variant="outline" className="gap-1 border-emerald-200 bg-emerald-50 text-emerald-700"> <Badge variant="outline" className="gap-1 border-emerald-200 bg-emerald-50 text-emerald-700">
@ -92,6 +99,36 @@ function getStatusBadge(status: string | undefined | null) {
} }
} }
function getProgressValue(status: string | undefined | null): number {
switch (status) {
case "PENDING":
return 33
case "APPLYING":
return 66
case "APPLIED":
return 100
case "FAILED":
return 100
default:
return 0
}
}
function getProgressColor(status: string | undefined | null): string {
switch (status) {
case "PENDING":
return "bg-amber-500"
case "APPLYING":
return "bg-blue-500"
case "APPLIED":
return "bg-emerald-500"
case "FAILED":
return "bg-red-500"
default:
return "bg-neutral-300"
}
}
interface UsbPolicyControlProps { interface UsbPolicyControlProps {
machineId: string machineId: string
machineName?: string machineName?: string
@ -168,11 +205,27 @@ export function UsbPolicyControl({
const content = ( const content = (
<div className="space-y-4"> <div className="space-y-4">
{/* Status atual no topo */} {/* Status atual com progress bar real */}
{usbPolicy?.status && ( {usbPolicy?.status && (
<div className="flex items-center justify-between rounded-lg border bg-slate-50 p-3"> <div className="space-y-2 rounded-lg border bg-slate-50 p-3">
<span className="text-sm font-medium text-slate-600">Status da política</span> <div className="flex items-center justify-between">
{getStatusBadge(usbPolicy.status)} <span className="text-sm font-medium text-slate-600">Status da política</span>
{getStatusBadge(usbPolicy.status)}
</div>
{/* Progress bar real baseada no estado */}
{(usbPolicy.status === "PENDING" || usbPolicy.status === "APPLYING") && (
<div className="space-y-1">
<div className="h-2 w-full overflow-hidden rounded-full bg-slate-200">
<div
className={`h-full transition-all duration-500 ease-out ${getProgressColor(usbPolicy.status)}`}
style={{ width: `${getProgressValue(usbPolicy.status)}%` }}
/>
</div>
<p className="text-xs text-muted-foreground">
{usbPolicy.status === "PENDING" ? "Aguardando agente..." : "Agente aplicando política..."}
</p>
</div>
)}
</div> </div>
)} )}