622 lines
17 KiB
Go
622 lines
17 KiB
Go
package websocket
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// DatabaseHandler menangani operasi database
|
|
type DatabaseHandler struct {
|
|
hub *Hub
|
|
}
|
|
|
|
func NewDatabaseHandler(hub *Hub) *DatabaseHandler {
|
|
return &DatabaseHandler{hub: hub}
|
|
}
|
|
|
|
func (h *DatabaseHandler) HandleMessage(client *Client, message WebSocketMessage) error {
|
|
switch message.Type {
|
|
case DatabaseInsertMessage:
|
|
return h.handleDatabaseInsert(client, message)
|
|
case DatabaseQueryMessage:
|
|
return h.handleDatabaseQuery(client, message)
|
|
case DatabaseCustomQueryMessage:
|
|
return h.handleDatabaseCustomQuery(client, message)
|
|
default:
|
|
return fmt.Errorf("unsupported database message type: %s", message.Type)
|
|
}
|
|
}
|
|
|
|
func (h *DatabaseHandler) MessageType() MessageType {
|
|
return DatabaseInsertMessage // Primary type for registration
|
|
}
|
|
|
|
func (h *DatabaseHandler) handleDatabaseInsert(client *Client, message WebSocketMessage) error {
|
|
data, ok := message.Data.(map[string]interface{})
|
|
if !ok {
|
|
client.sendErrorResponse("Invalid database insert format", "Data must be an object")
|
|
return nil
|
|
}
|
|
|
|
table, ok := data["m_deviceqris"].(string)
|
|
if !ok || table == "" {
|
|
client.sendErrorResponse("Invalid table name", "table is required")
|
|
return nil
|
|
}
|
|
|
|
insertData, ok := data["data"].(map[string]interface{})
|
|
if !ok {
|
|
client.sendErrorResponse("Invalid insert data", "data must be an object")
|
|
return nil
|
|
}
|
|
|
|
// Perform actual database insert
|
|
if h.hub.dbService != nil {
|
|
// Get database connection
|
|
db, err := h.hub.GetDatabaseConnection("simrs_prod")
|
|
if err != nil {
|
|
client.sendErrorResponse("Database connection error", err.Error())
|
|
return nil
|
|
}
|
|
|
|
// Build insert query
|
|
columns := make([]string, 0, len(insertData))
|
|
values := make([]interface{}, 0, len(insertData))
|
|
placeholders := make([]string, 0, len(insertData))
|
|
|
|
i := 1
|
|
for col, val := range insertData {
|
|
columns = append(columns, col)
|
|
values = append(values, val)
|
|
placeholders = append(placeholders, fmt.Sprintf("$%d", i))
|
|
i++
|
|
}
|
|
|
|
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)",
|
|
table,
|
|
strings.Join(columns, ", "),
|
|
strings.Join(placeholders, ", "))
|
|
|
|
// Execute insert
|
|
ctx, cancel := context.WithTimeout(client.ctx, 30*time.Second)
|
|
defer cancel()
|
|
|
|
result, err := db.ExecContext(ctx, query, values...)
|
|
if err != nil {
|
|
client.sendErrorResponse("Database insert error", err.Error())
|
|
return nil
|
|
}
|
|
|
|
rowsAffected, _ := result.RowsAffected()
|
|
client.sendDirectResponse("db_insert_result", map[string]interface{}{
|
|
"table": table,
|
|
"status": "success",
|
|
"rows_affected": rowsAffected,
|
|
"message": "Data inserted successfully",
|
|
})
|
|
} else {
|
|
client.sendErrorResponse("Database service not available", "Database service is not initialized")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *DatabaseHandler) handleDatabaseQuery(client *Client, message WebSocketMessage) error {
|
|
data, ok := message.Data.(map[string]interface{})
|
|
if !ok {
|
|
client.sendErrorResponse("Invalid database query format", "Data must be an object")
|
|
return nil
|
|
}
|
|
|
|
table, ok := data["m_deviceqris"].(string)
|
|
if !ok || table == "" {
|
|
client.sendErrorResponse("Invalid table name", "table is required")
|
|
return nil
|
|
}
|
|
|
|
// Execute query
|
|
results, err := h.hub.ExecuteDatabaseQuery("simrs_prod", fmt.Sprintf("SELECT * FROM %s LIMIT 100", table))
|
|
if err != nil {
|
|
client.sendErrorResponse("Database query error", err.Error())
|
|
return nil
|
|
}
|
|
|
|
client.sendDirectResponse("db_query_result", map[string]interface{}{
|
|
"table": table,
|
|
"status": "success",
|
|
"results": results,
|
|
"count": len(results),
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *DatabaseHandler) handleDatabaseCustomQuery(client *Client, message WebSocketMessage) error {
|
|
data, ok := message.Data.(map[string]interface{})
|
|
if !ok {
|
|
client.sendErrorResponse("Invalid database query format", "Data must be an object")
|
|
return nil
|
|
}
|
|
|
|
database, ok := data["database"].(string)
|
|
if !ok || database == "" {
|
|
database = "simrs_prod"
|
|
}
|
|
|
|
query, ok := data["query"].(string)
|
|
if !ok || query == "" {
|
|
client.sendErrorResponse("Invalid query", "query is required")
|
|
return nil
|
|
}
|
|
|
|
// Execute custom query
|
|
results, err := h.hub.ExecuteDatabaseQuery(database, query)
|
|
if err != nil {
|
|
client.sendErrorResponse("Database query error", err.Error())
|
|
return nil
|
|
}
|
|
|
|
client.sendDirectResponse("db_query_result", map[string]interface{}{
|
|
"database": database,
|
|
"query": query,
|
|
"status": "success",
|
|
"results": results,
|
|
"count": len(results),
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// AdminHandler menangani operasi admin
|
|
type AdminHandler struct {
|
|
hub *Hub
|
|
}
|
|
|
|
func NewAdminHandler(hub *Hub) *AdminHandler {
|
|
return &AdminHandler{hub: hub}
|
|
}
|
|
|
|
func (h *AdminHandler) HandleMessage(client *Client, message WebSocketMessage) error {
|
|
switch message.Type {
|
|
case AdminKickClientMessage:
|
|
return h.handleAdminKickClient(client, message)
|
|
case AdminKillServerMessage:
|
|
return h.handleAdminKillServer(client, message)
|
|
case AdminClearLogsMessage:
|
|
return h.handleAdminClearLogs(client, message)
|
|
default:
|
|
return fmt.Errorf("unsupported admin message type: %s", message.Type)
|
|
}
|
|
}
|
|
|
|
func (h *AdminHandler) MessageType() MessageType {
|
|
return AdminKickClientMessage // Primary type for registration
|
|
}
|
|
|
|
func (h *AdminHandler) handleAdminKickClient(client *Client, message WebSocketMessage) error {
|
|
data, ok := message.Data.(map[string]interface{})
|
|
if !ok {
|
|
client.sendErrorResponse("Invalid admin command format", "Data must be an object")
|
|
return nil
|
|
}
|
|
|
|
targetClientID, ok := data["client_id"].(string)
|
|
if !ok || targetClientID == "" {
|
|
client.sendErrorResponse("Invalid target client ID", "client_id is required")
|
|
return nil
|
|
}
|
|
|
|
// Check if target client exists
|
|
h.hub.mu.RLock()
|
|
targetClient, exists := h.hub.clientsByID[targetClientID]
|
|
h.hub.mu.RUnlock()
|
|
|
|
if !exists {
|
|
client.sendErrorResponse("Client not found", fmt.Sprintf("Client %s not found", targetClientID))
|
|
return nil
|
|
}
|
|
|
|
// Log activity
|
|
h.hub.logActivity("admin_kick_client", client.ID, fmt.Sprintf("Kicked client: %s", targetClientID))
|
|
|
|
// Disconnect the target client
|
|
targetClient.cancel()
|
|
targetClient.Conn.Close()
|
|
|
|
client.sendDirectResponse("admin_command_result", map[string]interface{}{
|
|
"command": "kick_client",
|
|
"target_client_id": targetClientID,
|
|
"status": "success",
|
|
"message": "Client kicked successfully",
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *AdminHandler) handleAdminKillServer(client *Client, message WebSocketMessage) error {
|
|
// Log activity
|
|
h.hub.logActivity("admin_kill_server", client.ID, "Server kill signal received")
|
|
|
|
// For testing purposes, just acknowledge (don't actually kill server)
|
|
client.sendDirectResponse("admin_command_result", map[string]interface{}{
|
|
"command": "kill_server",
|
|
"status": "acknowledged",
|
|
"message": "Kill server signal received (test mode - server not actually killed)",
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *AdminHandler) handleAdminClearLogs(client *Client, message WebSocketMessage) error {
|
|
// Clear activity logs
|
|
h.hub.activityMu.Lock()
|
|
h.hub.activityLog = make([]ActivityLog, 0, h.hub.config.ActivityLogSize)
|
|
h.hub.activityMu.Unlock()
|
|
|
|
// Log the clear action
|
|
h.hub.logActivity("admin_clear_logs", client.ID, "Activity logs cleared")
|
|
|
|
client.sendDirectResponse("admin_command_result", map[string]interface{}{
|
|
"command": "clear_logs",
|
|
"status": "success",
|
|
"message": "Server logs cleared successfully",
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// RoomHandler menangani operasi ruangan
|
|
type RoomHandler struct {
|
|
hub *Hub
|
|
}
|
|
|
|
func NewRoomHandler(hub *Hub) *RoomHandler {
|
|
return &RoomHandler{hub: hub}
|
|
}
|
|
|
|
func (h *RoomHandler) HandleMessage(client *Client, message WebSocketMessage) error {
|
|
switch message.Type {
|
|
case JoinRoomMessage:
|
|
return h.handleJoinRoom(client, message)
|
|
case LeaveRoomMessage:
|
|
return h.handleLeaveRoom(client, message)
|
|
case GetRoomInfoMessage:
|
|
return h.handleGetRoomInfo(client, message)
|
|
default:
|
|
return fmt.Errorf("unsupported room message type: %s", message.Type)
|
|
}
|
|
}
|
|
|
|
func (h *RoomHandler) MessageType() MessageType {
|
|
return JoinRoomMessage // Primary type for registration
|
|
}
|
|
|
|
func (h *RoomHandler) handleJoinRoom(client *Client, message WebSocketMessage) error {
|
|
data, ok := message.Data.(map[string]interface{})
|
|
if !ok {
|
|
client.sendErrorResponse("Invalid join room format", "Data must be an object")
|
|
return nil
|
|
}
|
|
|
|
roomName, ok := data["room"].(string)
|
|
if !ok || roomName == "" {
|
|
client.sendErrorResponse("Invalid room name", "room is required")
|
|
return nil
|
|
}
|
|
|
|
// Update client room
|
|
oldRoom := client.Room
|
|
client.Room = roomName
|
|
|
|
// Update hub room mapping
|
|
h.hub.mu.Lock()
|
|
if oldRoom != "" {
|
|
if roomClients, exists := h.hub.rooms[oldRoom]; exists {
|
|
delete(roomClients, client)
|
|
if len(roomClients) == 0 {
|
|
delete(h.hub.rooms, oldRoom)
|
|
}
|
|
}
|
|
}
|
|
|
|
if h.hub.rooms[roomName] == nil {
|
|
h.hub.rooms[roomName] = make(map[*Client]bool)
|
|
}
|
|
h.hub.rooms[roomName][client] = true
|
|
h.hub.mu.Unlock()
|
|
|
|
// Log activity
|
|
h.hub.logActivity("client_join_room", client.ID, fmt.Sprintf("Room: %s", roomName))
|
|
|
|
// Notify other clients in the room
|
|
h.hub.broadcaster.BroadcastServerToRoom(roomName, "user_joined_room", map[string]interface{}{
|
|
"client_id": client.ID,
|
|
"user_id": client.UserID,
|
|
"room": roomName,
|
|
"joined_at": time.Now().Unix(),
|
|
})
|
|
|
|
// Send response
|
|
client.sendDirectResponse("room_joined", map[string]interface{}{
|
|
"room": roomName,
|
|
"previous_room": oldRoom,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *RoomHandler) handleLeaveRoom(client *Client, message WebSocketMessage) error {
|
|
oldRoom := client.Room
|
|
|
|
if oldRoom == "" {
|
|
client.sendErrorResponse("Not in any room", "Client is not currently in a room")
|
|
return nil
|
|
}
|
|
|
|
// Update hub room mapping
|
|
h.hub.mu.Lock()
|
|
if roomClients, exists := h.hub.rooms[oldRoom]; exists {
|
|
delete(roomClients, client)
|
|
if len(roomClients) == 0 {
|
|
delete(h.hub.rooms, oldRoom)
|
|
}
|
|
}
|
|
h.hub.mu.Unlock()
|
|
|
|
// Clear client room
|
|
client.Room = ""
|
|
|
|
// Log activity
|
|
h.hub.logActivity("client_leave_room", client.ID, fmt.Sprintf("Room: %s", oldRoom))
|
|
|
|
// Notify other clients in the room
|
|
h.hub.broadcaster.BroadcastServerToRoom(oldRoom, "user_left_room", map[string]interface{}{
|
|
"client_id": client.ID,
|
|
"user_id": client.UserID,
|
|
"room": oldRoom,
|
|
"left_at": time.Now().Unix(),
|
|
})
|
|
|
|
// Send response
|
|
client.sendDirectResponse("room_left", map[string]interface{}{
|
|
"room": oldRoom,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *RoomHandler) handleGetRoomInfo(client *Client, message WebSocketMessage) error {
|
|
h.hub.mu.RLock()
|
|
defer h.hub.mu.RUnlock()
|
|
|
|
roomInfo := make(map[string]interface{})
|
|
|
|
// Info ruangan saat ini
|
|
if client.Room != "" {
|
|
if roomClients, exists := h.hub.rooms[client.Room]; exists {
|
|
clientIDs := make([]string, 0, len(roomClients))
|
|
for client := range roomClients {
|
|
clientIDs = append(clientIDs, client.ID)
|
|
}
|
|
roomInfo["current_room"] = map[string]interface{}{
|
|
"name": client.Room,
|
|
"client_count": len(roomClients),
|
|
"clients": clientIDs,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Info semua ruangan
|
|
allRooms := make(map[string]int)
|
|
for roomName, clients := range h.hub.rooms {
|
|
allRooms[roomName] = len(clients)
|
|
}
|
|
roomInfo["all_rooms"] = allRooms
|
|
roomInfo["total_rooms"] = len(h.hub.rooms)
|
|
|
|
client.sendDirectResponse("room_info", roomInfo)
|
|
|
|
return nil
|
|
}
|
|
|
|
// MonitoringHandler menangani operasi monitoring
|
|
type MonitoringHandler struct {
|
|
hub *Hub
|
|
}
|
|
|
|
func NewMonitoringHandler(hub *Hub) *MonitoringHandler {
|
|
return &MonitoringHandler{hub: hub}
|
|
}
|
|
|
|
func (h *MonitoringHandler) HandleMessage(client *Client, message WebSocketMessage) error {
|
|
switch message.Type {
|
|
case GetStatsMessage:
|
|
return h.handleGetStats(client, message)
|
|
case GetServerStatsMessage:
|
|
return h.handleGetServerStats(client, message)
|
|
case GetSystemHealthMessage:
|
|
return h.handleGetSystemHealth(client, message)
|
|
default:
|
|
return fmt.Errorf("unsupported monitoring message type: %s", message.Type)
|
|
}
|
|
}
|
|
|
|
func (h *MonitoringHandler) MessageType() MessageType {
|
|
return GetStatsMessage // Primary type for registration
|
|
}
|
|
|
|
func (h *MonitoringHandler) handleGetStats(client *Client, message WebSocketMessage) error {
|
|
stats := h.hub.GetStats()
|
|
client.sendDirectResponse("stats", stats)
|
|
return nil
|
|
}
|
|
|
|
func (h *MonitoringHandler) handleGetServerStats(client *Client, message WebSocketMessage) error {
|
|
// Create monitoring manager instance
|
|
monitoringManager := NewMonitoringManager(h.hub)
|
|
detailedStats := monitoringManager.GetDetailedStats()
|
|
client.sendDirectResponse("server_stats", detailedStats)
|
|
return nil
|
|
}
|
|
|
|
func (h *MonitoringHandler) handleGetSystemHealth(client *Client, message WebSocketMessage) error {
|
|
systemHealth := make(map[string]interface{})
|
|
if h.hub.dbService != nil {
|
|
systemHealth["databases"] = h.hub.dbService.Health()
|
|
systemHealth["available_dbs"] = h.hub.dbService.ListDBs()
|
|
}
|
|
systemHealth["websocket_status"] = "healthy"
|
|
systemHealth["uptime_seconds"] = time.Since(h.hub.startTime).Seconds()
|
|
|
|
client.sendDirectResponse("system_health", systemHealth)
|
|
return nil
|
|
}
|
|
|
|
// ConnectionHandler menangani operasi koneksi
|
|
type ConnectionHandler struct {
|
|
hub *Hub
|
|
}
|
|
|
|
func NewConnectionHandler(hub *Hub) *ConnectionHandler {
|
|
return &ConnectionHandler{hub: hub}
|
|
}
|
|
|
|
func (h *ConnectionHandler) HandleMessage(client *Client, message WebSocketMessage) error {
|
|
switch message.Type {
|
|
case PingMessage:
|
|
return h.handlePing(client, message)
|
|
case PongMessage:
|
|
return h.handlePong(client, message)
|
|
case HeartbeatMessage:
|
|
return h.handleHeartbeat(client, message)
|
|
case ConnectionTestMessage:
|
|
return h.handleConnectionTest(client, message)
|
|
case OnlineUsersMessage:
|
|
return h.handleGetOnlineUsers(client, message)
|
|
default:
|
|
return fmt.Errorf("unsupported connection message type: %s", message.Type)
|
|
}
|
|
}
|
|
|
|
func (h *ConnectionHandler) MessageType() MessageType {
|
|
return PingMessage // Primary type for registration
|
|
}
|
|
|
|
func (h *ConnectionHandler) handlePing(client *Client, message WebSocketMessage) error {
|
|
// Send pong response
|
|
pongMsg := NewWebSocketMessage(PongMessage, map[string]interface{}{
|
|
"timestamp": message.Timestamp.Unix(),
|
|
"client_id": client.ID,
|
|
}, client.ID, "")
|
|
|
|
select {
|
|
case client.Send <- pongMsg:
|
|
default:
|
|
// Channel penuh, abaikan
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *ConnectionHandler) handlePong(client *Client, message WebSocketMessage) error {
|
|
// Pong sudah ditangani di level koneksi, tapi kita bisa log aktivitas
|
|
h.hub.logActivity("pong_received", client.ID, "Pong message received")
|
|
return nil
|
|
}
|
|
|
|
func (h *ConnectionHandler) handleHeartbeat(client *Client, message WebSocketMessage) error {
|
|
// Send heartbeat acknowledgment
|
|
heartbeatAck := NewWebSocketMessage(MessageType("heartbeat_ack"), map[string]interface{}{
|
|
"timestamp": time.Now().Unix(),
|
|
"client_uptime": time.Since(client.connectedAt).Seconds(),
|
|
"server_uptime": time.Since(h.hub.startTime).Seconds(),
|
|
"client_id": client.ID,
|
|
}, client.ID, "")
|
|
|
|
select {
|
|
case client.Send <- heartbeatAck:
|
|
default:
|
|
// Channel penuh, abaikan
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *ConnectionHandler) handleConnectionTest(client *Client, message WebSocketMessage) error {
|
|
// Send connection test result
|
|
testResult := NewWebSocketMessage(MessageType("connection_test_result"), map[string]interface{}{
|
|
"timestamp": time.Now().Unix(),
|
|
"client_id": client.ID,
|
|
"connection_status": "healthy",
|
|
"latency_ms": 0, // Could be calculated if ping timestamp is provided
|
|
"uptime_seconds": time.Since(client.connectedAt).Seconds(),
|
|
}, client.ID, "")
|
|
|
|
select {
|
|
case client.Send <- testResult:
|
|
default:
|
|
// Channel penuh, abaikan
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *ConnectionHandler) handleGetOnlineUsers(client *Client, message WebSocketMessage) error {
|
|
h.hub.mu.RLock()
|
|
defer h.hub.mu.RUnlock()
|
|
|
|
users := make([]map[string]interface{}, 0, len(h.hub.clients))
|
|
for client := range h.hub.clients {
|
|
user := map[string]interface{}{
|
|
"id": client.ID,
|
|
"static_id": client.StaticID,
|
|
"user_id": client.UserID,
|
|
"room": client.Room,
|
|
"ip_address": client.IPAddress,
|
|
"connected_at": client.connectedAt,
|
|
"last_ping": client.lastPing,
|
|
"last_pong": client.lastPong,
|
|
"is_active": client.isClientActive(),
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
|
|
response := NewWebSocketMessage(OnlineUsersMessage, map[string]interface{}{
|
|
"users": users,
|
|
"count": len(users),
|
|
}, client.ID, "")
|
|
|
|
select {
|
|
case client.Send <- response:
|
|
default:
|
|
// Channel penuh, abaikan
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetupDefaultHandlers sets up all default message handlers
|
|
func SetupDefaultHandlers(registry *MessageRegistry, hub *Hub) {
|
|
// Register database handler
|
|
dbHandler := NewDatabaseHandler(hub)
|
|
registry.RegisterHandler(dbHandler)
|
|
|
|
// Register admin handler
|
|
adminHandler := NewAdminHandler(hub)
|
|
registry.RegisterHandler(adminHandler)
|
|
|
|
// Register room handler
|
|
roomHandler := NewRoomHandler(hub)
|
|
registry.RegisterHandler(roomHandler)
|
|
|
|
// Register monitoring handler
|
|
monitoringHandler := NewMonitoringHandler(hub)
|
|
registry.RegisterHandler(monitoringHandler)
|
|
|
|
// Register connection handler
|
|
connectionHandler := NewConnectionHandler(hub)
|
|
registry.RegisterHandler(connectionHandler)
|
|
}
|