Enhance job management and category handling
- Introduced field lists for job, category, execution, and log queries to streamline data retrieval. - Updated ListJobs, GetJob, GetCategories, and ListLogs functions to utilize new field lists for improved query efficiency. - Enhanced job creation logic to set default values for enabled status, ensuring new jobs are active by default. - Added category name resolution in job retrieval, enriching job data with associated category names. - Improved error handling and logging throughout job and category management processes for better traceability.
This commit is contained in:
parent
58f830b7b6
commit
71f7dcbbad
10 changed files with 1504 additions and 144 deletions
278
data/bindata.go
278
data/bindata.go
File diff suppressed because one or more lines are too long
184
job/data.go
184
job/data.go
|
|
@ -12,6 +12,41 @@ import (
|
|||
"github.com/yaoapp/xun/dbal"
|
||||
)
|
||||
|
||||
// ========================
|
||||
// Field Lists for SELECT queries
|
||||
// ========================
|
||||
|
||||
// JobFields defines the fields to select for job queries
|
||||
var JobFields = []interface{}{
|
||||
"id", "job_id", "name", "icon", "description", "category_id",
|
||||
"max_worker_nums", "status", "mode", "schedule_type", "schedule_expression",
|
||||
"max_retry_count", "default_timeout", "priority", "created_by",
|
||||
"next_run_at", "last_run_at", "current_execution_id", "config",
|
||||
"sort", "enabled", "system", "readonly", "created_at", "updated_at",
|
||||
}
|
||||
|
||||
// CategoryFields defines the fields to select for category queries
|
||||
var CategoryFields = []interface{}{
|
||||
"id", "category_id", "name", "icon", "description",
|
||||
"sort", "system", "enabled", "readonly", "created_at", "updated_at",
|
||||
}
|
||||
|
||||
// ExecutionFields defines the fields to select for execution queries
|
||||
var ExecutionFields = []interface{}{
|
||||
"id", "execution_id", "job_id", "status", "trigger_category", "trigger_source",
|
||||
"trigger_context", "scheduled_at", "worker_id", "process_id", "retry_attempt",
|
||||
"parent_execution_id", "started_at", "ended_at", "timeout_seconds", "duration",
|
||||
"progress", "execution_config", "execution_options", "config_snapshot",
|
||||
"result", "error_info", "stack_trace", "metrics", "context", "created_at", "updated_at",
|
||||
}
|
||||
|
||||
// LogFields defines the fields to select for log queries
|
||||
var LogFields = []interface{}{
|
||||
"id", "job_id", "level", "message", "context", "source", "execution_id",
|
||||
"step", "progress", "duration", "error_code", "stack_trace",
|
||||
"worker_id", "process_id", "timestamp", "sequence", "created_at", "updated_at",
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Jobs methods
|
||||
// ========================
|
||||
|
|
@ -22,7 +57,109 @@ func ListJobs(param model.QueryParam, page int, pagesize int) (maps.MapStrAny, e
|
|||
if mod == nil {
|
||||
return nil, fmt.Errorf("job model not found")
|
||||
}
|
||||
return mod.Paginate(param, page, pagesize)
|
||||
|
||||
// Set select fields if not already specified
|
||||
if len(param.Select) == 0 {
|
||||
param.Select = JobFields
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
log.Debug("ListJobs called with param: %+v, page: %d, pagesize: %d", param, page, pagesize)
|
||||
|
||||
result, err := mod.Paginate(param, page, pagesize)
|
||||
if err != nil {
|
||||
log.Error("ListJobs query error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract jobs data
|
||||
log.Debug("ListJobs raw result: %+v", result)
|
||||
|
||||
jobsData, ok := result["data"].([]maps.MapStrAny)
|
||||
if !ok {
|
||||
log.Debug("Data type conversion failed, result[\"data\"] type: %T, value: %+v", result["data"], result["data"])
|
||||
// Try alternative type conversion
|
||||
if dataSlice, ok := result["data"].([]interface{}); ok {
|
||||
jobsData = make([]maps.MapStrAny, len(dataSlice))
|
||||
for i, item := range dataSlice {
|
||||
if mapItem, ok := item.(maps.MapStrAny); ok {
|
||||
jobsData[i] = mapItem
|
||||
} else if mapItem, ok := item.(map[string]interface{}); ok {
|
||||
jobsData[i] = maps.MapStrAny(mapItem)
|
||||
} else {
|
||||
log.Debug("Item %d type conversion failed: %T", i, item)
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debug("Alternative conversion also failed")
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(jobsData) == 0 {
|
||||
log.Debug("No jobs found in data")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Collect unique category IDs
|
||||
categoryIDs := make(map[string]bool)
|
||||
for _, job := range jobsData {
|
||||
if categoryID, exists := job["category_id"]; exists && categoryID != nil {
|
||||
if categoryIDStr, ok := categoryID.(string); ok && categoryIDStr != "" {
|
||||
categoryIDs[categoryIDStr] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Query categories if we have category IDs
|
||||
categoryMap := make(map[string]string)
|
||||
if len(categoryIDs) > 0 {
|
||||
categoryIDList := make([]string, 0, len(categoryIDs))
|
||||
for categoryID := range categoryIDs {
|
||||
categoryIDList = append(categoryIDList, categoryID)
|
||||
}
|
||||
|
||||
categoryMod := model.Select("__yao.job.category")
|
||||
if categoryMod != nil {
|
||||
categoryParam := model.QueryParam{
|
||||
Select: []interface{}{"category_id", "name"},
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "category_id", OP: "in", Value: categoryIDList},
|
||||
},
|
||||
}
|
||||
|
||||
categories, err := categoryMod.Get(categoryParam)
|
||||
if err != nil {
|
||||
log.Warn("Failed to fetch categories: %v", err)
|
||||
} else {
|
||||
for _, category := range categories {
|
||||
if categoryID, ok := category["category_id"].(string); ok {
|
||||
if categoryName, ok := category["name"].(string); ok {
|
||||
categoryMap[categoryID] = categoryName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add category_name to jobs
|
||||
for i, job := range jobsData {
|
||||
if categoryID, exists := job["category_id"]; exists && categoryID != nil {
|
||||
if categoryIDStr, ok := categoryID.(string); ok {
|
||||
if categoryName, exists := categoryMap[categoryIDStr]; exists {
|
||||
jobsData[i]["category_name"] = categoryName
|
||||
} else {
|
||||
jobsData[i]["category_name"] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result["data"] = jobsData
|
||||
log.Debug("ListJobs result with categories: %+v", result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetActiveJobs get active jobs (running, ready status)
|
||||
|
|
@ -33,6 +170,7 @@ func GetActiveJobs() ([]*Job, error) {
|
|||
}
|
||||
|
||||
param := model.QueryParam{
|
||||
Select: JobFields,
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "status", OP: "in", Value: []string{"ready", "running"}},
|
||||
{Column: "enabled", Value: true},
|
||||
|
|
@ -185,6 +323,7 @@ func GetJob(jobID string) (*Job, error) {
|
|||
}
|
||||
|
||||
param := model.QueryParam{
|
||||
Select: JobFields,
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "job_id", Value: jobID},
|
||||
},
|
||||
|
|
@ -199,8 +338,33 @@ func GetJob(jobID string) (*Job, error) {
|
|||
return nil, fmt.Errorf("job not found: %s", jobID)
|
||||
}
|
||||
|
||||
jobData := results[0]
|
||||
|
||||
// Query category name if category_id exists
|
||||
if categoryID, exists := jobData["category_id"]; exists && categoryID != nil {
|
||||
if categoryIDStr, ok := categoryID.(string); ok && categoryIDStr != "" {
|
||||
categoryMod := model.Select("__yao.job.category")
|
||||
if categoryMod != nil {
|
||||
categoryParam := model.QueryParam{
|
||||
Select: []interface{}{"name"},
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "category_id", Value: categoryIDStr},
|
||||
},
|
||||
Limit: 1,
|
||||
}
|
||||
|
||||
categoryResults, err := categoryMod.Get(categoryParam)
|
||||
if err == nil && len(categoryResults) > 0 {
|
||||
if categoryName, ok := categoryResults[0]["name"].(string); ok {
|
||||
jobData["category_name"] = categoryName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job := &Job{}
|
||||
if err := mapToStruct(results[0], job); err != nil {
|
||||
if err := mapToStruct(jobData, job); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +382,11 @@ func GetCategories(param model.QueryParam) ([]*Category, error) {
|
|||
return nil, fmt.Errorf("job category model not found")
|
||||
}
|
||||
|
||||
// Set select fields if not already specified
|
||||
if len(param.Select) == 0 {
|
||||
param.Select = CategoryFields
|
||||
}
|
||||
|
||||
results, err := mod.Get(param)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -523,6 +692,11 @@ func ListLogs(jobID string, param model.QueryParam, page int, pagesize int) (map
|
|||
return nil, fmt.Errorf("job log model not found")
|
||||
}
|
||||
|
||||
// Set select fields if not already specified
|
||||
if len(param.Select) == 0 {
|
||||
param.Select = LogFields
|
||||
}
|
||||
|
||||
// Add job_id filter
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "job_id",
|
||||
|
|
@ -597,6 +771,7 @@ func GetExecutions(jobID string) ([]*Execution, error) {
|
|||
}
|
||||
|
||||
param := model.QueryParam{
|
||||
Select: ExecutionFields,
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "job_id", Value: jobID},
|
||||
},
|
||||
|
|
@ -701,6 +876,11 @@ func GetExecution(executionID string, param model.QueryParam) (*Execution, error
|
|||
return nil, fmt.Errorf("job execution model not found")
|
||||
}
|
||||
|
||||
// Set select fields if not already specified
|
||||
if len(param.Select) == 0 {
|
||||
param.Select = ExecutionFields
|
||||
}
|
||||
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "execution_id",
|
||||
Value: executionID,
|
||||
|
|
|
|||
|
|
@ -320,6 +320,14 @@ func makeJob(data []byte) (*Job, error) {
|
|||
job.CreatedBy = "system"
|
||||
}
|
||||
|
||||
// Set default enabled to true if not specified
|
||||
// Note: Go's zero value for bool is false, so we need to explicitly check if it was set
|
||||
// Since we can't distinguish between explicitly set false and zero value,
|
||||
// we'll assume new jobs should be enabled by default
|
||||
if !job.System && !job.Readonly {
|
||||
job.Enabled = true
|
||||
}
|
||||
|
||||
return &job, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
180
openapi/job/categories.go
Normal file
180
openapi/job/categories.go
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
package job
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/model"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/job"
|
||||
)
|
||||
|
||||
// ListCategories lists job categories
|
||||
func ListCategories(c *gin.Context) {
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
|
||||
// Add enabled filter (default to true)
|
||||
enabled := c.DefaultQuery("enabled", "true")
|
||||
if enabled == "true" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "enabled",
|
||||
Value: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Add system filter if provided
|
||||
if system := c.Query("system"); system != "" {
|
||||
systemBool := system == "true"
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "system",
|
||||
Value: systemBool,
|
||||
})
|
||||
}
|
||||
|
||||
// Order by sort and name
|
||||
param.Orders = []model.QueryOrder{
|
||||
{Column: "sort", Option: "asc"},
|
||||
{Column: "name", Option: "asc"},
|
||||
}
|
||||
|
||||
// Get categories
|
||||
categories, err := job.GetCategories(param)
|
||||
if err != nil {
|
||||
log.Error("Failed to list categories: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"data": categories,
|
||||
"total": len(categories),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetCategory gets a specific category by ID
|
||||
func GetCategory(c *gin.Context) {
|
||||
categoryID := c.Param("categoryID")
|
||||
if categoryID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "category_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Build query parameters to find by category_id
|
||||
param := model.QueryParam{
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "category_id", Value: categoryID},
|
||||
},
|
||||
Limit: 1,
|
||||
}
|
||||
|
||||
// Get categories with filter
|
||||
categories, err := job.GetCategories(param)
|
||||
if err != nil {
|
||||
log.Error("Failed to get category %s: %v", categoryID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if len(categories) == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, categories[0])
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Process Handlers
|
||||
// ========================
|
||||
|
||||
// ProcessListCategories process handler for listing categories
|
||||
func ProcessListCategories(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for listing categories
|
||||
args := process.Args
|
||||
log.Info("ProcessListCategories called with args: %v", args)
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
if len(args) > 0 {
|
||||
if queryParam, ok := args[0].(model.QueryParam); ok {
|
||||
param = queryParam
|
||||
}
|
||||
}
|
||||
|
||||
// Call job.GetCategories function
|
||||
categories, err := job.GetCategories(param)
|
||||
if err != nil {
|
||||
log.Error("Failed to list categories: %v", err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"categories": categories,
|
||||
"count": len(categories),
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessGetCategory process handler for getting a category
|
||||
func ProcessGetCategory(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for getting a category
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "category_id is required"}
|
||||
}
|
||||
|
||||
categoryID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "category_id must be a string"}
|
||||
}
|
||||
|
||||
log.Info("ProcessGetCategory called for category: %s", categoryID)
|
||||
|
||||
// Build query parameters to find by category_id
|
||||
param := model.QueryParam{
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "category_id", Value: categoryID},
|
||||
},
|
||||
Limit: 1,
|
||||
}
|
||||
|
||||
// Call job.GetCategories function with filter
|
||||
categories, err := job.GetCategories(param)
|
||||
if err != nil {
|
||||
log.Error("Failed to get category %s: %v", categoryID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
if len(categories) == 0 {
|
||||
return map[string]interface{}{"error": "category not found"}
|
||||
}
|
||||
|
||||
return categories[0]
|
||||
}
|
||||
|
||||
// ProcessCountCategories process handler for counting categories
|
||||
func ProcessCountCategories(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for counting categories
|
||||
args := process.Args
|
||||
log.Info("ProcessCountCategories called with args: %v", args)
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
if len(args) > 0 {
|
||||
if queryParam, ok := args[0].(model.QueryParam); ok {
|
||||
param = queryParam
|
||||
}
|
||||
}
|
||||
|
||||
// Call job.CountCategories function
|
||||
count, err := job.CountCategories(param)
|
||||
if err != nil {
|
||||
log.Error("Failed to count categories: %v", err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{"count": count}
|
||||
}
|
||||
333
openapi/job/executions.go
Normal file
333
openapi/job/executions.go
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
package job
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/model"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/job"
|
||||
)
|
||||
|
||||
// ListExecutions lists executions for a specific job
|
||||
func ListExecutions(c *gin.Context) {
|
||||
jobID := c.Param("jobID")
|
||||
if jobID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "job_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get executions for the job
|
||||
executions, err := job.GetExecutions(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to list executions for job %s: %v", jobID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Add optional status filter
|
||||
if status := c.Query("status"); status != "" {
|
||||
filtered := make([]*job.Execution, 0)
|
||||
for _, execution := range executions {
|
||||
if execution.Status == status {
|
||||
filtered = append(filtered, execution)
|
||||
}
|
||||
}
|
||||
executions = filtered
|
||||
}
|
||||
|
||||
// Simple pagination (client-side)
|
||||
page := 1
|
||||
pagesize := 50
|
||||
if p := c.Query("page"); p != "" {
|
||||
if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
if ps := c.Query("pagesize"); ps != "" {
|
||||
if parsed, err := strconv.Atoi(ps); err == nil && parsed > 0 && parsed <= 1000 {
|
||||
pagesize = parsed
|
||||
}
|
||||
}
|
||||
|
||||
total := len(executions)
|
||||
start := (page - 1) * pagesize
|
||||
end := start + pagesize
|
||||
|
||||
if start >= total {
|
||||
executions = []*job.Execution{}
|
||||
} else {
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
executions = executions[start:end]
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"data": executions,
|
||||
"page": page,
|
||||
"pagesize": pagesize,
|
||||
"total": total,
|
||||
"job_id": jobID,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetExecution gets a specific execution by ID
|
||||
func GetExecution(c *gin.Context) {
|
||||
executionID := c.Param("executionID")
|
||||
if executionID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "execution_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get the execution
|
||||
execution, err := job.GetExecution(executionID, model.QueryParam{})
|
||||
if err != nil {
|
||||
log.Error("Failed to get execution %s: %v", executionID, err)
|
||||
if err.Error() == "execution not found: "+executionID {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Execution not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, execution)
|
||||
}
|
||||
|
||||
// StopExecution stops a running execution
|
||||
func StopExecution(c *gin.Context) {
|
||||
executionID := c.Param("executionID")
|
||||
if executionID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "execution_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get the execution first to find the job
|
||||
execution, err := job.GetExecution(executionID, model.QueryParam{})
|
||||
if err != nil {
|
||||
log.Error("Failed to get execution %s: %v", executionID, err)
|
||||
if err.Error() == "execution not found: "+executionID {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Execution not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the job to stop the specific execution
|
||||
jobInstance, err := job.GetJob(execution.JobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get job %s for execution %s: %v", execution.JobID, executionID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// For now, we stop the entire job since individual execution stopping
|
||||
// would require more complex implementation in the job package
|
||||
err = jobInstance.Stop()
|
||||
if err != nil {
|
||||
log.Error("Failed to stop job %s (execution %s): %v", execution.JobID, executionID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Execution stopped successfully (job stopped)",
|
||||
"execution_id": executionID,
|
||||
"job_id": execution.JobID,
|
||||
"status": "stopped",
|
||||
})
|
||||
}
|
||||
|
||||
// GetExecutionProgress gets execution progress information
|
||||
func GetExecutionProgress(c *gin.Context) {
|
||||
executionID := c.Param("executionID")
|
||||
if executionID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "execution_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get the execution
|
||||
execution, err := job.GetExecution(executionID, model.QueryParam{})
|
||||
if err != nil {
|
||||
log.Error("Failed to get execution %s: %v", executionID, err)
|
||||
if err.Error() == "execution not found: "+executionID {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Execution not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"execution_id": executionID,
|
||||
"job_id": execution.JobID,
|
||||
"status": execution.Status,
|
||||
"progress": execution.Progress,
|
||||
"started_at": execution.StartedAt,
|
||||
"ended_at": execution.EndedAt,
|
||||
"duration": execution.Duration,
|
||||
"worker_id": execution.WorkerID,
|
||||
"process_id": execution.ProcessID,
|
||||
"retry_attempt": execution.RetryAttempt,
|
||||
}
|
||||
|
||||
// Add error info if available
|
||||
if execution.ErrorInfo != nil {
|
||||
response["error_info"] = execution.ErrorInfo
|
||||
}
|
||||
|
||||
// Add result if available
|
||||
if execution.Result != nil {
|
||||
response["result"] = execution.Result
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Process Handlers
|
||||
// ========================
|
||||
|
||||
// ProcessListExecutions process handler for listing executions
|
||||
func ProcessListExecutions(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for listing executions
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "job_id is required"}
|
||||
}
|
||||
|
||||
jobID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "job_id must be a string"}
|
||||
}
|
||||
|
||||
log.Info("ProcessListExecutions called for job: %s", jobID)
|
||||
|
||||
// Call job.GetExecutions function
|
||||
executions, err := job.GetExecutions(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to list executions for job %s: %v", jobID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"executions": executions,
|
||||
"count": len(executions),
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessGetExecution process handler for getting an execution
|
||||
func ProcessGetExecution(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for getting an execution
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "execution_id is required"}
|
||||
}
|
||||
|
||||
executionID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "execution_id must be a string"}
|
||||
}
|
||||
|
||||
log.Info("ProcessGetExecution called for execution: %s", executionID)
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
if len(args) > 1 {
|
||||
if queryParam, ok := args[1].(model.QueryParam); ok {
|
||||
param = queryParam
|
||||
}
|
||||
}
|
||||
|
||||
// Call job.GetExecution function
|
||||
execution, err := job.GetExecution(executionID, param)
|
||||
if err != nil {
|
||||
log.Error("Failed to get execution %s: %v", executionID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return execution
|
||||
}
|
||||
|
||||
// ProcessCountExecutions process handler for counting executions
|
||||
func ProcessCountExecutions(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for counting executions
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "job_id is required"}
|
||||
}
|
||||
|
||||
jobID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "job_id must be a string"}
|
||||
}
|
||||
|
||||
log.Info("ProcessCountExecutions called for job: %s", jobID)
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
if len(args) > 1 {
|
||||
if queryParam, ok := args[1].(model.QueryParam); ok {
|
||||
param = queryParam
|
||||
}
|
||||
}
|
||||
|
||||
// Call job.CountExecutions function
|
||||
count, err := job.CountExecutions(jobID, param)
|
||||
if err != nil {
|
||||
log.Error("Failed to count executions for job %s: %v", jobID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{"count": count}
|
||||
}
|
||||
|
||||
// ProcessStopExecution process handler for stopping an execution
|
||||
func ProcessStopExecution(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for stopping an execution
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "execution_id is required"}
|
||||
}
|
||||
|
||||
executionID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "execution_id must be a string"}
|
||||
}
|
||||
|
||||
log.Info("ProcessStopExecution called for execution: %s", executionID)
|
||||
|
||||
// Get the execution first to find the job
|
||||
execution, err := job.GetExecution(executionID, model.QueryParam{})
|
||||
if err != nil {
|
||||
log.Error("Failed to get execution %s: %v", executionID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
// Get the job to stop the specific execution
|
||||
jobInstance, err := job.GetJob(execution.JobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get job %s for execution %s: %v", execution.JobID, executionID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
// For now, we stop the entire job since individual execution stopping
|
||||
// would require more complex implementation in the job package
|
||||
err = jobInstance.Stop()
|
||||
if err != nil {
|
||||
log.Error("Failed to stop job %s (execution %s): %v", execution.JobID, executionID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"message": "Execution stopped successfully",
|
||||
"execution_id": executionID,
|
||||
"job_id": execution.JobID,
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,56 @@
|
|||
package job
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register job process handlers
|
||||
process.RegisterGroup("job", map[string]process.Handler{
|
||||
"jobs.list": ProcessListJobs,
|
||||
"jobs.get": ProcessGetJob,
|
||||
"jobs.count": ProcessCountJobs,
|
||||
"jobs.stop": ProcessStopJob,
|
||||
"executions.list": ProcessListExecutions,
|
||||
"executions.get": ProcessGetExecution,
|
||||
"executions.count": ProcessCountExecutions,
|
||||
"executions.stop": ProcessStopExecution,
|
||||
"logs.list": ProcessListLogs,
|
||||
"categories.list": ProcessListCategories,
|
||||
"categories.get": ProcessGetCategory,
|
||||
"categories.count": ProcessCountCategories,
|
||||
})
|
||||
}
|
||||
|
||||
// Attach attaches the Job API to the router
|
||||
func Attach(group *gin.RouterGroup, oauth types.OAuth) {
|
||||
// Protect all endpoints with OAuth
|
||||
group.Use(oauth.Guard)
|
||||
|
||||
// Job Management (Read-only operations)
|
||||
group.GET("/jobs", ListJobs)
|
||||
group.GET("/jobs/:jobID", GetJob)
|
||||
group.POST("/jobs/:jobID/stop", StopJob)
|
||||
|
||||
// Execution Management
|
||||
group.GET("/jobs/:jobID/executions", ListExecutions)
|
||||
group.GET("/executions/:executionID", GetExecution)
|
||||
group.POST("/executions/:executionID/stop", StopExecution)
|
||||
|
||||
// Log Management
|
||||
group.GET("/jobs/:jobID/logs", ListLogs)
|
||||
group.GET("/executions/:executionID/logs", ListExecutionLogs)
|
||||
|
||||
// Category Management (Read-only)
|
||||
group.GET("/categories", ListCategories)
|
||||
group.GET("/categories/:categoryID", GetCategory)
|
||||
|
||||
// Progress and Status
|
||||
group.GET("/jobs/:jobID/progress", GetJobProgress)
|
||||
group.GET("/executions/:executionID/progress", GetExecutionProgress)
|
||||
|
||||
// Statistics
|
||||
group.GET("/stats", GetStats)
|
||||
}
|
||||
|
|
|
|||
404
openapi/job/jobs.go
Normal file
404
openapi/job/jobs.go
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
package job
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/model"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/job"
|
||||
)
|
||||
|
||||
// ListJobs lists jobs with pagination
|
||||
func ListJobs(c *gin.Context) {
|
||||
// Parse pagination parameters
|
||||
page := 1
|
||||
pagesize := 20
|
||||
|
||||
if p := c.Query("page"); p != "" {
|
||||
if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
|
||||
if ps := c.Query("pagesize"); ps != "" {
|
||||
if parsed, err := strconv.Atoi(ps); err == nil && parsed > 0 && parsed <= 1000 {
|
||||
pagesize = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// Build query parameters from URL query
|
||||
param := model.QueryParam{
|
||||
Orders: []model.QueryOrder{
|
||||
{Column: "created_at", Option: "desc"},
|
||||
},
|
||||
}
|
||||
|
||||
// Add status filter if provided
|
||||
if status := c.Query("status"); status != "" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "status",
|
||||
Value: status,
|
||||
})
|
||||
}
|
||||
|
||||
// Add category filter if provided
|
||||
if categoryID := c.Query("category_id"); categoryID != "" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "category_id",
|
||||
Value: categoryID,
|
||||
})
|
||||
}
|
||||
|
||||
// Add enabled filter (default to show all for debugging)
|
||||
enabled := c.Query("enabled")
|
||||
if enabled == "true" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "enabled",
|
||||
Value: true,
|
||||
})
|
||||
} else if enabled == "false" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "enabled",
|
||||
Value: false,
|
||||
})
|
||||
}
|
||||
// Default: show all records regardless of enabled status
|
||||
|
||||
// Call job.ListJobs function
|
||||
result, err := job.ListJobs(param, page, pagesize)
|
||||
if err != nil {
|
||||
log.Error("Failed to list jobs: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// GetJob gets a specific job by ID
|
||||
func GetJob(c *gin.Context) {
|
||||
jobID := c.Param("jobID")
|
||||
if jobID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "job_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Call job.GetJob function
|
||||
jobInstance, err := job.GetJob(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get job %s: %v", jobID, err)
|
||||
if err.Error() == "job not found: "+jobID {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, jobInstance)
|
||||
}
|
||||
|
||||
// StopJob stops a running job
|
||||
func StopJob(c *gin.Context) {
|
||||
jobID := c.Param("jobID")
|
||||
if jobID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "job_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get the job first
|
||||
jobInstance, err := job.GetJob(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get job %s: %v", jobID, err)
|
||||
if err.Error() == "job not found: "+jobID {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Stop the job
|
||||
err = jobInstance.Stop()
|
||||
if err != nil {
|
||||
log.Error("Failed to stop job %s: %v", jobID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Job stopped successfully",
|
||||
"job_id": jobID,
|
||||
"status": "stopped",
|
||||
})
|
||||
}
|
||||
|
||||
// GetJobProgress gets job progress information
|
||||
func GetJobProgress(c *gin.Context) {
|
||||
jobID := c.Param("jobID")
|
||||
if jobID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "job_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get the job first
|
||||
jobInstance, err := job.GetJob(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get job %s: %v", jobID, err)
|
||||
if err.Error() == "job not found: "+jobID {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get executions for progress calculation
|
||||
executions, err := job.GetExecutions(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get executions for job %s: %v", jobID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate progress
|
||||
totalExecutions := len(executions)
|
||||
completedCount := 0
|
||||
runningCount := 0
|
||||
failedCount := 0
|
||||
totalProgress := 0
|
||||
|
||||
for _, execution := range executions {
|
||||
totalProgress += execution.Progress
|
||||
switch execution.Status {
|
||||
case "completed":
|
||||
completedCount++
|
||||
case "running":
|
||||
runningCount++
|
||||
case "failed":
|
||||
failedCount++
|
||||
}
|
||||
}
|
||||
|
||||
averageProgress := 0
|
||||
if totalExecutions > 0 {
|
||||
averageProgress = totalProgress / totalExecutions
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"job_id": jobID,
|
||||
"status": jobInstance.Status,
|
||||
"progress": averageProgress,
|
||||
"total_executions": totalExecutions,
|
||||
"completed_count": completedCount,
|
||||
"running_count": runningCount,
|
||||
"failed_count": failedCount,
|
||||
"last_run_at": jobInstance.LastRunAt,
|
||||
"next_run_at": jobInstance.NextRunAt,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetStats gets overall job statistics
|
||||
func GetStats(c *gin.Context) {
|
||||
// Count total jobs
|
||||
totalJobs, err := job.CountJobs(model.QueryParam{})
|
||||
if err != nil {
|
||||
log.Error("Failed to count total jobs: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Count running jobs
|
||||
runningJobs, err := job.CountJobs(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "status", Value: "running"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Failed to count running jobs: %v", err)
|
||||
runningJobs = 0
|
||||
}
|
||||
|
||||
// Count completed jobs
|
||||
completedJobs, err := job.CountJobs(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "status", Value: "completed"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Failed to count completed jobs: %v", err)
|
||||
completedJobs = 0
|
||||
}
|
||||
|
||||
// Count failed jobs
|
||||
failedJobs, err := job.CountJobs(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "status", Value: "failed"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Failed to count failed jobs: %v", err)
|
||||
failedJobs = 0
|
||||
}
|
||||
|
||||
// Get categories for category stats
|
||||
categories, err := job.GetCategories(model.QueryParam{})
|
||||
if err != nil {
|
||||
log.Error("Failed to get categories: %v", err)
|
||||
categories = []*job.Category{}
|
||||
}
|
||||
|
||||
categoryStats := make(map[string]int)
|
||||
for _, category := range categories {
|
||||
count, err := job.CountJobs(model.QueryParam{
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "category_id", Value: category.CategoryID},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
count = 0
|
||||
}
|
||||
categoryStats[category.Name] = count
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"total_jobs": totalJobs,
|
||||
"running_jobs": runningJobs,
|
||||
"completed_jobs": completedJobs,
|
||||
"failed_jobs": failedJobs,
|
||||
"category_stats": categoryStats,
|
||||
"total_categories": len(categories),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Process Handlers
|
||||
// ========================
|
||||
|
||||
// ProcessListJobs process handler for listing jobs
|
||||
func ProcessListJobs(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for listing jobs
|
||||
args := process.Args
|
||||
log.Info("ProcessListJobs called with args: %v", args)
|
||||
|
||||
// Default pagination values
|
||||
page := 1
|
||||
pagesize := 20
|
||||
|
||||
// Parse arguments if provided
|
||||
if len(args) > 0 {
|
||||
if p, ok := args[0].(int); ok {
|
||||
page = p
|
||||
}
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if ps, ok := args[1].(int); ok {
|
||||
pagesize = ps
|
||||
}
|
||||
}
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
if len(args) > 2 {
|
||||
if queryParam, ok := args[2].(model.QueryParam); ok {
|
||||
param = queryParam
|
||||
}
|
||||
}
|
||||
|
||||
// Call job.ListJobs function
|
||||
result, err := job.ListJobs(param, page, pagesize)
|
||||
if err != nil {
|
||||
log.Error("Failed to list jobs: %v", err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ProcessGetJob process handler for getting a job
|
||||
func ProcessGetJob(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for getting a job
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "job_id is required"}
|
||||
}
|
||||
|
||||
jobID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "job_id must be a string"}
|
||||
}
|
||||
|
||||
log.Info("ProcessGetJob called for job: %s", jobID)
|
||||
|
||||
// Call job.GetJob function
|
||||
result, err := job.GetJob(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get job %s: %v", jobID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ProcessCountJobs process handler for counting jobs
|
||||
func ProcessCountJobs(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for counting jobs
|
||||
args := process.Args
|
||||
log.Info("ProcessCountJobs called with args: %v", args)
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
if len(args) > 0 {
|
||||
if queryParam, ok := args[0].(model.QueryParam); ok {
|
||||
param = queryParam
|
||||
}
|
||||
}
|
||||
|
||||
// Call job.CountJobs function
|
||||
count, err := job.CountJobs(param)
|
||||
if err != nil {
|
||||
log.Error("Failed to count jobs: %v", err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{"count": count}
|
||||
}
|
||||
|
||||
// ProcessStopJob process handler for stopping a job
|
||||
func ProcessStopJob(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for stopping a job
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "job_id is required"}
|
||||
}
|
||||
|
||||
jobID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "job_id must be a string"}
|
||||
}
|
||||
|
||||
log.Info("ProcessStopJob called for job: %s", jobID)
|
||||
|
||||
// Get the job first
|
||||
jobInstance, err := job.GetJob(jobID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get job %s: %v", jobID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
// Stop the job
|
||||
err = jobInstance.Stop()
|
||||
if err != nil {
|
||||
log.Error("Failed to stop job %s: %v", jobID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{"message": "Job stopped successfully", "job_id": jobID}
|
||||
}
|
||||
188
openapi/job/logs.go
Normal file
188
openapi/job/logs.go
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
package job
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/model"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/job"
|
||||
)
|
||||
|
||||
// ListLogs lists logs for a specific job
|
||||
func ListLogs(c *gin.Context) {
|
||||
jobID := c.Param("jobID")
|
||||
if jobID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "job_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse pagination parameters
|
||||
page := 1
|
||||
pagesize := 50
|
||||
|
||||
if p := c.Query("page"); p != "" {
|
||||
if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
|
||||
if ps := c.Query("pagesize"); ps != "" {
|
||||
if parsed, err := strconv.Atoi(ps); err == nil && parsed > 0 && parsed <= 1000 {
|
||||
pagesize = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{
|
||||
Orders: []model.QueryOrder{
|
||||
{Column: "timestamp", Option: "desc"}, // 日志按时间戳倒序
|
||||
},
|
||||
}
|
||||
|
||||
// Add level filter if provided
|
||||
if level := c.Query("level"); level != "" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "level",
|
||||
Value: level,
|
||||
})
|
||||
}
|
||||
|
||||
// Add execution_id filter if provided
|
||||
if executionID := c.Query("execution_id"); executionID != "" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "execution_id",
|
||||
Value: executionID,
|
||||
})
|
||||
}
|
||||
|
||||
// Call job.ListLogs function
|
||||
result, err := job.ListLogs(jobID, param, page, pagesize)
|
||||
if err != nil {
|
||||
log.Error("Failed to list logs for job %s: %v", jobID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// ListExecutionLogs lists logs for a specific execution
|
||||
func ListExecutionLogs(c *gin.Context) {
|
||||
executionID := c.Param("executionID")
|
||||
if executionID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "execution_id is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// First get the execution to find the job_id
|
||||
execution, err := job.GetExecution(executionID, model.QueryParam{})
|
||||
if err != nil {
|
||||
log.Error("Failed to get execution %s: %v", executionID, err)
|
||||
if err.Error() == "execution not found: "+executionID {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Execution not found"})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Parse pagination parameters
|
||||
page := 1
|
||||
pagesize := 50
|
||||
|
||||
if p := c.Query("page"); p != "" {
|
||||
if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
|
||||
if ps := c.Query("pagesize"); ps != "" {
|
||||
if parsed, err := strconv.Atoi(ps); err == nil && parsed > 0 && parsed <= 1000 {
|
||||
pagesize = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// Build query parameters with execution_id filter
|
||||
param := model.QueryParam{
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "execution_id", Value: executionID},
|
||||
},
|
||||
Orders: []model.QueryOrder{
|
||||
{Column: "timestamp", Option: "desc"}, // 日志按时间戳倒序
|
||||
},
|
||||
}
|
||||
|
||||
// Add level filter if provided
|
||||
if level := c.Query("level"); level != "" {
|
||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||
Column: "level",
|
||||
Value: level,
|
||||
})
|
||||
}
|
||||
|
||||
// Call job.ListLogs function with job_id from execution
|
||||
result, err := job.ListLogs(execution.JobID, param, page, pagesize)
|
||||
if err != nil {
|
||||
log.Error("Failed to list logs for execution %s: %v", executionID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Process Handlers
|
||||
// ========================
|
||||
|
||||
// ProcessListLogs process handler for listing logs
|
||||
func ProcessListLogs(process *process.Process) interface{} {
|
||||
// TODO: Implement process handler for listing logs
|
||||
args := process.Args
|
||||
if len(args) == 0 {
|
||||
return map[string]interface{}{"error": "job_id is required"}
|
||||
}
|
||||
|
||||
jobID, ok := args[0].(string)
|
||||
if !ok {
|
||||
return map[string]interface{}{"error": "job_id must be a string"}
|
||||
}
|
||||
|
||||
// Default pagination values
|
||||
page := 1
|
||||
pagesize := 50
|
||||
|
||||
// Parse arguments if provided
|
||||
if len(args) > 1 {
|
||||
if p, ok := args[1].(int); ok && p > 0 {
|
||||
page = p
|
||||
}
|
||||
}
|
||||
if len(args) > 2 {
|
||||
if ps, ok := args[2].(int); ok && ps > 0 {
|
||||
pagesize = ps
|
||||
}
|
||||
}
|
||||
|
||||
// Build query parameters
|
||||
param := model.QueryParam{}
|
||||
if len(args) > 3 {
|
||||
if queryParam, ok := args[3].(model.QueryParam); ok {
|
||||
param = queryParam
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("ProcessListLogs called for job: %s (page: %d, pagesize: %d)", jobID, page, pagesize)
|
||||
|
||||
// Call job.ListLogs function
|
||||
result, err := job.ListLogs(jobID, param, page, pagesize)
|
||||
if err != nil {
|
||||
log.Error("Failed to list logs for job %s: %v", jobID, err)
|
||||
return map[string]interface{}{"error": err.Error()}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/yaoapp/yao/openapi/dsl"
|
||||
"github.com/yaoapp/yao/openapi/file"
|
||||
"github.com/yaoapp/yao/openapi/hello"
|
||||
"github.com/yaoapp/yao/openapi/job"
|
||||
"github.com/yaoapp/yao/openapi/kb"
|
||||
"github.com/yaoapp/yao/openapi/oauth"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
|
|
@ -95,6 +96,9 @@ func (openapi *OpenAPI) Attach(router *gin.Engine) {
|
|||
// Knowledge Base handlers
|
||||
kb.Attach(group.Group("/kb"), openapi.OAuth)
|
||||
|
||||
// Job Management handlers
|
||||
job.Attach(group.Group("/job"), openapi.OAuth)
|
||||
|
||||
// Chat handlers
|
||||
chat.Attach(group.Group("/chat"), openapi.OAuth)
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,14 @@
|
|||
"nullable": false,
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "max_worker_nums",
|
||||
"type": "integer",
|
||||
"label": "Max Worker Numbers",
|
||||
"comment": "Maximum number of concurrent workers for this job",
|
||||
"default": 1,
|
||||
"nullable": false
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"type": "enum",
|
||||
|
|
@ -218,9 +226,9 @@
|
|||
"comment": "Composite index for category and status queries"
|
||||
},
|
||||
{
|
||||
"name": "idx_job_process_type_status",
|
||||
"columns": ["process_type", "status"],
|
||||
"comment": "Composite index for process type and status queries"
|
||||
"name": "idx_job_mode_status",
|
||||
"columns": ["mode", "status"],
|
||||
"comment": "Composite index for mode and status queries"
|
||||
},
|
||||
{
|
||||
"name": "idx_job_schedule_type_next_run",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue