diff --git a/main.go b/main.go index 05e00a1..a6dd4d2 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "strconv" "strings" "sync" + "syscall" "time" "github.com/shirou/gopsutil/v3/cpu" @@ -710,13 +711,24 @@ var page = template.Must(template.New("index").Parse(`

-
Host
-
Hostname
-
User
-
Uptime
-
Boot
-
Arch
-
+
Host
+
Hostname
+
User
+
Uptime
+
Boot
+
Arch
+ +
+
Aktionen
+
+ +
+
+
+
+
+
+
OS
@@ -885,6 +897,29 @@ document.addEventListener('DOMContentLoaded', ()=>{ renderApps(filtered); }); } + + const btnGp = document.getElementById('btnGpupdate'); + const resGp = document.getElementById('gpupdateResult'); + if (btnGp && resGp) { + btnGp.addEventListener('click', async ()=>{ + resGp.textContent = 'gpupdate /force wird gestartet …'; + try { + const r = await fetch('/api/gpupdate', { method: 'POST' }); + if (!r.ok) { + resGp.textContent = 'Fehler: HTTP ' + r.status + ' ' + r.statusText; + return; + } + const j = await r.json(); + if (j.ok) { + resGp.textContent = 'gpupdate /force ausgeführt (ExitCode ' + (j.exit_code ?? 0) + ').'; + } else { + resGp.textContent = 'Fehler bei gpupdate (ExitCode ' + (j.exit_code ?? -1) + '): ' + (j.error || ''); + } + } catch (e) { + resGp.textContent = 'Request-Fehler: ' + e; + } + }); + } }); @@ -919,6 +954,7 @@ func runHTTP(ctx context.Context) error { mux.HandleFunc("/api/apps", appsHandler) mux.HandleFunc("/api/summary", apiHandler) mux.HandleFunc("/api/devices", devicesHandler) + mux.HandleFunc("/api/gpupdate", gpupdateHandler) mux.HandleFunc("/api/notify", notifyFromServerHandler) mux.HandleFunc("/api/notifications", notificationsForAgentHandler) @@ -1079,3 +1115,61 @@ func getWinNetProfiles() (map[int]string, error) { res[single.InterfaceIndex] = normalizeCat(single.NetworkCategory) return res, nil } + +type GPUpdateResponse struct { + OK bool `json:"ok"` + ExitCode int `json:"exit_code"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + Error string `json:"error,omitempty"` +} + +func gpupdateHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + // nur lokale Aufrufe zulassen + host, _, _ := net.SplitHostPort(r.RemoteAddr) + if host != "127.0.0.1" && host != "::1" { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + + ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute) + defer cancel() + + cmd := exec.CommandContext(ctx, "cmd", "/C", "gpupdate", "/force") + out, err := cmd.CombinedOutput() + + resp := GPUpdateResponse{ + Stdout: string(out), + Stderr: "", + OK: true, + } + + // Timeout? + if ctx.Err() == context.DeadlineExceeded { + resp.OK = false + resp.Error = "gpupdate /force: Timeout (2 Minuten)" + } + + // Exitcode auswerten + if err != nil { + resp.OK = false + resp.Error = err.Error() + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok2 := exitErr.Sys().(syscall.WaitStatus); ok2 { + resp.ExitCode = status.ExitStatus() + } + } + } else if cmd.ProcessState != nil { + if status, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok { + resp.ExitCode = status.ExitStatus() + } + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(resp) +}