//go:build windows package main import ( "bytes" "encoding/json" "html/template" "log" "net/http" "os" "strconv" "strings" "time" ) type NotifyRequest struct { Title string `json:"title"` Message string `json:"message"` TargetUser string `json:"target_user"` } type PageData struct { ClientHost string Port string Title string Message string TargetUser string Status string Error string } var ( tpl *template.Template notifyToken string defaultPort = "24000" listenAddr = ":8088" defaultTitle = "Benachrichtigung" ) func main() { notifyToken = os.Getenv("NOTIFY_TOKEN") if v := os.Getenv("SENDER_LISTEN_ADDR"); v != "" { listenAddr = v } if v := os.Getenv("DEFAULT_PORT"); v != "" { defaultPort = v } var err error tpl, err = template.New("index").Parse(pageHTML) if err != nil { log.Fatalf("template parse error: %v", err) } http.HandleFunc("/", indexHandler) log.Printf("Notification-Sender Web UI läuft auf http://localhost%s/", listenAddr) log.Fatalf("ListenAndServe: %v", http.ListenAndServe(listenAddr, nil)) } func indexHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: render(w, PageData{ Port: defaultPort, }) case http.MethodPost: if err := r.ParseForm(); err != nil { render(w, PageData{ Error: "Formular konnte nicht gelesen werden: " + err.Error(), Port: defaultPort, }) return } data := PageData{ ClientHost: strings.TrimSpace(r.FormValue("client_host")), Port: strings.TrimSpace(r.FormValue("port")), Title: strings.TrimSpace(r.FormValue("title")), Message: strings.TrimSpace(r.FormValue("message")), TargetUser: strings.TrimSpace(r.FormValue("target_user")), } if data.Port == "" { data.Port = defaultPort } if data.ClientHost == "" { data.Error = "Client-Host/IP darf nicht leer sein." render(w, data) return } if data.Message == "" { data.Error = "Nachricht darf nicht leer sein." render(w, data) return } if data.Title == "" { data.Title = defaultTitle } if err := sendNotification(data); err != nil { data.Error = "Fehler beim Senden: " + err.Error() } else { data.Status = "Benachrichtigung wurde gesendet." } render(w, data) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } func render(w http.ResponseWriter, data PageData) { w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := tpl.Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func sendNotification(d PageData) error { // Port validieren (nur zur Sicherheit, falls jemand Unsinn einträgt) if _, err := strconv.Atoi(d.Port); err != nil { return err } // URL bauen: // Wenn ClientHost schon ein Schema enthält (http://...), nutzen wir das, // sonst "http://:/api/notify" var url string if strings.Contains(d.ClientHost, "://") { url = strings.TrimRight(d.ClientHost, "/") + "/api/notify" } else { url = "http://" + d.ClientHost + ":" + d.Port + "/api/notify" } bodyStruct := NotifyRequest{ Title: d.Title, Message: d.Message, TargetUser: d.TargetUser, } body, err := json.Marshal(bodyStruct) if err != nil { return err } req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") if notifyToken != "" { req.Header.Set("X-Notify-Token", notifyToken) } client := &http.Client{ Timeout: 5 * time.Second, } resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 { return &httpError{Status: resp.Status} } return nil } type httpError struct { Status string } func (e *httpError) Error() string { return "HTTP-Status vom Client: " + e.Status } const pageHTML = ` Notification Sender

Notification Sender

Sende Benachrichtigungen an deine GoSysInfo-Clients (POST /api/notify).

{{if .Status}}
{{.Status}}
{{end}} {{if .Error}}
{{.Error}}
{{end}}
Hinweis: Dieses Tool sendet JSON an http://<Client>:<Port>/api/notify und nutzt optional den Header X-Notify-Token, wenn die Umgebungsvariable NOTIFY_TOKEN gesetzt ist. UI-Service
`