diff --git a/admin/console.go b/admin/console.go index 973e435d..9fc95e73 100644 --- a/admin/console.go +++ b/admin/console.go @@ -12,7 +12,7 @@ import ( "mu/wallet" ) -// ConsoleHandler provides an admin console for managing the system. +// ConsoleHandler provides an admin console. func ConsoleHandler(w http.ResponseWriter, r *http.Request) { _, _, err := auth.RequireAdmin(r) if err != nil { @@ -20,56 +20,107 @@ func ConsoleHandler(w http.ResponseWriter, r *http.Request) { return } - // Handle command - cmd := "" - output := "" + // POST: run command and return result if r.Method == "POST" { r.ParseForm() - cmd = strings.TrimSpace(r.FormValue("cmd")) + cmd := strings.TrimSpace(r.FormValue("cmd")) + output := "" if cmd != "" { output = runCommand(cmd) } - // Redirect with results to prevent form resubmission + // If Accept: application/json, return JSON + if app.WantsJSON(r) || r.Header.Get("Content-Type") == "application/json" { + app.RespondJSON(w, map[string]string{"output": output}) + return + } + // Fallback: redirect http.Redirect(w, r, "/admin/console?cmd="+url.QueryEscape(cmd)+"&output="+url.QueryEscape(output), http.StatusSeeOther) return } - // GET — show form + results from redirect + // GET: render page prevCmd := r.URL.Query().Get("cmd") prevOutput := r.URL.Query().Get("output") var sb strings.Builder + sb.WriteString(`
`) - // Form - sb.WriteString(`
`) - sb.WriteString(`
`) - sb.WriteString(fmt.Sprintf(``, htmlEsc(prevCmd))) - sb.WriteString(``) - sb.WriteString(`
`) - - // Output + // Output area + sb.WriteString(`
`) if prevOutput != "" { - sb.WriteString(fmt.Sprintf(`
%s
`, htmlEsc(prevOutput))) + sb.WriteString(fmt.Sprintf(`> %s +%s`, esc(prevCmd), esc(prevOutput))) } sb.WriteString(`
`) - // Help - sb.WriteString(`
-

search <query> · delete <type> <id> · user <id> · wallet <id> · types · stats

-
`) + // Input — form for fallback, JS for interactive + sb.WriteString(`
`) + sb.WriteString(`>`) + sb.WriteString(``) + sb.WriteString(``) + sb.WriteString(`
`) + + sb.WriteString(`
search · delete · user · wallet · types · stats · help
`) + sb.WriteString(`
`) + + // JS: intercept form, use fetch, append output inline + sb.WriteString(``) html := app.RenderHTMLForRequest("Console", "Admin Console", sb.String(), r) w.Write([]byte(html)) } -func htmlEsc(s string) string { - s = strings.ReplaceAll(s, "&", "&") - s = strings.ReplaceAll(s, "<", "<") - s = strings.ReplaceAll(s, ">", ">") - s = strings.ReplaceAll(s, `"`, """) - return s -} - func runCommand(cmd string) string { parts := strings.Fields(cmd) if len(parts) == 0 { @@ -135,9 +186,17 @@ func runCommand(cmd string) string { return fmt.Sprintf("Index entries: %d\nSQLite: %v", stats.TotalEntries, stats.UsingSQLite) case "help": - return "search · delete · user · wallet · types · stats" + return "search — search indexed content\ndelete — delete by type and ID\nuser — view user details\nwallet — view wallet balance\ntypes — list deletable content types\nstats — index stats" default: - return fmt.Sprintf("Unknown command: %s", parts[0]) + return fmt.Sprintf("Unknown: %s. Type help for commands.", parts[0]) } } + +func esc(s string) string { + s = strings.ReplaceAll(s, "&", "&") + s = strings.ReplaceAll(s, "<", "<") + s = strings.ReplaceAll(s, ">", ">") + s = strings.ReplaceAll(s, `"`, """) + return s +}