diff --git a/.gitignore b/.gitignore index 03ad15e..83e4eeb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,12 @@ coverage.* .idea/ .vscode/ .DS_Store +manager/node_modules/ +manager/src/ +manager/package.json +manager/package-lock.json +manager/postcss.config.js +manager/tailwind.config.ts +manager/tsconfig*.json +manager/vite.config.ts +manager/index.html diff --git a/Dockerfile b/Dockerfile index 4c0c7d6..319b1f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ WORKDIR /app COPY --from=build /build/server . COPY --from=build /build/manager/dist ./manager/dist +COPY --from=build /build/manager/dashboard ./manager/dashboard COPY --from=build /build/VERSION ./VERSION ENV TZ=America/Sao_Paulo diff --git a/manager/dashboard/index.html b/manager/dashboard/index.html new file mode 100644 index 0000000..ea4d4d0 --- /dev/null +++ b/manager/dashboard/index.html @@ -0,0 +1,258 @@ + + + + + + Evolution GO Manager + + + + + + +
+ + + + +
+
+ + +
+
+

Dashboard

+

Visão geral das instâncias WhatsApp

+
+ +
+ + +
+
+
+

Total de Instâncias

+
+ + + +
+
+

+

registradas no sistema

+
+ +
+
+

Conectadas

+
+ + + +
+
+

+

do total

+
+ +
+
+

Desconectadas

+
+ + + +
+
+

+

aguardando reconexão

+
+ +
+
+

Servidor

+
+ + + +
+
+

+

verificando…

+
+
+ + +
+
+

Instâncias

+

Atualizado automaticamente a cada 30s

+
+
+
Carregando…
+
+
+ +
+
+
+ + + + diff --git a/pkg/chat/handler/chat_handler.go b/pkg/chat/handler/chat_handler.go index 8240bc7..bc84a6e 100644 --- a/pkg/chat/handler/chat_handler.go +++ b/pkg/chat/handler/chat_handler.go @@ -204,7 +204,7 @@ func (c *chatHandler) ChatUnarchive(ctx *gin.Context) { // Mute a chat // @Summary Mute a chat -// @Description Mute a chat +// @Description Mute a chat. Set duration to the number of seconds to mute (e.g. 28800 = 8 hours, 604800 = 1 week). Use 0 to mute forever. // @Tags Chat // @Accept json // @Produce json diff --git a/pkg/chat/service/chat_service.go b/pkg/chat/service/chat_service.go index 0255c82..79aab6b 100644 --- a/pkg/chat/service/chat_service.go +++ b/pkg/chat/service/chat_service.go @@ -3,6 +3,7 @@ package chat_service import ( "context" "errors" + "fmt" "time" instance_model "github.com/EvolutionAPI/evolution-go/pkg/instance/model" @@ -32,6 +33,8 @@ type chatService struct { type BodyStruct struct { Chat string `json:"chat"` + // Duration is used by mute operations: seconds to mute (0 = mute forever). + Duration int64 `json:"duration,omitempty"` } type HistorySyncRequestStruct struct { @@ -170,12 +173,22 @@ func (c *chatService) ChatUnarchive(data *BodyStruct, instance *instance_model.I return ts.String(), nil } +// maxMuteDurationSeconds caps mute at 1 year to avoid unreasonably large timestamps. +const maxMuteDurationSeconds = 365 * 24 * 3600 + func (c *chatService) ChatMute(data *BodyStruct, instance *instance_model.Instance) (string, error) { client, err := c.ensureClientConnected(instance.Id) if err != nil { return "", err } + if data.Duration < 0 { + return "", errors.New("duration must be >= 0 (0 = mute forever)") + } + if data.Duration > maxMuteDurationSeconds { + return "", fmt.Errorf("duration exceeds maximum allowed value of %d seconds (1 year)", maxMuteDurationSeconds) + } + var ts time.Time recipient, ok := utils.ParseJID(data.Chat) @@ -184,7 +197,9 @@ func (c *chatService) ChatMute(data *BodyStruct, instance *instance_model.Instan return "", errors.New("invalid phone number") } - err = client.SendAppState(context.Background(), appstate.BuildMute(recipient, true, 1*time.Hour)) + // duration=0 is passed as-is: BuildMute treats 0 as "mute forever" (sets timestamp to -1). + muteDuration := time.Duration(data.Duration) * time.Second + err = client.SendAppState(context.Background(), appstate.BuildMute(recipient, true, muteDuration)) if err != nil { c.loggerWrapper.GetLogger(instance.Id).LogError("[%s] error mute chat: %v", instance.Id, err) return "", err diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index f51e7d9..3bf90b0 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -66,12 +66,13 @@ func (r *Routes) AssignRoutes(eng *gin.Engine) { // Rotas para o gerenciador React (sem autenticação) eng.Static("/assets", "./manager/dist/assets") - // Ajuste nas rotas do manager para suportar client-side routing do React - eng.GET("/manager/*any", func(c *gin.Context) { - c.File("manager/dist/index.html") + // Dashboard com métricas reais (página standalone) + eng.GET("/manager", func(c *gin.Context) { + c.File("manager/dashboard/index.html") }) - eng.GET("/manager", func(c *gin.Context) { + // Demais rotas do manager (instâncias, login, etc.) — bundle original + eng.GET("/manager/*any", func(c *gin.Context) { c.File("manager/dist/index.html") }) @@ -161,12 +162,12 @@ func (r *Routes) AssignRoutes(eng *gin.Engine) { { routes.Use(r.authMiddleware.Auth) { - routes.POST("/pin", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatPin) // TODO: not working - routes.POST("/unpin", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatUnpin) // TODO: not working - routes.POST("/archive", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatArchive) // TODO: not working - routes.POST("/unarchive", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatUnarchive) // TODO: not working - routes.POST("/mute", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatMute) // TODO: not working - routes.POST("/unmute", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatUnmute) // TODO: not working + routes.POST("/pin", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatPin) + routes.POST("/unpin", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatUnpin) + routes.POST("/archive", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatArchive) + routes.POST("/unarchive", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatUnarchive) + routes.POST("/mute", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatMute) + routes.POST("/unmute", r.jidValidationMiddleware.ValidateNumberField(), r.chatHandler.ChatUnmute) routes.POST("/history-sync", r.chatHandler.HistorySyncRequest) } }