feat: VM/CT Start/Stop im Frontend + Agent proxmox_action Handler

- Agent: neuer 'proxmox_action' Command (start/stop/shutdown fuer VMs und LXC)
- Backend: POST /api/v1/agents/{id}/proxmox/action Endpoint
- Frontend: Start/Stop/Shutdown Buttons in VMs- und Container-Tab
  - Play (gruen) = starten (nur wenn gestoppt)
  - Power (gelb) = Graceful Shutdown (nur VMs, nur wenn running)
  - Stop (rot) = Hard Stop (wenn running)
- api/client.js: proxmoxAction() Methode
This commit is contained in:
cynfo3000
2026-03-09 20:22:45 +01:00
parent 4cd9764cec
commit 291d2e3c17
4 changed files with 204 additions and 11 deletions
+64
View File
@@ -47,6 +47,8 @@ func (h *Handler) HandleCommand(msg Message) {
response = h.handleUpdate(msg)
case "zpoolscrub":
response = h.handleZpoolScrub(msg)
case "proxmox_action":
response = h.handleProxmoxAction(msg)
case "pty_start":
response = h.handlePTYStart(msg)
case "pty_stop":
@@ -481,3 +483,65 @@ func (h *Handler) handleZpoolScrub(msg Message) Message {
}
return response
}
// handleProxmoxAction: VM/CT starten oder stoppen via pvesh
func (h *Handler) handleProxmoxAction(msg Message) Message {
response := Message{Type: "response", ID: msg.ID}
vmType, _ := msg.Params["type"].(string) // "vm" oder "ct"
action, _ := msg.Params["action"].(string) // "start", "stop", "shutdown"
vmidFloat, ok := msg.Params["vmid"].(float64)
if !ok {
response.Status = "error"
response.Error = "Parameter 'vmid' erforderlich"
return response
}
vmid := int(vmidFloat)
if vmType != "vm" && vmType != "ct" {
response.Status = "error"
response.Error = "Parameter 'type' muss 'vm' oder 'ct' sein"
return response
}
if action != "start" && action != "stop" && action != "shutdown" {
response.Status = "error"
response.Error = "Parameter 'action' muss 'start', 'stop' oder 'shutdown' sein"
return response
}
// Node-Name ermitteln
nodeName, _ := os.Hostname()
if nodeName == "" {
nodeName = "localhost"
}
// pvesh-Pfad aufbauen
var apiPath string
if vmType == "vm" {
apiPath = fmt.Sprintf("/nodes/%s/qemu/%d/status/%s", nodeName, vmid, action)
} else {
if action == "shutdown" {
action = "stop" // LXC kennt kein "shutdown", stop reicht
}
apiPath = fmt.Sprintf("/nodes/%s/lxc/%d/status/%s", nodeName, vmid, action)
}
log.Printf("Proxmox Action: pvesh create %s", apiPath)
cmd := exec.Command("/usr/bin/pvesh", "create", apiPath, "--output-format", "json")
output, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Proxmox Action Fehler: %v — %s", err, string(output))
response.Status = "error"
response.Error = fmt.Sprintf("pvesh fehlgeschlagen: %v — %s", err, strings.TrimSpace(string(output)))
return response
}
log.Printf("Proxmox Action erfolgreich: %s %s %d", action, vmType, vmid)
response.Status = "ok"
response.Data = map[string]interface{}{
"message": fmt.Sprintf("%s %d: %s ausgeführt", vmType, vmid, action),
"output": strings.TrimSpace(string(output)),
}
return response
}