feat: initial commit
This commit is contained in:
1 parent
353f62e257
commit
c6852c6f1b
62 files changed
+5911
-2
No files matched your search
@@ -0,0 +1,116 @@
|
||||
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})
|
||||
}
|
||||
|
||||
// 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 !a.Online() {
|
||||
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 !a.Online() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user