package server import ( "encoding/base64" "encoding/json" "io" "net/http" "path" "time" "github.com/x-grk/longpolling-remote-access/internal/proto" ) // fsOps maps the URL op segment to a proto task type. var fsOps = map[string]string{ "list": proto.TaskFSList, "read": proto.TaskFSRead, "write": proto.TaskFSWrite, "mkdir": proto.TaskFSMkdir, "delete": proto.TaskFSDelete, "deltree": proto.TaskFSDelTree, "unzip": proto.TaskFSUnzip, "zip": proto.TaskFSZip, "move": proto.TaskFSMove, } // fsTaskTimeout bounds how long we wait for an agent to answer a file op. const fsTaskTimeout = 60 * time.Second func (s *Server) handleFilesPage(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") a, err := s.db.GetAgent(id) if err != nil || a == nil { http.NotFound(w, r) return } s.render(w, "files.html", map[string]any{"Agent": a, "EditLimit": proto.FSReadCap}) } // POST /api/panel/agents/{id}/fs/{op} - run a file-manager operation on the // agent and return its result. The request body is the op's JSON args and is // forwarded verbatim to the agent. func (s *Server) handleFSOp(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") op := r.PathValue("op") taskType, ok := fsOps[op] if !ok { http.Error(w, "unknown op", http.StatusBadRequest) return } a, err := s.db.GetAgent(agentID) if err != nil || a == nil { http.NotFound(w, r) return } if !s.online(a) { writeJSON(w, proto.TaskResult{Error: "машина офлайн"}) return } args, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 64<<20)) if err != nil { http.Error(w, "bad request", http.StatusBadRequest) return } taskID, resultCh := s.hub.CreateTask(agentID, taskType, args) defer s.hub.DiscardTask(taskID) select { case res := <-resultCh: writeJSON(w, res) case <-time.After(fsTaskTimeout): writeJSON(w, proto.TaskResult{Error: "таймаут ожидания агента"}) } } // GET /api/panel/agents/{id}/fs/download?path=... - stream a file as a download. func (s *Server) handleFSDownload(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") p := r.URL.Query().Get("path") a, err := s.db.GetAgent(agentID) if err != nil || a == nil { http.NotFound(w, r) return } if !s.online(a) { http.Error(w, "машина офлайн", http.StatusServiceUnavailable) return } args, _ := json.Marshal(proto.FSPathArgs{Path: p}) taskID, ch := s.hub.CreateTask(agentID, proto.TaskFSDownload, args) defer s.hub.DiscardTask(taskID) select { case res := <-ch: if !res.OK { http.Error(w, res.Error, http.StatusBadGateway) return } var rr proto.FSReadResult if err := json.Unmarshal(res.Data, &rr); err != nil { http.Error(w, "bad result", http.StatusBadGateway) return } data, _ := base64.StdEncoding.DecodeString(rr.Content) name := path.Base(p) w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"") if rr.Truncated { w.Header().Set("X-Truncated", "1") } _, _ = w.Write(data) case <-time.After(fsTaskTimeout): http.Error(w, "таймаут ожидания агента", http.StatusGatewayTimeout) } }