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
+
+
+
+
+
+
+
…
+
aguardando reconexão
+
+
+
+
+
+
+
+
+
Instâncias
+
Atualizado automaticamente a cada 30s
+
+
+
+
+
+
+
+
+
+
+
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)
}
}