diff --git a/management/src/index.css b/management/src/index.css index 66c2238..4028697 100644 --- a/management/src/index.css +++ b/management/src/index.css @@ -5197,6 +5197,14 @@ input:checked + .slider:before { color: #ffffff; } +.grid-proctor-container { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow-y: auto; +} + .grid-proctor-layout { /* Columns configured inline */ transition: all 0.3s ease; diff --git a/server/internal/websocket/websocket.go b/server/internal/websocket/websocket.go index 48af6a5..984e198 100644 --- a/server/internal/websocket/websocket.go +++ b/server/internal/websocket/websocket.go @@ -23,8 +23,16 @@ type SocketClient struct { StaffID uint Role string // "student" | "teacher" Addr string + writeMu sync.Mutex } +func (c *SocketClient) WriteJSON(v any) error { + c.writeMu.Lock() + defer c.writeMu.Unlock() + return c.Conn.WriteJSON(v) +} + + type WsHub struct { mu sync.RWMutex students map[int64]*SocketClient @@ -54,7 +62,7 @@ func (h *WsHub) PushExamToStudent(studentRkID int64, data map[string]any) { if !ok || client == nil { return } - _ = client.Conn.WriteJSON(SocketMsg{Event: "exam:paper-sent", Data: data}) + _ = client.WriteJSON(SocketMsg{Event: "exam:paper-sent", Data: data}) } func (h *WsHub) PushChatToStudent(studentRkID int64, data map[string]any) { @@ -64,7 +72,7 @@ func (h *WsHub) PushChatToStudent(studentRkID int64, data map[string]any) { if !ok || client == nil { return } - _ = client.Conn.WriteJSON(SocketMsg{Event: "chat:message", Data: data}) + _ = client.WriteJSON(SocketMsg{Event: "chat:message", Data: data}) } func (h *WsHub) PushChatToStaff(staffID uint, data map[string]any) { @@ -77,7 +85,7 @@ func (h *WsHub) PushChatToStaff(staffID uint, data map[string]any) { defer h.mu.RUnlock() for _, t := range h.teachers { if t != nil && t.Role == "teacher" { - _ = t.Conn.WriteJSON(msg) + _ = t.WriteJSON(msg) } } } @@ -107,8 +115,8 @@ func (h *WsHub) Register(c *SocketClient) { h.students[c.StudentID] = c log.Printf("[WS] Student %d registered (Address: %s, Class: %d)", c.StudentID, c.Addr, c.ClassID) if subs, exists := h.subscribers[c.StudentID]; exists && len(subs) > 0 { - _ = c.Conn.WriteJSON(SocketMsg{Event: "start_screenshot_stream"}) - _ = c.Conn.WriteJSON(SocketMsg{Event: "start_webcam_stream"}) + _ = c.WriteJSON(SocketMsg{Event: "start_screenshot_stream"}) + _ = c.WriteJSON(SocketMsg{Event: "start_webcam_stream"}) log.Printf("[WS] Student %d has active subscribers. Sent start stream commands.", c.StudentID) } } else if c.Role == "teacher" { @@ -132,7 +140,7 @@ func (h *WsHub) Unregister(c *SocketClient) { if teachers, exists := h.subscribers[c.StudentID]; exists { for _, tAddr := range teachers { if t, found := h.teachers[tAddr]; found { - _ = t.Conn.WriteJSON(SocketMsg{ + _ = t.WriteJSON(SocketMsg{ Event: "teacher:stream-stopped", Data: map[string]any{"studentId": c.StudentID}, }) @@ -170,8 +178,8 @@ func (h *WsHub) Unregister(c *SocketClient) { delete(h.subscribers, sID) // Nếu không còn ai xem học sinh này, gửi lệnh tắt camera/screen cho client học sinh if student, exists := h.students[sID]; exists { - _ = student.Conn.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"}) - _ = student.Conn.WriteJSON(SocketMsg{Event: "stop_webcam_stream"}) + _ = student.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"}) + _ = student.WriteJSON(SocketMsg{Event: "stop_webcam_stream"}) } } else { h.subscribers[sID] = newList @@ -201,8 +209,8 @@ func (h *WsHub) Subscribe(teacherAddr string, studentID int64) { // Phát lệnh cho máy học sinh bật stream (nếu học sinh đang online) if student, exists := h.students[studentID]; exists { - _ = student.Conn.WriteJSON(SocketMsg{Event: "start_screenshot_stream"}) - _ = student.Conn.WriteJSON(SocketMsg{Event: "start_webcam_stream"}) + _ = student.WriteJSON(SocketMsg{Event: "start_screenshot_stream"}) + _ = student.WriteJSON(SocketMsg{Event: "start_webcam_stream"}) } } @@ -229,8 +237,8 @@ func (h *WsHub) Unsubscribe(teacherAddr string, studentID int64) { // Báo học sinh tắt camera & screen stream để tiết kiệm mạng và CPU if student, exists := h.students[studentID]; exists { - _ = student.Conn.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"}) - _ = student.Conn.WriteJSON(SocketMsg{Event: "stop_webcam_stream"}) + _ = student.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"}) + _ = student.WriteJSON(SocketMsg{Event: "stop_webcam_stream"}) } } else { h.subscribers[studentID] = newList @@ -262,7 +270,7 @@ func (h *WsHub) RelayFrame(studentID int64, event string, data map[string]any) { for _, addr := range teachersList { if t, found := h.teachers[addr]; found { - _ = t.Conn.WriteJSON(msg) + _ = t.WriteJSON(msg) } } } diff --git a/server/server.exe b/server/server.exe index 7c7e053..c4ab49b 100644 Binary files a/server/server.exe and b/server/server.exe differ