zerolin1024 commited on
Commit
2196bfe
·
verified ·
1 Parent(s): d1114b0

Upload 16 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ .env
2
+ .git
3
+ .gitignore
4
+ README.md
5
+ Dockerfile
6
+ .dockerignore
7
+ test.db
8
+ *.log
9
+ .DS_Store
.env ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # For local development, use sqlite
2
+ DB_DIALECT=sqlite
3
+
4
+ # For production, comment out the line above and use mysql
5
+ # DB_DIALECT=mysql
6
+ # DATABASE_URI="user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
7
+ SECRET="asupersecretkey"
8
+
9
+ ADMIN_USERNAME="admin"
10
+ ADMIN_PASSWORD="password"
11
+
12
+ CORS_ALLOWED_ORIGINS="http://localhost:3000,http://192.168.2.4:3000"
.env.example ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ DB_DIALECT=mysql
2
+ DATABASE_URI="user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
3
+
4
+ REDIS_URI="rediss://user:password@host:port/db"
5
+ SECRET="asupersecretkey"
6
+
7
+ ADMIN_USERNAME="admin"
8
+ ADMIN_PASSWORD="password"
9
+
10
+ CORS_ALLOWED_ORIGINS="http://localhost:3000,http://192.168.2.4:3000"
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Go 镜像作为构建环境
2
+ FROM golang:1.21-alpine AS builder
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 复制 go mod 文件
8
+ COPY go.mod go.sum ./
9
+
10
+ # 下载依赖
11
+ RUN go mod download
12
+
13
+ # 复制源代码
14
+ COPY . .
15
+
16
+ # 构建应用
17
+ RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o main .
18
+
19
+ # 使用轻量级的 Alpine Linux 作为运行环境
20
+ FROM alpine:latest
21
+
22
+ # 安装必要的包
23
+ RUN apk --no-cache add ca-certificates sqlite
24
+
25
+ # 设置工作目录
26
+ WORKDIR /root/
27
+
28
+ # 从构建阶段复制可执行文件
29
+ COPY --from=builder /app/main .
30
+
31
+ # 环境变量将在运行时传入,而不是在构建时。
32
+
33
+ # 暴露端口
34
+ EXPOSE 8080
35
+
36
+ # 运行应用
37
+ CMD ["./main"]
controllers/auth.go ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "net/http"
5
+ "os"
6
+ "time"
7
+ "uptime/backend/database"
8
+ "uptime/backend/models"
9
+
10
+ "github.com/gin-gonic/gin"
11
+ "github.com/golang-jwt/jwt/v5"
12
+ "golang.org/x/crypto/bcrypt"
13
+ )
14
+
15
+ func Login(c *gin.Context) {
16
+ var body struct {
17
+ Username string `json:"username"`
18
+ Password string `json:"password"`
19
+ }
20
+
21
+ if err := c.BindJSON(&body); err != nil {
22
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read body"})
23
+ return
24
+ }
25
+
26
+ var user models.User
27
+ database.DB.First(&user, "username = ?", body.Username)
28
+
29
+ if user.ID == 0 {
30
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid username or password"})
31
+ return
32
+ }
33
+
34
+ err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password))
35
+ if err != nil {
36
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid username or password"})
37
+ return
38
+ }
39
+
40
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
41
+ "sub": user.ID,
42
+ "exp": time.Now().Add(time.Hour * 24 * 30).Unix(),
43
+ })
44
+
45
+ tokenString, err := token.SignedString([]byte(os.Getenv("SECRET")))
46
+ if err != nil {
47
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to create token"})
48
+ return
49
+ }
50
+
51
+ c.SetSameSite(http.SameSiteLaxMode)
52
+ c.SetCookie("Authorization", tokenString, 3600*24*30, "/", "", false, true)
53
+
54
+ c.JSON(http.StatusOK, gin.H{})
55
+ }
controllers/monitor.go ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "net/http"
5
+ "uptime/backend/database"
6
+ "uptime/backend/models"
7
+ "uptime/backend/services"
8
+
9
+ "github.com/gin-gonic/gin"
10
+ )
11
+
12
+ // CreateMonitor creates a new monitor
13
+ func CreateMonitor(c *gin.Context) {
14
+ var monitor models.Monitor
15
+ if err := c.ShouldBindJSON(&monitor); err != nil {
16
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
17
+ return
18
+ }
19
+
20
+ database.DB.Create(&monitor)
21
+ c.JSON(http.StatusOK, monitor)
22
+ }
23
+
24
+ // GetMonitors retrieves all monitors
25
+ func GetMonitors(c *gin.Context) {
26
+ var monitors []models.Monitor
27
+ database.DB.Find(&monitors)
28
+
29
+ type MonitorWithLatency struct {
30
+ models.Monitor
31
+ Latency uint `json:"latency"`
32
+ }
33
+
34
+ var monitorsWithLatency []MonitorWithLatency
35
+
36
+ for _, m := range monitors {
37
+ var latency models.Latency
38
+ database.DB.Where("monitor_id = ?", m.ID).Order("created_at desc").First(&latency)
39
+ monitorsWithLatency = append(monitorsWithLatency, MonitorWithLatency{
40
+ Monitor: m,
41
+ Latency: latency.Latency,
42
+ })
43
+ }
44
+
45
+ c.JSON(http.StatusOK, monitorsWithLatency)
46
+ }
47
+
48
+ // GetMonitor retrieves a single monitor by ID
49
+ func GetMonitor(c *gin.Context) {
50
+ id := c.Params.ByName("id")
51
+ var monitor models.Monitor
52
+ if err := database.DB.First(&monitor, id).Error; err != nil {
53
+ c.JSON(http.StatusNotFound, gin.H{"error": "Monitor not found"})
54
+ return
55
+ }
56
+
57
+ var latencies []models.Latency
58
+ database.DB.Where("monitor_id = ?", monitor.ID).Order("created_at desc").Limit(100).Find(&latencies)
59
+
60
+ c.JSON(http.StatusOK, gin.H{
61
+ "monitor": monitor,
62
+ "latencies": latencies,
63
+ })
64
+ }
65
+
66
+ // UpdateMonitor updates an existing monitor
67
+ func UpdateMonitor(c *gin.Context) {
68
+ id := c.Params.ByName("id")
69
+ var monitor models.Monitor
70
+ if err := database.DB.First(&monitor, id).Error; err != nil {
71
+ c.JSON(http.StatusNotFound, gin.H{"error": "Monitor not found"})
72
+ return
73
+ }
74
+
75
+ if err := c.ShouldBindJSON(&monitor); err != nil {
76
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
77
+ return
78
+ }
79
+
80
+ database.DB.Save(&monitor)
81
+ c.JSON(http.StatusOK, monitor)
82
+ }
83
+
84
+ // DeleteMonitor deletes a monitor
85
+ func DeleteMonitor(c *gin.Context) {
86
+ id := c.Params.ByName("id")
87
+ var monitor models.Monitor
88
+ if err := database.DB.First(&monitor, id).Error; err != nil {
89
+ c.JSON(http.StatusNotFound, gin.H{"error": "Monitor not found"})
90
+ return
91
+ }
92
+ database.DB.Unscoped().Delete(&monitor)
93
+ services.DeletedMonitorIDs <- monitor.ID
94
+ c.JSON(http.StatusOK, gin.H{"message": "Monitor deleted successfully"})
95
+ }
96
+
97
+ // ManualCheck triggers a manual check for a specific monitor
98
+ func ManualCheck(c *gin.Context) {
99
+ id := c.Params.ByName("id")
100
+ var monitor models.Monitor
101
+ if err := database.DB.First(&monitor, id).Error; err != nil {
102
+ c.JSON(http.StatusNotFound, gin.H{"error": "Monitor not found"})
103
+ return
104
+ }
105
+
106
+ // TODO: Trigger manual check - for now just update status to pending
107
+ monitor.Status = "pending"
108
+ database.DB.Save(&monitor)
109
+
110
+ c.JSON(http.StatusOK, gin.H{"message": "Manual check triggered", "monitor": monitor})
111
+ }
database/database.go ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package database
2
+
3
+ import (
4
+ "log"
5
+ "os"
6
+ "strings"
7
+ "uptime/backend/models"
8
+
9
+ "github.com/glebarez/sqlite"
10
+ "gorm.io/driver/mysql"
11
+ "gorm.io/gorm"
12
+ )
13
+
14
+ var DB *gorm.DB
15
+
16
+ func ConnectDatabase() {
17
+ var err error
18
+ dsn := os.Getenv("DATABASE_URI")
19
+ if dsn == "" {
20
+ log.Println("DATABASE_URI is not set, falling back to sqlite.")
21
+ DB, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
22
+ } else if strings.HasPrefix(dsn, "mysql://") {
23
+ // GORM's MySQL driver expects a DSN without the scheme.
24
+ // Example: user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
25
+ mysqlDSN := strings.TrimPrefix(dsn, "mysql://")
26
+ DB, err = gorm.Open(mysql.Open(mysqlDSN), &gorm.Config{})
27
+ } else {
28
+ log.Fatalf("Unsupported database URI scheme: %s", dsn)
29
+ }
30
+
31
+ if err != nil {
32
+ log.Fatal("Failed to connect to database!", err)
33
+ }
34
+
35
+ DB.AutoMigrate(&models.Monitor{}, &models.Latency{}, &models.User{})
36
+ }
database/redis.go ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package database
2
+
3
+ import (
4
+ "context"
5
+ "crypto/tls"
6
+ "log"
7
+ "os"
8
+ "strings"
9
+
10
+ "github.com/go-redis/redis/v8"
11
+ )
12
+
13
+ var RDB *redis.Client
14
+
15
+ func ConnectRedis() {
16
+ redisURI := os.Getenv("REDIS_URI")
17
+ if redisURI == "" {
18
+ log.Println("REDIS_URI is not set, skipping Redis connection.")
19
+ return
20
+ }
21
+
22
+ opt, err := redis.ParseURL(redisURI)
23
+ if err != nil {
24
+ log.Fatalf("Failed to parse Redis URI: %v", err)
25
+ }
26
+
27
+ // The `rediss` scheme implies TLS. The go-redis library handles this automatically
28
+ // if the URI scheme is "rediss". For explicit clarity or custom TLS configs,
29
+ // you could add a TLS config to opt.TLSConfig.
30
+ if strings.HasPrefix(redisURI, "rediss://") {
31
+ opt.TLSConfig = &tls.Config{
32
+ MinVersion: tls.VersionTLS12,
33
+ }
34
+ }
35
+
36
+ RDB = redis.NewClient(opt)
37
+
38
+ ctx := context.Background()
39
+ _, err = RDB.Ping(ctx).Result()
40
+ if err != nil {
41
+ log.Fatalf("Failed to connect to Redis: %v", err)
42
+ }
43
+
44
+ log.Println("Successfully connected to Redis.")
45
+ }
go.mod ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module uptime/backend
2
+
3
+ go 1.24.1
4
+
5
+ require (
6
+ filippo.io/edwards25519 v1.1.0 // indirect
7
+ github.com/bytedance/sonic v1.13.3 // indirect
8
+ github.com/bytedance/sonic/loader v0.3.0 // indirect
9
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
10
+ github.com/cloudwego/base64x v0.1.5 // indirect
11
+ github.com/cloudwego/iasm v0.2.0 // indirect
12
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
13
+ github.com/dustin/go-humanize v1.0.1 // indirect
14
+ github.com/gabriel-vasile/mimetype v1.4.9 // indirect
15
+ github.com/gin-contrib/cors v1.7.6 // indirect
16
+ github.com/gin-contrib/sse v1.1.0 // indirect
17
+ github.com/gin-gonic/gin v1.10.1 // indirect
18
+ github.com/glebarez/go-sqlite v1.21.2 // indirect
19
+ github.com/glebarez/sqlite v1.11.0 // indirect
20
+ github.com/go-playground/locales v0.14.1 // indirect
21
+ github.com/go-playground/universal-translator v0.18.1 // indirect
22
+ github.com/go-playground/validator/v10 v10.27.0 // indirect
23
+ github.com/go-redis/redis/v8 v8.11.5 // indirect
24
+ github.com/go-sql-driver/mysql v1.9.3 // indirect
25
+ github.com/goccy/go-json v0.10.5 // indirect
26
+ github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
27
+ github.com/google/uuid v1.6.0 // indirect
28
+ github.com/jinzhu/inflection v1.0.0 // indirect
29
+ github.com/jinzhu/now v1.1.5 // indirect
30
+ github.com/joho/godotenv v1.5.1 // indirect
31
+ github.com/json-iterator/go v1.1.12 // indirect
32
+ github.com/klauspost/cpuid/v2 v2.2.11 // indirect
33
+ github.com/leodido/go-urn v1.4.0 // indirect
34
+ github.com/mattn/go-isatty v0.0.20 // indirect
35
+ github.com/mattn/go-sqlite3 v1.14.22 // indirect
36
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
37
+ github.com/modern-go/reflect2 v1.0.2 // indirect
38
+ github.com/ncruces/go-strftime v0.1.9 // indirect
39
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
40
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
41
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
42
+ github.com/ugorji/go/codec v1.3.0 // indirect
43
+ golang.org/x/arch v0.19.0 // indirect
44
+ golang.org/x/crypto v0.40.0 // indirect
45
+ golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
46
+ golang.org/x/net v0.42.0 // indirect
47
+ golang.org/x/sys v0.34.0 // indirect
48
+ golang.org/x/text v0.27.0 // indirect
49
+ google.golang.org/protobuf v1.36.6 // indirect
50
+ gopkg.in/yaml.v3 v3.0.1 // indirect
51
+ gorm.io/driver/mysql v1.6.0 // indirect
52
+ gorm.io/driver/sqlite v1.6.0 // indirect
53
+ gorm.io/gorm v1.30.0 // indirect
54
+ modernc.org/libc v1.65.10 // indirect
55
+ modernc.org/mathutil v1.7.1 // indirect
56
+ modernc.org/memory v1.11.0 // indirect
57
+ modernc.org/sqlite v1.38.0 // indirect
58
+ )
go.sum ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
2
+ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
3
+ github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
4
+ github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
5
+ github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
6
+ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
7
+ github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
8
+ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
9
+ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
10
+ github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
11
+ github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
12
+ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
13
+ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
14
+ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
17
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
18
+ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
19
+ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
20
+ github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
21
+ github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
22
+ github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
23
+ github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
24
+ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
25
+ github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
26
+ github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
27
+ github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
28
+ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
29
+ github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
30
+ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
31
+ github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
32
+ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
33
+ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
34
+ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
35
+ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
36
+ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
37
+ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
38
+ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
39
+ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
40
+ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
41
+ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
42
+ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
43
+ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
44
+ github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
45
+ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
46
+ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
47
+ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
48
+ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
49
+ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
50
+ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
51
+ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
52
+ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
53
+ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
54
+ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
55
+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
56
+ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
57
+ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
58
+ github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
59
+ github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
60
+ github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
61
+ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
62
+ github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
63
+ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
64
+ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
65
+ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
66
+ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
67
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
68
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
69
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
70
+ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
71
+ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
72
+ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
73
+ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
74
+ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
75
+ github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
76
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
77
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
78
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
79
+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
80
+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
81
+ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
82
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
83
+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
84
+ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
85
+ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
86
+ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
87
+ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
88
+ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
89
+ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
90
+ github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
91
+ golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
92
+ golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
93
+ golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
94
+ golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
95
+ golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
96
+ golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
97
+ golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
98
+ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
99
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
100
+ golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
101
+ golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
102
+ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
103
+ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
104
+ google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
105
+ google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
106
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
107
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
108
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
109
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
110
+ gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
111
+ gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
112
+ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
113
+ gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
114
+ gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
115
+ gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
116
+ modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
117
+ modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
118
+ modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
119
+ modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
120
+ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
121
+ modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
122
+ modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
123
+ modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
124
+ nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
125
+ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
main.go ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "log"
5
+ "os"
6
+ "strings"
7
+ "uptime/backend/controllers"
8
+ "uptime/backend/database"
9
+ "uptime/backend/middleware"
10
+ "uptime/backend/models"
11
+ "uptime/backend/services"
12
+
13
+ "github.com/gin-contrib/cors"
14
+ "github.com/gin-gonic/gin"
15
+ "github.com/joho/godotenv"
16
+ "golang.org/x/crypto/bcrypt"
17
+ )
18
+
19
+ func main() {
20
+ // 在开发环境中加载 .env 文件,如果文件不存在则忽略错误
21
+ // 在生产环境中,变量将通过环境变量直接提供
22
+ if err := godotenv.Load(); err != nil {
23
+ log.Println("No .env file found, using environment variables")
24
+ }
25
+
26
+ database.ConnectDatabase()
27
+ database.ConnectRedis()
28
+
29
+ createAdminUser()
30
+
31
+ go services.StartMonitoring()
32
+
33
+ r := gin.Default()
34
+ config := cors.DefaultConfig()
35
+ origins := os.Getenv("CORS_ALLOWED_ORIGINS")
36
+ if origins == "" {
37
+ origins = "http://localhost:3000" // Default for local development
38
+ }
39
+ config.AllowOrigins = strings.Split(origins, ",")
40
+ config.AllowCredentials = true
41
+ r.Use(cors.New(config))
42
+
43
+ api := r.Group("/api")
44
+ {
45
+ api.POST("/login", controllers.Login)
46
+
47
+ authorized := api.Group("/")
48
+ authorized.Use(middleware.RequireAuth)
49
+ {
50
+ authorized.POST("/monitors", controllers.CreateMonitor)
51
+ authorized.GET("/monitors", controllers.GetMonitors)
52
+ authorized.GET("/monitors/:id", controllers.GetMonitor)
53
+ authorized.PUT("/monitors/:id", controllers.UpdateMonitor)
54
+ authorized.DELETE("/monitors/:id", controllers.DeleteMonitor)
55
+ authorized.POST("/monitors/:id/check", controllers.ManualCheck)
56
+ }
57
+ }
58
+
59
+ r.GET("/ping", func(c *gin.Context) {
60
+ c.JSON(200, gin.H{
61
+ "message": "pong",
62
+ })
63
+ })
64
+ r.Run() // listen and serve on 0.0.0.0:8080
65
+ }
66
+
67
+ func createAdminUser() {
68
+ username := os.Getenv("ADMIN_USERNAME")
69
+ password := os.Getenv("ADMIN_PASSWORD")
70
+
71
+ if username == "" || password == "" {
72
+ log.Println("ADMIN_USERNAME or ADMIN_PASSWORD not set, skipping admin user creation.")
73
+ return
74
+ }
75
+
76
+ var user models.User
77
+ database.DB.First(&user, "username = ?", username)
78
+
79
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 10)
80
+ if err != nil {
81
+ log.Fatalf("Failed to hash password: %v", err)
82
+ }
83
+
84
+ if user.ID == 0 {
85
+ // Create new admin user
86
+ admin := models.User{Username: username, Password: string(hashedPassword)}
87
+ if err := database.DB.Create(&admin).Error; err != nil {
88
+ log.Fatalf("Failed to create admin user: %v", err)
89
+ }
90
+ log.Println("Admin user created successfully.")
91
+ } else {
92
+ // Update existing admin user's password
93
+ if err := database.DB.Model(&user).Update("password", string(hashedPassword)).Error; err != nil {
94
+ log.Fatalf("Failed to update admin user: %v", err)
95
+ }
96
+ log.Println("Admin user password updated successfully.")
97
+ }
98
+ }
middleware/requireAuth.go ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "fmt"
5
+ "net/http"
6
+ "os"
7
+ "time"
8
+ "uptime/backend/database"
9
+ "uptime/backend/models"
10
+
11
+ "github.com/gin-gonic/gin"
12
+ "github.com/golang-jwt/jwt/v5"
13
+ )
14
+
15
+ func RequireAuth(c *gin.Context) {
16
+ tokenString, err := c.Cookie("Authorization")
17
+
18
+ if err != nil {
19
+ c.AbortWithStatus(http.StatusUnauthorized)
20
+ return
21
+ }
22
+
23
+ token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
24
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
25
+ return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
26
+ }
27
+
28
+ return []byte(os.Getenv("SECRET")), nil
29
+ })
30
+
31
+ if err != nil {
32
+ c.AbortWithStatus(http.StatusUnauthorized)
33
+ return
34
+ }
35
+
36
+ if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
37
+ if float64(time.Now().Unix()) > claims["exp"].(float64) {
38
+ c.AbortWithStatus(http.StatusUnauthorized)
39
+ return
40
+ }
41
+
42
+ var user models.User
43
+ database.DB.First(&user, claims["sub"])
44
+
45
+ if user.ID == 0 {
46
+ c.AbortWithStatus(http.StatusUnauthorized)
47
+ return
48
+ }
49
+
50
+ c.Set("user", user)
51
+
52
+ c.Next()
53
+ } else {
54
+ c.AbortWithStatus(http.StatusUnauthorized)
55
+ }
56
+ }
models/latency.go ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import "gorm.io/gorm"
4
+
5
+ type Latency struct {
6
+ gorm.Model
7
+ MonitorID uint `json:"monitorId"`
8
+ Latency uint `json:"latency"` // Latency in milliseconds
9
+ }
models/monitor.go ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import "gorm.io/gorm"
4
+
5
+ type Monitor struct {
6
+ gorm.Model
7
+ Name string `json:"name"`
8
+ URL string `json:"url"`
9
+ Interval uint `json:"interval"` // Interval in seconds
10
+ Status string `json:"status"` // "up", "down", "pending"
11
+ Timeout uint `json:"timeout"`
12
+ Retries uint `json:"retries"`
13
+ RetryInterval uint `json:"retryInterval"`
14
+ IgnoreTLS bool `json:"ignoreTls"`
15
+ HttpMethod string `json:"httpMethod"`
16
+ HttpBody string `json:"httpBody"`
17
+ HttpHeaders string `json:"httpHeaders"`
18
+ AuthMethod string `json:"authMethod"`
19
+ AuthUsername string `json:"authUsername"`
20
+ AuthPassword string `json:"authPassword"`
21
+ Delay uint `json:"delay"` // Delay in seconds before check
22
+ }
models/user.go ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import "gorm.io/gorm"
4
+
5
+ type User struct {
6
+ gorm.Model
7
+ Username string `gorm:"unique"`
8
+ Password string
9
+ }
services/monitor.go ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "crypto/tls"
5
+ "log"
6
+ "net/http"
7
+ "strings"
8
+ "time"
9
+ "uptime/backend/database"
10
+ "uptime/backend/models"
11
+ )
12
+
13
+ var DeletedMonitorIDs = make(chan uint, 100)
14
+
15
+ func StartMonitoring() {
16
+ log.Printf("[INFO] Monitoring service started at %v", time.Now())
17
+ ticker := time.NewTicker(10 * time.Second) // Check for new monitors every 10 seconds
18
+ monitorTimers := make(map[uint]*time.Timer)
19
+
20
+ for {
21
+ select {
22
+ case <-ticker.C:
23
+ var monitors []models.Monitor
24
+ database.DB.Find(&monitors)
25
+
26
+ for i := range monitors {
27
+ monitor := &monitors[i]
28
+
29
+ // Skip if timer already exists for this monitor
30
+ if _, exists := monitorTimers[monitor.ID]; exists {
31
+ continue
32
+ }
33
+
34
+ // Create individual timer for each monitor based on its interval
35
+ interval := time.Duration(monitor.Interval) * time.Second
36
+ log.Printf("[INFO] Creating timer for monitor ID=%d Name=%s Interval=%v", monitor.ID, monitor.Name, interval)
37
+ monitorTimers[monitor.ID] = time.AfterFunc(0, func() {
38
+ scheduleMonitorCheck(monitor, monitorTimers, interval)
39
+ })
40
+ }
41
+ case id := <-DeletedMonitorIDs:
42
+ if timer, exists := monitorTimers[id]; exists {
43
+ timer.Stop()
44
+ delete(monitorTimers, id)
45
+ log.Printf("[INFO] Stopped and removed timer for monitor ID=%d", id)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ func scheduleMonitorCheck(monitor *models.Monitor, timers map[uint]*time.Timer, interval time.Duration) {
52
+ // Add a delay before the check if specified
53
+ if monitor.Delay > 0 {
54
+ delay := time.Duration(monitor.Delay) * time.Second
55
+ log.Printf("[INFO] Monitor ID=%d Name=%s: Delaying check by %v", monitor.ID, monitor.Name, delay)
56
+ time.Sleep(delay)
57
+ }
58
+
59
+ checkMonitorWithRetries(monitor)
60
+
61
+ // Schedule next check
62
+ timers[monitor.ID] = time.AfterFunc(interval, func() {
63
+ scheduleMonitorCheck(monitor, timers, interval)
64
+ })
65
+ }
66
+
67
+ func checkMonitorWithRetries(monitor *models.Monitor) {
68
+ maxRetries := int(monitor.Retries)
69
+ retryInterval := time.Duration(monitor.RetryInterval) * time.Second
70
+
71
+ for attempt := 0; attempt <= maxRetries; attempt++ {
72
+ success := checkMonitor(monitor)
73
+ if success || attempt == maxRetries {
74
+ break
75
+ }
76
+
77
+ if attempt < maxRetries {
78
+ log.Printf("[WARN] Monitor ID=%d Name=%s failed (attempt %d/%d), retrying in %v",
79
+ monitor.ID, monitor.Name, attempt+1, maxRetries+1, retryInterval)
80
+ time.Sleep(retryInterval)
81
+ }
82
+ }
83
+ }
84
+
85
+ func checkMonitor(monitor *models.Monitor) bool {
86
+ client := &http.Client{
87
+ Timeout: time.Duration(monitor.Timeout) * time.Second,
88
+ Transport: &http.Transport{
89
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: monitor.IgnoreTLS},
90
+ },
91
+ }
92
+
93
+ req, err := http.NewRequest(monitor.HttpMethod, monitor.URL, strings.NewReader(monitor.HttpBody))
94
+ if err != nil {
95
+ log.Printf("[ERROR] Monitor ID=%d Name=%s: Error creating request: %s", monitor.ID, monitor.Name, err.Error())
96
+ updateMonitorStatus(monitor, "down")
97
+ return false
98
+ }
99
+
100
+ // Add headers
101
+ // For simplicity, assuming headers are in "Key:Value\nKey2:Value2" format
102
+ if monitor.HttpHeaders != "" {
103
+ headers := strings.Split(monitor.HttpHeaders, "\n")
104
+ for _, header := range headers {
105
+ parts := strings.SplitN(header, ":", 2)
106
+ if len(parts) == 2 {
107
+ req.Header.Set(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]))
108
+ }
109
+ }
110
+ }
111
+
112
+ // Add basic auth
113
+ if monitor.AuthMethod == "basic" && monitor.AuthUsername != "" {
114
+ req.SetBasicAuth(monitor.AuthUsername, monitor.AuthPassword)
115
+ }
116
+
117
+ start := time.Now()
118
+ resp, err := client.Do(req)
119
+ latency := time.Since(start).Milliseconds()
120
+
121
+ if err != nil {
122
+ log.Printf("[ERROR] Monitor ID=%d Name=%s: HTTP request failed: %s", monitor.ID, monitor.Name, err.Error())
123
+ updateMonitorStatus(monitor, "down")
124
+ return false
125
+ }
126
+ defer resp.Body.Close()
127
+
128
+ // Store latency
129
+ latencyRecord := models.Latency{MonitorID: monitor.ID, Latency: uint(latency)}
130
+ database.DB.Create(&latencyRecord)
131
+
132
+ if resp.StatusCode >= 200 && resp.StatusCode < 300 {
133
+ log.Printf("[INFO] Monitor ID=%d Name=%s: Status UP (HTTP %d), Latency: %dms", monitor.ID, monitor.Name, resp.StatusCode, latency)
134
+ updateMonitorStatus(monitor, "up")
135
+ return true
136
+ } else {
137
+ log.Printf("[WARN] Monitor ID=%d Name=%s: Status DOWN (HTTP %d), Latency: %dms", monitor.ID, monitor.Name, resp.StatusCode, latency)
138
+ updateMonitorStatus(monitor, "down")
139
+ return false
140
+ }
141
+ }
142
+
143
+ func updateMonitorStatus(monitor *models.Monitor, status string) {
144
+ oldStatus := monitor.Status
145
+ monitor.Status = status
146
+ database.DB.Save(monitor)
147
+ log.Printf("[INFO] Monitor ID=%d Name=%s: Status changed from %s to %s", monitor.ID, monitor.Name, oldStatus, status)
148
+ }