se agregaron nuevas caracteristicas
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user