diff options
Diffstat (limited to 'internal/api/executions.go')
| -rw-r--r-- | internal/api/executions.go | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/internal/api/executions.go b/internal/api/executions.go new file mode 100644 index 0000000..d9214c0 --- /dev/null +++ b/internal/api/executions.go @@ -0,0 +1,100 @@ +package api + +import ( + "fmt" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/thepeterstone/claudomator/internal/storage" +) + +// handleListRecentExecutions returns executions across all tasks. +// GET /api/executions?since=<RFC3339>&limit=<int>&task_id=<id> +func (s *Server) handleListRecentExecutions(w http.ResponseWriter, r *http.Request) { + since := time.Now().Add(-24 * time.Hour) + if v := r.URL.Query().Get("since"); v != "" { + if t, err := time.Parse(time.RFC3339, v); err == nil { + since = t + } + } + + limit := 50 + if v := r.URL.Query().Get("limit"); v != "" { + if n, err := strconv.Atoi(v); err == nil && n > 0 { + limit = n + } + } + + taskID := r.URL.Query().Get("task_id") + + execs, err := s.store.ListRecentExecutions(since, limit, taskID) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + if execs == nil { + execs = []*storage.RecentExecution{} + } + writeJSON(w, http.StatusOK, execs) +} + +// handleGetExecutionLog returns the tail of an execution log. +// GET /api/executions/{id}/log?tail=<int>&follow=<bool> +// If follow=true, streams as SSE (delegates to handleStreamLogs). +// If follow=false (default), returns last N raw lines as plain text. +func (s *Server) handleGetExecutionLog(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + exec, err := s.store.GetExecution(id) + if err != nil { + http.Error(w, "execution not found", http.StatusNotFound) + return + } + + if r.URL.Query().Get("follow") == "true" { + s.handleStreamLogs(w, r) + return + } + + tailN := 500 + if v := r.URL.Query().Get("tail"); v != "" { + if n, err := strconv.Atoi(v); err == nil && n > 0 { + tailN = n + } + } + + if exec.StdoutPath == "" { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + return + } + + content, err := tailLogFile(exec.StdoutPath, tailN) + if err != nil { + http.Error(w, "could not read log", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, content) +} + +// tailLogFile reads the last n lines from the file at path. +func tailLogFile(path string, n int) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + content := strings.TrimRight(string(data), "\n") + if content == "" { + return "", nil + } + lines := strings.Split(content, "\n") + if len(lines) > n { + lines = lines[len(lines)-n:] + } + return strings.Join(lines, "\n"), nil +} |
