mirror of
https://github.com/komari-monitor/komari.git
synced 2026-11-11 17:00:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efb4cc6a4d | ||
|
|
09dd2ad329 | ||
|
|
5f079c46d6 | ||
|
|
ec899e3ee4 | ||
|
|
f7813e4b1b | ||
|
|
f5d00c0a2a | ||
|
|
9b9348b341 | ||
|
|
b5266cda66 | ||
|
|
d6d716c5ab | ||
|
|
de5a611718 | ||
|
|
d595acbde3 | ||
|
|
638ce9ae6c | ||
|
|
bd2c67d725 | ||
|
|
f8ae5418c9 | ||
|
|
5e1959dec3 | ||
|
|
b7a276dd05 | ||
|
|
b2b3db616a |
No files matched your search
@@ -20,6 +20,25 @@ jobs:
|
||||
matrix:
|
||||
goos: [windows, linux]
|
||||
goarch: [amd64, arm64]
|
||||
include:
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
gcc: x86_64-w64-mingw32-gcc
|
||||
CGO_CFLAGS: ''
|
||||
CGO_LDFLAGS: ''
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
gcc: gcc
|
||||
CGO_CFLAGS: ''
|
||||
CGO_LDFLAGS: ''
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
gcc: aarch64-linux-gnu-gcc
|
||||
CGO_CFLAGS: '-pthread'
|
||||
CGO_LDFLAGS: '-pthread'
|
||||
exclude:
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -28,7 +47,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '23'
|
||||
node-version: "23"
|
||||
|
||||
- name: Clone and build frontend
|
||||
run: |
|
||||
@@ -43,13 +62,33 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: "1.23"
|
||||
|
||||
- name: Install cross-compiler
|
||||
run: |
|
||||
sudo apt-get update
|
||||
if [ "${{ matrix.goarch }}" = "arm64" ] && [ "${{ matrix.goos }}" = "linux" ]; then
|
||||
sudo apt-get install gcc-aarch64-linux-gnu
|
||||
fi
|
||||
if [ "${{ matrix.goarch }}" = "amd64" ] && [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
sudo apt-get install mingw-w64
|
||||
fi
|
||||
if [ "${{ matrix.goarch }}" = "arm64" ] && [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
sudo apt-get install wget xz-utils
|
||||
mkdir -p /tmp/mingw64
|
||||
wget https://github.com/mstorsjo/llvm-mingw/releases/download/20250613/llvm-mingw-20250613-ucrt-ubuntu-22.04-aarch64.tar.xz -O /tmp/mingw64.tar.xz
|
||||
tar -xf /tmp/mingw64.tar.xz -C /tmp/mingw64 --strip-components=1
|
||||
fi
|
||||
|
||||
- name: Build binary
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
CGO_ENABLED: 1
|
||||
CC: ${{ matrix.gcc }}
|
||||
CGO_CFLAGS: ${{ matrix.CGO_CFLAGS }}
|
||||
CGO_LDFLAGS: ${{ matrix.CGO_LDFLAGS }}
|
||||
GIN_MODE: release
|
||||
run: |
|
||||
BINARY_NAME=komari-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
@@ -102,4 +141,4 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
VERSION=${{ github.event.release.tag_name }}
|
||||
VERSION_HASH=${{ github.sha }}
|
||||
VERSION_HASH=${{ github.sha }}
|
||||
@@ -84,7 +84,7 @@ Komari 是一款轻量级的自托管服务器监控工具,旨在提供简单
|
||||
欢迎提交 Issue 或 Pull Request!
|
||||
|
||||
## 鸣谢
|
||||
- 感谢 [Alice Networks LTD](https://app.alice.ws/) 提供的免费服务器
|
||||
- 感谢我自己能这么闲
|
||||
|
||||
## 引用
|
||||
- [gorm.io](https://gorm.io/)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/komari-monitor/komari/api"
|
||||
"github.com/komari-monitor/komari/database/dbcore"
|
||||
"github.com/komari-monitor/komari/database/models"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// POST body : []uuid
|
||||
func EnableOfflineNotification(c *gin.Context) {
|
||||
var uuids []string
|
||||
if err := c.ShouldBindJSON(&uuids); err != nil {
|
||||
api.RespondError(c, 400, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
var notifications []models.OfflineNotification
|
||||
for _, uuid := range uuids {
|
||||
notifications = append(notifications, models.OfflineNotification{
|
||||
Client: uuid,
|
||||
Enable: true,
|
||||
})
|
||||
}
|
||||
err := dbcore.GetDBInstance().Model(&models.OfflineNotification{}).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "client"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"enable"}),
|
||||
}).
|
||||
Select("client", "enable").
|
||||
Create(notifications).Error
|
||||
if err != nil {
|
||||
api.RespondError(c, 500, "Failed to enable offline notifications: "+err.Error())
|
||||
return
|
||||
}
|
||||
api.RespondSuccess(c, nil)
|
||||
}
|
||||
|
||||
// POST body : []uuid
|
||||
func DisableOfflineNotification(c *gin.Context) {
|
||||
var uuids []string
|
||||
if err := c.ShouldBindJSON(&uuids); err != nil {
|
||||
api.RespondError(c, 400, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
var notifications []models.OfflineNotification
|
||||
for _, uuid := range uuids {
|
||||
notifications = append(notifications, models.OfflineNotification{
|
||||
Client: uuid,
|
||||
Enable: false,
|
||||
})
|
||||
}
|
||||
err := dbcore.GetDBInstance().Model(&models.OfflineNotification{}).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "client"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"enable"}),
|
||||
}).
|
||||
Select("client", "enable").
|
||||
Create(notifications).Error
|
||||
if err != nil {
|
||||
api.RespondError(c, 500, "Failed to disable offline notifications: "+err.Error())
|
||||
return
|
||||
}
|
||||
api.RespondSuccess(c, nil)
|
||||
}
|
||||
|
||||
func EditOfflineNotification(c *gin.Context) {
|
||||
var notifications []models.OfflineNotification
|
||||
if err := c.ShouldBindJSON(¬ifications); err != nil {
|
||||
api.RespondError(c, 400, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
if len(notifications) == 0 {
|
||||
api.RespondError(c, 400, "At least one notification is required")
|
||||
return
|
||||
}
|
||||
for _, noti := range notifications {
|
||||
if noti.Client == "" {
|
||||
api.RespondError(c, 400, "Client UUID cannot be empty")
|
||||
return
|
||||
}
|
||||
if noti.GracePeriod <= 0 || noti.Cooldown <= 0 {
|
||||
api.RespondError(c, 400, "GracePeriod and Cooldown must be positive integers")
|
||||
return
|
||||
}
|
||||
}
|
||||
err := dbcore.GetDBInstance().Model(&models.OfflineNotification{}).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "client"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"enable", "cooldown", "grace_period"}),
|
||||
}).
|
||||
Select("*").
|
||||
Create(notifications).Error
|
||||
if err != nil {
|
||||
api.RespondError(c, 500, "Failed to edit offline notifications: "+err.Error())
|
||||
return
|
||||
}
|
||||
api.RespondSuccess(c, nil)
|
||||
}
|
||||
|
||||
func ListOfflineNotifications(c *gin.Context) {
|
||||
var notifications []models.OfflineNotification
|
||||
err := dbcore.GetDBInstance().Model(&models.OfflineNotification{}).Find(¬ifications).Error
|
||||
if err != nil {
|
||||
api.RespondError(c, 500, "Failed to list offline notifications: "+err.Error())
|
||||
return
|
||||
}
|
||||
api.RespondSuccess(c, notifications)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/komari-monitor/komari/api"
|
||||
"github.com/komari-monitor/komari/common"
|
||||
"github.com/komari-monitor/komari/database/clients"
|
||||
"github.com/komari-monitor/komari/utils/notification"
|
||||
"github.com/komari-monitor/komari/ws"
|
||||
)
|
||||
|
||||
@@ -109,10 +110,11 @@ func WebSocketReport(c *gin.Context) {
|
||||
conn.WriteJSON(gin.H{"status": "error", "error": "Token already in use"})
|
||||
return
|
||||
}
|
||||
|
||||
ws.ConnectedClients[uuid] = conn
|
||||
go notification.OnlineNotification(uuid)
|
||||
defer func() {
|
||||
delete(ws.ConnectedClients, uuid)
|
||||
notification.OfflineNotification(uuid)
|
||||
}()
|
||||
|
||||
for {
|
||||
|
||||
+9
-6
@@ -5,11 +5,13 @@ import (
|
||||
|
||||
"github.com/komari-monitor/komari/cmd/flags"
|
||||
"github.com/komari-monitor/komari/database/accounts"
|
||||
"github.com/komari-monitor/komari/database/dbcore"
|
||||
"github.com/komari-monitor/komari/database/models"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
Username string
|
||||
//Username string
|
||||
NewPassword string
|
||||
)
|
||||
|
||||
@@ -17,7 +19,7 @@ var ChpasswdCmd = &cobra.Command{
|
||||
Use: "chpasswd",
|
||||
Short: "Force change password",
|
||||
Long: `Force change password`,
|
||||
Example: `komari chpasswd -u <username> -p <password>`,
|
||||
Example: `komari chpasswd -p <password>`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if NewPassword == "" {
|
||||
cmd.Help()
|
||||
@@ -27,9 +29,10 @@ var ChpasswdCmd = &cobra.Command{
|
||||
cmd.Println("Database file does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("Changing password for user:", Username)
|
||||
if err := accounts.ForceResetPassword(Username, NewPassword); err != nil {
|
||||
user := &models.User{}
|
||||
dbcore.GetDBInstance().Model(&models.User{}).First(user)
|
||||
cmd.Println("Changing password for user:", user.Username)
|
||||
if err := accounts.ForceResetPassword(user.Username, NewPassword); err != nil {
|
||||
cmd.Println("Error:", err)
|
||||
return
|
||||
}
|
||||
@@ -45,7 +48,7 @@ var ChpasswdCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
ChpasswdCmd.PersistentFlags().StringVarP(&Username, "user", "u", "admin", "The username of the account to change password")
|
||||
//ChpasswdCmd.PersistentFlags().StringVarP(&Username, "user", "u", "admin", "The username of the account to change password")
|
||||
ChpasswdCmd.PersistentFlags().StringVarP(&NewPassword, "password", "p", "", "New password")
|
||||
RootCmd.AddCommand(ChpasswdCmd)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/komari-monitor/komari/database/dbcore"
|
||||
"github.com/komari-monitor/komari/database/models"
|
||||
"github.com/spf13/cobra"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var Disable2FA = &cobra.Command{
|
||||
Use: "disable-2fa",
|
||||
Short: "Force disable 2FA",
|
||||
Long: `Force disable 2FA`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
db := dbcore.GetDBInstance()
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
return tx.Model(&models.User{}).Where("two_factor != ?", "").
|
||||
Update("two_factor", "").Error
|
||||
})
|
||||
if err != nil {
|
||||
cmd.Println("Error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cmd.Println("2FA has been disabled.")
|
||||
cmd.Println("Please restart the server to apply the changes.")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(Disable2FA)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/komari-monitor/komari/api/admin"
|
||||
"github.com/komari-monitor/komari/api/admin/clipboard"
|
||||
log_api "github.com/komari-monitor/komari/api/admin/log"
|
||||
"github.com/komari-monitor/komari/api/admin/notification"
|
||||
"github.com/komari-monitor/komari/api/admin/test"
|
||||
"github.com/komari-monitor/komari/api/admin/update"
|
||||
"github.com/komari-monitor/komari/api/client"
|
||||
@@ -180,6 +181,15 @@ var ServerCmd = &cobra.Command{
|
||||
clipboardGroup.POST("/remove", clipboard.BatchDeleteClipboard)
|
||||
clipboardGroup.POST("/:id/remove", clipboard.DeleteClipboard)
|
||||
}
|
||||
|
||||
notificationGroup := adminAuthrized.Group("/notification")
|
||||
{
|
||||
// offline notifications
|
||||
notificationGroup.GET("/offline", notification.ListOfflineNotifications)
|
||||
notificationGroup.POST("/offline/edit", notification.EditOfflineNotification)
|
||||
notificationGroup.POST("/offline/enable", notification.EnableOfflineNotification)
|
||||
notificationGroup.POST("/offline/disable", notification.DisableOfflineNotification)
|
||||
}
|
||||
}
|
||||
|
||||
public.Static(r.Group("/"), func(handlers ...gin.HandlerFunc) {
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"github.com/komari-monitor/komari/cmd/flags"
|
||||
"github.com/komari-monitor/komari/common"
|
||||
"github.com/komari-monitor/komari/database/models"
|
||||
"github.com/komari-monitor/komari/utils"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// mergeClientInfo 将旧版ClientInfo数据迁移到新版Client表
|
||||
@@ -122,7 +124,13 @@ func GetDBInstance() *gorm.DB {
|
||||
var err error
|
||||
|
||||
logConfig := &gorm.Config{
|
||||
//Logger: logger.Default.LogMode(logger.Silent),
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
}
|
||||
|
||||
if utils.VersionHash == "unknown" {
|
||||
logConfig = &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
}
|
||||
}
|
||||
|
||||
// 根据数据库类型选择不同的连接方式
|
||||
@@ -161,6 +169,8 @@ func GetDBInstance() *gorm.DB {
|
||||
&models.Config{},
|
||||
&models.Log{},
|
||||
&models.Clipboard{},
|
||||
&models.LoadNotification{},
|
||||
&models.OfflineNotification{},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create tables: %v", err)
|
||||
|
||||
@@ -23,6 +23,8 @@ type Config struct {
|
||||
TelegramEndpoint string `json:"telegram_endpoint" gorm:"type:varchar(255);default:'https://api.telegram.org/bot'"`
|
||||
TelegramBotToken string `json:"telegram_bot_token" gorm:"type:varchar(255)"`
|
||||
TelegramChatID string `json:"telegram_chat_id" gorm:"type:varchar(255)"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
// 通知
|
||||
NotificationEnabled bool `json:"notification_enabled" gorm:"default:false"` // 通知总开关
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
// Notification 定义了通知相关的数据库模型
|
||||
type OfflineNotification struct {
|
||||
Client string `json:"client" gorm:"type:varchar(36);not null;index;unique;constraint:OnDelete:CASCADE"`
|
||||
Enable bool `json:"enable" gorm:"type:boolean;default:false"`
|
||||
Cooldown int `json:"cooldown" gorm:"type:int;not null;default:1800"` // 冷却时间(秒),默认 30 分钟
|
||||
GracePeriod int `json:"grace_period" gorm:"type:int;not null;default:180"` // 宽限期(秒),默认 3 分钟
|
||||
}
|
||||
|
||||
// LoadNotification 定义了基于资源占用达标时间比的负载通知规则
|
||||
type LoadNotification struct {
|
||||
Client string `json:"client" gorm:"type:varchar(36);not null;index;constraint:OnDelete:CASCADE"`
|
||||
Enable bool `json:"enable" gorm:"type:boolean;default:false"`
|
||||
Metric string `json:"metric" gorm:"type:varchar(50);not null;default:'cpu'"` // 监控指标,如 cpu, ram, load
|
||||
Threshold float32 `json:"threshold" gorm:"type:decimal(5,2);not null;default:80.00"` // 阈值百分比
|
||||
Window int `json:"window" gorm:"type:int;not null;default:300"` // 观测窗口(秒)
|
||||
Ratio float32 `json:"ratio" gorm:"type:decimal(5,2);not null;default:0.80"` // 达标时间比
|
||||
Cooldown int `json:"cooldown" gorm:"type:int;not null;default:1800"` // 冷却时间(秒)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package notification
|
||||
@@ -69,7 +69,7 @@ func QuantileMean(values []float32) float32 {
|
||||
// 压缩数据库,针对每个ClientUUID,10分钟内的数据不动,24小时内的数据精简为每15分钟一条,3天为每30分钟一条,7天为每1小时一条,30天为每12小时一条。所有精简的数据是取 [0.02,0.98] 区间内的平均值
|
||||
func CompactRecord() error {
|
||||
db := dbcore.GetDBInstance()
|
||||
log.Printf("Compacting records...")
|
||||
//log.Printf("Compacting records...")
|
||||
var clientUUIDs []string
|
||||
if err := db.Model(&models.Record{}).Distinct("client").Pluck("client", &clientUUIDs).Error; err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/komari-monitor/komari/database/clients"
|
||||
"github.com/komari-monitor/komari/database/config"
|
||||
"github.com/komari-monitor/komari/database/dbcore"
|
||||
"github.com/komari-monitor/komari/database/models"
|
||||
"github.com/komari-monitor/komari/utils/telegram"
|
||||
)
|
||||
|
||||
var (
|
||||
lastNotified = make(map[string]time.Time)
|
||||
lastOnlineNotified = make(map[string]time.Time)
|
||||
pendingOffline = make(map[string]time.Time)
|
||||
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
// OfflineNotification sends an offline notification for the client if enabled and not sent in cooldown period
|
||||
func OfflineNotification(clientID string) {
|
||||
client, err := clients.GetClientByUUID(clientID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
conf, _ := config.Get()
|
||||
if !conf.NotificationEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
noti_conf := models.OfflineNotification{
|
||||
Client: clientID,
|
||||
}
|
||||
|
||||
db := dbcore.GetDBInstance()
|
||||
err = db.Model(&models.OfflineNotification{}).Where("client = ?", clientID).FirstOrCreate(¬i_conf).Error
|
||||
if err != nil {
|
||||
log.Println("Failed to get or create offline notification config:", err)
|
||||
return
|
||||
}
|
||||
if !noti_conf.Enable {
|
||||
return
|
||||
}
|
||||
|
||||
cooldownDuration := time.Duration(noti_conf.Cooldown) * time.Second
|
||||
gracePeriod := time.Duration(noti_conf.GracePeriod) * time.Second
|
||||
if cooldownDuration <= 0 {
|
||||
cooldownDuration = 30 * time.Minute // default cooldown if not set
|
||||
}
|
||||
if gracePeriod <= 0 {
|
||||
gracePeriod = 5 * time.Minute // default grace period if not set
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if _, exists := pendingOffline[clientID]; exists {
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
pendingOffline[clientID] = now
|
||||
mu.Unlock()
|
||||
|
||||
go func(start time.Time) {
|
||||
time.Sleep(gracePeriod)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
// if no longer pending (client reconnected), skip
|
||||
if ts, ok := pendingOffline[clientID]; !ok || !ts.Equal(start) {
|
||||
return
|
||||
}
|
||||
// check cooldown
|
||||
if last, ok := lastNotified[clientID]; ok && time.Since(last) < cooldownDuration {
|
||||
delete(pendingOffline, clientID)
|
||||
return
|
||||
}
|
||||
// record notification time and clear pending
|
||||
lastNotified[clientID] = time.Now()
|
||||
delete(pendingOffline, clientID)
|
||||
|
||||
message := fmt.Sprintf("🔴<code>%s</code> is offline", client.Name)
|
||||
go func(msg string) {
|
||||
if err := telegram.SendHTMLMessage(msg); err != nil {
|
||||
log.Println("Failed to send offline notification:", err)
|
||||
}
|
||||
}(message)
|
||||
}(now)
|
||||
}
|
||||
|
||||
// OnlineNotification sends an online notification for the client if enabled and not sent in cooldown period
|
||||
func OnlineNotification(clientID string) {
|
||||
client, err := clients.GetClientByUUID(clientID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
noti_conf := models.OfflineNotification{
|
||||
Client: clientID,
|
||||
}
|
||||
|
||||
db := dbcore.GetDBInstance()
|
||||
err = db.Model(&models.OfflineNotification{}).Where("client = ?", clientID).FirstOrCreate(¬i_conf).Error
|
||||
if err != nil {
|
||||
log.Println("Failed to get or create offline notification config:", err)
|
||||
return
|
||||
}
|
||||
if !noti_conf.Enable {
|
||||
return
|
||||
}
|
||||
cooldownDuration := time.Duration(noti_conf.Cooldown) * time.Second
|
||||
gracePeriod := time.Duration(noti_conf.GracePeriod) * time.Second
|
||||
if cooldownDuration <= 0 {
|
||||
cooldownDuration = 30 * time.Minute // default cooldown if not set
|
||||
}
|
||||
if gracePeriod <= 0 {
|
||||
gracePeriod = 5 * time.Minute // default grace period if not set
|
||||
}
|
||||
// clear any pending offline debounce
|
||||
mu.Lock()
|
||||
delete(pendingOffline, clientID)
|
||||
mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
// if never went offline before, skip first online notification
|
||||
if _, wasOffline := lastNotified[clientID]; !wasOffline {
|
||||
return
|
||||
}
|
||||
// cooldown for online notifications
|
||||
if last, exists := lastOnlineNotified[clientID]; exists && now.Sub(last) < cooldownDuration {
|
||||
return
|
||||
}
|
||||
// send online notification
|
||||
message := fmt.Sprintf("🟢<code>%s</code> is online", client.Name)
|
||||
go func(msg string) {
|
||||
if err := telegram.SendHTMLMessage(msg); err != nil {
|
||||
log.Println("Failed to send online notification:", err)
|
||||
}
|
||||
}(message)
|
||||
|
||||
lastOnlineNotified[clientID] = now
|
||||
delete(lastNotified, clientID)
|
||||
}
|
||||
+42
-33
@@ -11,46 +11,55 @@ import (
|
||||
)
|
||||
|
||||
func SendMessage(message, msgType string) error {
|
||||
conf, err := config.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var lastErr error
|
||||
for i := 0; i < 3; i++ {
|
||||
conf, err := config.Get()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
if !conf.TelegramEnabled || message == "" {
|
||||
return errors.New("telegram is disabled or message is empty")
|
||||
}
|
||||
if !conf.TelegramEnabled || message == "" {
|
||||
return errors.New("telegram is disabled or message is empty")
|
||||
}
|
||||
|
||||
endpoint := conf.TelegramEndpoint + conf.TelegramBotToken + "/sendMessage"
|
||||
endpoint := conf.TelegramEndpoint + conf.TelegramBotToken + "/sendMessage"
|
||||
|
||||
data := url.Values{}
|
||||
data.Set("chat_id", conf.TelegramChatID)
|
||||
data.Set("text", message)
|
||||
if msgType != "text" {
|
||||
data.Set("parse_mode", msgType)
|
||||
}
|
||||
resp, err := http.PostForm(endpoint, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send message: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data := url.Values{}
|
||||
data.Set("chat_id", conf.TelegramChatID)
|
||||
data.Set("text", message)
|
||||
if msgType != "text" {
|
||||
data.Set("parse_mode", msgType)
|
||||
}
|
||||
resp, err := http.PostForm(endpoint, data)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("failed to send message: %v", err)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("telegram API returned non-OK status: %d", resp.StatusCode)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
lastErr = fmt.Errorf("telegram API returned non-OK status: %d", resp.StatusCode)
|
||||
continue
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Ok bool `json:"ok"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
var result struct {
|
||||
Ok bool `json:"ok"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
if !result.Ok {
|
||||
return fmt.Errorf("telegram API error: %s", result.Description)
|
||||
}
|
||||
if !result.Ok {
|
||||
lastErr = fmt.Errorf("telegram API error: %s", result.Description)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// SendTextMessage sends a text message via Telegram API
|
||||
|
||||
Reference in new issue
Block a user