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.4
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
}

func GetLast20PublicFeeds() ([]*Action, error) {
func GetLast20PublicFeeds(opTypes []int) ([]*Action, error) {
cond := builder.NewCond()
cond = cond.And(builder.Eq{"is_private": 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)



+ 1
- 0
models/ai_model_manage.go View File

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

type AiModelQueryOptions struct {


+ 50
- 7
models/cloudbrain.go View File

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

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

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

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

User *User `xorm:"-"`
Repo *Repository `xorm:"-"`
}
@@ -207,7 +214,7 @@ type CloudbrainsOptions struct {
CloudbrainIDs []int64
// JobStatus CloudbrainStatus
Type int
JobType string
JobTypes []string
VersionName string
IsLatestVersion string
JobTypeNot bool
@@ -644,6 +651,25 @@ type Config struct {
Flavor Flavor `json:"flavor"`
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 {
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 {
cond = cond.And(
builder.Neq{"cloudbrain.job_type": opts.JobType},
builder.NotIn("cloudbrain.job_type", opts.JobTypes),
)
} else {
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(
builder.Eq{"job_type": "TRAIN"},
)

cloudbrains := make([]*CloudbrainInfo, 0)
if err := sess.Select("job_id,job_name").Table(&Cloudbrain{}).Where(cond).OrderBy("created_unix DESC").
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(
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
}

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) {
sess := x.NewSession()
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"`
}

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 {
return validate(errs, ctx.Data, f, ctx.Locale)
}

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

@@ -38,6 +38,7 @@ const (
// "]}"
CodePath = "/code/"
OutputPath = "/output/"
ResultPath = "/result/"
LogPath = "/log/"
JobPath = "/job/"
OrderDesc = "desc" //向下查询
@@ -45,6 +46,8 @@ const (
Lines = 500
TrainUrl = "train_url"
DataUrl = "data_url"
ResultUrl = "result_url"
CkptUrl = "ckpt_url"
PerPage = 10
IsLatestVersion = "1"
NotLatestVersion = "0"
@@ -113,6 +116,36 @@ type GenerateTrainJobVersionReq struct {
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 {
Version []struct {
ID int `json:"id"`
@@ -329,12 +362,14 @@ func GenerateTrainJobVersion(ctx *context.Context, req *GenerateTrainJobReq, job
return err
}

var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeTrain))
repo := ctx.Repo.Repository
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 {
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)
VersionOutputPath = "V" + talVersionCountToString
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
}

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"`
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
func ObsHasObject(path string) (bool, error) {
@@ -333,7 +340,8 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er
input.MaxKeys = 100
input.Prefix = prefix
index := 1
fileInfos := make([]FileInfo, 0)
fileInfoList := FileInfoList{}

prefixLen := len(prefix)
log.Info("prefix=" + input.Prefix)
for {
@@ -358,7 +366,7 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er
IsDir: isDir,
ParenDir: "",
}
fileInfos = append(fileInfos, fileInfo)
fileInfoList = append(fileInfoList, fileInfo)
}
if output.IsTruncated {
input.Marker = output.NextMarker
@@ -373,13 +381,14 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er
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.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, "/")
output, err := ObsCli.ListObjects(input)
fileInfos := make([]FileInfo, 0)
@@ -401,7 +410,7 @@ func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error
nextParentDir = parentDir + "/" + fileName
}

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


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

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

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_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]
repos = Repositories
select_repos = Select the project
users = Users
organizations = Organizations
images = CloudImages
images = Cloudbrain Mirror
search = Search
code = Code
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?

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_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?
@@ -869,6 +869,7 @@ modelarts.notebook=Debug Task
modelarts.train_job=Train Task
modelarts.train_job.new_debug= New Debug 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.new=New train Task
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.train_job.compute_node=Compute Node
modelarts.create_model = Create Model
modelarts.model_label=Model Label
modelarts.infer_dataset = Inference Dataset


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.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.create_error=Equal Name and Version has existed.
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.project = Repositories
head.openi = OpenI
head.openi.repo = OpenI Projects
head.dataset = Datasets
foot.council = Council
foot.technical_committee = Technical Committee


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

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

@@ -876,9 +876,10 @@ modelarts.notebook=调试任务
modelarts.train_job=训练任务
modelarts.train_job.new_debug=新建调试任务
modelarts.train_job.new_train=新建训练任务
modelarts.train_job.new_infer=新建推理任务
modelarts.train_job.config=配置信息
modelarts.train_job.new=新建训练任务
modelarts.train_job.new_place=描述字数不超过256个字符
modelarts.train_job.new_place=描述字数不超过255个字符
modelarts.model_name=模型名称
modelarts.model_size=模型大小
modelarts.import_model=导入模型
@@ -888,6 +889,8 @@ modelarts.current_version=当前版本
modelarts.parent_version=父版本
modelarts.run_version=运行版本
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.save_helper=保存当前作业的配置参数,后续您可以使用已保存的配置参数快速创建训练作业。
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.parameter_description=任务参数描述
modelarts.log=日志
@@ -939,6 +942,14 @@ modelarts.train_job_para_admin=任务参数管理
modelarts.train_job_para.edit=编辑
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.create_error=相同的名称和版本的模型已经存在。
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);

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

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

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

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

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

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"]]);


function getRepoOrOrg(key,isZhLang){
function getRepoOrOrg(key,isZhLang,numbers=1){
if(numbers > 1){
key+="1";
}
if(isZhLang){
return repoAndOrgZH[key];
}else{
@@ -436,7 +451,7 @@ function displayOrg(json){
html += " <img class=\"ui image\" src=\"" + record["Avatar"] + "\">";
html += " <div class=\"content nowrap\">";
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>";


+ 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.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))
}, 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 versionName = ctx.Query("version_name")
// var logFileName = ctx.Query("file_name")
var baseLine = ctx.Query("base_line")
var order = ctx.Query("order")
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
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 {
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())
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 {
log.Info("get TrainJobListModel failed:", 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 !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
}
}
@@ -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 {
log.Info("delete model start. id=" + id)
model, err := models.QueryModelById(id)
if !isCanDeleteOrDownload(ctx, model) {
if !isCanDelete(ctx, model.UserId) {
return errors.New(ctx.Tr("repo.model_noright"))
}
if err == nil {
@@ -278,8 +270,8 @@ func DownloadMultiModelFile(ctx *context.Context) {
ctx.ServerError("no such model:", err)
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
}

@@ -371,7 +363,16 @@ func DownloadSingleModelFile(ctx *context.Context) {
parentDir := ctx.Query("parentDir")
fileName := ctx.Query("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 != "" {
body, err := storage.ObsDownloadAFile(setting.Bucket, path)
if err != nil {
@@ -414,6 +415,8 @@ func ShowModelInfo(ctx *context.Context) {
ctx.Data["ID"] = ctx.Query("ID")
ctx.Data["name"] = ctx.Query("name")
ctx.Data["isModelManage"] = true
ctx.Data["ModelManageAccess"] = ctx.Repo.CanWrite(models.UnitTypeModelManage)

ctx.HTML(200, tplModelInfo)
}

@@ -426,6 +429,7 @@ func ShowSingleModel(ctx *context.Context) {
userIds := make([]int64, len(models))
for i, model := range models {
model.IsCanOper = isOper(ctx, model.UserId)
model.IsCanDelete = isCanDelete(ctx, model.UserId)
userIds[i] = model.UserId
}
userNameMap := queryUserName(userIds)
@@ -468,6 +472,7 @@ func ShowOneVersionOtherModel(ctx *context.Context) {
userIds := make([]int64, len(aimodels))
for i, model := range aimodels {
model.IsCanOper = isOper(ctx, model.UserId)
model.IsCanDelete = isCanDelete(ctx, model.UserId)
userIds[i] = model.UserId
}
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
Type := -1
_, count, _ := models.QueryModel(&models.AiModelQueryOptions{
@@ -501,10 +505,15 @@ func ShowModelTemplate(ctx *context.Context) {
New: MODEL_LATEST,
})
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)
log.Info("query train count=" + fmt.Sprint(trainCount))

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

@@ -603,8 +626,9 @@ func ModifyModelInfo(ctx *context.Context) {
ctx.ServerError("no such model:", err)
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
}



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

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

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

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
@@ -37,6 +40,10 @@ const (
tplModelArtsTrainJobNew base.TplName = "repo/modelarts/trainjob/new"
tplModelArtsTrainJobShow base.TplName = "repo/modelarts/trainjob/show"
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) {
@@ -49,12 +56,15 @@ func DebugJobIndex(ctx *context.Context) {
page = 1
}
debugType := modelarts.DebugType
jobTypeNot := false
if debugListType == models.GPUResource {
debugType = models.TypeCloudBrainOne
} else if debugListType == models.NPUResource {
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{
ListOptions: models.ListOptions{
Page: page,
@@ -62,8 +72,8 @@ func DebugJobIndex(ctx *context.Context) {
},
RepoID: repo.ID,
Type: debugType,
JobTypeNot: true,
JobType: string(models.JobTypeTrain),
JobTypeNot: jobTypeNot,
JobTypes: jobTypes,
})
if err != nil {
ctx.ServerError("Get debugjob faild:", err)
@@ -367,6 +377,8 @@ func TrainJobIndex(ctx *context.Context) {
page = 1
}

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

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

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

//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())
commitID, _ := gitRepo.GetBranchCommitID(branch_name)
@@ -973,7 +978,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
ctx.ServerError("GetCloudbrainByJobIDAndIsLatestVersion faild:", err)
return
}
VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(latestTask.TotalVersionCount + 1)
VersionOutputPath := modelarts.GetOutputPathByCount(latestTask.TotalVersionCount + 1)

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

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

//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())
commitID, _ := gitRepo.GetBranchCommitID(branch_name)
@@ -1264,6 +1268,42 @@ func paramCheckCreateTrainJob(form auth.CreateModelArtsTrainJobForm) error {
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) {
ctx.Data["PageIsCloudBrain"] = true
var jobID = ctx.Params(":jobid")
@@ -1273,6 +1313,9 @@ func TrainJobShow(ctx *context.Context) {
if page <= 0 {
page = 1
}

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

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

var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeTrain))
VersionListTasks, _, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeTrain),
JobTypes: jobTypes,
JobID: jobID,
})
if err != nil {
@@ -1518,6 +1563,427 @@ func getConfigList(perPage, page int, sortBy, order, searchContent, configType s
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) {
var (
err error
@@ -1546,6 +2012,31 @@ func ModelDownload(ctx *context.Context) {
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 {
//delete local
localJobPath := setting.JobPath + jobName
@@ -1563,3 +2054,82 @@ func DeleteJobStorage(jobName string) error {

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

var error_projects = make([]string, 0)
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
repoGitStat, err := models.GetRepoKPIStats(repo)
if err != nil {
log.Error("GetRepoKPIStats failed: %s", getDistinctProjectName(repo))
log.Error("GetRepoKPIStats failed: %s", projectName)
} else {
numDevMonths = repoGitStat.DevelopAge
numKeyContributor = repoGitStat.KeyContributors
@@ -79,26 +81,26 @@ func RepoStatisticDaily(date string) {
var numVersions int64
numVersions, err = models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{})
if err != nil {
log.Error("GetReleaseCountByRepoID failed(%s): %v", getDistinctProjectName(repo), err)
log.Error("GetReleaseCountByRepoID failed(%s): %v", projectName, err)
}

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

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

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

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

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
}

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

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

//radar map
log.Info("begin statistic radar")
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["PageIsSettingsOptions"] = true
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
SetModelCount(ctx)
SetJobCount(ctx)
ctx.HTML(200, tplSettingsOptions)
}

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

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

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.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())

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


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

@@ -10,6 +10,8 @@ import (
"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 {
Clients *orderedmap.OrderedMap
Register chan *Client
@@ -47,13 +49,15 @@ func (h *ClientsManager) Run() {
close(client.Send)
}
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:
@@ -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() {
actions, err := models.GetLast20PublicFeeds()
actions, err := models.GetLast20PublicFeeds(opTypes)
if err == nil {
for i := len(actions) - 1; i >= 0; i-- {



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

@@ -43,7 +43,7 @@
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{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>
{{else if .IsLandingPageHome}}


+ 1
- 1
templates/home.tmpl View File

@@ -57,7 +57,7 @@
<div class="leftline02-2"></div>
<div class="ui center homepro-tit am-mb-20">
<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 class="homepro-list">


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

@@ -147,7 +147,7 @@
</div>
<div class="inline required field">
<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 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;">
<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>
<datalist class="ui search" id="cloudbrain_image" style='width:385px;' name="image">
{{range .images}}
@@ -225,27 +225,27 @@

<div class="inline required field">
<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 class="inline required field">
<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 class="inline required field">
<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 class="inline required field cloudbrain_benchmark">
<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 class="inline required field cloudbrain_snn4imagenet">
<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 class="inline required field cloudbrain_brainscore">
<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 class="inline required field" hidden>
<label>启动命令</label>


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

@@ -54,7 +54,7 @@
</div>
<div class="inline field {{if .Err_Description}}error{{end}}">
<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 class="inline field">
<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">
<label class="d-block">{{.i18n.Tr "dataset.title"}}</label>
<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>
<label class="d-block">{{.i18n.Tr "dataset.description"}}</label>
<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">
<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/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
</div>
</div>
<div class="column right aligned">
@@ -430,12 +431,12 @@
<div class="inline required field dis">
<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 class="inline field">
<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 class="ui divider"></div>
<div class="inline field">
@@ -488,6 +489,7 @@

<script>
// 调试和评分新开窗口
console.log({{.Tasks}})
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
let url={{.RepoLink}}
let getParam=getQueryVariable('debugListType')
@@ -606,7 +608,7 @@
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
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())) {
return


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

@@ -15,7 +15,7 @@
<div class="ui segment content">
<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}}
<div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div>
{{end}}


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

@@ -3,7 +3,7 @@
<h1 class="twelve wide column">
<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">
<input value="{{.Issue.Title}}" maxlength="254">
<input value="{{.Issue.Title}}" maxlength="255">
</div>
</h1>
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}


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

@@ -122,7 +122,7 @@
</div>
<div class="inline field {{if .Err_Description}}error{{end}}">
<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 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 class="inline required field">
<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 class="inline field">
@@ -64,11 +64,11 @@

<div class="inline required field">
<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 class="inline required field">
<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 class="inline required field">
<label>规格</label>
@@ -81,11 +81,11 @@
</div>
<div class="inline required field">
<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 class="inline field">
<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 class="inline field">
<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>
<div class="required field">
<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 class="field">
<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>
<h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}</h4>
<div class="required field">
@@ -52,7 +52,7 @@
</div>
<div class="inline required field">
<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>
<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>
@@ -128,7 +128,7 @@
</div>
<div class="inline required field">
<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 class="inline field">
<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">
<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="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
</div>
</div>
<div class="column right aligned">
@@ -141,11 +142,11 @@
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{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"}}
</a>
{{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"}}
</a>
{{end}}
@@ -262,7 +263,8 @@
const repoPath = job.dataset.repopath
const versionname = job.dataset.version
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
}
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => {
@@ -273,6 +275,7 @@
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(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>
<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}}" 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 class="unite min_title inline field">
<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 class="ui divider"></div>

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

@@ -140,9 +140,9 @@
<div class="inline unite min_title field required">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
{{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}}
<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}}
<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>
@@ -226,7 +226,7 @@
<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>
@@ -312,7 +312,6 @@
})
});
console.log(parameters)
$('.ui.parameter.modal')
.modal('hide');
for(var i = 2; i < parameters.length; i++){
@@ -379,65 +378,16 @@
$('select.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(){
$('.ui.form')
.form({
on: 'blur',
inline:true,
fields: {
boot_file: {
identifier : 'boot_file',
rules: [
{
type: 'regExp[/.+\.py$/g]',
prompt : '启动文件必须为.py结尾'
}
]
},
@@ -445,8 +395,7 @@
identifier : 'job_name',
rules: [
{
type: 'regExp[/^[a-zA-Z0-9-_]{1,36}$/]',
prompt : '只包含大小写字母、数字、_和-,最长36个字符。'
type: 'regExp[/^[a-zA-Z0-9-_]{1,64}[^-]$/]',
}
]
},
@@ -455,7 +404,6 @@
rules: [
{
type: 'empty',
prompt : '选择一个数据集'
}
]

@@ -465,7 +413,6 @@
rules: [
{
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">
<h4 class="ui header" id="vertical-segment">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/cloudbrain">
<a class="section" href="{{.RepoLink}}/debugjob?debugListType=all">
{{.i18n.Tr "repo.cloudbrain"}}
</a>
<div class="divider"> / </div>
@@ -196,25 +196,25 @@ td, th {
<span>
<div style="float: right;">
{{$.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}}
<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}}
{{$.CsrfTokenHtml}}
{{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>
{{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>
{{end}}
{{$.CsrfTokenHtml}}
{{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>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a>
{{end}}
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a>
{{else}}
@@ -505,7 +505,7 @@ td, th {
</div>
<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"}}
</button>
</div>
@@ -522,7 +522,6 @@ td, th {

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

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

@@ -152,9 +152,9 @@
<div class="inline unite min_title field required">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
{{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}}
<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}}
<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>
@@ -245,7 +245,7 @@
<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>


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

@@ -23,7 +23,7 @@
<div class="column"></div>
<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>
{{if eq $.MODEL_COUNT 0}}
@@ -38,6 +38,10 @@
<div class="bgtask-content-txt">训练任务:您还没创建过训练任务,请先创建<a href="{{.RepoLink}}/modelarts/train-job">训练任务</a>。</div>
{{end}}
<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>
{{else}}
@@ -90,6 +94,7 @@
</div>
<div class="content content-padding">
<form id="formId" method="POST" class="ui form">
<input type="hidden" name="initModel" value="{{$.MODEL_COUNT}}">
<div class="ui error message">
<!-- <p>asdasdasd</p> -->
</div>


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

@@ -53,7 +53,7 @@
</div>
<div class="inline field {{if .Err_Description}}error{{end}}">
<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 class="inline field">


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

@@ -19,7 +19,7 @@
{{if .PageIsEditRelease}}
<b>{{.tag_name}}</b><span class="at">@</span><strong>{{.tag_target}}</strong>
{{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>
<div class="ui selection dropdown">
<input type="hidden" name="tag_target" value="{{.tag_target}}"/>
@@ -42,7 +42,7 @@
<div class="eleven wide column">
<div class="field {{if .Err_Title}}error{{end}}">
<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 class="field">
<label>{{.i18n.Tr "repo.release.content"}}</label>


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

@@ -41,7 +41,7 @@
{{end}}
<div class="field {{if .Err_Description}}error{{end}}">
<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 class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.i18n.Tr "repo.settings.site"}}</label>
@@ -152,7 +152,7 @@
{{$isModelMangeEnabled := .Repository.UnitEnabled $.UnitTypeModelManage }}
<div class="inline field">
<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}}>
<label>{{.i18n.Tr "repo.settings.model_desc"}}</label>
</div>
@@ -160,7 +160,7 @@
{{$isCloudBrainEnabled := .Repository.UnitEnabled $.UnitTypeCloudBrain }}
<div class="inline field">
<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}}>
<label>{{.i18n.Tr "repo.settings.cloudbrain_desc"}}</label>
</div>
@@ -625,4 +625,4 @@
{{end}}
{{end}}

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

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

@@ -34,7 +34,7 @@
</div>
<div class="field {{if .Err_Description}}error{{end}}">
<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 class="field {{if .Err_Website}}error{{end}}">
<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">
<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 :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>
</template>
@@ -263,8 +263,10 @@ export default {
let cName = $("input[name='Name']").val()
let version = $("input[name='Version']").val()
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
$("#mask").css({"display":"block","z-index":"9999"})

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