@@ -2,9 +2,11 @@ package handler
22
33import (
44 "encoding/csv"
5+ "fmt"
56 "log/slog"
67 "net/http"
78 "strconv"
9+ "time"
810
911 "github.com/gin-gonic/gin"
1012 "opencsg.com/csghub-server/api/httpbase"
@@ -29,6 +31,11 @@ type ClusterHandler struct {
2931 c component.ClusterComponent
3032}
3133
34+ const (
35+ deployTimeLayout = "2006-01-02 15:04:05"
36+ deployDateOnlyLayout = "2006-01-02"
37+ )
38+
3239// Getclusters godoc
3340// @Security ApiKey
3441// @Summary Get cluster list
@@ -146,6 +153,8 @@ func (h *ClusterHandler) GetDeploys(ctx *gin.Context) {
146153// @Produce text/csv
147154// @Param status query string false "status" default(all) Enums(all, running, stopped, deployfailed)
148155// @Param search query string false "search" default("")
156+ // @Param start_time query string false "filter deploys created after or at this time"
157+ // @Param end_time query string false "filter deploys created before or at this time"
149158// @Success 200 {string} string "CSV file"
150159// @Failure 400 {object} types.APIBadRequest "Bad request"
151160// @Failure 500 {object} types.APIInternalServerError "Internal server error"
@@ -165,6 +174,11 @@ func (h *ClusterHandler) GetDeploysReport(ctx *gin.Context) {
165174 req .Status = []int {code .DeployFailed }
166175 }
167176 req .Query = ctx .Query ("search" )
177+ if err := bindDeployDateRange (ctx , & req ); err != nil {
178+ slog .Error ("Invalid date range for deploy report" , slog .Any ("error" , err ))
179+ httpbase .BadRequest (ctx , err .Error ())
180+ return
181+ }
168182
169183 filename := "deploys_report.csv"
170184 ctx .Header ("Content-Type" , "text/csv; charset=utf-8" )
@@ -189,7 +203,6 @@ func (h *ClusterHandler) GetDeploysReport(ctx *gin.Context) {
189203 })
190204 writer .Flush ()
191205
192- const timeLayout = "2006-01-02 15:04:05"
193206 totalProcessed := 0
194207
195208 for {
@@ -207,7 +220,7 @@ func (h *ClusterHandler) GetDeploysReport(ctx *gin.Context) {
207220 d .DeployName ,
208221 d .User .Username ,
209222 d .Resource ,
210- d .CreateTime .Local ().Format (timeLayout ),
223+ d .CreateTime .Local ().Format (deployTimeLayout ),
211224 d .Status ,
212225 strconv .Itoa (d .TotalTimeInMin ),
213226 strconv .Itoa (d .TotalFeeInCents ),
@@ -246,3 +259,43 @@ func (h *ClusterHandler) Update(ctx *gin.Context) {
246259 }
247260 httpbase .OK (ctx , result )
248261}
262+
263+ func bindDeployDateRange (ctx * gin.Context , req * types.DeployReq ) error {
264+ startTime := ctx .Query ("start_time" )
265+ endTime := ctx .Query ("end_time" )
266+ if startTime == "" && endTime == "" {
267+ return nil
268+ }
269+ if startTime == "" || endTime == "" {
270+ return fmt .Errorf ("start_time and end_time must be provided together" )
271+ }
272+ parsedStart , err := parseDeployQueryTime (startTime , false )
273+ if err != nil {
274+ return err
275+ }
276+ parsedEnd , err := parseDeployQueryTime (endTime , true )
277+ if err != nil {
278+ return err
279+ }
280+ req .StartTime = & parsedStart
281+ req .EndTime = & parsedEnd
282+ return nil
283+ }
284+
285+ func parseDeployQueryTime (value string , isEnd bool ) (time.Time , error ) {
286+ layouts := []string {deployTimeLayout , deployDateOnlyLayout }
287+ for _ , layout := range layouts {
288+ parsed , err := time .ParseInLocation (layout , value , time .UTC )
289+ if err != nil {
290+ continue
291+ }
292+ if layout == deployDateOnlyLayout {
293+ if isEnd {
294+ parsed = parsed .Add (24 * time .Hour - time .Nanosecond )
295+ }
296+ return parsed , nil
297+ }
298+ return parsed , nil
299+ }
300+ return time.Time {}, fmt .Errorf ("invalid datetime format, use '%s' or '%s'" , deployTimeLayout , deployDateOnlyLayout )
301+ }
0 commit comments