Files
soft_usite/rest/controllers/servidor_controller.go
T
2026-05-21 22:37:31 -05:00

373 lines
10 KiB
Go
Executable File

package controllers
import (
"fmt"
"math"
"net"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
// RENDER
func Servidor(c *fiber.Ctx) error {
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
if err := c.Render("servidor", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
// ServidorDashboard renders the server dashboard view
func ServidorDashboard(c *fiber.Ctx) error {
data := fiber.Map{
"user": c.Locals("user").(map[string]interface{}),
"modules": c.Locals("userModules"),
}
if err := c.Render("servidor_dashboard", data, "layouts/main"); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error rendering template",
"error": err.Error(),
})
}
return nil
}
func GetServidor(c *fiber.Ctx) error {
pageStr := c.Query("page", "1")
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
searchQuery := c.Query("search", "")
limitStr := c.Query("limit", "10")
limit, _ := strconv.Atoi(limitStr)
offset := (page - 1) * limit
records, total, err := models.GetAllServidores(limit, offset, searchQuery)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving records",
"error": err.Error(),
})
}
// Build connection count map per server
type conxCount struct {
ServidorID uint `gorm:"column:servidor_id"`
Count int64 `gorm:"column:count"`
}
var counts []conxCount
serverIDs := make([]uint, len(records))
for i, s := range records {
serverIDs[i] = s.ID
}
if len(serverIDs) > 0 {
app.Http.Database.DB.Table("conx_db").
Select("servidor_id, count(*) as count").
Where("servidor_id IN ? AND deleted_at IS NULL", serverIDs).
Group("servidor_id").
Scan(&counts)
}
countMap := make(map[uint]int64)
for _, cc := range counts {
countMap[cc.ServidorID] = cc.Count
}
// Attach conx_count to each record
type servidorWithCount struct {
models.Servidor
ConxCount int64 `json:"conx_count"`
}
enriched := make([]servidorWithCount, len(records))
for i, s := range records {
enriched[i] = servidorWithCount{Servidor: s, ConxCount: countMap[s.ID]}
}
totalPages := int(math.Ceil(float64(total) / float64(limit)))
return c.JSON(fiber.Map{
"registros": enriched,
"total": total,
"totalPages": totalPages,
"page": page,
"limit": limit,
})
}
func GetServidorSelect(c *fiber.Ctx) error {
records, err := models.GetAllServidoresSelect()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error retrieving records",
"error": err.Error(),
})
}
return c.JSON(fiber.Map{
"registros": records,
})
}
func CreateServidor(c *fiber.Ctx) error {
var m models.Servidor
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error parsing request body",
"error": err.Error(),
})
}
if err := models.CreateServidor(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error creating servidor",
"error": err.Error(),
})
}
return c.Status(fiber.StatusCreated).JSON(m)
}
func UpdateServidor(c *fiber.Ctx) error {
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": err.Error(),
})
}
var m models.Servidor
if err := c.BodyParser(&m); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Error al parsear el cuerpo de la solicitud",
"error": err.Error(),
})
}
m.ID = uint(uid)
// Cargar el registro actual para detectar cambios que deben propagarse a Hostinger
var actual models.Servidor
app.Http.Database.DB.First(&actual, uid)
nombreAnterior := actual.Nombre
if err := models.UpdateServidor(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al actualizar el servidor",
"error": err.Error(),
})
}
// Sincronizar hostname en Hostinger si está vinculado y el nombre cambió
if actual.HostingerVpsID != nil && m.Nombre != "" && m.Nombre != nombreAnterior {
if client, hErr := hostingerClient(); hErr == nil {
_ = client.SetVPSHostname(*actual.HostingerVpsID, m.Nombre)
}
}
return c.Status(fiber.StatusOK).JSON(m)
}
// SyncServidorFromHostinger sincroniza los datos del VPS de Hostinger hacia el servidor local.
// Actualiza: ip_servidor, ram, nucleos, disco, hostinger_state, vencimiento (si hay suscripción).
// La verdad de las specs físicas siempre viene de Hostinger.
func SyncServidorFromHostinger(c *fiber.Ctx) error {
id := c.Params("id")
db := app.Http.Database.DB
var servidor models.Servidor
if err := db.First(&servidor, id).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "servidor no encontrado"})
}
if servidor.HostingerVpsID == nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "el servidor no tiene hostinger_vps_id configurado"})
}
client, err := hostingerClient()
if err != nil {
return c.Status(fiber.StatusFailedDependency).JSON(fiber.Map{"error": "No se encontró configuración activa de Hostinger"})
}
vps, err := client.GetVPSByID(*servidor.HostingerVpsID)
if err != nil {
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{"error": err.Error()})
}
updates := map[string]interface{}{
"hostinger_state": vps.State,
}
// IP principal (primer IPv4)
if len(vps.IPV4) > 0 && vps.IPV4[0].Address != "" {
updates["ip_servidor"] = vps.IPV4[0].Address
}
// RAM: Hostinger devuelve en MB → convertir a GB
if vps.RAMBytes > 0 {
gbRam := float64(vps.RAMBytes) / 1024.0
updates["ram"] = fmt.Sprintf("%.0f GB", gbRam)
}
// CPU cores
if vps.CPU > 0 {
updates["nucleos"] = fmt.Sprintf("%d", vps.CPU)
}
// Disco: Hostinger devuelve en GB directamente
if vps.DiskBytes > 0 {
updates["disco"] = fmt.Sprintf("%d GB", vps.DiskBytes)
}
// Vencimiento: buscar suscripción cuyo nombre contenga el hostname del VPS
if subs, sErr := client.GetOrders(); sErr == nil {
for _, sub := range subs {
if sub.ExpiresAt != "" && len(sub.ExpiresAt) >= 10 &&
(strings.Contains(strings.ToLower(sub.Name), strings.ToLower(vps.Hostname)) ||
strings.Contains(strings.ToLower(sub.Name), "vps")) {
updates["vencimiento"] = sub.ExpiresAt[:10]
break
}
}
}
if err := db.Model(&servidor).Updates(updates).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo guardar"})
}
db.First(&servidor, id)
return c.JSON(fiber.Map{"ok": true, "servidor": servidor})
}
func DeleteServidor(c *fiber.Ctx) error {
uid, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "ID inválido",
"error": err.Error(),
})
}
var m models.Servidor
m.ID = uint(uid)
if err := models.DeleteServidor(m); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al eliminar el servidor",
"error": err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Servidor eliminado exitosamente",
})
}
// GetServidorDashboard retorna un servidor con todas sus conexiones de BD
func GetServidorDashboard(c *fiber.Ctx) error {
servidorID := c.Params("id")
uid, err := strconv.ParseUint(servidorID, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "ID inválido",
})
}
// Obtener servidor
var servidor models.Servidor
db := app.Http.Database.DB
if err := db.Preload("ProvServidor").Preload("TipoServidor").First(&servidor, uid).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "Servidor no encontrado",
})
}
// Obtener conexiones de BD asociadas
var conexiones []models.ConxDb
if err := db.Preload("Servidor").Preload("TipoDb").Where("servidor_id = ?", uid).Find(&conexiones).Error; err != nil {
conexiones = []models.ConxDb{}
}
return c.JSON(fiber.Map{
"servidor": servidor,
"conexiones": conexiones,
})
}
// PingConexion tests the connection to a database and returns the response time
func PingConexion(c *fiber.Ctx) error {
conexionID := c.Params("id")
uid, err := strconv.ParseUint(conexionID, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"exitoso": false,
"error": "ID inválido",
})
}
var conexion models.ConxDb
db := app.Http.Database.DB
if err := db.Preload("TipoDb").Preload("Servidor").First(&conexion, uid).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"exitoso": false,
"error": "Conexión no encontrada",
})
}
host := conexion.HostEfectivo()
puerto := conexion.Puerto
// Primero intentar conexión real a la BD (más fiable que solo TCP)
inicio := time.Now()
testErr := services.TestDBConnection(conexion)
if testErr == nil {
return c.JSON(fiber.Map{
"exitoso": true,
"tiempo": time.Since(inicio).Milliseconds(),
"host": host,
"puerto": puerto,
"tipo": conexion.TipoDb.Nombre,
"error": "",
})
}
// Si la BD no conecta, probar al menos TCP (puerto abierto)
tcpAddr := fmt.Sprintf("%s:%s", host, puerto)
inicioTCP := time.Now()
conn, errTCP := net.DialTimeout("tcp", tcpAddr, 3*time.Second)
if errTCP == nil {
conn.Close()
return c.JSON(fiber.Map{
"exitoso": false,
"tiempo": time.Since(inicioTCP).Milliseconds(),
"host": host,
"puerto": puerto,
"tipo": conexion.TipoDb.Nombre,
// Puerto abierto pero la BD rechaza la conexión
"error": fmt.Sprintf("Puerto %s abierto pero la BD no responde: %s", puerto, testErr.Error()),
})
}
return c.JSON(fiber.Map{
"exitoso": false,
"tiempo": int64(0),
"host": host,
"puerto": puerto,
"tipo": conexion.TipoDb.Nombre,
"error": fmt.Sprintf("No se puede alcanzar %s:%s — verifica que el host sea accesible desde este servidor. Tip: si el DB solo escucha en localhost del servidor, configura el campo 'Host' de la conexión.", host, puerto),
})
}