From fd8f4255488b818bc1b14ffe126a6583a8949cdf Mon Sep 17 00:00:00 2001 From: palytoxin Date: Sat, 18 Jul 2020 15:36:28 +0800 Subject: [PATCH] add cloudbrain api support --- go.mod | 3 +- go.sum | 5 + models/cloudbrain.go | 210 ++++++++++++++++++ models/migrations/migrations.go | 1 + models/migrations/v141.go | 26 +++ models/models.go | 1 + modules/auth/cloudbrain.go | 16 ++ modules/cloudbrain/cloudbrain.go | 58 +++++ modules/cloudbrain/resty.go | 120 ++++++++++ modules/setting/cloudbrain.go | 19 ++ routers/repo/cloudbrain.go | 53 ++++- routers/routes/routes.go | 5 +- .../repo/cloudbrain/cloudbrain_list.tmpl | 26 --- templates/repo/cloudbrain/index.tmpl | 32 ++- templates/repo/cloudbrain/new.tmpl | 5 +- vendor/modules.txt | 5 +- 16 files changed, 549 insertions(+), 36 deletions(-) create mode 100644 models/cloudbrain.go create mode 100644 models/migrations/v141.go create mode 100644 modules/auth/cloudbrain.go create mode 100644 modules/cloudbrain/cloudbrain.go create mode 100644 modules/cloudbrain/resty.go create mode 100644 modules/setting/cloudbrain.go delete mode 100644 templates/repo/cloudbrain/cloudbrain_list.tmpl diff --git a/go.mod b/go.mod index 3484f2d76..5f59928c4 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( github.com/go-ini/ini v1.56.0 // indirect github.com/go-openapi/jsonreference v0.19.3 // indirect github.com/go-redis/redis v6.15.2+incompatible + github.com/go-resty/resty/v2 v2.3.0 github.com/go-sql-driver/mysql v1.4.1 github.com/go-swagger/go-swagger v0.21.0 github.com/gobwas/glob v0.2.3 @@ -106,7 +107,7 @@ require ( github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 golang.org/x/mod v0.3.0 // indirect - golang.org/x/net v0.0.0-20200506145744-7e3656a0809f + golang.org/x/net v0.0.0-20200513185701-a91f0712d120 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f golang.org/x/text v0.3.2 diff --git a/go.sum b/go.sum index 0df9a912c..d9d06d409 100644 --- a/go.sum +++ b/go.sum @@ -270,6 +270,9 @@ github.com/go-openapi/validate v0.19.3 h1:PAH/2DylwWcIU1s0Y7k3yNmeAgWOcKrNE2Q7Ww github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-resty/resty v1.12.0 h1:L1P5qymrXL5H/doXe2pKUr1wxovAI5ilm2LdVLbwThc= +github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= +github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -740,6 +743,8 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3ob golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/models/cloudbrain.go b/models/cloudbrain.go new file mode 100644 index 000000000..dc97062da --- /dev/null +++ b/models/cloudbrain.go @@ -0,0 +1,210 @@ +package models + +import ( + "fmt" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + "xorm.io/builder" +) + +type CloudbrainStatus int8 + +const ( + JobWaiting CloudbrainStatus = iota + JobStopped + JobSucceeded + JobFailed +) + +type Cloudbrain struct { + ID int64 `xorm:"pk autoincr"` + JobID string + // Title string `xorm:"INDEX NOT NULL"` + Status int32 `xorm:"INDEX"` + UserID int64 `xorm:"INDEX"` + RepoID int64 `xorm:"INDEX"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + + User *User `xorm:"-"` + Repo *Repository `xorm:"-"` +} + +type CloudBrainLoginResult struct { + Code string + Msg string + Payload struct { + UserID string `json:"userId"` + RealName string `json:"realName"` + Token string `json:"token"` + Admin bool `json:"admin"` + } +} + +type TaskRole struct { + Name string `json:"name"` + TaskNumber int8 `json:"taskNumber"` + MinSucceededTaskCount int8 `json:"minSucceededTaskCount"` + MinFailedTaskCount int8 `json:"minFailedTaskCount"` + CPUNumber int8 `json:"cpuNumber"` + GPUNumber int8 `json:"gpuNumber"` + MemoryMB int `json:"memoryMB"` + ShmMB int `json:"shmMB"` + Command string `json:"command"` + NeedIBDevice bool `json:"needIBDevice"` + IsMainRole bool `json:"isMainRole"` +} + +type CreateJobParams struct { + JobName string `json:"jobName"` + RetryCount int8 `json:"retryCount"` + GpuType string `json:"gpuType"` + Image string `json:"image"` + TaskRoles []TaskRole `json:"taskRoles"` +} + +type CreateJobResult struct { + Code string + Msg string + Payload struct { + JobID string `json:"jobId"` + } +} + +type GetJobResult struct { + Code string + Msg string + Payload struct { + ID string `json:"Id"` + Name string + Platform string + JobStatus struct { + Username string + State string + SubState string `json:"subState"` + ExecutionType string `json:"executionType"` + Retries int8 `json:"retries"` + CreatedTime int64 `json:"createdTime"` + CompletedTime int64 `json:"completedTime"` + AppID string `json:"appId"` + AppProgress string `json:"appProgress"` + AppTrackingURL string `json:"appTrackingUrl"` + AppLaunchedTime int64 `json:"appLaunchedTime"` + AppCompletedTime int64 `json:"appCompletedTime"` + AppExitCode int8 `json:"appExitCode"` + AppExitDiagnostics string `json:"appExitDiagnostics"` + AppExitType string `json:"appExitType"` + VirtualCluster string `json:"virtualCluster"` + } `json:"jobStatus"` + + TaskRoles string `json:"taskRoles"` + + Resource struct { + CPU int8 `json:"cpu"` + Memory string + GPU string `json:"nvidia.com/gpu"` + } `json:"resource"` + + Config struct { + Image string + JobID string `json:"jobId"` + GpuType string `json:"gpuType"` + JobName string `json:"jobName"` + JobType string `json:"jobType"` + RetryCount int8 `json:"retryCount"` + TaskRoles []struct { + Name string `json:"name"` + ShmMB int32 `json:"shmMB"` + Command string `json:"command"` + MemoryMB int64 `json:"memoryMB"` + CPUNumber int8 `json:"cpuNumber"` + GPUNumber int8 `json:"gpuNumber"` + IsMainRole bool `json:"isMainRole"` + TaskNumber int32 `json:"taskNumber"` + NeedIBDevice bool `json:"needIBDevice"` + MinFailedTaskCount int8 `json:"minFailedTaskCount"` + MinSucceededTaskCount int8 `json:"minSucceededTaskCount"` + } `json:"taskRoles"` + } + + Userinfo struct { + User string + OrgID string `json:"org_id"` + } + } +} + +type CloudbrainsOptions struct { + ListOptions + RepoID int64 // include all repos if empty + UserID int64 + JobStatus CloudbrainStatus + SortType string + CloudbrainIDs []int64 +} + +func Cloudbrains(opts *CloudbrainsOptions) ([]*Cloudbrain, int64, error) { + sess := x.NewSession() + defer sess.Close() + + var cond = builder.NewCond() + if opts.RepoID > 0 { + cond.And( + builder.Eq{"cloudbrain.repo_id": opts.RepoID}, + ) + } + + if opts.UserID > 0 { + cond.And( + builder.Eq{"cloudbrain.user_id": opts.UserID}, + ) + } + + switch opts.JobStatus { + case JobWaiting: + cond.And(builder.Eq{"cloudbrain.status": int(JobWaiting)}) + case JobFailed: + cond.And(builder.Eq{"cloudbrain.status": int(JobFailed)}) + case JobStopped: + cond.And(builder.Eq{"cloudbrain.status": int(JobStopped)}) + case JobSucceeded: + cond.And(builder.Eq{"cloudbrain.status": int(JobSucceeded)}) + } + + if len(opts.CloudbrainIDs) > 0 { + cond.And(builder.In("cloudbrain.id", opts.CloudbrainIDs)) + } + + count, err := sess.Where(cond).Count(new(Cloudbrain)) + if err != nil { + return nil, 0, fmt.Errorf("Count: %v", err) + } + + if opts.Page >= 0 && opts.PageSize > 0 { + var start int + if opts.Page == 0 { + start = 0 + } else { + start = (opts.Page - 1) * opts.PageSize + } + sess.Limit(opts.PageSize, start) + } + + sess.OrderBy("cloudbrain.created_unix DESC") + cloudbrains := make([]*Cloudbrain, 0, setting.UI.IssuePagingNum) + if err := sess.Find(&cloudbrains); err != nil { + return nil, 0, fmt.Errorf("Find: %v", err) + } + sess.Close() + + return cloudbrains, count, nil +} + +func CreateCloudbrain(cloudbrain *Cloudbrain) (err error) { + if _, err = x.Insert(cloudbrain); err != nil { + return err + } + + return nil +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 367a8d876..87b79c018 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -213,6 +213,7 @@ var migrations = []Migration{ // v139 -> v140 NewMigration("prepend refs/heads/ to issue refs", prependRefsHeadsToIssueRefs), NewMigration("add dataset migration", addDatasetTable), + NewMigration("add cloudbrain migration", addCloudBrainTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v141.go b/models/migrations/v141.go new file mode 100644 index 000000000..45e9dc2b1 --- /dev/null +++ b/models/migrations/v141.go @@ -0,0 +1,26 @@ +package migrations + +import ( + "fmt" + + "code.gitea.io/gitea/modules/timeutil" + "xorm.io/xorm" +) + +func addCloudBrainTable(x *xorm.Engine) error { + type Cloudbrain struct { + ID int64 `xorm:"pk autoincr"` + JobId string + // Title string `xorm:"INDEX NOT NULL"` + Status int32 `xorm:"INDEX"` + UserID int64 `xorm:"INDEX"` + RepoID int64 `xorm:"INDEX"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + if err := x.Sync2(new(Cloudbrain)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/models.go b/models/models.go index e3d7d934f..8c73ebba8 100644 --- a/models/models.go +++ b/models/models.go @@ -126,6 +126,7 @@ func init() { new(LanguageStat), new(EmailHash), new(Dataset), + new(Cloudbrain), ) gonicNames := []string{"SSL", "UID"} diff --git a/modules/auth/cloudbrain.go b/modules/auth/cloudbrain.go new file mode 100644 index 000000000..c77204937 --- /dev/null +++ b/modules/auth/cloudbrain.go @@ -0,0 +1,16 @@ +package auth + +import ( + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" +) + +// CreateDatasetForm form for dataset page +type CreateCloudBrainForm struct { + Image string `binding:"Required"` + Command string `binding:"Required"` +} + +func (f *CreateCloudBrainForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/modules/cloudbrain/cloudbrain.go b/modules/cloudbrain/cloudbrain.go new file mode 100644 index 000000000..a015fa53a --- /dev/null +++ b/modules/cloudbrain/cloudbrain.go @@ -0,0 +1,58 @@ +package cloudbrain + +import ( + "errors" + "fmt" + "strconv" + "time" + + "code.gitea.io/gitea/modules/context" + + "code.gitea.io/gitea/models" +) + +func GenerateTask(ctx *context.Context, image, command string) error { + nowStr := strconv.FormatInt(time.Now().Unix(), 10) + + jobName := fmt.Sprintf("%s%s", ctx.User.Name, nowStr[len(nowStr)-5:]) + jobResult, err := CreateJob(jobName, models.CreateJobParams{ + JobName: jobName, + RetryCount: 1, + GpuType: "debug", + Image: image, + TaskRoles: []models.TaskRole{ + { + Name: jobName, + TaskNumber: 1, + MinSucceededTaskCount: 1, + MinFailedTaskCount: 1, + CPUNumber: 2, + GPUNumber: 1, + MemoryMB: 16384, + ShmMB: 8192, + Command: command, + NeedIBDevice: false, + IsMainRole: false, + }, + }, + }) + if err != nil { + return err + } + if jobResult.Code != "S000" { + return errors.New(jobResult.Msg) + } + + err = models.CreateCloudbrain(&models.Cloudbrain{ + Status: int32(models.JobWaiting), + UserID: ctx.User.ID, + RepoID: ctx.Repo.Repository.ID, + JobID: jobResult.Payload.JobID, + }) + + if err != nil { + return err + } + + return nil +} diff --git a/modules/cloudbrain/resty.go b/modules/cloudbrain/resty.go new file mode 100644 index 000000000..5f4551a8b --- /dev/null +++ b/modules/cloudbrain/resty.go @@ -0,0 +1,120 @@ +package cloudbrain + +import ( + "fmt" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" + resty "github.com/go-resty/resty/v2" +) + +var ( + restyClient *resty.Client + HOST string + TOKEN string +) + +func getRestyClient() *resty.Client { + if restyClient == nil { + restyClient = resty.New() + } + return restyClient +} + +func checkSetting() { + if len(HOST) != 0 && len(TOKEN) != 0 && restyClient != nil { + return + } + _ = loginCloudbrain() +} + +func loginCloudbrain() error { + conf := setting.GetCloudbrainConfig() + + username := conf.Username + password := conf.Password + HOST = conf.Host + var loginResult models.CloudBrainLoginResult + + client := getRestyClient() + + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(map[string]interface{}{"username": username, "password": password, "expiration": "604800"}). + SetResult(&loginResult). + Post(HOST + "/rest-server/api/v1/token") + if err != nil { + return fmt.Errorf("resty loginCloudbrain: %s", err) + } + + if loginResult.Code != "S000" { + return fmt.Errorf("%s: %s", loginResult.Msg, res.String()) + } + + TOKEN = loginResult.Payload.Token + return nil +} + +func CreateJob(jobName string, createJobParams models.CreateJobParams) (*models.CreateJobResult, error) { + checkSetting() + client := getRestyClient() + var jobResult models.CreateJobResult + + retry := 0 + +sendjob: + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetBody(createJobParams). + SetResult(&jobResult). + Put(HOST + "/rest-server/api/v1/jobs/" + jobName) + + if err != nil { + return nil, fmt.Errorf("resty create job: %s", err) + } + + if jobResult.Code == "S401" && retry < 1 { + retry++ + _ = loginCloudbrain() + goto sendjob + } + + if jobResult.Code != "S000" { + return &jobResult, fmt.Errorf("jobResult err: %s", res.String()) + } + + return &jobResult, nil +} + +func GetJob(jobID string) (*models.GetJobResult, error) { + checkSetting() + // http://192.168.204.24/rest-server/api/v1/jobs/90e26e500c4b3011ea0a251099a987938b96 + client := getRestyClient() + var getJobResult models.GetJobResult + + retry := 0 + +sendjob: + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetResult(&getJobResult). + Get(HOST + "/rest-server/api/v1/jobs/" + jobID) + + if err != nil { + return nil, fmt.Errorf("resty GetJob: %s", err) + } + + if getJobResult.Code == "S401" && retry < 1 { + retry++ + _ = loginCloudbrain() + goto sendjob + } + + if getJobResult.Code != "S000" { + return &getJobResult, fmt.Errorf("jobResult GetJob err: %s", res.String()) + } + + return &getJobResult, nil +} diff --git a/modules/setting/cloudbrain.go b/modules/setting/cloudbrain.go new file mode 100644 index 000000000..71d59c697 --- /dev/null +++ b/modules/setting/cloudbrain.go @@ -0,0 +1,19 @@ +package setting + +type CloudbrainLoginConfig struct { + Username string + Password string + Host string +} + +var ( + Cloudbrain = CloudbrainLoginConfig{} +) + +func GetCloudbrainConfig() CloudbrainLoginConfig { + cloudbrainSec := Cfg.Section("cloudbrain") + Cloudbrain.Username = cloudbrainSec.Key("USERNAME").MustString("") + Cloudbrain.Password = cloudbrainSec.Key("PASSWORD").MustString("") + Cloudbrain.Host = cloudbrainSec.Key("HOST").MustString("") + return Cloudbrain +} diff --git a/routers/repo/cloudbrain.go b/routers/repo/cloudbrain.go index a6aee1407..d0667e151 100644 --- a/routers/repo/cloudbrain.go +++ b/routers/repo/cloudbrain.go @@ -1,8 +1,12 @@ package repo import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/cloudbrain" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" ) const ( @@ -10,14 +14,59 @@ const ( tplCloudBrainNew base.TplName = "repo/cloudbrain/new" ) +// MustEnableDataset check if repository enable internal cb +func MustEnableCloudbrain(ctx *context.Context) { + if !ctx.Repo.CanRead(models.UnitTypeCloudBrain) { + ctx.NotFound("MustEnableCloudbrain", nil) + return + } +} func CloudBrainIndex(ctx *context.Context) { - ctx.Data["PageIsViewCloudBrain"] = true + MustEnableCloudbrain(ctx) + repo := ctx.Repo.Repository + page := ctx.QueryInt("page") + if page <= 0 { + page = 1 + } + + ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ + ListOptions: models.ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + }, + RepoID: repo.ID, + // SortType: sortType, + }) + if err != nil { + ctx.ServerError("Cloudbrain", err) + return + } + + pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) + pager.SetDefaultParams(ctx) + ctx.Data["Page"] = pager + ctx.Data["PageIsCloudBrain"] = true + ctx.Data["Tasks"] = ciTasks ctx.HTML(200, tplCloudBrainIndex) } func CloudBrainNew(ctx *context.Context) { - ctx.Data["PageIsViewCloudBrain"] = true + ctx.Data["PageIsCloudBrain"] = true + ctx.Data["image"] = "192.168.202.74:5000/user-images/deepo:v2.0" + ctx.Data["command"] = `pip3 install jupyterlab==1.1.4;service ssh stop;jupyter lab --no-browser --ip=0.0.0.0 --allow-root --notebook-dir=\"/userhome\" --port=80 --NotebookApp.token=\"\" --LabApp.allow_origin=\"self https://cloudbrain.pcl.ac.cn\"` ctx.HTML(200, tplCloudBrainNew) } + +func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { + ctx.Data["PageIsCloudBrain"] = true + image := form.Image + command := form.Command + err := cloudbrain.GenerateTask(ctx, image, command) + if err != nil { + ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form) + return + } + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain") +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 9ffa50059..4f66593de 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -873,8 +873,9 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/cloudbrain", func() { m.Get("", reqRepoCloudBrainReader, repo.CloudBrainIndex) - m.Get("/new", reqRepoCloudBrainWriter, repo.CloudBrainNew) - }) + m.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainNew) + m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) + }, context.RepoRef()) m.Group("/wiki", func() { m.Get("/?:page", repo.Wiki) diff --git a/templates/repo/cloudbrain/cloudbrain_list.tmpl b/templates/repo/cloudbrain/cloudbrain_list.tmpl deleted file mode 100644 index 6a881c779..000000000 --- a/templates/repo/cloudbrain/cloudbrain_list.tmpl +++ /dev/null @@ -1,26 +0,0 @@ -
-
- -
- waiting -
-
- {{svg "octicon-flame" 16}} 2020-07-13 16:06:08 -
- -
- 18h 0m 8s -
-
- Stop -
-
- 再次提交 -
-
-
\ No newline at end of file diff --git a/templates/repo/cloudbrain/index.tmpl b/templates/repo/cloudbrain/index.tmpl index e0199e9e0..4243d79a9 100644 --- a/templates/repo/cloudbrain/index.tmpl +++ b/templates/repo/cloudbrain/index.tmpl @@ -9,7 +9,7 @@
- {{.i18n.Tr "repo.cloudbrain.new"}} + {{.i18n.Tr "repo.cloudbrain.new"}}
@@ -30,7 +30,35 @@
- {{template "repo/cloudbrain/cloudbrain_list" .}} + {{range .Tasks}} +
+
+ +
+ waiting +
+
+ {{svg "octicon-flame" 16}} {{TimeSinceUnix .CreatedUnix $.Lang}} +
+ +
+ 18h 0m 8s +
+
+ {{.Status}} +
+
+ 再次提交 +
+
+
+ {{end}} + {{template "base/paginate" .}}
diff --git a/templates/repo/cloudbrain/new.tmpl b/templates/repo/cloudbrain/new.tmpl index 00260f6c2..42d12ecf7 100644 --- a/templates/repo/cloudbrain/new.tmpl +++ b/templates/repo/cloudbrain/new.tmpl @@ -3,7 +3,8 @@ {{template "repo/header" .}}
-
+ {{template "base/alert" .}} + {{.CsrfTokenHtml}}

New Cloudbrain task @@ -12,7 +13,7 @@
- +
diff --git a/vendor/modules.txt b/vendor/modules.txt index bd8131df5..9828e9e63 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -314,6 +314,9 @@ github.com/go-redis/redis/internal/hashtag github.com/go-redis/redis/internal/pool github.com/go-redis/redis/internal/proto github.com/go-redis/redis/internal/util +# github.com/go-resty/resty/v2 v2.3.0 +## explicit +github.com/go-resty/resty/v2 # github.com/go-sql-driver/mysql v1.4.1 ## explicit github.com/go-sql-driver/mysql @@ -719,7 +722,7 @@ golang.org/x/crypto/ssh/knownhosts ## explicit golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.0.0-20200506145744-7e3656a0809f +# golang.org/x/net v0.0.0-20200513185701-a91f0712d120 ## explicit golang.org/x/net/context golang.org/x/net/context/ctxhttp