diff --git a/resources/triptico/turnosxpress-triptico-interno.afdesign~lock~ b/resources/triptico/turnosxpress-triptico-interno.afdesign~lock~ index 6f13bf8..6a5c211 100644 Binary files a/resources/triptico/turnosxpress-triptico-interno.afdesign~lock~ and b/resources/triptico/turnosxpress-triptico-interno.afdesign~lock~ differ diff --git a/server/src/Models/WapServer/WapServer.Adapter.Mongoose.ts b/server/src/Models/WapServer/WapServer.Adapter.Mongoose.ts index 65943c3..38cef4b 100644 --- a/server/src/Models/WapServer/WapServer.Adapter.Mongoose.ts +++ b/server/src/Models/WapServer/WapServer.Adapter.Mongoose.ts @@ -1,4 +1,5 @@ import { + CreateWapServerParams, FindWapServerParams, IWapServer, IWapServerAdapter, @@ -28,6 +29,10 @@ export class WapServerAdapterMongoose implements IWapServerAdapter { this.serverList = model("WapServer", this.schema); } + public async create(data: CreateWapServerParams): Promise { + return await this.serverList.create(data); + } + public async find(data: FindWapServerParams): Promise { return this.serverList.find(data).exec(); } diff --git a/server/src/Models/WapServer/WapServer.Interface.ts b/server/src/Models/WapServer/WapServer.Interface.ts index 5876612..fb090c2 100644 --- a/server/src/Models/WapServer/WapServer.Interface.ts +++ b/server/src/Models/WapServer/WapServer.Interface.ts @@ -114,6 +114,16 @@ export type UpdateWapServerParams = { active?: boolean; }; +export type CreateWapServerParams = { + name: string; + ipv4: string; + port: number; + maxBots: number; + ipv6?: string; + description?: string; + active?: boolean; +}; + export type PaginateWapServerParams = FindWapServerParams & { page: number; limit: number; @@ -206,6 +216,10 @@ export type SysAdminGetWapServerParams = PaginateWapServerParams & { payload: IPayload; }; +export type SysAdminCreateWapServerParams = CreateWapServerParams & { + payload: IPayload; +}; + export type SysAdminUpdateWapServerParams = UpdateWapServerParams & { payload: IPayload; }; @@ -227,6 +241,7 @@ export type SysAdminWapServerOrganizationActionRequest = SysAdminWapServerOrgani }; export interface IWapServerAdapter { + create(data: CreateWapServerParams): Promise; find(data: FindWapServerParams): Promise; findOne(data: FindWapServerParams): Promise; delete(id: string): Promise; @@ -249,6 +264,7 @@ export interface IWapServerManager { getQr(data: BotEventParams): Promise; sendMessage(data: SendBotMessageParams): Promise; sysAdminGetWapServers(data: PaginateWapServerParams): Promise; + sysAdminCreateWapServer(data: CreateWapServerParams): Promise; sysAdminUpdateWapServer(data: UpdateWapServerParams): Promise; sysAdminAuditWapServer(data: SysAdminWapServerAuditParams): Promise; sysAdminRecalculateWapServerCount(data: SysAdminWapServerRecalculateCountParams): Promise; diff --git a/server/src/Models/WapServer/WapServer.ts b/server/src/Models/WapServer/WapServer.ts index 3b451be..49acaea 100644 --- a/server/src/Models/WapServer/WapServer.ts +++ b/server/src/Models/WapServer/WapServer.ts @@ -29,6 +29,7 @@ import { BOT_STATE, SERVER_STATE, BOT_SESSION_STATUS, + CreateWapServerParams, VALIDATE_BOT_ENVENT_ERRORS, PaginateWapServerResults, PaginateWapServerParams, @@ -981,6 +982,35 @@ class WapServerManager implements IWapServerManager { return servers; } + public async sysAdminCreateWapServer(data: CreateWapServerParams): Promise { + if (!data.name || !String(data.name).trim()) { + throw new Error("Name is required"); + } + + if (!data.ipv4 || !String(data.ipv4).trim()) { + throw new Error("IPv4 is required"); + } + + if (!Number.isFinite(data.port) || data.port < 0) { + throw new Error("Port must be a finite non-negative number"); + } + + if (!Number.isFinite(data.maxBots) || data.maxBots < 0) { + throw new Error("Max bots must be a finite non-negative number"); + } + + return await this.servers.create({ + name: String(data.name).trim(), + description: data.description ? String(data.description).trim() : "", + ipv4: this.normalizeBotAdminApiUrl(data.ipv4), + ipv6: data.ipv6 ? String(data.ipv6).trim() : "", + countBots: 0, + port: data.port, + maxBots: data.maxBots, + active: data.active === undefined ? true : Boolean(data.active), + } as CreateWapServerParams & { countBots: number }); + } + public async sysAdminUpdateWapServer(data: UpdateWapServerParams): Promise { if (!data.serverId) { throw new Error("Server id is required"); diff --git a/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Controller.ts b/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Controller.ts index e363d21..a1e502d 100644 --- a/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Controller.ts +++ b/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Controller.ts @@ -1,5 +1,6 @@ import { PaginateWapServerResults, + SysAdminCreateWapServerParams, SysAdminDeleteWapServerRequest, SysAdminDeleteWapServerResult, SysAdminAuditWapServerParams, @@ -55,6 +56,27 @@ const getErrorDesc = (error: unknown): string => { return fallback || "Unknown error while auditing WAP server"; }; +@Route("sysadmin/wapserver/create") +export class SysAdminWapServerCreateController extends Controller { + @Response(500, "Ha ocurrido un error") + @SuccessResponse(200, "Done") + @Post() + @Middlewares(sysAdminAuthMiddleware) + public async sysAdminCreateWapServer( + @Body() requestBody: SysAdminCreateWapServerParams, + ): Promise { + try { + const { payload, ...cleanRequestBody } = requestBody as any; + const result = await new SA_WapServerService().sysAdminCreateWapServer(cleanRequestBody); + this.setStatus(200); + return result; + } catch (e) { + this.setStatus(500); + return new ApiValidationError(500, getErrorDesc(e)); + } + } +} + @Route("sysadmin/wapserver/get") export class SysAdminWapServerGetController extends Controller { @Response(500, "Ha ocurrido un error") diff --git a/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Service.ts b/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Service.ts index 369a41e..86de983 100644 --- a/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Service.ts +++ b/server/src/api/SysAdmin/WapServers/SysAdminWapServer.Service.ts @@ -1,6 +1,7 @@ import { connect } from "mongoose"; import { + CreateWapServerParams, PaginateWapServerParams, PaginateWapServerResults, SysAdminDeleteWapServerParams, @@ -23,6 +24,11 @@ export class SA_WapServerService { return await WapServerList.sysAdminGetWapServers(data); } + public async sysAdminCreateWapServer(data: CreateWapServerParams) { + await connect(`${process.env.DATABASE_CONNECTION}`); + return await WapServerList.sysAdminCreateWapServer(data); + } + public async sysAdminUpdateWapServer(data: UpdateWapServerParams) { await connect(`${process.env.DATABASE_CONNECTION}`); return await WapServerList.sysAdminUpdateWapServer(data); diff --git a/sysadmin-cli/src/api/sysadmin.ts b/sysadmin-cli/src/api/sysadmin.ts index c80ff5f..8bcad5a 100644 --- a/sysadmin-cli/src/api/sysadmin.ts +++ b/sysadmin-cli/src/api/sysadmin.ts @@ -196,6 +196,21 @@ export type UpdateWapServerPayload = { active?: boolean; }; +export type CreateWapServerPayload = { + name: string; + ipv4: string; + port: number; + maxBots: number; + ipv6?: string; + description?: string; + active?: boolean; +}; + +export const createWapServer = async (data: CreateWapServerPayload): Promise => { + const res = await sysadminApi.post('/wap/create', data); + return res.data; +}; + export const updateWapServer = async (data: UpdateWapServerPayload): Promise => { const res = await sysadminApi.post('/wap/update', data); return res.data; diff --git a/sysadmin-cli/src/pages/WapServersPage.tsx b/sysadmin-cli/src/pages/WapServersPage.tsx index d9210d8..167e19f 100644 --- a/sysadmin-cli/src/pages/WapServersPage.tsx +++ b/sysadmin-cli/src/pages/WapServersPage.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; -import { Search, RotateCcw, CheckCircle, XCircle, Server, ServerOff, ClipboardList, Calculator, X, MoreVertical, Play, Pause, Power, Trash2, Unlink, QrCode, RotateCw, Edit3 } from 'lucide-react'; -import { auditWapServer, deleteWapBot, deleteWapServer, detachWapOrganizationServer, fetchWapServers, getWapBotQr, recalculateWapServerCount, restartWapBot, startWapBot, stopWapBot, updateWapServer, type WapContainerDto, type WapServerAuditResult, type WapServerQrResult, type WapServerRecalculateCountResult } from '../api/sysadmin'; +import { Search, RotateCcw, CheckCircle, XCircle, Server, ServerOff, ClipboardList, Calculator, X, MoreVertical, Play, Pause, Power, Trash2, Unlink, QrCode, RotateCw, Edit3, Plus } from 'lucide-react'; +import { auditWapServer, createWapServer, deleteWapBot, deleteWapServer, detachWapOrganizationServer, fetchWapServers, getWapBotQr, recalculateWapServerCount, restartWapBot, startWapBot, stopWapBot, updateWapServer, type WapContainerDto, type WapServerAuditResult, type WapServerQrResult, type WapServerRecalculateCountResult } from '../api/sysadmin'; const getErrorMessage = (error: unknown, fallback: string): string => { const responseData = (error as any)?.response?.data; @@ -30,6 +30,7 @@ export const WapServersPage = () => { const [openActionsServerId, setOpenActionsServerId] = useState(null); const [openAuditActionKey, setOpenAuditActionKey] = useState(null); const [editingServer, setEditingServer] = useState(null); + const [serverFormMode, setServerFormMode] = useState<'create' | 'edit'>('edit'); const [editForm, setEditForm] = useState({ name: '', ipv4: '', port: '', maxBots: '', active: true }); // Filters @@ -103,6 +104,7 @@ export const WapServersPage = () => { }; const openEditServer = (server: any) => { + setServerFormMode('edit'); setEditingServer(server); setEditForm({ name: server.name || '', @@ -113,11 +115,17 @@ export const WapServersPage = () => { }); }; + const openCreateServer = () => { + setServerFormMode('create'); + setEditingServer({}); + setEditForm({ name: '', ipv4: '', port: '', maxBots: '', active: true }); + }; + const handleSaveServer = async (event: React.FormEvent) => { event.preventDefault(); if (!editingServer) return; - const serverId = getServerId(editingServer); + const serverId = serverFormMode === 'edit' ? getServerId(editingServer) : 'create'; const port = Number(editForm.port); const maxBots = Number(editForm.maxBots); @@ -128,15 +136,26 @@ export const WapServersPage = () => { setActionServerId(serverId); try { - await updateWapServer({ - serverId, - name: editForm.name, - ipv4: editForm.ipv4, - port, - maxBots, - active: editForm.active, - }); - setActionMessage('Servidor WAP actualizado.'); + if (serverFormMode === 'create') { + await createWapServer({ + name: editForm.name, + ipv4: editForm.ipv4, + port, + maxBots, + active: editForm.active, + }); + setActionMessage('Servidor WAP creado.'); + } else { + await updateWapServer({ + serverId, + name: editForm.name, + ipv4: editForm.ipv4, + port, + maxBots, + active: editForm.active, + }); + setActionMessage('Servidor WAP actualizado.'); + } setEditingServer(null); await loadServers(); } catch (e) { @@ -282,6 +301,9 @@ export const WapServersPage = () => {

Servidores WAP

+
{/* Filters Bar */} @@ -567,8 +589,8 @@ export const WapServersPage = () => {
event.stopPropagation()}>
-

Editar servidor

-

{editingServer.name || getServerId(editingServer)}

+

{serverFormMode === 'create' ? 'Nuevo servidor' : 'Editar servidor'}

+

{serverFormMode === 'create' ? 'Crear servidor WAP' : editingServer.name || getServerId(editingServer)}

- +
diff --git a/sysadmin/src/controllers/wap.controller.ts b/sysadmin/src/controllers/wap.controller.ts index d273455..b65c08b 100644 --- a/sysadmin/src/controllers/wap.controller.ts +++ b/sysadmin/src/controllers/wap.controller.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { Request, Response } from "express"; import { WapService } from "../services/wap.service"; -import { PaginateWapServerParams, UpdateWapServerParams, WapServerByIdParams, WapServerOrganizationActionParams } from "src/models/WapServers.Model"; +import { CreateWapServerParams, PaginateWapServerParams, UpdateWapServerParams, WapServerByIdParams, WapServerOrganizationActionParams } from "src/models/WapServers.Model"; const wapService = new WapService(); @@ -58,6 +58,17 @@ export class WapController { } }; + public createServer = async (req: Request, res: Response): Promise => { + try { + const result = await wapService.createWapServer(req.body as CreateWapServerParams); + res.json(result); + } catch (error) { + console.error("Error creating wap server:", error); + const message = getErrorMessage(error); + res.status(getErrorStatus(error)).json({ success: false, message, desc: message }); + } + }; + public recalculateCount = async (req: Request, res: Response): Promise => { try { const data: WapServerByIdParams = req.body as WapServerByIdParams; diff --git a/sysadmin/src/models/WapServers.Model.ts b/sysadmin/src/models/WapServers.Model.ts index 529fff3..dcad16f 100644 --- a/sysadmin/src/models/WapServers.Model.ts +++ b/sysadmin/src/models/WapServers.Model.ts @@ -33,6 +33,16 @@ export type UpdateWapServerParams = { active?: boolean; }; +export type CreateWapServerParams = { + name: string; + ipv4: string; + port: number; + maxBots: number; + ipv6?: string; + description?: string; + active?: boolean; +}; + export type PaginateWapServerParams = FindWapServerParams & { page: number; limit: number; diff --git a/sysadmin/src/routes/wap.routes.ts b/sysadmin/src/routes/wap.routes.ts index 88f0ef3..6c7f0d5 100644 --- a/sysadmin/src/routes/wap.routes.ts +++ b/sysadmin/src/routes/wap.routes.ts @@ -5,6 +5,7 @@ const router = Router(); const wapController = new WapController(); router.post("/paginate", wapController.getServers); +router.post("/create", wapController.createServer); router.post("/update", wapController.updateServer); router.post("/audit", wapController.auditServer); router.post("/recalculate-count", wapController.recalculateCount); diff --git a/sysadmin/src/services/wap.service.ts b/sysadmin/src/services/wap.service.ts index 9064918..331e533 100644 --- a/sysadmin/src/services/wap.service.ts +++ b/sysadmin/src/services/wap.service.ts @@ -3,6 +3,7 @@ import { getApiHost } from "../helpers/GetApiHost"; import axios from "axios"; import { getPayload } from "../helpers/GetPayload"; import { + CreateWapServerParams, PaginateWapServerParams, PaginateWapServerResults, WapServerAuditResult, @@ -45,6 +46,18 @@ export class WapService { return response.data as WapServerAuditResult; } + public async createWapServer(data: CreateWapServerParams): Promise { + const nonce = await getSysAdminNonce(); + const postData = { ...data, ...{ payload: getPayload(nonce) } }; + + const response = await axios.post(`${getApiHost()}/sysadmin/wapserver/create`, postData, { + headers: { "Content-Type": "application/json" }, + timeout: 5000, + }); + + return response.data as IWapServer; + } + public async updateWapServer(data: UpdateWapServerParams): Promise { const nonce = await getSysAdminNonce(); const postData = { ...data, ...{ payload: getPayload(nonce) } };