你确认删除该任务么?此任务一旦删除不可恢复。
+diff --git a/models/cloudbrain.go b/models/cloudbrain.go index 00c88494c..8bb3357ae 100755 --- a/models/cloudbrain.go +++ b/models/cloudbrain.go @@ -219,17 +219,20 @@ type GetImagesPayload struct { type CloudbrainsOptions struct { ListOptions - RepoID int64 // include all repos if empty - UserID int64 - JobID string - SortType string - CloudbrainIDs []int64 - // JobStatus CloudbrainStatus + RepoID int64 // include all repos if empty + UserID int64 + JobID string + SortType string + CloudbrainIDs []int64 + JobStatus []string + JobStatusNot bool + Keyword string Type int JobTypes []string VersionName string IsLatestVersion string JobTypeNot bool + NeedRepoInfo bool } type TaskPod struct { @@ -1082,16 +1085,39 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { } if (opts.IsLatestVersion) != "" { - cond = cond.And( - builder.Eq{"cloudbrain.is_latest_version": opts.IsLatestVersion}, - ) + cond = cond.And(builder.Or(builder.And(builder.Eq{"cloudbrain.is_latest_version": opts.IsLatestVersion}, builder.Eq{"cloudbrain.job_type": "TRAIN"}), builder.Neq{"cloudbrain.job_type": "TRAIN"})) } if len(opts.CloudbrainIDs) > 0 { cond = cond.And(builder.In("cloudbrain.id", opts.CloudbrainIDs)) } - count, err := sess.Where(cond).Count(new(Cloudbrain)) + if len(opts.JobStatus) > 0 { + if opts.JobStatusNot { + cond = cond.And( + builder.NotIn("cloudbrain.status", opts.JobStatus), + ) + } else { + cond = cond.And( + builder.In("cloudbrain.status", opts.JobStatus), + ) + } + } + + var count int64 + var err error + condition := "cloudbrain.user_id = `user`.id" + if len(opts.Keyword) == 0 { + count, err = sess.Where(cond).Count(new(Cloudbrain)) + } else { + lowerKeyWord := strings.ToLower(opts.Keyword) + + cond = cond.And(builder.Or(builder.Like{"LOWER(cloudbrain.job_name)", lowerKeyWord}, builder.Like{"`user`.lower_name", lowerKeyWord})) + count, err = sess.Table(&Cloudbrain{}).Where(cond). + Join("left", "`user`", condition).Count(new(CloudbrainInfo)) + + } + if err != nil { return nil, 0, fmt.Errorf("Count: %v", err) } @@ -1109,11 +1135,25 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { sess.OrderBy("cloudbrain.created_unix DESC") cloudbrains := make([]*CloudbrainInfo, 0, setting.UI.IssuePagingNum) if err := sess.Table(&Cloudbrain{}).Where(cond). - Join("left", "`user`", "cloudbrain.user_id = `user`.id"). + Join("left", "`user`", condition). Find(&cloudbrains); err != nil { return nil, 0, fmt.Errorf("Find: %v", err) } + if opts.NeedRepoInfo { + var ids []int64 + for _, task := range cloudbrains { + ids = append(ids, task.RepoID) + } + repositoryMap, err := GetRepositoriesMapByIDs(ids) + if err == nil { + for _, task := range cloudbrains { + task.Repo = repositoryMap[task.RepoID] + } + } + + } + return cloudbrains, count, nil } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d26f603f6..5c0c600be 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -864,9 +864,13 @@ confirm_choice = confirm cloudbran1_tips = Only data in zip format can create cloudbrain tasks cloudbrain_creator=Creator cloudbrain_task = Task Name +cloudbrain_task_type = Task Type +cloudbrain_task_name=Cloud Brain Task Name cloudbrain_operate = Operate cloudbrain_status_createtime = Status/Createtime cloudbrain_status_runtime = Running Time +cloudbrain_jobname_err=Name must start with a lowercase letter or number,can include lowercase letter,number,_ and -,can not end with _, and can be up to 36 characters long. +cloudbrain_query_fail=Failed to query cloudbrain information. record_begintime_get_err=Can not get the record begin time. parameter_is_wrong=The input parameter is wrong. @@ -2344,6 +2348,12 @@ datasets.owner=Owner datasets.name=name datasets.private=Private +cloudbrain.all_task_types=All Task Types +cloudbrain.all_computing_resources=All Computing Resources +cloudbrain.all_status=All Status +cloudbrain.download_report=Download Report +cloudbrain.cloudbrain_name=Cloudbrain Name + hooks.desc = Webhooks automatically make HTTP POST requests to a server when certain openi events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the webhooks guide. hooks.add_webhook = Add Default Webhook hooks.update_webhook = Update Default Webhook diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 31f4ed947..42deebcb7 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -868,10 +868,13 @@ confirm_choice=确定 cloudbran1_tips=只有zip格式的数据集才能发起云脑任务 cloudbrain_creator=创建者 cloudbrain_task=任务名称 +cloudbrain_task_type=任务类型 +cloudbrain_task_name=云脑侧任务名称 cloudbrain_operate=操作 cloudbrain_status_createtime=状态/创建时间 cloudbrain_status_runtime = 运行时长 cloudbrain_jobname_err=只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。 +cloudbrain_query_fail=查询云脑任务失败。 record_begintime_get_err=无法获取统计开始时间。 parameter_is_wrong=输入参数错误,请检查输入参数。 @@ -2354,6 +2357,12 @@ datasets.owner=所有者 datasets.name=名称 datasets.private=私有 +cloudbrain.all_task_types=全部任务类型 +cloudbrain.all_computing_resources=全部计算资源 +cloudbrain.all_status=全部状态 +cloudbrain.download_report=下载此报告 +cloudbrain.cloudbrain_name=云脑侧名称 + hooks.desc=当某些 openi 事件触发时, Web 钩子会自动向服务器发出 HTTP POST 请求。此处定义的 Web 钩子是默认值, 将复制到所有新建项目中。参阅 Web钩子指南 获取更多内容。 hooks.add_webhook=新增默认Web钩子 hooks.update_webhook=更新默认Web钩子 @@ -2420,7 +2429,7 @@ auths.sspi_auto_activate_users_helper=允许 SSPI 认证自动激活新用户 auths.sspi_strip_domain_names=从用户名中删除域名部分 auths.sspi_strip_domain_names_helper=如果选中此项,域名将从登录名中删除(例如,"DOMAIN\user"和"user@example.org",两者都将变成只是“用户”)。 auths.sspi_separator_replacement=要使用的分隔符代替\, / 和 @ -auths.sspi_separator_replacement_helper=用于替换下级登录名称分隔符的字符 (例如) "DOMAIN\user") 中的 \ 和用户主名字(如"user@example.org中的 @ )。 +auths.sspi_separator_replacement_helper=用于替换下级登录名称分隔符的字符 (例如) "DOMAIN\user") 中的 \ 和用户主名字(如"user@example.org"中的 @ )。 auths.sspi_default_language=默认语言 auths.sspi_default_language_helper=SSPI 认证方法为用户自动创建的默认语言。如果您想要自动检测到语言,请留空。 auths.tips=帮助提示 diff --git a/package-lock.json b/package-lock.json index a8e5e3e25..7b706207b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13869,6 +13869,130 @@ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, + "ts-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-4.0.0.tgz", + "integrity": "sha512-iissbnuJkqbB3YAmnWyEbmdNcGcoiiXopKHKyqdoCrFQVi9pnplXeveQDXJnQOCYNNcb2pjT2zzSYTX6c9QtAA==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^3.1.4", + "semver": "^5.0.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, "tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", @@ -13928,6 +14052,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + }, "ua-parser-js": { "version": "0.7.21", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", diff --git a/package.json b/package.json index ba5459a07..e5f829bf1 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,8 @@ "script-loader": "0.7.2", "stylelint": "13.3.3", "stylelint-config-standard": "20.0.0", + "ts-loader": "4.0.0", + "typescript": "4.5.5", "updates": "10.2.11" }, "browserslist": [ diff --git a/routers/admin/cloudbrains.go b/routers/admin/cloudbrains.go new file mode 100644 index 000000000..1cf5ca256 --- /dev/null +++ b/routers/admin/cloudbrains.go @@ -0,0 +1,217 @@ +package admin + +import ( + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/360EntSecGroup-Skylar/excelize/v2" + + "code.gitea.io/gitea/modules/modelarts" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +const ( + tplCloudBrains base.TplName = "admin/cloudbrain/list" + EXCEL_DATE_FORMAT = "20060102150405" + CREATE_TIME_FORMAT = "2006/01/02 15:04:05.00" +) + +func CloudBrains(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.cloudBrains") + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminCloudBrains"] = true + + listType := ctx.Query("listType") + jobType := ctx.Query("jobType") + jobStatus := ctx.Query("jobStatus") + + ctx.Data["ListType"] = listType + ctx.Data["JobType"] = jobType + ctx.Data["JobStatus"] = jobStatus + + page := ctx.QueryInt("page") + if page <= 0 { + page = 1 + } + debugType := modelarts.DebugType + if listType == models.GPUResource { + debugType = models.TypeCloudBrainOne + } else if listType == models.NPUResource { + debugType = models.TypeCloudBrainTwo + } + + var jobTypes []string + jobTypeNot := false + if jobType == string(models.JobTypeDebug) { + jobTypes = append(jobTypes, string(models.JobTypeSnn4imagenet), string(models.JobTypeBrainScore), string(models.JobTypeDebug)) + } else if jobType != "all" && jobType != "" { + jobTypes = append(jobTypes, jobType) + } + + var jobStatuses []string + jobStatusNot := false + if jobStatus == "other" { + jobStatusNot = true + jobStatuses = append(jobStatuses, string(models.ModelArtsTrainJobWaiting), string(models.ModelArtsTrainJobFailed), string(models.ModelArtsRunning), string(models.ModelArtsTrainJobCompleted), + string(models.ModelArtsStarting), string(models.ModelArtsRestarting), string(models.ModelArtsStartFailed), + string(models.ModelArtsStopping), string(models.ModelArtsStopped)) + } else if jobStatus != "all" && jobStatus != "" { + jobStatuses = append(jobStatuses, jobStatus) + } + + keyword := strings.Trim(ctx.Query("q"), " ") + + ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ + ListOptions: models.ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + }, + Keyword: keyword, + Type: debugType, + JobTypeNot: jobTypeNot, + JobStatusNot: jobStatusNot, + JobStatus: jobStatuses, + JobTypes: jobTypes, + NeedRepoInfo: true, + IsLatestVersion: modelarts.IsLatestVersion, + }) + if err != nil { + ctx.ServerError("Get job failed:", err) + return + } + + for i, task := range ciTasks { + ciTasks[i].CanDebug = true + ciTasks[i].CanDel = true + ciTasks[i].Cloudbrain.ComputeResource = task.ComputeResource + } + + pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, getTotalPage(count, setting.UI.IssuePagingNum)) + pager.SetDefaultParams(ctx) + pager.AddParam(ctx, "listType", "ListType") + ctx.Data["Page"] = pager + ctx.Data["PageIsCloudBrain"] = true + ctx.Data["Tasks"] = ciTasks + ctx.Data["CanCreate"] = true + ctx.Data["Keyword"] = keyword + + ctx.HTML(200, tplCloudBrains) + +} + +func DownloadCloudBrains(ctx *context.Context) { + + page := 1 + + pageSize := 300 + + var cloudBrain = ctx.Tr("repo.cloudbrain") + fileName := getFileName(cloudBrain) + + _, total, err := models.Cloudbrains(&models.CloudbrainsOptions{ + ListOptions: models.ListOptions{ + Page: page, + PageSize: 1, + }, + Type: modelarts.DebugType, + NeedRepoInfo: false, + IsLatestVersion: modelarts.IsLatestVersion, + }) + + if err != nil { + log.Warn("Can not get cloud brain info", err) + ctx.Error(http.StatusBadRequest, ctx.Tr("repo.cloudbrain_query_fail")) + return + } + + totalPage := getTotalPage(total, pageSize) + + f := excelize.NewFile() + + index := f.NewSheet(cloudBrain) + f.DeleteSheet("Sheet1") + + for k, v := range allHeader(ctx) { + f.SetCellValue(cloudBrain, k, v) + } + + var row = 2 + for i := 0; i < totalPage; i++ { + + pageRecords, _, err := models.Cloudbrains(&models.CloudbrainsOptions{ + ListOptions: models.ListOptions{ + Page: page, + PageSize: pageSize, + }, + Type: modelarts.DebugType, + NeedRepoInfo: true, + }) + if err != nil { + log.Warn("Can not get cloud brain info", err) + continue + } + for _, record := range pageRecords { + + for k, v := range allValues(row, record, ctx) { + f.SetCellValue(cloudBrain, k, v) + } + row++ + + } + + page++ + } + f.SetActiveSheet(index) + + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(fileName)) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + + f.WriteTo(ctx.Resp) +} + +func allValues(row int, rs *models.CloudbrainInfo, ctx *context.Context) map[string]string { + return map[string]string{getCellName("A", row): rs.JobName, getCellName("B", row): rs.Status, getCellName("C", row): rs.JobType, getCellName("D", row): time.Unix(int64(rs.Cloudbrain.CreatedUnix), 0).Format(CREATE_TIME_FORMAT), getCellName("E", row): getDurationTime(rs), + getCellName("F", row): rs.ComputeResource, getCellName("G", row): rs.Name, getCellName("H", row): rs.Repo.OwnerName + "/" + rs.Repo.Alias, getCellName("I", row): rs.JobName, + } +} + +func getDurationTime(rs *models.CloudbrainInfo) string { + if rs.JobType == "TRAIN" || rs.JobType == "INFERENCE" { + return rs.TrainJobDuration + } else { + return "-" + } +} + +func getFileName(baseName string) string { + return baseName + "_" + time.Now().Format(EXCEL_DATE_FORMAT) + ".xlsx" + +} + +func getTotalPage(total int64, pageSize int) int { + + another := 0 + if int(total)%pageSize != 0 { + another = 1 + } + return int(total)/pageSize + another + +} + +func allHeader(ctx *context.Context) map[string]string { + + return map[string]string{"A1": ctx.Tr("repo.cloudbrain_task"), "B1": ctx.Tr("repo.modelarts.status"), "C1": ctx.Tr("repo.cloudbrain_task_type"), "D1": ctx.Tr("repo.modelarts.createtime"), "E1": ctx.Tr("repo.modelarts.train_job.dura_time"), "F1": ctx.Tr("repo.modelarts.computing_resources"), "G1": ctx.Tr("repo.cloudbrain_creator"), "H1": ctx.Tr("repo.repo_name"), "I1": ctx.Tr("repo.cloudbrain_task_name")} + +} + +func getCellName(col string, row int) string { + return col + strconv.Itoa(row) +} diff --git a/routers/repo/cloudbrain.go b/routers/repo/cloudbrain.go index fdaa60517..344115977 100755 --- a/routers/repo/cloudbrain.go +++ b/routers/repo/cloudbrain.go @@ -586,7 +586,13 @@ func CloudBrainDel(ctx *context.Context) { ctx.ServerError(err.Error(), err) return } - ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=" + listType) + + var isAdminPage = ctx.Query("isadminpage") + if ctx.IsUserSiteAdmin() && isAdminPage == "true" { + ctx.Redirect(setting.AppSubURL + "/admin" + "/cloudbrains") + } else { + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") + } } func deleteCloudbrainJob(ctx *context.Context) error { @@ -1348,5 +1354,11 @@ func BenchmarkDel(ctx *context.Context) { ctx.ServerError(err.Error(), err) return } - ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain/benchmark") + + var isAdminPage = ctx.Query("isadminpage") + if ctx.IsUserSiteAdmin() && isAdminPage == "true" { + ctx.Redirect(setting.AppSubURL + "/admin" + "/cloudbrains") + } else { + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain/benchmark") + } } diff --git a/routers/repo/modelarts.go b/routers/repo/modelarts.go index 611b0d9d1..4e76b8d3a 100755 --- a/routers/repo/modelarts.go +++ b/routers/repo/modelarts.go @@ -445,7 +445,12 @@ func NotebookDel(ctx *context.Context) { return } - ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=" + listType) + var isAdminPage = ctx.Query("isadminpage") + if ctx.IsUserSiteAdmin() && isAdminPage == "true" { + ctx.Redirect(setting.AppSubURL + "/admin" + "/cloudbrains") + } else { + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") + } } func TrainJobIndex(ctx *context.Context) { @@ -1551,7 +1556,12 @@ func TrainJobDel(ctx *context.Context) { DeleteJobStorage(VersionListTasks[0].JobName) } - ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job") + var isAdminPage = ctx.Query("isadminpage") + if ctx.IsUserSiteAdmin() && isAdminPage == "true" { + ctx.Redirect(setting.AppSubURL + "/admin" + "/cloudbrains") + } else { + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job") + } } func TrainJobStop(ctx *context.Context) { diff --git a/routers/routes/routes.go b/routers/routes/routes.go index aab04ac52..e9c7d3fd1 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -508,6 +508,10 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("", admin.Datasets) // m.Post("/delete", admin.DeleteDataset) }) + m.Group("/cloudbrains", func() { + m.Get("", admin.CloudBrains) + m.Get("/download", admin.DownloadCloudBrains) + }) m.Group("/^:configType(hooks|system-hooks)$", func() { m.Get("", admin.DefaultOrSystemWebhooks) diff --git a/templates/admin/cloudbrain/list.tmpl b/templates/admin/cloudbrain/list.tmpl new file mode 100644 index 000000000..0a09230eb --- /dev/null +++ b/templates/admin/cloudbrain/list.tmpl @@ -0,0 +1,219 @@ +{{template "base/head" .}} + +
你确认删除该任务么?此任务一旦删除不可恢复。
+