Browse Source

Merge pull request 'inference-patch02' (#1382) from inference-patch02 into develop

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/1382
Reviewed-by: lewis <747342561@qq.com>
tags/v1.22.1.3
lewis 3 years ago
parent
commit
32bb87e7e7
43 changed files with 2685 additions and 258 deletions
  1. +3
    -2
      models/action.go
  2. +1
    -0
      models/ai_model_manage.go
  3. +50
    -7
      models/cloudbrain.go
  4. +24
    -0
      modules/auth/modelarts.go
  5. +114
    -5
      modules/modelarts/modelarts.go
  6. +56
    -0
      modules/modelarts/resty.go
  7. +15
    -6
      modules/storage/obs.go
  8. +40
    -29
      options/locale/locale_en-US.ini
  9. +14
    -3
      options/locale/locale_zh-CN.ini
  10. +32
    -17
      public/home/home.js
  11. +9
    -0
      routers/api/v1/api.go
  12. +80
    -6
      routers/api/v1/repo/modelarts.go
  13. +45
    -21
      routers/repo/ai_model_manage.go
  14. +599
    -29
      routers/repo/modelarts.go
  15. +16
    -9
      routers/repo/repo_statistic.go
  16. +4
    -1
      routers/repo/setting.go
  17. +11
    -0
      routers/routes/routes.go
  18. +23
    -8
      services/socketwrap/clientManager.go
  19. +1
    -1
      templates/base/head_navbar.tmpl
  20. +1
    -1
      templates/home.tmpl
  21. +8
    -8
      templates/repo/cloudbrain/new.tmpl
  22. +1
    -1
      templates/repo/create.tmpl
  23. +1
    -1
      templates/repo/datasets/index.tmpl
  24. +5
    -3
      templates/repo/debugjob/index.tmpl
  25. +1
    -1
      templates/repo/issue/new_form.tmpl
  26. +1
    -1
      templates/repo/issue/view_title.tmpl
  27. +1
    -1
      templates/repo/migrate.tmpl
  28. +337
    -0
      templates/repo/modelarts/inferencejob/index.tmpl
  29. +477
    -0
      templates/repo/modelarts/inferencejob/new.tmpl
  30. +653
    -0
      templates/repo/modelarts/inferencejob/show.tmpl
  31. +5
    -5
      templates/repo/modelarts/notebook/new.tmpl
  32. +4
    -4
      templates/repo/modelarts/trainjob/edit_para.tmpl
  33. +6
    -3
      templates/repo/modelarts/trainjob/index.tmpl
  34. +7
    -60
      templates/repo/modelarts/trainjob/new.tmpl
  35. +12
    -10
      templates/repo/modelarts/trainjob/show.tmpl
  36. +4
    -4
      templates/repo/modelarts/trainjob/version_new.tmpl
  37. +6
    -1
      templates/repo/modelmanage/index.tmpl
  38. +1
    -1
      templates/repo/pulls/fork.tmpl
  39. +2
    -2
      templates/repo/release/new.tmpl
  40. +4
    -4
      templates/repo/settings/options.tmpl
  41. +1
    -1
      templates/user/settings/profile.tmpl
  42. +9
    -1
      web_src/js/components/Model.vue
  43. +1
    -1
      web_src/js/index.js

+ 3
- 2
models/action.go View File

@@ -346,11 +346,12 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
return actions, nil return actions, nil
} }


func GetLast20PublicFeeds() ([]*Action, error) {
func GetLast20PublicFeeds(opTypes []int) ([]*Action, error) {
cond := builder.NewCond() cond := builder.NewCond()
cond = cond.And(builder.Eq{"is_private": false}) cond = cond.And(builder.Eq{"is_private": false})
cond = cond.And(builder.Eq{"is_deleted": false}) cond = cond.And(builder.Eq{"is_deleted": false})

cond = cond.And(builder.Expr("user_id=act_user_id"))
cond = cond.And(builder.In("op_type", opTypes))


actions := make([]*Action, 0, 20) actions := make([]*Action, 0, 20)




+ 1
- 0
models/ai_model_manage.go View File

@@ -36,6 +36,7 @@ type AiModelManage struct {
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
IsCanOper bool IsCanOper bool
IsCanDelete bool
} }


type AiModelQueryOptions struct { type AiModelQueryOptions struct {


+ 50
- 7
models/cloudbrain.go View File

@@ -33,6 +33,7 @@ const (
JobTypeSnn4imagenet JobType = "SNN4IMAGENET" JobTypeSnn4imagenet JobType = "SNN4IMAGENET"
JobTypeBrainScore JobType = "BRAINSCORE" JobTypeBrainScore JobType = "BRAINSCORE"
JobTypeTrain JobType = "TRAIN" JobTypeTrain JobType = "TRAIN"
JobTypeInference JobType = "INFERENCE"


//notebook //notebook
ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中 ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中
@@ -111,7 +112,7 @@ type Cloudbrain struct {
ComputeResource string //计算资源,例如npu ComputeResource string //计算资源,例如npu
EngineID int64 //引擎id EngineID int64 //引擎id


TrainUrl string //输出的obs路径
TrainUrl string //输出模型的obs路径
BranchName string //分支名称 BranchName string //分支名称
Parameters string //传给modelarts的param参数 Parameters string //传给modelarts的param参数
BootFile string //启动文件 BootFile string //启动文件
@@ -125,6 +126,12 @@ type Cloudbrain struct {
EngineName string //引擎名称 EngineName string //引擎名称
TotalVersionCount int //任务的所有版本数量,包括删除的 TotalVersionCount int //任务的所有版本数量,包括删除的


LabelName string //标签名称
ModelName string //模型名称
ModelVersion string //模型版本
CkptName string //权重文件名称
ResultUrl string //推理结果的obs路径

User *User `xorm:"-"` User *User `xorm:"-"`
Repo *Repository `xorm:"-"` Repo *Repository `xorm:"-"`
} }
@@ -207,7 +214,7 @@ type CloudbrainsOptions struct {
CloudbrainIDs []int64 CloudbrainIDs []int64
// JobStatus CloudbrainStatus // JobStatus CloudbrainStatus
Type int Type int
JobType string
JobTypes []string
VersionName string VersionName string
IsLatestVersion string IsLatestVersion string
JobTypeNot bool JobTypeNot bool
@@ -644,6 +651,25 @@ type Config struct {
Flavor Flavor `json:"flavor"` Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"` PoolID string `json:"pool_id"`
} }
type CreateInferenceJobParams struct {
JobName string `json:"job_name"`
Description string `json:"job_desc"`
InfConfig InfConfig `json:"config"`
WorkspaceID string `json:"workspace_id"`
}

type InfConfig struct {
WorkServerNum int `json:"worker_server_num"`
AppUrl string `json:"app_url"` //训练作业的代码目录
BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下
Parameter []Parameter `json:"parameter"`
DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL
EngineID int64 `json:"engine_id"`
LogUrl string `json:"log_url"`
CreateVersion bool `json:"create_version"`
Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
}


type CreateTrainJobVersionParams struct { type CreateTrainJobVersionParams struct {
Description string `json:"job_desc"` Description string `json:"job_desc"`
@@ -894,14 +920,14 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
) )
} }


if (opts.JobType) != "" {
if len(opts.JobTypes) > 0 {
if opts.JobTypeNot { if opts.JobTypeNot {
cond = cond.And( cond = cond.And(
builder.Neq{"cloudbrain.job_type": opts.JobType},
builder.NotIn("cloudbrain.job_type", opts.JobTypes),
) )
} else { } else {
cond = cond.And( cond = cond.And(
builder.Eq{"cloudbrain.job_type": opts.JobType},
builder.In("cloudbrain.job_type", opts.JobTypes),
) )
} }
} }
@@ -978,6 +1004,7 @@ func QueryModelTrainJobList(repoId int64) ([]*CloudbrainInfo, int, error) {
cond = cond.And( cond = cond.And(
builder.Eq{"job_type": "TRAIN"}, builder.Eq{"job_type": "TRAIN"},
) )

cloudbrains := make([]*CloudbrainInfo, 0) cloudbrains := make([]*CloudbrainInfo, 0)
if err := sess.Select("job_id,job_name").Table(&Cloudbrain{}).Where(cond).OrderBy("created_unix DESC"). if err := sess.Select("job_id,job_name").Table(&Cloudbrain{}).Where(cond).OrderBy("created_unix DESC").
Find(&cloudbrains); err != nil { Find(&cloudbrains); err != nil {
@@ -1025,9 +1052,9 @@ func CloudbrainsVersionList(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int, e
) )
} }


if (opts.JobType) != "" {
if len(opts.JobTypes) > 0 {
cond = cond.And( cond = cond.And(
builder.Eq{"cloudbrain.job_type": opts.JobType},
builder.In("cloudbrain.job_type", opts.JobTypes),
) )
} }


@@ -1211,6 +1238,22 @@ func GetCloudbrainTrainJobCountByUserID(userID int64) (int, error) {
return int(count), err return int(count), err
} }


func GetCloudbrainInferenceJobCountByUserID(userID int64) (int, error) {
count, err := x.In("status", ModelArtsTrainJobInit, ModelArtsTrainJobImageCreating, ModelArtsTrainJobSubmitTrying, ModelArtsTrainJobWaiting, ModelArtsTrainJobRunning, ModelArtsTrainJobScaling, ModelArtsTrainJobCheckInit, ModelArtsTrainJobCheckRunning, ModelArtsTrainJobCheckRunningCompleted).
And("job_type = ? and user_id = ? and type = ?", JobTypeInference, userID, TypeCloudBrainTwo).Count(new(Cloudbrain))
return int(count), err
}

func UpdateInferenceJob(job *Cloudbrain) error {
return updateInferenceJob(x, job)
}

func updateInferenceJob(e Engine, job *Cloudbrain) error {
var sess *xorm.Session
sess = e.Where("job_id = ?", job.JobID)
_, err := sess.Cols("status", "train_job_duration").Update(job)
return err
}
func RestartCloudbrain(old *Cloudbrain, new *Cloudbrain) (err error) { func RestartCloudbrain(old *Cloudbrain, new *Cloudbrain) (err error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()


+ 24
- 0
modules/auth/modelarts.go View File

@@ -45,6 +45,30 @@ type CreateModelArtsTrainJobForm struct {
EngineName string `form:"engine_names" binding:"Required"` EngineName string `form:"engine_names" binding:"Required"`
} }


type CreateModelArtsInferenceJobForm struct {
JobName string `form:"job_name" binding:"Required"`
Attachment string `form:"attachment" binding:"Required"`
BootFile string `form:"boot_file" binding:"Required"`
WorkServerNumber int `form:"work_server_number" binding:"Required"`
EngineID int `form:"engine_id" binding:"Required"`
PoolID string `form:"pool_id" binding:"Required"`
Flavor string `form:"flavor" binding:"Required"`
Params string `form:"run_para_list" binding:"Required"`
Description string `form:"description"`
IsSaveParam string `form:"is_save_para"`
ParameterTemplateName string `form:"parameter_template_name"`
PrameterDescription string `form:"parameter_description"`
BranchName string `form:"branch_name" binding:"Required"`
VersionName string `form:"version_name" binding:"Required"`
FlavorName string `form:"flaver_names" binding:"Required"`
EngineName string `form:"engine_names" binding:"Required"`
LabelName string `form:"label_names" binding:"Required"`
TrainUrl string `form:"train_url" binding:"Required"`
ModelName string `form:"model_name" binding:"Required"`
ModelVersion string `form:"model_version" binding:"Required"`
CkptName string `form:"ckpt_name" binding:"Required"`
}

func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateModelArtsTrainJobForm) 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)
} }

+ 114
- 5
modules/modelarts/modelarts.go View File

@@ -38,6 +38,7 @@ const (
// "]}" // "]}"
CodePath = "/code/" CodePath = "/code/"
OutputPath = "/output/" OutputPath = "/output/"
ResultPath = "/result/"
LogPath = "/log/" LogPath = "/log/"
JobPath = "/job/" JobPath = "/job/"
OrderDesc = "desc" //向下查询 OrderDesc = "desc" //向下查询
@@ -45,6 +46,8 @@ const (
Lines = 500 Lines = 500
TrainUrl = "train_url" TrainUrl = "train_url"
DataUrl = "data_url" DataUrl = "data_url"
ResultUrl = "result_url"
CkptUrl = "ckpt_url"
PerPage = 10 PerPage = 10
IsLatestVersion = "1" IsLatestVersion = "1"
NotLatestVersion = "0" NotLatestVersion = "0"
@@ -113,6 +116,36 @@ type GenerateTrainJobVersionReq struct {
TotalVersionCount int TotalVersionCount int
} }


type GenerateInferenceJobReq struct {
JobName string
Uuid string
Description string
CodeObsPath string
BootFile string
BootFileUrl string
DataUrl string
TrainUrl string
FlavorCode string
LogUrl string
PoolID string
WorkServerNumber int
EngineID int64
Parameters []models.Parameter
CommitID string
Params string
BranchName string
FlavorName string
EngineName string
LabelName string
IsLatestVersion string
VersionCount int
TotalVersionCount int
ModelName string
ModelVersion string
CkptName string
ResultUrl string
}

type VersionInfo struct { type VersionInfo struct {
Version []struct { Version []struct {
ID int `json:"id"` ID int `json:"id"`
@@ -329,12 +362,14 @@ func GenerateTrainJobVersion(ctx *context.Context, req *GenerateTrainJobReq, job
return err return err
} }


var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeTrain))
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeTrain),
JobID: strconv.FormatInt(jobResult.JobID, 10),
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobTypes: jobTypes,
JobID: strconv.FormatInt(jobResult.JobID, 10),
}) })
if err != nil { if err != nil {
ctx.ServerError("Cloudbrain", err) ctx.ServerError("Cloudbrain", err)
@@ -441,8 +476,82 @@ func TransTrainJobStatus(status int) string {
} }
} }


func GetVersionOutputPathByTotalVersionCount(TotalVersionCount int) (VersionOutputPath string) {
func GetOutputPathByCount(TotalVersionCount int) (VersionOutputPath string) {
talVersionCountToString := fmt.Sprintf("%04d", TotalVersionCount) talVersionCountToString := fmt.Sprintf("%04d", TotalVersionCount)
VersionOutputPath = "V" + talVersionCountToString VersionOutputPath = "V" + talVersionCountToString
return VersionOutputPath return VersionOutputPath
} }

func GenerateInferenceJob(ctx *context.Context, req *GenerateInferenceJobReq) (err error) {
jobResult, err := createInferenceJob(models.CreateInferenceJobParams{
JobName: req.JobName,
Description: req.Description,
InfConfig: models.InfConfig{
WorkServerNum: req.WorkServerNumber,
AppUrl: req.CodeObsPath,
BootFileUrl: req.BootFileUrl,
DataUrl: req.DataUrl,
EngineID: req.EngineID,
// TrainUrl: req.TrainUrl,
LogUrl: req.LogUrl,
PoolID: req.PoolID,
CreateVersion: true,
Flavor: models.Flavor{
Code: req.FlavorCode,
},
Parameter: req.Parameters,
},
})
if err != nil {
log.Error("CreateJob failed: %v", err.Error())
return err
}

attach, err := models.GetAttachmentByUUID(req.Uuid)
if err != nil {
log.Error("GetAttachmentByUUID(%s) failed:%v", strconv.FormatInt(jobResult.JobID, 10), err.Error())
return err
}

err = models.CreateCloudbrain(&models.Cloudbrain{
Status: TransTrainJobStatus(jobResult.Status),
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
JobID: strconv.FormatInt(jobResult.JobID, 10),
JobName: req.JobName,
JobType: string(models.JobTypeInference),
Type: models.TypeCloudBrainTwo,
VersionID: jobResult.VersionID,
VersionName: jobResult.VersionName,
Uuid: req.Uuid,
DatasetName: attach.Name,
CommitID: req.CommitID,
EngineID: req.EngineID,
TrainUrl: req.TrainUrl,
BranchName: req.BranchName,
Parameters: req.Params,
BootFile: req.BootFile,
DataUrl: req.DataUrl,
LogUrl: req.LogUrl,
FlavorCode: req.FlavorCode,
Description: req.Description,
WorkServerNumber: req.WorkServerNumber,
FlavorName: req.FlavorName,
EngineName: req.EngineName,
LabelName: req.LabelName,
IsLatestVersion: req.IsLatestVersion,
VersionCount: req.VersionCount,
TotalVersionCount: req.TotalVersionCount,
ModelName: req.ModelName,
ModelVersion: req.ModelVersion,
CkptName: req.CkptName,
ResultUrl: req.ResultUrl,
})

if err != nil {
log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error())
return err
}

return nil
}

+ 56
- 0
modules/modelarts/resty.go View File

@@ -874,3 +874,59 @@ sendjob:


return &result, nil return &result, nil
} }

func createInferenceJob(createJobParams models.CreateInferenceJobParams) (*models.CreateTrainJobResult, error) {
checkSetting()
client := getRestyClient()
var result models.CreateTrainJobResult

retry := 0

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetBody(createJobParams).
SetResult(&result).
Post(HOST + "/v1/" + setting.ProjectID + urlTrainJob)

if err != nil {
return nil, fmt.Errorf("resty create inference-job: %s", err)
}

req, _ := json.Marshal(createJobParams)
log.Info("%s", req)

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
BootFileErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.BootFileUrl + "'."
DataSetErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.DataUrl + "'."
if temp.ErrorMsg == BootFileErrorMsg {
log.Error("启动文件错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return &result, fmt.Errorf("启动文件错误!")
}
if temp.ErrorMsg == DataSetErrorMsg {
log.Error("数据集错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return &result, fmt.Errorf("数据集错误!")
}
return &result, fmt.Errorf("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}

if !result.IsSuccess {
log.Error("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg)
return &result, fmt.Errorf("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg)
}

return &result, nil
}

+ 15
- 6
modules/storage/obs.go View File

@@ -28,6 +28,13 @@ type FileInfo struct {
ParenDir string `json:"ParenDir"` ParenDir string `json:"ParenDir"`
UUID string `json:"UUID"` UUID string `json:"UUID"`
} }
type FileInfoList []FileInfo

func (ulist FileInfoList) Swap(i, j int) { ulist[i], ulist[j] = ulist[j], ulist[i] }
func (ulist FileInfoList) Len() int { return len(ulist) }
func (ulist FileInfoList) Less(i, j int) bool {
return strings.Compare(ulist[i].FileName, ulist[j].FileName) > 0
}


//check if has the object //check if has the object
func ObsHasObject(path string) (bool, error) { func ObsHasObject(path string) (bool, error) {
@@ -333,7 +340,8 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er
input.MaxKeys = 100 input.MaxKeys = 100
input.Prefix = prefix input.Prefix = prefix
index := 1 index := 1
fileInfos := make([]FileInfo, 0)
fileInfoList := FileInfoList{}

prefixLen := len(prefix) prefixLen := len(prefix)
log.Info("prefix=" + input.Prefix) log.Info("prefix=" + input.Prefix)
for { for {
@@ -358,7 +366,7 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er
IsDir: isDir, IsDir: isDir,
ParenDir: "", ParenDir: "",
} }
fileInfos = append(fileInfos, fileInfo)
fileInfoList = append(fileInfoList, fileInfo)
} }
if output.IsTruncated { if output.IsTruncated {
input.Marker = output.NextMarker input.Marker = output.NextMarker
@@ -373,13 +381,14 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er
return nil, err return nil, err
} }
} }
return fileInfos, nil
sort.Sort(fileInfoList)
return fileInfoList, nil
} }


func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error) {
func GetObsListObject(jobName, outPutPath, parentDir, versionName string) ([]FileInfo, error) {
input := &obs.ListObjectsInput{} input := &obs.ListObjectsInput{}
input.Bucket = setting.Bucket input.Bucket = setting.Bucket
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, versionName, parentDir), "/")
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, outPutPath, versionName, parentDir), "/")
strPrefix := strings.Split(input.Prefix, "/") strPrefix := strings.Split(input.Prefix, "/")
output, err := ObsCli.ListObjects(input) output, err := ObsCli.ListObjects(input)
fileInfos := make([]FileInfo, 0) fileInfos := make([]FileInfo, 0)
@@ -401,7 +410,7 @@ func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error
nextParentDir = parentDir + "/" + fileName nextParentDir = parentDir + "/" + fileName
} }


if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath {
if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == outPutPath {
continue continue
} }
} else { } else {


+ 40
- 29
options/locale/locale_en-US.ini View File

@@ -221,42 +221,42 @@ issues.in_your_repos = In your repositories
contributors = Contributors contributors = Contributors


page_title=Explore Better AI page_title=Explore Better AI
page_small_title=OpenI AI development cooperation platform
page_small_title=OpenI AI Development Cooperation Platform
page_description=The one-stop collaborative development environment for AI field provides AI development pipeline integrating code development, data management, model debugging, reasoning and evaluation page_description=The one-stop collaborative development environment for AI field provides AI development pipeline integrating code development, data management, model debugging, reasoning and evaluation
page_use=Use Now page_use=Use Now
page_only_dynamic=Show only open source project dynamics
page_recommend_org=Recommended organization
page_recommend_org_desc=These excellent organizations are using Qizhi AI to develop collaboration platforms; Your organization also wants to show here,
page_recommend_org_commit=Click here to submit
page_recommend_org_more=More organizations
page_recommend_repo=Recommended projects
page_recommend_repo_desc=Excellent AI project recommendation; Your project also wants to show here,
page_recommend_repo_commit=Click here to submit
page_recommend_repo_go=. Click here
page_recommend_repo_more=Project Square
page_dev_env=Collaborative development environment
page_dev_env_desc=The biggest difference between Qizhi AI collaborative development platform and traditional git platform is that it provides a collaborative development environment for AI development
page_dev_env_desc_title=Unified management of development elements
page_dev_env_desc_desc=The platform provides four elements of AI development: unified management of model code, data set, model and execution environment
page_dev_env_desc1_title=Data collaboration and sharing
page_dev_env_desc1_desc=By uploading data sets in the project, many project members cooperate to complete data preprocessing; You can also establish a better model with community developers by setting the data as a public dataset
page_dev_env_desc2_title=Model management and sharing
page_dev_env_desc2_desc=Associate the model with the code version, adjust the model in different ways based on the code history version, and save the results; The trained model can be open and shared, so that more people can use the model to test and give feedback
page_dev_env_desc3_title=One configuration, multiple use
page_dev_env_desc3_desc=Provide execution environment sharing, one-time configuration and multiple use, reduce the threshold of model development, and avoid spending repeated time configuring complex environments
page_dev_yunlao=PengCheng Cloudbrain open source collaboration
page_dev_yunlao_desc1=The platform has been connected with Pengcheng Cloudbrain and can use the rich computing resources of Pengcheng Cloudbrain to complete AI development tasks
page_dev_yunlao_desc2=Pengcheng Cloudbrain's existing AI computing power is 100p FLOPS@FP16 (billions of half precision floating-point calculations per second), the main hardware infrastructure is composed of GPU server equipped with NVIDIA Tesla V100 and Atlas 900 AI cluster equipped with Kunpeng and shengteng processors
page_dev_yunlao_desc3=Developers can freely choose the corresponding computing resources according to the use requirements, and can test the adaptability, performance and stability of the model in different hardware environments
page_dev_yunlao_desc4=If your model needs more computing resources, you can also apply separately
page_dev_yunlao_apply=Separate apply
page_only_dynamic=Only show the dynamics of open source projects
page_recommend_org=Recommended Organization
page_recommend_org_desc=These excellent organizations are using the OpenI AI Collaboration Platform for collaborative development of projects. To show your organization here,
page_recommend_org_commit=Click here to submit.
page_recommend_org_more=More Organizations
page_recommend_repo=Recommended Projects
page_recommend_repo_desc=Excellent AI projects recommendation. To show your project here,
page_recommend_repo_commit=Click here to submit.
page_recommend_repo_go=Click here to
page_recommend_repo_more=explore more projects.
page_dev_env=Collaborative Development Environment
page_dev_env_desc=Provide a collaborative development environment for AI development, which is the biggest highlight that distinguishes the OpenI AI Collaboration Platform from other traditional Git platforms.
page_dev_env_desc_title=Unified Management of Development Elements
page_dev_env_desc_desc=The platform provides four elements of AI development: unified management of model code, data set, model and execution environment.
page_dev_env_desc1_title=Data Collaboration and Sharing
page_dev_env_desc1_desc=By uploading data sets in the project, many project members cooperate to complete data preprocessing. You can also establish a better model with community developers by setting the data as a public dataset.
page_dev_env_desc2_title=Model Management and Sharing
page_dev_env_desc2_desc=Associate the model with the code version, you can adjust the model in different ways based on the historical version of the code and save the results. The trained model can be open and shared, so that more people can use the model to test and give feedback.
page_dev_env_desc3_title=Once Configuration, Multiple Reuse
page_dev_env_desc3_desc=Provide execution environment sharing, Once Configuration, Multiple Reuse. Lower the threshold of model development, and avoid spending repetitive time configuring complex environments.
page_dev_yunlao=PengCheng Cloudbrain Open Source Collaboration
page_dev_yunlao_desc1=The platform has been connected with Pengcheng Cloudbrain and can use the rich computing resources of Pengcheng Cloudbrain to complete AI development tasks.
page_dev_yunlao_desc2=Pengcheng Cloudbrain's existing AI computing power is 100p FLOPS@FP16 (billions of half precision floating-point calculations per second), the main hardware infrastructure is composed of GPU server equipped with NVIDIA Tesla V100 and Atlas 900 AI cluster equipped with Kunpeng and Ascend processors.
page_dev_yunlao_desc3=Developers can freely choose the corresponding computing resources according to their needs, and can test the adaptability, performance, stability of the model in different hardware environments.
page_dev_yunlao_desc4=If your model requires more computing resources, you can also apply for it separately.
page_dev_yunlao_apply=Apply Separately


[explore] [explore]
repos = Repositories repos = Repositories
select_repos = Select the project select_repos = Select the project
users = Users users = Users
organizations = Organizations organizations = Organizations
images = CloudImages
images = Cloudbrain Mirror
search = Search search = Search
code = Code code = Code
repo_no_results = No matching repositories found. repo_no_results = No matching repositories found.
@@ -630,7 +630,7 @@ oauth2_application_create_description = OAuth2 applications gives your third-par
oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue? oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue?


authorized_oauth2_applications = Authorized OAuth2 Applications authorized_oauth2_applications = Authorized OAuth2 Applications
authorized_oauth2_applications_description = You've granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed.
authorized_oauth2_applications_description = You have granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed.
revoke_key = Revoke revoke_key = Revoke
revoke_oauth2_grant = Revoke Access revoke_oauth2_grant = Revoke Access
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure? revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure?
@@ -869,6 +869,7 @@ modelarts.notebook=Debug Task
modelarts.train_job=Train Task modelarts.train_job=Train Task
modelarts.train_job.new_debug= New Debug Task modelarts.train_job.new_debug= New Debug Task
modelarts.train_job.new_train=New Train Task modelarts.train_job.new_train=New Train Task
modelarts.train_job.new_infer=New Inference Task
modelarts.train_job.config=Configuration information modelarts.train_job.config=Configuration information
modelarts.train_job.new=New train Task modelarts.train_job.new=New train Task
modelarts.train_job.new_place=The description should not exceed 256 characters modelarts.train_job.new_place=The description should not exceed 256 characters
@@ -882,6 +883,8 @@ modelarts.parent_version=Parent Version
modelarts.run_version=Run Version modelarts.run_version=Run Version
modelarts.train_job.compute_node=Compute Node modelarts.train_job.compute_node=Compute Node
modelarts.create_model = Create Model modelarts.create_model = Create Model
modelarts.model_label=Model Label
modelarts.infer_dataset = Inference Dataset




modelarts.train_job.basic_info=Basic Info modelarts.train_job.basic_info=Basic Info
@@ -928,6 +931,13 @@ modelarts.train_job_para_admin=train_job_para_admin
modelarts.train_job_para.edit=train_job_para.edit modelarts.train_job_para.edit=train_job_para.edit
modelarts.train_job_para.connfirm=train_job_para.connfirm modelarts.train_job_para.connfirm=train_job_para.connfirm


modelarts.infer_job_model = Model
modelarts.infer_job_model_file = Model File
modelarts.infer_job = Inference Job
modelarts.infer_job.model_version = Model/Version
modelarts.infer_job.select_model = Select Model
modelarts.infer_job.tooltip = The model has been deleted and cannot be viewed.

model.manage.import_new_model=Import New Model model.manage.import_new_model=Import New Model
model.manage.create_error=Equal Name and Version has existed. model.manage.create_error=Equal Name and Version has existed.
model.manage.model_name = Model Name model.manage.model_name = Model Name
@@ -2687,6 +2697,7 @@ error.unit_not_allowed = You are not allowed to access this repository section.
head.community = Community head.community = Community
head.project = Repositories head.project = Repositories
head.openi = OpenI head.openi = OpenI
head.openi.repo = OpenI Projects
head.dataset = Datasets head.dataset = Datasets
foot.council = Council foot.council = Council
foot.technical_committee = Technical Committee foot.technical_committee = Technical Committee


+ 14
- 3
options/locale/locale_zh-CN.ini View File

@@ -831,7 +831,7 @@ debug=调试
debug_again=再次调试 debug_again=再次调试
stop=停止 stop=停止
delete=删除 delete=删除
model_download=模型下载
model_download=结果下载
submit_image=提交镜像 submit_image=提交镜像
download=模型下载 download=模型下载


@@ -876,9 +876,10 @@ modelarts.notebook=调试任务
modelarts.train_job=训练任务 modelarts.train_job=训练任务
modelarts.train_job.new_debug=新建调试任务 modelarts.train_job.new_debug=新建调试任务
modelarts.train_job.new_train=新建训练任务 modelarts.train_job.new_train=新建训练任务
modelarts.train_job.new_infer=新建推理任务
modelarts.train_job.config=配置信息 modelarts.train_job.config=配置信息
modelarts.train_job.new=新建训练任务 modelarts.train_job.new=新建训练任务
modelarts.train_job.new_place=描述字数不超过256个字符
modelarts.train_job.new_place=描述字数不超过255个字符
modelarts.model_name=模型名称 modelarts.model_name=模型名称
modelarts.model_size=模型大小 modelarts.model_size=模型大小
modelarts.import_model=导入模型 modelarts.import_model=导入模型
@@ -888,6 +889,8 @@ modelarts.current_version=当前版本
modelarts.parent_version=父版本 modelarts.parent_version=父版本
modelarts.run_version=运行版本 modelarts.run_version=运行版本
modelarts.create_model=创建模型 modelarts.create_model=创建模型
modelarts.model_label=模型标签
modelarts.infer_dataset = 推理数据集






@@ -929,7 +932,7 @@ modelarts.train_job.NAS_mount_path=NAS挂载路径
modelarts.train_job.query_whether_save_parameter=保存作业参数 modelarts.train_job.query_whether_save_parameter=保存作业参数
modelarts.train_job.save_helper=保存当前作业的配置参数,后续您可以使用已保存的配置参数快速创建训练作业。 modelarts.train_job.save_helper=保存当前作业的配置参数,后续您可以使用已保存的配置参数快速创建训练作业。
modelarts.train_job.common_frame=常用框架 modelarts.train_job.common_frame=常用框架
modelarts.train_job.amount_of_compute_node=计算节点
modelarts.train_job.amount_of_compute_node=计算节点数
modelarts.train_job.job_parameter_name=任务参数名称 modelarts.train_job.job_parameter_name=任务参数名称
modelarts.train_job.parameter_description=任务参数描述 modelarts.train_job.parameter_description=任务参数描述
modelarts.log=日志 modelarts.log=日志
@@ -939,6 +942,14 @@ modelarts.train_job_para_admin=任务参数管理
modelarts.train_job_para.edit=编辑 modelarts.train_job_para.edit=编辑
modelarts.train_job_para.connfirm=确定 modelarts.train_job_para.connfirm=确定


modelarts.infer_job_model = 模型名称
modelarts.infer_job_model_file = 模型文件
modelarts.infer_job = 推理任务
modelarts.infer_job.model_version = 模型/版本
modelarts.infer_job.select_model = 选择模型
modelarts.infer_job.boot_file_helper=启动文件是您程序执行的入口文件,必须是以.py结尾的文件。比如inference.py、main.py、example/inference.py、case/main.py。
modelarts.infer_job.tooltip = 该模型已删除,无法查看。

model.manage.import_new_model=导入新模型 model.manage.import_new_model=导入新模型
model.manage.create_error=相同的名称和版本的模型已经存在。 model.manage.create_error=相同的名称和版本的模型已经存在。
model.manage.model_name = 模型名称 model.manage.model_name = 模型名称


+ 32
- 17
public/home/home.js View File

@@ -42,10 +42,10 @@ if(document.location.host == "git.openi.org.cn" || document.URL.startsWith("http
var socket = new WebSocket(url); var socket = new WebSocket(url);


socket.onopen = function () { socket.onopen = function () {
messageQueue = [];
console.log("message has connected."); console.log("message has connected.");
}; };


var messageQueue = [];
var maxSize = 20; var maxSize = 20;
var html =document.documentElement; var html =document.documentElement;
var lang = html.attributes["lang"] var lang = html.attributes["lang"]
@@ -165,12 +165,12 @@ function getTime(UpdatedUnix,currentTime){
var seconds= leave3; var seconds= leave3;


if(hours == 0 && minutes == 0){ if(hours == 0 && minutes == 0){
return seconds + getRepoOrOrg(6,isZh);
return seconds + getRepoOrOrg(6,isZh,seconds);
}else{ }else{
if(hours > 0){ if(hours > 0){
return hours + getRepoOrOrg(4,isZh);
return hours + getRepoOrOrg(4,isZh,hours);
}else{ }else{
return minutes + getRepoOrOrg(5,isZh);
return minutes + getRepoOrOrg(5,isZh,minutes);
} }
} }
} }
@@ -239,7 +239,7 @@ var actionNameZH={
"5":"推送了 {branch} 分支的代码到", "5":"推送了 {branch} 分支的代码到",
"6":"创建了任务", "6":"创建了任务",
"7":"创建了合并请求", "7":"创建了合并请求",
"9":"推送了 {branch} 分支的代码到",
"9":"推送了标签 {branch} 到",
"10":"评论了任务", "10":"评论了任务",
"11":"合并了合并请求", "11":"合并了合并请求",
"12":"关闭了任务", "12":"关闭了任务",
@@ -247,7 +247,7 @@ var actionNameZH={
"14":"关闭了合并请求", "14":"关闭了合并请求",
"15":"重新开启了合并请求", "15":"重新开启了合并请求",
"17":"从 {repoName} 删除分支 {deleteBranchName}", "17":"从 {repoName} 删除分支 {deleteBranchName}",
"22":"拒绝了合并请求",
"22":"建议变更",
"23":"评论了合并请求" "23":"评论了合并请求"
}; };


@@ -257,7 +257,7 @@ var actionNameEN={
"5":" pushed to {branch} at", "5":" pushed to {branch} at",
"6":" opened issue", "6":" opened issue",
"7":" created pull request", "7":" created pull request",
"9":" pushed to {branch} at",
"9":" pushed tag {branch} to ",
"10":" commented on issue", "10":" commented on issue",
"11":" merged pull request", "11":" merged pull request",
"12":" closed issue", "12":" closed issue",
@@ -265,7 +265,7 @@ var actionNameEN={
"14":" closed pull request", "14":" closed pull request",
"15":" reopened pull request", "15":" reopened pull request",
"17":" deleted branch {deleteBranchName} from {repoName}", "17":" deleted branch {deleteBranchName} from {repoName}",
"22":" rejected pull request",
"22":" proposed changes",
"23":" commented on pull request" "23":" commented on pull request"
}; };


@@ -273,18 +273,30 @@ var repoAndOrgZH={
"1":"项目", "1":"项目",
"2":"成员", "2":"成员",
"3":"团队", "3":"团队",
"11":"项目",
"21":"成员",
"31":"团队",
"4":"小时前", "4":"小时前",
"5":"分钟前", "5":"分钟前",
"6":"秒前"
"6":"秒前",
"41":"小时前",
"51":"分钟前",
"61":"秒前"
}; };


var repoAndOrgEN={ var repoAndOrgEN={
"1":"repository",
"2":"Members ",
"3":"Teams",
"4":" hours ago",
"5":" minutes ago",
"6":" seconds ago"
"1":"Repository",
"2":"Member ",
"3":"Team",
"11":"Repositorys",
"22":"Members ",
"31":"Teams",
"4":" hour ago",
"5":" minute ago",
"6":" second ago",
"41":" hours ago",
"51":" minutes ago",
"61":" seconds ago"
}; };




@@ -415,7 +427,10 @@ function displayRepo(json){
//var repoAndOrgEN = new Map([['1', "Repository"], ['2', "Members"], ['3', "Teams"]]); //var repoAndOrgEN = new Map([['1', "Repository"], ['2', "Members"], ['3', "Teams"]]);




function getRepoOrOrg(key,isZhLang){
function getRepoOrOrg(key,isZhLang,numbers=1){
if(numbers > 1){
key+="1";
}
if(isZhLang){ if(isZhLang){
return repoAndOrgZH[key]; return repoAndOrgZH[key];
}else{ }else{
@@ -436,7 +451,7 @@ function displayOrg(json){
html += " <img class=\"ui image\" src=\"" + record["Avatar"] + "\">"; html += " <img class=\"ui image\" src=\"" + record["Avatar"] + "\">";
html += " <div class=\"content nowrap\">"; html += " <div class=\"content nowrap\">";
html += " <span class=\"ui blue\">" + record["Name"] + "</span> " + record["FullName"]; html += " <span class=\"ui blue\">" + record["Name"] + "</span> " + record["FullName"];
html += " <div class=\"sub header\">" + record["NumRepos"] +" " + getRepoOrOrg(1,isZh) + " ・ " + record["NumMembers"] +" " + getRepoOrOrg(2,isZh) + " ・ " + record["NumTeams"] + " " + getRepoOrOrg(3,isZh) + "</div>";
html += " <div class=\"sub header\">" + record["NumRepos"] +" " + getRepoOrOrg(1,isZh,record["NumRepos"]) + " ・ " + record["NumMembers"] +" " + getRepoOrOrg(2,isZh,record["NumMembers"]) + " ・ " + record["NumTeams"] + " " + getRepoOrOrg(3,isZh,record["NumTeams"]) + "</div>";
html += " </div>"; html += " </div>";
html += " </div>"; html += " </div>";
html += " </div>"; html += " </div>";


+ 9
- 0
routers/api/v1/api.go View File

@@ -892,6 +892,15 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/model_list", repo.ModelList) m.Get("/model_list", repo.ModelList)
}) })
}) })
m.Group("/inference-job", func() {
m.Group("/:jobid", func() {
m.Get("", repo.GetModelArtsInferenceJob)
m.Get("/log", repo.TrainJobGetLog)
m.Post("/del_version", repo.DelTrainJobVersion)
m.Post("/stop_version", repo.StopTrainJobVersion)
m.Get("/result_list", repo.ResultList)
})
})
}, reqRepoReader(models.UnitTypeCloudBrain)) }, reqRepoReader(models.UnitTypeCloudBrain))
}, repoAssignment()) }, repoAssignment())
}) })


+ 80
- 6
routers/api/v1/repo/modelarts.go View File

@@ -133,7 +133,6 @@ func TrainJobGetLog(ctx *context.APIContext) {


var jobID = ctx.Params(":jobid") var jobID = ctx.Params(":jobid")
var versionName = ctx.Query("version_name") var versionName = ctx.Query("version_name")
// var logFileName = ctx.Query("file_name")
var baseLine = ctx.Query("base_line") var baseLine = ctx.Query("base_line")
var order = ctx.Query("order") var order = ctx.Query("order")
var lines = ctx.Query("lines") var lines = ctx.Query("lines")
@@ -222,12 +221,14 @@ func DelTrainJobVersion(ctx *context.APIContext) {
} }


//获取删除后的版本数量 //获取删除后的版本数量
var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeTrain))
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeTrain),
JobID: jobID,
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobTypes: jobTypes,
JobID: jobID,
}) })
if err != nil { if err != nil {
ctx.ServerError("get VersionListCount failed", err) ctx.ServerError("get VersionListCount failed", err)
@@ -299,7 +300,80 @@ func ModelList(ctx *context.APIContext) {
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error())
return return
} }
models, err := storage.GetObsListObject(task.JobName, parentDir, versionName)
models, err := storage.GetObsListObject(task.JobName, "output/", parentDir, versionName)
if err != nil {
log.Info("get TrainJobListModel failed:", err)
ctx.ServerError("GetObsListObject:", err)
return
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"JobID": jobID,
"VersionName": versionName,
"StatusOK": 0,
"Path": dirArray,
"Dirs": models,
"task": task,
"PageIsCloudBrain": true,
})
}

func GetModelArtsInferenceJob(ctx *context.APIContext) {
var (
err error
)

jobID := ctx.Params(":jobid")
job, err := models.GetCloudbrainByJobID(jobID)
if err != nil {
ctx.NotFound(err)
return
}
result, err := modelarts.GetTrainJob(jobID, strconv.FormatInt(job.VersionID, 10))
if err != nil {
ctx.NotFound(err)
return
}

job.Status = modelarts.TransTrainJobStatus(result.IntStatus)
job.Duration = result.Duration
job.TrainJobDuration = result.TrainJobDuration

if result.Duration != 0 {
job.TrainJobDuration = util.AddZero(result.Duration/3600000) + ":" + util.AddZero(result.Duration%3600000/60000) + ":" + util.AddZero(result.Duration%60000/1000)

} else {
job.TrainJobDuration = "00:00:00"
}

err = models.UpdateInferenceJob(job)
if err != nil {
log.Error("UpdateJob failed:", err)
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"JobID": jobID,
"JobStatus": job.Status,
"JobDuration": job.TrainJobDuration,
})

}

func ResultList(ctx *context.APIContext) {
var (
err error
)

var jobID = ctx.Params(":jobid")
var versionName = ctx.Query("version_name")
parentDir := ctx.Query("parentDir")
dirArray := strings.Split(parentDir, "/")
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName)
if err != nil {
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error())
return
}
models, err := storage.GetObsListObject(task.JobName, "result/", parentDir, versionName)
if err != nil { if err != nil {
log.Info("get TrainJobListModel failed:", err) log.Info("get TrainJobListModel failed:", err)
ctx.ServerError("GetObsListObject:", err) ctx.ServerError("GetObsListObject:", err)


+ 45
- 21
routers/repo/ai_model_manage.go View File

@@ -146,7 +146,8 @@ func SaveModel(ctx *context.Context) {


if !trainTaskCreate { if !trainTaskCreate {
if !ctx.Repo.CanWrite(models.UnitTypeModelManage) { if !ctx.Repo.CanWrite(models.UnitTypeModelManage) {
ctx.ServerError("No right.", errors.New(ctx.Tr("repo.model_noright")))
//ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
ctx.JSON(403, ctx.Tr("repo.model_noright"))
return return
} }
} }
@@ -209,20 +210,11 @@ func DeleteModel(ctx *context.Context) {
}) })
} }
} }
func isCanDeleteOrDownload(ctx *context.Context, model *models.AiModelManage) bool {
if ctx.User.IsAdmin || ctx.User.ID == model.UserId {
return true
}
if ctx.Repo.IsOwner() {
return true
}
return false
}


func deleteModelByID(ctx *context.Context, id string) error { func deleteModelByID(ctx *context.Context, id string) error {
log.Info("delete model start. id=" + id) log.Info("delete model start. id=" + id)
model, err := models.QueryModelById(id) model, err := models.QueryModelById(id)
if !isCanDeleteOrDownload(ctx, model) {
if !isCanDelete(ctx, model.UserId) {
return errors.New(ctx.Tr("repo.model_noright")) return errors.New(ctx.Tr("repo.model_noright"))
} }
if err == nil { if err == nil {
@@ -278,8 +270,8 @@ func DownloadMultiModelFile(ctx *context.Context) {
ctx.ServerError("no such model:", err) ctx.ServerError("no such model:", err)
return return
} }
if !isCanDeleteOrDownload(ctx, task) {
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
if !isOper(ctx, task.UserId) {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
return return
} }


@@ -371,7 +363,16 @@ func DownloadSingleModelFile(ctx *context.Context) {
parentDir := ctx.Query("parentDir") parentDir := ctx.Query("parentDir")
fileName := ctx.Query("fileName") fileName := ctx.Query("fileName")
path := Model_prefix + models.AttachmentRelativePath(id) + "/" + parentDir + fileName path := Model_prefix + models.AttachmentRelativePath(id) + "/" + parentDir + fileName

task, err := models.QueryModelById(id)
if err != nil {
log.Error("no such model!", err.Error())
ctx.ServerError("no such model:", err)
return
}
if !isOper(ctx, task.UserId) {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
return
}
if setting.PROXYURL != "" { if setting.PROXYURL != "" {
body, err := storage.ObsDownloadAFile(setting.Bucket, path) body, err := storage.ObsDownloadAFile(setting.Bucket, path)
if err != nil { if err != nil {
@@ -414,6 +415,8 @@ func ShowModelInfo(ctx *context.Context) {
ctx.Data["ID"] = ctx.Query("ID") ctx.Data["ID"] = ctx.Query("ID")
ctx.Data["name"] = ctx.Query("name") ctx.Data["name"] = ctx.Query("name")
ctx.Data["isModelManage"] = true ctx.Data["isModelManage"] = true
ctx.Data["ModelManageAccess"] = ctx.Repo.CanWrite(models.UnitTypeModelManage)

ctx.HTML(200, tplModelInfo) ctx.HTML(200, tplModelInfo)
} }


@@ -426,6 +429,7 @@ func ShowSingleModel(ctx *context.Context) {
userIds := make([]int64, len(models)) userIds := make([]int64, len(models))
for i, model := range models { for i, model := range models {
model.IsCanOper = isOper(ctx, model.UserId) model.IsCanOper = isOper(ctx, model.UserId)
model.IsCanDelete = isCanDelete(ctx, model.UserId)
userIds[i] = model.UserId userIds[i] = model.UserId
} }
userNameMap := queryUserName(userIds) userNameMap := queryUserName(userIds)
@@ -468,6 +472,7 @@ func ShowOneVersionOtherModel(ctx *context.Context) {
userIds := make([]int64, len(aimodels)) userIds := make([]int64, len(aimodels))
for i, model := range aimodels { for i, model := range aimodels {
model.IsCanOper = isOper(ctx, model.UserId) model.IsCanOper = isOper(ctx, model.UserId)
model.IsCanDelete = isCanDelete(ctx, model.UserId)
userIds[i] = model.UserId userIds[i] = model.UserId
} }
userNameMap := queryUserName(userIds) userNameMap := queryUserName(userIds)
@@ -487,8 +492,7 @@ func ShowOneVersionOtherModel(ctx *context.Context) {
} }
} }


func ShowModelTemplate(ctx *context.Context) {
ctx.Data["isModelManage"] = true
func SetModelCount(ctx *context.Context) {
repoId := ctx.Repo.Repository.ID repoId := ctx.Repo.Repository.ID
Type := -1 Type := -1
_, count, _ := models.QueryModel(&models.AiModelQueryOptions{ _, count, _ := models.QueryModel(&models.AiModelQueryOptions{
@@ -501,10 +505,15 @@ func ShowModelTemplate(ctx *context.Context) {
New: MODEL_LATEST, New: MODEL_LATEST,
}) })
ctx.Data["MODEL_COUNT"] = count ctx.Data["MODEL_COUNT"] = count
}


func ShowModelTemplate(ctx *context.Context) {
ctx.Data["isModelManage"] = true
repoId := ctx.Repo.Repository.ID
SetModelCount(ctx)
ctx.Data["ModelManageAccess"] = ctx.Repo.CanWrite(models.UnitTypeModelManage)
_, trainCount, _ := models.QueryModelTrainJobList(repoId) _, trainCount, _ := models.QueryModelTrainJobList(repoId)
log.Info("query train count=" + fmt.Sprint(trainCount)) log.Info("query train count=" + fmt.Sprint(trainCount))

ctx.Data["TRAIN_COUNT"] = trainCount ctx.Data["TRAIN_COUNT"] = trainCount
ctx.HTML(200, tplModelManageIndex) ctx.HTML(200, tplModelManageIndex)
} }
@@ -520,11 +529,24 @@ func isQueryRight(ctx *context.Context) bool {
} }
} }


func isCanDelete(ctx *context.Context, modelUserId int64) bool {
if ctx.User == nil {
return false
}
if ctx.User.IsAdmin || ctx.User.ID == modelUserId {
return true
}
if ctx.Repo.IsOwner() {
return true
}
return false
}

func isOper(ctx *context.Context, modelUserId int64) bool { func isOper(ctx *context.Context, modelUserId int64) bool {
if ctx.User == nil { if ctx.User == nil {
return false return false
} }
if ctx.User.IsAdmin || ctx.Repo.IsOwner() || ctx.User.ID == modelUserId {
if ctx.User.IsAdmin || ctx.User.ID == modelUserId {
return true return true
} }
return false return false
@@ -533,7 +555,7 @@ func isOper(ctx *context.Context, modelUserId int64) bool {
func ShowModelPageInfo(ctx *context.Context) { func ShowModelPageInfo(ctx *context.Context) {
log.Info("ShowModelInfo start.") log.Info("ShowModelInfo start.")
if !isQueryRight(ctx) { if !isQueryRight(ctx) {
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
return return
} }
page := ctx.QueryInt("page") page := ctx.QueryInt("page")
@@ -563,6 +585,7 @@ func ShowModelPageInfo(ctx *context.Context) {
userIds := make([]int64, len(modelResult)) userIds := make([]int64, len(modelResult))
for i, model := range modelResult { for i, model := range modelResult {
model.IsCanOper = isOper(ctx, model.UserId) model.IsCanOper = isOper(ctx, model.UserId)
model.IsCanDelete = isCanDelete(ctx, model.UserId)
userIds[i] = model.UserId userIds[i] = model.UserId
} }


@@ -603,8 +626,9 @@ func ModifyModelInfo(ctx *context.Context) {
ctx.ServerError("no such model:", err) ctx.ServerError("no such model:", err)
return return
} }
if !isCanDeleteOrDownload(ctx, task) {
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
if !isOper(ctx, task.UserId) {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
//ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
return return
} }




+ 599
- 29
routers/repo/modelarts.go View File

@@ -1,15 +1,18 @@
package repo package repo


import ( import (
"archive/zip"
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path" "path"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
@@ -37,6 +40,10 @@ const (
tplModelArtsTrainJobNew base.TplName = "repo/modelarts/trainjob/new" tplModelArtsTrainJobNew base.TplName = "repo/modelarts/trainjob/new"
tplModelArtsTrainJobShow base.TplName = "repo/modelarts/trainjob/show" tplModelArtsTrainJobShow base.TplName = "repo/modelarts/trainjob/show"
tplModelArtsTrainJobVersionNew base.TplName = "repo/modelarts/trainjob/version_new" tplModelArtsTrainJobVersionNew base.TplName = "repo/modelarts/trainjob/version_new"

tplModelArtsInferenceJobIndex base.TplName = "repo/modelarts/inferencejob/index"
tplModelArtsInferenceJobNew base.TplName = "repo/modelarts/inferencejob/new"
tplModelArtsInferenceJobShow base.TplName = "repo/modelarts/inferencejob/show"
) )


func DebugJobIndex(ctx *context.Context) { func DebugJobIndex(ctx *context.Context) {
@@ -49,12 +56,15 @@ func DebugJobIndex(ctx *context.Context) {
page = 1 page = 1
} }
debugType := modelarts.DebugType debugType := modelarts.DebugType
jobTypeNot := false
if debugListType == models.GPUResource { if debugListType == models.GPUResource {
debugType = models.TypeCloudBrainOne debugType = models.TypeCloudBrainOne
} else if debugListType == models.NPUResource { } else if debugListType == models.NPUResource {
debugType = models.TypeCloudBrainTwo debugType = models.TypeCloudBrainTwo
} }


var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeBenchmark), string(models.JobTypeSnn4imagenet), string(models.JobTypeBrainScore), string(models.JobTypeDebug))
ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{ ListOptions: models.ListOptions{
Page: page, Page: page,
@@ -62,8 +72,8 @@ func DebugJobIndex(ctx *context.Context) {
}, },
RepoID: repo.ID, RepoID: repo.ID,
Type: debugType, Type: debugType,
JobTypeNot: true,
JobType: string(models.JobTypeTrain),
JobTypeNot: jobTypeNot,
JobTypes: jobTypes,
}) })
if err != nil { if err != nil {
ctx.ServerError("Get debugjob faild:", err) ctx.ServerError("Get debugjob faild:", err)
@@ -367,6 +377,8 @@ func TrainJobIndex(ctx *context.Context) {
page = 1 page = 1
} }


var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeTrain))
tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{ ListOptions: models.ListOptions{
Page: page, Page: page,
@@ -375,7 +387,7 @@ func TrainJobIndex(ctx *context.Context) {
RepoID: repo.ID, RepoID: repo.ID,
Type: models.TypeCloudBrainTwo, Type: models.TypeCloudBrainTwo,
JobTypeNot: false, JobTypeNot: false,
JobType: string(models.JobTypeTrain),
JobTypes: jobTypes,
IsLatestVersion: modelarts.IsLatestVersion, IsLatestVersion: modelarts.IsLatestVersion,
}) })
if err != nil { if err != nil {
@@ -749,7 +761,7 @@ func versionErrorDataPrepare(ctx *context.Context, form auth.CreateModelArtsTrai


func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) { func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) {
ctx.Data["PageIsTrainJob"] = true ctx.Data["PageIsTrainJob"] = true
VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(modelarts.TotalVersionCount)
VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount)
jobName := form.JobName jobName := form.JobName
uuid := form.Attachment uuid := form.Attachment
description := form.Description description := form.Description
@@ -794,18 +806,11 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
return return
} }


// attach, err := models.GetAttachmentByUUID(uuid)
// if err != nil {
// log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error())
// return
// }

//todo: del the codeLocalPath //todo: del the codeLocalPath
// _, err := ioutil.ReadDir(codeLocalPath)
// if err == nil {
// os.RemoveAll(codeLocalPath)
// }
os.RemoveAll(codeLocalPath)
_, err = ioutil.ReadDir(codeLocalPath)
if err == nil {
os.RemoveAll(codeLocalPath)
}


gitRepo, _ := git.OpenRepository(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath())
commitID, _ := gitRepo.GetBranchCommitID(branch_name) commitID, _ := gitRepo.GetBranchCommitID(branch_name)
@@ -973,7 +978,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
ctx.ServerError("GetCloudbrainByJobIDAndIsLatestVersion faild:", err) ctx.ServerError("GetCloudbrainByJobIDAndIsLatestVersion faild:", err)
return return
} }
VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(latestTask.TotalVersionCount + 1)
VersionOutputPath := modelarts.GetOutputPathByCount(latestTask.TotalVersionCount + 1)


jobName := form.JobName jobName := form.JobName
uuid := form.Attachment uuid := form.Attachment
@@ -1011,18 +1016,17 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
return return
} }


// attach, err := models.GetAttachmentByUUID(uuid)
// if err != nil {
// log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error())
// return
// }

//todo: del the codeLocalPath //todo: del the codeLocalPath
// _, err = ioutil.ReadDir(codeLocalPath)
// if err == nil {
// os.RemoveAll(codeLocalPath)
// }
os.RemoveAll(codeLocalPath)
_, err = ioutil.ReadDir(codeLocalPath)
if err == nil {
os.RemoveAll(codeLocalPath)
} else {
log.Error("创建任务失败,原代码还未删除,请重试!: %s (%v)", repo.FullName(), err)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("创建任务失败,原代码还未删除,请重试!", tplModelArtsTrainJobVersionNew, &form)
return
}
// os.RemoveAll(codeLocalPath)


gitRepo, _ := git.OpenRepository(repo.RepoPath()) gitRepo, _ := git.OpenRepository(repo.RepoPath())
commitID, _ := gitRepo.GetBranchCommitID(branch_name) commitID, _ := gitRepo.GetBranchCommitID(branch_name)
@@ -1264,6 +1268,42 @@ func paramCheckCreateTrainJob(form auth.CreateModelArtsTrainJobForm) error {
return nil return nil
} }


func paramCheckCreateInferenceJob(form auth.CreateModelArtsInferenceJobForm) error {
if !strings.HasSuffix(form.BootFile, ".py") {
log.Error("the boot file(%s) must be a python file", form.BootFile)
return errors.New("启动文件必须是python文件")
}

if form.WorkServerNumber > 25 || form.WorkServerNumber < 1 {
log.Error("the WorkServerNumber(%d) must be in (1,25)", form.WorkServerNumber)
return errors.New("计算节点数必须在1-25之间")
}

if form.ModelName == "" {
log.Error("the ModelName(%d) must not be nil", form.ModelName)
return errors.New("模型名称不能为空")
}
if form.ModelVersion == "" {
log.Error("the ModelVersion(%d) must not be nil", form.ModelVersion)
return errors.New("模型版本不能为空")
}
if form.CkptName == "" {
log.Error("the CkptName(%d) must not be nil", form.CkptName)
return errors.New("权重文件不能为空")
}
if form.BranchName == "" {
log.Error("the Branch(%d) must not be nil", form.BranchName)
return errors.New("分支名不能为空")
}

if utf8.RuneCountInString(form.Description) > 255 {
log.Error("the Description length(%d) must not more than 255", form.Description)
return errors.New("描述字符不能超过255个字符")
}

return nil
}

func TrainJobShow(ctx *context.Context) { func TrainJobShow(ctx *context.Context) {
ctx.Data["PageIsCloudBrain"] = true ctx.Data["PageIsCloudBrain"] = true
var jobID = ctx.Params(":jobid") var jobID = ctx.Params(":jobid")
@@ -1273,6 +1313,9 @@ func TrainJobShow(ctx *context.Context) {
if page <= 0 { if page <= 0 {
page = 1 page = 1
} }

var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeTrain))
VersionListTasks, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ VersionListTasks, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{ ListOptions: models.ListOptions{
Page: page, Page: page,
@@ -1280,7 +1323,7 @@ func TrainJobShow(ctx *context.Context) {
}, },
RepoID: repo.ID, RepoID: repo.ID,
Type: models.TypeCloudBrainTwo, Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeTrain),
JobTypes: jobTypes,
JobID: jobID, JobID: jobID,
}) })


@@ -1392,10 +1435,12 @@ func TrainJobDel(ctx *context.Context) {
var jobID = ctx.Params(":jobid") var jobID = ctx.Params(":jobid")
repo := ctx.Repo.Repository repo := ctx.Repo.Repository


var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeTrain))
VersionListTasks, _, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ VersionListTasks, _, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{
RepoID: repo.ID, RepoID: repo.ID,
Type: models.TypeCloudBrainTwo, Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeTrain),
JobTypes: jobTypes,
JobID: jobID, JobID: jobID,
}) })
if err != nil { if err != nil {
@@ -1518,6 +1563,427 @@ func getConfigList(perPage, page int, sortBy, order, searchContent, configType s
return list, nil return list, nil
} }


func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) {
ctx.Data["PageIsTrainJob"] = true
VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount)
jobName := form.JobName
uuid := form.Attachment
description := form.Description
workServerNumber := form.WorkServerNumber
engineID := form.EngineID
bootFile := form.BootFile
flavorCode := form.Flavor
params := form.Params
poolID := form.PoolID
repo := ctx.Repo.Repository
codeLocalPath := setting.JobPath + jobName + modelarts.CodePath
codeObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.CodePath
resultObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.ResultPath + VersionOutputPath + "/"
logObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.LogPath + VersionOutputPath + "/"
dataPath := "/" + setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" + uuid + uuid + "/"
branch_name := form.BranchName
FlavorName := form.FlavorName
EngineName := form.EngineName
LabelName := form.LabelName
isLatestVersion := modelarts.IsLatestVersion
VersionCount := modelarts.VersionCount
trainUrl := form.TrainUrl
modelName := form.ModelName
modelVersion := form.ModelVersion
ckptName := form.CkptName

ckptUrl := form.TrainUrl + form.CkptName

if err := paramCheckCreateInferenceJob(form); err != nil {
log.Error("paramCheckCreateInferenceJob failed:(%v)", err)
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form)
return
}

count, err := models.GetCloudbrainInferenceJobCountByUserID(ctx.User.ID)
if err != nil {
log.Error("GetCloudbrainInferenceJobCountByUserID failed:%v", err, ctx.Data["MsgID"])
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("system error", tplModelArtsInferenceJobNew, &form)
return
} else {
if count >= 1 {
log.Error("the user already has running or waiting inference task", ctx.Data["MsgID"])
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("you have already a running or waiting inference task, can not create more", tplModelArtsInferenceJobNew, &form)
return
}
}

//todo: del the codeLocalPath
_, err = ioutil.ReadDir(codeLocalPath)
if err == nil {
os.RemoveAll(codeLocalPath)
}

gitRepo, _ := git.OpenRepository(repo.RepoPath())
commitID, _ := gitRepo.GetBranchCommitID(branch_name)

if err := git.Clone(repo.RepoPath(), codeLocalPath, git.CloneRepoOptions{
Branch: branch_name,
}); err != nil {
log.Error("创建任务失败,服务器超时!: %s (%v)", repo.FullName(), err)
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("创建任务失败,服务器超时!", tplModelArtsInferenceJobNew, &form)
return
}

//todo: upload code (send to file_server todo this work?)
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.ResultPath + VersionOutputPath + "/"); err != nil {
log.Error("Failed to obsMkdir_result: %s (%v)", repo.FullName(), err)
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to obsMkdir_result", tplModelArtsInferenceJobNew, &form)
return
}

if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.LogPath + VersionOutputPath + "/"); err != nil {
log.Error("Failed to obsMkdir_log: %s (%v)", repo.FullName(), err)
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to obsMkdir_log", tplModelArtsInferenceJobNew, &form)
return
}

if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil {
log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err)
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to uploadCodeToObs", tplModelArtsInferenceJobNew, &form)
return
}

//todo: del local code?
var parameters models.Parameters
param := make([]models.Parameter, 0)
param = append(param, models.Parameter{
Label: modelarts.ResultUrl,
Value: "s3:/" + resultObsPath,
}, models.Parameter{
Label: modelarts.CkptUrl,
Value: "s3:/" + ckptUrl,
})
if len(params) != 0 {
err := json.Unmarshal([]byte(params), &parameters)
if err != nil {
log.Error("Failed to Unmarshal params: %s (%v)", params, err)
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("运行参数错误", tplModelArtsInferenceJobNew, &form)
return
}

for _, parameter := range parameters.Parameter {
if parameter.Label != modelarts.TrainUrl && parameter.Label != modelarts.DataUrl {
param = append(param, models.Parameter{
Label: parameter.Label,
Value: parameter.Value,
})
}
}
}

req := &modelarts.GenerateInferenceJobReq{
JobName: jobName,
DataUrl: dataPath,
Description: description,
CodeObsPath: codeObsPath,
BootFileUrl: codeObsPath + bootFile,
BootFile: bootFile,
TrainUrl: trainUrl,
FlavorCode: flavorCode,
WorkServerNumber: workServerNumber,
EngineID: int64(engineID),
LogUrl: logObsPath,
PoolID: poolID,
Uuid: uuid,
Parameters: param, //modelarts训练时用到
CommitID: commitID,
BranchName: branch_name,
Params: form.Params,
FlavorName: FlavorName,
EngineName: EngineName,
LabelName: LabelName,
IsLatestVersion: isLatestVersion,
VersionCount: VersionCount,
TotalVersionCount: modelarts.TotalVersionCount,
ModelName: modelName,
ModelVersion: modelVersion,
CkptName: ckptName,
ResultUrl: resultObsPath,
}

//将params转换Parameters.Parameter,出错时返回给前端
// var Parameters modelarts.Parameters
// if err := json.Unmarshal([]byte(params), &Parameters); err != nil {
// ctx.ServerError("json.Unmarshal failed:", err)
// return
// }

err = modelarts.GenerateInferenceJob(ctx, req)
if err != nil {
log.Error("GenerateTrainJob failed:%v", err.Error())
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form)
return
}
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/inference-job")
}
func InferenceJobIndex(ctx *context.Context) {
MustEnableModelArts(ctx)

repo := ctx.Repo.Repository
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}

var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeInference))
tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobTypes: jobTypes,
})
if err != nil {
ctx.ServerError("Cloudbrain", err)
return
}

for i, task := range tasks {
tasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain)
tasks[i].CanModify = cloudbrain.CanModifyJob(ctx, &task.Cloudbrain)
tasks[i].ComputeResource = models.NPUResource
}

repoId := ctx.Repo.Repository.ID
Type := -1
_, model_count, _ := models.QueryModel(&models.AiModelQueryOptions{
ListOptions: models.ListOptions{
Page: 1,
PageSize: 2,
},
RepoID: repoId,
Type: Type,
New: MODEL_LATEST,
})
ctx.Data["MODEL_COUNT"] = model_count

pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5)
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager

ctx.Data["PageIsCloudBrain"] = true
ctx.Data["Tasks"] = tasks
ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx)
ctx.Data["RepoIsEmpty"] = repo.IsEmpty
ctx.HTML(200, tplModelArtsInferenceJobIndex)
}
func InferenceJobNew(ctx *context.Context) {
err := inferenceJobNewDataPrepare(ctx)
if err != nil {
ctx.ServerError("get new inference-job info failed", err)
return
}
ctx.HTML(200, tplModelArtsInferenceJobNew)
}
func inferenceJobNewDataPrepare(ctx *context.Context) error {
ctx.Data["PageIsCloudBrain"] = true

t := time.Now()
var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:]
ctx.Data["job_name"] = jobName

attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID)
if err != nil {
ctx.ServerError("GetAllUserAttachments failed:", err)
return err
}
ctx.Data["attachments"] = attachs

var resourcePools modelarts.ResourcePool
if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["resource_pools"] = resourcePools.Info

var engines modelarts.Engine
if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["engines"] = engines.Info

var versionInfos modelarts.VersionInfo
if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["engine_versions"] = versionInfos.Version

var flavorInfos modelarts.Flavor
if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}

ctx.Data["flavor_infos"] = flavorInfos.Info
ctx.Data["params"] = ""
ctx.Data["branchName"] = ctx.Repo.BranchName

configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom)
if err != nil {
ctx.ServerError("getConfigList failed:", err)
return err
}
ctx.Data["config_list"] = configList.ParaConfigs

repoId := ctx.Repo.Repository.ID
Type := -1
_, model_count, _ := models.QueryModel(&models.AiModelQueryOptions{
ListOptions: models.ListOptions{
Page: 1,
PageSize: 2,
},
RepoID: repoId,
Type: Type,
New: MODEL_LATEST,
})
ctx.Data["MODEL_COUNT"] = model_count

return nil
}

func inferenceJobErrorNewDataPrepare(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) error {
ctx.Data["PageIsCloudBrain"] = true

t := time.Now()
var jobName = "inference" + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:]
ctx.Data["job_name"] = jobName

attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID)
if err != nil {
ctx.ServerError("GetAllUserAttachments failed:", err)
return err
}
ctx.Data["attachments"] = attachs

var resourcePools modelarts.ResourcePool
if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["resource_pools"] = resourcePools.Info

var engines modelarts.Engine
if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["engines"] = engines.Info

var versionInfos modelarts.VersionInfo
if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["engine_versions"] = versionInfos.Version

var flavorInfos modelarts.Flavor
if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["flavor_infos"] = flavorInfos.Info

configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom)
if err != nil {
ctx.ServerError("getConfigList failed:", err)
return err
}
var Parameters modelarts.Parameters
if err = json.Unmarshal([]byte(form.Params), &Parameters); err != nil {
ctx.ServerError("json.Unmarshal failed:", err)
return err
}
ctx.Data["params"] = Parameters.Parameter
ctx.Data["config_list"] = configList.ParaConfigs
ctx.Data["bootFile"] = form.BootFile
ctx.Data["uuid"] = form.Attachment
ctx.Data["branch_name"] = form.BranchName
ctx.Data["model_name"] = form.ModelName
ctx.Data["model_version"] = form.ModelVersion
ctx.Data["ckpt_name"] = form.CkptName
ctx.Data["train_url"] = form.TrainUrl

return nil
}
func InferenceJobShow(ctx *context.Context) {
ctx.Data["PageIsCloudBrain"] = true
var jobID = ctx.Params(":jobid")

page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
task, err := models.GetCloudbrainByJobID(jobID)

if err != nil {
log.Error("GetInferenceTask(%s) failed:%v", jobID, err.Error())
ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobShow, nil)
return
}
//设置权限
canNewJob, err := canUserCreateTrainJobVersion(ctx, task.UserID)
if err != nil {
ctx.ServerError("canNewJob failed", err)
return
}
ctx.Data["canNewJob"] = canNewJob

//将运行参数转化为epoch_size = 3, device_target = Ascend的格式
var parameters models.Parameters
err = json.Unmarshal([]byte(task.Parameters), &parameters)
if err != nil {
log.Error("Failed to Unmarshal Parameters: %s (%v)", task.Parameters, err)
trainJobNewDataPrepare(ctx)
return
}

if len(parameters.Parameter) > 0 {
paramTemp := ""
for _, Parameter := range parameters.Parameter {
param := Parameter.Label + " = " + Parameter.Value + "; "
paramTemp = paramTemp + param
}
task.Parameters = paramTemp[:len(paramTemp)-2]
} else {
task.Parameters = ""
}

LabelName := strings.Fields(task.LabelName)
ctx.Data["labelName"] = LabelName
ctx.Data["jobID"] = jobID
ctx.Data["jobName"] = task.JobName
ctx.Data["task"] = task

tempUids := []int64{}
tempUids = append(tempUids, task.UserID)
JobCreater, err := models.GetUserNamesByIDs(tempUids)
if err != nil {
log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err)
}
ctx.Data["userName"] = JobCreater[0]
ctx.HTML(http.StatusOK, tplModelArtsInferenceJobShow)
}

func ModelDownload(ctx *context.Context) { func ModelDownload(ctx *context.Context) {
var ( var (
err error err error
@@ -1546,6 +2012,31 @@ func ModelDownload(ctx *context.Context) {
http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently) http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently)
} }


func ResultDownload(ctx *context.Context) {
var (
err error
)

var jobID = ctx.Params(":jobid")
versionName := ctx.Query("version_name")
parentDir := ctx.Query("parent_dir")
fileName := ctx.Query("file_name")
log.Info("DownloadResult start.")
task, err := models.GetCloudbrainByJobID(jobID)
if err != nil {
ctx.Data["error"] = err.Error()
}
path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName, parentDir, fileName), "/")
log.Info("Download path is:%s", path)

url, err := storage.GetObsCreateSignedUrlByBucketAndKey(setting.Bucket, path)
if err != nil {
log.Error("GetObsCreateSignedUrl failed: %v", err.Error(), ctx.Data["msgID"])
ctx.ServerError("GetObsCreateSignedUrl", err)
return
}
http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently)
}
func DeleteJobStorage(jobName string) error { func DeleteJobStorage(jobName string) error {
//delete local //delete local
localJobPath := setting.JobPath + jobName localJobPath := setting.JobPath + jobName
@@ -1563,3 +2054,82 @@ func DeleteJobStorage(jobName string) error {


return nil return nil
} }

func DownloadMultiResultFile(ctx *context.Context) {
var jobID = ctx.Params(":jobid")
var versionName = ctx.Query("version_name")
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName)
if err != nil {
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error())
return
}
// if !isCanDeleteOrDownload(ctx, task) {
// ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
// return
// }

// path := Model_prefix + models.AttachmentRelativePath(id) + "/"
path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName), "/") + "/"

allFile, err := storage.GetAllObjectByBucketAndPrefix(setting.Bucket, path)
if err == nil {
//count++
// models.ModifyModelDownloadCount(id)

returnFileName := task.JobName + ".zip"
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+returnFileName)
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
w := zip.NewWriter(ctx.Resp)
defer w.Close()
for _, oneFile := range allFile {
if oneFile.IsDir {
log.Info("zip dir name:" + oneFile.FileName)
} else {
log.Info("zip file name:" + oneFile.FileName)
fDest, err := w.Create(oneFile.FileName)
if err != nil {
log.Info("create zip entry error, download file failed: %s\n", err.Error())
ctx.ServerError("download file failed:", err)
return
}
body, err := storage.ObsDownloadAFile(setting.Bucket, path+oneFile.FileName)
if err != nil {
log.Info("download file failed: %s\n", err.Error())
ctx.ServerError("download file failed:", err)
return
} else {
defer body.Close()
p := make([]byte, 1024)
var readErr error
var readCount int
// 读取对象内容
for {
readCount, readErr = body.Read(p)
if readCount > 0 {
fDest.Write(p[:readCount])
}
if readErr != nil {
break
}
}
}
}
}
} else {
log.Info("error,msg=" + err.Error())
ctx.ServerError("no file to download.", err)
}
}

func SetJobCount(ctx *context.Context) {
repoId := ctx.Repo.Repository.ID
_, jobCount, err := models.Cloudbrains(&models.CloudbrainsOptions{
RepoID: repoId,
Type: modelarts.DebugType,
})
if err != nil {
ctx.ServerError("Get job faild:", err)
return
}
ctx.Data["jobCount"] = jobCount
}

+ 16
- 9
routers/repo/repo_statistic.go View File

@@ -51,12 +51,14 @@ func RepoStatisticDaily(date string) {


isInitMinMaxRadar := false isInitMinMaxRadar := false


var error_projects = make([]string, 0)
for _, repo := range repos { for _, repo := range repos {
log.Info("start statistic: %s", getDistinctProjectName(repo))
projectName := getDistinctProjectName(repo)
log.Info("start statistic: %s", projectName)
var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth, numCommits int64 var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth, numCommits int64
repoGitStat, err := models.GetRepoKPIStats(repo) repoGitStat, err := models.GetRepoKPIStats(repo)
if err != nil { if err != nil {
log.Error("GetRepoKPIStats failed: %s", getDistinctProjectName(repo))
log.Error("GetRepoKPIStats failed: %s", projectName)
} else { } else {
numDevMonths = repoGitStat.DevelopAge numDevMonths = repoGitStat.DevelopAge
numKeyContributor = repoGitStat.KeyContributors numKeyContributor = repoGitStat.KeyContributors
@@ -79,26 +81,26 @@ func RepoStatisticDaily(date string) {
var numVersions int64 var numVersions int64
numVersions, err = models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{}) numVersions, err = models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{})
if err != nil { if err != nil {
log.Error("GetReleaseCountByRepoID failed(%s): %v", getDistinctProjectName(repo), err)
log.Error("GetReleaseCountByRepoID failed(%s): %v", projectName, err)
} }


var datasetSize int64 var datasetSize int64
datasetSize, err = getDatasetSize(repo) datasetSize, err = getDatasetSize(repo)
if err != nil { if err != nil {
log.Error("getDatasetSize failed(%s): %v", getDistinctProjectName(repo), err)
log.Error("getDatasetSize failed(%s): %v", projectName, err)
} }


var numComments int64 var numComments int64
numComments, err = models.GetCommentCountByRepoID(repo.ID) numComments, err = models.GetCommentCountByRepoID(repo.ID)
if err != nil { if err != nil {
log.Error("GetCommentCountByRepoID failed(%s): %v", getDistinctProjectName(repo), err)
log.Error("GetCommentCountByRepoID failed(%s): %v", projectName, err)
} }


beginTime, endTime := getStatTime(date) beginTime, endTime := getStatTime(date)
var numVisits int var numVisits int
numVisits, err = repository.AppointProjectView(repo.OwnerName, repo.Name, beginTime, endTime) numVisits, err = repository.AppointProjectView(repo.OwnerName, repo.Name, beginTime, endTime)
if err != nil { if err != nil {
log.Error("AppointProjectView failed(%s): %v", getDistinctProjectName(repo), err)
log.Error("AppointProjectView failed(%s): %v", projectName, err)
} }


repoStat := models.RepoStatistic{ repoStat := models.RepoStatistic{
@@ -162,9 +164,10 @@ func RepoStatisticDaily(date string) {
} }


if _, err = models.InsertRepoStat(&repoStat); err != nil { if _, err = models.InsertRepoStat(&repoStat); err != nil {
log.Error("InsertRepoStat failed(%s): %v", getDistinctProjectName(repo), err)
log.Error("failed statistic: %s", getDistinctProjectName(repo))
mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage)
log.Error("InsertRepoStat failed(%s): %v", projectName, err)
log.Error("failed statistic: %s", projectName)
error_projects = append(error_projects, projectName)

continue continue
} }


@@ -247,6 +250,10 @@ func RepoStatisticDaily(date string) {
log.Info("finish statistic: %s", getDistinctProjectName(repo)) log.Info("finish statistic: %s", getDistinctProjectName(repo))
} }


if len(error_projects) > 0 {
mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage)
}

//radar map //radar map
log.Info("begin statistic radar") log.Info("begin statistic radar")
for _, radarInit := range reposRadar { for _, radarInit := range reposRadar {


+ 4
- 1
routers/repo/setting.go View File

@@ -50,6 +50,8 @@ func Settings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
SetModelCount(ctx)
SetJobCount(ctx)
ctx.HTML(200, tplSettingsOptions) ctx.HTML(200, tplSettingsOptions)
} }


@@ -57,7 +59,8 @@ func Settings(ctx *context.Context) {
func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true

SetModelCount(ctx)
SetJobCount(ctx)
repo := ctx.Repo.Repository repo := ctx.Repo.Repository


switch ctx.Query("action") { switch ctx.Query("action") {


+ 11
- 0
routers/routes/routes.go View File

@@ -1033,6 +1033,17 @@ func RegisterRoutes(m *macaron.Macaron) {


m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList) m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList)
}) })

m.Group("/inference-job", func() {
m.Get("", reqRepoCloudBrainReader, repo.InferenceJobIndex)
m.Group("/:jobid", func() {
m.Get("", reqRepoCloudBrainReader, repo.InferenceJobShow)
m.Get("/result_download", cloudbrain.AdminOrJobCreaterRight, repo.ResultDownload)
m.Get("/downloadall", repo.DownloadMultiResultFile)
})
m.Get("/create", reqRepoCloudBrainWriter, repo.InferenceJobNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate)
})
}, context.RepoRef()) }, context.RepoRef())


m.Group("/blockchain", func() { m.Group("/blockchain", func() {


+ 23
- 8
services/socketwrap/clientManager.go View File

@@ -10,6 +10,8 @@ import (
"github.com/elliotchance/orderedmap" "github.com/elliotchance/orderedmap"
) )


var opTypes = []int{1, 2, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 22, 23}

type ClientsManager struct { type ClientsManager struct {
Clients *orderedmap.OrderedMap Clients *orderedmap.OrderedMap
Register chan *Client Register chan *Client
@@ -47,13 +49,15 @@ func (h *ClientsManager) Run() {
close(client.Send) close(client.Send)
} }
case message := <-models.ActionChan: case message := <-models.ActionChan:
LastActionsQueue.Push(message)
for _, client := range h.Clients.Keys() {
select {
case client.(*Client).Send <- message:
default:
close(client.(*Client).Send)
h.Clients.Delete(client)
if isInOpTypes(opTypes, message.OpType) {
LastActionsQueue.Push(message)
for _, client := range h.Clients.Keys() {
select {
case client.(*Client).Send <- message:
default:
close(client.(*Client).Send)
h.Clients.Delete(client)
}
} }
} }
case s := <-sig: case s := <-sig:
@@ -71,8 +75,19 @@ func (h *ClientsManager) Run() {
} }
} }


func isInOpTypes(types []int, opType models.ActionType) bool {
isFound := false
for _, value := range types {
if value == int(opType) {
isFound = true
break
}
}
return isFound
}

func initActionQueue() { func initActionQueue() {
actions, err := models.GetLast20PublicFeeds()
actions, err := models.GetLast20PublicFeeds(opTypes)
if err == nil { if err == nil {
for i := len(actions) - 1; i >= 0; i-- { for i := len(actions) - 1; i >= 0; i-- {




+ 1
- 1
templates/base/head_navbar.tmpl View File

@@ -43,7 +43,7 @@
{{if .IsOperator}} {{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}} {{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a>
</div> </div>
</div> </div>
{{else if .IsLandingPageHome}} {{else if .IsLandingPageHome}}


+ 1
- 1
templates/home.tmpl View File

@@ -57,7 +57,7 @@
<div class="leftline02-2"></div> <div class="leftline02-2"></div>
<div class="ui center homepro-tit am-mb-20"> <div class="ui center homepro-tit am-mb-20">
<h2>{{.page_recommend_repo}}</h2> <h2>{{.page_recommend_repo}}</h2>
<p><span class="ui text grey">{{.page_recommend_repo_desc}}</span><a href="{{.RecommendURL}}">{{.page_recommend_repo_commit}}</a>{{.page_recommend_repo_go}}<a href="{{AppSubUrl}}/explore/">{{.page_recommend_repo_more}}</a></p>
<p><span class="ui text grey">{{.page_recommend_repo_desc}}</span><a href="{{.RecommendURL}}">{{.page_recommend_repo_commit}}</a>{{.page_recommend_repo_go}}&nbsp;<a href="{{AppSubUrl}}/explore/">{{.page_recommend_repo_more}}</a></p>
</div> </div>


<div class="homepro-list"> <div class="homepro-list">


+ 8
- 8
templates/repo/cloudbrain/new.tmpl View File

@@ -147,7 +147,7 @@
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>任务名称</label> <label>任务名称</label>
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255" onkeyup="this.value=this.value.replace(/[, ]/g,'')">
</div> </div>


<div class="inline required field" style="{{if ((.is_benchmark_enabled) or (.is_snn4imagenet_enabled) or (.is_brainscore_enabled))}}display:block;{{else}}display:none;{{end}}"> <div class="inline required field" style="{{if ((.is_benchmark_enabled) or (.is_snn4imagenet_enabled) or (.is_brainscore_enabled))}}display:block;{{else}}display:none;{{end}}">
@@ -192,7 +192,7 @@


<div class="inline required field" style="position: relative;"> <div class="inline required field" style="position: relative;">
<label>镜像</label> <label>镜像</label>
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image" required autofocus maxlength="254">
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image" required autofocus maxlength="255">
<i class="times circle outline icon icons" style="visibility: hidden;" onclick="clearValue()"></i> <i class="times circle outline icon icons" style="visibility: hidden;" onclick="clearValue()"></i>
<datalist class="ui search" id="cloudbrain_image" style='width:385px;' name="image"> <datalist class="ui search" id="cloudbrain_image" style='width:385px;' name="image">
{{range .images}} {{range .images}}
@@ -225,27 +225,27 @@


<div class="inline required field"> <div class="inline required field">
<label>数据集存放路径</label> <label>数据集存放路径</label>
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>模型存放路径</label> <label>模型存放路径</label>
<input name="model_path" id="cloudbrain_model_path" value="{{.model_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="model_path" id="cloudbrain_model_path" value="{{.model_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>代码存放路径</label> <label>代码存放路径</label>
<input name="code_path" id="cloudbrain_code_path" value="{{.code_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="code_path" id="cloudbrain_code_path" value="{{.code_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field cloudbrain_benchmark"> <div class="inline required field cloudbrain_benchmark">
<label>benchmark脚本存放路径</label> <label>benchmark脚本存放路径</label>
<input name="benchmark_path" id="cloudbrain_benchmark_path" value="{{.benchmark_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="benchmark_path" id="cloudbrain_benchmark_path" value="{{.benchmark_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field cloudbrain_snn4imagenet"> <div class="inline required field cloudbrain_snn4imagenet">
<label>snn4imagenet脚本存放路径</label> <label>snn4imagenet脚本存放路径</label>
<input name="snn4imagenet_path" id="cloudbrain_snn4imagenet_path" value="{{.snn4imagenet_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="snn4imagenet_path" id="cloudbrain_snn4imagenet_path" value="{{.snn4imagenet_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field cloudbrain_brainscore"> <div class="inline required field cloudbrain_brainscore">
<label>brainscore脚本存放路径</label> <label>brainscore脚本存放路径</label>
<input name="brainscore_path" id="cloudbrain_brainscore_path" value="{{.brainscore_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="brainscore_path" id="cloudbrain_brainscore_path" value="{{.brainscore_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field" hidden> <div class="inline required field" hidden>
<label>启动命令</label> <label>启动命令</label>


+ 1
- 1
templates/repo/create.tmpl View File

@@ -54,7 +54,7 @@
</div> </div>
<div class="inline field {{if .Err_Description}}error{{end}}"> <div class="inline field {{if .Err_Description}}error{{end}}">
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> <label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
<textarea id="description" name="description" maxlength="254">{{.description}}</textarea>
<textarea id="description" name="description" maxlength="255">{{.description}}</textarea>
</div> </div>
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.template"}}</label> <label>{{.i18n.Tr "repo.template"}}</label>


+ 1
- 1
templates/repo/datasets/index.tmpl View File

@@ -57,7 +57,7 @@
<div class="ui grid form segment success {{if not .Error}}hide{{end}}" id="dataset-content-edit"> <div class="ui grid form segment success {{if not .Error}}hide{{end}}" id="dataset-content-edit">
<label class="d-block">{{.i18n.Tr "dataset.title"}}</label> <label class="d-block">{{.i18n.Tr "dataset.title"}}</label>
<div class="sixteen wide column"> <div class="sixteen wide column">
<input name="title" placeholder='{{.i18n.Tr "dataset.title"}}' value="{{.dataset.Title}}" autofocus required maxlength="254">
<input name="title" placeholder='{{.i18n.Tr "dataset.title"}}' value="{{.dataset.Title}}" autofocus required maxlength="255">
</div> </div>
<label class="d-block">{{.i18n.Tr "dataset.description"}}</label> <label class="d-block">{{.i18n.Tr "dataset.description"}}</label>
<div class="sixteen wide column"> <div class="sixteen wide column">


+ 5
- 3
templates/repo/debugjob/index.tmpl View File

@@ -217,6 +217,7 @@
<div class="ui blue small menu compact selectcloudbrain"> <div class="ui blue small menu compact selectcloudbrain">
<a class="active item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> <a class="active item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> <a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
</div> </div>
</div> </div>
<div class="column right aligned"> <div class="column right aligned">
@@ -430,12 +431,12 @@
<div class="inline required field dis"> <div class="inline required field dis">
<label>镜像标签:</label> <label>镜像标签:</label>
<input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="254" style="width:75%">
<input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="255" style="width:75%">
</div> </div>
<div class="inline field"> <div class="inline field">
<label class="label_after">镜像描述:</label> <label class="label_after">镜像描述:</label>
<textarea name="description" maxlength="254" rows="8" style="width:75%;margin-left: 0.2em;"></textarea>
<textarea name="description" maxlength="255" rows="8" style="width:75%;margin-left: 0.2em;"></textarea>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="inline field"> <div class="inline field">
@@ -488,6 +489,7 @@


<script> <script>
// 调试和评分新开窗口 // 调试和评分新开窗口
console.log({{.Tasks}})
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
let url={{.RepoLink}} let url={{.RepoLink}}
let getParam=getQueryVariable('debugListType') let getParam=getQueryVariable('debugListType')
@@ -606,7 +608,7 @@
const jobID = job.dataset.jobid; const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath; const repoPath = job.dataset.repopath;
const computeResource = job.dataset.resource const computeResource = job.dataset.resource
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED']
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED','UNAVAILABLE','DELETED','RESIZE_FAILED']
if (initArray.includes(job.textContent.trim())) { if (initArray.includes(job.textContent.trim())) {
return return


+ 1
- 1
templates/repo/issue/new_form.tmpl View File

@@ -15,7 +15,7 @@
<div class="ui segment content"> <div class="ui segment content">
<div class="field"> <div class="field">
<!-- --> <!-- -->
<input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="254">
<input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="255">
{{if .PageIsComparePull}} {{if .PageIsComparePull}}
<div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div> <div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div>
{{end}} {{end}}


+ 1
- 1
templates/repo/issue/view_title.tmpl View File

@@ -3,7 +3,7 @@
<h1 class="twelve wide column"> <h1 class="twelve wide column">
<span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{RenderEmoji .Issue.Title}}</span> <span class="index">#{{.Issue.Index}}</span> <span id="issue-title">{{RenderEmoji .Issue.Title}}</span>
<div id="edit-title-input" class="ui input" style="display: none"> <div id="edit-title-input" class="ui input" style="display: none">
<input value="{{.Issue.Title}}" maxlength="254">
<input value="{{.Issue.Title}}" maxlength="255">
</div> </div>
</h1> </h1>
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}} {{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}


+ 1
- 1
templates/repo/migrate.tmpl View File

@@ -122,7 +122,7 @@
</div> </div>
<div class="inline field {{if .Err_Description}}error{{end}}"> <div class="inline field {{if .Err_Description}}error{{end}}">
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> <label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
<textarea id="description" name="description" maxlength="254">{{.description}}</textarea>
<textarea id="description" name="description" maxlength="255">{{.description}}</textarea>
</div> </div>


<div class="inline field"> <div class="inline field">


+ 337
- 0
templates/repo/modelarts/inferencejob/index.tmpl View File

@@ -0,0 +1,337 @@
<!-- 头部导航栏 -->
{{template "base/head" .}}

<style>
.fontsize14{
font-size: 14px;
}
.padding0{
padding: 0 !important;
}
</style>

<!-- 弹窗 -->
<div id="mask">
<div id="loadingPage">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>

<!-- 提示框 -->
<div class="alert"></div>

<div class="repository release dataset-list view">
{{template "repo/header" .}}
<!-- 列表容器 -->
<div class="ui container">
{{template "base/alert" .}}
<div class="ui two column stackable grid ">
<div class="column">
<div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="active item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
</div>
</div>
<div class="column right aligned">
{{if .Permission.CanWrite $.UnitTypeCloudBrain}}
<a class="ui green button" href="{{.RepoLink}}/modelarts/inference-job/create">{{$.i18n.Tr "repo.modelarts.train_job.new_infer"}}</a>
{{else}}
<a class="ui disabled button" >{{$.i18n.Tr "repo.modelarts.train_job.new_infer"}}</a>
{{end}}
</div>
</div>
{{if eq 0 (len .Tasks)}}
<div class="ui placeholder segment bgtask-none">
<div class="ui icon header bgtask-header-pic"></div>
<div class="bgtask-content-header">未创建过推理任务</div>
<div class="bgtask-content">
{{if $.RepoIsEmpty}}
<div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div>
{{end}}
{{if eq 0 $.MODEL_COUNT}}
<div class="bgtask-content-txt">模型文件:您还没有模型文件,请先通过<a href="{{.RepoLink}}/modelarts/train-job">训练任务</a>产生并 <a href="{{.RepoLink}}/modelmanage/show_model">导出模型</a> ;</div>
{{end}}
<div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div>
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div>
</div>
</div>
{{else}}
<!-- 中下列表展示区 -->
<div class="ui grid">
<div class="row">
<div class="ui sixteen wide column">
<!-- 任务展示 -->
<div class="dataset list">

<!-- 表头 -->
<div class="ui grid stackable" style="background: #f0f0f0;;">
<div class="row">
<div class="three wide column padding0">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span>
</div>
<div class="three wide column text center padding0">
<span style="margin:0 6px">{{$.i18n.Tr "repo.modelarts.infer_job.model_version"}}</span>
</div>
<div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.modelarts.status"}}</span>
</div>
<div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.modelarts.createtime"}}</span>
</div>
<div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.cloudbrain_status_runtime"}}</span>
</div>
<!-- <div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.modelarts.computing_resources"}}</span>
</div> -->
<div class="one wide column text center padding0">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="three wide column text center padding0">
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>
</div>
</div>

{{range .Tasks}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="three wide column padding0">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>
<!-- 模型版本 -->
<!-- href="{{$.RepoLink}}/modelmanage/show_model_info?name={{.ModelName}}" -->
<div class="three wide column text center padding0">
<a id="{{.JobName}}" href="javascript:void(0);" data-variation="inverted" data-position="top center" data-content="{{$.i18n.Tr "repo.modelarts.infer_job.tooltip"}}" onclick="getModelInfo({{.ModelName}},{{.ModelVersion}},{{.JobName}})">{{.ModelName}}&nbsp;</a>&nbsp;<span style="font-size: 12px;">{{.ModelVersion}} </span>
</div>
<!-- 任务状态 -->
<div class="two wide column text center padding0" >
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<span><i id="{{.JobID}}-icon" style="vertical-align: middle;" class="{{.Status}}"></i><span id="{{.JobID}}-text" style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span>
</span>
</div>
<!-- 任务创建时间 -->
<div class="two wide column text center padding0">
<span style="font-size: 12px;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>
<!-- 任务运行时间 -->
<div class="two wide column text center padding0">
<span style="font-size: 12px;" id="duration-{{.JobID}}">{{.TrainJobDuration}}</span>
</div>
<!-- 计算资源 -->
<!-- <div class="two wide column text center padding0">
<span style="font-size: 12px;">{{.ComputeResource}}</span>
</div> -->
<!-- 创建者 -->
<div class="one wide column text center padding0">
{{if .User.Name}}
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a>
{{else}}
<a title="Ghost"><img class="ui avatar image" src="{{AppSubUrl}}/user/avatar/Ghost/-1"></a>
{{end}}
</div>

<div class="three wide column text center padding0">
<!-- 停止任务 -->
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic disabled button">
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}

</div>
<!-- 下载 -->
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if .CanModify}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-download-{{.JobID}}" href="{{$.RepoLink}}/modelarts/inference-job/{{.JobID}}/downloadall?version_name={{.VersionName}}" class="ui basic blue button" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.model_download"}}
</a>
{{else}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.model_download"}}
</a>
{{end}}
</div>
<!-- 删除任务 -->
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this,{{.VersionName}},{{.JobID}})" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
</div>
</div>
</div>
</div>
{{end}} {{template "base/paginate" .}}
</div>

</div>
</div>
</div>
{{end}}

</div>

</div>
</div>

</div>

<!-- 确认模态框 -->
<div id="deletemodel">
<div class="ui basic modal">
<div class="ui icon header">
<i class="trash icon"></i> 删除任务
</div>

<div class="content">
<p>你确认删除该任务么?此任务一旦删除不可恢复。</p>
</div>
<div class="actions">
<div class="ui red basic inverted cancel button">
<i class="remove icon"></i> 取消操作
</div>
<div class="ui green basic inverted ok button">
<i class="checkmark icon"></i> 确定操作
</div>
</div>
</div>
</div>

</div>
{{template "base/footer" .}}
<script>
// 加载任务状态
var timeid = window.setInterval(loadJobStatus, 15000);
$(document).ready(loadJobStatus);
function loadJobStatus() {
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid
const repoPath = job.dataset.repopath
const versionname = job.dataset.version
const status_text = $(`#${jobID}-text`).text()
const finalState = ['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED','SUBMIT_MODEL_FAILED','DEPLOY_SERVICE_FAILED','CHECK_FAILED']
if(finalState.includes(status_text)){
return
}
$.get(`/api/v1/repos/${repoPath}/modelarts/inference-job/${jobID}?version_name=${versionname}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
const duration = data.JobDuration
$('#duration-'+jobID).text(duration)
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)
finalState.includes(status) && $('#' + jobID + '-stop').removeClass('blue').addClass('disabled')
}
}).fail(function(err) {
console.log(err);
});
});
};
function getModelInfo(ID,version,JobName){
$.get("{{$.RepoLink}}/modelmanage/show_model_info_api?name="+ID,(data)=>{
if(data.length===0){
$(`#${JobName}`).popup('toggle')
}else{
let versionData = data.filter((item)=>{
return item.Version === version
})
if(versionData.length==0){
$(`#${JobName}`).popup('toggle')
}
else{
location.href = "{{$.RepoLink}}/modelmanage/show_model_info?name="+ID
}
}
})

}
function deleteVersion(version_name,jobID){
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/del_version'
$.post(url,{version_name:version_name},(data)=>{
if(data.StatusOK===0){
location.reload()
}
}).fail(function(err) {
console.log(err);
});
}
function stopVersion(version_name,jobID){
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/stop_version'
$.post(url,{version_name:version_name},(data)=>{
if(data.StatusOK===0){
$('#'+version_name+'-stop').removeClass('blue')
$('#'+version_name+'-stop').addClass('disabled')
refreshStatus(version_name,jobID)
}
}).fail(function(err) {
console.log(err);
});
}
function refreshStatus(version_name,jobID){

const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'?version_name='+version_name
$.get(url,(data)=>{
$(`#${jobID}-icon`).attr("class",data.JobStatus)
// detail status and duration
$(`#${jobID}-text`).text(data.JobStatus)


}).fail(function(err) {
console.log(err);
});
}
function assertDelete(obj,version_name,jobID) {
if (obj.style.color == "rgb(204, 204, 204)") {
return
} else {
// var delId = obj.parentNode.id
flag = 1;
$('.ui.basic.modal')
.modal({
onDeny: function() {
flag = false
},
onApprove: function() {
// document.getElementById(delId).submit()
deleteVersion(version_name,jobID)
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
}
}
</script>

+ 477
- 0
templates/repo/modelarts/inferencejob/new.tmpl View File

@@ -0,0 +1,477 @@
{{template "base/head" .}}
<style>
.unite{
font-family: SourceHanSansSC-medium !important;
color: rgba(16, 16, 16, 100) !important;
}

.title{
font-size: 16px !important;
padding-left: 3rem !important;
}
.min_title{
font-size: 14px !important;
padding-left: 6rem !important;
margin-bottom: 2rem !important;

}
.width80{
width: 80.7% !important;
}
.width84{
width: 84% !important;
margin-left: 5.1rem !important;
}
.width35{
width: 35.5% !important;
}


.nowrap {
white-space: nowrap !important;
}
</style>
<div id="mask">
<div id="loadingPage">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
<div class="repository">
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{.i18n.Tr "repo.modelarts.train_job.new_infer"}}
</h4>
<div class="ui attached segment">
<!-- equal width -->
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update">
<input type="hidden" id="ai_engine_name" name="engine_names" value="">
<input type="hidden" id="ai_flaver_name" name="flaver_names" value="">
{{if $.model_version}}
<input type="hidden" id="ai_model_version" name="model_version" value="{{$.model_version}}">
{{else}}
<input type="hidden" id="ai_model_version" name="model_version" value="">
{{end}}
{{if $.label_names}}
<input type="hidden" id="ai_model_label" name="label_names" value="{{$.label_names}}">
{{else}}
<input type="hidden" id="ai_model_label" name="label_names" value="">
{{end}}
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4>
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label>
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" onkeyup="this.value=this.value.replace(/[, ]/g,'')" tabindex="3" autofocus required maxlength="64">
<span class="tooltips" style="display: block;">请输入字母、数字、_和-,最长64个字符,且不能以中划线(-)结尾。</span>
</div>
<div class="unite min_title inline field">
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}</label>&nbsp;&nbsp;
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="255" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea>
</div>
<div class="ui divider"></div>

<!-- 模型相关配置 -->
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}:</h4>
<div class="required unite inline min_title fields" style="width: 91.8%;">
<div class="required eight wide field">
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.modelarts.infer_job.select_model"}}</label>&nbsp;&nbsp;
<div class="ui fluid search selection dropdown loading " id="select_model">
{{if $.ckpt_name}}
<input type="hidden" name="model_name" value="{{$.model_name}}" required>
<div class="text">{{$.model_name}}</div>
{{else}}
<input type="hidden" name="model_name" required>
<div class="text"></div>
{{end}}
<i class="dropdown icon"></i>
<div class="menu" id="model_name">
</div>
</div>
</div>
<div class="three wide field">
<div class="ui fluid search selection dropdown" id="select_model_version">
{{if $.ckpt_name}}
<input type="hidden" name="train_url" value="{{$.train_url}}" required>
<div class="text">{{$.model_version}}</div>
{{else}}
<input type="hidden" name="train_url" required>
<div class="text"></div>
{{end}}
<i class="dropdown icon"></i>
<div class="menu" id="model_name_version">
</div>
</div>
</div>
<div class="five wide field">
<div class="ui fluid search selection dropdown" id="select_model_checkpoint">
{{if $.ckpt_name}}
<input type="hidden" name="ckpt_name" value="{{$.ckpt_name}}" required>
<div class="text">{{$.ckpt_name}}</div>
{{else}}
<input type="hidden" name="ckpt_name" required>
<div class="text"></div>
{{end}}
<i class="dropdown icon"></i>
<div class="menu" id="model_checkpoint">
</div>
</div>
</div>
<span >
<i class="question circle icon" data-content="模型文件位置存储在环境变量ckpt_url中。" data-position="top center" data-variation="inverted mini"></i>
</span>
</div>
<!-- AI引擎 -->
<div class="required unite inline min_title fields" style="width: 90%;">
<div class="required eight wide field">
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.modelarts.train_job.AI_driver"}}</label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<select class="ui fluid selection search dropdown" id="trainjob_engines">
{{range .engines}}
<option value="{{.Value}}">{{.Value}}</option>
{{end}}
</select>
</div>
<div class="eight wide field" id="engine_name">
<select class="ui fluid selection dropdown nowrap" id="trainjob_engine_versions" name="engine_id" style="white-space: nowrap;">
{{range .engine_versions}}
<option name="engine_id" value="{{.ID}}">{{.Value}}</option>
{{end}}
</select>
</div>
</div>
<!-- 代码分支 -->
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.code_version"}}</label>&nbsp;
<select class="ui dropdown width35" id="code_version" name="branch_name">
{{if .branch_name}}
<option name="branch_name" value="{{.branch_name}}">{{.branch_name}}</option>
{{range $k, $v :=.Branches}}
{{ if ne $v $.branch_name }}
<option name="branch_name" value="{{$v}}">{{$v}}</option>
{{end}}
{{end}}
{{else}}
<option name="branch_name" value="{{.branchName}}">{{.branchName}}</option>
{{range $k, $v :=.Branches}}
{{ if ne $v $.branchName }}
<option name="branch_name" value="{{$v}}">{{$v}}</option>
{{end}}
{{end}}
{{end}}
</select>
</div>
<!-- 数据集 -->
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label>&nbsp;&nbsp;&nbsp;&nbsp;
<select class="ui dropdown width35" id="trainjob_datasets" name="attachment" placeholder="选择数据集" required>
{{if $.uuid}}
<option name="attachment" value="{{$.uuid}}">{{$.datasetName}}</option>
{{end}}
{{range .attachments}}
<option value="">选择数据集</option>
<option name="attachment" value="{{.UUID}}">{{.Attachment.Name}}</option>
{{end}}
</select>
<span>
<i class="question circle icon" data-content="数据集位置存储在环境变量data_url中。" data-position="top center" data-variation="inverted mini"></i>
</span>
</div>
<!-- 启动文件 -->
<div class="inline unite min_title field required">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>&nbsp;
{{if .bootFile}}
<input style="width: 35.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="255" >
{{else}}
<input style="width: 35.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255" >
{{end}}
<span >
<i class="question circle icon" data-content={{.i18n.Tr "repo.modelarts.infer_job.boot_file_helper"}} data-position="top center" data-variation="inverted mini"></i>
</span>
<a href="https://git.openi.org.cn/OpenIOSSG/MINIST_Example" target="_blank">查看样例</a>
</div>
<!-- 运行参数 -->
<div class="inline unite min_title field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.run_parameter"}}</label>&nbsp;&nbsp;
<span id="add_run_para" style="margin-left: 0.5rem;cursor:pointer;color: rgba(3, 102, 214, 100);font-size: 14px;line-height: 26px;font-family: SourceHanSansSC-medium;"><i class="plus square outline icon"></i>{{.i18n.Tr "repo.modelarts.train_job.add_run_parameter"}}</span>
<input id="store_run_para" type="hidden" name="run_para_list">
<div class="dynamic field" style="margin-top: 1rem;">
{{if ne 0 (len .params)}}
{{range $k ,$v := .params}}
<div class="two fields width84" id="para{{$k}}">
<div class="field">
<input type="text" name="shipping_first-name" value={{$v.Label}} required>
</div>
<div class="field">
<input type="text" name="shipping_last-name" value={{$v.Value}} required>
</div>
<span>
<i class="trash icon"></i>
</span>

</div>
{{end}}
{{end}}
</div>
</div>
<div class="required field " style="display: none;">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.resource_pool"}}</label>
<select class="ui dropdown" id="trainjob_resource_pool" style='width:385px' name="pool_id">
{{range .resource_pools}}
<option value="{{.ID}}">{{.Value}}</option>
{{end}}
</select>
</div>
<!-- 规格 -->
<div class="required unite min_title inline field" id="flaver_name">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<select class="ui dropdown width80" id="trainjob-flavor" name="flavor">
{{range .flavor_infos}}
<option name="flavor" value="{{.Code}}">{{.Value}}</option>
{{end}}
</select>
</div>
<!-- 计算节点 -->
<div class="inline required unite min_title field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label>
<div class="ui labeled input" style="width: 5%;">
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255" value="1" readonly>
</div>
<span class="tooltips" style="display: block;">推理输出路径存储在环境变量result_url中。</span>
</div>
<!-- 表单操作 -->
<div class="inline unite min_title field">
<button class="ui create_train_job green button">
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
<!-- 模态框 -->
</form>
</div>
</div>
</div>
{{template "base/footer" .}}

<script>
const RepoLink = {{.RepoLink}}
const url_href = window.location.pathname.split('create')[0]
let nameMap,nameList
$(".ui.button").attr('href',url_href)
// 获取模型列表和模型名称对应的模型版本
$.get(`${RepoLink}/modelmanage/query_model_for_predict`, (data) => {
nameMap = data.nameMap
nameList = data.nameList
let html = ''
nameList.forEach(element => {
html += `<div class="item" data-value=${element}>${element}</div>`
});
if(nameList.length!==0){
const initModelVersion = nameMap[nameList[0]][0]
const initTrainTaskInfo = JSON.parse(initModelVersion.TrainTaskInfo)
$('#model_name').append(html)
$("#select_model").dropdown('set text',nameList[0])
$("#select_model").dropdown('set value',nameList[0],nameList[0])
}
$('#select_model').removeClass("loading")
})
// 根据选中的模型名称获取相应的模型版本
$(function(){
$('#select_model').dropdown({
onChange: function(value, text, $selectedItem) {
$("#select_model_version").addClass("loading")
$('#model_name_version').empty()
let html = ''
nameMap[value].forEach(element => {
let {TrainTaskInfo} = element
TrainTaskInfo = JSON.parse(TrainTaskInfo)
html += `<div class="item" data-label="${element.Label}" data-id="${element.ID}" data-value="${TrainTaskInfo.TrainUrl}">${element.Version}</div>`
});
$('#model_name_version').append(html)
$("#select_model_version").removeClass("loading")
const initVersionText = $('#model_name_version div.item:first-child').text()
const initVersionValue = $('#model_name_version div.item:first-child').data('value')
$("#select_model_version").dropdown('set text',initVersionText)
$("#select_model_version").dropdown('set value',initVersionValue,initVersionText,$('#model_name_version div.item:first-child'))
}
})
})
// 根据选中的模型版本获取相应的模型权重文件
$(function(){
$('#select_model_version').dropdown({
onChange: function(value, text, $selectedItem) {
const dataID = $selectedItem[0].getAttribute("data-id")
const label = $selectedItem[0].getAttribute("data-label")
$("#select_model_checkpoint").addClass("loading")
$("#model_checkpoint").empty()
let html = ''
loadCheckpointList(dataID).then((res)=>{
res.forEach(element => {
const ckptSuffix = element.FileName.split(".")
const loadCheckpointFile = ['ckpt','pb','h5','json','pkl','pth','t7']
if(!element.IsDir && loadCheckpointFile.includes(ckptSuffix[ckptSuffix.length-1])){
html += `<div class="item" data-value=${element.FileName}>${element.FileName}</div>`
}
})
$('#model_checkpoint').append(html)
$("#select_model_checkpoint").removeClass("loading")
const initVersionText = $('#model_checkpoint div.item:first-child').text()
const initVersionValue = $('#model_checkpoint div.item:first-child').data('value')
$("#select_model_checkpoint").dropdown('set text',initVersionText)
$("#select_model_checkpoint").dropdown('set value',initVersionValue,initVersionText,$('#model_name_version div.item:first-child'))
})

$("input#ai_model_version").val(text)
$("input#ai_model_label").val(label)
}
})
})
function loadCheckpointList(value){
return new Promise((resolve,reject)=>{
$.get(`${RepoLink}/modelmanage/query_modelfile_for_predict`,{ID:value}, (data) => {
resolve(data)
})
})
}

$('.question.circle.icon').hover(function(){
$(this).popup('show')
});

// 参数增加、删除、修改、保存
function Add_parameter(i){
value = '<div class="two fields width84" id= "para'+ i +'">' +
'<div class="field">' +
'<input type="text" name="shipping_first-name" required placeholder={{.i18n.Tr "repo.modelarts.train_job.parameter_name"}}> ' +
'</div> ' +
'<div class="field"> ' +
'<input type="text" name="shipping_last-name" required placeholder={{.i18n.Tr "repo.modelarts.train_job.parameter_value"}}>' +
'</div>'+
'<span>' +
'<i class="trash icon">' +
'</i>' +
'</span>' +
'</div>'
$(".dynamic.field").append(value)
}

$('#add_run_para').click(function(){
var len = $(".dynamic.field .two.fields").length
Add_parameter(len)
});

$(".dynamic.field").on("click",".trash.icon", function() {
var index = $(this).parent().parent().index()
$(this).parent().parent().remove()
var len = $(".dynamic.field .two.fields").length
$(".dynamic.field .two.fields").each(function(){
var cur_index = $(this).index()
$(this).attr('id', 'para' + cur_index)
})
});
function send_run_para(){
var run_parameters = []
var msg = {}
$(".dynamic.field .two.fields").each(function(){
var para_name = $(this).find('input[name=shipping_first-name]').val()
var para_value = $(this).find('input[name=shipping_last-name]').val()
run_parameters.push({"label": para_name, "value": para_value})
})
msg["parameter"] = run_parameters
msg = JSON.stringify(msg)
$('#store_run_para').val(msg)
}
function get_name(){
let name1=$("#engine_name .text").text()
let name2=$("#flaver_name .text").text()
$("input#ai_engine_name").val(name1)
$("input#ai_flaver_name").val(name2)

}
function validate(){
$('.ui.form')
.form({
on: 'blur',
fields: {
boot_file: {
identifier : 'boot_file',
rules: [
{
type: 'regExp[/.+\.py$/g]',
}
]
},
job_name:{
identifier : 'job_name',
rules: [
{
type: 'regExp[/^[a-zA-Z0-9-_]{1,64}[^-]$/]',
}
]
},
attachment:{
identifier : 'attachment',
rules: [
{
type: 'empty',
}
]

},
model_name:{
identifier : 'model_name',
rules: [
{
type: 'empty',
}
]
},
train_url:{
identifier : 'train_url',
rules: [
{
type: 'empty',
}
]
},
ckpt_name:{
identifier : 'ckpt_name',
rules: [
{
type: 'empty',
}
]
}
},
onSuccess: function(){
document.getElementById("mask").style.display = "block"
},
onFailure: function(e){
return false;
}
})
}
document.onreadystatechange = function() {
if (document.readyState === "complete") {
document.getElementById("mask").style.display = "none"
}
}
$('.ui.create_train_job.green.button').click(function(e) {
send_run_para()
get_name()
validate()
})
</script>

+ 653
- 0
templates/repo/modelarts/inferencejob/show.tmpl View File

@@ -0,0 +1,653 @@
{{template "base/head" .}}
<style>
.according-panel-heading{
box-sizing: border-box;
padding: 8px 16px;
color: #252b3a;
background-color: #f2f5fc;
line-height: 1.5;
cursor: pointer;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.accordion-panel-title {
margin-top: 0;
margin-bottom: 0;
color: #252b3a;
}
.accordion-panel-title-content{
vertical-align: middle;
display: inline-block;
width: calc(100% - 32px);
cursor: default;
}
.acc-margin-bottom {
margin-bottom: 5px;
}
.title_text {
font-size: 12px;
}
.ac-display-inblock {
display: inline-block;
}
.cti-mgRight-sm {
margin-right: 8px;
}
.ac-text-normal {
font-size: 14px;
color: #575d6c;
}
.uc-accordionTitle-black {
color: #333;
}
.accordion-border{
border:1px solid #cce2ff;
}
.padding0{
padding: 0 !important;
}
.content-pad{
padding: 15px 35px;
}
.content-margin{
margin:10px 5px ;
}
.tab_2_content {
min-height: 360px;
margin-left: 10px;
}
.ac-grid {
display: block;
*zoom: 1;
}
.ac-grid-col {
float: left;
width: 100%;
}
.ac-grid-col2 .ac-grid-col {
width: 50%;
}
.ti-form {
text-align: left;
max-width: 100%;
vertical-align: middle;
}
.ti-form>tbody {
font-size: 12px;
}
.ti-form>tbody, .ti-form>tbody>tr {
vertical-align: inherit;
}
.ti-text-form-label {
padding-bottom: 20px;
padding-right: 20px;
color: #8a8e99;
font-size: 12px;
white-space: nowrap !important;
width: 80px;
line-height: 30px;
}
.ti-text-form-content{
line-height: 30px;
padding-bottom: 20px;
}
.ti-form>tbody>tr>td {
vertical-align: top;
white-space: normal;
}
td, th {
padding: 0;
}
.ac-grid-col .text-span {
width: 450px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.redo-color{
color: #3291F8;
}
.ti-action-menu-item:not(:last-child){
margin-right: 10px;
padding-right: 11px;
text-decoration: none!important;
color: #526ecc;
cursor: pointer;
display: inline-block;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
position: relative;
}
.ti-action-menu-item:not(:last-child):after {
content: "";
display: inline-block;
position: absolute;
height: 12px;
right: 0;
top: 50%;
-webkit-transform: translateY(-6px);
-ms-transform: translateY(-6px);
-o-transform: translateY(-6px);
transform: translateY(-6px);
border-right: 1px solid #dfe1e6;
}
.text-width80{
width: 100px;
line-height: 30px;
}
.border-according{
border: 1px solid #dfe1e6;
}
.disabled {
cursor: default;
pointer-events: none;
color: rgba(0,0,0,.6) !important;
opacity: .45 !important;
}
.pad20{
border:0px !important;
}
.model_file_bread{
margin-bottom: -0.5rem !important;
padding-left: 1rem;
padding-top: 0.5rem ;
}
</style>
<div class="repository">
{{template "repo/header" .}}
<div class="ui container">
<h4 class="ui header" id="vertical-segment">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/debugjob?debugListType=all">
{{.i18n.Tr "repo.cloudbrain"}}
</a>
<div class="divider"> / </div>
<a class="section" href="{{$.RepoLink}}/modelarts/inference-job">
{{$.i18n.Tr "repo.modelarts.infer_job"}}
</a>
<div class="divider"> / </div>
<div class="active section">{{.jobName}}</div>
</div>
</h4>
{{with .task}}
<div class="content-pad" style="border: 1px solid #e2e2e2;margin-top: 24px;padding: 20px 60px 40px 60px;">
<div class="ui pointing secondary menu" style="border-bottom: 1px solid rgba(34,36,38,.15);">
<a class="active item" data-tab="first">{{$.i18n.Tr "repo.modelarts.train_job.config"}}</a>
<a class="item" data-tab="second" onclick="loadLog({{.VersionName}})">{{$.i18n.Tr "repo.modelarts.log"}}</a>
<a class="item" data-tab="third" onclick="loadModelFile({{.VersionName}},'','','init')">{{$.i18n.Tr "repo.model_download"}}</a>
</div>
<div class="ui tab active" data-tab="first">
<div style="padding-top: 10px;">
<div class="tab_2_content">
<div class="ac-grid ac-grid-col2">
<div class="ac-grid-col">
<table class="ti-form">
<tbody class="ti-text-form">
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.cloudbrain_task"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.JobName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.status"}}
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-status">
{{.Status}}
</div>
</td>
</tr>
<!-- <tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.run_version"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.VersionName}}
</div>
</td>
</tr> -->
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.start_time"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
<span style="font-size: 12px;" class="">{{TimeSinceUnix1 .CreatedUnix}}</span>
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-duration">
{{.TrainJobDuration}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.EngineName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.model.manage.description"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-desc" style="width: 380px;">
{{if .Description}}
<span title="{{.Description}}">{{.Description}}</span>
{{else}}
<span>--</span>
{{end}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
创建人
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-creator">
{{$.userName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.WorkServerNumber}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="ac-grid-col">
<table class="ti-form">
<tbody class="ti-text-form">
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.infer_job_model"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
<span>{{.ModelName}}</span>&nbsp;&nbsp;
<span style="color: #8a8e99">{{$.i18n.Tr "repo.modelarts.version"}}:</span><span>{{.ModelVersion}}</span>&nbsp;&nbsp;
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.infer_job_model_file"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.CkptName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.model_label"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-labels">
{{if .LabelName}}
{{range $.labelName}}
<a class="ui label" title="{{.}}">{{.}}</a>
{{end}}
{{else}}
<span>--</span>
{{end}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.code_version"}}
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.BranchName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.BootFile}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.infer_dataset"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.DatasetName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80" >
{{$.i18n.Tr "repo.modelarts.train_job.run_parameter"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" title="{{.Parameters}}">
{{if .Parameters}}
<span>{{.Parameters}}</span>
{{else}}
<span>--</span>
{{end}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.standard"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.FlavorName}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="ui tab" data-tab="second">
<div>
<div class="ui message message{{.VersionName}}" style="display: none;">
<div id="header"></div>
</div>
<div class="ui attached log" onscroll="logScroll({{.VersionName}})" id="log{{.VersionName}}" style="height: 300px !important; overflow: auto;">
<input type="hidden" name="end_line" value>
<input type="hidden" name="start_line" value>
<pre id="log_file{{.VersionName}}"></pre>
</div>
</div>
</div>
<div class="ui tab" data-tab="third">
<input type="hidden" name="model{{.VersionName}}" value="-1">
<input type="hidden" name="modelback{{.VersionName}}" value="-1">
<div class='ui breadcrumb model_file_bread' id='file_breadcrumb{{.VersionName}}'>
<div class="active section">result</div>
<div class="divider"> / </div>

</div>
<div id="dir_list{{.VersionName}}">
</div>
</div>
</div>
{{end}}
</div>
<!-- 确认模态框 -->
<div id="deletemodel">
<div class="ui basic modal">
<div class="ui icon header">
<i class="trash icon"></i> 删除任务
</div>
<div class="content">
<p>你确认删除该任务么?此任务一旦删除不可恢复。</p>
</div>
<div class="actions">
<div class="ui red basic inverted cancel button">
<i class="remove icon"></i> 取消操作
</div>
<div class="ui green basic inverted ok button">
<i class="checkmark icon"></i> 确定操作
</div>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}
<script>
console.log({{.task}})
$(document).ready(function(){
$('.secondary.menu .item').tab();
});
let userName
let repoPath
let jobID
$(document).ready(function(){
let url = window.location.href;
let urlArr = url.split('/')
userName = urlArr.slice(-5)[0]
repoPath = urlArr.slice(-4)[0]
jobID = urlArr.slice(-1)[0]
})
function loadLog(version_name){
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&lines=50&order=asc`, (data) => {
$('input[name=end_line]').val(data.EndLine)
$('input[name=start_line]').val(data.StartLine)
$(`#log_file${version_name}`).text(data.Content)
}).fail(function(err) {
console.log(err);
});
}
function logScroll(version_name) {
let container = document.querySelector(`#log${version_name}`)
let scrollTop = container.scrollTop
let scrollHeight = container.scrollHeight
let clientHeight = container.clientHeight
let scrollLeft = container.scrollLeft
if((parseInt(scrollTop) + clientHeight == scrollHeight || parseInt(scrollTop) + clientHeight +1 == scrollHeight || parseInt(scrollTop) + clientHeight - 1 == scrollHeight) && (scrollLeft===0)){
let end_line = $(`#log${version_name} input[name=end_line]`).val()
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&base_line=${end_line}&lines=50&order=desc`, (data) => {
if (data.Lines == 0){
$(`.message${version_name} #header`).text('您已翻阅至日志底部')
$(`.message${version_name}`).css('display', 'block')
setTimeout(function(){
$(`.message${version_name}`).css('display', 'none')
}, 1000)
}else{
if(end_line===data.EndLine){
return
}
else{
$(`#log${version_name} input[name=end_line]`).val(data.EndLine)
$(`#log${version_name}`).append('<pre>' + data.Content)
}
}
}).fail(function(err) {
console.log(err);
});
}
if(scrollTop == 0 && scrollLeft==0){
let start_line = $(`#log${version_name} input[name=start_line]`).val()
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/log?version_name=${version_name}&base_line=${start_line}&lines=50&order=asc`, (data) => {
if (data.Lines == 0){
$(`.message${version_name} #header`).text('您已翻阅至日志顶部')
$(`.message${version_name}`).css('display', 'block')
setTimeout(function(){
$(`.message${version_name}`).css('display', 'none')
}, 1000)
}else{
$(`#log${version_name} input[name=start_line]`).val(data.StartLine) //如果变动就改变所对应的值
$(`#log${version_name}`).prepend('<pre>' + data.Content)
}
}).fail(function(err) {
console.log(err);
});
}
}
function renderSize(value){
if(null==value||value==''){
return "0 Bytes";
}
var unitArr = new Array("Bytes","KB","MB","GB","TB","PB","EB","ZB","YB");
var index=0;
var srcsize = parseFloat(value);
index=Math.floor(Math.log(srcsize)/Math.log(1024));
var size =srcsize/Math.pow(1024,index);
size=size.toFixed(0);//保留的小数位数
return size+unitArr[index];
}
function loadModelFile(version_name,parents,filename,init){
parents = parents || ''
filename = filename || ''
init = init || ''
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/inference-job/${jobID}/result_list?version_name=${version_name}&parentDir=${parents}`, (data) => {
$(`#dir_list${version_name}`).empty()
renderDir(data,version_name)
if(init==="init"){
$(`input[name=model${version_name}]`).val("")
$(`input[name=modelback${version_name}]`).val(version_name)
$(`#file_breadcrumb${version_name}`).empty()
let htmlBread = ""
htmlBread += `<div class='active section'>result</div>`
htmlBread += "<div class='divider'> / </div>"
$(`#file_breadcrumb${version_name}`).append(htmlBread)
}else{
renderBrend(version_name,parents,filename,init)
}
}).fail(function(err) {
console.log(err,version_name);
});
}
function renderBrend(version_name,parents,filename,init){
if(init=="folder"){
let htmlBrend = ""
let sectionName=$(`#file_breadcrumb${version_name} .active.section`).text()
let parents1 = $(`input[name=model${version_name}]`).val()
let filename1 = $(`input[name=modelback${version_name}]`).val()
if(parents1===""){
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','','init')">${sectionName}</a>`)
}else{
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','${filename1}')">${sectionName}</a>`)
}
htmlBrend += `<div class='active section'>${filename}</div>`
htmlBrend += "<div class='divider'> / </div>"
$(`#file_breadcrumb${version_name}`).append(htmlBrend)
$(`input[name=model${version_name}]`).val(parents)
$(`input[name=modelback${version_name}]`).val(filename)
}else{
$(`input[name=model${version_name}]`).val(parents)
$(`input[name=modelback${version_name}]`).val(filename)
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).nextAll().remove()
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).replaceWith(`<div class='active section'>${filename}</div>`)
$(`#file_breadcrumb${version_name} div.section:contains(${filename})`).append("<div class='divider'> / </div>")
}
}
function renderDir(data,version_name){
let html=""
html += "<div class='ui grid' style='margin:0;'>"
html += "<div class='row' style='padding: 0;'>"
html += "<div class='ui sixteen wide column' style='padding:1rem;'>"
html += "<div class='dir list'>"
html += "<table id='repo-files-table' class='ui single line table pad20'>"
html += '<tbody>'
// html += "</tbody>"
for(let i=0;i<data.Dirs.length;i++){
let dirs_size = renderSize(data.Dirs[i].Size)
html += "<tr>"
html += "<td class='name six wid'>"
html += "<span class='truncate'>"
html += "<span class='octicon octicon-file-directory'>"
html += "</span>"
if(data.Dirs[i].IsDir){
html += `<a onclick="loadModelFile('${version_name}','${data.Dirs[i].ParenDir}','${data.Dirs[i].FileName}','folder')">`
html += "<span class='fitted'><i class='folder icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}else{
html += `<a href="${location.href}/result_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">`
html += "<span class='fitted'><i class='file icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}
html += '</a>'
html += "</span>"
html += "</td>"
html += "<td class='message seven wide'>"
if(data.Dirs[i].IsDir){
html += "<span class='truncate has-emoji'></span>"
}else{
html += "<span class='truncate has-emoji'>"+ `${dirs_size}` + "</span>"
}
html += "</td>"

html += "<td class='text right age three wide'>"
html += "<span class='truncate has-emoji'>" + data.Dirs[i].ModTime + "</span>"
html += "</td>"
html += "</tr>"
}
html += "</tbody>"
html += "</table>"
html += "</div>"
html += "</div>"
html += "</div>"
html += "</div>"
$(`#dir_list${version_name}`).append(html)
}
</script>

+ 5
- 5
templates/repo/modelarts/notebook/new.tmpl View File

@@ -48,7 +48,7 @@
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>任务名称</label> <label>任务名称</label>
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255" onkeyup="this.value=this.value.replace(/[, ]/g,'')">
</div> </div>


<div class="inline field"> <div class="inline field">
@@ -64,11 +64,11 @@


<div class="inline required field"> <div class="inline required field">
<label>工作环境</label> <label>工作环境</label>
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>类型</label> <label>类型</label>
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>规格</label> <label>规格</label>
@@ -81,11 +81,11 @@
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>数据集存放路径</label> <label>数据集存放路径</label>
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div> </div>
<div class="inline field"> <div class="inline field">
<label>描述</label> <label>描述</label>
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="254">
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="255">
</div> </div>
<div class="inline field"> <div class="inline field">
<label></label> <label></label>


+ 4
- 4
templates/repo/modelarts/trainjob/edit_para.tmpl View File

@@ -18,11 +18,11 @@
<h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}</h4> <h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}</h4>
<div class="required field"> <div class="required field">
<label>{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label> <label>{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label>
<input name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="254" readonly="">
<input name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="255" readonly="">
</div> </div>
<div class="field"> <div class="field">
<label for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}</label> <label for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}</label>
<textarea id="description" maxlength="254" name="description" rows="2"></textarea>
<textarea id="description" maxlength="255" name="description" rows="2"></textarea>
</div> </div>
<h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}</h4> <h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}</h4>
<div class="required field"> <div class="required field">
@@ -52,7 +52,7 @@
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> <label>{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
<input name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254">
<input name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255">
<span> <span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> <i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
</span> </span>
@@ -128,7 +128,7 @@
</div> </div>
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> <label>{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label>
<input name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254">
<input name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255">
</div> </div>
<div class="inline field"> <div class="inline field">
<button class="ui green button"> <button class="ui green button">


+ 6
- 3
templates/repo/modelarts/trainjob/index.tmpl View File

@@ -34,6 +34,7 @@
<div class="ui blue small menu compact selectcloudbrain"> <div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> <a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="active item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a> <a class="active item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
</div> </div>
</div> </div>
<div class="column right aligned"> <div class="column right aligned">
@@ -141,11 +142,11 @@
<div class="ui compact buttons"> <div class="ui compact buttons">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
{{if .CanDel}} {{if .CanDel}}
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})">
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})">
{{$.i18n.Tr "repo.stop"}} {{$.i18n.Tr "repo.stop"}}
</a> </a>
{{else}} {{else}}
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic disabled button">
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic disabled button">
{{$.i18n.Tr "repo.stop"}} {{$.i18n.Tr "repo.stop"}}
</a> </a>
{{end}} {{end}}
@@ -262,7 +263,8 @@
const repoPath = job.dataset.repopath const repoPath = job.dataset.repopath
const versionname = job.dataset.version const versionname = job.dataset.version
const status_text = $(`#${jobID}-text`).text() const status_text = $(`#${jobID}-text`).text()
if(['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED'].includes(status_text)){
const finalState = ['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED','SUBMIT_MODEL_FAILED','DEPLOY_SERVICE_FAILED','CHECK_FAILED']
if(finalState.includes(status_text)){
return return
} }
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => { $.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => {
@@ -273,6 +275,7 @@
if (status != job.textContent.trim()) { if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status) $('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status) $('#' + jobID+ '-text').text(status)
finalState.includes(status) && $('#' + jobID + '-stop').removeClass('blue').addClass('disabled')


} }


+ 7
- 60
templates/repo/modelarts/trainjob/new.tmpl View File

@@ -80,12 +80,13 @@
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4> <h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4>
<div class="required unite min_title inline field"> <div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label> <label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label>
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" onkeyup="this.value=this.value.replace(/[, ]/g,'')" autofocus required maxlength="64">
<span class="tooltips" style="display: block;">请输入字母、数字、_和-,最长64个字符,且不能以中划线(-)结尾。</span>
</div> </div>
<div class="unite min_title inline field"> <div class="unite min_title inline field">
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label> <label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label>
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea>
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="255" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>


@@ -110,7 +111,6 @@
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
</select> </select>
</div> </div>


@@ -140,9 +140,9 @@
<div class="inline unite min_title field required"> <div class="inline unite min_title field required">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> <label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
{{if .bootFile}} {{if .bootFile}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="254" >
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="255" >
{{else}} {{else}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254" >
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255" >
{{end}} {{end}}
<span> <span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> <i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
@@ -226,7 +226,7 @@
<div class="ui labeled input" style="width: 5%;"> <div class="ui labeled input" style="width: 5%;">


<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254" value="1" readonly>
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255" value="1" readonly>


</div> </div>
@@ -312,7 +312,6 @@
}) })
}); });
console.log(parameters)
$('.ui.parameter.modal') $('.ui.parameter.modal')
.modal('hide'); .modal('hide');
for(var i = 2; i < parameters.length; i++){ for(var i = 2; i < parameters.length; i++){
@@ -379,65 +378,16 @@
$('select.dropdown') $('select.dropdown')
.dropdown(); .dropdown();


$('.ui.form')
.form({
on: 'blur',
inline:true,
fields: {
boot_file: {
identifier : 'boot_file',
rules: [
{
type: 'regExp[/.+\.py$/g]',
prompt : '启动文件必须为.py结尾'
}
]
},
job_name:{
identifier : 'job_name',
rules: [
{
type: 'regExp[/^[a-zA-Z0-9-_]{1,36}$/]',
prompt : '只包含大小写字母、数字、_和-,最长36个字符。'
}
]
},
attachment:{
identifier : 'attachment',
rules: [
{
type: 'empty',
prompt : '选择一个数据集'
}
]

},
work_server_number: {
identifier : 'work_server_number',
rules: [
{
type : 'integer[1..25]',
prompt : '计算节点需要在1-25之间,请您键入正确的值'
}
]
}
},
})



function validate(){ function validate(){
$('.ui.form') $('.ui.form')
.form({ .form({
on: 'blur', on: 'blur',
inline:true,
fields: { fields: {
boot_file: { boot_file: {
identifier : 'boot_file', identifier : 'boot_file',
rules: [ rules: [
{ {
type: 'regExp[/.+\.py$/g]', type: 'regExp[/.+\.py$/g]',
prompt : '启动文件必须为.py结尾'
} }
] ]
}, },
@@ -445,8 +395,7 @@
identifier : 'job_name', identifier : 'job_name',
rules: [ rules: [
{ {
type: 'regExp[/^[a-zA-Z0-9-_]{1,36}$/]',
prompt : '只包含大小写字母、数字、_和-,最长36个字符。'
type: 'regExp[/^[a-zA-Z0-9-_]{1,64}[^-]$/]',
} }
] ]
}, },
@@ -455,7 +404,6 @@
rules: [ rules: [
{ {
type: 'empty', type: 'empty',
prompt : '选择一个数据集'
} }
] ]


@@ -465,7 +413,6 @@
rules: [ rules: [
{ {
type : 'integer[1..25]', type : 'integer[1..25]',
prompt : '计算节点需要在1-25之间,请您键入正确的值'
} }
] ]
} }


+ 12
- 10
templates/repo/modelarts/trainjob/show.tmpl View File

@@ -175,7 +175,7 @@ td, th {
<div class="ui container"> <div class="ui container">
<h4 class="ui header" id="vertical-segment"> <h4 class="ui header" id="vertical-segment">
<div class="ui breadcrumb"> <div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/cloudbrain">
<a class="section" href="{{.RepoLink}}/debugjob?debugListType=all">
{{.i18n.Tr "repo.cloudbrain"}} {{.i18n.Tr "repo.cloudbrain"}}
</a> </a>
<div class="divider"> / </div> <div class="divider"> / </div>
@@ -196,25 +196,25 @@ td, th {
<span> <span>
<div style="float: right;"> <div style="float: right;">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
{{if and (.CanModify) (eq .Status "COMPLETED")}}
<a class="ti-action-menu-item" onclick="showcreate({{.}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{if and (.CanModify) (eq .Status "COMPLETED") ($.Permission.CanWrite $.UnitTypeModelManage) }}
<a class="ti-action-menu-item" id="{{.VersionName}}-create-model" onclick="showcreate({{.}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{else}} {{else}}
<a class="ti-action-menu-item disabled">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-create-model" onclick="showcreate({{.}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{end}} {{end}}
{{$.CsrfTokenHtml}}
{{if .CanModify}} {{if .CanModify}}
<a class="ti-action-menu-item" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> <a class="ti-action-menu-item" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a>
{{else}} {{else}}
<a class="ti-action-menu-item disabled" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> <a class="ti-action-menu-item disabled" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a>
{{end}} {{end}}
{{$.CsrfTokenHtml}}
{{if .CanDel}} {{if .CanDel}}
<a class="ti-action-menu-item {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{end}}" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> <a class="ti-action-menu-item {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{end}}" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a>
{{else}} {{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> <a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a>
{{end}} {{end}}
{{$.CsrfTokenHtml}}
{{if .CanDel}} {{if .CanDel}}
<a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a> <a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a>
{{else}} {{else}}
@@ -505,7 +505,7 @@ td, th {
</div> </div>
<div class="inline field" style="margin-left: 75px;"> <div class="inline field" style="margin-left: 75px;">
<button onclick="createModel()" id="submitId" type="button" class="ui create_train_job green button" style="position: absolute;">
<button onclick="createModel()" type="button" class="ui create_train_job green button" style="position: absolute;">
{{.i18n.Tr "repo.model.manage.sava_model"}} {{.i18n.Tr "repo.model.manage.sava_model"}}
</button> </button>
</div> </div>
@@ -522,7 +522,6 @@ td, th {


<script> <script>
$('.menu .item').tab() $('.menu .item').tab()

$(document).ready(function(){ $(document).ready(function(){
$('.ui.accordion').accordion({selector:{trigger:'.icon'}}); $('.ui.accordion').accordion({selector:{trigger:'.icon'}});
}); });
@@ -614,7 +613,7 @@ td, th {
var srcsize = parseFloat(value); var srcsize = parseFloat(value);
index=Math.floor(Math.log(srcsize)/Math.log(1024)); index=Math.floor(Math.log(srcsize)/Math.log(1024));
var size =srcsize/Math.pow(1024,index); var size =srcsize/Math.pow(1024,index);
size=size.toFixed(2);//保留的小数位数
size=size.toFixed(0);//保留的小数位数
return size+unitArr[index]; return size+unitArr[index];
} }
function loadJobStatus() { function loadJobStatus() {
@@ -644,6 +643,9 @@ td, th {
if(stopArray.includes(data.JobStatus)){ if(stopArray.includes(data.JobStatus)){
$('#'+versionname+'-stop').addClass('disabled') $('#'+versionname+'-stop').addClass('disabled')
} }
if(data.JobStatus==="COMPLETED"){
$('#'+versionname+'-create-model').removeClass('disabled').addClass('blue')
}
}).fail(function(err) { }).fail(function(err) {
console.log(err); console.log(err);
}); });


+ 4
- 4
templates/repo/modelarts/trainjob/version_new.tmpl View File

@@ -99,7 +99,7 @@
<div class="unite min_title inline field"> <div class="unite min_title inline field">
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label> <label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label>
<textarea style="width: 80%;" id="description" value="{{.description}}" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 256)">{{.description}}</textarea>
<textarea style="width: 80%;" id="description" value="{{.description}}" name="description" rows="3" maxlength="255" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 256)">{{.description}}</textarea>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>


@@ -152,9 +152,9 @@
<div class="inline unite min_title field required"> <div class="inline unite min_title field required">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label> <label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
{{if .boot_file}} {{if .boot_file}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.boot_file}}" tabindex="3" autofocus required maxlength="254" >
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.boot_file}}" tabindex="3" autofocus required maxlength="255" >
{{else}} {{else}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254" >
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255" >
{{end}} {{end}}
<span> <span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> <i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
@@ -245,7 +245,7 @@
<div class="ui labeled input" style="width: 5%;"> <div class="ui labeled input" style="width: 5%;">


<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254" value="{{.work_server_number}}" readonly>
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255" value="{{.work_server_number}}" readonly>


</div> </div>


+ 6
- 1
templates/repo/modelmanage/index.tmpl View File

@@ -23,7 +23,7 @@
<div class="column"></div> <div class="column"></div>
<div class="column right aligned"> <div class="column right aligned">
<!-- --> <!-- -->
<a class="ui button {{if .Permission.CanWrite $.UnitTypeCloudBrain}} green {{else}} disabled {{end}}" onclick="showcreate(this)">{{$.i18n.Tr "repo.model.manage.import_new_model"}}</a>
<a class="ui button {{if .Permission.CanWrite $.UnitTypeModelManage}} green {{else}} disabled {{end}}" onclick="showcreate(this)">{{$.i18n.Tr "repo.model.manage.import_new_model"}}</a>
</div> </div>
</div> </div>
{{if eq $.MODEL_COUNT 0}} {{if eq $.MODEL_COUNT 0}}
@@ -38,6 +38,10 @@
<div class="bgtask-content-txt">训练任务:您还没创建过训练任务,请先创建<a href="{{.RepoLink}}/modelarts/train-job">训练任务</a>。</div> <div class="bgtask-content-txt">训练任务:您还没创建过训练任务,请先创建<a href="{{.RepoLink}}/modelarts/train-job">训练任务</a>。</div>
{{end}} {{end}}
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> <div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div>
</div>
<div style="display: none;">
<div id="model_list"></div>
</div> </div>
</div> </div>
{{else}} {{else}}
@@ -90,6 +94,7 @@
</div> </div>
<div class="content content-padding"> <div class="content content-padding">
<form id="formId" method="POST" class="ui form"> <form id="formId" method="POST" class="ui form">
<input type="hidden" name="initModel" value="{{$.MODEL_COUNT}}">
<div class="ui error message"> <div class="ui error message">
<!-- <p>asdasdasd</p> --> <!-- <p>asdasdasd</p> -->
</div> </div>


+ 1
- 1
templates/repo/pulls/fork.tmpl View File

@@ -53,7 +53,7 @@
</div> </div>
<div class="inline field {{if .Err_Description}}error{{end}}"> <div class="inline field {{if .Err_Description}}error{{end}}">
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> <label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
<textarea id="description" name="description" maxlength="254">{{.description}}</textarea>
<textarea id="description" name="description" maxlength="255">{{.description}}</textarea>
</div> </div>


<div class="inline field"> <div class="inline field">


+ 2
- 2
templates/repo/release/new.tmpl View File

@@ -19,7 +19,7 @@
{{if .PageIsEditRelease}} {{if .PageIsEditRelease}}
<b>{{.tag_name}}</b><span class="at">@</span><strong>{{.tag_target}}</strong> <b>{{.tag_name}}</b><span class="at">@</span><strong>{{.tag_target}}</strong>
{{else}} {{else}}
<input id="tag-name" name="tag_name" value="{{.tag_name}}" placeholder="{{.i18n.Tr "repo.release.tag_name"}}" autofocus required maxlength="254">
<input id="tag-name" name="tag_name" value="{{.tag_name}}" placeholder="{{.i18n.Tr "repo.release.tag_name"}}" autofocus required maxlength="255">
<span class="at">@</span> <span class="at">@</span>
<div class="ui selection dropdown"> <div class="ui selection dropdown">
<input type="hidden" name="tag_target" value="{{.tag_target}}"/> <input type="hidden" name="tag_target" value="{{.tag_target}}"/>
@@ -42,7 +42,7 @@
<div class="eleven wide column"> <div class="eleven wide column">
<div class="field {{if .Err_Title}}error{{end}}"> <div class="field {{if .Err_Title}}error{{end}}">
<label>{{.i18n.Tr "repo.release.title"}}</label> <label>{{.i18n.Tr "repo.release.title"}}</label>
<input name="title" placeholder="{{.i18n.Tr "repo.release.title"}}" value="{{.title}}" autofocus required maxlength="254">
<input name="title" placeholder="{{.i18n.Tr "repo.release.title"}}" value="{{.title}}" autofocus required maxlength="255">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "repo.release.content"}}</label> <label>{{.i18n.Tr "repo.release.content"}}</label>


+ 4
- 4
templates/repo/settings/options.tmpl View File

@@ -41,7 +41,7 @@
{{end}} {{end}}
<div class="field {{if .Err_Description}}error{{end}}"> <div class="field {{if .Err_Description}}error{{end}}">
<label for="description">{{$.i18n.Tr "repo.repo_desc"}}</label> <label for="description">{{$.i18n.Tr "repo.repo_desc"}}</label>
<textarea id="description" name="description" rows="2" maxlength="254">{{.Repository.Description}}</textarea>
<textarea id="description" name="description" rows="2" maxlength="255">{{.Repository.Description}}</textarea>
</div> </div>
<div class="field {{if .Err_Website}}error{{end}}"> <div class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.i18n.Tr "repo.settings.site"}}</label> <label for="website">{{.i18n.Tr "repo.settings.site"}}</label>
@@ -152,7 +152,7 @@
{{$isModelMangeEnabled := .Repository.UnitEnabled $.UnitTypeModelManage }} {{$isModelMangeEnabled := .Repository.UnitEnabled $.UnitTypeModelManage }}
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.model_manager"}}</label> <label>{{.i18n.Tr "repo.model_manager"}}</label>
<div class="ui checkbox">
<div class="ui checkbox {{if ne $.MODEL_COUNT 0}}disabled{{end}}">
<input class="enable-system" name="enable_model_manager" type="checkbox" {{if $isModelMangeEnabled}}checked{{end}}> <input class="enable-system" name="enable_model_manager" type="checkbox" {{if $isModelMangeEnabled}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.model_desc"}}</label> <label>{{.i18n.Tr "repo.settings.model_desc"}}</label>
</div> </div>
@@ -160,7 +160,7 @@
{{$isCloudBrainEnabled := .Repository.UnitEnabled $.UnitTypeCloudBrain }} {{$isCloudBrainEnabled := .Repository.UnitEnabled $.UnitTypeCloudBrain }}
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.cloudbrain"}}</label> <label>{{.i18n.Tr "repo.cloudbrain"}}</label>
<div class="ui checkbox">
<div class="ui checkbox {{if ne $.jobCount 0}}disabled{{end}}">
<input class="enable-system" name="enable_cloud_brain" type="checkbox" {{if $isCloudBrainEnabled}}checked{{end}}> <input class="enable-system" name="enable_cloud_brain" type="checkbox" {{if $isCloudBrainEnabled}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.cloudbrain_desc"}}</label> <label>{{.i18n.Tr "repo.settings.cloudbrain_desc"}}</label>
</div> </div>
@@ -625,4 +625,4 @@
{{end}} {{end}}
{{end}} {{end}}


{{template "base/footer" .}}
{{template "base/footer" .}}

+ 1
- 1
templates/user/settings/profile.tmpl View File

@@ -34,7 +34,7 @@
</div> </div>
<div class="field {{if .Err_Description}}error{{end}}"> <div class="field {{if .Err_Description}}error{{end}}">
<label for="description">{{$.i18n.Tr "user.user_bio"}}</label> <label for="description">{{$.i18n.Tr "user.user_bio"}}</label>
<textarea id="description" name="description" rows="2" maxlength="254">{{.SignedUser.Description}}</textarea>
<textarea id="description" name="description" rows="2" maxlength="255">{{.SignedUser.Description}}</textarea>
</div> </div>
<div class="field {{if .Err_Website}}error{{end}}"> <div class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.i18n.Tr "settings.website"}}</label> <label for="website">{{.i18n.Tr "settings.website"}}</label>


+ 9
- 1
web_src/js/components/Model.vue View File

@@ -106,7 +106,7 @@
<div class="space-around"> <div class="space-around">
<a :style="{visibility:!scope.row.Children ? 'visible':'hidden'}" :class="{'disabled':!scope.row.IsCanOper}" @click="showcreateVue(scope.row.Name,scope.row.Version,scope.row.Label)">创建新版本</a> <a :style="{visibility:!scope.row.Children ? 'visible':'hidden'}" :class="{'disabled':!scope.row.IsCanOper}" @click="showcreateVue(scope.row.Name,scope.row.Version,scope.row.Label)">创建新版本</a>
<a :href="loadhref+scope.row.ID" :class="{'disabled':!scope.row.IsCanOper}">下载</a> <a :href="loadhref+scope.row.ID" :class="{'disabled':!scope.row.IsCanOper}">下载</a>
<a :class="{'disabled':!scope.row.IsCanOper}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a>
<a :class="{'disabled':!scope.row.IsCanDelete}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a>
</div> </div>
</template> </template>
@@ -263,8 +263,10 @@ export default {
let cName = $("input[name='Name']").val() let cName = $("input[name='Name']").val()
let version = $("input[name='Version']").val() let version = $("input[name='Version']").val()
let data = $("#formId").serialize() let data = $("#formId").serialize()
const initModel = $("input[name='initModel']").val()
let url_href = version === '0.0.1' ? context.url_create_newModel : context.url_create_newVersion let url_href = version === '0.0.1' ? context.url_create_newModel : context.url_create_newVersion
$("#mask").css({"display":"block","z-index":"9999"}) $("#mask").css({"display":"block","z-index":"9999"})

$.ajax({ $.ajax({
url:url_href, url:url_href,
type:'POST', type:'POST',
@@ -273,6 +275,9 @@ export default {
// context.loadrefresh1(row) // context.loadrefresh1(row)
context.getModelList() context.getModelList()
$('.ui.modal.second').modal('hide') $('.ui.modal.second').modal('hide')
if(initModel==='0'){
location.reload()
}
}, },
error: function(xhr){ error: function(xhr){
// 隐藏 loading // 隐藏 loading
@@ -360,6 +365,9 @@ export default {
this.tableData[i].hasChildren = res.data.data[i].VersionCount===1 ? false : true this.tableData[i].hasChildren = res.data.data[i].VersionCount===1 ? false : true
} }
this.totalNum = res.data.count this.totalNum = res.data.count
// if(res.data.count===1 && res.data.data[0].VersionCount===1){
// location.reload()
// }
}) })
}catch (e) { }catch (e) {
console.log(e) console.log(e)


+ 1
- 1
web_src/js/index.js View File

@@ -4128,7 +4128,7 @@ function initDropDown() {
} }


//云脑提示 //云脑提示
$('.question.circle.icon').hover(function(){
$('.question.circle.icon.cloudbrain-question').hover(function(){
$(this).popup('show') $(this).popup('show')
$('.ui.popup.mini.top.center').css({"border-color":'rgba(50, 145, 248, 100)',"color":"rgba(3, 102, 214, 100)","border-radius":"5px","border-shadow":"none"}) $('.ui.popup.mini.top.center').css({"border-color":'rgba(50, 145, 248, 100)',"color":"rgba(3, 102, 214, 100)","border-radius":"5px","border-shadow":"none"})
}); });


Loading…
Cancel
Save