fix(instance): GET /instance/status returns 400 after manual device disconnection#38
Open
edilsonoliveirama wants to merge 5 commits intoEvolutionAPI:mainfrom
Conversation
The /manager dashboard previously showed only a static placeholder
("Dashboard content will be implemented here..."). This replaces it
with a standalone HTML page that fetches live data from the API and
displays real metrics:
- Total instances count
- Connected instances count and percentage
- Disconnected instances count
- Server health status (GET /server/ok)
- AlwaysOnline count
- Instance table with name, status badge, phone number, client and
AlwaysOnline indicator
- Auto-refresh every 30 seconds with manual refresh button
Implementation uses a standalone HTML file (Tailwind CDN + vanilla JS
fetch) served at GET /manager, keeping the existing compiled bundle
intact for all other routes (/manager/instances, /manager/login, etc.).
Changes:
- manager/dashboard/index.html: new self-contained dashboard page
- pkg/routes/routes.go: serve dashboard/index.html for GET /manager
(exact), keep dist/index.html for GET /manager/*any (wildcard)
- Dockerfile: copy manager/dashboard/ into the final image
- .gitignore: exclude manager build artifacts from version control
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the '// TODO: not working' markers from the six chat endpoints (pin, unpin, archive, unarchive, mute, unmute). Investigation confirmed the implementation is correct: the endpoints work on fully-established sessions that have synced WhatsApp app state keys. The markers were likely added after testing on a fresh session where keys had not yet been distributed by the WhatsApp server. Also fixes the hardcoded 1-hour mute duration: the BodyStruct now accepts an optional `duration` field (seconds). Sending 0 or omitting the field mutes the chat indefinitely, matching WhatsApp's own behaviour.
Reject negative duration values with a 400-level validation error. Document that duration=0 maps to 'mute forever' (BuildMute treats 0 as a zero time.Duration, which causes BuildMuteAbs to set the WhatsApp sentinel timestamp of -1). Clamp duration to a maximum of 1 year (31536000 seconds) to avoid unreasonably large timestamps being sent to the WhatsApp API.
Adds GET /metrics serving standard Prometheus text format. No authentication required — follows the Prometheus convention of protecting the endpoint at the network/ingress level. Metrics exposed: evolution_instances_total total registered instances (gauge) evolution_instances_connected connected instances (gauge) evolution_instances_disconnected disconnected instances (gauge) evolution_http_requests_total HTTP requests by method/path/status (counter) evolution_http_request_duration_seconds HTTP latency by method/path (histogram) evolution_build_info always 1, version label carries the value (gauge) evolution_uptime_seconds seconds since server start (gauge) Instance gauges use a custom Collector that queries the database on each scrape, so values are always current without event hooks. HTTP path labels use Gin registered route patterns (e.g. /instance/:instanceId) to keep cardinality bounded regardless of distinct IDs in the path. New dependency: github.com/prometheus/client_golang v1.20.5
…volutionAPI#20 GET /instance/status was calling ensureClientConnected, which returns an error when the WhatsApp client exists but is not connected (e.g. after the user manually removes the device from their phone). This caused the endpoint to return HTTP 400 until the container was restarted, making it impossible for clients to detect the disconnected state without restarting the server. Status is a read-only query: it should report the current state, not require an active connection to do so. The fix reads clientPointer directly and returns Connected=false/LoggedIn=false when the client is nil or disconnected, without attempting reconnection. Fixes EvolutionAPI#20
Reviewer's GuideRefactors instance status handling to avoid enforcing an active WhatsApp client, adds metrics and a standalone dashboard for monitoring instances, and improves chat mute behavior and routing/docs. Sequence diagram for updated instance status retrieval without forced reconnectionsequenceDiagram
actor User
participant HttpClient as HTTPClient
participant GinRouter as GinRouter
participant InstanceHandler as InstanceHTTPHandler
participant InstanceService as InstanceService
participant ClientMap as clientPointer
participant WAClient as WhatsAppClient
User ->> HttpClient: Trigger GET /instance/status
HttpClient ->> GinRouter: GET /instance/status
GinRouter ->> InstanceHandler: Route request
InstanceHandler ->> InstanceService: Status(instance)
InstanceService ->> ClientMap: Lookup clientPointer[instance.Id]
ClientMap -->> InstanceService: client
alt client is nil
InstanceService -->> InstanceHandler: StatusStruct{Connected=false, LoggedIn=false}
else client not nil
InstanceService ->> WAClient: IsConnected()
WAClient -->> InstanceService: isConnected
InstanceService ->> WAClient: IsLoggedIn()
WAClient -->> InstanceService: isLoggedIn
InstanceService -->> InstanceHandler: StatusStruct{Connected=isConnected, LoggedIn=isLoggedIn}
end
InstanceHandler -->> GinRouter: HTTP 200 JSON
GinRouter -->> HttpClient: HTTP 200 {connected:false,...} when disconnected
HttpClient -->> User: Show current instance status
Class diagram for metrics registry, instance repository, and chat mute changesclassDiagram
class Registry {
-prometheus.Registry reg
-prometheus.CounterVec httpRequests
-prometheus.HistogramVec httpDuration
+New(version string, instanceRepo InstanceRepository) Registry
+Handler() http.Handler
+GinMiddleware() gin.HandlerFunc
}
class InstanceRepository {
<<interface>>
+GetAllConnectedInstances() []*Instance
+GetAllConnectedInstancesByClientName(clientName string) []*Instance
+GetAll(clientName string) []*Instance
+GetAllInstances() []*Instance
+Delete(instanceId string) error
+GetAdvancedSettings(instanceId string) *AdvancedSettings
+UpdateAdvancedSettings(instanceId string, settings *AdvancedSettings) error
}
class InstanceRepositoryImpl {
-gorm.DB db
+GetAllInstances() []*Instance
}
InstanceRepository <|.. InstanceRepositoryImpl
class instanceCollector {
-InstanceRepository repo
-prometheus.Desc descTotal
-prometheus.Desc descConnected
-prometheus.Desc descDisconnected
+Describe(ch chan *prometheus.Desc)
+Collect(ch chan prometheus.Metric)
}
class Instance {
+string Id
+bool Connected
+string Name
+string ClientName
}
class InstanceService {
-map~string, WAClient~ clientPointer
+Status(instance *Instance) *StatusStruct
}
class StatusStruct {
+bool Connected
+bool LoggedIn
+string myJid
+string Name
}
class WAClient {
+IsConnected() bool
+IsLoggedIn() bool
+Store Store
}
class Store {
+string ID
+string PushName
}
class chatService {
+ensureClientConnected(instanceId string) (WAClient, error)
+ChatMute(data *BodyStruct, instance *Instance) (string, error)
}
class BodyStruct {
+string Chat
+int64 Duration
}
class appstate {
+BuildMute(recipient JID, enabled bool, duration time.Duration) AppState
}
class GinEngine {
+Use(middleware gin.HandlerFunc)
+GET(path string, handler gin.HandlerFunc)
}
class DashboardHTML {
+loadData()
+apiFetch(path string) Promise
+renderTable(instances []InstanceView)
}
class InstanceView {
+string name
+bool connected
+bool alwaysOnline
+string jid
+string clientName
}
Registry --> InstanceRepository : uses
Registry --> instanceCollector : registers
instanceCollector --> InstanceRepository : queries instances
InstanceRepositoryImpl --> Instance : persists
InstanceService --> Instance : reads Id
InstanceService --> WAClient : uses
InstanceService --> StatusStruct : returns
chatService --> BodyStruct : uses
chatService --> WAClient : uses
chatService --> appstate : BuildMute
GinEngine --> Registry : uses GinMiddleware
GinEngine --> Registry : exposes Handler at /metrics
DashboardHTML --> InstanceView : displays
DashboardHTML --> GinEngine : calls /instance/all, /server/ok
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The Prometheus instanceCollector calls repo.GetAllInstances() on every scrape and silently drops metrics on error; consider adding logging and, if the instances table can grow large or scrapes are frequent, adding a context/timeout or lightweight view to avoid DB pressure from metrics.
- In the new Status implementation, clientPointer is read directly without any synchronization; if other code mutates clientPointer under a lock, you may want to mirror that here to avoid potential data races under concurrent access.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The Prometheus instanceCollector calls repo.GetAllInstances() on every scrape and silently drops metrics on error; consider adding logging and, if the instances table can grow large or scrapes are frequent, adding a context/timeout or lightweight view to avoid DB pressure from metrics.
- In the new Status implementation, clientPointer is read directly without any synchronization; if other code mutates clientPointer under a lock, you may want to mirror that here to avoid potential data races under concurrent access.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #20
Root cause
GET /instance/statuschamavaensureClientConnectedinternamente.Essa função retorna erro quando o cliente WhatsApp existe na memória
mas não está conectado — exatamente o estado após o usuário remover
o dispositivo manualmente pelo celular.
Isso causava HTTP 400 em loop até reiniciar o container.
Correção
Statusé uma consulta de leitura: deve reportar o estado atual,não exigir conexão ativa para isso. A correção lê
clientPointerdiretamente e retorna
Connected=false / LoggedIn=falsequando oclient é nil ou está desconectado, sem tentar reconectar.
Antes / Depois
HTTP 400HTTP 200 {connected:false}HTTP 400HTTP 200 {connected:false}HTTP 200HTTP 200(sem mudança)Summary by Sourcery
Report instance connection status without forcing reconnection and add observability and dashboard improvements.
Bug Fixes:
Enhancements:
Build: