Browse Source

Merge pull request '提交模型管理最终代码,包括前后端' (#1036) from zouap into V20211213

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/1036
tags/v1.21.12.1^2
lewis 3 years ago
parent
commit
3f0a892f7c
30 changed files with 2827 additions and 754 deletions
  1. +203
    -0
      models/ai_model_manage.go
  2. +42
    -0
      models/cloudbrain.go
  3. +1
    -0
      models/models.go
  4. +6
    -0
      models/repo.go
  5. +16
    -0
      models/repo_unit.go
  6. +14
    -0
      models/unit.go
  7. +1
    -0
      modules/auth/repo_form.go
  8. +1
    -0
      modules/context/repo.go
  9. +207
    -25
      modules/storage/obs.go
  10. +24
    -0
      options/locale/locale_en-US.ini
  11. +21
    -0
      options/locale/locale_zh-CN.ini
  12. +513
    -0
      routers/repo/ai_model_manage.go
  13. +6
    -6
      routers/repo/cloudbrain.go
  14. +3
    -34
      routers/repo/dir.go
  15. +1
    -1
      routers/repo/http.go
  16. +12
    -0
      routers/repo/setting.go
  17. +19
    -0
      routers/routes/routes.go
  18. +575
    -0
      templates/repo/debugjob/index.tmpl
  19. +6
    -81
      templates/repo/header.tmpl
  20. +0
    -181
      templates/repo/modelarts/notebook/index.tmpl
  21. +2
    -83
      templates/repo/modelarts/notebook/new.tmpl
  22. +0
    -178
      templates/repo/modelarts/trainjob/index.tmpl
  23. +1
    -81
      templates/repo/modelarts/trainjob/new.tmpl
  24. +0
    -81
      templates/repo/modelarts/trainjob/version_new.tmpl
  25. +299
    -0
      templates/repo/modelmanage/index.tmpl
  26. +218
    -0
      templates/repo/modelmanage/showinfo.tmpl
  27. +8
    -1
      templates/repo/settings/options.tmpl
  28. +427
    -0
      web_src/js/components/Model.vue
  29. +18
    -2
      web_src/js/index.js
  30. +183
    -0
      web_src/less/openi.less

+ 203
- 0
models/ai_model_manage.go View File

@@ -0,0 +1,203 @@
package models

import (
"fmt"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
"xorm.io/xorm"
)

type AiModelManage struct {
ID string `xorm:"pk"`
Name string `xorm:"NOT NULL"`
Version string `xorm:"NOT NULL"`
VersionCount int `xorm:"NOT NULL DEFAULT 0"`
New int `xorm:"NOT NULL"`
Type int `xorm:"NOT NULL"`
Size int64 `xorm:"NOT NULL"`
Description string `xorm:"varchar(2000)"`
Label string `xorm:"varchar(1000)"`
Path string `xorm:"varchar(400) NOT NULL"`
DownloadCount int `xorm:"NOT NULL DEFAULT 0"`
Engine int64 `xorm:"NOT NULL DEFAULT 0"`
Status int `xorm:"NOT NULL DEFAULT 0"`
Accuracy string `xorm:"varchar(1000)"`
AttachmentId string `xorm:"NULL"`
RepoId int64 `xorm:"NULL"`
CodeBranch string `xorm:"varchar(400) NULL"`
CodeCommitID string `xorm:"NULL"`
UserId int64 `xorm:"NOT NULL"`
UserName string `xorm:"NULL"`
UserRelAvatarLink string `xorm:"NULL"`
TrainTaskInfo string `xorm:"text NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
IsCanOper bool
}

type AiModelQueryOptions struct {
ListOptions
RepoID int64 // include all repos if empty
UserID int64
ModelID string
SortType string
New int
// JobStatus CloudbrainStatus
Type int
}

func SaveModelToDb(model *AiModelManage) error {
sess := x.NewSession()
defer sess.Close()

re, err := sess.Insert(model)
if err != nil {
log.Info("insert error." + err.Error())
return err
}
log.Info("success to save db.re=" + fmt.Sprint((re)))
return nil
}

func QueryModelById(id string) (*AiModelManage, error) {
sess := x.NewSession()
defer sess.Close()
sess.Select("*").Table("ai_model_manage").
Where("id='" + id + "'")
aiModelManageList := make([]*AiModelManage, 0)
err := sess.Find(&aiModelManageList)
if err == nil {
if len(aiModelManageList) == 1 {
return aiModelManageList[0], nil
}
}
return nil, err
}

func DeleteModelById(id string) error {
sess := x.NewSession()
defer sess.Close()

re, err := sess.Delete(&AiModelManage{
ID: id,
})
if err != nil {
return err
}
log.Info("success to delete from db.re=" + fmt.Sprint((re)))
return nil

}

func ModifyModelDescription(id string, description string) error {
var sess *xorm.Session
sess = x.ID(id)
defer sess.Close()
re, err := sess.Cols("description").Update(&AiModelManage{
Description: description,
})
if err != nil {
return err
}
log.Info("success to update description from db.re=" + fmt.Sprint((re)))
return nil
}

func ModifyModelNewProperty(id string, new int, versioncount int) error {
var sess *xorm.Session
sess = x.ID(id)
defer sess.Close()
re, err := sess.Cols("new", "version_count").Update(&AiModelManage{
New: new,
VersionCount: versioncount,
})
if err != nil {
return err
}
log.Info("success to update new property from db.re=" + fmt.Sprint((re)))
return nil
}

func ModifyModelDownloadCount(id string) error {
sess := x.NewSession()
defer sess.Close()
if _, err := sess.Exec("UPDATE `ai_model_manage` SET download_count = download_count + 1 WHERE id = ?", id); err != nil {
return err
}

return nil
}

func QueryModelByName(name string, repoId int64) []*AiModelManage {
sess := x.NewSession()
defer sess.Close()
sess.Select("*").Table("ai_model_manage").
Where("name='" + name + "' and repo_id=" + fmt.Sprint(repoId)).OrderBy("version desc")
aiModelManageList := make([]*AiModelManage, 0)
sess.Find(&aiModelManageList)
return aiModelManageList
}

func QueryModel(opts *AiModelQueryOptions) ([]*AiModelManage, int64, error) {
sess := x.NewSession()
defer sess.Close()

var cond = builder.NewCond()
if opts.RepoID > 0 {
cond = cond.And(
builder.Eq{"ai_model_manage.repo_id": opts.RepoID},
)
}

if opts.UserID > 0 {
cond = cond.And(
builder.Eq{"ai_model_manage.user_id": opts.UserID},
)
}

if opts.New >= 0 {
cond = cond.And(
builder.Eq{"ai_model_manage.new": opts.New},
)
}

if len(opts.ModelID) > 0 {
cond = cond.And(
builder.Eq{"ai_model_manage.id": opts.ModelID},
)
}

if (opts.Type) >= 0 {
cond = cond.And(
builder.Eq{"ai_model_manage.type": opts.Type},
)
}

count, err := sess.Where(cond).Count(new(AiModelManage))
if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err)
}

if opts.Page >= 0 && opts.PageSize > 0 {
var start int
if opts.Page == 0 {
start = 0
} else {
start = (opts.Page - 1) * opts.PageSize
}
sess.Limit(opts.PageSize, start)
}

sess.OrderBy("ai_model_manage.created_unix DESC")
aiModelManages := make([]*AiModelManage, 0, setting.UI.IssuePagingNum)
if err := sess.Table("ai_model_manage").Where(cond).
Find(&aiModelManages); err != nil {
return nil, 0, fmt.Errorf("Find: %v", err)
}
sess.Close()

return aiModelManages, count, nil
}

+ 42
- 0
models/cloudbrain.go View File

@@ -929,6 +929,48 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
return cloudbrains, count, nil
}

func QueryModelTrainJobVersionList(jobId string) ([]*CloudbrainInfo, int, error) {
sess := x.NewSession()
defer sess.Close()

var cond = builder.NewCond()

cond = cond.And(
builder.Eq{"cloudbrain.job_id": jobId},
)
cond = cond.And(
builder.Eq{"cloudbrain.Status": "COMPLETED"},
)

sess.OrderBy("cloudbrain.created_unix DESC")
cloudbrains := make([]*CloudbrainInfo, 0)
if err := sess.Table(&Cloudbrain{}).Where(cond).
Find(&cloudbrains); err != nil {
return nil, 0, fmt.Errorf("Find: %v", err)
}

return cloudbrains, int(len(cloudbrains)), nil
}

func QueryModelTrainJobList(repoId int64) ([]*CloudbrainInfo, int, error) {
sess := x.NewSession()
defer sess.Close()
var cond = builder.NewCond()
cond = cond.And(
builder.Eq{"repo_id": repoId},
)
cond = cond.And(
builder.Eq{"Status": "COMPLETED"},
)
sess.OrderBy("job_id DESC")
cloudbrains := make([]*CloudbrainInfo, 0)
if err := sess.Distinct("job_id,job_name").Table(&Cloudbrain{}).Where(cond).
Find(&cloudbrains); err != nil {
return nil, 0, fmt.Errorf("Find: %v", err)
}
return cloudbrains, int(len(cloudbrains)), nil
}

func CloudbrainsVersionList(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int, error) {
sess := x.NewSession()
defer sess.Close()


+ 1
- 0
models/models.go View File

@@ -133,6 +133,7 @@ func init() {
new(FileChunk),
new(BlockChain),
new(RecommendOrg),
new(AiModelManage),
)

tablesStatistic = append(tablesStatistic,


+ 6
- 0
models/repo.go View File

@@ -1114,6 +1114,12 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Type: tp,
Config: &BlockChainConfig{EnableBlockChain: true},
})
} else if tp == UnitTypeModelManage {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &ModelManageConfig{EnableModelManage: true},
})
} else {
units = append(units, RepoUnit{
RepoID: repo.ID,


+ 16
- 0
models/repo_unit.go View File

@@ -131,6 +131,20 @@ type CloudBrainConfig struct {
EnableCloudBrain bool
}

type ModelManageConfig struct {
EnableModelManage bool
}

// FromDB fills up a CloudBrainConfig from serialized format.
func (cfg *ModelManageConfig) FromDB(bs []byte) error {
return json.Unmarshal(bs, &cfg)
}

// ToDB exports a CloudBrainConfig to a serialized format.
func (cfg *ModelManageConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}

// FromDB fills up a CloudBrainConfig from serialized format.
func (cfg *CloudBrainConfig) FromDB(bs []byte) error {
return json.Unmarshal(bs, &cfg)
@@ -176,6 +190,8 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
r.Config = new(CloudBrainConfig)
case UnitTypeBlockChain:
r.Config = new(BlockChainConfig)
case UnitTypeModelManage:
r.Config = new(ModelManageConfig)
default:
panic("unrecognized repo unit type: " + com.ToStr(*val))
}


+ 14
- 0
models/unit.go View File

@@ -27,6 +27,7 @@ const (
UnitTypeDatasets UnitType = 10 // 10 Dataset
UnitTypeCloudBrain UnitType = 11 // 11 CloudBrain
UnitTypeBlockChain UnitType = 12 // 12 BlockChain
UnitTypeModelManage UnitType = 13 // 13 ModelManage
)

// Value returns integer value for unit type
@@ -56,6 +57,8 @@ func (u UnitType) String() string {
return "UnitTypeCloudBrain"
case UnitTypeBlockChain:
return "UnitTypeBlockChain"
case UnitTypeModelManage:
return "UnitTypeModelManage"
}
return fmt.Sprintf("Unknown UnitType %d", u)
}
@@ -80,6 +83,7 @@ var (
UnitTypeDatasets,
UnitTypeCloudBrain,
UnitTypeBlockChain,
UnitTypeModelManage,
}

// DefaultRepoUnits contains the default unit types
@@ -92,6 +96,7 @@ var (
UnitTypeDatasets,
UnitTypeCloudBrain,
UnitTypeBlockChain,
UnitTypeModelManage,
}

// NotAllowedDefaultRepoUnits contains units that can't be default
@@ -281,6 +286,14 @@ var (
7,
}

UnitModelManage = Unit{
UnitTypeModelManage,
"repo.modelmanage",
"/modelmanage",
"repo.modelmanage.desc",
8,
}

// Units contains all the units
Units = map[UnitType]Unit{
UnitTypeCode: UnitCode,
@@ -293,6 +306,7 @@ var (
UnitTypeDatasets: UnitDataset,
UnitTypeCloudBrain: UnitCloudBrain,
UnitTypeBlockChain: UnitBlockChain,
UnitTypeModelManage: UnitModelManage,
}
)



+ 1
- 0
modules/auth/repo_form.go View File

@@ -122,6 +122,7 @@ type RepoSettingForm struct {
// Advanced settings
EnableDataset bool
EnableCloudBrain bool
EnableModelManager bool
EnableWiki bool
EnableExternalWiki bool
ExternalWikiURL string


+ 1
- 0
modules/context/repo.go View File

@@ -821,5 +821,6 @@ func UnitTypes() macaron.Handler {
ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki
ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker
ctx.Data["UnitTypeBlockChain"] = models.UnitTypeBlockChain
ctx.Data["UnitTypeModelManage"] = models.UnitTypeModelManage
}
}

+ 207
- 25
modules/storage/obs.go View File

@@ -5,6 +5,7 @@
package storage

import (
"errors"
"io"
"net/url"
"path"
@@ -140,11 +141,51 @@ func ObsMultiPartUpload(uuid string, uploadId string, partNumber int, fileName s

}

func ObsDownload(uuid string, fileName string) (io.ReadCloser, error) {
//delete all file under the dir path
func ObsRemoveObject(bucket string, path string) error {
log.Info("Bucket=" + bucket + " path=" + path)
if len(path) == 0 {
return errors.New("path canot be null.")
}
input := &obs.ListObjectsInput{}
input.Bucket = bucket
// 设置每页100个对象
input.MaxKeys = 100
input.Prefix = path
index := 1
log.Info("prefix=" + input.Prefix)
for {
output, err := ObsCli.ListObjects(input)
if err == nil {
log.Info("Page:%d\n", index)
index++
for _, val := range output.Contents {
log.Info("delete obs file:" + val.Key)
delObj := &obs.DeleteObjectInput{}
delObj.Bucket = setting.Bucket
delObj.Key = val.Key
ObsCli.DeleteObject(delObj)
}
if output.IsTruncated {
input.Marker = output.NextMarker
} else {
break
}
} else {
if obsError, ok := err.(obs.ObsError); ok {
log.Info("Code:%s\n", obsError.Code)
log.Info("Message:%s\n", obsError.Message)
}
return err
}
}
return nil
}

func ObsDownloadAFile(bucket string, key string) (io.ReadCloser, error) {
input := &obs.GetObjectInput{}
input.Bucket = setting.Bucket
input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, fileName)), "/")
// input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/")
input.Bucket = bucket
input.Key = key
output, err := ObsCli.GetObject(input)
if err == nil {
log.Info("StorageClass:%s, ETag:%s, ContentType:%s, ContentLength:%d, LastModified:%s\n",
@@ -158,6 +199,11 @@ func ObsDownload(uuid string, fileName string) (io.ReadCloser, error) {
}
}

func ObsDownload(uuid string, fileName string) (io.ReadCloser, error) {

return ObsDownloadAFile(setting.Bucket, strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, fileName)), "/"))
}

func ObsModelDownload(JobName string, fileName string) (io.ReadCloser, error) {
input := &obs.GetObjectInput{}
input.Bucket = setting.Bucket
@@ -176,6 +222,160 @@ func ObsModelDownload(JobName string, fileName string) (io.ReadCloser, error) {
}
}

func ObsCopyManyFile(srcBucket string, srcPath string, destBucket string, destPath string) (int64, error) {
input := &obs.ListObjectsInput{}
input.Bucket = srcBucket
// 设置每页100个对象
input.MaxKeys = 100
input.Prefix = srcPath
index := 1
length := len(srcPath)
var fileTotalSize int64
log.Info("prefix=" + input.Prefix)
for {
output, err := ObsCli.ListObjects(input)
if err == nil {
log.Info("Page:%d\n", index)
index++
for _, val := range output.Contents {
destKey := destPath + val.Key[length:]
obsCopyFile(srcBucket, val.Key, destBucket, destKey)
fileTotalSize += val.Size
}
if output.IsTruncated {
input.Marker = output.NextMarker
} else {
break
}
} else {
if obsError, ok := err.(obs.ObsError); ok {
log.Info("Code:%s\n", obsError.Code)
log.Info("Message:%s\n", obsError.Message)
}
return 0, err
}
}
return fileTotalSize, nil
}

func obsCopyFile(srcBucket string, srcKeyName string, destBucket string, destKeyName string) error {
input := &obs.CopyObjectInput{}
input.Bucket = destBucket
input.Key = destKeyName
input.CopySourceBucket = srcBucket
input.CopySourceKey = srcKeyName
_, err := ObsCli.CopyObject(input)
if err == nil {
log.Info("copy success,destBuckName:%s, destkeyname:%s", destBucket, destKeyName)
} else {
log.Info("copy failed,,destBuckName:%s, destkeyname:%s", destBucket, destKeyName)
if obsError, ok := err.(obs.ObsError); ok {
log.Info(obsError.Code)
log.Info(obsError.Message)
}
return err
}
return nil
}

func GetOneLevelAllObjectUnderDir(bucket string, prefixRootPath string, relativePath string) ([]FileInfo, error) {
input := &obs.ListObjectsInput{}
input.Bucket = bucket
input.Prefix = prefixRootPath + relativePath
if !strings.HasSuffix(input.Prefix, "/") {
input.Prefix += "/"
}
output, err := ObsCli.ListObjects(input)
fileInfos := make([]FileInfo, 0)
prefixLen := len(input.Prefix)
if err == nil {
for _, val := range output.Contents {
log.Info("val key=" + val.Key)
var isDir bool
var fileName string
if val.Key == input.Prefix {
continue
}
if strings.Contains(val.Key[prefixLen:len(val.Key)-1], "/") {
continue
}
if strings.HasSuffix(val.Key, "/") {
isDir = true
fileName = val.Key[prefixLen : len(val.Key)-1]
relativePath += val.Key[prefixLen:]
} else {
isDir = false
fileName = val.Key[prefixLen:]
}
fileInfo := FileInfo{
ModTime: val.LastModified.Local().Format("2006-01-02 15:04:05"),
FileName: fileName,
Size: val.Size,
IsDir: isDir,
ParenDir: relativePath,
}
fileInfos = append(fileInfos, fileInfo)
}
return fileInfos, err
} else {
if obsError, ok := err.(obs.ObsError); ok {
log.Error("Code:%s, Message:%s", obsError.Code, obsError.Message)
}
return nil, err
}

}

func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, error) {
input := &obs.ListObjectsInput{}
input.Bucket = bucket
// 设置每页100个对象
input.MaxKeys = 100
input.Prefix = prefix
index := 1
fileInfos := make([]FileInfo, 0)
prefixLen := len(prefix)
log.Info("prefix=" + input.Prefix)
for {
output, err := ObsCli.ListObjects(input)
if err == nil {
log.Info("Page:%d\n", index)
index++
for _, val := range output.Contents {
var isDir bool
if prefixLen == len(val.Key) {
continue
}
if strings.HasSuffix(val.Key, "/") {
isDir = true
} else {
isDir = false
}
fileInfo := FileInfo{
ModTime: val.LastModified.Format("2006-01-02 15:04:05"),
FileName: val.Key[prefixLen:],
Size: val.Size,
IsDir: isDir,
ParenDir: "",
}
fileInfos = append(fileInfos, fileInfo)
}
if output.IsTruncated {
input.Marker = output.NextMarker
} else {
break
}
} else {
if obsError, ok := err.(obs.ObsError); ok {
log.Info("Code:%s\n", obsError.Code)
log.Info("Message:%s\n", obsError.Message)
}
return nil, err
}
}
return fileInfos, nil
}

func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error) {
input := &obs.ListObjectsInput{}
input.Bucket = setting.Bucket
@@ -258,27 +458,6 @@ func ObsGenMultiPartSignedUrl(uuid string, uploadId string, partNumber int, file
return output.SignedUrl, nil
}

func GetObsCreateSignedUrl(jobName, parentDir, fileName string) (string, error) {
input := &obs.CreateSignedUrlInput{}
input.Bucket = setting.Bucket
input.Key = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir, fileName), "/")

input.Expires = 60 * 60
input.Method = obs.HttpMethodGet

reqParams := make(map[string]string)
fileName = url.QueryEscape(fileName)
reqParams["response-content-disposition"] = "attachment; filename=\"" + fileName + "\""
input.QueryParams = reqParams
output, err := ObsCli.CreateSignedUrl(input)
if err != nil {
log.Error("CreateSignedUrl failed:", err.Error())
return "", err
}
log.Info("SignedUrl:%s", output.SignedUrl)
return output.SignedUrl, nil
}

func GetObsCreateSignedUrlByBucketAndKey(bucket, key string) (string, error) {
input := &obs.CreateSignedUrlInput{}
input.Bucket = bucket
@@ -302,7 +481,10 @@ func GetObsCreateSignedUrlByBucketAndKey(bucket, key string) (string, error) {
}

return output.SignedUrl, nil
}

func GetObsCreateSignedUrl(jobName, parentDir, fileName string) (string, error) {
return GetObsCreateSignedUrlByBucketAndKey(setting.Bucket, strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir, fileName), "/"))
}

func ObsGetPreSignedUrl(uuid, fileName string) (string, error) {


+ 24
- 0
options/locale/locale_en-US.ini View File

@@ -816,6 +816,11 @@ get_repo_info_error=Can not get the information of the repository.
generate_statistic_file_error=Fail to generate file.
repo_stat_inspect=ProjectAnalysis
all=All
modelarts.status=Status
modelarts.createtime=CreateTime
modelarts.version_nums = Version Nums
modelarts.version = Version
modelarts.computing_resources=compute Resources
modelarts.notebook=Debug Task
modelarts.train_job=Train Task
modelarts.train_job.new_debug= New Debug Task
@@ -823,6 +828,10 @@ modelarts.train_job.new_train=New Train 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
modelarts.model_name=Model Name
modelarts.model_size=Model Size
modelarts.import_model=Import Model

modelarts.modify=Modify
modelarts.current_version=Current version
modelarts.parent_version=Parent Version
@@ -874,6 +883,20 @@ 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

model.manage.import_new_model=Import New Model
model.manage.create_error=Equal Name and Version has existed.
model.manage.model_name = Model Name
model.manage.version = Version
model.manage.label = Label
model.manage.size = Size
model.manage.create_time = Create Time
model.manage.Description = Description
model.manage.Accuracy = Accuracy
model.manage.F1 = F1
model.manage.Precision = Precision
model.manage.Recall = Recall


template.items = Template Items
template.git_content = Git Content (Default Branch)
template.git_hooks = Git Hooks
@@ -1552,6 +1575,7 @@ settings.external_wiki_url_error = The external wiki URL is not a valid URL.
settings.external_wiki_url_desc = Visitors are redirected to the external wiki URL when clicking the wiki tab.
settings.dataset_desc = Enable Repository Dataset
settings.cloudbrain_desc = Enable Cloudbarin
settings.model_desc = Enable Model Manage
settings.issues_desc = Enable Repository Issue Tracker
settings.use_internal_issue_tracker = Use Built-In Issue Tracker
settings.use_external_issue_tracker = Use External Issue Tracker


+ 21
- 0
options/locale/locale_zh-CN.ini View File

@@ -782,6 +782,9 @@ datasets=数据集
datasets.desc=数据集功能
cloudbrain_helper=使用GPU/NPU资源,开启Notebook、模型训练任务等

model_manager = 模型管理
model_noright=无权限操作

debug=调试
stop=停止
delete=删除
@@ -824,6 +827,7 @@ all=所有
modelarts.status=状态
modelarts.createtime=创建时间
modelarts.version_nums=版本数
modelarts.version=版本
modelarts.computing_resources=计算资源
modelarts.notebook=调试任务
modelarts.train_job=训练任务
@@ -831,6 +835,10 @@ modelarts.train_job.new_debug=新建调试任务
modelarts.train_job.new_train=新建训练任务
modelarts.train_job.config=配置信息
modelarts.train_job.new=新建训练任务
modelarts.train_job.new_place=描述字数不超过256个字符
modelarts.model_name=模型名称
modelarts.model_size=模型大小
modelarts.import_model=导入模型
modelarts.train_job.new_place=描述字数不超过255个字符
modelarts.modify=修改
modelarts.current_version=当前版本
@@ -887,6 +895,18 @@ modelarts.train_job_para_admin=任务参数管理
modelarts.train_job_para.edit=编辑
modelarts.train_job_para.connfirm=确定

model.manage.import_new_model=导入新模型
model.manage.create_error=相同的名称和版本的模型已经存在。
model.manage.model_name = 模型名称
model.manage.version = 版本
model.manage.label = 标签
model.manage.size = 大小
model.manage.create_time = 创建时间
model.manage.description = 描述
model.manage.Accuracy = 准确率
model.manage.F1 = F1值
model.manage.Precision = 精确率
model.manage.Recall = 召回率

template.items=模板选项
template.git_content=Git数据(默认分支)
@@ -1566,6 +1586,7 @@ settings.external_wiki_url_error=外部百科链接无效
settings.external_wiki_url_desc=当点击任务标签时,访问者将被重定向到外部任务系统的URL。
settings.dataset_desc=启用数据集
settings.cloudbrain_desc = 启用云脑
settings.model_desc = 启用模型管理
settings.issues_desc=启用任务系统
settings.use_internal_issue_tracker=使用内置的轻量级任务管理系统
settings.use_external_issue_tracker=使用外部的任务管理系统


+ 513
- 0
routers/repo/ai_model_manage.go View File

@@ -0,0 +1,513 @@
package repo

import (
"archive/zip"
"encoding/json"
"errors"
"fmt"
"net/http"
"path"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
uuid "github.com/satori/go.uuid"
)

const (
Model_prefix = "aimodels/"
tplModelManageIndex = "repo/modelmanage/index"
tplModelManageDownload = "repo/modelmanage/download"
tplModelInfo = "repo/modelmanage/showinfo"
MODEL_LATEST = 1
MODEL_NOT_LATEST = 0
)

func saveModelByParameters(jobId string, versionName string, name string, version string, label string, description string, ctx *context.Context) error {
aiTask, err := models.GetCloudbrainByJobIDAndVersionName(jobId, versionName)
//aiTask, err := models.GetCloudbrainByJobID(jobId)
if err != nil {
log.Info("query task error." + err.Error())
return err
}

uuid := uuid.NewV4()
id := uuid.String()
modelPath := id
var lastNewModelId string
var modelSize int64
cloudType := models.TypeCloudBrainTwo

log.Info("find task name:" + aiTask.JobName)
aimodels := models.QueryModelByName(name, aiTask.RepoID)
if len(aimodels) > 0 {
for _, model := range aimodels {
if model.Version == version {
return errors.New(ctx.Tr("repo.model.manage.create_error"))
}
if model.New == MODEL_LATEST {
lastNewModelId = model.ID
}
}
}
cloudType = aiTask.Type
//download model zip //train type
if cloudType == models.TypeCloudBrainTwo {
modelPath, modelSize, err = downloadModelFromCloudBrainTwo(id, aiTask.JobName, "")
if err != nil {
log.Info("download model from CloudBrainTwo faild." + err.Error())
return err
}
}
accuracy := make(map[string]string)
accuracy["F1"] = ""
accuracy["Recall"] = ""
accuracy["Accuracy"] = ""
accuracy["Precision"] = ""
accuracyJson, _ := json.Marshal(accuracy)
log.Info("accuracyJson=" + string(accuracyJson))
aiTaskJson, _ := json.Marshal(aiTask)

//taskConfigInfo,err := models.GetCloudbrainByJobIDAndVersionName(jobId,aiTask.VersionName)
model := &models.AiModelManage{
ID: id,
Version: version,
VersionCount: len(aimodels) + 1,
Label: label,
Name: name,
Description: description,
New: MODEL_LATEST,
Type: cloudType,
Path: modelPath,
Size: modelSize,
AttachmentId: aiTask.Uuid,
RepoId: aiTask.RepoID,
UserId: ctx.User.ID,
UserName: ctx.User.Name,
UserRelAvatarLink: ctx.User.RelAvatarLink(),
CodeBranch: aiTask.BranchName,
CodeCommitID: aiTask.CommitID,
Engine: aiTask.EngineID,
TrainTaskInfo: string(aiTaskJson),
Accuracy: string(accuracyJson),
}

err = models.SaveModelToDb(model)
if err != nil {
return err
}
if len(lastNewModelId) > 0 {
//udpate status and version count
models.ModifyModelNewProperty(lastNewModelId, MODEL_NOT_LATEST, 0)
}

log.Info("save model end.")

return nil
}

func SaveModel(ctx *context.Context) {
log.Info("save model start.")
JobId := ctx.Query("JobId")
VersionName := ctx.Query("VersionName")
name := ctx.Query("Name")
version := ctx.Query("Version")
label := ctx.Query("Label")
description := ctx.Query("Description")

if !ctx.Repo.CanWrite(models.UnitTypeModelManage) {
ctx.ServerError("No right.", errors.New(ctx.Tr("repo.model_noright")))
return
}

if JobId == "" || VersionName == "" {
ctx.Error(500, fmt.Sprintf("JobId or VersionName is null."))
return
}

if name == "" || version == "" {
ctx.Error(500, fmt.Sprintf("name or version is null."))
return
}

err := saveModelByParameters(JobId, VersionName, name, version, label, description, ctx)

if err != nil {
log.Info("save model error." + err.Error())
ctx.Error(500, fmt.Sprintf("save model error. %v", err))
return
}

log.Info("save model end.")
}

func downloadModelFromCloudBrainTwo(modelUUID string, jobName string, parentDir string) (string, int64, error) {

objectkey := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/")
modelDbResult, err := storage.GetOneLevelAllObjectUnderDir(setting.Bucket, objectkey, "")
log.Info("bucket=" + setting.Bucket + " objectkey=" + objectkey)
if err != nil {
log.Info("get TrainJobListModel failed:", err)
return "", 0, err
}
if len(modelDbResult) == 0 {
return "", 0, errors.New("cannot create model, as model is empty.")
}

prefix := objectkey + "/"
destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(modelUUID) + "/"

size, err := storage.ObsCopyManyFile(setting.Bucket, prefix, setting.Bucket, destKeyNamePrefix)

dataActualPath := setting.Bucket + "/" + destKeyNamePrefix
return dataActualPath, size, nil
}

func DeleteModel(ctx *context.Context) {
log.Info("delete model start.")
id := ctx.Query("ID")
err := deleteModelByID(ctx, id)
if err != nil {
ctx.JSON(500, err.Error())
} else {
ctx.JSON(200, map[string]string{
"result_code": "0",
})
}
}
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) {
return errors.New(ctx.Tr("repo.model_noright"))
}
if err == nil {
log.Info("bucket=" + setting.Bucket + " path=" + model.Path)
if strings.HasPrefix(model.Path, setting.Bucket+"/"+Model_prefix) {
err := storage.ObsRemoveObject(setting.Bucket, model.Path[len(setting.Bucket)+1:])
if err != nil {
log.Info("Failed to delete model. id=" + id)
return err
}
}
err = models.DeleteModelById(id)
if err == nil { //find a model to change new
aimodels := models.QueryModelByName(model.Name, model.RepoId)
if model.New == MODEL_LATEST {
if len(aimodels) > 0 {
//udpate status and version count
models.ModifyModelNewProperty(aimodels[0].ID, MODEL_LATEST, len(aimodels))
}
} else {
for _, tmpModel := range aimodels {
if tmpModel.New == MODEL_LATEST {
models.ModifyModelNewProperty(tmpModel.ID, MODEL_LATEST, len(aimodels))
break
}
}
}
}
}
return err
}

func QueryModelByParameters(repoId int64, page int) ([]*models.AiModelManage, int64, error) {

return models.QueryModel(&models.AiModelQueryOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repoId,
Type: -1,
New: MODEL_LATEST,
})
}

func DownloadMultiModelFile(ctx *context.Context) {
log.Info("DownloadMultiModelFile start.")
id := ctx.Query("ID")
log.Info("id=" + id)
task, err := models.QueryModelById(id)
if err != nil {
log.Error("no such model!", err.Error())
ctx.ServerError("no such model:", err)
return
}
if !isCanDeleteOrDownload(ctx, task) {
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
return
}

path := Model_prefix + models.AttachmentRelativePath(id) + "/"

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

returnFileName := task.Name + "_" + task.Version + ".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 QueryTrainJobVersionList(ctx *context.Context) {
log.Info("query train job version list. start.")
JobID := ctx.Query("JobID")

VersionListTasks, count, err := models.QueryModelTrainJobVersionList(JobID)

log.Info("query return count=" + fmt.Sprint(count))

if err != nil {
ctx.ServerError("QueryTrainJobList:", err)
} else {
ctx.JSON(200, VersionListTasks)
}
}

func QueryTrainJobList(ctx *context.Context) {
log.Info("query train job list. start.")
repoId := ctx.QueryInt64("repoId")

VersionListTasks, count, err := models.QueryModelTrainJobList(repoId)
log.Info("query return count=" + fmt.Sprint(count))

if err != nil {
ctx.ServerError("QueryTrainJobList:", err)
} else {
ctx.JSON(200, VersionListTasks)
}

}

func DownloadSingleModelFile(ctx *context.Context) {
log.Info("DownloadSingleModelFile start.")
id := ctx.Params(":ID")
parentDir := ctx.Query("parentDir")
fileName := ctx.Query("fileName")
path := Model_prefix + models.AttachmentRelativePath(id) + "/" + parentDir + fileName

if setting.PROXYURL != "" {
body, err := storage.ObsDownloadAFile(setting.Bucket, path)
if err != nil {
log.Info("download error.")
} else {
//count++
models.ModifyModelDownloadCount(id)
defer body.Close()
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+fileName)
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
p := make([]byte, 1024)
var readErr error
var readCount int
// 读取对象内容
for {
readCount, readErr = body.Read(p)
if readCount > 0 {
ctx.Resp.Write(p[:readCount])
//fmt.Printf("%s", p[:readCount])
}
if readErr != nil {
break
}
}
}
} else {
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
}
//count++
models.ModifyModelDownloadCount(id)
http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently)
}
}

func ShowModelInfo(ctx *context.Context) {
ctx.Data["ID"] = ctx.Query("ID")
ctx.Data["name"] = ctx.Query("name")
ctx.Data["isModelManage"] = true
ctx.HTML(200, tplModelInfo)
}

func ShowSingleModel(ctx *context.Context) {
name := ctx.Query("name")

log.Info("Show single ModelInfo start.name=" + name)
models := models.QueryModelByName(name, ctx.Repo.Repository.ID)

ctx.JSON(http.StatusOK, models)
}

func ShowOneVersionOtherModel(ctx *context.Context) {
repoId := ctx.Repo.Repository.ID
name := ctx.Query("name")
aimodels := models.QueryModelByName(name, repoId)
for _, model := range aimodels {
log.Info("model=" + model.Name)
log.Info("model.UserId=" + fmt.Sprint(model.UserId))
model.IsCanOper = isOper(ctx, model.UserId)
}
if len(aimodels) > 0 {
ctx.JSON(200, aimodels[1:])
} else {
ctx.JSON(200, aimodels)
}
}

func ShowModelTemplate(ctx *context.Context) {
ctx.Data["isModelManage"] = true
ctx.HTML(200, tplModelManageIndex)
}

func isQueryRight(ctx *context.Context) bool {
if ctx.Repo.Repository.IsPrivate {
if ctx.Repo.CanRead(models.UnitTypeModelManage) || ctx.User.IsAdmin || ctx.Repo.IsAdmin() || ctx.Repo.IsOwner() {
return true
}
return false
} else {
return true
}
}

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

func ShowModelPageInfo(ctx *context.Context) {
log.Info("ShowModelInfo start.")
if !isQueryRight(ctx) {
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
return
}
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
repoId := ctx.Repo.Repository.ID
Type := -1
modelResult, count, err := models.QueryModel(&models.AiModelQueryOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repoId,
Type: Type,
New: MODEL_LATEST,
})
if err != nil {
ctx.ServerError("Cloudbrain", err)
return
}

for _, model := range modelResult {
log.Info("model=" + model.Name)
log.Info("model.UserId=" + fmt.Sprint(model.UserId))
model.IsCanOper = isOper(ctx, model.UserId)
}

mapInterface := make(map[string]interface{})
mapInterface["data"] = modelResult
mapInterface["count"] = count
ctx.JSON(http.StatusOK, mapInterface)
}

func ModifyModel(id string, description string) error {
err := models.ModifyModelDescription(id, description)
if err == nil {
log.Info("modify success.")
} else {
log.Info("Failed to modify.id=" + id + " desc=" + description + " error:" + err.Error())
}
return err
}

func ModifyModelInfo(ctx *context.Context) {
log.Info("modify model start.")
id := ctx.Query("ID")
description := ctx.Query("Description")

task, err := models.QueryModelById(id)
if err != nil {
log.Error("no such model!", err.Error())
ctx.ServerError("no such model:", err)
return
}
if !isCanDeleteOrDownload(ctx, task) {
ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright")))
return
}

err = ModifyModel(id, description)

if err != nil {
log.Info("modify error," + err.Error())
ctx.ServerError("error.", err)
} else {
ctx.JSON(200, "success")
}

}

+ 6
- 6
routers/repo/cloudbrain.go View File

@@ -344,7 +344,7 @@ func CloudBrainDebug(ctx *context.Context) {
var jobID = ctx.Params(":jobid")
if !ctx.IsSigned {
log.Error("the user has not signed in")
ctx.Error(http.StatusForbidden, "","the user has not signed in")
ctx.Error(http.StatusForbidden, "", "the user has not signed in")
return
}
task, err := models.GetCloudbrainByJobID(jobID)
@@ -361,7 +361,7 @@ func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrain
var jobID = ctx.Params(":jobid")
if !ctx.IsSigned {
log.Error("the user has not signed in")
ctx.Error(http.StatusForbidden, "","the user has not signed in")
ctx.Error(http.StatusForbidden, "", "the user has not signed in")
return
}
task, err := models.GetCloudbrainByJobID(jobID)
@@ -542,10 +542,10 @@ func CloudBrainShowModels(ctx *context.Context) {
}

//get dirs
dirs, err := getModelDirs(task.JobName, parentDir)
dirs, err := GetModelDirs(task.JobName, parentDir)
if err != nil {
log.Error("getModelDirs failed:%v", err.Error(), ctx.Data["msgID"])
ctx.ServerError("getModelDirs failed:", err)
log.Error("GetModelDirs failed:%v", err.Error(), ctx.Data["msgID"])
ctx.ServerError("GetModelDirs failed:", err)
return
}

@@ -605,7 +605,7 @@ func getImages(ctx *context.Context, imageType string) {
log.Info("Get images end")
}

func getModelDirs(jobName string, parentDir string) (string, error) {
func GetModelDirs(jobName string, parentDir string) (string, error) {
var req string
modelActualPath := setting.JobPath + jobName + "/model/"
if parentDir == "" {


+ 3
- 34
routers/repo/dir.go View File

@@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/obs"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
)
@@ -70,40 +69,10 @@ func DeleteAllUnzipFile(attachment *models.Attachment, parentDir string) {
}
}
if attachment.Type == models.TypeCloudBrainTwo {

input := &obs.ListObjectsInput{}
input.Bucket = setting.Bucket
// 设置每页100个对象
input.MaxKeys = 100
input.Prefix = setting.BasePath + attachment.RelativePath() + attachment.UUID
index := 1
log.Info("prefix=" + input.Prefix)
for {
output, err := storage.ObsCli.ListObjects(input)
if err == nil {
log.Info("Page:%d\n", index)
index++
for _, val := range output.Contents {
log.Info("delete obs file:" + val.Key)
delObj := &obs.DeleteObjectInput{}
delObj.Bucket = setting.Bucket
delObj.Key = val.Key
storage.ObsCli.DeleteObject(delObj)
}
if output.IsTruncated {
input.Marker = output.NextMarker
} else {
break
}
} else {
if obsError, ok := err.(obs.ObsError); ok {
log.Info("Code:%s\n", obsError.Code)
log.Info("Message:%s\n", obsError.Message)
}
break
}
err := storage.ObsRemoveObject(setting.Bucket, setting.BasePath+attachment.RelativePath()+attachment.UUID)
if err != nil {
log.Info("delete file error.")
}

}

}


+ 1
- 1
routers/repo/http.go View File

@@ -257,7 +257,6 @@ func HTTP(ctx *context.Context) {
models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID),
models.EnvIsDeployKey + "=false",
}

if !authUser.KeepEmailPrivate {
environ = append(environ, models.EnvPusherEmail+"="+authUser.Email)
}
@@ -559,6 +558,7 @@ func serviceRPC(h serviceHandler, service string) {
if service == "receive-pack" {
cmd.Env = append(os.Environ(), h.environ...)
}

cmd.Stdout = h.w
cmd.Stdin = reqBody
cmd.Stderr = &stderr


+ 12
- 0
routers/repo/setting.go View File

@@ -239,6 +239,18 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeCloudBrain)
}

if form.EnableModelManager && !models.UnitTypeModelManage.UnitGlobalDisabled() {
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeModelManage,
Config: &models.ModelManageConfig{
EnableModelManage: form.EnableModelManager,
},
})
} else if !models.UnitTypeModelManage.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeModelManage)
}

if form.EnableWiki && form.EnableExternalWiki && !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
if !validation.IsValidExternalURL(form.ExternalWikiURL) {
ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error"))


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

@@ -614,6 +614,8 @@ func RegisterRoutes(m *macaron.Macaron) {
reqRepoDatasetWriter := context.RequireRepoWriter(models.UnitTypeDatasets)
reqRepoCloudBrainReader := context.RequireRepoReader(models.UnitTypeCloudBrain)
reqRepoCloudBrainWriter := context.RequireRepoWriter(models.UnitTypeCloudBrain)
reqRepoModelManageReader := context.RequireRepoReader(models.UnitTypeModelManage)
reqRepoModelManageWriter := context.RequireRepoWriter(models.UnitTypeModelManage)
//reqRepoBlockChainReader := context.RequireRepoReader(models.UnitTypeBlockChain)
//reqRepoBlockChainWriter := context.RequireRepoWriter(models.UnitTypeBlockChain)

@@ -970,6 +972,23 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)
}, context.RepoRef())
m.Group("/modelmanage", func() {
m.Post("/create_model", reqRepoModelManageWriter, repo.SaveModel)
m.Delete("/delete_model", repo.DeleteModel)
m.Put("/modify_model", repo.ModifyModelInfo)
m.Get("/show_model", reqRepoModelManageReader, repo.ShowModelTemplate)
m.Get("/show_model_info", repo.ShowModelInfo)
m.Get("/show_model_info_api", repo.ShowSingleModel)
m.Get("/show_model_api", repo.ShowModelPageInfo)
m.Get("/show_model_child_api", repo.ShowOneVersionOtherModel)
m.Get("/query_train_job", reqRepoCloudBrainReader, repo.QueryTrainJobList)
m.Get("/query_train_job_version", reqRepoCloudBrainReader, repo.QueryTrainJobVersionList)
m.Group("/:ID", func() {
m.Get("", repo.ShowSingleModel)
m.Get("/downloadsingle", repo.DownloadSingleModelFile)
})
m.Get("/downloadall", repo.DownloadMultiModelFile)
}, context.RepoRef())

m.Group("/modelarts", func() {
m.Group("/notebook", func() {


+ 575
- 0
templates/repo/debugjob/index.tmpl View File

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

<style>
.label_after::after{
margin: -.2em 0 0 .2em;
content: '\00a0';
}
.selectcloudbrain .active.item{
color: #0087f5 !important;
border: 1px solid #0087f5;
margin: -1px;
background: #FFF !important;
}
#deletemodel {
width: 100%;
height: 100%;
}
/* 弹窗 */

#mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: #777;
z-index: 1000;
display: none;
opacity: 0.8;
-moz-opacity: 0.5;
padding-top: 100px;
color: #000000
}

#loadingPage {
margin: 200px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;
}

#loadingPage>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

#loadingPage .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}

#loadingPage .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}

#loadingPage .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}

#loadingPage .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}

@keyframes sk-stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}
/* 消息框 */

.alert {
display: none;
position: fixed;
width: 100%;
z-index: 1001;
padding: 15px;
border: 1px solid transparent;
border-radius: 4px;
text-align: center;
font-weight: bold;
}

.alert-success {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}

.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}

.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}

.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}

.pusher {
width: calc(100% - 260px);
box-sizing: border-box;
}
/* 弹窗 (background) */

#imageModal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
/* 弹窗内容 */

.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 30%;
}
/* 关闭按钮 */

.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}

.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}

.dis {
margin-bottom: 20px;
}

.disabled {
cursor: pointer;
pointer-events: none;
}
.time-show{
font-size: 10px;
margin-top: 0.4rem;
display: inline-block;
}
</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">
<div class="ui two column stackable grid ">
<div class="column">
<div class="ui blue small menu compact selectcloudbrain">
<a class="active item" href="{{.RepoLink}}/debugjob">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
</div>
</div>
<div class="column right aligned">
<div class="ui selection dropdown" style="min-width: 10em;min-height:2.6em;border-radius: .28571429rem;margin-right: 1em;padding: .67em 3.2em .7em 1em;">
{{svg "octicon-server" 16}}
<div class="default text" style="color: rgba(0,0,0,.87);"> 全部</div>
<i class="dropdown icon"></i>
<div class="menu">
<a class="active item" href="{{.RepoLink}}/debugjob" data-value="11">全部</a>
<a class="item" href="{{.RepoLink}}/cloudbrain" data-value="22">CPU / GPU</a>
<a class="item" href="{{.RepoLink}}/modelarts/notebook" data-value="33">Ascend NPU</a>
</div>
</div>
{{if .Permission.CanWrite $.UnitTypeCloudBrain}}
<a class="ui green button" href="{{.RepoLink}}/cloudbrain/create">{{$.i18n.Tr "repo.modelarts.train_job.new_debug"}}</a>{{end}}
</div>
</div>
<!-- 中下列表展示区 -->
<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="four wide column">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span>
</div>
<div class="two wide column text center">
<span style="margin:0 6px">{{$.i18n.Tr "repo.modelarts.status"}}</span>
</div>
<div class="two wide column text center">
<span>{{$.i18n.Tr "repo.modelarts.createtime"}}</span>
</div>
<div class="two wide column text center">
<span>{{$.i18n.Tr "repo.modelarts.computing_resources"}}</span>
</div>
<div class="one wide column text center">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="five wide column text center">
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>
</div>
</div>
{{range .Tasks}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="four wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted text_over" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>
<div class="two wide column text center">
<!--任务状态 -->
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-resource="{{.ComputeResource}}">
<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">
<!-- 任务创建时间 -->
<span style="font-size: 12px;margin-left: 0.4rem;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>
<div class="two wide column text center">
<!-- 任务计算资源 -->
<span style="font-size: 12px;margin-left: 0.4rem;" class="">{{.ComputeResource}}</span>
</div>
<div class="one wide column text center">
{{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="five wide column text center">
<div class="ui compact buttons">
{{if and (ne .Status "WAITING") (ne .JobType "DEBUG")}}
<a class="ui basic button" href="{{$.Link}}/{{.JobID}}/rate" target="_blank">
评分
</a>
{{end}}
<!-- 调试 -->
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
{{if eq .ComputeResource "CPU/GPU"}}
<a id="model-debug-{{.JobID}}" class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.RepoLink}}/cloudbrain/{{.JobID}}/debug" target="_blank">
{{$.i18n.Tr "repo.debug"}}
</a>
{{else}}
<a id="model-debug-{{.JobID}}" class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.RepoLink}}/modelarts/notebook/{{.JobID}}/debug" target="_blank">
{{$.i18n.Tr "repo.debug"}}
</a>
{{end}}
{{else}}
<a class="ui basic disabled button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank">
{{$.i18n.Tr "repo.debug"}}
</a>
{{end}}
<!-- 停止 -->
<form id="stopForm-{{.JobID}}" action="{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}/stop" method="post" style="margin-left:-1px;">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a id="stop-model-debug-{{.JobID}}" class="ui basic {{if or (eq .Status "STOPPED") (eq .Status "FAILED")}}disabled {{else}}blue {{end}}button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a class="ui basic disabled button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}
<input type="hidden" name="debugListType" value="all">
</form>
</div>
<div class="ui compact buttons">
<!-- 模型下载 -->
<!-- <a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}/models" target="_blank">
{{$.i18n.Tr "repo.download"}}
</a> -->
<!-- 接收结果 -->
<iframe src="" frameborder="0" name="iframeContent" style="display: none;"></iframe>
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a id="model-image-{{.JobID}}" style="{{if eq .ComputeResource "CPU/GPU"}} visibility: visible {{else}} visibility: hidden{{end}}" class="imageBtn ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" value="{{.CanDebug}}">{{$.i18n.Tr "repo.submit_image"}}</a>
{{else}}
<a class="imageBtn ui basic disabled button" style="{{if eq .ComputeResource "CPU/GPU"}} visibility: visible {{else}} visibility: hidden{{end}}" value="{{.CanDebug}}">{{$.i18n.Tr "repo.submit_image"}}</a>
{{end}}
</div>
{{.CanDel}}
<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}/del" method="post">
<input type="hidden" name="debugListType" value="all">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<!-- {{if not .CanDel}}disabled {{else}} blue {{end}} -->
<a id="model-delete-{{.JobID}}" class="ui basic blue button " onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
<a class="ui basic button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
</form>
</div>
<!-- 镜像列表弹窗 -->
<div id="imageModal" class="modal" style="display: none;">
<div class="modal-content">
<!-- 表格 -->
<div class="ui form">
<form id="commitImageForm" action="{{$.RepoLink}}/cloudbrain/{{.JobID}}/commit_image" method="post" target="iframeContent">
{{$.CsrfTokenHtml}}
<div class="row">
<p style="display: inline;">提交任务镜像</p>
<span class="close">&times;</span>
</div>
<div class="ui divider"></div>
<div class="inline required field dis">
<label>镜像标签:</label>
<input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="254" 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>
</div>
<div class="ui divider"></div>
<div class="inline field">
<label></label>
<button class="ui green button" onclick="showmask()">
{{$.i18n.Tr "repo.cloudbrain.commit_image"}}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{{end}} {{template "base/paginate" .}}
</div>
</div>
</div>
</div>
</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>
// 调试和评分新开窗口
console.log({{.Tasks}})
function stop(obj) {
if (obj.style.color != "rgb(204, 204, 204)") {
obj.target = '_blank'
} else {
return
}
}

// 删除时用户确认
function assertDelete(obj) {
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()
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
}
}

// 加载任务状态
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 computeResource = job.dataset.resource
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED']
if (initArray.includes(job.textContent.trim())) {
return
}
const diffResource = computeResource == "NPU" ? 'modelarts/notebook' : 'cloudbrain'
$.get(`/api/v1/repos/${repoPath}/${diffResource}/${jobID}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)
}
if(status==="RUNNING"){
$('#model-debug-'+jobID).removeClass('disabled')
$('#model-debug-'+jobID).addClass('blue')
$('#model-image-'+jobID).removeClass('disabled')
$('#model-image-'+jobID).addClass('blue')
}
if(status!=="RUNNING"){
$('#model-debug-'+jobID).removeClass('blue')
$('#model-debug-'+jobID).addClass('disabled')
$('#model-image-'+jobID).removeClass('blue')
$('#model-image-'+jobID).addClass('disabled')

}
if(status!=="STOPPED" || status!=="FAILED"){
$('#stop-model-debug-'+jobID).removeClass('disabled')
$('#stop-model-debug-'+jobID).addClass('blue')
// $('#model-delete-'+jobID).removeClass('red')
// $('#model-delete-'+jobID).addClass('disabled')
}
if(status=="STOPPED" || status=="FAILED"){
$('#stop-model-debug-'+jobID).removeClass('blue')
$('#stop-model-debug-'+jobID).addClass('disabled')
$('#model-delete-'+jobID).removeClass('disabled')
$('#model-delete-'+jobID).addClass('red')
}
}).fail(function(err) {
console.log(err);
});
});
};

// 获取弹窗
var modal = document.getElementById('imageModal');

// 打开弹窗的按钮对象
var btns = document.getElementsByClassName("imageBtn");

// 获取 <span> 元素,用于关闭弹窗
var spans = document.getElementsByClassName('close');

// 点击按钮打开弹窗
for (i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
modal.style.display = "block";
}
}

// 点击 <span> (x), 关闭弹窗
for (i = 0; i < spans.length; i++) {
spans[i].onclick = function() {
modal.style.display = "none";
}
}

// 在用户点击其他地方时,关闭弹窗
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}

// 显示弹窗,弹出相应的信息
function showmask() {
var image_tag = !$('#image_tag').val()
console.log("image_tag",image_tag)
if(image_tag){
return
}
$('#imageModal').css('display', 'none')
$('#mask').css('display', 'block')

$("iframe[name=iframeContent]").on("load", function() {  
var responseText = $("iframe")[0].contentDocument.body.getElementsByTagName("pre")[0].innerHTML; 
var json1 = JSON.parse(responseText)
$('#mask').css('display', 'none')
parent.location.href

if (json1.result_code === "0") {
$('.alert').html('操作成功!').removeClass('alert-danger').addClass('alert-success').show().delay(1500).fadeOut();
} else {
$('.alert').html(json1.error_msg).removeClass('alert-success').addClass('alert-danger').show().delay(5000).fadeOut();
}
})
}
</script>

+ 6
- 81
templates/repo/header.tmpl View File

@@ -137,6 +137,12 @@
{{svg "octicon-inbox" 16}} {{.i18n.Tr "datasets"}}
</a>
{{end}}
{{if .Permission.CanRead $.UnitTypeModelManage}}
<a class="{{if .isModelManage}}active{{end}} item" href="{{.RepoLink}}/modelmanage/show_model">
<svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M3.741 1.408l18.462 10.154a.5.5 0 0 1 0 .876L3.741 22.592A.5.5 0 0 1 3 22.154V1.846a.5.5 0 0 1 .741-.438zM5 13v6.617L18.85 12 5 4.383V11h5v2H5z"/></svg>
{{.i18n.Tr "repo.model_manager"}}
</a>
{{end}}
{{if .Permission.CanRead $.UnitTypeCloudBrain}}
<a class="{{if .PageIsCloudBrain}}active{{end}} item" href="{{.RepoLink}}/cloudbrain">
<span>{{svg "octicon-server" 16}} {{.i18n.Tr "repo.cloudbrain"}}<i class="question circle icon link cloudbrain-question" data-content={{.i18n.Tr "repo.cloudbrain_helper"}} data-position="top center" data-variation="mini"></i></span>
@@ -164,86 +170,5 @@
<div class="ui tabs divider"></div>
</div>

<div class="ui select_cloudbrain modal">
<div class="header">
{{$.i18n.Tr "repo.cloudbrain_selection"}}
</div>
<div class="content">
<div class="ui form" method="post">
<div class="grouped fields">
<label for="CloudBrain">{{$.i18n.Tr "repo.cloudbrain_platform_selection"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="CloudBrainSelect" checked tabindex="0" class="hidden" value="0">
<label>{{$.i18n.Tr "repo.cloudbrain1"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="CloudBrainSelect" tabindex="0" class="hidden" value="1">
<label>{{$.i18n.Tr "repo.cloudbrain2"}}</label>
</div>
</div>
</div>
<div class="actions">
<div class="mb-2 ui positive right labeled icon button">
{{$.i18n.Tr "repo.confirm_choice"}}
<i class="checkmark icon"></i>
</div>
</div>
</div>
</div>


</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>
<script>
// 点击云脑进行选择云脑平台并进入相应的界面
$('.item.cloudbrain').click(function(){
$('.ui.select_cloudbrain.modal')
.modal('closable', false)
.modal('show');
// $('.ui.select_cloudbrain.modal').modal('show');
$('.ui.radio.checkbox').checkbox();

var repolink = $(".cloudbrain_link").text()
console.log(repolink)
$(".ui.positive.right.icon.button").click(function(){
// 声明一个变量来接收以及获取单选框选择的情况
var checked_radio = $("input[name='CloudBrainSelect']:checked").val()
console.log(checked_radio)

if(checked_radio=='0'){
window.location.href = repolink+'/cloudbrain'
}else if(checked_radio=='1'){
window.location.href = repolink+'/modelarts/notebook'
}else{
return;
}
})
})

// 点击数据集进行选择云脑平台并进入相应的界面
$('.item.dataset').click(function(){
$('.ui.select_cloudbrain.modal')
.modal('closable', false)
.modal('show');
$('.ui.radio.checkbox').checkbox();

var repolink = $(".dataset_link").text()
console.log(repolink)
$(".ui.positive.right.icon.button").click(function(){
// 声明一个变量来接收以及获取单选框选择的情况
var checked_radio = $("input[type='radio']:checked").val()
$('.ui.select_cloudbrain.modal')
.modal('show');
// 向后端传递对象
window.location.href = repolink + "/datasets?type=" + checked_radio
})
})
$('.question.circle.icon').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"})
});
</script>

+ 0
- 181
templates/repo/modelarts/notebook/index.tmpl View File

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

<style>
.selectcloudbrain .active.item{
color: #0087f5 !important;
border: 1px solid #0087f5;
margin: -1px;
background: #FFF !important;
}
#deletemodel {
width: 100%;
height: 100%;
}
/* 弹窗 */

#mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: #777;
z-index: 1000;
display: none;
opacity: 0.8;
-moz-opacity: 0.5;
padding-top: 100px;
color: #000000
}

#loadingPage {
margin: 200px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;
}

#loadingPage>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

#loadingPage .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}

#loadingPage .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}

#loadingPage .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}

#loadingPage .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}

@keyframes sk-stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}
/* 消息框 */

.alert {
display: none;
position: fixed;
width: 100%;
z-index: 1001;
padding: 15px;
border: 1px solid transparent;
border-radius: 4px;
text-align: center;
font-weight: bold;
}

.alert-success {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}

.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}

.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}

.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}

.pusher {
width: calc(100% - 260px);
box-sizing: border-box;
}
/* 弹窗 (background) */

#imageModal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
/* 弹窗内容 */

.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 30%;
}
/* 关闭按钮 */

.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}

.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}

.dis {
margin-bottom: 20px;
}

.disabled {
cursor: pointer;
pointer-events: none;
}
</style>

<!-- 弹窗 -->
<div id="mask">
<div id="loadingPage">


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

@@ -1,89 +1,8 @@
{{template "base/head" .}}
<style>
/* 遮罩层css效果图 */
#mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: #777;
z-index: 1000;
.inline.required.field.cloudbrain_benchmark {
display: none;
opacity: 0.8;
-moz-opacity: 0.5;
padding-top: 100px;
color: #000000
}
/* 加载圈css效果图 */
#loadingPage {
margin: 200px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;
}
#loadingPage>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}
#loadingPage .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
#loadingPage .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
#loadingPage .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
#loadingPage .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
@-webkit-keyframes sk-stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}
@keyframes sk-stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}
.inline.required.field.cloudbrain_benchmark {
display: none;
}
}
</style>

<div id="mask">


+ 0
- 178
templates/repo/modelarts/trainjob/index.tmpl View File

@@ -2,184 +2,6 @@
{{template "base/head" .}}

<style>
.selectcloudbrain .active.item{
color: #0087f5 !important;
border: 1px solid #0087f5;
margin: -1px;
background: #FFF !important;
}
#deletemodel {
width: 100%;
height: 100%;
}
/* 弹窗 */

#mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: #777;
z-index: 1000;
display: none;
opacity: 0.8;
-moz-opacity: 0.5;
padding-top: 100px;
color: #000000
}

#loadingPage {
margin: 200px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;
}

#loadingPage>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

#loadingPage .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}

#loadingPage .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}

#loadingPage .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}

#loadingPage .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}

@keyframes sk-stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}
/* 消息框 */

.alert {
display: none;
position: fixed;
width: 100%;
z-index: 1001;
padding: 15px;
border: 1px solid transparent;
border-radius: 4px;
text-align: center;
font-weight: bold;
}

.alert-success {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}

.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}

.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}

.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}

.pusher {
width: calc(100% - 260px);
box-sizing: border-box;
}
/* 弹窗 (background) */

#imageModal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
/* 弹窗内容 */

.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 30%;
}
/* 关闭按钮 */

.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}

.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}

.dis {
margin-bottom: 20px;
}

.disabled {
cursor: pointer;
pointer-events: none;
}
.fontsize14{
font-size: 14px;
}


+ 1
- 81
templates/repo/modelarts/trainjob/new.tmpl View File

@@ -47,87 +47,7 @@
border-radius: 5px 0px 0px 5px;
line-height: 21px;
text-align: center;
color: #C2C7CC;"
}
#mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: #777;
z-index: 1000;
display: none;
opacity: 0.8;
-moz-opacity: 0.5;
padding-top: 100px;
color: #000000
}
/* 加载圈css效果图 */
#loadingPage {
margin: 200px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;
}
#loadingPage>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}
#loadingPage .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
#loadingPage .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
#loadingPage .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
#loadingPage .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.left2{
margin-left: -2px;
}
@-webkit-keyframes sk-stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}
@keyframes sk-stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
color: #C2C7CC;
}

</style>


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

@@ -49,87 +49,6 @@
text-align: center;
color: #C2C7CC;"
}
#mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: #777;
z-index: 1000;
display: none;
opacity: 0.8;
-moz-opacity: 0.5;
padding-top: 100px;
color: #000000
}
/* 加载圈css效果图 */
#loadingPage {
margin: 200px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;
}
#loadingPage>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}
#loadingPage .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
#loadingPage .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
#loadingPage .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
#loadingPage .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.left2{
margin-left: -2px;
}
@-webkit-keyframes sk-stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}
@keyframes sk-stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}

</style>
<!-- <div class="ui page dimmer">


+ 299
- 0
templates/repo/modelmanage/index.tmpl View File

@@ -0,0 +1,299 @@
<!-- 头部导航栏 -->
{{template "base/head" .}}
<style>
.width70{
width: 70% !important;
}
.width83{
width: 83% !important;
}
.content-padding{
padding: 40px !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>
{{$repository := .Repository.ID}}
<!-- 提示框 -->
<div class="alert"></div>

<div class="repository release dataset-list view">
{{template "repo/header" .}}
<!-- 列表容器 -->
<div class="ui container active loader" id="loadContainer">
{{template "base/alert" .}}
<div class="ui two column stackable grid ">
<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>
</div>
</div>

<!-- 中下列表展示区 -->
<div class="ui grid">
<div class="row" style="padding-top: 0;">
<div class="ui sixteen wide column">
<!-- 任务展示 -->
<div class="dataset list" id="model_list">
<!-- 表头 -->
</div>
</div>
</div>
</div>

</div>

</div>

<!-- div full height-->
</div>



<!-- 确认模态框 -->
<div id="deletemodel">
<div class="ui basic modal first">
<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 id="newmodel">
<div class="ui modal second">
<div class="header" style="padding: 1rem;background-color: rgba(240, 240, 240, 100);">
<h4>导入新模型</h4>
</div>
<div class="content content-padding">
<form id="formId" method="POST" class="ui form">
<div class="ui error message">
<!-- <p>asdasdasd</p> -->
</div>
<input type="hidden" name="_csrf" value="">
<div class="two inline fields ">
<div class="required ten wide field">
<label style="margin-left: -23px;">选择训练任务</label>
<div class="ui dropdown selection search width83 loading" id="choice_model">
<input type="hidden" id="JobId" name="JobId" required>
<div class="default text">选择训练任务</div>
<i class="dropdown icon"></i>
<div class="menu" id="job-name">
</div>
</div>
</div>
<div class="required six widde field">
<label>版本</label>
<div class="ui dropdown selection search width70" id="choice_version">
<input type="hidden" id="VersionName" name="VersionName" required>
<div class="default text">选择版本</div>
<i class="dropdown icon"></i>
<div class="menu" id="job-version">
</div>
</div>
</div>
</div>

<div class="required inline field" id="modelname">
<label>模型名称</label>
<input style="width: 45%;" id="name" name="Name" required maxlength="25" onkeyup="this.value=this.value.replace(/[, ]/g,'')">
</div>
<div class="required inline field" id="verionname">
<label>模型版本</label>
<input style="width: 45%;" id="version" name="Version" value="" readonly required maxlength="255">
</div>
<div class="inline field">
<label>模型标签</label>
<input style="width: 83%;margin-left: 7px;" name="Label" maxlength="255">
</div>
<div class="inline field">
<label for="description">模型描述</label>
<textarea style="width: 83%;margin-left: 7px;" 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, 256)"></textarea>
</div>

<div class="inline field" style="margin-left: 75px;">
<button id="submitId" type="button" class="ui create_train_job green button" style="position: absolute;">
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
</div>
</form>
<div class="actions" style="display: inline-block;margin-left: 180px;">
<button class="ui button cancel" >{{.i18n.Tr "repo.cloudbrain.cancel"}}</button>
</div>
</div>
</div>


{{template "base/footer" .}}

<script>
let repolink = {{.RepoLink}}
let repoId = {{$repository}}
let url_href = window.location.pathname.split('show_model')[0] + 'create_model'
const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config;
$('input[name="_csrf"]').val(csrf)

function createModelName(){
let repoName = location.pathname.split('/')[2]
let modelName = repoName + '_model_' + Math.random().toString(36).substr(2, 4)
$('#name').val(modelName)
$('#version').val("0.0.1")
}
function showcreate(obj){
$('.ui.modal.second')
.modal({
centered: false,
onShow:function(){
$('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"})
$("#job-name").empty()
createModelName()
loadTrainList()
},
onHide:function(){
document.getElementById("formId").reset();
$('#choice_model').dropdown('clear')
$('#choice_version').dropdown('clear')
$('.ui.dimmer').css({"background-color":""})
$('.ui.error.message').css('display','none')
}
})
.modal('show')
}

$(function(){
$('#choice_model').dropdown({
onChange:function(value){
$(".ui.dropdown.selection.search.width70").addClass("loading")
$("#job-version").empty()
loadTrainVersion(value)
}
})
})
function versionAdd(version){
let versionArray = version.split('.')
if(versionArray[2]=='9'){
if(versionArray[1]=='9'){
versionArray[0] = String(Number(versionArray[1])+1)
versionArray[1] = '0'
}else{
versionArray[1]=String(Number(versionArray[1])+1)
}
versionArray[2]='0'
}else{
versionArray[2]=String(Number(versionArray[2])+1)
}
return versionArray.join('.')
}
function loadTrainList(){
$.get(`${repolink}/modelmanage/query_train_job?repoId=${repoId}`, (data) => {
const n_length = data.length
let train_html=''
for (let i=0;i<n_length;i++){
train_html += `<div class="item" data-value="${data[i].JobID}">${data[i].JobName}</div>`
train_html += '</div>'
}
$("#job-name").append(train_html)
$(".ui.dropdown.selection.search.width83").removeClass("loading")
})
}
function loadTrainVersion(value){
$.get(`${repolink}/modelmanage/query_train_job_version?JobID=${value}`, (data) => {
const n_length = data.length
let train_html=''
for (let i=0;i<n_length;i++){
train_html += `<div class="item" data-value="${data[i].VersionName}">${data[i].VersionName}</div>`
train_html += '</div>'
}
$("#job-version").append(train_html)
$(".ui.dropdown.selection.search.width70").removeClass("loading")
})
}

function check(){
let jobid = document.getElementById("JobId").value
let versionname = document.getElementById("VersionName").value
let name= document.getElementById("name").value
let version= document.getElementById("version").value
if(jobid==""){
$(".required.ten.wide.field").addClass("error")
return false
}else{
$(".required.ten.wide.field").removeClass("error")
}
if(versionname==""){
$(".required.six.widde.field").addClass("error")
return false
}else{
$(".required.six.widde.field").removeClass("error")
}
if(name==""){
$("#modelname").addClass("error")
return false
}else{
$("#modelname").removeClass("error")
}
if(versionname==""){
$("#verionname").addClass("error")
return false
}else{
$("#verionname").removeClass("error")
}
return true
}
$('#submitId').click(function(){
let flag=check()
if(flag){
let data = $("#formId").serialize()
$("#mask").css({"display":"block","z-index":"9999"})
$.ajax({
url:url_href,
type:'POST',
data:data,
success:function(res){
$('.ui.modal.second').modal('hide')
window.location.href=location.href
},
error: function(xhr){
// 隐藏 loading
// 只有请求不正常(状态码不为200)才会执行
console.log("-------------",xhr)
$('.ui.error.message').text(xhr.responseText)
$('.ui.error.message').css('display','block')
},
complete:function(xhr){
$("#mask").css({"display":"none","z-index":"1"})
}
})
}else{
return false
}
})
</script>


+ 218
- 0
templates/repo/modelmanage/showinfo.tmpl View File

@@ -0,0 +1,218 @@
{{template "base/head" .}}
<div class="repository">
{{template "repo/header" .}}
<style>
.model_header_text{
font-size: 14px;
color: #101010;
font-weight: bold;
}
.ti_form{
text-align: left;
max-width: 100%;
vertical-align: middle;
}
.ti-text-form-label {
padding-bottom: 20px;
padding-right: 20px;
color: #8a8e99;
font-size: 14px;
white-space: nowrap !important;
width: 80px;
line-height: 30px;
}
.ti-text-form-content {
line-height: 30px;
padding-bottom: 20px;
width: 100%;
}
.change-version{
min-width: auto !important;
border: 1px solid rgba(187, 187, 187, 100) !important;
border-radius: .38571429rem !important;
margin-left: 1.5em;
}
.title-word-elipsis{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 30%;
}
.word-elipsis{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 80px;
}
.half-table{
width: 50%;
float: left;
}
.text-width80 {
width: 100px;
line-height: 30px;
}
.tableStyle{
width:100%;
table-layout: fixed;
}
.iword-elipsis{
display: inline-block;
width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
<div class="ui container">
<h4 class="ui header" id="vertical-segment">
<!-- <a href="javascript:window.history.back();"><i class="arrow left icon"></i>返回</a> -->
<div class="ui breadcrumb">
<a class="section" href="{{$.RepoLink}}/modelmanage/show_model">
模型管理
</a>
<div class="divider"> / </div>
<div class="active section">{{.name}}</div>
</div>
<select class="ui dropdown tiny change-version" id="dropdown" onchange="changeInfo(this.value)">
</select>
</h4>
<div id="showInfo" style="border:1px solid #e2e2e2;padding: 20px 60px;margin-top:24px">
</div>
</div>
</div>
{{template "base/footer" .}}
<script>
let url = location.href.split('show_model')[0]
let ID = location.search.split('?name=').pop()
$(document).ready(loadInfo);
function changeInfo(version){
$.get(`${url}show_model_info_api?name=${ID}`,(data)=>{
let versionData = data.filter((item)=>{
return item.Version === version
})
let initObj = transObj(versionData)[0]
let initModelAcc = transObj(versionData)[1]
let id= transObj(data)[2]
$('#showInfo').empty()
renderInfo(initObj,initModelAcc,id)
})
}
function loadInfo(){
$.get(`${url}show_model_info_api?name=${ID}`,(data)=>{
let html = ''
for (let i=0;i<data.length;i++){
html += `<option value="${data[i].Version}">${data[i].Version}</option>`
}
$('#dropdown').append(html)
let initObj = transObj(data)[0]
let initModelAcc = transObj(data)[1]
let id= transObj(data)[2]
renderInfo(initObj,initModelAcc,id)
})
}
function transObj(data){
let {ID,Name,Version,Label,Size,Description,CreatedUnix,Accuracy} = data[0]
let modelAcc = JSON.parse(Accuracy)
let size = tranSize(Size)
let time = transTime(CreatedUnix)
let initObj = {
ModelName:Name || '--',
Version:Version,
Label:Label || '--',
Size:size,
CreateTime:time,
Description:Description || '--',
}
let initModelAcc = {
Accuracy: modelAcc.Accuracy || '--',
F1: modelAcc.F1 || '--',
Precision:modelAcc.Precision || '--',
Recall: modelAcc.Recall || '--'
}
return [initObj,initModelAcc,ID]
}
function transTime(time){
let date = new Date(time * 1000);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
let Y = date.getFullYear() + '-';
let M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1):date.getMonth()+1) + '-';
let D = (date.getDate()< 10 ? '0'+date.getDate():date.getDate())+ ' ';
let h = (date.getHours() < 10 ? '0'+date.getHours():date.getHours())+ ':';
let m = (date.getMinutes() < 10 ? '0'+date.getMinutes():date.getMinutes()) + ':';
let s = date.getSeconds() < 10 ? '0'+date.getSeconds():date.getSeconds();
return Y+M+D+h+m+s;
}
function tranSize(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(2);//保留的小数位数
return size+unitArr[index];
}
function editorFn(text,id){
$('#edit-td').replaceWith("<div id='edit-div' style='width:80%;display: inline-block;'><textarea id='textarea-value' rows='3' maxlength='255' style='width:80%;' id='edit-text'></textarea><i class='check icon' style='color: #50d4ab;' onclick='editorSure(\"" + text + "\",\"" + id + "\")'></i><i class='times icon' style='color: #f66f6a;' onclick='editorCancel(\"" + text + "\",\"" + id + "\")'></i></div>");
}
function editorCancel(text,id){
$('#edit-div').replaceWith('<div id="edit-td" style="display:flex;"><span title="\'' + text + '\'" class="iword-elipsis">'+text+'</span><i class="pencil alternate icon" style="cursor:pointer;vertical-align: top;" id="editor" onclick="editorFn(\'' + text + '\',\'' + id + '\')"></div>')
}
function editorSure(text,id){
let description=$('#textarea-value').val()
let data = {
ID:id,
Description:description
}
$.ajax({
url:`${url}modify_model`,
type:'PUT',
data:data
}).done((res)=>{
$('#edit-div').replaceWith('<div id="edit-td" style="display:flex;"><span title="\'' + description + '\'" class="iword-elipsis">'+description+'</span><i class="pencil alternate icon" style="cursor:pointer;vertical-align: top;" id="editor" onclick="editorFn(\'' + description + '\',\'' + id + '\')"></div>')
})

}
function renderInfo(obj,accObj,id){
let html = ''
html += '<div class="half-table">'
html += '<span class="model_header_text">基本信息</span>'
html += '<table class="tableStyle" style="margin-top:20px;">'
html += '<tbody>'
for(let key in obj){
html += '<tr style="font-size: 12px;">'
html += `<td class="ti-text-form-label text-width80">${key}</td>`
if(key==="Description"){
let description = obj[key]
html += '<td class="ti-text-form-content" ><div id="edit-td" style="display:flex"><span title="\'' + description + '\'" class="iword-elipsis">'+description+'</span><i class="pencil alternate icon" style="cursor:pointer;vertical-align: top;" id="editor" onclick="editorFn(\'' + description + '\',\'' + id + '\')"></div></td>'
}else{
html += `<td class="ti-text-form-content word-elipsis"><span title="${obj[key]}">${obj[key]}</span></td>`
}
html += '</tr>'
}

html += '</tbody>'
html += '</table>'
html += '</div>'
html += '<div class="half-table">'
html += '<span class="model_header_text">模型精度</span>'
html += '<table class="tableStyle" style="margin-top:20px;">'
html += '<tbody>'
for(let key in accObj){
html += '<tr style="font-size: 12px;">'
html += `<td class="ti-text-form-label text-width80">${key}</td>`
html += `<td class="ti-text-form-content word-elipsis">${accObj[key]}</td>`
html += '</tr>'
}
html += '</tbody>'
html += '</table>'
html += '</div>'
html += '<div style="clear: both;"></div>'
$('#showInfo').append(html)
}

</script>

+ 8
- 1
templates/repo/settings/options.tmpl View File

@@ -158,7 +158,14 @@
<label>{{.i18n.Tr "repo.settings.cloudbrain_desc"}}</label>
</div>
</div>

{{$isModelMangeEnabled := .Repository.UnitEnabled $.UnitTypeModelManage }}
<div class="inline field">
<label>{{.i18n.Tr "repo.model_manager"}}</label>
<div class="ui checkbox">
<input class="enable-system" name="enable_model_manager" type="checkbox" {{if $isModelMangeEnabled}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.model_desc"}}</label>
</div>
</div>
{{$isWikiEnabled := or (.Repository.UnitEnabled $.UnitTypeWiki) (.Repository.UnitEnabled $.UnitTypeExternalWiki)}}
<div class="inline field">
<label>{{.i18n.Tr "repo.wiki"}}</label>


+ 427
- 0
web_src/js/components/Model.vue View File

@@ -0,0 +1,427 @@
<template>
<div>
<div class="ui container" id="header">
<el-row style="margin-top:15px;" v-loading.fullscreen.lock="fullscreenLoading">
<el-table
ref="table"
:data="tableData"
style="min-width: 100%"
row-key="ID"
lazy
:load="load"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
:header-cell-style="tableHeaderStyle"
>
<el-table-column
prop="Name"
label="模型名称"
align="left"
min-width="18%"
>
<template slot-scope="scope">
<div class="expand-icon" v-if="scope.row.hasChildren===false">
<i class="el-icon-arrow-right"></i>
</div>
<!-- <i class="el-icon-time"></i> -->
<a class="text-over" :href="showinfoHref+scope.row.Name" :title="scope.row.Name">{{ scope.row.Name }}</a>
</template>
</el-table-column>
<el-table-column
prop="Version"
label="版本"
align="center"
min-width="6.5%"
>
<template slot-scope="scope">
<span class="text-over" :title="scope.row.Version">{{ scope.row.Version}}</span>
</template>
</el-table-column>
<el-table-column
prop="VersionCount"
label="版本数"
align="center"
min-width="7.5%"
>
<template slot-scope="scope">
<span class="text-over" :title="scope.row.VersionCount">{{ scope.row.VersionCount}}</span>
</template>
</el-table-column>

<el-table-column
prop="Size"
label="模型大小"
align="center"
min-width="10.5%"
>
<template slot-scope="scope">
<span class="text-over">{{ renderSize(scope.row.Size)}}</span>
</template>
</el-table-column>
<el-table-column
prop="EngineName"
label="AI引擎"
align="center"
min-width="8.5%"
>
<template slot-scope="scope">
<span class="text-over">{{ scope.row.EngineName}}</span>
</template>
</el-table-column>
<el-table-column
prop="ComputeResource"
label="计算资源"
align="center"
min-width="10.5%"
>
<template slot-scope="scope">
<span class="text-over">{{ scope.row.ComputeResource}}</span>
</template>
</el-table-column>
<el-table-column
prop="CreatedUnix"
label="创建时间"
align="center"
min-width="13.75%"
>
<template slot-scope="scope">
{{transTime(scope.row.CreatedUnix)}}
</template>
</el-table-column>
<el-table-column
prop="UserName"
label="创建者"
align="center"
min-width="6.75%"
>
<template slot-scope="scope">
<a :href="'/'+scope.row.UserName" :title="scope.row.UserName">
<img class="ui avatar image" :src="scope.row.UserRelAvatarLink">
</a>
</template>
</el-table-column>

<el-table-column label="操作" min-width="18%" align="center">
<template slot-scope="scope">
<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)">创建新版本</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>
</div>
</template>
</el-table-column>
</el-table>
</el-row>
<div class="ui container" style="margin-top:50px;text-align:center">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalNum">
</el-pagination>
</div>
</div>
</div>

</template>

<script>

const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config;

export default {
components: {
},
data() {
return {
currentPage:1,
pageSize:10,
totalNum:0,
params:{page:0,pageSize:10},
tableData: [],
fullscreenLoading: false,
url:'',
isLoading:true,
loadNodeMap:new Map()
};
},
methods: {
load(tree, treeNode, resolve) {
this.loadNodeMap.set(tree.cName, tree.ID)
this.$axios.get(this.url+'show_model_child_api',{params:{
name:tree.cName
}}).then((res)=>{
let TrainTaskInfo
let tableData
tableData= res.data
for(let i=0;i<tableData.length;i++){
TrainTaskInfo = JSON.parse(tableData[i].TrainTaskInfo)
tableData[i].EngineName = TrainTaskInfo.EngineName.split('-')[0]
tableData[i].ComputeResource = TrainTaskInfo.ComputeResource
tableData[i].cName=tableData[i].Name
tableData[i].Name=''
tableData[i].VersionCount = ''
tableData[i].Children = true
}
resolve(tableData)
})
},
tableHeaderStyle({row,column,rowIndex,columnIndex}){
if(rowIndex===0){
return 'background:#f5f5f6;color:#606266'
}
},
handleSizeChange(val){
this.params.size = val
this.getModelList()
},
handleCurrentChange(val){
this.params.page = val
this.getModelList()
},
showcreateVue(name,version){
$('.ui.modal.second')
.modal({
centered: false,
onShow:function(){
$('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"})
$("#job-name").empty()
$('#name').val(name)
let version_string = versionAdd(version)
$('#version').val(version_string)
loadTrainList()
},
onHide:function(){
document.getElementById("formId").reset();
$('#choice_model').dropdown('clear')
$('#choice_version').dropdown('clear')
$('.ui.dimmer').css({"background-color":""})
}
})
.modal('show')
},
deleteModel(id,name){
let tree={cName:name}
let _this = this
let flag=1
$('.ui.basic.modal.first')
.modal({
onDeny: function() {
flag = false
},
onApprove: function() {
_this.$axios.delete(_this.url+'delete_model',{
params:{
ID:id
}}).then((res)=>{
_this.getModelList()
_this.loadrefresh(tree)
})
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
},
loadrefresh(tree){
this.$axios.get(this.url+'show_model_child_api',{params:{
name:tree.cName
}}).then((res)=>{
let TrainTaskInfo
let tableData
tableData= res.data
for(let i=0;i<tableData.length;i++){
TrainTaskInfo = JSON.parse(tableData[i].TrainTaskInfo)
tableData[i].EngineName = TrainTaskInfo.EngineName.split('-')[0]
tableData[i].ComputeResource = TrainTaskInfo.ComputeResource
tableData[i].cName=tableData[i].Name
tableData[i].Name=''
tableData[i].VersionCount = ''
tableData[i].Children = true
}
let id = this.loadNodeMap.get(tree.cName)
this.$set(this.$refs.table.store.states.lazyTreeNodeMap, id, tableData)
})
},
getModelList(){
this.fullscreenLoading = false
this.$axios.get(location.href+'_api',{
params:this.params
}).then((res)=>{
$("#loadContainer").removeClass("loader")
let TrainTaskInfo
this.tableData = res.data.data
for(let i=0;i<this.tableData.length;i++){
TrainTaskInfo = JSON.parse(this.tableData[i].TrainTaskInfo)
this.tableData[i].cName=this.tableData[i].Name
this.tableData[i].EngineName = TrainTaskInfo.EngineName.split('-')[0]
this.tableData[i].ComputeResource = TrainTaskInfo.ComputeResource
this.tableData[i].hasChildren = res.data.data[i].VersionCount===1 ? false : true
}
this.totalNum = res.data.count
this.fullscreenLoading = false
})
},
},
computed:{
loadhref(){
return this.url+'downloadall?ID='
},
showinfoHref(){
return this.url + 'show_model_info?name='
},
transTime(){
return function(time){
let date = new Date(time * 1000);//时间戳为10位需*1000,时间戳为13位的话不需乘1000
let Y = date.getFullYear() + '-';
let M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1):date.getMonth()+1) + '-';
let D = (date.getDate()< 10 ? '0'+date.getDate():date.getDate())+ ' ';
let h = (date.getHours() < 10 ? '0'+date.getHours():date.getHours())+ ':';
let m = (date.getMinutes() < 10 ? '0'+date.getMinutes():date.getMinutes()) + ':';
let s = date.getSeconds() < 10 ? '0'+date.getSeconds():date.getSeconds();
return Y+M+D+h+m+s;
}
},
renderSize(){
return function(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(2);//保留的小数位数
return size+unitArr[index];
}
}
},
mounted() {
this.getModelList()
this.url = location.href.split('show_model')[0]
}

};
</script>

<style scoped>






/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active {
background-color: #5bb973;
color: #FFF;
}
/deep/ .el-pagination.is-background .el-pager li.active {
color: #fff;
cursor: default;
}
/deep/ .el-pagination.is-background .el-pager li:hover {
color: #5bb973;
}
/deep/ .el-pagination.is-background .el-pager li:not(.disabled):hover {
color: #5bb973;
}
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active:hover {
background-color: #5bb973;
color: #FFF;
}

/deep/ .el-pager li.active {
color: #08C0B9;
cursor: default;
}
/deep/ .el-pagination .el-pager li:hover {
color: #08C0B9;
}
/deep/ .el-pagination .el-pager li:not(.disabled):hover {
color: #08C0B9;
}

.text-over{
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
.el-icon-arrow-right{
font-family: element-icons!important;
speak: none;
font-style: normal;
font-weight: 400;
font-feature-settings: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
vertical-align: middle;
display: inline-block;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
border: 1px solid #D4D4D5;
border-radius: 50%;
color: #D4D4D5;
margin-right: 4px;
}
.el-icon-arrow-right::before{
content: "\e6e0";
}
.expand-icon{
display: inline-block;
width: 20px;
line-height: 20px;
height: 20px;
text-align: center;
margin-right: 3px;
font-size: 12px;
}
/deep/ .el-table_1_column_1.is-left .cell {padding-right: 0px !important;}
/deep/ .el-table__expand-icon .el-icon-arrow-right{
font-family: element-icons!important;
speak: none;
font-style: normal;
font-weight: 400;
font-feature-settings: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
vertical-align: middle;
display: inline-block;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
border: 1px solid #3291F8;
border-radius: 50%;
color: #3291F8;
margin-right: 4px;
}
.space-around{
display: flex;
justify-content: space-around;
}
.disabled {
cursor: default;
pointer-events: none;
color: rgba(0,0,0,.6) !important;
opacity: .45 !important;
}

</style>

+ 18
- 2
web_src/js/index.js View File

@@ -39,6 +39,7 @@ import Images from './components/Images.vue';
import EditTopics from './components/EditTopics.vue';
import DataAnalysis from './components/DataAnalysis.vue'
import Contributors from './components/Contributors.vue'
import Model from './components/Model.vue';

Vue.use(ElementUI);
Vue.prototype.$axios = axios;
@@ -2916,11 +2917,12 @@ $(document).ready(async () => {
initVueEditTopic();
initVueContributors();
initVueImages();
initVueModel();
initVueDataAnalysis();
initTeamSettings();
initCtrlEnterSubmit();
initNavbarContentToggle();
// initTopicbar();
// initTopicbar();vim
// closeTopicbar();
initU2FAuth();
initU2FRegister();
@@ -3647,7 +3649,7 @@ function initVueContributors() {

function initVueImages() {
const el = document.getElementById('images');
console.log("el",el)
if (!el) {
return;
@@ -3659,6 +3661,20 @@ function initVueImages() {
render: h => h(Images)
});
}
function initVueModel() {
const el = document.getElementById('model_list');
if (!el) {
return;
}

new Vue({
el: el,
render: h => h(Model)
});
}
function initVueDataAnalysis() {
const el = document.getElementById('data_analysis');
console.log("el",el)


+ 183
- 0
web_src/less/openi.less View File

@@ -335,4 +335,187 @@ display: block;
margin-left: 4px !important;
color: #3291F8;
}


.selectcloudbrain .active.item{
color: #0087f5 !important;
border: 1px solid #0087f5;
margin: -1px;
background: #FFF !important;
}
#deletemodel {
width: 100%;
height: 100%;
}
/* 弹窗 */

#mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: #777;
z-index: 1000;
display: none;
opacity: 0.8;
-moz-opacity: 0.5;
padding-top: 100px;
color: #000000
}

#loadingPage {
margin: 200px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;
}

#loadingPage>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

#loadingPage .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}

#loadingPage .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}

#loadingPage .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}

#loadingPage .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}

@keyframes sk-stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}
/* 消息框 */

.alert {
display: none;
position: fixed;
width: 100%;
z-index: 1001;
padding: 15px;
border: 1px solid transparent;
border-radius: 4px;
text-align: center;
font-weight: bold;
}

.alert-success {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}

.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}

.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}

.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}

.pusher {
width: calc(100% - 260px);
box-sizing: border-box;
}
/* 弹窗 (background) */

#imageModal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.4);
}
/* 弹窗内容 */

.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 30%;
}
/* 关闭按钮 */

.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}

.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}

.dis {
margin-bottom: 20px;
}

.disabled {
cursor: pointer;
pointer-events: none;
}
.letf2{
margin-left: -2px;
}

Loading…
Cancel
Save