This commit is contained in:
2026-07-20 06:01:51 +07:00
parent 1ed246eec8
commit aed692cffe
12 changed files with 67 additions and 64 deletions

View File

@@ -46,13 +46,13 @@ cd client
chmod +x build.sh chmod +x build.sh
./build.sh ./build.sh
``` ```
Sau khi build xong, file thực thi sẽ nằm tại: `client/build/bin/simple_care_v1.2` Sau khi build xong, file thực thi sẽ nằm tại: `client/build/bin/simple_care_v1.3`
--- ---
## 2. Cho Người dùng cuối (End-user - Chỉ chạy ứng dụng) ## 2. Cho Người dùng cuối (End-user - Chỉ chạy ứng dụng)
Người dùng cuối **không cần** cài đặt Go, Node hay trình biên dịch C. Họ chỉ cần tệp thực thi `simple_care_v1.2` và cài đặt các thư viện đồ họa cơ bản của hệ thống: Người dùng cuối **không cần** cài đặt Go, Node hay trình biên dịch C. Họ chỉ cần tệp thực thi `simple_care_v1.3` và cài đặt các thư viện đồ họa cơ bản của hệ thống:
* **Ubuntu / Debian / Linux Mint:** * **Ubuntu / Debian / Linux Mint:**
```bash ```bash
@@ -73,8 +73,8 @@ Người dùng cuối **không cần** cài đặt Go, Node hay trình biên d
### Lệnh chạy ứng dụng: ### Lệnh chạy ứng dụng:
Cấp quyền chạy cho file và khởi chạy: Cấp quyền chạy cho file và khởi chạy:
```bash ```bash
chmod +x simple_care_v1.2 chmod +x simple_care_v1.3
./simple_care_v1.2 ./simple_care_v1.3
``` ```
--- ---

View File

@@ -8,8 +8,8 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/json"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"html" "html"
@@ -59,7 +59,7 @@ func getWsUrl(apiBase string) string {
return "ws://" + apiBase return "ws://" + apiBase
} }
var wifiReportLast sync.Map // ssidKey -> time.Time var wifiReportLast sync.Map // ssidKey -> time.Time
func getDynamicEncryptionKey() []byte { func getDynamicEncryptionKey() []byte {
base := "simple-care-proctor-secretkey32!" base := "simple-care-proctor-secretkey32!"
@@ -120,45 +120,45 @@ type StudentData struct {
} }
type App struct { type App struct {
ctx context.Context ctx context.Context
student *StudentData student *StudentData
mu sync.Mutex mu sync.Mutex
sessionPath string sessionPath string
statsPath string statsPath string
webviewDataPath string webviewDataPath string
pendingViolPath string pendingViolPath string
runLockPath string runLockPath string
wsConn *websocket.Conn wsConn *websocket.Conn
wsConnected bool wsConnected bool
wsConnecting bool wsConnecting bool
wsBackoff time.Duration wsBackoff time.Duration
wsWriteMu sync.Mutex wsWriteMu sync.Mutex
wifiSSID string wifiSSID string
wifiBSSID string wifiBSSID string
allowedApps string allowedApps string
onlineSecs int onlineSecs int
offlineSecs int offlineSecs int
unsyncedOn int unsyncedOn int
unsyncedOff int unsyncedOff int
lastSyncTime time.Time lastSyncTime time.Time
isStreamingSc bool isStreamingSc bool
streamScStop chan struct{} streamScStop chan struct{}
isStreamingCam bool isStreamingCam bool
streamCamStop chan struct{} streamCamStop chan struct{}
statusMsg string statusMsg string
expectingLogin bool expectingLogin bool
needsClearPortalStorage bool needsClearPortalStorage bool
backendOnline bool backendOnline bool
dashboard StudentDashboardSnapshot dashboard StudentDashboardSnapshot
wifiEnforce bool wifiEnforce bool
acceptedWifi map[string]bool acceptedWifi map[string]bool
wifiRejected bool wifiRejected bool
quitDialogShown bool quitDialogShown bool
monitoringTornDown bool monitoringTornDown bool
localBrowserActive bool // trình duyệt local — chỉ cho phép localhost localBrowserActive bool // trình duyệt local — chỉ cho phép localhost
chatUnread int chatUnread int
replyStaffID uint replyStaffID uint
fetchAppsMu sync.Mutex fetchAppsMu sync.Mutex
} }
type LocalStats struct { type LocalStats struct {
@@ -219,13 +219,13 @@ func NewApp() *App {
_ = os.MkdirAll(webviewDir, 0755) _ = os.MkdirAll(webviewDir, 0755)
return &App{ return &App{
sessionPath: filepath.Join(appDir, "student_session.json"), sessionPath: filepath.Join(appDir, "student_session.json"),
statsPath: filepath.Join(appDir, "student_stats.json"), statsPath: filepath.Join(appDir, "student_stats.json"),
webviewDataPath: webviewDir, webviewDataPath: webviewDir,
pendingViolPath: filepath.Join(appDir, "pending_violations.json"), pendingViolPath: filepath.Join(appDir, "pending_violations.json"),
runLockPath: filepath.Join(appDir, "monitoring.lock"), runLockPath: filepath.Join(appDir, "monitoring.lock"),
lastSyncTime: time.Now(), lastSyncTime: time.Now(),
expectingLogin: false, expectingLogin: false,
} }
} }
@@ -1473,12 +1473,12 @@ func (a *App) fetchAllowedApps(classId int64) {
reason = "Không đáp ứng điều kiện học tập (chưa cấu hình ứng dụng được phép hoặc ngoài giờ học)." reason = "Không đáp ứng điều kiện học tập (chưa cấu hình ứng dụng được phép hoặc ngoài giờ học)."
} }
log.Printf("[CLIENT] Conditions not met: %s. Blocker suspended.", reason) log.Printf("[CLIENT] Conditions not met: %s. Blocker suspended.", reason)
a.mu.Lock() a.mu.Lock()
a.allowedApps = "" a.allowedApps = ""
a.statusMsg = reason a.statusMsg = reason
a.mu.Unlock() a.mu.Unlock()
blocker.Instance.Stop() blocker.Instance.Stop()
return return
} }
@@ -1490,7 +1490,7 @@ func (a *App) fetchAllowedApps(classId int64) {
a.statusMsg = "Đang trong giờ học" a.statusMsg = "Đang trong giờ học"
} }
a.mu.Unlock() a.mu.Unlock()
log.Printf("[CLIENT] Allowed apps fetched from server: %s", res.Keywords) log.Printf("[CLIENT] Allowed apps fetched from server: %s", res.Keywords)
blocker.Instance.SetKeywords(res.Keywords) blocker.Instance.SetKeywords(res.Keywords)
blocker.Instance.Start() blocker.Instance.Start()
@@ -2401,7 +2401,7 @@ func (a *App) DownloadExamResource(fileID uint) (string, error) {
// LoadExamViewFile tải file đề/tài nguyên qua Go (không mở URL trực tiếp trong WebView). // LoadExamViewFile tải file đề/tài nguyên qua Go (không mở URL trực tiếp trong WebView).
func (a *App) LoadExamViewFile(fileURL string) (map[string]any, error) { func (a *App) LoadExamViewFile(fileURL string) (map[string]any, error) {
fileURL = strings.TrimSpace(fileURL) fileURL = strings.TrimSpace(fileURL)
if !strings.HasPrefix(fileURL, API_BASE + "/api/student/exam/download") { if !strings.HasPrefix(fileURL, API_BASE+"/api/student/exam/download") {
return nil, errors.New("URL không hợp lệ") return nil, errors.New("URL không hợp lệ")
} }
if _, _, err := a.examStudentContext(); err != nil { if _, _, err := a.examStudentContext(); err != nil {
@@ -2477,7 +2477,7 @@ func (a *App) SubmitExamWork() (string, error) {
} }
_ = w.Close() _ = w.Close()
req, err := http.NewRequest("POST", API_BASE + "/api/student/exam/submit", &body) req, err := http.NewRequest("POST", API_BASE+"/api/student/exam/submit", &body)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@@ -1,12 +1,12 @@
{ {
"name": "frontend", "name": "frontend",
"version": "1.2.0", "version": "1.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "frontend", "name": "frontend",
"version": "1.2.0", "version": "1.3.0",
"dependencies": { "dependencies": {
"pdfjs-dist": "^3.11.174" "pdfjs-dist": "^3.11.174"
}, },

View File

@@ -1,7 +1,7 @@
{ {
"name": "frontend", "name": "frontend",
"private": true, "private": true,
"version": "1.2.0", "version": "1.3.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",

View File

@@ -1 +1 @@
06a16f2c007333cdf273a92d4914c63b 792441f7861d7badf1335f3778fcdebd

View File

@@ -19,7 +19,7 @@ if (typeof window.go.main.App === 'undefined') {
} }
const brandLogoHtml = `<img src="${logoUrl}" alt="Simple Care" class="brand-logo-img" />`; const brandLogoHtml = `<img src="${logoUrl}" alt="Simple Care" class="brand-logo-img" />`;
const APP_VERSION = '1.2'; const APP_VERSION = '1.3';
// Trạng thái cục bộ // Trạng thái cục bộ
let loggedIn = false; let loggedIn = false;

View File

@@ -173,6 +173,7 @@ var systemAllowed = map[string]bool{
"simple_care_v1.0": true, "simple_care_v1.0": true,
"simple_care_v1.1": true, "simple_care_v1.1": true,
"simple_care_v1.2": true, "simple_care_v1.2": true,
"simple_care_v1.3": true,
"simple_care": true, "simple_care": true,
"wails": true, "wails": true,
"code": true, // VSCode "code": true, // VSCode

View File

@@ -238,6 +238,7 @@ var systemAllowed = map[string]bool{
"simple_care_v1.0": true, "simple_care_v1.0": true,
"simple_care_v1.1": true, "simple_care_v1.1": true,
"simple_care_v1.2": true, "simple_care_v1.2": true,
"simple_care_v1.3": true,
"simple_care": true, "simple_care": true,
} }

View File

@@ -331,6 +331,7 @@ var systemAllowed = map[string]bool{
"simple_care_v1.0.exe": true, "simple_care_v1.0.exe": true,
"simple_care_v1.1.exe": true, "simple_care_v1.1.exe": true,
"simple_care_v1.2.exe": true, "simple_care_v1.2.exe": true,
"simple_care_v1.3.exe": true,
"simple_care.exe": true, "simple_care.exe": true,
"wails.exe": true, "wails.exe": true,
"msedgewebview2.exe": true, // WebView2 runtime (Wails renderer) "msedgewebview2.exe": true, // WebView2 runtime (Wails renderer)

View File

@@ -22,13 +22,13 @@ func resizeRGBA(src *image.RGBA, width, height int) *image.RGBA {
minY := srcBounds.Min.Y minY := srcBounds.Min.Y
for y := 0; y < height; y++ { for y := 0; y < height; y++ {
srcY := minY + (y * dy) / height srcY := minY + (y*dy)/height
for x := 0; x < width; x++ { for x := 0; x < width; x++ {
srcX := minX + (x * dx) / width srcX := minX + (x*dx)/width
srcOffset := src.PixOffset(srcX, srcY) srcOffset := src.PixOffset(srcX, srcY)
dstOffset := dst.PixOffset(x, y) dstOffset := dst.PixOffset(x, y)
copy(dst.Pix[dstOffset:dstOffset+4], src.Pix[srcOffset:srcOffset+4]) copy(dst.Pix[dstOffset:dstOffset+4], src.Pix[srcOffset:srcOffset+4])
} }
} }

View File

@@ -62,7 +62,7 @@ func main() {
// Create application with options // Create application with options
err := wails.Run(&options.App{ err := wails.Run(&options.App{
Title: "Simple Care v1.2 — Rikkei Education", Title: "Simple Care v1.3 — Rikkei Education",
Width: 1024, Width: 1024,
Height: 768, Height: 768,
Menu: appMenu, Menu: appMenu,

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://wails.io/schemas/config.v2.json", "$schema": "https://wails.io/schemas/config.v2.json",
"name": "Simple Care by Rikkei Edu", "name": "Simple Care by Rikkei Edu",
"outputfilename": "simple_care_v1.2", "outputfilename": "simple_care_v1.3",
"frontend:install": "npm install", "frontend:install": "npm install",
"frontend:build": "npm run build", "frontend:build": "npm run build",
"frontend:dev:watcher": "npm run dev", "frontend:dev:watcher": "npm run dev",