421 lines
12 KiB
Go
Executable File
421 lines
12 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
|
|
|
|
// Usar map para Updates y así no perder campos con valor cero (incluido hostinger_vps_id=nil)
|
|
updateMap := map[string]interface{}{
|
|
"nombre": m.Nombre,
|
|
"ip_servidor": m.IpServidor,
|
|
"so": m.So,
|
|
"vencimiento": m.Vencimiento,
|
|
"ram": m.Ram,
|
|
"nucleos": m.Nucleos,
|
|
"disco": m.Disco,
|
|
"ultimo_ping": m.UltimoPing,
|
|
"prov_servidor_id": m.ProvServidorID,
|
|
"tipo_servidor_id": m.TipoServidorID,
|
|
"hostinger_vps_id": m.HostingerVpsID,
|
|
"hostinger_subscription_id": m.HostingerSubscriptionID,
|
|
}
|
|
if err := app.Http.Database.DB.Model(&models.Servidor{}).Where("id = ?", uid).Updates(updateMap).Error; 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()})
|
|
}
|
|
if vps.ID == 0 {
|
|
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{"error": "Hostinger no devolvio datos para ese VPS ID"})
|
|
}
|
|
|
|
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 MB (igual que memory) → convertir a GB
|
|
if vps.DiskBytes > 0 {
|
|
gbDisk := vps.DiskBytes / 1024
|
|
updates["disco"] = fmt.Sprintf("%d GB", gbDisk)
|
|
}
|
|
|
|
// Vencimiento: resolución en cascada
|
|
// 1) subscription_id que viene directo del VPS (el más confiable)
|
|
// 2) subscription_id ya guardado en el servidor local
|
|
// 3) fallback fuzzy por nombre
|
|
subID := vps.SubscriptionID
|
|
if subID == "" && servidor.HostingerSubscriptionID != "" {
|
|
subID = servidor.HostingerSubscriptionID
|
|
}
|
|
|
|
if subID != "" {
|
|
// Lookup directo — sin ambigüedad
|
|
if sub, sErr := client.GetSubscriptionByID(subID); sErr == nil {
|
|
// Para suscripciones activas con auto-renovación, expires_at viene vacío;
|
|
// en ese caso usar next_billing_at como fecha de vencimiento.
|
|
venc := sub.ExpiresAt
|
|
if venc == "" {
|
|
venc = sub.NextBillingAt
|
|
}
|
|
if venc != "" && len(venc) >= 10 {
|
|
updates["vencimiento"] = venc[:10]
|
|
}
|
|
updates["hostinger_subscription_id"] = subID
|
|
}
|
|
} else {
|
|
// Fallback: buscar por nombre si aún no tenemos subscription_id
|
|
if subs, sErr := client.GetOrders(); sErr == nil {
|
|
for _, sub := range subs {
|
|
venc := sub.ExpiresAt
|
|
if venc == "" {
|
|
venc = sub.NextBillingAt
|
|
}
|
|
if venc != "" && len(venc) >= 10 &&
|
|
strings.Contains(strings.ToLower(sub.Name), strings.ToLower(vps.Hostname)) {
|
|
updates["vencimiento"] = venc[:10]
|
|
updates["hostinger_subscription_id"] = sub.ID
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := db.Model(&servidor).Updates(updates).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo guardar", "detalle": err.Error()})
|
|
}
|
|
|
|
db.First(&servidor, id)
|
|
return c.JSON(fiber.Map{"ok": true, "servidor": servidor, "campos_actualizados": updates})
|
|
}
|
|
|
|
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),
|
|
})
|
|
}
|