| @@ -31,6 +31,7 @@ type Cloudbrain struct { | |||
| RepoID int64 `xorm:"INDEX"` | |||
| SubTaskName string `xorm:"INDEX"` | |||
| ContainerID string | |||
| ContainerIp string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| CanDebug bool `xorm:"-"` | |||
| @@ -219,9 +220,10 @@ type ImageInfo struct { | |||
| } | |||
| type CommitImageParams struct { | |||
| Ip string `json:"ip"` | |||
| TaskContainerId string `json:"taskContainerId"` | |||
| ImageDescription string `json:"imageDescription"` | |||
| Ip string `json:"ip"` | |||
| TaskContainerId string `json:"taskContainerId"` | |||
| ImageTag string `json:"imageTag"` | |||
| ImageDescription string `json:"imageDescription"` | |||
| } | |||
| type CommitImageResult struct { | |||
| @@ -339,6 +341,6 @@ func UpdateJob(job *Cloudbrain) error { | |||
| func updateJob(e Engine, job *Cloudbrain) error { | |||
| var sess *xorm.Session | |||
| sess = e.Where("job_id = ?", job.JobID) | |||
| _, err := sess.Cols("status", "container_id").Update(job) | |||
| _, err := sess.Cols("status", "container_id", "container_ip").Update(job) | |||
| return err | |||
| } | |||
| @@ -13,6 +13,11 @@ type CreateCloudBrainForm struct { | |||
| Attachment string `form:"attachment" binding:"Required"` | |||
| } | |||
| type CommitImageCloudBrainForm struct { | |||
| Description string `form:"description" binding:"Required"` | |||
| Tag string `form:"tag" binding:"Required"` | |||
| } | |||
| func (f *CreateCloudBrainForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
| return validate(errs, ctx.Data, f, ctx.Locale) | |||
| } | |||
| @@ -150,7 +150,7 @@ sendjob: | |||
| return &getImagesResult, nil | |||
| } | |||
| func CommitImage(jobID string, params models.CommitImageParams) (*models.CommitImageResult, error) { | |||
| func CommitImage(jobID string, params models.CommitImageParams) error { | |||
| checkSetting() | |||
| client := getRestyClient() | |||
| var result models.CommitImageResult | |||
| @@ -166,7 +166,7 @@ sendjob: | |||
| Post(HOST + "/rest-server/api/v1/jobs/" + jobID + "/commitImage") | |||
| if err != nil { | |||
| return nil, fmt.Errorf("resty CommitImage: %v", err) | |||
| return fmt.Errorf("resty CommitImage: %v", err) | |||
| } | |||
| if result.Code == "S401" && retry < 1 { | |||
| @@ -176,10 +176,10 @@ sendjob: | |||
| } | |||
| if result.Code != Success { | |||
| return &result, fmt.Errorf("CommitImage err: %s", res.String()) | |||
| return fmt.Errorf("CommitImage err: %s", res.String()) | |||
| } | |||
| return &result, nil | |||
| return nil | |||
| } | |||
| func StopJob(jobID string) error { | |||
| @@ -752,6 +752,7 @@ cloudbrain=云脑 | |||
| cloudbrain.new=新建任务 | |||
| cloudbrain.desc=云脑功能 | |||
| cloudbrain.cancel=取消 | |||
| cloudbrain.commit_image=提交 | |||
| template.items=模板选项 | |||
| template.git_content=Git数据(默认分支) | |||
| @@ -1,6 +1,7 @@ | |||
| package repo | |||
| import ( | |||
| "errors" | |||
| "os" | |||
| "os/exec" | |||
| "strconv" | |||
| @@ -164,6 +165,7 @@ func CloudBrainShow(ctx *context.Context) { | |||
| ctx.Data["taskRes"] = taskRes | |||
| task.Status = taskRes.TaskStatuses[0].State | |||
| task.ContainerID = taskRes.TaskStatuses[0].ContainerID | |||
| task.ContainerIp = taskRes.TaskStatuses[0].ContainerIP | |||
| err = models.UpdateJob(task) | |||
| if err != nil { | |||
| ctx.Data["error"] = err.Error() | |||
| @@ -186,27 +188,23 @@ func CloudBrainDebug(ctx *context.Context) { | |||
| ctx.Redirect(debugUrl) | |||
| } | |||
| func CloudBrainCommitImage(ctx *context.Context) { | |||
| func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrainForm) { | |||
| var jobID = ctx.Params(":jobid") | |||
| var description = ctx.Query("description") | |||
| log.Info(description) | |||
| task, err := models.GetCloudbrainByJobID(jobID) | |||
| if err != nil { | |||
| ctx.ServerError("GetCloudbrainByJobID failed", err) | |||
| return | |||
| } | |||
| jobResult, err := cloudbrain.CommitImage(jobID, models.CommitImageParams{ | |||
| Ip: setting.ImageServerHost, | |||
| TaskContainerId: task.ContainerID, | |||
| ImageDescription: description, | |||
| err = cloudbrain.CommitImage(jobID, models.CommitImageParams{ | |||
| Ip: task.ContainerIp, | |||
| TaskContainerId: task.ContainerID, | |||
| ImageDescription: form.Description, | |||
| ImageTag: form.Tag, | |||
| }) | |||
| if err != nil { | |||
| log.Error("CommitImage failed:", err.Error()) | |||
| return | |||
| } | |||
| if jobResult.Code != cloudbrain.Success { | |||
| log.Error("CommitImage(%s) failed:%s", jobID, jobResult.Msg) | |||
| ctx.ServerError("CommitImage failed", err) | |||
| return | |||
| } | |||
| @@ -215,9 +213,30 @@ func CloudBrainCommitImage(ctx *context.Context) { | |||
| func CloudBrainStop(ctx *context.Context) { | |||
| var jobID = ctx.Params(":jobid") | |||
| err := cloudbrain.StopJob(jobID) | |||
| log.Info(jobID) | |||
| task, err := models.GetCloudbrainByJobID(jobID) | |||
| if err != nil { | |||
| ctx.ServerError("GetCloudbrainByJobID failed", err) | |||
| return | |||
| } | |||
| if task.Status != string(models.JobRunning) { | |||
| log.Error("the job(%s) is not running", task.JobName) | |||
| ctx.ServerError("the job is not running", errors.New("the job is not running")) | |||
| return | |||
| } | |||
| err = cloudbrain.StopJob(jobID) | |||
| if err != nil { | |||
| log.Error("StopJob(%s) failed:%v", task.JobName, err.Error()) | |||
| ctx.ServerError("StopJob failed", err) | |||
| return | |||
| } | |||
| task.Status = string(models.JobStopped) | |||
| err = models.UpdateJob(task) | |||
| if err != nil { | |||
| log.Error("StopJob failed:", err.Error()) | |||
| ctx.ServerError("UpdateJob failed", err) | |||
| return | |||
| } | |||
| @@ -894,10 +894,12 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Group("/cloudbrain", func() { | |||
| m.Get("", reqRepoCloudBrainReader, repo.CloudBrainIndex) | |||
| m.Get("/:jobid", reqRepoCloudBrainReader, repo.CloudBrainShow) | |||
| m.Get("/:jobid/debug", reqRepoCloudBrainReader, repo.CloudBrainDebug) | |||
| m.Post("/:jobid/commit_image", reqRepoCloudBrainReader, repo.CloudBrainCommitImage) | |||
| m.Post("/:jobid/stop", reqRepoCloudBrainReader, repo.CloudBrainStop) | |||
| m.Group("/:jobid", func() { | |||
| m.Get("", reqRepoCloudBrainReader, repo.CloudBrainShow) | |||
| m.Get("/debug", reqRepoCloudBrainReader, repo.CloudBrainDebug) | |||
| m.Post("/commit_image", reqRepoCloudBrainWriter, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.CloudBrainCommitImage) | |||
| m.Post("/stop", reqRepoCloudBrainWriter, repo.CloudBrainStop) | |||
| }) | |||
| m.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainNew) | |||
| m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | |||
| }, context.RepoRef()) | |||
| @@ -63,18 +63,50 @@ | |||
| </div> | |||
| <div class="two wide column"> | |||
| <span class="ui text center clipboard"> | |||
| <form id="stopForm" action="{{if not .CanDebug}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/commit_image{{end}}" method="post"> | |||
| <form id="commitImageForm" action="{{if not .CanDebug}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/commit_image{{end}}" method="post"> | |||
| {{$.CsrfTokenHtml}} | |||
| <a class="fitted" onclick="document.getElementById('commitImageForm').submit();" style="{{if not .CanDebug}}color:#CCCCCC{{end}}; font-size:16px; font-weight:bold">镜像提交</a> | |||
| </form> | |||
| <a class="fitted" onclick="document.getElementById(stopForm).submit();" style="{{if not .CanDebug}}color:#CCCCCC{{end}}; font-size:16px; font-weight:bold">镜像提交</a> | |||
| </span> | |||
| </div> | |||
| <div class="one wide column"> | |||
| <span class="ui text center clipboard"> | |||
| <form id="stopForm" action="{{if ne .Status "RUNNING"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/stop{{end}}" method="post"> | |||
| {{$.CsrfTokenHtml}} | |||
| <a class="fitted" onclick="document.getElementById('stopForm').submit();" style="{{if ne .Status "RUNNING"}}color:#CCCCCC{{end}}; font-size:16px; font-weight:bold">停止</a> | |||
| </form> | |||
| <a class="fitted" onclick="document.getElementById(stopForm).submit();" style="{{if ne .Status "RUNNING"}}color:#CCCCCC{{end}}; font-size:16px; font-weight:bold">停止</a> | |||
| </span> | |||
| </div> | |||
| <!-- 打开弹窗按钮 --> | |||
| <button id="imageBtn">提交镜像</button> | |||
| <!-- 弹窗 --> | |||
| <div id="imageModal" class="modal"> | |||
| <!-- 弹窗内容 --> | |||
| <div class="modal-content"> | |||
| <span class="close">×</span> | |||
| <form id="commitImageForm" action="{{$.Link}}/{{.JobID}}/commit_image" method="post"> | |||
| {{$.CsrfTokenHtml}} | |||
| <div class="inline required field"> | |||
| <label>镜像标签:</label> | |||
| <input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="255"> | |||
| </div> | |||
| <div class="inline required field"> | |||
| <label>镜像描述:</label> | |||
| <textarea name="description" rows="10"></textarea> | |||
| </div> | |||
| <div class="inline field"> | |||
| <label></label> | |||
| <button class="ui green button"> | |||
| {{$.i18n.Tr "repo.cloudbrain.commit_image"}} | |||
| </button> | |||
| <a class="ui button" href="/">{{$.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| @@ -106,4 +138,71 @@ $( document ).ready(function() { | |||
| }); | |||
| }); | |||
| // 获取弹窗 | |||
| var modal = document.getElementById('imageModal'); | |||
| // 打开弹窗的按钮对象 | |||
| var btn = document.getElementById("imageBtn"); | |||
| // 获取 <span> 元素,用于关闭弹窗 | |||
| var span = document.querySelector('.close'); | |||
| // 点击按钮打开弹窗 | |||
| btn.onclick = function() { | |||
| modal.style.display = "block"; | |||
| } | |||
| // 点击 <span> (x), 关闭弹窗 | |||
| span.onclick = function() { | |||
| modal.style.display = "none"; | |||
| } | |||
| // 在用户点击其他地方时,关闭弹窗 | |||
| window.onclick = function(event) { | |||
| if (event.target == modal) { | |||
| modal.style.display = "none"; | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| /* 弹窗 (background) */ | |||
| .modal { | |||
| display: none; /* 默认隐藏 */ | |||
| position: fixed; /* 固定定位 */ | |||
| z-index: 1; /* 设置在顶层 */ | |||
| left: 0; | |||
| top: 0; | |||
| width: 100%; | |||
| height: 100%; | |||
| overflow: auto; | |||
| background-color: rgb(0,0,0); | |||
| background-color: rgba(0,0,0,0.4); | |||
| } | |||
| /* 弹窗内容 */ | |||
| .modal-content { | |||
| background-color: #fefefe; | |||
| margin: 15% auto; | |||
| padding: 20px; | |||
| border: 1px solid #888; | |||
| width: 30%; | |||
| } | |||
| /* 关闭按钮 */ | |||
| .close { | |||
| color: #aaa; | |||
| float: right; | |||
| font-size: 28px; | |||
| font-weight: bold; | |||
| } | |||
| .close:hover, | |||
| .close:focus { | |||
| color: black; | |||
| text-decoration: none; | |||
| cursor: pointer; | |||
| } | |||
| </style> | |||