se agregaron nuevas caracteristicas

This commit is contained in:
Carlos Tello
2026-08-13 23:07:01 -03:00
parent 1bfb808c79
commit d736db982e
23 changed files with 1244 additions and 100 deletions
+135 -3
View File
@@ -8,7 +8,9 @@ import {
Trash2,
Laptop,
Server as ServerIcon,
X
X,
Edit2,
RefreshCw
} from 'lucide-react';
import { ClientItem, api } from '../services/api';
@@ -19,6 +21,10 @@ interface ClientsViewProps {
export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh }) => {
const [showModal, setShowModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [editingClient, setEditingClient] = useState<ClientItem | null>(null);
const [editAlias, setEditAlias] = useState('');
const [editQuotaGb, setEditQuotaGb] = useState<number>(100);
const [clientHint, setClientHint] = useState('');
const [generatedCode, setGeneratedCode] = useState<{ code: string; expires_at: string } | null>(null);
const [isCopied, setIsCopied] = useState(false);
@@ -67,6 +73,47 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
}
};
const handleReRegister = async (client: ClientItem) => {
if (confirm(`¿Estás seguro de que deseas volver a generar el código de vinculación para el cliente "${client.name}"?\n\nEsto revocará de inmediato los tokens de acceso actuales del equipo.`)) {
setLoading(true);
try {
const codeData = await api.generateReRegisterCode(client.id);
setGeneratedCode(codeData);
setShowModal(true);
} catch (err: any) {
alert(`Error al generar código de re-vinculación: ${err.message}`);
} finally {
setLoading(false);
}
}
};
const handleOpenEditModal = (client: ClientItem) => {
setEditingClient(client);
setEditAlias(client.alias || '');
setEditQuotaGb(Math.round(client.storage_quota_bytes / (1024 * 1024 * 1024)));
setShowEditModal(true);
};
const handleSaveEdit = async (e: React.FormEvent) => {
e.preventDefault();
if (!editingClient) return;
setLoading(true);
try {
const quotaBytes = editQuotaGb * 1024 * 1024 * 1024;
await api.updateClient(editingClient.id, {
alias: editAlias.trim(),
storage_quota_bytes: quotaBytes
});
setShowEditModal(false);
onRefresh();
} catch (err: any) {
alert(`Error al actualizar cliente: ${err.message}`);
} finally {
setLoading(false);
}
};
const serverUrl = window.location.origin;
const psCommand = generatedCode
? `python agent_cli.py register --server "${serverUrl}" --code "${generatedCode.code}"`
@@ -100,6 +147,7 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
<tr>
<th>Cliente ID</th>
<th>Nombre / Hostname</th>
<th>Ubicación / Alias</th>
<th>Sistema Operativo</th>
<th>Dirección IP</th>
<th>Estado</th>
@@ -133,6 +181,9 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
</div>
</div>
</td>
<td style={{ fontSize: '0.84rem', color: 'var(--accent-cyan)', fontWeight: 600 }}>
{client.alias || '—'}
</td>
<td style={{ fontSize: '0.84rem', color: 'var(--text-muted)' }}>
{client.os_info || 'Windows'}
</td>
@@ -145,7 +196,12 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
</span>
</td>
<td style={{ fontFamily: 'var(--font-mono)', fontSize: '0.84rem' }}>
{formatBytes(client.storage_used_bytes)}
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div>{formatBytes(client.storage_used_bytes)}</div>
<div style={{ fontSize: '0.74rem', color: 'var(--text-dim)' }}>
de {formatBytes(client.storage_quota_bytes)}
</div>
</div>
</td>
<td style={{ fontSize: '0.82rem', color: 'var(--text-dim)' }}>
{client.last_seen_at
@@ -154,6 +210,14 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
</td>
<td style={{ textAlign: 'right' }}>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px' }}>
<button
className="btn btn-secondary"
style={{ padding: '6px 10px', fontSize: '0.78rem' }}
onClick={() => handleOpenEditModal(client)}
title="Editar Cliente (Alias / Cuota)"
>
<Edit2 size={14} />
</button>
<button
className="btn btn-secondary"
style={{ padding: '6px 10px', fontSize: '0.78rem' }}
@@ -163,6 +227,15 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
<ShieldAlert size={14} color="var(--accent-amber)" />
Revocar
</button>
<button
className="btn btn-secondary"
style={{ padding: '6px 10px', fontSize: '0.78rem' }}
onClick={() => handleReRegister(client)}
title="Re-vincular Agente (Generar nuevo código)"
>
<RefreshCw size={14} color="var(--accent-cyan)" />
Re-vincular
</button>
<button
className="btn btn-danger"
style={{ padding: '6px 10px', fontSize: '0.78rem' }}
@@ -178,7 +251,7 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
})}
{clients.length === 0 && (
<tr>
<td colSpan={8} style={{ textAlign: 'center', padding: '40px', color: 'var(--text-dim)' }}>
<td colSpan={9} style={{ textAlign: 'center', padding: '40px', color: 'var(--text-dim)' }}>
No hay clientes Windows registrados. Haz clic en "Registrar Nuevo Cliente" para comenzar.
</td>
</tr>
@@ -283,6 +356,65 @@ export const ClientsView: React.FC<ClientsViewProps> = ({ clients, onRefresh })
</div>
</div>
)}
{/* Edit Client Modal */}
{showEditModal && editingClient && (
<div className="modal-backdrop">
<div className="modal-card">
<div className="modal-header">
<h3 style={{ fontSize: '1.1rem', fontWeight: 700 }}>Editar Cliente {editingClient.client_code}</h3>
<button
style={{ background: 'transparent', border: 'none', color: 'var(--text-muted)', cursor: 'pointer' }}
onClick={() => setShowEditModal(false)}
>
<X size={20} />
</button>
</div>
<form onSubmit={handleSaveEdit}>
<div className="form-group" style={{ marginBottom: '12px' }}>
<label>Nombre del Equipo (Solo lectura):</label>
<input
type="text"
className="form-input"
disabled
value={editingClient.name}
/>
</div>
<div className="form-group" style={{ marginBottom: '12px' }}>
<label>Alias / Ubicación (ej. "CLUB REGATAS"):</label>
<input
type="text"
className="form-input"
placeholder="Sin ubicación"
value={editAlias}
onChange={(e) => setEditAlias(e.target.value)}
/>
</div>
<div className="form-group" style={{ marginBottom: '12px' }}>
<label>Cuota de Almacenamiento Asignada (GB):</label>
<input
type="number"
min={1}
className="form-input"
value={editQuotaGb}
onChange={(e) => setEditQuotaGb(Number(e.target.value))}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '24px' }}>
<button type="button" className="btn btn-secondary" onClick={() => setShowEditModal(false)}>
Cancelar
</button>
<button type="submit" className="btn btn-primary" disabled={loading}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
};