fix
All checks were successful
Deploy on Master Change / deploy (push) Successful in 1m45s

This commit is contained in:
2026-07-20 06:06:54 +07:00
parent aed692cffe
commit 6ee9773f7f
15 changed files with 561 additions and 447 deletions

View File

@@ -291,43 +291,54 @@ func (a *App) handleGuardViolation(kind, reason string) {
a.showQuitDialog("Vi phạm giám sát", reason)
}
// HandleBeforeClose — SV bấm X / Alt+F4 khi đang giám sát = vi phạm.
// Trả về false để cho phép đóng sau khi đã báo cáo.
// HandleBeforeClose — SV bấm X / Alt+F4: luôn hỏi xác nhận.
// Có → thoát + lưu vết vi phạm (nếu đang giám sát exam/learning). Không → hủy đóng.
func (a *App) HandleBeforeClose() (prevent bool) {
if !a.CheckLoginStatus() || !a.isMonitoringActive() {
a.clearRunLock()
return false
}
a.mu.Lock()
mode := a.dashboard.MonitorMode
loggedIn := a.student != nil
a.mu.Unlock()
if !shouldRecordViolation(mode) {
a.clearRunLock()
return false
message := "Bạn có chắc muốn thoát Simple Care không?"
if loggedIn && a.isMonitoringActive() && shouldRecordViolation(mode) {
message = "Bạn có chắc chắn muốn thoát ứng dụng không?\n\nLưu ý: Thoát khi đang trong giờ thi/học sẽ được ghi nhận là VI PHẠM."
}
selection, err := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.QuestionDialog,
Title: "Xác nhận thoát",
Message: "Bạn có chắc chắn muốn thoát ứng dụng không?\n\nLưu ý: Thoát ứng dụng khi đang trong giờ thi/học sẽ được ghi nhận là VI PHẠM.",
Message: message,
Buttons: []string{"Có, thoát ứng dụng", "Không, tiếp tục"},
DefaultButton: "Không, tiếp tục",
CancelButton: "Không, tiếp tục",
})
if err != nil {
a.reportViolation("app_closed", "Sinh viên tự đóng ứng dụng khi đang giám sát ("+mode+")")
a.tearDownBeforeQuit()
a.clearRunLock()
return false
log.Printf("[CLOSE] MessageDialog error: %v — hủy thoát để an toàn", err)
return true
}
if selection == "Có, thoát ứng dụng" {
a.reportViolation("app_closed", "Sinh viên tự đóng ứng dụng khi đang giám sát ("+mode+")")
a.tearDownBeforeQuit()
a.clearRunLock()
return false
if !isConfirmQuitSelection(selection) {
return true // Không / Cancel → ở lại
}
return true // Prevent close!
// Có → gửi vi phạm lên server rồi mới thoát
if loggedIn && a.isMonitoringActive() && shouldRecordViolation(mode) {
a.reportViolation("app_closed", "Sinh viên xác nhận tắt ứng dụng khi đang giám sát ("+mode+")")
a.tearDownBeforeQuit()
}
a.clearRunLock()
log.Printf("[CLOSE] User confirmed quit (mode=%s, loggedIn=%v)", mode, loggedIn)
return false
}
func isConfirmQuitSelection(selection string) bool {
s := strings.TrimSpace(strings.ToLower(selection))
switch s {
case "có, thoát ứng dụng", "co, thoat ung dung", "yes", "ok", "có", "co":
return true
}
// Windows đôi khi trả về đúng nhãn nút đã truyền
return strings.Contains(s, "thoát") || strings.Contains(s, "thoat") || s == "yes"
}
// tearDownBeforeQuit ngắt mọi kênh giám sát ngay — trước khi hiện dialog (tránh treo OK để duy trì kết nối).
@@ -380,6 +391,8 @@ type pendingViolation struct {
MonitorMode string `json:"monitorMode"`
ClientAt string `json:"clientAt"`
StudentRkID int64 `json:"studentRkId"`
ClassRkID int64 `json:"classRkId"`
ExamRoomID uint `json:"examRoomId"`
}
// shouldRecordViolation — chỉ lưu vi phạm khi đang thi hoặc đang học (trong giờ).
@@ -396,6 +409,11 @@ func (a *App) markRunLock() {
a.mu.Lock()
studentID := int64(0)
mode := a.dashboard.MonitorMode
classID := a.dashboard.ClassRkID
examRoomID := uint(0)
if a.dashboard.Exam != nil {
examRoomID = a.dashboard.Exam.ExamRoomID
}
if a.student != nil {
studentID = a.student.StudentID
}
@@ -406,6 +424,8 @@ func (a *App) markRunLock() {
payload, _ := json.Marshal(map[string]any{
"studentRkId": studentID,
"monitorMode": mode,
"classRkId": classID,
"examRoomId": examRoomID,
"startedAt": time.Now().Format(time.RFC3339),
})
_ = os.WriteFile(a.runLockPath, payload, 0644)
@@ -425,6 +445,8 @@ func (a *App) detectUncleanShutdown() {
var meta struct {
StudentRkID int64 `json:"studentRkId"`
MonitorMode string `json:"monitorMode"`
ClassRkID int64 `json:"classRkId"`
ExamRoomID uint `json:"examRoomId"`
StartedAt string `json:"startedAt"`
}
_ = json.Unmarshal(data, &meta)
@@ -448,6 +470,8 @@ func (a *App) detectUncleanShutdown() {
MonitorMode: meta.MonitorMode,
ClientAt: time.Now().Format(time.RFC3339),
StudentRkID: meta.StudentRkID,
ClassRkID: meta.ClassRkID,
ExamRoomID: meta.ExamRoomID,
})
}
@@ -499,9 +523,11 @@ func (a *App) postViolation(v pendingViolation) bool {
"reason": v.Reason,
"monitorMode": v.MonitorMode,
"clientAt": v.ClientAt,
"classRkId": v.ClassRkID,
"examRoomId": v.ExamRoomID,
}
bodyBytes, _ := json.Marshal(payload)
client := http.Client{Timeout: 5 * time.Second}
client := http.Client{Timeout: 8 * time.Second}
resp, err := client.Post(API_BASE+"/api/student/report-violation", "application/json", bytes.NewBuffer(bodyBytes))
if err != nil {
log.Printf("[VIOLATION] report failed: %v", err)
@@ -513,7 +539,7 @@ func (a *App) postViolation(v pendingViolation) bool {
log.Printf("[VIOLATION] report status %d: %s", resp.StatusCode, string(body))
return false
}
log.Printf("[VIOLATION] reported kind=%s student=%d", v.Kind, v.StudentRkID)
log.Printf("[VIOLATION] reported kind=%s student=%d class=%d exam=%d", v.Kind, v.StudentRkID, v.ClassRkID, v.ExamRoomID)
return true
}
@@ -521,6 +547,11 @@ func (a *App) reportViolation(kind, reason string) {
a.mu.Lock()
studentID := int64(0)
mode := a.dashboard.MonitorMode
classID := a.dashboard.ClassRkID
examRoomID := uint(0)
if a.dashboard.Exam != nil {
examRoomID = a.dashboard.Exam.ExamRoomID
}
if a.student != nil {
studentID = a.student.StudentID
}
@@ -534,6 +565,8 @@ func (a *App) reportViolation(kind, reason string) {
MonitorMode: mode,
ClientAt: time.Now().Format(time.RFC3339),
StudentRkID: studentID,
ClassRkID: classID,
ExamRoomID: examRoomID,
}
if !a.postViolation(v) {
a.enqueuePendingViolation(v)