update mit Notification
All checks were successful
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
build-binaries / publish-agent (push) Has been skipped

This commit is contained in:
2025-12-16 21:17:36 +01:00
parent a7bb94ecab
commit 99e55a2e87

158
main.go
View File

@@ -14,6 +14,7 @@ import (
"os/user"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -82,20 +83,158 @@ type InstalledApp struct {
}
type DeviceInfo struct {
InstanceID string `json:"instance_id"`
Class string `json:"class"`
ClassGUID string `json:"class_guid"`
FriendlyName string `json:"friendly_name"`
Manufacturer string `json:"manufacturer"`
Status string `json:"status"`
Present bool `json:"present"`
InstanceID string `json:"instance_id"`
Class string `json:"class"`
ClassGUID string `json:"class_guid"`
FriendlyName string `json:"friendly_name"`
Manufacturer string `json:"manufacturer"`
Status string `json:"status"`
Present bool `json:"present"`
}
type Notification struct {
ID int64 `json:"id"`
CreatedAt time.Time `json:"created_at"`
Title string `json:"title"`
Message string `json:"message"`
TargetUser string `json:"target_user"` // "" = Broadcast an alle User
}
var (
notifyMu sync.Mutex
notifySeq int64
notifyRing []Notification
)
// maximale Anzahl vorgehaltener Nachrichten (FIFO-Ring)
const maxNotifications = 1000
var (
mu sync.RWMutex
lastCPUUsage float64
)
func addNotification(n Notification) Notification {
notifyMu.Lock()
defer notifyMu.Unlock()
notifySeq++
n.ID = notifySeq
if n.CreatedAt.IsZero() {
n.CreatedAt = time.Now()
}
notifyRing = append(notifyRing, n)
if len(notifyRing) > maxNotifications {
// älteste abschneiden
notifyRing = notifyRing[len(notifyRing)-maxNotifications:]
}
return n
}
// userName: z.B. "DOMAIN\\user" oder "user"
// sinceID: letzte gesehene ID des Agents
func getNotificationsForUser(userName string, sinceID int64) []Notification {
notifyMu.Lock()
defer notifyMu.Unlock()
userNameLower := strings.ToLower(strings.TrimSpace(userName))
var res []Notification
for _, n := range notifyRing {
if n.ID <= sinceID {
continue
}
// Broadcast
if strings.TrimSpace(n.TargetUser) == "" {
res = append(res, n)
continue
}
// Ziel-User matchen (case-insensitive)
if strings.EqualFold(n.TargetUser, userName) ||
strings.EqualFold(n.TargetUser, userNameLower) {
res = append(res, n)
}
}
return res
}
type NotifyRequest struct {
Title string `json:"title"`
Message string `json:"message"`
TargetUser string `json:"target_user"` // optional, "" = an alle
}
func notifyFromServerHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// optional: Authentifizierung per Token
token := os.Getenv("NOTIFY_TOKEN")
if token != "" && r.Header.Get("X-Notify-Token") != token {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
var req NotifyRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request: "+err.Error(), http.StatusBadRequest)
return
}
req.Title = strings.TrimSpace(req.Title)
req.Message = strings.TrimSpace(req.Message)
req.TargetUser = strings.TrimSpace(req.TargetUser)
if req.Message == "" {
http.Error(w, "message must not be empty", http.StatusBadRequest)
return
}
if req.Title == "" {
req.Title = "Benachrichtigung"
}
n := addNotification(Notification{
Title: req.Title,
Message: req.Message,
TargetUser: req.TargetUser,
})
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(n)
}
func notificationsForAgentHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
userName := strings.TrimSpace(r.URL.Query().Get("user"))
if userName == "" {
http.Error(w, "missing 'user' query parameter", http.StatusBadRequest)
return
}
sinceStr := r.URL.Query().Get("since_id")
var sinceID int64
if sinceStr != "" {
if v, err := strconv.ParseInt(sinceStr, 10, 64); err == nil {
sinceID = v
}
}
// optional: Zugriff auf localhost beschränken
host, _, _ := net.SplitHostPort(r.RemoteAddr)
if host != "127.0.0.1" && host != "::1" {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
notifs := getNotificationsForUser(userName, sinceID)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(notifs)
}
func getPnpDevices() ([]DeviceInfo, error) {
// PowerShell: aktuelle PnP-Geräte, inkl. ClassGuid
cmd := exec.Command(
@@ -779,7 +918,10 @@ func runHTTP(ctx context.Context) error {
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/api/apps", appsHandler)
mux.HandleFunc("/api/summary", apiHandler)
mux.HandleFunc("/api/devices", devicesHandler)
mux.HandleFunc("/api/devices", devicesHandler)
mux.HandleFunc("/api/notify", notifyFromServerHandler)
mux.HandleFunc("/api/notifications", notificationsForAgentHandler)
addr := ":24000"