feat(cron): add command to show job details
Add new CLI command `cc-connect cron info <id>` that displays full JSON details of a scheduled task. This helps users inspect all configuration fields of a cron job that aren't visible in the compact `cron list` output. Changes: - Add `/cron/info` API endpoint in core/api.go - Add `runCronInfo` CLI handler in cmd/cc-connect/cron.go - Update help text to include the new command Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2700b5b027
commit
38ece2a2d2
2 files changed
+122
No files matched your search
@@ -26,6 +26,8 @@ func runCron(args []string) {
|
||||
runCronList(args[1:])
|
||||
case "edit":
|
||||
runCronEdit(args[1:])
|
||||
case "info":
|
||||
runCronInfo(args[1:])
|
||||
case "del", "delete", "rm", "remove":
|
||||
runCronDel(args[1:])
|
||||
case "--help", "-h", "help":
|
||||
@@ -313,6 +315,98 @@ func runCronDel(args []string) {
|
||||
fmt.Printf("Cron job %s deleted.\n", id)
|
||||
}
|
||||
|
||||
func runCronInfo(args []string) {
|
||||
var dataDir, id, field string
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--data-dir":
|
||||
if i+1 < len(args) {
|
||||
i++
|
||||
dataDir = args[i]
|
||||
}
|
||||
default:
|
||||
if id == "" {
|
||||
id = args[i]
|
||||
} else if field == "" {
|
||||
field = args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: job ID is required")
|
||||
fmt.Fprintln(os.Stderr, "Usage: cc-connect cron info <id> [field]")
|
||||
fmt.Fprintln(os.Stderr, "Use 'cc-connect cron list' to see all task IDs.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sockPath := resolveSocketPath(dataDir)
|
||||
if _, err := os.Stat(sockPath); os.IsNotExist(err) {
|
||||
fmt.Fprintf(os.Stderr, "Error: cc-connect is not running (socket not found: %s)\n", sockPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||
return net.Dial("unix", sockPath)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Get("http://unix/cron/info?id=" + id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
fmt.Fprintf(os.Stderr, "Error: job '%s' not found\n", id)
|
||||
os.Exit(1)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", strings.TrimSpace(string(body)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// If field specified, extract and print only that field
|
||||
if field != "" {
|
||||
var result map[string]any
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: invalid JSON response: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
val, ok := result[field]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Error: field '%s' not found\n", field)
|
||||
fmt.Fprintln(os.Stderr, "Available fields:")
|
||||
for k := range result {
|
||||
fmt.Fprintf(os.Stderr, " - %s\n", k)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
// Output field value (string directly, otherwise JSON formatted)
|
||||
if s, ok := val.(string); ok {
|
||||
fmt.Println(s)
|
||||
} else {
|
||||
data, _ := json.MarshalIndent(val, "", " ")
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pretty-print full JSON
|
||||
var prettyJSON bytes.Buffer
|
||||
if err := json.Indent(&prettyJSON, body, "", " "); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: invalid JSON response: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(prettyJSON.String())
|
||||
}
|
||||
|
||||
func runCronEdit(args []string) {
|
||||
var dataDir string
|
||||
var id, field string
|
||||
@@ -424,6 +518,8 @@ Commands:
|
||||
add Create a new scheduled task
|
||||
list List all scheduled tasks
|
||||
edit Edit a scheduled task field
|
||||
info <id> [field] Show detailed info of a scheduled task
|
||||
(optionally filter to a single field)
|
||||
del <id> Delete a scheduled task
|
||||
|
||||
Run 'cc-connect cron <command> --help' for details.`)
|
||||
|
||||
+26
@@ -65,6 +65,7 @@ func NewAPIServer(dataDir string) (*APIServer, error) {
|
||||
s.mux.HandleFunc("/sessions", s.handleSessions)
|
||||
s.mux.HandleFunc("/cron/add", s.handleCronAdd)
|
||||
s.mux.HandleFunc("/cron/list", s.handleCronList)
|
||||
s.mux.HandleFunc("/cron/info", s.handleCronInfo)
|
||||
s.mux.HandleFunc("/cron/edit", s.handleCronEdit)
|
||||
s.mux.HandleFunc("/cron/del", s.handleCronDel)
|
||||
s.mux.HandleFunc("/relay/send", s.handleRelaySend)
|
||||
@@ -351,6 +352,31 @@ func (s *APIServer) handleCronDel(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIServer) handleCronInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "GET only", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if s.cron == nil {
|
||||
http.Error(w, "cron scheduler not available", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
id := r.URL.Query().Get("id")
|
||||
if id == "" {
|
||||
http.Error(w, "id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
job := s.cron.store.Get(id)
|
||||
if job == nil {
|
||||
http.Error(w, fmt.Sprintf("job %q not found", id), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
apiJSON(w, http.StatusOK, job)
|
||||
}
|
||||
|
||||
func (s *APIServer) handleCronEdit(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "POST only", http.StatusMethodNotAllowed)
|
||||
|
||||
Reference in new issue
Block a user