| @@ -31,6 +31,7 @@ type Cloudbrain struct { | |||||
| RepoID int64 `xorm:"INDEX"` | RepoID int64 `xorm:"INDEX"` | ||||
| SubTaskName string `xorm:"INDEX"` | SubTaskName string `xorm:"INDEX"` | ||||
| ContainerID string | ContainerID string | ||||
| ContainerIp string | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | ||||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | ||||
| CanDebug bool `xorm:"-"` | CanDebug bool `xorm:"-"` | ||||
| @@ -219,9 +220,10 @@ type ImageInfo struct { | |||||
| } | } | ||||
| type CommitImageParams 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 { | type CommitImageResult struct { | ||||
| @@ -339,6 +341,6 @@ func UpdateJob(job *Cloudbrain) error { | |||||
| func updateJob(e Engine, job *Cloudbrain) error { | func updateJob(e Engine, job *Cloudbrain) error { | ||||
| var sess *xorm.Session | var sess *xorm.Session | ||||
| sess = e.Where("job_id = ?", job.JobID) | 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 | return err | ||||
| } | } | ||||
| @@ -13,6 +13,11 @@ type CreateCloudBrainForm struct { | |||||
| Attachment string `form:"attachment" binding:"Required"` | 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 { | func (f *CreateCloudBrainForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | ||||
| return validate(errs, ctx.Data, f, ctx.Locale) | return validate(errs, ctx.Data, f, ctx.Locale) | ||||
| } | } | ||||
| @@ -150,7 +150,7 @@ sendjob: | |||||
| return &getImagesResult, nil | return &getImagesResult, nil | ||||
| } | } | ||||
| func CommitImage(jobID string, params models.CommitImageParams) (*models.CommitImageResult, error) { | |||||
| func CommitImage(jobID string, params models.CommitImageParams) error { | |||||
| checkSetting() | checkSetting() | ||||
| client := getRestyClient() | client := getRestyClient() | ||||
| var result models.CommitImageResult | var result models.CommitImageResult | ||||
| @@ -166,7 +166,7 @@ sendjob: | |||||
| Post(HOST + "/rest-server/api/v1/jobs/" + jobID + "/commitImage") | Post(HOST + "/rest-server/api/v1/jobs/" + jobID + "/commitImage") | ||||
| if err != nil { | if err != nil { | ||||
| return nil, fmt.Errorf("resty CommitImage: %v", err) | |||||
| return fmt.Errorf("resty CommitImage: %v", err) | |||||
| } | } | ||||
| if result.Code == "S401" && retry < 1 { | if result.Code == "S401" && retry < 1 { | ||||
| @@ -176,10 +176,10 @@ sendjob: | |||||
| } | } | ||||
| if result.Code != Success { | 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 { | func StopJob(jobID string) error { | ||||
| @@ -752,6 +752,7 @@ cloudbrain=云脑 | |||||
| cloudbrain.new=新建任务 | cloudbrain.new=新建任务 | ||||
| cloudbrain.desc=云脑功能 | cloudbrain.desc=云脑功能 | ||||
| cloudbrain.cancel=取消 | cloudbrain.cancel=取消 | ||||
| cloudbrain.commit_image=提交 | |||||
| template.items=模板选项 | template.items=模板选项 | ||||
| template.git_content=Git数据(默认分支) | template.git_content=Git数据(默认分支) | ||||
| @@ -1,6 +1,7 @@ | |||||
| package repo | package repo | ||||
| import ( | import ( | ||||
| "errors" | |||||
| "os" | "os" | ||||
| "os/exec" | "os/exec" | ||||
| "strconv" | "strconv" | ||||
| @@ -164,6 +165,7 @@ func CloudBrainShow(ctx *context.Context) { | |||||
| ctx.Data["taskRes"] = taskRes | ctx.Data["taskRes"] = taskRes | ||||
| task.Status = taskRes.TaskStatuses[0].State | task.Status = taskRes.TaskStatuses[0].State | ||||
| task.ContainerID = taskRes.TaskStatuses[0].ContainerID | task.ContainerID = taskRes.TaskStatuses[0].ContainerID | ||||
| task.ContainerIp = taskRes.TaskStatuses[0].ContainerIP | |||||
| err = models.UpdateJob(task) | err = models.UpdateJob(task) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.Data["error"] = err.Error() | ctx.Data["error"] = err.Error() | ||||
| @@ -186,27 +188,23 @@ func CloudBrainDebug(ctx *context.Context) { | |||||
| ctx.Redirect(debugUrl) | ctx.Redirect(debugUrl) | ||||
| } | } | ||||
| func CloudBrainCommitImage(ctx *context.Context) { | |||||
| func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrainForm) { | |||||
| var jobID = ctx.Params(":jobid") | var jobID = ctx.Params(":jobid") | ||||
| var description = ctx.Query("description") | |||||
| log.Info(description) | |||||
| task, err := models.GetCloudbrainByJobID(jobID) | task, err := models.GetCloudbrainByJobID(jobID) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("GetCloudbrainByJobID failed", err) | ctx.ServerError("GetCloudbrainByJobID failed", err) | ||||
| return | 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 { | if err != nil { | ||||
| log.Error("CommitImage failed:", err.Error()) | 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 | return | ||||
| } | } | ||||
| @@ -215,9 +213,30 @@ func CloudBrainCommitImage(ctx *context.Context) { | |||||
| func CloudBrainStop(ctx *context.Context) { | func CloudBrainStop(ctx *context.Context) { | ||||
| var jobID = ctx.Params(":jobid") | 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 { | if err != nil { | ||||
| log.Error("StopJob failed:", err.Error()) | |||||
| ctx.ServerError("UpdateJob failed", err) | |||||
| return | return | ||||
| } | } | ||||
| @@ -894,10 +894,12 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Group("/cloudbrain", func() { | m.Group("/cloudbrain", func() { | ||||
| m.Get("", reqRepoCloudBrainReader, repo.CloudBrainIndex) | 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.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainNew) | ||||
| m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | ||||
| }, context.RepoRef()) | }, context.RepoRef()) | ||||
| @@ -63,18 +63,50 @@ | |||||
| </div> | </div> | ||||
| <div class="two wide column"> | <div class="two wide column"> | ||||
| <span class="ui text center clipboard"> | <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> | </form> | ||||
| <a class="fitted" onclick="document.getElementById(stopForm).submit();" style="{{if not .CanDebug}}color:#CCCCCC{{end}}; font-size:16px; font-weight:bold">镜像提交</a> | |||||
| </span> | </span> | ||||
| </div> | </div> | ||||
| <div class="one wide column"> | <div class="one wide column"> | ||||
| <span class="ui text center clipboard"> | <span class="ui text center clipboard"> | ||||
| <form id="stopForm" action="{{if ne .Status "RUNNING"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/stop{{end}}" method="post"> | <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> | </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> | </span> | ||||
| </div> | </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> | ||||
| </div> | </div> | ||||
| {{end}} | {{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> | </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> | |||||