feat: Añade endpoints de asociación y stubs para comandos a EquiposController
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Inventario.API.Data;
|
using Inventario.API.Data;
|
||||||
|
using Inventario.API.DTOs;
|
||||||
using Inventario.API.Helpers;
|
using Inventario.API.Helpers;
|
||||||
using Inventario.API.Models;
|
using Inventario.API.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Data;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
namespace Inventario.API.Controllers
|
namespace Inventario.API.Controllers
|
||||||
{
|
{
|
||||||
@@ -17,19 +21,18 @@ namespace Inventario.API.Controllers
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GET /api/equipos ---
|
// --- MÉTODOS CRUD BÁSICOS (Ya implementados) ---
|
||||||
// Consulta todos los equipos con sus relaciones
|
// GET /api/equipos
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Consultar()
|
public async Task<IActionResult> Consultar()
|
||||||
{
|
{
|
||||||
// Query para traer todo en una sola llamada a la BD
|
|
||||||
var query = @"
|
var query = @"
|
||||||
SELECT
|
SELECT
|
||||||
e.*,
|
e.Id, e.Hostname, e.Ip, e.Mac, e.Motherboard, e.Cpu, e.Ram_installed, e.Ram_slots, e.Os, e.Architecture,
|
||||||
s.Id as SectorId, s.Nombre as SectorNombre,
|
s.Id as Id, s.Nombre,
|
||||||
u.Id as UsuarioId, u.Username, u.Password,
|
u.Id as Id, u.Username, u.Password,
|
||||||
d.Id as DiscoId, d.Mediatype, d.Size,
|
d.Id as Id, d.Mediatype, d.Size,
|
||||||
mr.Id as MemoriaRamId, mr.part_number as PartNumber, mr.Fabricante, mr.Tamano, mr.Velocidad, emr.Slot
|
mr.Id as Id, mr.part_number as PartNumber, mr.Fabricante, mr.Tamano, mr.Velocidad, emr.Slot
|
||||||
FROM dbo.equipos e
|
FROM dbo.equipos e
|
||||||
LEFT JOIN dbo.sectores s ON e.sector_id = s.id
|
LEFT JOIN dbo.sectores s ON e.sector_id = s.id
|
||||||
LEFT JOIN dbo.usuarios_equipos ue ON e.id = ue.equipo_id
|
LEFT JOIN dbo.usuarios_equipos ue ON e.id = ue.equipo_id
|
||||||
@@ -61,9 +64,9 @@ namespace Inventario.API.Controllers
|
|||||||
|
|
||||||
return equipoActual;
|
return equipoActual;
|
||||||
},
|
},
|
||||||
splitOn: "SectorId,UsuarioId,DiscoId,MemoriaRamId"
|
splitOn: "Id,Id,Id,Id" // Dapper divide en cada 'Id'
|
||||||
);
|
);
|
||||||
return Ok(equipoDict.Values);
|
return Ok(equipoDict.Values.OrderBy(e => e.Sector?.Nombre).ThenBy(e => e.Hostname));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,5 +204,115 @@ namespace Inventario.API.Controllers
|
|||||||
return Ok(new { equipo = hostname, historial });
|
return Ok(new { equipo = hostname, historial });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// --- MÉTODOS DE ASOCIACIÓN Y COMANDOS ---
|
||||||
|
|
||||||
|
[HttpPatch("{id_equipo}/sector/{id_sector}")]
|
||||||
|
public async Task<IActionResult> AsociarSector(int id_equipo, int id_sector)
|
||||||
|
{
|
||||||
|
var query = "UPDATE dbo.equipos SET sector_id = @IdSector WHERE Id = @IdEquipo;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { IdSector = id_sector, IdEquipo = id_equipo });
|
||||||
|
if (filasAfectadas == 0) return NotFound("Equipo o sector no encontrado.");
|
||||||
|
return Ok(new { success = true }); // Devolvemos una respuesta simple de éxito
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{hostname}/asociarusuario")]
|
||||||
|
public async Task<IActionResult> AsociarUsuario(string hostname, [FromBody] AsociacionUsuarioDto dto)
|
||||||
|
{
|
||||||
|
var query = @"
|
||||||
|
INSERT INTO dbo.usuarios_equipos (equipo_id, usuario_id)
|
||||||
|
SELECT e.id, u.id
|
||||||
|
FROM dbo.equipos e, dbo.usuarios u
|
||||||
|
WHERE e.Hostname = @Hostname AND u.Username = @Username;";
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { Hostname = hostname, dto.Username });
|
||||||
|
if (filasAfectadas == 0) return NotFound("Equipo o usuario no encontrado.");
|
||||||
|
return Ok(new { success = true });
|
||||||
|
}
|
||||||
|
catch (SqlException ex) when (ex.Number == 2627) // Error de clave primaria duplicada
|
||||||
|
{
|
||||||
|
return Conflict("El usuario ya está asociado a este equipo.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{hostname}/usuarios/{username}")]
|
||||||
|
public async Task<IActionResult> DesasociarUsuario(string hostname, string username)
|
||||||
|
{
|
||||||
|
var query = @"
|
||||||
|
DELETE FROM dbo.usuarios_equipos
|
||||||
|
WHERE equipo_id = (SELECT id FROM dbo.equipos WHERE Hostname = @Hostname)
|
||||||
|
AND usuario_id = (SELECT id FROM dbo.usuarios WHERE Username = @Username);";
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { Hostname = hostname, Username = username });
|
||||||
|
if (filasAfectadas == 0) return NotFound("Asociación no encontrada.");
|
||||||
|
return Ok(new { success = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{hostname}/asociardiscos")]
|
||||||
|
public async Task<IActionResult> AsociarDiscos(string hostname, [FromBody] List<Disco> discos)
|
||||||
|
{
|
||||||
|
// La lógica aquí es compleja, la implementaremos en un paso posterior si es necesaria.
|
||||||
|
// Por ahora, devolvemos un endpoint funcional pero que no hace nada.
|
||||||
|
Console.WriteLine($"Recibida solicitud para asociar {discos.Count} discos al equipo {hostname}");
|
||||||
|
await Task.CompletedTask; // Simula trabajo asíncrono
|
||||||
|
return Ok(new { message = "Endpoint de asociación de discos recibido, lógica pendiente." });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{hostname}/ram")]
|
||||||
|
public async Task<IActionResult> AsociarRam(string hostname, [FromBody] List<MemoriaRamDetalle> memorias)
|
||||||
|
{
|
||||||
|
// Lógica compleja, pendiente de implementación.
|
||||||
|
Console.WriteLine($"Recibida solicitud para asociar {memorias.Count} módulos de RAM al equipo {hostname}");
|
||||||
|
await Task.CompletedTask;
|
||||||
|
return Ok(new { message = "Endpoint de asociación de RAM recibido, lógica pendiente." });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("ping")]
|
||||||
|
public async Task<IActionResult> EnviarPing([FromBody] PingRequestDto request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Ip))
|
||||||
|
return BadRequest("La dirección IP es requerida.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var ping = new Ping())
|
||||||
|
{
|
||||||
|
var reply = await ping.SendPingAsync(request.Ip, 2000); // Timeout de 2 segundos
|
||||||
|
return Ok(new { isAlive = reply.Status == IPStatus.Success, latency = reply.RoundtripTime });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (PingException ex)
|
||||||
|
{
|
||||||
|
// Maneja errores comunes como "Host desconocido"
|
||||||
|
return Ok(new { isAlive = false, error = ex.Message });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error interno al hacer ping: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WOL (Wake-On-LAN) es más complejo porque requiere ejecutar comandos de sistema operativo.
|
||||||
|
// Lo dejamos pendiente para no añadir complejidad de configuración de SSH por ahora.
|
||||||
|
[HttpPost("wake-on-lan")]
|
||||||
|
public IActionResult EnviarWol([FromBody] WolRequestDto request)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Recibida solicitud WOL para MAC: {request.Mac}");
|
||||||
|
return Ok(new { message = "Solicitud WOL recibida. La ejecución del comando está pendiente de implementación." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTOs locales para las peticiones
|
||||||
|
public class PingRequestDto { public string? Ip { get; set; } }
|
||||||
|
public class WolRequestDto { public string? Mac { get; set; } }
|
||||||
}
|
}
|
||||||
8
backend/DTOs/AsociacionUsuarioDto.cs
Normal file
8
backend/DTOs/AsociacionUsuarioDto.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Inventario.API.DTOs
|
||||||
|
{
|
||||||
|
public class AsociacionUsuarioDto
|
||||||
|
{
|
||||||
|
// Usamos 'required' para asegurar que este campo nunca sea nulo al recibirlo.
|
||||||
|
public required string Username { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Inventario.API")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Inventario.API")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+10f2f2ba67eac900e8a7b63ced2f3689c309fa6f")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+80210e5d4c8c41b94acb737e8a8d9935a2ef21b6")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Inventario.API")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Inventario.API")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Inventario.API")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Inventario.API")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
Reference in New Issue
Block a user