From 08fc2d79a81b632dd201f638e228eb10eb47f859 Mon Sep 17 00:00:00 2001 From: iRedTea Date: Tue, 13 Oct 2026 14:50:49 +0300 Subject: [PATCH] fix: take the file edit limit in the panel from the agent's read cap The limit was repeated in the page; a file of exactly the cap is read in full, so it is editable too. --- internal/agent/fs.go | 4 ++-- internal/proto/proto.go | 4 ++++ internal/server/handlers_panel_fs.go | 2 +- internal/server/web/templates/files.html | 6 +++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/agent/fs.go b/internal/agent/fs.go index 22a807e..d1b712d 100644 --- a/internal/agent/fs.go +++ b/internal/agent/fs.go @@ -17,8 +17,8 @@ import ( "github.com/x-grk/longpolling-remote-access/internal/proto" ) -// fsReadCap bounds how many bytes a view/read returns (5 MiB). -const fsReadCap = 5 * 1024 * 1024 +// fsReadCap bounds how many bytes a view/read returns. +const fsReadCap = proto.FSReadCap // fsDownloadCap bounds a browser download (kept under the 64 MiB result/body // limit once base64-encoded). diff --git a/internal/proto/proto.go b/internal/proto/proto.go index d8662a1..be5567f 100644 --- a/internal/proto/proto.go +++ b/internal/proto/proto.go @@ -91,6 +91,10 @@ const ( TaskSysUserAdd = "sys.useradd" // create a new OS user ) +// FSReadCap bounds how many bytes TaskFSRead returns (5 MiB). The panel lets a +// file be edited only when it fits, so that a save never cuts it short. +const FSReadCap = 5 * 1024 * 1024 + // TaskEnvelope is what the agent fetches for a task id. type TaskEnvelope struct { Type string `json:"type"` diff --git a/internal/server/handlers_panel_fs.go b/internal/server/handlers_panel_fs.go index fb407b6..20819ea 100644 --- a/internal/server/handlers_panel_fs.go +++ b/internal/server/handlers_panel_fs.go @@ -34,7 +34,7 @@ func (s *Server) handleFilesPage(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - s.render(w, "files.html", map[string]any{"Agent": a}) + 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 diff --git a/internal/server/web/templates/files.html b/internal/server/web/templates/files.html index 29ef3fc..45c508b 100644 --- a/internal/server/web/templates/files.html +++ b/internal/server/web/templates/files.html @@ -107,7 +107,7 @@ }).then(r => r.json()).catch(e => ({ok:false, error:'ошибка сети'})).finally(() => setBusy(false)); }; - const EDIT_LIMIT = 5 * 1024 * 1024; + const EDIT_LIMIT = {{.EditLimit}}; // Monochrome inline icons (inherit button color via currentColor). const SVG = ''; @@ -190,10 +190,10 @@ msg.textContent = 'Размер: '+fmtSize(d.size); } else { ta.value = new TextDecoder().decode(b64ToBytes(d.content)); - const editable = d.size < EDIT_LIMIT && !d.truncated; + const editable = d.size <= EDIT_LIMIT && !d.truncated; ta.readOnly = !editable; save.style.display = editable ? '' : 'none'; - msg.textContent = 'Размер: '+fmtSize(d.size) + (d.truncated?' · показано первые 5 МБ':'') + (editable?' · редактируемо':' · только чтение (≥ 5 МБ)'); + msg.textContent = 'Размер: '+fmtSize(d.size) + (d.truncated?' · показано первые '+fmtSize(EDIT_LIMIT):'') + (editable?' · редактируемо':' · только чтение (больше '+fmtSize(EDIT_LIMIT)+')'); save.onclick = async () => { const bytes = new TextEncoder().encode(ta.value); const w = await api('write', {path: join(cwd, e.name), data: bytesToB64(bytes)});