Browse Source

Merge pull request 'V20211213' (#1142) from V20211213 into develop

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/1142
tags/v1.21.12.1
lewis 3 years ago
parent
commit
f56299061d
87 changed files with 4842 additions and 2225 deletions
  1. +31
    -31
      custom/conf/app.ini.sample
  2. +202
    -0
      models/ai_model_manage.go
  3. +56
    -5
      models/cloudbrain.go
  4. +4
    -0
      models/dataset.go
  5. +1
    -0
      models/models.go
  6. +52
    -40
      models/repo.go
  7. +16
    -0
      models/repo_unit.go
  8. +14
    -0
      models/unit.go
  9. +16
    -5
      models/user.go
  10. +553
    -313
      models/user_business_analysis.go
  11. +22
    -0
      modules/auth/auth.go
  12. +1
    -0
      modules/auth/repo_form.go
  13. +76
    -5
      modules/cloudbrain/cloudbrain.go
  14. +1
    -2
      modules/context/auth.go
  15. +1
    -0
      modules/context/context.go
  16. +1
    -0
      modules/context/repo.go
  17. +16
    -3
      modules/cron/tasks_basic.go
  18. +9
    -0
      modules/git/commit.go
  19. +4
    -0
      modules/git/repo.go
  20. +2
    -2
      modules/git/repo_stats_custom.go
  21. +56
    -6
      modules/migrations/github.go
  22. +8
    -4
      modules/modelarts/modelarts.go
  23. +8
    -3
      modules/normalization/normalization.go
  24. +21
    -3
      modules/repository/repo.go
  25. +21
    -18
      modules/setting/git.go
  26. +1
    -0
      modules/setting/indexer.go
  27. +6
    -0
      modules/setting/migrations.go
  28. +1
    -0
      modules/setting/queue.go
  29. +51
    -35
      modules/setting/setting.go
  30. +1
    -0
      modules/setting/webhook.go
  31. +4
    -0
      modules/storage/local.go
  32. +6
    -0
      modules/storage/minio.go
  33. +207
    -25
      modules/storage/obs.go
  34. +4
    -0
      modules/storage/storage.go
  35. +24
    -0
      options/locale/locale_en-US.ini
  36. +26
    -3
      options/locale/locale_zh-CN.ini
  37. +1
    -0
      public/img/member.svg
  38. +1
    -0
      public/img/overview_rgb.svg
  39. +1
    -0
      public/img/pro_num.svg
  40. +1
    -0
      public/img/pro_rgb.svg
  41. +1
    -0
      public/img/search.svg
  42. +1
    -0
      public/img/user_rgb.svg
  43. +12
    -8
      routers/api/v1/api.go
  44. +18
    -16
      routers/api/v1/repo/modelarts.go
  45. +4
    -3
      routers/api/v1/repo/repo_dashbord.go
  46. +42
    -0
      routers/home.go
  47. +6
    -0
      routers/init.go
  48. +1
    -1
      routers/private/hook.go
  49. +571
    -0
      routers/repo/ai_model_manage.go
  50. +191
    -77
      routers/repo/cloudbrain.go
  51. +3
    -34
      routers/repo/dir.go
  52. +1
    -1
      routers/repo/http.go
  53. +1
    -1
      routers/repo/issue.go
  54. +88
    -65
      routers/repo/modelarts.go
  55. +1
    -0
      routers/repo/repo.go
  56. +12
    -0
      routers/repo/setting.go
  57. +3
    -9
      routers/repo/user_data_analysis.go
  58. +43
    -18
      routers/routes/routes.go
  59. +47
    -1
      routers/user/profile.go
  60. +64
    -25
      templates/base/head_navbar.tmpl
  61. +59
    -23
      templates/base/head_navbar_fluid.tmpl
  62. +37
    -22
      templates/base/head_navbar_home.tmpl
  63. +216
    -0
      templates/base/head_navbar_pro.tmpl
  64. +208
    -0
      templates/base/head_pro.tmpl
  65. +1
    -1
      templates/explore/repos.tmpl
  66. +19
    -20
      templates/repo/cloudbrain/new.tmpl
  67. +3
    -2
      templates/repo/cloudbrain/show.tmpl
  68. +144
    -160
      templates/repo/debugjob/index.tmpl
  69. +20
    -85
      templates/repo/header.tmpl
  70. +0
    -551
      templates/repo/modelarts/notebook/index.tmpl
  71. +20
    -88
      templates/repo/modelarts/notebook/new.tmpl
  72. +2
    -2
      templates/repo/modelarts/notebook/show.tmpl
  73. +26
    -275
      templates/repo/modelarts/trainjob/index.tmpl
  74. +5
    -84
      templates/repo/modelarts/trainjob/new.tmpl
  75. +19
    -47
      templates/repo/modelarts/trainjob/show.tmpl
  76. +2
    -81
      templates/repo/modelarts/trainjob/version_new.tmpl
  77. +240
    -0
      templates/repo/modelmanage/index.tmpl
  78. +279
    -0
      templates/repo/modelmanage/showinfo.tmpl
  79. +8
    -2
      templates/repo/settings/options.tmpl
  80. +60
    -8
      templates/user/profile.tmpl
  81. +12
    -4
      web_src/js/components/DataAnalysis.vue
  82. +520
    -0
      web_src/js/components/Model.vue
  83. +11
    -1
      web_src/js/components/ProAnalysis.vue
  84. +3
    -3
      web_src/js/components/UserAnalysis.vue
  85. +39
    -2
      web_src/js/index.js
  86. +1
    -0
      web_src/less/_svg.less
  87. +251
    -2
      web_src/less/openi.less

+ 31
- 31
custom/conf/app.ini.sample View File

@@ -1059,50 +1059,50 @@ RESULT_BACKEND = redis://localhost:6379
[cloudbrain]
USERNAME =
PASSWORD =
REST_SERVER_HOST = http://192.168.202.73
JOB_PATH = /datasets/minio/data/opendata/jobs/
DEBUG_SERVER_HOST = http://192.168.202.73/
REST_SERVER_HOST =
JOB_PATH =
DEBUG_SERVER_HOST =
; cloudbrain visit opendata
USER = cW4cMtH24eoWPE7X
PWD = 4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DA.C
GPU_TYPE_DEFAULT = openidebug
GPU_TYPES = {"gpu_type":[{"id":1,"queue":"openidebug","value":"T4"},{"id":2,"queue":"openidgx","value":"V100"}]}
USER =
PWD =
GPU_TYPE_DEFAULT =
GPU_TYPES =

[benchmark]
ENABLED = true
BENCHMARKCODE = https://yangzhx:justfortest123@git.openi.org.cn/yangzhx/detection_benchmark_script.git
HOST = http://192.168.202.90:3366/
ENABLED =
BENCHMARKCODE =
HOST =

[snn4imagenet]
ENABLED = true
SNN4IMAGENETCODE = https://yult:eh2Ten4iLYjFkbj@git.openi.org.cn/ylt/snn4imagenet.git
HOST = http://192.168.202.90:3366/
ENABLED =
SNN4IMAGENETCODE =
HOST =

[decompress]
HOST = http://192.168.207.34:39987
USER = cW4cMtH24eoWPE7X
PASSWORD = 4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC
HOST =
USER =
PASSWORD =

[blockchain]
HOST = http://192.168.207.84:3002/
COMMIT_VALID_DATE = 2021-01-15
HOST =
COMMIT_VALID_DATE =

[obs]
ENDPOINT = https://obs.cn-south-222.ai.pcl.cn
ACCESS_KEY_ID = FDP3LRMHLB9S77VWEHE3
SECRET_ACCESS_KEY = LyM82Wk80pgjhs2z7AdDcsdpCWhbsJtSzQ7hkESN
BUCKET = testopendata
LOCATION = cn-south-222
BASE_PATH = attachment/
ENDPOINT =
ACCESS_KEY_ID =
SECRET_ACCESS_KEY =
BUCKET =
LOCATION =
BASE_PATH =

[modelarts]
ORGANIZATION = modelarts
ENDPOINT = https://modelarts.cn-south-222.ai.pcl.cn
PROJECT_ID = edfccf24aace4e17a56da6bcbb55a5aa
PROJECT_NAME = cn-south-222_test
USERNAME = test1
PASSWORD = Qizhi@test.
DOMAIN = cn-south-222
ORGANIZATION =
ENDPOINT =
PROJECT_ID =
PROJECT_NAME =
USERNAME =
PASSWORD =
DOMAIN =

[radar_map]
impact=0.3


+ 202
- 0
models/ai_model_manage.go View File

@@ -0,0 +1,202 @@
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:"INDEX 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:"INDEX NULL"`
CodeBranch string `xorm:"varchar(400) NULL"`
CodeCommitID string `xorm:"NULL"`
UserId int64 `xorm:"NOT NULL"`
UserName string
UserRelAvatarLink string
TrainTaskInfo string `xorm:"text NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"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("created_unix 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)
}

return aiModelManages, count, nil
}

+ 56
- 5
models/cloudbrain.go View File

@@ -91,6 +91,7 @@ type Cloudbrain struct {
DeletedAt time.Time `xorm:"deleted"`
CanDebug bool `xorm:"-"`
CanDel bool `xorm:"-"`
CanModify bool `xorm:"-"`
Type int

VersionID int64 //版本id
@@ -928,6 +929,58 @@ 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"},
)

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

keys := make(map[string]string)
uniqueElements := make([]*CloudbrainInfo, 0)
for _, entry := range cloudbrains {
if _, value := keys[entry.JobID]; !value {
keys[entry.JobID] = entry.JobName
uniqueElements = append(uniqueElements, entry)
}
}

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

func CloudbrainsVersionList(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int, error) {
sess := x.NewSession()
defer sess.Close()
@@ -1032,13 +1085,13 @@ func GetCloudbrainByJobIDAndIsLatestVersion(jobID string, isLatestVersion string

func GetCloudbrainsNeededStopByUserID(userID int64) ([]*Cloudbrain, error) {
cloudBrains := make([]*Cloudbrain, 0)
err := x.Cols("job_id", "status", "type").Where("user_id=? AND status !=?", userID, string(JobStopped)).Find(&cloudBrains)
err := x.Cols("job_id", "status", "type", "job_type", "version_id").Where("user_id=? AND status !=?", userID, string(JobStopped)).Find(&cloudBrains)
return cloudBrains, err
}

func GetCloudbrainsNeededStopByRepoID(repoID int64) ([]*Cloudbrain, error) {
cloudBrains := make([]*Cloudbrain, 0)
err := x.Cols("job_id", "status", "type").Where("repo_id=? AND status !=?", repoID, string(JobStopped)).Find(&cloudBrains)
err := x.Cols("job_id", "status", "type", "job_type", "version_id").Where("repo_id=? AND status !=?", repoID, string(JobStopped)).Find(&cloudBrains)
return cloudBrains, err
}

@@ -1065,9 +1118,7 @@ func UpdateJob(job *Cloudbrain) error {
}

func updateJob(e Engine, job *Cloudbrain) error {
var sess *xorm.Session
sess = e.Where("job_id = ?", job.JobID)
_, err := sess.Cols("status", "container_id", "container_ip").Update(job)
_, err := e.ID(job.ID).AllCols().Update(job)
return err
}



+ 4
- 0
models/dataset.go View File

@@ -93,6 +93,7 @@ type SearchDatasetOptions struct {
IncludePublic bool
ListOptions
SearchOrderBy
IsOwner bool
}

func CreateDataset(dataset *Dataset) (err error) {
@@ -150,6 +151,9 @@ func SearchDatasetCondition(opts *SearchDatasetOptions) builder.Cond {
}
} else if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"repository.owner_id": opts.OwnerID})
if !opts.IsOwner {
cond = cond.And(builder.Eq{"dataset.status": DatasetStatusPublic})
}
}

return cond


+ 1
- 0
models/models.go View File

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

tablesStatistic = append(tablesStatistic,


+ 52
- 40
models/repo.go View File

@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"html/template"
"xorm.io/xorm"

"code.gitea.io/gitea/modules/blockchain"

@@ -648,53 +649,41 @@ func (repo *Repository) GetAssignees() (_ []*User, err error) {
return repo.getAssignees(x)
}

func (repo *Repository) getReviewersPrivate(e Engine, doerID, posterID int64) (users []*User, err error) {
users = make([]*User, 0, 20)

if err = e.
SQL("SELECT * FROM `user` WHERE id in (SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?)) ORDER BY name",
repo.ID, AccessModeRead,
doerID, posterID).
Find(&users); err != nil {
return nil, err
}

return users, nil
}

func (repo *Repository) getReviewersPublic(e Engine, doerID, posterID int64) (_ []*User, err error) {

users := make([]*User, 0)

const SQLCmd = "SELECT * FROM `user` WHERE id IN ( " +
"SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? AND user_id NOT IN ( ?, ?) " +
"UNION " +
"SELECT user_id FROM `watch` WHERE repo_id = ? AND user_id NOT IN ( ?, ?) AND mode IN (?, ?) " +
") ORDER BY name"

if err = e.
SQL(SQLCmd,
repo.ID, AccessModeRead, doerID, posterID,
repo.ID, doerID, posterID, RepoWatchModeNormal, RepoWatchModeAuto).
Find(&users); err != nil {
return nil, err
}

return users, nil
}

func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) (users []*User, err error) {
if err = repo.getOwner(e); err != nil {
return nil, err
}

if repo.IsPrivate ||
(repo.Owner.IsOrganization() && repo.Owner.Visibility == api.VisibleTypePrivate) {
users, err = repo.getReviewersPrivate(x, doerID, posterID)
if repo.Owner.IsOrganization() {
const SQLCmd = "SELECT * FROM `user` WHERE id IN (" +
"SELECT DISTINCT(t3.user_id) FROM ( " +
"SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ?" +
"UNION select t2.uid as user_id from team t1 left join team_user t2 on t1.id = t2.team_id where t1.org_id = ? and t1.authorize = 4) t3)" +
" AND id NOT IN ( ?, ?) ORDER BY name "

if err = e.
SQL(SQLCmd,
repo.ID, AccessModeWrite, repo.ID, doerID, posterID).
Find(&users); err != nil {
return nil, err
}
} else {
users, err = repo.getReviewersPublic(x, doerID, posterID)
const SQLCmd = "SELECT * FROM `user` WHERE id IN ( " +
"SELECT DISTINCT(t3.user_id) FROM ( " +
"SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? " +
" UNION" +
" SELECT owner_id as user_id FROM `repository` WHERE id = ?) t3)" +
" AND id NOT IN ( ?, ?) ORDER BY name "

if err = e.
SQL(SQLCmd,
repo.ID, AccessModeWrite, repo.ID, doerID, posterID).
Find(&users); err != nil {
return nil, err
}
}
return

return users, nil
}

// GetReviewers get all users can be requested to review
@@ -1118,6 +1107,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,
@@ -1630,6 +1625,9 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
releaseAttachments = append(releaseAttachments, attachments[i].RelativePath())
}

// Delete dataset attachment record and remove related files
deleteDatasetAttachmentByRepoId(sess, repoID)

if err = deleteBeans(sess,
&Access{RepoID: repo.ID},
&Action{RepoID: repo.ID},
@@ -1815,6 +1813,20 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
return nil
}

func deleteDatasetAttachmentByRepoId(sess *xorm.Session, repoId int64) error {
attachments := make([]*Attachment, 0)
if err := sess.Join("INNER", "dataset", "dataset.id = attachment.dataset_id").
Where("dataset.repo_id = ?", repoId).
Find(&attachments); err != nil {
return err
}
if len(attachments) == 0 {
return nil
}
_, err := DeleteAttachments(attachments, true)
return err
}

// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame.
func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) {
return getRepositoryByOwnerAndName(x, ownerName, repoName)


+ 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,
}
)



+ 16
- 5
models/user.go View File

@@ -145,6 +145,7 @@ type User struct {
AllowImportLocal bool // Allow migrate repository by local path
AllowCreateOrganization bool `xorm:"DEFAULT true"`
ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"`
IsOperator bool `xorm:"NOT NULL DEFAULT false"` //运营人员

// Avatar
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
@@ -928,8 +929,17 @@ var (
"template",
"user",
"vendor",
}
reservedUserPatterns = []string{"*.keys", "*.gpg"}
"dashboard",
"operation",
"blockchain",
"avatar",
"swagger.v1.json",
"secure",
"serviceworker.js",
"self",
"repo-avatars",
}
reservedUserPatterns = []string{"*.keys", "*.gpg", "*.png"}
)

// isUsableName checks if name is reserved or pattern of name is not allowed
@@ -1551,11 +1561,11 @@ func GetUserByActivateEmail(email string) (*User, error) {
if err := ctx.e.Join("INNER", "email_address", "email_address.uid = \"user\".id").
Where("email_address.email= ?", email).
Find(&users); err != nil {
return nil,err
return nil, err
}
if len(users) >= 1 {
return &users[0],nil
}else {
return &users[0], nil
} else {
// Finally, if email address is the protected email address:用户邮件地址设置为隐藏电子邮件地址
if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) {
username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress))
@@ -1571,6 +1581,7 @@ func GetUserByActivateEmail(email string) (*User, error) {
return nil, errors.New("cannot find user by email")
}
}

// GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(email string) (*User, error) {
return GetUserByEmailContext(DefaultDBContext(), email)


+ 553
- 313
models/user_business_analysis.go
File diff suppressed because it is too large
View File


+ 22
- 0
modules/auth/auth.go View File

@@ -9,6 +9,9 @@ import (
"reflect"
"strings"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/validation"
@@ -31,6 +34,8 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
return nil, false
}

checkAutoLogin(ctx, sess)

// Try to sign in with each of the enabled plugins
for _, ssoMethod := range sso.Methods() {
if !ssoMethod.IsEnabled() {
@@ -46,6 +51,23 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
return nil, false
}

func checkAutoLogin(ctx *macaron.Context, sess session.Store) {
uid := sess.Get("uid")
if uid == nil {
uname := ctx.GetCookie(setting.CookieUserName)

u, err := models.GetUserByName(uname)
if err == nil {

if val, ok := ctx.GetSuperSecureCookie(
base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); ok && val == u.Name {
sess.Set("uid", u.ID)
}
}
}

}

// Form form binding interface
type Form interface {
binding.Validator


+ 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


+ 76
- 5
modules/cloudbrain/cloudbrain.go View File

@@ -1,8 +1,10 @@
package cloudbrain

import (
"code.gitea.io/gitea/modules/setting"
"errors"
"strconv"

"code.gitea.io/gitea/modules/setting"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
@@ -16,7 +18,7 @@ const (
ModelMountPath = "/model"
BenchMarkMountPath = "/benchmark"
Snn4imagenetMountPath = "/snn4imagenet"
BrainScoreMountPath = "/brainscore"
BrainScoreMountPath = "/brainscore"
TaskInfoName = "/taskInfo"

SubTaskName = "task1"
@@ -28,6 +30,75 @@ var (
ResourceSpecs *models.ResourceSpecs
)

func isAdminOrOwnerOrJobCreater(ctx *context.Context, job *models.Cloudbrain, err error) bool {
if !ctx.IsSigned {
return false
}
log.Info("is repo owner:" + strconv.FormatBool(ctx.IsUserRepoOwner()))
log.Info("is user admin:" + strconv.FormatBool(ctx.IsUserSiteAdmin()))
if err != nil {

return ctx.IsUserRepoOwner() || ctx.IsUserSiteAdmin()
} else {
log.Info("is job creator:" + strconv.FormatBool(ctx.User.ID == job.UserID))
return ctx.IsUserRepoOwner() || ctx.IsUserSiteAdmin() || ctx.User.ID == job.UserID
}

}

func CanDeleteJob(ctx *context.Context, job *models.Cloudbrain) bool {

return isAdminOrOwnerOrJobCreater(ctx, job, nil)
}

func CanCreateOrDebugJob(ctx *context.Context) bool {
if !ctx.IsSigned {
return false
}
return ctx.Repo.CanWrite(models.UnitTypeCloudBrain)
}

func CanModifyJob(ctx *context.Context, job *models.Cloudbrain) bool {

return isAdminOrJobCreater(ctx, job, nil)
}

func isAdminOrJobCreater(ctx *context.Context, job *models.Cloudbrain, err error) bool {
if !ctx.IsSigned {
return false
}
if err != nil {
return ctx.IsUserSiteAdmin()
} else {
return ctx.IsUserSiteAdmin() || ctx.User.ID == job.UserID
}

}

func AdminOrOwnerOrJobCreaterRight(ctx *context.Context) {

var jobID = ctx.Params(":jobid")

job, err := models.GetCloudbrainByJobID(jobID)

if !isAdminOrOwnerOrJobCreater(ctx, job, err) {

ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
}

}

func AdminOrJobCreaterRight(ctx *context.Context) {

var jobID = ctx.Params(":jobid")
job, err := models.GetCloudbrainByJobID(jobID)
if !isAdminOrJobCreater(ctx, job, err) {

ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
}

}

func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, brainScorePath, jobType, gpuQueue string, resourceSpecId int) error {
dataActualPath := setting.Attachment.Minio.RealPath +
setting.Attachment.Minio.Bucket + "/" +
@@ -46,7 +117,7 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath,
log.Error("no such resourceSpecId(%d)", resourceSpecId, ctx.Data["MsgID"])
return errors.New("no such resourceSpec")
}
jobResult, err := CreateJob(jobName, models.CreateJobParams{
JobName: jobName,
RetryCount: 1,
@@ -131,8 +202,8 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath,
JobName: jobName,
SubTaskName: SubTaskName,
JobType: jobType,
Type: models.TypeCloudBrainOne,
Uuid: uuid,
Type: models.TypeCloudBrainOne,
Uuid: uuid,
})

if err != nil {


+ 1
- 2
modules/context/auth.go View File

@@ -145,8 +145,7 @@ func Toggle(options *ToggleOptions) macaron.Handler {
}

if options.OperationRequired {
//todo: add isOperator judgement
if !ctx.User.IsAdmin {
if !ctx.User.IsOperator {
ctx.Error(403)
return
}


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

@@ -310,6 +310,7 @@ func Contexter() macaron.Handler {
ctx.Data["SignedUserID"] = ctx.User.ID
ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
ctx.Data["IsOperator"] = ctx.User.IsOperator
c.Data["SignedUserName"] = ctx.User.Name
} else {
ctx.Data["SignedUserID"] = int64(0)


+ 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
}
}

+ 16
- 3
modules/cron/tasks_basic.go View File

@@ -134,7 +134,7 @@ func registerHandleBlockChainUnSuccessRepos() {
RegisterTaskFatal("handle_blockchain_unsuccess_repos", &BaseConfig{
Enabled: true,
RunAtStart: true,
Schedule: "@every 1m",
Schedule: "@every 10m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.HandleBlockChainUnSuccessRepos()
return nil
@@ -145,7 +145,7 @@ func registerHandleBlockChainMergedPulls() {
RegisterTaskFatal("handle_blockchain_merged_pull", &BaseConfig{
Enabled: true,
RunAtStart: true,
Schedule: "@every 1m",
Schedule: "@every 10m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.HandleBlockChainMergedPulls()
return nil
@@ -156,7 +156,7 @@ func registerHandleBlockChainUnSuccessCommits() {
RegisterTaskFatal("handle_blockchain_unsuccess_commits", &BaseConfig{
Enabled: true,
RunAtStart: true,
Schedule: "@every 3m",
Schedule: "@every 10m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.HandleBlockChainUnSuccessCommits()
return nil
@@ -185,6 +185,17 @@ func registerHandleSummaryStatistic() {
})
}

func registerSyncCloudbrainStatus() {
RegisterTaskFatal("sync_cloudbrain_status", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@every 10m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.SyncCloudbrainStatus()
return nil
})
}

func initBasicTasks() {
registerUpdateMirrorTask()
registerRepoHealthCheck()
@@ -202,4 +213,6 @@ func initBasicTasks() {

registerHandleRepoAndUserStatistic()
registerHandleSummaryStatistic()

registerSyncCloudbrainStatus()
}

+ 9
- 0
modules/git/commit.go View File

@@ -271,6 +271,15 @@ func AllCommitsCount(repoPath string) (int64, error) {
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}

func HEADCommitsCount(repoPath string) (int64, error) {
stdout, err := NewCommand("rev-list", "HEAD", "--count").RunInDir(repoPath)
if err != nil {
return 0, err
}

return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}

func commitsCount(repoPath, revision, relpath string) (int64, error) {
cmd := NewCommand("rev-list", "--count")
cmd.AddArguments(revision)


+ 4
- 0
modules/git/repo.go View File

@@ -52,6 +52,10 @@ func (repo *Repository) GetAllCommitsCount() (int64, error) {
return AllCommitsCount(repo.Path)
}

func (repo *Repository) GetHeadCommitsCount() (int64, error) {
return HEADCommitsCount(repo.Path)
}

func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) {
l := list.New()
if len(logs) == 0 {


+ 2
- 2
modules/git/repo_stats_custom.go View File

@@ -126,7 +126,7 @@ func GetUserKPIStats(repoPath string) (map[string]*UserKPIStats, error) {

func SetRepoKPIStats(repoPath string, fromTime time.Time, stats *RepoKPIStats, newContributers map[string]struct{}) error {
since := fromTime.Format(time.RFC3339)
args := []string{"log", "--numstat", "--no-merges", "--branches=*", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}
args := []string{"log", "--numstat", "--no-merges", "HEAD", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}

stdout, err := NewCommand(args...).RunInDirBytes(repoPath)
if err != nil {
@@ -212,7 +212,7 @@ func SetRepoKPIStats(repoPath string, fromTime time.Time, stats *RepoKPIStats, n

func GetContributorsDetail(repoPath string, fromTime time.Time) ([]Contributor, error) {
since := fromTime.Format(time.RFC3339)
cmd := NewCommand("shortlog", "-sne", "--all", fmt.Sprintf("--since='%s'", since))
cmd := NewCommand("shortlog", "-sne", "HEAD", fmt.Sprintf("--since='%s'", since))
stdout, err := cmd.RunInDir(repoPath)
if err != nil {
return nil, err


+ 56
- 6
modules/migrations/github.go View File

@@ -13,6 +13,9 @@ import (
"strings"
"time"

"code.gitea.io/gitea/modules/setting"
"golang.org/x/net/proxy"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/structs"
@@ -98,13 +101,41 @@ func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *Gith
)
client = oauth2.NewClient(downloader.ctx, ts)
} else {
client = &http.Client{
Transport: &http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
req.SetBasicAuth(userName, password)
return nil, nil
if setting.Migrations.Proxy != "" {
contextDialer, err := getProxyDialContext()
if err != nil {
log.Warn("Failed to use proxy for Github.", err)
client = &http.Client{
Transport: &http.Transport{

Proxy: func(req *http.Request) (*url.URL, error) {
req.SetBasicAuth(userName, password)
return nil, nil
},
},
}
} else {
client = &http.Client{
Transport: &http.Transport{

Proxy: func(req *http.Request) (*url.URL, error) {
req.SetBasicAuth(userName, password)
return nil, nil
},
DialContext: contextDialer.DialContext,
},
}
}
} else {
client = &http.Client{
Transport: &http.Transport{

Proxy: func(req *http.Request) (*url.URL, error) {
req.SetBasicAuth(userName, password)
return nil, nil
},
},
},
}
}
}
}
@@ -112,6 +143,25 @@ func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *Gith
return &downloader
}

func getProxyDialContext() (proxy.ContextDialer, error) {
authInfo := &proxy.Auth{
setting.Migrations.Username,
setting.Migrations.Password,
}
dialSocksProxy, err := proxy.SOCKS5("tcp", setting.Migrations.Proxy, authInfo, proxy.Direct)

if err != nil {
return nil, err
}
if contextDialer, ok := dialSocksProxy.(proxy.ContextDialer); ok {
return contextDialer, nil

} else {
return nil, fmt.Errorf("It is not a valiad dialer.")
}

}

// SetContext set context
func (g *GithubDownloaderV3) SetContext(ctx context.Context) {
g.ctx = ctx


+ 8
- 4
modules/modelarts/modelarts.go View File

@@ -48,8 +48,12 @@ const (
PerPage = 10
IsLatestVersion = "1"
NotLatestVersion = "0"
ComputeResource = "NPU"
VersionCount = 1
// ComputeResource = "NPU"
NPUResource = "NPU"
GPUResource = "CPU/GPU"
AllResource = "all"
DebugType = -1
VersionCount = 1

SortByCreateTime = "create_time"
ConfigTypeCustom = "custom"
@@ -273,7 +277,7 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error
DatasetName: attach.Name,
CommitID: req.CommitID,
IsLatestVersion: req.IsLatestVersion,
ComputeResource: ComputeResource,
ComputeResource: NPUResource,
EngineID: req.EngineID,
TrainUrl: req.TrainUrl,
BranchName: req.BranchName,
@@ -356,7 +360,7 @@ func GenerateTrainJobVersion(ctx *context.Context, req *GenerateTrainJobReq, job
CommitID: req.CommitID,
IsLatestVersion: req.IsLatestVersion,
PreVersionName: req.PreVersionName,
ComputeResource: ComputeResource,
ComputeResource: NPUResource,
EngineID: req.EngineID,
TrainUrl: req.TrainUrl,
BranchName: req.BranchName,


+ 8
- 3
modules/normalization/normalization.go View File

@@ -4,6 +4,8 @@ import (
"code.gitea.io/gitea/modules/setting"
)

const MAX_LINES_RECORD = 100

func Normalization(value float64, minValue float64, maxValue float64) float64 {

min := int64(minValue * 100)
@@ -72,9 +74,12 @@ func GetTeamHealthInitValue(contributors int64, keyContributors int64, newContri

}

func GetRepoGrowthInitValue(codelinesGrowth int64, issueGrowth int64, commitsGrowth int64, newContributors int64, commentsGrowth int64) float64 {

return setting.RadarMap.GrowthCodeLines*float64(codelinesGrowth) +
func GetRepoGrowthInitValue(codeLinesGrowth int64, issueGrowth int64, commitsGrowth int64, newContributors int64, commentsGrowth int64) float64 {
codeLinesKB := codeLinesGrowth / 1000
if codeLinesKB > MAX_LINES_RECORD {
codeLinesKB = MAX_LINES_RECORD
}
return setting.RadarMap.GrowthCodeLines*float64(codeLinesKB) +
setting.RadarMap.GrowthIssue*float64(issueGrowth) +
setting.RadarMap.GrowthCommit*float64(commitsGrowth) +
setting.RadarMap.GrowthContributors*float64(newContributors) +


+ 21
- 3
modules/repository/repo.go View File

@@ -6,6 +6,7 @@ package repository

import (
"fmt"
"net/url"
"os"
"path"
"strings"
@@ -54,29 +55,34 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt
repo.NumWatches = 1
}

migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
migrateTimeout := getMigrateTimeout(opts.CloneAddr)

var err error
if err = os.RemoveAll(repoPath); err != nil {
return repo, fmt.Errorf("Failed to remove %s: %v", repoPath, err)
}
log.Info("clone begin:" + opts.CloneAddr)
if err = git.Clone(opts.CloneAddr, repoPath, git.CloneRepoOptions{
Mirror: true,
Quiet: true,
Timeout: migrateTimeout,
}); err != nil {
log.Warn("clone err")
return repo, fmt.Errorf("Clone: %v", err)
}

log.Info("clone end:" + opts.CloneAddr)

if opts.Wiki {
log.Info("test wiki path begin")
wikiPath := models.WikiPath(u.Name, opts.RepoName)
wikiRemotePath := wikiRemoteURL(opts.CloneAddr)
log.Info("test wiki path end")
if len(wikiRemotePath) > 0 {
if err := os.RemoveAll(wikiPath); err != nil {
return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err)
}
log.Info("wiki clone begin")
if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{
Mirror: true,
Quiet: true,
@@ -88,6 +94,7 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt
return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err)
}
}
log.Info("wiki clone end")
}
}

@@ -137,9 +144,20 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt
repo, err = CleanUpMigrateInfo(repo)
}

log.Info("clone all end:" + opts.CloneAddr)

return repo, err
}

func getMigrateTimeout(urlClone string) time.Duration {
u, err := url.Parse(urlClone)
if err == nil && strings.EqualFold(u.Host, "github.com") {
return time.Duration(setting.Git.Timeout.GitHubMigrate) * time.Second
}
return time.Duration(setting.Git.Timeout.Migrate) * time.Second

}

// cleanUpMigrateGitConfig removes mirror info which prevents "push --all".
// This also removes possible user credentials.
func cleanUpMigrateGitConfig(configPath string) error {


+ 21
- 18
modules/setting/git.go View File

@@ -27,12 +27,13 @@ var (
EnableAutoGitWireProtocol bool
PullRequestPushMessage bool
Timeout struct {
Default int
Migrate int
Mirror int
Clone int
Pull int
GC int `ini:"GC"`
Default int
Migrate int
GitHubMigrate int
Mirror int
Clone int
Pull int
GC int `ini:"GC"`
} `ini:"git.timeout"`
}{
DisableDiffHighlight: false,
@@ -45,19 +46,21 @@ var (
EnableAutoGitWireProtocol: true,
PullRequestPushMessage: true,
Timeout: struct {
Default int
Migrate int
Mirror int
Clone int
Pull int
GC int `ini:"GC"`
Default int
Migrate int
GitHubMigrate int
Mirror int
Clone int
Pull int
GC int `ini:"GC"`
}{
Default: int(git.DefaultCommandExecutionTimeout / time.Second),
Migrate: 600,
Mirror: 300,
Clone: 300,
Pull: 300,
GC: 60,
Default: int(git.DefaultCommandExecutionTimeout / time.Second),
Migrate: 900,
GitHubMigrate: 1800,
Mirror: 1200,
Clone: 300,
Pull: 300,
GC: 60,
},
}
)


+ 1
- 0
modules/setting/indexer.go View File

@@ -83,6 +83,7 @@ func newIndexerService() {
Indexer.UpdateQueueLength = sec.Key("UPDATE_BUFFER_LEN").MustInt(20)
Indexer.MaxIndexerFileSize = sec.Key("MAX_FILE_SIZE").MustInt64(1024 * 1024)
Indexer.StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(30 * time.Second)
log.Info("New IndexerService Inited.")
}

// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing


+ 6
- 0
modules/setting/migrations.go View File

@@ -9,6 +9,9 @@ var (
Migrations = struct {
MaxAttempts int
RetryBackoff int
Proxy string
Username string
Password string
}{
MaxAttempts: 3,
RetryBackoff: 3,
@@ -19,4 +22,7 @@ func newMigrationsService() {
sec := Cfg.Section("migrations")
Migrations.MaxAttempts = sec.Key("MAX_ATTEMPTS").MustInt(Migrations.MaxAttempts)
Migrations.RetryBackoff = sec.Key("RETRY_BACKOFF").MustInt(Migrations.RetryBackoff)
Migrations.Proxy = sec.Key("Proxy").MustString("")
Migrations.Username = sec.Key("Username").MustString("")
Migrations.Password = sec.Key("Password").MustString("")
}

+ 1
- 0
modules/setting/queue.go View File

@@ -160,6 +160,7 @@ func NewQueueService() {
if _, ok := sectionMap["LENGTH"]; !ok {
_, _ = section.NewKey("LENGTH", fmt.Sprintf("%d", Repository.PullRequestQueueLength))
}
log.Info("New QueueService Inited.")
}

// ParseQueueConnStr parses a queue connection string


+ 51
- 35
modules/setting/setting.go View File

@@ -433,35 +433,43 @@ var (
AuthUser string
AuthPassword string

//home page
RecommentRepoAddr string

//labelsystem config
LabelTaskName string
LabelDatasetDeleteQueue string
DecompressOBSTaskName string

//cloudbrain config
CBAuthUser string
CBAuthPassword string
RestServerHost string
JobPath string
JobType string
GpuTypes string
DebugServerHost string
ResourceSpecs string
CBAuthUser string
CBAuthPassword string
RestServerHost string
JobPath string
CBCodePathPrefix string
JobType string
GpuTypes string
DebugServerHost string
ResourceSpecs string
MaxDuration int64

//benchmark config
IsBenchmarkEnabled bool
BenchmarkCode string
BenchmarkOwner string
BenchmarkName string
BenchmarkServerHost string
BenchmarkCategory string

//snn4imagenet config
IsSnn4imagenetEnabled bool
Snn4imagenetCode string
Snn4imagenetOwner string
Snn4imagenetName string
Snn4imagenetServerHost string

//snn4imagenet config
IsBrainScoreEnabled bool
BrainScoreCode string
BrainScoreOwner string
BrainScoreName string
BrainScoreServerHost string

//blockchain config
@@ -549,7 +557,7 @@ var (
RecordBeginTime string
IgnoreMirrorRepo bool
}{}
Warn_Notify_Mails []string
)

@@ -1216,51 +1224,59 @@ func NewContext() {

sec = Cfg.Section("decompress")
DecompressAddress = sec.Key("HOST").MustString("http://192.168.207.34:39987")
AuthUser = sec.Key("USER").MustString("cW4cMtH24eoWPE7X")
AuthPassword = sec.Key("PASSWORD").MustString("4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC")
AuthUser = sec.Key("USER").MustString("")
AuthPassword = sec.Key("PASSWORD").MustString("")

sec = Cfg.Section("labelsystem")
LabelTaskName = sec.Key("LabelTaskName").MustString("LabelRedisQueue")
LabelDatasetDeleteQueue = sec.Key("LabelDatasetDeleteQueue").MustString("LabelDatasetDeleteQueue")
DecompressOBSTaskName = sec.Key("DecompressOBSTaskName").MustString("LabelDecompressOBSQueue")

sec = Cfg.Section("homepage")
RecommentRepoAddr = sec.Key("Address").MustString("https://git.openi.org.cn/OpenIOSSG/promote/raw/branch/master/")

sec = Cfg.Section("cloudbrain")
CBAuthUser = sec.Key("USER").MustString("cW4cMtH24eoWPE7X")
CBAuthPassword = sec.Key("PWD").MustString("4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC")
CBAuthUser = sec.Key("USER").MustString("")
CBAuthPassword = sec.Key("PWD").MustString("")
RestServerHost = sec.Key("REST_SERVER_HOST").MustString("http://192.168.202.73")
JobPath = sec.Key("JOB_PATH").MustString("/datasets/minio/data/opendata/jobs/")
CBCodePathPrefix = sec.Key("CODE_PATH_PREFIX").MustString("jobs/")
DebugServerHost = sec.Key("DEBUG_SERVER_HOST").MustString("http://192.168.202.73")
JobType = sec.Key("GPU_TYPE_DEFAULT").MustString("openidebug")
GpuTypes = sec.Key("GPU_TYPES").MustString("")
ResourceSpecs = sec.Key("RESOURCE_SPECS").MustString("")
MaxDuration = sec.Key("MAX_DURATION").MustInt64(14400)

sec = Cfg.Section("benchmark")
IsBenchmarkEnabled = sec.Key("ENABLED").MustBool(false)
BenchmarkCode = sec.Key("BENCHMARKCODE").MustString("https://yangzhx:justfortest123@git.openi.org.cn/yangzhx/detection_benchmark_script.git")
BenchmarkServerHost = sec.Key("HOST").MustString("http://192.168.202.90:3366/")
BenchmarkOwner = sec.Key("OWNER").MustString("")
BenchmarkName = sec.Key("NAME").MustString("")
BenchmarkServerHost = sec.Key("HOST").MustString("")
BenchmarkCategory = sec.Key("CATEGORY").MustString("")

sec = Cfg.Section("snn4imagenet")
IsSnn4imagenetEnabled = sec.Key("ENABLED").MustBool(false)
Snn4imagenetCode = sec.Key("SNN4IMAGENETCODE").MustString("https://yult:19910821ylt@git.openi.org.cn/yult/snn4imagenet_script.git")
Snn4imagenetServerHost = sec.Key("HOST").MustString("http://192.168.207.76:8080/")
Snn4imagenetOwner = sec.Key("OWNER").MustString("")
Snn4imagenetName = sec.Key("NAME").MustString("")
Snn4imagenetServerHost = sec.Key("HOST").MustString("")

sec = Cfg.Section("brainscore")
IsBrainScoreEnabled = sec.Key("ENABLED").MustBool(false)
BrainScoreCode = sec.Key("BRAINSCORECODE").MustString("https://yult:19910821ylt@git.openi.org.cn/yult/brainscore_script.git")
BrainScoreServerHost = sec.Key("HOST").MustString("http://192.168.207.76:8080/")
BrainScoreOwner = sec.Key("OWNER").MustString("")
BrainScoreName = sec.Key("NAME").MustString("")
BrainScoreServerHost = sec.Key("HOST").MustString("")

sec = Cfg.Section("blockchain")
BlockChainHost = sec.Key("HOST").MustString("http://192.168.136.66:3302/")
CommitValidDate = sec.Key("COMMIT_VALID_DATE").MustString("2021-01-15")

sec = Cfg.Section("obs")
Endpoint = sec.Key("ENDPOINT").MustString("112.95.163.82")
Endpoint = sec.Key("ENDPOINT").MustString("")
AccessKeyID = sec.Key("ACCESS_KEY_ID").MustString("")
SecretAccessKey = sec.Key("SECRET_ACCESS_KEY").MustString("")
Bucket = sec.Key("BUCKET").MustString("testopendata")
Location = sec.Key("LOCATION").MustString("cn-south-222")
BasePath = sec.Key("BASE_PATH").MustString("attachment/")
Bucket = sec.Key("BUCKET").MustString("")
Location = sec.Key("LOCATION").MustString("")
BasePath = sec.Key("BASE_PATH").MustString("")
TrainJobModelPath = sec.Key("TrainJobModel_Path").MustString("job/")
OutPutPath = sec.Key("Output_Path").MustString("output/")
CodePathPrefix = sec.Key("CODE_PATH_PREFIX").MustString("code/")
@@ -1268,17 +1284,17 @@ func NewContext() {
PROXYURL = sec.Key("PROXY_URL").MustString("")

sec = Cfg.Section("modelarts")
ModelArtsHost = sec.Key("ENDPOINT").MustString("112.95.163.80")
IamHost = sec.Key("IAMHOST").MustString("112.95.163.80")
ModelArtsHost = sec.Key("ENDPOINT").MustString("")
IamHost = sec.Key("IAMHOST").MustString("")
ProjectID = sec.Key("PROJECT_ID").MustString("")
ProjectName = sec.Key("PROJECT_NAME").MustString("")
ModelArtsUsername = sec.Key("USERNAME").MustString("")
ModelArtsPassword = sec.Key("PASSWORD").MustString("")
ModelArtsDomain = sec.Key("DOMAIN").MustString("cn-south-222")
ModelArtsDomain = sec.Key("DOMAIN").MustString("")
AllowedOrg = sec.Key("ORGANIZATION").MustString("")
ProfileID = sec.Key("PROFILE_ID").MustString("")
PoolInfos = sec.Key("POOL_INFOS").MustString("")
Flavor = sec.Key("FLAVOR").MustString("modelarts.bm.910.arm.public.2")
Flavor = sec.Key("FLAVOR").MustString("")
ResourcePools = sec.Key("Resource_Pools").MustString("")
Engines = sec.Key("Engines").MustString("")
EngineVersions = sec.Key("Engine_Versions").MustString("")
@@ -1286,10 +1302,10 @@ func NewContext() {
TrainJobFLAVORINFOS = sec.Key("TrainJob_FLAVOR_INFOS").MustString("")

sec = Cfg.Section("elk")
ElkUrl = sec.Key("ELKURL").MustString("http://192.168.207.35:5601/internal/bsearch")
ElkUser = sec.Key("ELKUSER").MustString("Qizhi")
ElkPassword = sec.Key("ELKPASSWORD").MustString("Pcl2020")
Index = sec.Key("INDEX").MustString("filebeat-7.3.2*")
ElkUrl = sec.Key("ELKURL").MustString("")
ElkUser = sec.Key("ELKUSER").MustString("")
ElkPassword = sec.Key("ELKPASSWORD").MustString("")
Index = sec.Key("INDEX").MustString("")
TimeField = sec.Key("TIMEFIELD").MustString(" @timestamptest")
ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time")

@@ -1313,7 +1329,7 @@ func SetRadarMapConfig() {
RadarMap.CompletenessIssuesClosed = sec.Key("completeness_issues_closed").MustFloat64(0.2)
RadarMap.CompletenessReleases = sec.Key("completeness_releases").MustFloat64(0.3)
RadarMap.CompletenessDevelopAge = sec.Key("completeness_develop_age").MustFloat64(0.1)
RadarMap.CompletenessDataset = sec.Key("completeness_dataset").MustFloat64(0.1)
RadarMap.CompletenessDataset = sec.Key("completeness_dataset").MustFloat64(0)
RadarMap.CompletenessModel = sec.Key("completeness_model").MustFloat64(0.1)
RadarMap.CompletenessWiki = sec.Key("completeness_wiki").MustFloat64(0.1)
RadarMap.Liveness = sec.Key("liveness").MustFloat64(0.3)


+ 1
- 0
modules/setting/webhook.go View File

@@ -48,4 +48,5 @@ func newWebhookService() {
}
}
Webhook.ProxyHosts = sec.Key("PROXY_HOSTS").Strings(",")
log.Info("New WebhookService Inited.")
}

+ 4
- 0
modules/storage/local.go View File

@@ -76,3 +76,7 @@ func (l *LocalStorage) PresignedPutURL(path string) (string, error) {
func (l *LocalStorage) HasObject(path string) (bool, error) {
return false, nil
}

func (l *LocalStorage) UploadObject(fileName, filePath string) error {
return nil
}

+ 6
- 0
modules/storage/minio.go View File

@@ -122,3 +122,9 @@ func (m *MinioStorage) HasObject(path string) (bool, error) {

return hasObject, nil
}

//upload object
func (m *MinioStorage) UploadObject(fileName, filePath string) error {
_, err := m.client.FPutObject(m.bucket, fileName, filePath, minio.PutObjectOptions{})
return err
}

+ 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) {


+ 4
- 0
modules/storage/storage.go View File

@@ -26,6 +26,7 @@ type ObjectStorage interface {
PresignedGetURL(path string, fileName string) (string, error)
PresignedPutURL(path string) (string, error)
HasObject(path string) (bool, error)
UploadObject(fileName, filePath string) error
}

// Copy copys a file from source ObjectStorage to dest ObjectStorage
@@ -51,6 +52,7 @@ func Init() error {
switch setting.Attachment.StoreType {
case LocalStorageType:
Attachments, err = NewLocalStorage(setting.Attachment.Path)
log.Info("local storage inited.")
case MinioStorageType:
minio := setting.Attachment.Minio
Attachments, err = NewMinioStorage(
@@ -62,6 +64,7 @@ func Init() error {
minio.BasePath,
minio.UseSSL,
)
log.Info("minio storage inited.")
default:
return fmt.Errorf("Unsupported attachment store type: %s", setting.Attachment.StoreType)
}
@@ -71,6 +74,7 @@ func Init() error {
log.Error("obs.New failed:", err)
return err
}
log.Info("obs cli inited.")

if err != nil {
return err


+ 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
model.manage.sava_model = Sava Model

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


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

@@ -228,6 +228,7 @@ users=用户
organizations=组织
images = 云脑镜像
search=搜索
search_pro=搜项目
code=代码
data_analysis=数字看板(内测)
repo_no_results=未找到匹配的项目。
@@ -781,6 +782,9 @@ datasets=数据集
datasets.desc=数据集功能
cloudbrain_helper=使用GPU/NPU资源,开启Notebook、模型训练任务等

model_manager = 模型
model_noright=无权限操作

debug=调试
stop=停止
delete=删除
@@ -823,6 +827,7 @@ all=所有
modelarts.status=状态
modelarts.createtime=创建时间
modelarts.version_nums=版本数
modelarts.version=版本
modelarts.computing_resources=计算资源
modelarts.notebook=调试任务
modelarts.train_job=训练任务
@@ -830,7 +835,11 @@ modelarts.train_job.new_debug=新建调试任务
modelarts.train_job.new_train=新建训练任务
modelarts.train_job.config=配置信息
modelarts.train_job.new=新建训练任务
modelarts.train_job.new_place=描述字数不超过255个字符
modelarts.train_job.new_place=描述字数不超过256个字符
modelarts.model_name=模型名称
modelarts.model_size=模型大小
modelarts.import_model=导入模型
modelarts.train_job.label_place=输入标签,多个标签用空格区分
modelarts.modify=修改
modelarts.current_version=当前版本
modelarts.parent_version=父版本
@@ -848,7 +857,7 @@ modelarts.train_job.description=任务描述
modelarts.train_job.parameter_setting=参数设置
modelarts.train_job.parameter_setting_info=参数信息
modelarts.train_job.fast_parameter_setting=一键式参数配置
modelarts.train_job.fast_parameter_setting_config=如您已保存过参数配置,可单击
modelarts.train_job.fast_parameter_setting_config=如您已保存过参数配置,可单击
modelarts.train_job.fast_parameter_setting_config_link=这里
modelarts.train_job.frames=常用框架
modelarts.train_job.algorithm_origin=算法来源
@@ -886,6 +895,19 @@ 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 = 召回率
model.manage.sava_model = 保存模型

template.items=模板选项
template.git_content=Git数据(默认分支)
@@ -1565,6 +1587,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=使用外部的任务管理系统
@@ -1965,7 +1988,7 @@ team_unit_desc=允许访问项目单元
team_unit_disabled=(已禁用)

form.name_reserved=组织名称 '%s' 是被保留的。
form.name_pattern_not_allowed=项目名称中不允许使用 "%s"。
form.name_pattern_not_allowed=组织名称中不允许使用 "%s"。
form.create_org_not_allowed=此账号禁止创建组织

settings=组织设置


+ 1
- 0
public/img/member.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="120" height="120"><path fill="none" d="M0 0h24v24H0z"/><path d="M20 22h-2v-2a3 3 0 0 0-3-3H9a3 3 0 0 0-3 3v2H4v-2a5 5 0 0 1 5-5h6a5 5 0 0 1 5 5v2zm-8-9a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8z" fill="rgba(0,0,0,0.4)"/></svg>

+ 1
- 0
public/img/overview_rgb.svg View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1638325192035" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2222" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M979.792374 404.577188 574.183101 83.942886c-34.918864-27.694272-89.619352-27.694272-124.538216 0L44.207626 404.577188c-13.933143 11.008903-16.169326 31.134554-5.332437 44.895683s30.618512 16.169326 44.551655 5.332437l12.55703-10.320847 0 387.547791c0 54.872501 57.968755 95.983874 108.712918 95.983874l639.892491 0c50.22812 0 83.254829-38.531161 83.254829-95.983874L927.844112 445.860575l11.69696 8.944734c5.84848 4.644381 13.073072 6.880564 20.125651 6.880564 9.460776 0 18.921552-4.128339 25.286074-12.213002C995.9617 435.711742 993.725517 415.586091 979.792374 404.577188zM479.919368 864.026877 479.919368 686.508315c0-8.77272 15.997312-13.245087 31.994625-13.245087s31.994625 4.472367 31.994625 13.245087l0 177.346548L479.919368 864.026877 479.919368 864.026877zM864.026877 832.032253c0 21.157736-5.84848 31.994625-19.26558 31.994625L608.585923 864.026877c0-0.516042-0.688056-0.860071-0.688056-1.376113L607.897867 686.508315c0-37.155048-29.930455-77.234336-95.983874-77.234336s-95.983874 40.079288-95.983874 77.234336l0 176.142449c0 0.516042 0.860071 0.860071 0.860071 1.376113L204.868806 864.026877c-20.125651 0-44.723669-17.373425-44.723669-31.994625L160.145137 393.740299 488.864102 134.171006c11.868974-9.288762 33.198723-9.288762 44.895683 0l330.095078 261.11742L863.854863 832.032253z" p-id="2223" fill="#1684FC"></path></svg>

+ 1
- 0
public/img/pro_num.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="120" height="120"><path fill="none" d="M0 0h24v24H0z"/><path d="M20 3l2 4v13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.004L4 3h16zm0 6H4v10h16V9zm-8 1l4 4h-3v4h-2v-4H8l4-4zm6.764-5H5.236l-.999 2h15.527l-1-2z" fill="rgba(0,0,0,0.4)"/></svg>

+ 1
- 0
public/img/pro_rgb.svg View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1638171446718" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2430" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 69.717333l383.018667 221.141334v442.282666L512 954.282667 128.981333 733.141333V290.858667L512 69.717333zM192.96 375.402667v320.768L480 861.888V541.141333l-287.04-165.76z m638.058667 0L544 541.162667V861.866667l287.018667-165.717334V375.424zM512 143.637333L215.722667 314.666667 512 485.717333l296.256-171.050666L512 143.616z" fill="#1684FC" p-id="2431"></path></svg>

+ 1
- 0
public/img/search.svg View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1638433773401" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2884" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M469.333333 768c-166.4 0-298.666667-132.266667-298.666666-298.666667s132.266667-298.666667 298.666666-298.666666 298.666667 132.266667 298.666667 298.666666-132.266667 298.666667-298.666667 298.666667z m0-85.333333c119.466667 0 213.333333-93.866667 213.333334-213.333334s-93.866667-213.333333-213.333334-213.333333-213.333333 93.866667-213.333333 213.333333 93.866667 213.333333 213.333333 213.333334z m251.733334 0l119.466666 119.466666-59.733333 59.733334-119.466667-119.466667 59.733334-59.733333z" fill="#5BB973" p-id="2885"></path></svg>

+ 1
- 0
public/img/user_rgb.svg View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1638171175505" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2087" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M947.924 963.76c0 21.752-17.62 39.372-39.354 39.372-21.75 0-39.37-17.62-39.37-39.371l-0.354-3.655c0-1.554 0.283-3.054 0.442-4.59-24.576-172.792-172.72-299.768-353.104-301.763-1.447 0.036-2.825 0.23-4.29 0.23-1.43 0-2.807-0.194-4.237-0.23C329.34 655.731 182.607 779.9 155.56 949.69c0.9 3.337 1.536 6.78 1.536 10.4l0.37 3.707c0 22.086-17.902 39.989-39.97 39.989s-39.99-17.903-39.99-39.99l-0.035-0.352h-0.76c0.107-1.165 0.336-2.278 0.46-3.425 0-3.302 0.53-6.427 1.27-9.499C98.94 791.217 205.684 662.422 350.968 606.95c-84.586-54.2-139.74-149.963-139.74-259.372 0-169.224 131.478-306.405 300.702-306.405 169.208 0 300.703 137.18 300.703 306.405 0 109.374-55.102 205.118-139.652 259.32 144.278 55.03 250.544 182.325 272.19 340.038 1.465 4.131 2.436 8.528 2.436 13.17l0.318 3.656z m-204.43-616.094c0-127.947-103.706-231.654-231.653-231.654S280.188 219.736 280.188 347.666c0 127.929 103.724 231.636 231.653 231.636s231.654-103.707 231.654-231.636z" fill="#1684FC" p-id="2088"></path></svg>

+ 12
- 8
routers/api/v1/api.go View File

@@ -75,6 +75,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/repo"
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
"code.gitea.io/gitea/routers/api/v1/user"
repo_ext "code.gitea.io/gitea/routers/repo"

"gitea.com/macaron/binding"
"gitea.com/macaron/macaron"
@@ -523,23 +524,26 @@ func RegisterRoutes(m *macaron.Macaron) {
Get(notify.GetThread).
Patch(notify.ReadThread)
}, reqToken())
adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
operationReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, OperationRequired: true})
//Project board
m.Group("/projectboard", func() {

m.Get("/restoreFork", adminReq, repo.RestoreForkNumber)
m.Get("/downloadAll", adminReq, repo.ServeAllProjectsPeriodStatisticsFile)
m.Get("/downloadAllOpenI", adminReq, repo.ServeAllProjectsOpenIStatisticsFile)
m.Get("/restoreFork", repo.RestoreForkNumber)
m.Get("/downloadAll", repo.ServeAllProjectsPeriodStatisticsFile)
m.Get("/downloadAllOpenI", repo.ServeAllProjectsOpenIStatisticsFile)
m.Group("/project", func() {
m.Get("", adminReq, repo.GetAllProjectsPeriodStatistics)
m.Get("", repo.GetAllProjectsPeriodStatistics)

m.Group("/:id", func() {
m.Get("", adminReq, repo.GetProjectLatestStatistics)
m.Get("/period", adminReq, repo.GetProjectPeriodStatistics)
m.Get("", repo.GetProjectLatestStatistics)
m.Get("/period", repo.GetProjectPeriodStatistics)

})
})
})
}, operationReq)

m.Get("/query_user_static_page", operationReq, repo_ext.QueryUserStaticDataPage)

// Users
m.Group("/users", func() {


+ 18
- 16
routers/api/v1/repo/modelarts.go View File

@@ -240,26 +240,28 @@ func DelTrainJobVersion(ctx *context.APIContext) {
ctx.ServerError("get VersionListCount faild", err)
return
}

// 判断当前删掉的任务是否是最新版本,若是,将排序后的TotalVersionCount置为删掉的最新版本的TotalVersionCount,若不是,按时间排序后的版本列表的第一个版本设置为最新版本,TotalVersionCount不变
if task.IsLatestVersion == modelarts.IsLatestVersion {
err = models.SetVersionCountAndLatestVersion(jobID, VersionTaskList[0].Cloudbrain.VersionName, VersionListCount, modelarts.IsLatestVersion, task.TotalVersionCount)
if err != nil {
ctx.ServerError("UpdateJobVersionCount failed", err)
return
}
} else {
err = models.SetVersionCountAndLatestVersion(jobID, VersionTaskList[0].VersionName, VersionListCount, modelarts.IsLatestVersion, VersionTaskList[0].Cloudbrain.TotalVersionCount)
if err != nil {
ctx.ServerError("UpdateJobVersionCount failed", err)
return
if VersionListCount > 0 {
// 判断当前删掉的任务是否是最新版本,若是,将排序后的TotalVersionCount置为删掉的最新版本的TotalVersionCount,若不是,按时间排序后的版本列表的第一个版本设置为最新版本,TotalVersionCount不变
if task.IsLatestVersion == modelarts.IsLatestVersion {
err = models.SetVersionCountAndLatestVersion(jobID, VersionTaskList[0].Cloudbrain.VersionName, VersionListCount, modelarts.IsLatestVersion, task.TotalVersionCount)
if err != nil {
ctx.ServerError("UpdateJobVersionCount failed", err)
return
}
} else {
err = models.SetVersionCountAndLatestVersion(jobID, VersionTaskList[0].VersionName, VersionListCount, modelarts.IsLatestVersion, VersionTaskList[0].Cloudbrain.TotalVersionCount)
if err != nil {
ctx.ServerError("UpdateJobVersionCount failed", err)
return
}
}
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"JobID": jobID,
"VersionName": versionName,
"StatusOK": 0,
"JobID": jobID,
"VersionName": versionName,
"StatusOK": 0,
"VersionListCount": VersionListCount,
})
}



+ 4
- 3
routers/api/v1/repo/repo_dashbord.go View File

@@ -5,6 +5,7 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"time"

"github.com/360EntSecGroup-Skylar/excelize/v2"
@@ -467,7 +468,7 @@ func generateCountSql(beginTime time.Time, endTime time.Time, latestDate string,
"(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" +
" where A.repo_id=B.repo_id"
if q != "" {
countSql = countSql + " and B.name like '%" + q + "%'"
countSql = countSql + " and LOWER(B.name) like '%" + strings.ToLower(q) + "%'"
}
return countSql
}
@@ -488,7 +489,7 @@ func generateTypeAllSql(beginTime time.Time, endTime time.Time, latestDate strin
" where A.repo_id=B.repo_id"

if q != "" {
sql = sql + " and name like '%" + q + "%'"
sql = sql + " and LOWER(name) like '%" + strings.ToLower(q) + "%'"
}
sql = sql + " order by " + orderBy + " desc,repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
return sql
@@ -511,7 +512,7 @@ func generatePageSql(beginTime time.Time, endTime time.Time, latestDate string,
"(SELECT repo_id,name,owner_name,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" +
" where A.repo_id=B.repo_id"
if q != "" {
sql = sql + " and B.name like '%" + q + "%'"
sql = sql + " and LOWER(B.name) like '%" + strings.ToLower(q) + "%'"
}
sql = sql + " order by " + orderBy + " desc,A.repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
return sql


+ 42
- 0
routers/home.go View File

@@ -7,6 +7,8 @@ package routers

import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"

@@ -511,3 +513,43 @@ func NotFound(ctx *context.Context) {
ctx.Data["Title"] = "Page Not Found"
ctx.NotFound("home.NotFound", nil)
}
func RecommendOrgFromPromote(ctx *context.Context) {
url := setting.RecommentRepoAddr + "organizations"
recommendFromPromote(ctx, url)
}

func recommendFromPromote(ctx *context.Context, url string) {
resp, err := http.Get(url)
if err != nil {
log.Info("Get organizations url error=" + err.Error())
ctx.ServerError("QueryTrainJobList:", err)
return
}
bytes, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Info("Get organizations url error=" + err.Error())
ctx.ServerError("QueryTrainJobList:", err)
return
}

allLineStr := string(bytes)
lines := strings.Split(allLineStr, "\n")
result := make([]string, len(lines))
for i, line := range lines {

tmpIndex := strings.Index(line, ".")
log.Info("i=" + fmt.Sprint(i) + " line=" + line + " tmpIndex=" + fmt.Sprint(tmpIndex))
if tmpIndex == -1 {
result[i] = strings.Trim(line, " ")
} else {
result[i] = strings.Trim(line[tmpIndex+1:], " ")
}
}
ctx.JSON(http.StatusOK, result)
}

func RecommendRepoFromPromote(ctx *context.Context) {
url := setting.RecommentRepoAddr + "projects"
recommendFromPromote(ctx, url)
}

+ 6
- 0
routers/init.go View File

@@ -60,11 +60,17 @@ func NewServices() {
if err := storage.Init(); err != nil {
log.Fatal("storage init failed: %v", err)
}
log.Info("storage init succeed.")
mailer.NewContext()
log.Info("mailer.NewContext() succeed.")
_ = cache.NewContext()
log.Info("cache.NewContext() succeed.")
notification.NewContext()
log.Info("notification.NewContext() succeed.")
decompression.NewContext()
log.Info("decompression.NewContext() succeed.")
labelmsg.Init()
log.Info("labelmsg.Init() succeed.")
}

// In case of problems connecting to DB, retry connection. Eg, PGSQL in Docker Container on Synology


+ 1
- 1
routers/private/hook.go View File

@@ -539,7 +539,7 @@ func updateRepoCommitCnt(ctx *macaron.Context, repo *models.Repository) error {
}
defer gitRepo.Close()

count, err := gitRepo.GetAllCommitsCount()
count, err := gitRepo.GetHeadCommitsCount()
if err != nil {
log.Error("GetAllCommitsCount failed:%v", err.Error(), ctx.Data["MsgID"])
return err


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

@@ -0,0 +1,571 @@
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)
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, "", aiTask.TrainUrl)
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)

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,
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, trainUrl string) (string, int64, error) {
objectkey := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/")
if trainUrl != "" {
objectkey = strings.Trim(trainUrl[len(setting.Bucket)+1:], "/")
}

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)

userIds := make([]int64, len(models))
for i, model := range models {
model.IsCanOper = isOper(ctx, model.UserId)
userIds[i] = model.UserId
}
userNameMap := queryUserName(userIds)

for _, model := range models {
value := userNameMap[model.UserId]
if value != nil {
model.UserName = value.Name
model.UserRelAvatarLink = value.RelAvatarLink()
}
}
ctx.JSON(http.StatusOK, models)
}

func queryUserName(intSlice []int64) map[int64]*models.User {
keys := make(map[int64]string)
uniqueElements := []int64{}
for _, entry := range intSlice {
if _, value := keys[entry]; !value {
keys[entry] = ""
uniqueElements = append(uniqueElements, entry)
}
}
result := make(map[int64]*models.User)
userLists, err := models.GetUsersByIDs(uniqueElements)
if err == nil {
for _, user := range userLists {
result[user.ID] = user
}
}

return result
}

func ShowOneVersionOtherModel(ctx *context.Context) {
repoId := ctx.Repo.Repository.ID
name := ctx.Query("name")
aimodels := models.QueryModelByName(name, repoId)

userIds := make([]int64, len(aimodels))
for i, model := range aimodels {
model.IsCanOper = isOper(ctx, model.UserId)
userIds[i] = model.UserId
}
userNameMap := queryUserName(userIds)

for _, model := range aimodels {
value := userNameMap[model.UserId]
if value != nil {
model.UserName = value.Name
model.UserRelAvatarLink = value.RelAvatarLink()
}
}

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.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
}
pageSize := ctx.QueryInt("pageSize")
if pageSize <= 0 {
pageSize = setting.UI.IssuePagingNum
}
repoId := ctx.Repo.Repository.ID
Type := -1
modelResult, count, err := models.QueryModel(&models.AiModelQueryOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: pageSize,
},
RepoID: repoId,
Type: Type,
New: MODEL_LATEST,
})
if err != nil {
ctx.ServerError("Cloudbrain", err)
return
}

userIds := make([]int64, len(modelResult))
for i, model := range modelResult {
model.IsCanOper = isOper(ctx, model.UserId)
userIds[i] = model.UserId
}

userNameMap := queryUserName(userIds)

for _, model := range modelResult {
value := userNameMap[model.UserId]
if value != nil {
model.UserName = value.Name
model.UserRelAvatarLink = value.RelAvatarLink()
}
}

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")
}

}

+ 191
- 77
routers/repo/cloudbrain.go View File

@@ -8,7 +8,6 @@ import (
"io"
"net/http"
"os"
"os/exec"
"regexp"
"sort"
"strconv"
@@ -50,46 +49,6 @@ func MustEnableCloudbrain(ctx *context.Context) {
return
}
}
func CloudBrainIndex(ctx *context.Context) {
MustEnableCloudbrain(ctx)
repo := ctx.Repo.Repository
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}

ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
Type: models.TypeCloudBrainOne,
})
if err != nil {
ctx.ServerError("Cloudbrain", err)
return
}

timestamp := time.Now().Unix()
for i, task := range ciTasks {
if task.Status == string(models.JobRunning) && (timestamp-int64(task.Cloudbrain.CreatedUnix) > 10) {
ciTasks[i].CanDebug = true
} else {
ciTasks[i].CanDebug = false
}

ciTasks[i].CanDel = models.CanDelJob(ctx.IsSigned, ctx.User, task)
}

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

ctx.Data["PageIsCloudBrain"] = true
ctx.Data["Tasks"] = ciTasks
ctx.HTML(200, tplCloudBrainIndex)
}

func cutString(str string, lens int) string {
if len(str) < lens {
@@ -247,14 +206,11 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
}
repo := ctx.Repo.Repository
downloadCode(repo, codePath)
uploadCodeToMinio(codePath+"/", jobName, "/code/")

modelPath := setting.JobPath + jobName + cloudbrain.ModelMountPath
err = os.MkdirAll(modelPath, os.ModePerm)
if err != nil {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form)
return
}
modelPath := setting.JobPath + jobName + cloudbrain.ModelMountPath + "/"
mkModelPath(modelPath)
uploadCodeToMinio(modelPath, jobName, cloudbrain.ModelMountPath+"/")

benchmarkPath := setting.JobPath + jobName + cloudbrain.BenchMarkMountPath
if setting.IsBenchmarkEnabled && jobType == string(models.JobTypeBenchmark) {
@@ -264,26 +220,31 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
gpuType = gpuInfo.Value
}
}
downloadRateCode(repo, jobName, setting.BenchmarkCode, benchmarkPath, form.BenchmarkCategory, gpuType)
downloadRateCode(repo, jobName, setting.BenchmarkOwner, setting.BrainScoreName, benchmarkPath, form.BenchmarkCategory, gpuType)
uploadCodeToMinio(benchmarkPath+"/", jobName, cloudbrain.BenchMarkMountPath+"/")
}

snn4imagenetPath := setting.JobPath + jobName + cloudbrain.Snn4imagenetMountPath
if setting.IsSnn4imagenetEnabled && jobType == string(models.JobTypeSnn4imagenet) {
downloadRateCode(repo, jobName, setting.Snn4imagenetCode, snn4imagenetPath, "", "")
downloadRateCode(repo, jobName, setting.Snn4imagenetOwner, setting.Snn4imagenetName, snn4imagenetPath, "", "")
uploadCodeToMinio(snn4imagenetPath+"/", jobName, cloudbrain.Snn4imagenetMountPath+"/")
}

brainScorePath := setting.JobPath + jobName + cloudbrain.BrainScoreMountPath
if setting.IsBrainScoreEnabled && jobType == string(models.JobTypeBrainScore) {
downloadRateCode(repo, jobName, setting.BrainScoreCode, brainScorePath, "", "")
downloadRateCode(repo, jobName, setting.BrainScoreOwner, setting.BrainScoreName, brainScorePath, "", "")
uploadCodeToMinio(brainScorePath+"/", jobName, cloudbrain.BrainScoreMountPath+"/")
}

err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, brainScorePath, jobType, gpuQueue, resourceSpecId)
err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, codePath, getMinioPath(jobName, cloudbrain.ModelMountPath+"/"),
getMinioPath(jobName, cloudbrain.BenchMarkMountPath+"/"), getMinioPath(jobName, cloudbrain.Snn4imagenetMountPath+"/"),
getMinioPath(jobName, cloudbrain.BrainScoreMountPath+"/"), jobType, gpuQueue, resourceSpecId)
if err != nil {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form)
return
}
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain")
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob")
}

func CloudBrainShow(ctx *context.Context) {
@@ -338,7 +299,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)
@@ -355,7 +316,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)
@@ -415,8 +376,7 @@ func CloudBrainStop(ctx *context.Context) {
ctx.ServerError("UpdateJob failed", err)
return
}

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain")
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob")
}

func StopJobsByUserID(userID int64) {
@@ -452,14 +412,22 @@ func StopJobs(cloudBrains []*models.Cloudbrain) {

logErrorAndUpdateJobStatus(err, taskInfo)
} else {
param := models.NotebookAction{
Action: models.ActionStop,
if taskInfo.JobType == string(models.JobTypeTrain) {
err := retry(3, time.Second*30, func() error {
_, err := modelarts.StopTrainJob(taskInfo.JobID, strconv.FormatInt(taskInfo.VersionID, 10))
return err
})
logErrorAndUpdateJobStatus(err, taskInfo)
} else {
param := models.NotebookAction{
Action: models.ActionStop,
}
err := retry(3, time.Second*30, func() error {
_, err := modelarts.StopJob(taskInfo.JobID, param)
return err
})
logErrorAndUpdateJobStatus(err, taskInfo)
}
err := retry(3, time.Second*30, func() error {
_, err := modelarts.StopJob(taskInfo.JobID, param)
return err
})
logErrorAndUpdateJobStatus(err, taskInfo)
}

}
@@ -510,8 +478,7 @@ func CloudBrainDel(ctx *context.Context) {
ctx.ServerError("DeleteJob failed", err)
return
}

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain")
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob")
}

func CloudBrainShowModels(ctx *context.Context) {
@@ -528,10 +495,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
}

@@ -591,9 +558,9 @@ 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/"
modelActualPath := getMinioPath(jobName, cloudbrain.ModelMountPath+"/")
if parentDir == "" {
req = "baseDir=" + modelActualPath
} else {
@@ -603,6 +570,10 @@ func getModelDirs(jobName string, parentDir string) (string, error) {
return getDirs(req)
}

func getMinioPath(jobName, suffixPath string) string {
return setting.Attachment.Minio.RealPath + setting.Attachment.Minio.Bucket + "/" + setting.CBCodePathPrefix + jobName + suffixPath
}

func CloudBrainDownloadModel(ctx *context.Context) {
parentDir := ctx.Query("parentDir")
fileName := ctx.Query("fileName")
@@ -684,19 +655,21 @@ func downloadCode(repo *models.Repository, codePath string) error {
return nil
}

func downloadRateCode(repo *models.Repository, taskName, gitPath, codePath, benchmarkCategory, gpuType string) error {
func downloadRateCode(repo *models.Repository, taskName, rateOwnerName, rateRepoName, codePath, benchmarkCategory, gpuType string) error {
err := os.MkdirAll(codePath, os.ModePerm)
if err != nil {
log.Error("mkdir codePath failed", err.Error())
return err
}

command := "git clone " + gitPath + " " + codePath
cmd := exec.Command("/bin/bash", "-c", command)
output, err := cmd.Output()
log.Info(string(output))
repoExt, err := models.GetRepositoryByOwnerAndName(rateOwnerName, rateRepoName)
if err != nil {
log.Error("exec.Command(%s) failed:%v", command, err)
log.Error("GetRepositoryByOwnerAndName(%s) failed", rateRepoName, err.Error())
return err
}

if err := git.Clone(repoExt.RepoPath(), codePath, git.CloneRepoOptions{}); err != nil {
log.Error("Failed to clone repository: %s (%v)", repoExt.FullName(), err)
return err
}

@@ -730,3 +703,144 @@ func downloadRateCode(repo *models.Repository, taskName, gitPath, codePath, benc

return nil
}

func uploadCodeToMinio(codePath, jobName, parentDir string) error {
files, err := readDir(codePath)
if err != nil {
log.Error("readDir(%s) failed: %s", codePath, err.Error())
return err
}

for _, file := range files {
if file.IsDir() {
if err = uploadCodeToMinio(codePath+file.Name()+"/", jobName, parentDir+file.Name()+"/"); err != nil {
log.Error("uploadCodeToMinio(%s) failed: %s", file.Name(), err.Error())
return err
}
} else {
destObject := setting.CBCodePathPrefix + jobName + parentDir + file.Name()
sourceFile := codePath + file.Name()
err = storage.Attachments.UploadObject(destObject, sourceFile)
if err != nil {
log.Error("UploadObject(%s) failed: %s", file.Name(), err.Error())
return err
}
}
}

return nil
}

func mkModelPath(modelPath string) error {
err := os.MkdirAll(modelPath, os.ModePerm)
if err != nil {
log.Error("MkdirAll(%s) failed:%v", modelPath, err)
return err
}

fileName := modelPath + "README"
f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
log.Error("OpenFile failed", err.Error())
return err
}

defer f.Close()

_, err = f.WriteString("You can put the model file into this directory and download it by the web page.")
if err != nil {
log.Error("WriteString failed", err.Error())
return err
}

return nil
}

func SyncCloudbrainStatus() {
cloudBrains, err := models.GetCloudBrainUnStoppedJob()
if err != nil {
log.Error("GetCloudBrainUnStoppedJob failed:", err.Error())
return
}

for _, task := range cloudBrains {
if task.Type == models.TypeCloudBrainOne {
result, err := cloudbrain.GetJob(task.JobID)
if err != nil {
log.Error("GetJob(%s) failed:%v", task.JobName, err)
continue
}

if result != nil {
jobRes, _ := models.ConvertToJobResultPayload(result.Payload)
taskRoles := jobRes.TaskRoles
taskRes, _ := models.ConvertToTaskPod(taskRoles[cloudbrain.SubTaskName].(map[string]interface{}))
task.Status = taskRes.TaskStatuses[0].State
if task.Status != string(models.JobWaiting) {
task.Duration = time.Now().Unix() - taskRes.TaskStatuses[0].StartAt.Unix()
err = models.UpdateJob(task)
if err != nil {
log.Error("UpdateJob(%s) failed:%v", task.JobName, err)
}

if task.Duration >= setting.MaxDuration {
log.Info("begin to stop job(%s), because of the duration", task.JobName)
err = cloudbrain.StopJob(task.JobID)
if err != nil {
log.Error("StopJob(%s) failed:%v", task.JobName, err)
continue
}
task.Status = string(models.JobStopped)
err = models.UpdateJob(task)
if err != nil {
log.Error("UpdateJob(%s) failed:%v", task.JobName, err)
}
}
}
}
} else if task.Type == models.TypeCloudBrainTwo {
if task.JobType == string(models.JobTypeDebug) {
result, err := modelarts.GetJob(task.JobID)
if err != nil {
log.Error("GetJob(%s) failed:%v", task.JobName, err)
continue
}

if result != nil {
task.Status = result.Status

err = models.UpdateJob(task)
if err != nil {
log.Error("UpdateJob(%s) failed:%v", task.JobName, err)
continue
}
}
} else if task.JobType == string(models.JobTypeTrain) {
result, err := modelarts.GetTrainJob(task.JobID, strconv.FormatInt(task.VersionID, 10))
if err != nil {
log.Error("GetTrainJob(%s) failed:%v", task.JobName, err)
continue
}

if result != nil {
task.Status = modelarts.TransTrainJobStatus(result.IntStatus)
task.Duration = result.Duration
task.TrainJobDuration = result.TrainJobDuration

err = models.UpdateJob(task)
if err != nil {
log.Error("UpdateJob(%s) failed:%v", task.JobName, err)
continue
}
}
} else {
log.Error("task.JobType(%s) is error:%s", task.JobName, task.JobType)
}

} else {
log.Error("task.Type(%s) is error:%d", task.JobName, task.Type)
}
}

return
}

+ 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


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

@@ -1390,7 +1390,7 @@ func isLegalReviewRequest(reviewer, doer *models.User, isAdd bool, issue *models

var pemResult bool
if isAdd {
pemResult = permReviewer.CanAccessAny(models.AccessModeRead, models.UnitTypePullRequests)
pemResult = permReviewer.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests)
if !pemResult {
return fmt.Errorf("Reviewer can't read [user_id: %d, repo_name: %s]", reviewer.ID, issue.Repo.Name)
}


+ 88
- 65
routers/repo/modelarts.go View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"os"
"path"
@@ -12,6 +11,8 @@ import (
"strings"
"time"

"code.gitea.io/gitea/modules/cloudbrain"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
@@ -27,6 +28,8 @@ import (
)

const (
tplDebugJobIndex base.TplName = "repo/debugjob/index"

tplModelArtsNotebookIndex base.TplName = "repo/modelarts/notebook/index"
tplModelArtsNotebookNew base.TplName = "repo/modelarts/notebook/new"
tplModelArtsNotebookShow base.TplName = "repo/modelarts/notebook/show"
@@ -37,21 +40,23 @@ const (
tplModelArtsTrainJobVersionNew base.TplName = "repo/modelarts/trainjob/version_new"
)

// MustEnableDataset check if repository enable internal cb
func MustEnableModelArts(ctx *context.Context) {
if !ctx.Repo.CanRead(models.UnitTypeCloudBrain) {
ctx.NotFound("MustEnableCloudbrain", nil)
return
}
}

func NotebookIndex(ctx *context.Context) {
MustEnableModelArts(ctx)
func DebugJobIndex(ctx *context.Context) {
debugListType := ctx.Query("debugListType")
MustEnableCloudbrain(ctx)
repo := ctx.Repo.Repository
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
debugType := modelarts.DebugType
jobType := string(models.JobTypeDebug)
if debugListType == modelarts.GPUResource {
debugType = models.TypeCloudBrainOne
jobType = ""
}
if debugListType == modelarts.NPUResource {
debugType = models.TypeCloudBrainTwo
}

ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{
@@ -59,29 +64,44 @@ func NotebookIndex(ctx *context.Context) {
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeDebug),
Type: debugType,
JobType: jobType,
})
if err != nil {
ctx.ServerError("Cloudbrain", err)
ctx.ServerError("Get debugjob faild:", err)
return
}

for i, task := range ciTasks {
if task.Status == string(models.JobRunning) {
ciTasks[i].CanDebug = true
} else {
ciTasks[i].CanDebug = false
if task.Cloudbrain.Type == models.TypeCloudBrainOne {
ciTasks[i].CanDebug = cloudbrain.CanCreateOrDebugJob(ctx)
ciTasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain)
ciTasks[i].Cloudbrain.ComputeResource = modelarts.GPUResource
}
if task.Cloudbrain.Type == models.TypeCloudBrainTwo {
ciTasks[i].CanDebug = cloudbrain.CanCreateOrDebugJob(ctx)
ciTasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain)
ciTasks[i].Cloudbrain.ComputeResource = modelarts.NPUResource
}

}

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

ctx.Data["PageIsCloudBrain"] = true
ctx.Data["Tasks"] = ciTasks
ctx.HTML(200, tplModelArtsNotebookIndex)
ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx)
ctx.Data["RepoIsEmpty"] = repo.IsEmpty
ctx.HTML(200, tplDebugJobIndex)
}

// MustEnableDataset check if repository enable internal cb
func MustEnableModelArts(ctx *context.Context) {
if !ctx.Repo.CanRead(models.UnitTypeCloudBrain) {
ctx.NotFound("MustEnableCloudbrain", nil)
return
}
}

func NotebookNew(ctx *context.Context) {
@@ -136,8 +156,7 @@ func NotebookCreate(ctx *context.Context, form auth.CreateModelArtsNotebookForm)
ctx.RenderWithErr(err.Error(), tplModelArtsNotebookNew, &form)
return
}

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/notebook")
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob")
}

func NotebookShow(ctx *context.Context) {
@@ -244,8 +263,7 @@ func NotebookStop(ctx *context.Context) {
ctx.ServerError("UpdateJob failed", err)
return
}

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/notebook")
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob")
}

func NotebookDel(ctx *context.Context) {
@@ -275,7 +293,7 @@ func NotebookDel(ctx *context.Context) {
return
}

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/notebook")
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob")
}

func TrainJobIndex(ctx *context.Context) {
@@ -302,12 +320,19 @@ func TrainJobIndex(ctx *context.Context) {
return
}

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

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

ctx.Data["PageIsCloudBrain"] = true
ctx.Data["Tasks"] = tasks
ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx)
ctx.Data["RepoIsEmpty"] = repo.IsEmpty
ctx.HTML(200, tplModelArtsTrainJobIndex)
}

@@ -376,9 +401,8 @@ func trainJobNewDataPrepare(ctx *context.Context) error {

outputObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.OutputPath
ctx.Data["train_url"] = outputObsPath

ctx.Data["params"] = ""
ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["branchName"] = ctx.Repo.BranchName

configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom)
if err != nil {
@@ -390,7 +414,7 @@ func trainJobNewDataPrepare(ctx *context.Context) error {
return nil
}

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

//can, err := canUserCreateTrainJob(ctx.User.ID)
@@ -570,7 +594,7 @@ func trainJobNewVersionDataPrepare(ctx *context.Context) error {
return nil
}

func VersionErrorDataPrepare(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) error {
func versionErrorDataPrepare(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) error {
ctx.Data["PageIsCloudBrain"] = true
var jobID = ctx.Params(":jobid")
// var versionName = ctx.Params(":version-name")
@@ -687,13 +711,13 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
count, err := models.GetCloudbrainTrainJobCountByUserID(ctx.User.ID)
if err != nil {
log.Error("GetCloudbrainTrainJobCountByUserID failed:%v", err, ctx.Data["MsgID"])
ErrorNewDataPrepare(ctx, form)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("system error", tplModelArtsTrainJobNew, &form)
return
} else {
if count >= 1 {
log.Error("the user already has running or waiting task", ctx.Data["MsgID"])
ErrorNewDataPrepare(ctx, form)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("you have already a running or waiting task, can not create more", tplModelArtsTrainJobNew, &form)
return
}
@@ -701,22 +725,23 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)

if err := paramCheckCreateTrainJob(form); err != nil {
log.Error("paramCheckCreateTrainJob failed:(%v)", err)
ErrorNewDataPrepare(ctx, form)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobNew, &form)
return
}

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

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

gitRepo, _ := git.OpenRepository(repo.RepoPath())
commitID, _ := gitRepo.GetBranchCommitID(branch_name)
@@ -725,7 +750,7 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
Branch: branch_name,
}); err != nil {
log.Error("创建任务失败,服务器超时!: %s (%v)", repo.FullName(), err)
trainJobNewDataPrepare(ctx)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("创建任务失败,服务器超时!", tplModelArtsTrainJobNew, &form)
return
}
@@ -733,14 +758,14 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
//todo: upload code (send to file_server todo this work?)
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.OutputPath + VersionOutputPath + "/"); err != nil {
log.Error("Failed to obsMkdir_output: %s (%v)", repo.FullName(), err)
trainJobNewDataPrepare(ctx)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to obsMkdir_output", tplModelArtsTrainJobNew, &form)
return
}

if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.LogPath + VersionOutputPath + "/"); err != nil {
log.Error("Failed to obsMkdir_log: %s (%v)", repo.FullName(), err)
trainJobNewDataPrepare(ctx)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to obsMkdir_log", tplModelArtsTrainJobNew, &form)
return
}
@@ -749,7 +774,7 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil {
// if err := uploadCodeToObs(codeLocalPath, jobName, parentDir); err != nil {
log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err)
trainJobNewDataPrepare(ctx)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to uploadCodeToObs", tplModelArtsTrainJobNew, &form)
return
}
@@ -769,7 +794,7 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
err := json.Unmarshal([]byte(params), &parameters)
if err != nil {
log.Error("Failed to Unmarshal params: %s (%v)", params, err)
trainJobNewDataPrepare(ctx)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("运行参数错误", tplModelArtsTrainJobNew, &form)
return
}
@@ -812,7 +837,7 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)

if err != nil {
log.Error("Failed to CreateTrainJobConfig: %v", err)
trainJobNewDataPrepare(ctx)
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr("保存作业参数失败:"+err.Error(), tplModelArtsTrainJobNew, &form)
return
}
@@ -853,12 +878,7 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
err = modelarts.GenerateTrainJob(ctx, req)
if err != nil {
log.Error("GenerateTrainJob failed:%v", err.Error())
trainJobNewDataPrepare(ctx)
ctx.Data["bootFile"] = form.BootFile
ctx.Data["uuid"] = form.Attachment
ctx.Data["datasetName"] = attach.Name
ctx.Data["params"] = Parameters.Parameter
ctx.Data["branch_name"] = branch_name
trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobNew, &form)
return
}
@@ -872,13 +892,13 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
count, err := models.GetCloudbrainTrainJobCountByUserID(ctx.User.ID)
if err != nil {
log.Error("GetCloudbrainTrainJobCountByUserID failed:%v", err, ctx.Data["MsgID"])
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("system error", tplModelArtsTrainJobVersionNew, &form)
return
} else {
if count >= 1 {
log.Error("the user already has running or waiting task", ctx.Data["MsgID"])
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("you have already a running or waiting task, can not create more", tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -922,7 +942,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ

if err := paramCheckCreateTrainJob(form); err != nil {
log.Error("paramCheckCreateTrainJob failed:(%v)", err)
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -946,7 +966,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
Branch: branch_name,
}); err != nil {
log.Error("创建任务失败,任务名称已存在!: %s (%v)", repo.FullName(), err)
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("创建任务失败,任务名称已存在!", tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -954,14 +974,14 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
//todo: upload code (send to file_server todo this work?)
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.OutputPath + VersionOutputPath + "/"); err != nil {
log.Error("Failed to obsMkdir_output: %s (%v)", repo.FullName(), err)
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to obsMkdir_output", tplModelArtsTrainJobVersionNew, &form)
return
}

if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.LogPath + VersionOutputPath + "/"); err != nil {
log.Error("Failed to obsMkdir_log: %s (%v)", repo.FullName(), err)
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to obsMkdir_log", tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -971,7 +991,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
// if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil {
if err := uploadCodeToObs(codeLocalPath, jobName, parentDir); err != nil {
log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err)
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("Failed to uploadCodeToObs", tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -991,7 +1011,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
err := json.Unmarshal([]byte(params), &parameters)
if err != nil {
log.Error("Failed to Unmarshal params: %s (%v)", params, err)
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("运行参数错误", tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -1010,7 +1030,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
if isSaveParam == "on" {
if form.ParameterTemplateName == "" {
log.Error("ParameterTemplateName is empty")
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("保存作业参数时,作业参数名称不能为空", tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -1034,7 +1054,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ

if err != nil {
log.Error("Failed to CreateTrainJobConfig: %v", err)
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr("保存作业参数失败:"+err.Error(), tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -1081,7 +1101,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
err = modelarts.GenerateTrainJobVersion(ctx, req, jobID)
if err != nil {
log.Error("GenerateTrainJob failed:%v", err.Error())
VersionErrorDataPrepare(ctx, form)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobVersionNew, &form)
return
}
@@ -1210,7 +1230,7 @@ func TrainJobShow(ctx *context.Context) {
ctx.Data["canNewJob"] = canNewJob

//将运行参数转化为epoch_size = 3, device_target = Ascend的格式
for i, _ := range VersionListTasks {
for i, task := range VersionListTasks {

var parameters models.Parameters

@@ -1231,6 +1251,9 @@ func TrainJobShow(ctx *context.Context) {
} else {
VersionListTasks[i].Parameters = ""
}

VersionListTasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain)
VersionListTasks[i].CanModify = cloudbrain.CanModifyJob(ctx, &task.Cloudbrain)
}

pager := context.NewPagination(VersionListCount, setting.UI.IssuePagingNum, page, 5)


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

@@ -532,6 +532,7 @@ func Download(ctx *context.Context) {
}

ctx.Repo.Repository.IncreaseCloneCnt()
ctx.Repo.Repository.IncreaseGitCloneCnt()

ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext)
}


+ 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"))


+ 3
- 9
routers/repo/user_data_analysis.go View File

@@ -148,11 +148,6 @@ func QueryUserStaticDataPage(ctx *context.Context) {

func TimingCountDataByDateAndReCount(date string, isReCount bool) {

if date == "refreshAll" {
models.RefreshUserStaticAllTabel()
return
}

t, _ := time.Parse("2006-01-02", date)
startTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())

@@ -205,10 +200,9 @@ func TimingCountDataByDateAndReCount(date string, isReCount bool) {
log.Error("count user info error." + err.Error())
mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage)
}

if isReCount {
models.RefreshUserStaticAllTabel()
}
log.Info("start to count all user info data")
//models.RefreshUserStaticAllTabel(wikiMap)
log.Info("end to count all user info data")
}

func TimingCountDataByDate(date string) {


+ 43
- 18
routers/routes/routes.go View File

@@ -12,6 +12,8 @@ import (
"text/template"
"time"

"code.gitea.io/gitea/modules/cloudbrain"

"code.gitea.io/gitea/routers/operation"
"code.gitea.io/gitea/routers/private"

@@ -313,6 +315,8 @@ func RegisterRoutes(m *macaron.Macaron) {
})
m.Get("/", routers.Home)
m.Get("/dashboard", routers.Dashboard)
m.Get("/recommend/org", routers.RecommendOrgFromPromote)
m.Get("/recommend/repo", routers.RecommendRepoFromPromote)
m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/explore/repos")
@@ -612,6 +616,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)

@@ -792,7 +798,7 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef())

m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
m.Get("/tool/query_user_static_page", adminReq, repo.QueryUserStaticDataPage)
// Grouping for those endpoints not requiring authentication
m.Group("/:username/:reponame", func() {
m.Get("/contributors", repo.Contributors)
@@ -954,29 +960,48 @@ func RegisterRoutes(m *macaron.Macaron) {
}, context.RepoRef())

m.Group("/cloudbrain", func() {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainIndex)
m.Group("/:jobid", func() {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainShow)
m.Get("/debug", reqRepoCloudBrainReader, repo.CloudBrainDebug)
m.Post("/commit_image", reqRepoCloudBrainWriter, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.CloudBrainCommitImage)
m.Post("/stop", reqRepoCloudBrainWriter, repo.CloudBrainStop)
m.Post("/del", reqRepoCloudBrainWriter, repo.CloudBrainDel)
m.Get("/debug", reqRepoCloudBrainWriter, repo.CloudBrainDebug)
m.Post("/commit_image", cloudbrain.AdminOrOwnerOrJobCreaterRight, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.CloudBrainCommitImage)
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainDel)
m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate)
m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels)
m.Get("/download_model", reqRepoCloudBrainReader, repo.CloudBrainDownloadModel)
m.Get("/download_model", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainDownloadModel)
})
m.Get("/create", reqRepoCloudBrainReader, repo.CloudBrainNew)
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("/debugjob", func() {
m.Get("", reqRepoCloudBrainReader, repo.DebugJobIndex)
}, context.RepoRef())

m.Group("/modelarts", func() {
m.Group("/notebook", func() {
m.Get("", reqRepoCloudBrainReader, repo.NotebookIndex)
m.Group("/:jobid", func() {
m.Get("", reqRepoCloudBrainReader, repo.NotebookShow)
m.Get("/debug", reqRepoCloudBrainReader, repo.NotebookDebug)
m.Post("/stop", reqRepoCloudBrainWriter, repo.NotebookStop)
m.Post("/del", reqRepoCloudBrainWriter, repo.NotebookDel)
m.Get("/debug", reqRepoCloudBrainWriter, repo.NotebookDebug)
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel)
})
m.Get("/create", reqRepoCloudBrainWriter, repo.NotebookNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.NotebookCreate)
@@ -986,13 +1011,13 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", reqRepoCloudBrainReader, repo.TrainJobIndex)
m.Group("/:jobid", func() {
m.Get("", reqRepoCloudBrainReader, repo.TrainJobShow)
m.Post("/stop", reqRepoCloudBrainWriter, repo.TrainJobStop)
m.Post("/del", reqRepoCloudBrainWriter, repo.TrainJobDel)
m.Get("/model_download", reqRepoCloudBrainReader, repo.ModelDownload)
m.Get("/create_version", reqRepoCloudBrainReader, repo.TrainJobNewVersion)
m.Post("/create_version", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion)
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobDel)
m.Get("/model_download", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.ModelDownload)
m.Get("/create_version", cloudbrain.AdminOrJobCreaterRight, repo.TrainJobNewVersion)
m.Post("/create_version", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion)
})
m.Get("/create", reqRepoCloudBrainReader, repo.TrainJobNew)
m.Get("/create", reqRepoCloudBrainWriter, repo.TrainJobNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate)

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


+ 47
- 1
routers/user/profile.go View File

@@ -6,6 +6,7 @@
package user

import (
"errors"
"fmt"
"path"
"strings"
@@ -103,10 +104,47 @@ func Profile(ctx *context.Context) {
return
}

for _, org := range orgs {
_, repoCount, err := models.SearchRepository(&models.SearchRepoOptions{
OwnerID: org.ID,
Private: ctx.IsSigned,
Actor: ctx.User,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
return
}

var opts = models.FindOrgMembersOpts{
OrgID: org.ID,
PublicOnly: true,
}

if ctx.User != nil {
isMember, err := org.IsOrgMember(ctx.User.ID)
if err != nil {
ctx.Error(500, "IsOrgMember")
return
}
opts.PublicOnly = !isMember && !ctx.User.IsAdmin
}

membersCount, err := models.CountOrgMembers(opts)
if err != nil {
ctx.ServerError("CountOrgMembers", err)
return
}
org.NumMembers = int(membersCount)
org.NumRepos = int(repoCount)
}

ctx.Data["Orgs"] = orgs
ctx.Data["HasOrgsVisible"] = models.HasOrgsVisible(orgs, ctx.User)

tab := ctx.Query("tab")
if tab == "" {
tab = "activity"
}
ctx.Data["TabName"] = tab

page := ctx.QueryInt("page")
@@ -210,10 +248,15 @@ func Profile(ctx *context.Context) {

total = int(count)
case "datasets":
var isOwner = false
if ctx.User != nil && ctx.User.ID == ctxUser.ID {
isOwner = true
}
datasetSearchOptions := &models.SearchDatasetOptions{
Keyword: keyword,
OwnerID: ctxUser.ID,
SearchOrderBy: orderBy,
IsOwner: isOwner,
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.ExplorePagingNum,
@@ -230,7 +273,7 @@ func Profile(ctx *context.Context) {
}
total = int(count)
ctx.Data["Datasets"] = datasets
default:
case "repository":
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
ListOptions: models.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
@@ -251,6 +294,9 @@ func Profile(ctx *context.Context) {
}

total = int(count)
default:
ctx.ServerError("tab error", errors.New("tab error"))
return
}
ctx.Data["Repos"] = repos
ctx.Data["Total"] = total


+ 64
- 25
templates/base/head_navbar.tmpl View File

@@ -17,47 +17,63 @@

{{if .IsSigned}}
<a class="item {{if .PageIsDashboard}}active{{end}}" href="/dashboard">{{.i18n.Tr "index"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
{{if not .UnitIssuesGlobalDisabled}}
<a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
{{end}}
{{if not .UnitPullsGlobalDisabled}}
<a class="item {{if .PageIsPulls}}active{{end}}" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
{{end}}
{{if not (and .UnitIssuesGlobalDisabled .UnitPullsGlobalDisabled)}}
{{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}}
{{end}}
<div class="ui dropdown item">
<div class=" item edge">
<div class="dropdown-menu">
<a class=" item lfpd" href="/dashboard" >
{{.i18n.Tr "index"}} <i class="dropdown icon mglf" ></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>

<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_explore'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsAdmin}}
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
</div>
{{else if .IsLandingPageHome}}
<a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/dashboard">{{.i18n.Tr "home"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
<div class="item edge">
<div class="dropdown-menu">
<a class=" item lfpd" href="/user/login">
{{.i18n.Tr "home"}} <i class="dropdown icon mglf"></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>

<div class="ui dropdown item">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_PageHome'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "datasets"}}</a>
<div class="menu" >
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsAdmin}}
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
{{else if .IsLandingPageExplore}}
@@ -78,7 +94,18 @@
*/}}

{{if .IsSigned}}
<div class="right stackable menu">
<div class="right stackable menu">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty" style="margin:auto" action="/explore/repos">
<div class="ui fluid action input" style="background:#363840 ;border-radius: 5px;width: 200px;height:30px;border: #888888 solid 1px;">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search_pro"}}..."
style="transition: background-color 5000s ease-in-out 0s;-webkit-text-fill-color:#888888;background:#363840 ;color:#888888;border: none;outline: none;">
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="sort" value="hot">
<button style="border: none;background-color: #363840;outline: none;border-radius:5px"><img type = "submit" style="width: 25px; height: 25px;margin: auto;" src="/img/search.svg" >
</button>
<!-- <button class="ui green button">{{.i18n.Tr "explore.search"}}</button> -->
</div>
</form>
<a href="{{AppSubUrl}}/notifications" class="item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
<span class="text">
<span class="fitted">{{svg "octicon-bell" 16}}</span>
@@ -163,6 +190,18 @@

<!--a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io">{{.i18n.Tr "help"}}</a-->
<div class="right stackable menu">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty" style="margin:auto" action="/explore/repos">
<div class="ui fluid action input" style="background:#363840 ;border-radius: 5px;width: 200px;height:30px;border: #888888 solid 1px;">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search_pro"}}..."
style="transition: background-color 5000s ease-in-out 0s;-webkit-text-fill-color:#888888;background:#363840 ;color:#888888;border: none;outline: none;">
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="sort" value="hot">
<button style="border: none;background-color: #363840;outline: none;border-radius:5px"><img type = "submit" style="width: 25px; height: 25px;margin: auto;" src="/img/search.svg" >
</button>
<!-- <button class="ui green button">{{.i18n.Tr "explore.search"}}</button> -->
</div>
</form>
{{if .ShowRegistrationButton}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}


+ 59
- 23
templates/base/head_navbar_fluid.tmpl View File

@@ -17,47 +17,61 @@

{{if .IsSigned}}
<a class="item {{if .PageIsDashboard}}active{{end}}" href="/dashboard">{{.i18n.Tr "index"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
{{if not .UnitIssuesGlobalDisabled}}
<a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
{{end}}
{{if not .UnitPullsGlobalDisabled}}
<a class="item {{if .PageIsPulls}}active{{end}}" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
{{end}}
{{if not (and .UnitIssuesGlobalDisabled .UnitPullsGlobalDisabled)}}
{{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}}
{{end}}
<div class="ui dropdown item">
<div class="item edge" >
<div class="dropdown-menu">
<a class=" item lfpd" href="/dashboard">
{{.i18n.Tr "index"}} <i class="dropdown icon mglf"></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_explore'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsAdmin}}
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
{{else if .IsLandingPageHome}}
<a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/dashboard">{{.i18n.Tr "home"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>

<div class="ui dropdown item">
<div class="item edge" >
<div class="dropdown-menu">
<a class=" item lfpd" href="/user/login">
{{.i18n.Tr "home"}} <i class="dropdown icon mglf"></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_PageHome'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "datasets"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsAdmin}}
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
{{else if .IsLandingPageExplore}}
@@ -79,6 +93,17 @@

{{if .IsSigned}}
<div class="right stackable menu">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty" style="margin:auto" action="/explore/repos">
<div class="ui fluid action input" style="background:#363840 ;border-radius: 5px;width: 200px;height:30px;border: #888888 solid 1px;">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search_pro"}}..."
style="transition: background-color 5000s ease-in-out 0s;-webkit-text-fill-color:#888888;background:#363840 ;color:#888888;border: none;outline: none;">
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="sort" value="{{$.SortType}}">
<button style="border: none;background-color: #363840;outline: none;border-radius:5px"><img type = "submit" style="width: 25px; height: 25px;margin: auto;" src="/img/search.svg" >
</button>
<!-- <button class="ui green button">{{.i18n.Tr "explore.search"}}</button> -->
</div>
</form>
<a href="{{AppSubUrl}}/notifications" class="item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
<span class="text">
<span class="fitted">{{svg "octicon-bell" 16}}</span>
@@ -163,6 +188,17 @@

<!--a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io">{{.i18n.Tr "help"}}</a-->
<div class="right stackable menu">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty" style="margin:auto" action="/explore/repos">
<div class="ui fluid action input" style="background:#363840 ;border-radius: 5px;width: 200px;height:30px;border: #888888 solid 1px;">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search_pro"}}..."
style="transition: background-color 5000s ease-in-out 0s;-webkit-text-fill-color:#888888;background:#363840 ;color:#888888;border: none;outline: none;">
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="sort" value="{{$.SortType}}">
<button style="border: none;background-color: #363840;outline: none;border-radius:5px"><img type = "submit" style="width: 25px; height: 25px;margin: auto;" src="/img/search.svg" >
</button>
<!-- <button class="ui green button">{{.i18n.Tr "explore.search"}}</button> -->
</div>
</form>
{{if .ShowRegistrationButton}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}


+ 37
- 22
templates/base/head_navbar_home.tmpl View File

@@ -9,47 +9,62 @@
</div>

{{if .IsSigned}}
<a class="item {{if .PageIsDashboard}}active{{end}}" href="/dashboard">{{.i18n.Tr "index"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
{{if not .UnitIssuesGlobalDisabled}}
<a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
{{end}}
{{if not .UnitPullsGlobalDisabled}}
<a class="item {{if .PageIsPulls}}active{{end}}" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
{{end}}
{{if not (and .UnitIssuesGlobalDisabled .UnitPullsGlobalDisabled)}}
{{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}}
{{end}}
<div class="ui dropdown item">
<div class="item edge" >
<div class="dropdown-menu">
<a class=" item lfpd" href="/dashboard">
{{.i18n.Tr "index"}} <i class="dropdown icon mglf"></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_explore'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsAdmin}}
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
{{else if .IsLandingPageHome}}
<a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/dashboard">{{.i18n.Tr "home"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
<div class="item edge" >
<div class="dropdown-menu">
<a class=" item lfpd" href="/user/login">
{{.i18n.Tr "home"}} <i class="dropdown icon mglf"></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>

<div class="ui dropdown item">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_PageHome'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "datasets"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsAdmin}}
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
{{else if .IsLandingPageExplore}}


+ 216
- 0
templates/base/head_navbar_pro.tmpl View File

@@ -0,0 +1,216 @@
<div class="ui container" id="navbar">
<div class="item brand" style="justify-content: space-between;">
<a href="https://openi.org.cn/">
<img class="ui mini image" src="{{StaticUrlPrefix}}/img/logo-w.svg">
</a>
<div class="ui basic icon button mobile-only" id="navbar-expand-toggle">
<i class="sidebar icon"></i>
</div>
</div>
<div style="width:1px;background:#606266;height:80%;margin:auto 0.5rem"></div>
<div class="item brand" style="margin-left: 0.9rem;">
<a href="/">
<img class="ui mini image" style="height: 1.3rem;" src="{{StaticUrlPrefix}}/img/git-logo.svg">
</a>
</div>


{{if .IsSigned}}
<div class=" item edge" >
<div class="dropdown-menu">
<a class=" item lfpd" href="/dashboard">
{{.i18n.Tr "index"}} <i class="dropdown icon mglf"></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>

<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_explore'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
{{else if .IsLandingPageHome}}
<div class="item edge">
<div class="dropdown-menu">
<a class=" item" href="/user/login">
{{.i18n.Tr "home"}} <i class="dropdown icon mglf"></i>
</a>
<div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
</div>
</div>
</div>

<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui dropdown item" id='dropdown_PageHome'>
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu" >
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
</div>
</div>
{{else if .IsLandingPageExplore}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a>
{{end}}

{{template "custom/extra_links" .}}

{{/*
<div class="item">
<div class="ui icon input">
<input class="searchbox" type="text" placeholder="{{.i18n.Tr "search_project"}}">
<i class="search icon"></i>
</div>
</div>
*/}}

{{if .IsSigned}}
<div class="right stackable menu">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty" style="margin:auto" action="/explore/repos">
<div class="ui fluid action input" style="background:#363840 ;border-radius: 5px;width: 200px;height:30px;border: #888888 solid 1px;">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search_pro"}}..."
style="transition: background-color 5000s ease-in-out 0s;-webkit-text-fill-color:#888888;background:#363840 ;color:#888888;border: none;outline: none;">
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="sort" value="{{$.SortType}}">
<button style="border: none;background-color: #363840;outline: none;border-radius:5px"><img type = "submit" style="width: 25px; height: 25px;margin: auto;" src="/img/search.svg" >
</button>
<!-- <button class="ui green button">{{.i18n.Tr "explore.search"}}</button> -->
</div>
</form>
<a href="{{AppSubUrl}}/notifications" class="item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
<span class="text">
<span class="fitted">{{svg "octicon-bell" 16}}</span>
<span class="sr-mobile-only">{{.i18n.Tr "notifications"}}</span>
{{$notificationUnreadCount := 0}}
{{if .NotificationUnreadCount}}{{$notificationUnreadCount = call .NotificationUnreadCount}}{{end}}
<span class="ui red label {{if not $notificationUnreadCount}}hidden{{end}} notification_count">
{{$notificationUnreadCount}}
</span>
</span>
</a>

<div class="ui dropdown jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted">
<span class="text">
<span class="fitted">{{svg "octicon-plus" 16}}</span>
<span class="sr-mobile-only">{{.i18n.Tr "create_new"}}</span>
<span class="fitted not-mobile">{{svg "octicon-triangle-down" 16}}</span>
</span>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/repo/create">
<span class="fitted">{{svg "octicon-plus" 16}}</span> {{.i18n.Tr "new_repo"}}
</a>
<a class="item" href="{{AppSubUrl}}/repo/migrate">
<span class="fitted">{{svg "octicon-repo-clone" 16}}</span> {{.i18n.Tr "new_migrate"}}
</a>
{{if .SignedUser.CanCreateOrganization}}
<a class="item" href="{{AppSubUrl}}/org/create">
<span class="fitted">{{svg "octicon-organization" 16}}</span> {{.i18n.Tr "new_org"}}
</a>
{{end}}
</div><!-- end content create new menu -->
</div><!-- end dropdown menu create new -->

<div class="ui dropdown jump item poping up" tabindex="-1" data-content="{{.i18n.Tr "user_profile_and_more"}}" data-variation="tiny inverted">
<span class="text">
<img class="ui tiny avatar image" width="24" height="24" src="{{.SignedUser.RelAvatarLink}}">
<span class="sr-only">{{.i18n.Tr "user_profile_and_more"}}</span>
<span class="mobile-only">{{.SignedUser.Name}}</span>
<span class="fitted not-mobile" tabindex="-1">{{svg "octicon-triangle-down" 16}}</span>
</span>
<div class="menu user-menu" tabindex="-1">
<div class="ui header">
{{.i18n.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
</div>

<div class="divider"></div>
<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}">
{{svg "octicon-person" 16}}
{{.i18n.Tr "your_profile"}}<!-- Your profile -->
</a>
<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}?tab=stars">
{{svg "octicon-star" 16}}
{{.i18n.Tr "your_starred"}}
</a>
<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
{{svg "octicon-settings" 16}}
{{.i18n.Tr "your_settings"}}<!-- Your settings -->
</a>
<!--a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io">
{{svg "octicon-question" 16}}
{{.i18n.Tr "help"}}<!-- Help -->
</a-->
{{if .IsAdmin}}
<div class="divider"></div>

<a class="{{if .PageIsAdmin}}active{{end}} item" href="{{AppSubUrl}}/admin">
<i class="icon settings"></i>
{{.i18n.Tr "admin_panel"}}<!-- Admin Panel -->
</a>
{{end}}

<div class="divider"></div>
<a class="item link-action" href data-url="{{AppSubUrl}}/user/logout" data-redirect="{{AppSubUrl}}/">
{{svg "octicon-sign-out" 16}}
{{.i18n.Tr "sign_out"}}<!-- Sign Out -->
</a>
</div><!-- end content avatar menu -->
</div><!-- end dropdown avatar menu -->
</div><!-- end signed user right menu -->

{{else}}

<!--a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io">{{.i18n.Tr "help"}}</a-->
<div class="right stackable menu">
<form class="fourteen wide mobile ten wide tablet ten wide computer column ui form ignore-dirty" style="margin:auto" action="/explore/repos">
<div class="ui fluid action input" style="background:#363840 ;border-radius: 5px;width: 200px;height:30px;border: #888888 solid 1px;">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search_pro"}}..."
style="transition: background-color 5000s ease-in-out 0s;-webkit-text-fill-color:#888888;background:#363840 ;color:#888888;border: none;outline: none;">
<input type="hidden" name="tab" value="{{$.TabName}}">
<input type="hidden" name="sort" value="{{$.SortType}}">
<button style="border: none;background-color: #363840;outline: none;border-radius:5px"><img type = "submit" style="width: 25px; height: 25px;margin: auto;" src="/img/search.svg" >
</button>
<!-- <button class="ui green button">{{.i18n.Tr "explore.search"}}</button> -->
</div>
</form>
{{if .ShowRegistrationButton}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}
</a>
{{end}}
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login">
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}}
</a>
</div><!-- end anonymous right menu -->

{{end}}
</div>

+ 208
- 0
templates/base/head_pro.tmpl View File

@@ -0,0 +1,208 @@
<!DOCTYPE html>
<html lang="{{.Language}}">
<head data-suburl="{{AppSubUrl}}">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{{if .Title}}{{.Title}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
<link rel="manifest" href="{{AppSubUrl}}/manifest.json" crossorigin="use-credentials">
{{if UseServiceWorker}}
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('{{AppSubUrl}}/serviceworker.js').then(function(registration) {
// Registration was successful
console.info('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.info('ServiceWorker registration failed: ', err);
});
}
</script>
{{else}}
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
registrations.forEach(function(registration) {
registration.unregister();
console.info('ServiceWorker unregistered');
});
});
}
</script>
{{end}}
<meta name="theme-color" content="{{ThemeColorMetaTag}}">
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}" />
<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}" />
<meta name="keywords" content="{{MetaKeywords}}">
<meta name="referrer" content="no-referrer" />
<meta name="_csrf" content="{{.CsrfToken}}" />
{{if .IsSigned}}
<meta name="_uid" content="{{.SignedUser.ID}}" />
{{end}}
{{if .ContextUser}}
<meta name="_context_uid" content="{{.ContextUser.ID}}" />
{{end}}
{{if .SearchLimit}}
<meta name="_search_limit" content="{{.SearchLimit}}" />
{{end}}
{{if .GoGetImport}}
<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">
<meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}">
{{end}}
<script>
{{SafeJS `/*
@licstart The following is the entire license notice for the
JavaScript code in this page.

Copyright (c) 2016 The Gitea Authors
Copyright (c) 2015 The Gogs Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
Licensing information for additional javascript libraries can be found at:
{{StaticUrlPrefix}}/vendor/librejs.html

@licend The above is the entire license notice
for the JavaScript code in this page.
*/`}}
</script>
<script>
window.config = {
AppSubUrl: '{{AppSubUrl}}',
StaticUrlPrefix: '{{StaticUrlPrefix}}',
csrf: '{{.CsrfToken}}',
HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}},
Minicolors: {{if .RequireMinicolors}}true{{else}}false{{end}},
SimpleMDE: {{if .RequireSimpleMDE}}true{{else}}false{{end}},
Tribute: {{if .RequireTribute}}true{{else}}false{{end}},
U2F: {{if .RequireU2F}}true{{else}}false{{end}},
Heatmap: {{if .EnableHeatmap}}true{{else}}false{{end}},
heatmapUser: {{if .HeatmapUser}}'{{.HeatmapUser}}'{{else}}null{{end}},
NotificationSettings: {
MinTimeout: {{NotificationSettings.MinTimeout}},
TimeoutStep: {{NotificationSettings.TimeoutStep}},
MaxTimeout: {{NotificationSettings.MaxTimeout}},
EventSourceUpdateTime: {{NotificationSettings.EventSourceUpdateTime}},
},
{{if .RequireTribute}}
tributeValues: [
{{ range .Assignees }}
{key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.RelAvatarLink}}'},
{{ end }}
],
{{end}}
};
</script>
<link rel="shortcut icon" href="{{StaticUrlPrefix}}/img/favicon.png">
<link rel="mask-icon" href="{{StaticUrlPrefix}}/img/openi-safari.svg" color="#609926">
<link rel="fluid-icon" href="{{StaticUrlPrefix}}/img/gitea-lg.png" title="{{AppName}}">
<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/assets/font-awesome/css/font-awesome.min.css">
<link rel="preload" as="font" href="{{StaticUrlPrefix}}/fomantic/themes/default/assets/fonts/icons.woff2" type="font/woff2" crossorigin="anonymous">
<link rel="preload" as="font" href="{{StaticUrlPrefix}}/fomantic/themes/default/assets/fonts/outline-icons.woff2" type="font/woff2" crossorigin="anonymous">
{{if .RequireSimpleMDE}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css">
{{end}}

{{if .RequireTribute}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/tribute/tribute.css">
{{end}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/fomantic/semantic.min.css?v={{MD5 AppVer}}">
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/index.css?v={{MD5 AppVer}}">
<noscript>
<style>
.dropdown:hover > .menu { display: block; }
.ui.secondary.menu .dropdown.item > .menu { margin-top: 0; }
</style>
</noscript>
{{if .RequireMinicolors}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css">
{{end}}
<style class="list-search-style"></style>
{{if .PageIsUserProfile}}
<meta property="og:title" content="{{.Owner.Name}}" />
<meta property="og:type" content="profile" />
<meta property="og:image" content="{{.Owner.AvatarLink}}" />
<meta property="og:url" content="{{.Owner.HTMLURL}}" />
{{if .Owner.Description}}
<meta property="og:description" content="{{.Owner.Description}}">
{{end}}
{{else if .Repository}}
{{if .Issue}}
<meta property="og:title" content="{{.Issue.Title}}" />
<meta property="og:url" content="{{.Issue.HTMLURL}}" />
{{if .Issue.Content}}
<meta property="og:description" content="{{.Issue.Content}}" />
{{end}}
{{else}}
<meta property="og:title" content="{{.Repository.Name}}" />
<meta property="og:url" content="{{.Repository.HTMLURL}}" />
{{if .Repository.Description}}
<meta property="og:description" content="{{.Repository.Description}}" />
{{end}}
{{end}}
<meta property="og:type" content="object" />
<meta property="og:image" content="{{.Repository.Owner.AvatarLink}}" />
{{else}}
<meta property="og:title" content="{{AppName}}">
<meta property="og:type" content="website" />
<meta property="og:image" content="{{StaticUrlPrefix}}/img/gitea-lg.png" />
<meta property="og:url" content="{{AppUrl}}" />
<meta property="og:description" content="{{MetaDescription}}">
{{end}}
<meta property="og:site_name" content="{{AppName}}" />
{{if .IsSigned }}
{{ if ne .SignedUser.Theme "gitea" }}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/theme-{{.SignedUser.Theme}}.css?v={{MD5 AppVer}}">
{{end}}
{{else if ne DefaultTheme "gitea"}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/theme-{{DefaultTheme}}.css?v={{MD5 AppVer}}">
{{end}}
{{template "custom/header" .}}

<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<script src="/self/func.js" type="text/javascript"></script>

</head>
<body>
{{template "custom/body_outer_pre" .}}

<div class="full height">
<noscript>{{.i18n.Tr "enable_javascript"}}</noscript>

{{template "custom/body_inner_pre" .}}

{{if not .PageIsInstall}}
<div class="ui top secondary stackable main menu following bar dark">
{{template "base/head_navbar_pro" .}}
</div><!-- end bar -->
{{end}}
{{/*
</div>
</body>
</html>
*/}}

+ 1
- 1
templates/explore/repos.tmpl View File

@@ -1,4 +1,4 @@
{{template "base/head" .}}
{{template "base/head_pro" .}}
<div class="explore repositories">

{{template "explore/repo_search" .}}


+ 19
- 20
templates/repo/cloudbrain/new.tmpl View File

@@ -93,16 +93,6 @@
display: none;
}

.select2-container .select2-selection--single{
height:38px !important;
}

.select2-container--default .select2-selection--single {
border : 1px solid rgba(34,36,38,.15) !important;
}
.select2-container--default .select2-selection--single .select2-selection__rendered{
line-height: 38px !important;
}
</style>

<div id="mask">
@@ -129,6 +119,24 @@
{{.i18n.Tr "repo.cloudbrain.new"}}
</h3>
<div class="ui attached segment">
<div class="inline required field">
<label>计算资源</label>
<div class="ui blue small menu compact selectcloudbrain">
<a class="active item" href="{{.RepoLink}}/cloudbrain/create">
<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 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/>
</svg>
CPU/GPU
</a>
<a class="item" href="{{.RepoLink}}/modelarts/notebook/create">
<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 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/>
</svg>
Ascend NPU</a>
</div>
</div>
<div class="inline required field">
<label>任务名称</label>
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
@@ -240,7 +248,7 @@
<button class="ui green button" >
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
<a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
</div>
</form>
@@ -250,12 +258,7 @@
</div>
</div>
{{template "base/footer" .}}
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>

let url_href = window.location.pathname.split('create')[0]
$(".ui.button").attr('href',url_href)
let form = document.getElementById('form_id');

@@ -273,10 +276,6 @@
$('#messageInfo p').text(str)
return false
}
// if(!value_image || !value_data){
// console.log("------------------------")
// return false
// }
let min_value_task = value_task.toLowerCase()
$("input[name='job_name']").attr("value",min_value_task)
document.getElementById("mask").style.display = "block"


+ 3
- 2
templates/repo/cloudbrain/show.tmpl View File

@@ -7,11 +7,11 @@

<h4 class="ui header" id="vertical-segment">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/cloudbrain">
<a class="section" href="{{.RepoLink}}/debugjob?debugListType=all">
{{.i18n.Tr "repo.cloudbrain"}}
</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/cloudbrain">
<a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType=CPU/GPU">
{{$.i18n.Tr "repo.modelarts.notebook"}}
</a>
<div class="divider"> / </div>
@@ -98,3 +98,4 @@
</div>
</div>
{{template "base/footer" .}}


templates/repo/cloudbrain/index.tmpl → templates/repo/debugjob/index.tmpl View File

@@ -5,10 +5,7 @@
.label_after::after{
margin: -.2em 0 0 .2em;
content: '\00a0';
}
.selectcloudbrain .active.item{
color: #0087f5 !important;
border: 1px solid #0087f5;
@@ -205,7 +202,6 @@
<div class="rect5"></div>
</div>
</div>

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

@@ -213,164 +209,132 @@
{{template "repo/header" .}}
<!-- 列表容器 -->
<div class="ui container">

<!-- 中间云脑和新建任务按钮 -->
<!-- <div class="ui three column stack able grid">
<div class="column">
<h2>{{.i18n.Tr "repo.cloudbrain1"}}</h2>
</div>

<div class="column">
</div>

<div class="column right aligned">
{{if .Permission.CanWrite $.UnitTypeCloudBrain}}
<a class="ui green button" href="{{.RepoLink}}/cloudbrain/create">{{.i18n.Tr "repo.cloudbrain.new"}}</a> {{end}}
</div>
</div>

<p>使用鹏城云脑计算资源进行调试,云脑1提供CPU / GPU资源,云脑2提供Ascend NPU资源;调试使用的数据集也需要上传到对应的环境。</p>
<div class="ui blue mini menu selectcloudbrain">
<a class="active item" href="{{.RepoLink}}/cloudbrain">{{svg "octicon-server" 16}} CPU / GPU</a>
<a class="item" href="{{.RepoLink}}/modelarts">{{svg "octicon-server" 16}} Ascend NPU</a>
</div> -->

<div class="ui two column stackable grid ">

<div class="column">
<div class="ui blue small menu compact selectcloudbrain">
<a class="active item" href="{{.RepoLink}}/cloudbrain">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="active item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
</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);"> CPU / GPU</div>
<div class="default text" style="color: rgba(0,0,0,.87);"></div>
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{.RepoLink}}/cloudbrain" data-value="11">CPU / GPU</a>
<a class="item" href="{{.RepoLink}}/modelarts/notebook" data-value="22">Ascend NPU</a>
<div class="item" data-value="all">全部</div>
<div class="item" data-value="CPU/GPU">CPU/GPU</div>
<div class="item" data-value="NPU">NPU</div>
</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}}
<a class="ui green button" href="{{.RepoLink}}/cloudbrain/create">{{$.i18n.Tr "repo.modelarts.train_job.new_debug"}}</a>
{{else}}
<a class="ui disabled button">{{$.i18n.Tr "repo.modelarts.train_job.new_debug"}}</a>
{{end}}
</div>
</div>

{{if eq 0 (len .Tasks)}}
<div class="ui placeholder segment bgtask-none">
<div class="ui icon header bgtask-header-pic"></div>
<div class="bgtask-content-header">未创建过调试任务</div>
<div class="bgtask-content">
{{if $.RepoIsEmpty}}
<div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本</a>;</div>
{{end}}
<div class="bgtask-content-txt">运行时长:最长不超过4个小时,超过4个小时将自动停止;</div>
<div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div>
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div>
</div>
</div>
{{else}}
<!-- 中下列表展示区 -->
<div class="ui grid">
<div class="row">
<div class="ui sixteen wide column">

<!-- 排序区 -->
<!-- <div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
</div>
<div class="column right aligned">
<div class="ui right dropdown type jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i>
</span>
</div>
</div>
</div>
</div> -->

<!-- 任务展示 -->

<!-- 表头 -->
<div class="dataset list">
<div class="ui grid stackable" style="background: #f0f0f0;;">
<div class="row">
<div class="five wide column">
<div class="four wide column">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span>
</div>
<div class="three wide column">
<span>{{$.i18n.Tr "repo.cloudbrain_status_createtime"}}</span>
<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">
<div class="one wide column text center">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="seven wide column text center">
<div class="five wide column text center">
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>

</div>
</div>
</div>
{{range .Tasks}}
<div class="ui grid stackable item">
<div class="row">

<!-- 任务名 -->
<div class="five wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<div class="four wide column">
<a class="title" href='{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}' title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted text_over" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>

<div class="three wide column">
<!--任务状态 -->
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
<!-- {{.Status}} -->
<!-- {{if eq .Status "RUNNING"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-stop"></i><span style="margin-left: 0.4em;font-size: 12px;">运行中</span></span>
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-stop"></i><span style="margin-left: 0.4em;font-size: 12px;">运行中</span></span>
{{else}}
{{.Status}}
{{end}} -->
<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>
<!-- <span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" >
{{if eq .Status "STOPPED"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-stop"></i><span style="margin-left: 0.4em;font-size: 12px;">已停止</span></span>
{{else if eq .Status "RUNNING"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行中</span></span>
{{else if eq .Status "FAILED"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="i-round i-bg-running"></i><span style="margin-left: 0.4em;font-size: 12px;">运行失败</span></span>
{{else if eq .Status "WAITING"}}
<span style="display:flex;position: relative; justify-content: flex-start;"><i class="showCircle"></i><span style="margin-left: 0.4em;font-size: 12px;">初始化等待</span></span>
{{end}}
</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="one wide column">
<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="seven wide column text right">
<div class="five wide column text center">
<div class="ui compact buttons">
{{if and (ne .Status "WAITING") (ne .JobType "DEBUG")}}
<!-- {{if and (ne .Status "WAITING") (ne .JobType "DEBUG")}}
<a class="ui basic button" href="{{$.Link}}/{{.JobID}}/rate" target="_blank">
评分
</a>
{{end}}

{{end}} -->
<!-- 调试 -->
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a id="model-debug-{{.JobID}}" class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank">
{{$.i18n.Tr "repo.debug"}}
</a>
{{if .CanDebug}}
{{if eq .ComputeResource "CPU/GPU"}}
<a id="model-debug-{{.JobID}}" class='ui basic {{if ne .Status "RUNNING"}} 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 ne .Status "RUNNING"}} 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">
<a class="ui basic disabled button">
{{$.i18n.Tr "repo.debug"}}
</a>
{{end}}
<form id="stopForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/stop" method="post" style="margin-left:-1px;">
<!-- 停止 -->
<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();">
{{if .CanDel}}
<a id="stop-model-debug-{{.JobID}}" class='ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "STOPPING" "CREATING" "STARTING"}}disabled {{else}}blue {{end}}button' onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
@@ -378,45 +342,59 @@
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}
<input type="hidden" name="debugListType" value="all">
</form>
<!-- 删除 -->
<form 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}}
<a id="model-delete-{{.JobID}}" class='ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED"}}blue {{else}}disabled {{end}}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 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}}" 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" value="{{.CanDebug}}">{{$.i18n.Tr "repo.submit_image"}}</a>
{{end}}
</div>
<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/del" method="post">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a id="model-delete-{{.JobID}}" class="ui basic button {{if not .CanDel}}disabled {{else}} blue {{end}}" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
<a class="ui basic blue button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
</form>
<div class="ui compact buttons" style="{{if eq .ComputeResource "CPU/GPU"}} visibility: visible {{else}} visibility: hidden{{end}}">
<div class="ui dropdown" id="model_more" style="padding: .58928571em 1.125em .58928571em;">
<div class="text">更多</div>
<i class="dropdown icon"></i>
<div class="menu" style="right: auto;">
<div class="item" style="padding: 0 !important;">
<!-- 接收结果 -->
<iframe src="" frameborder="0" name="iframeContent" style="display: none;"></iframe>
{{if .CanDel}}
<a id="model-image-{{.JobID}}" class='imageBtn ui basic {{if ne .Status "RUNNING"}}disabled {{else}}blue {{end}}button'>{{$.i18n.Tr "repo.submit_image"}}</a>
{{else}}
<a class="imageBtn ui basic disabled button">{{$.i18n.Tr "repo.submit_image"}}</a>
{{end}}
</div>
<div class="item" style="padding: 0 !important;">
<!-- 模型下载 -->
{{if .CanDel}}
<a class="ui basic blue button" href="{{$.RepoLink}}/cloudbrain/{{.JobID}}/models" target="_blank">{{$.i18n.Tr "repo.download"}}</a>
{{else}}
<a class="ui basic disabled button">{{$.i18n.Tr "repo.download"}}</a>
{{end}}
</div>
</div>
</div>
</div>
</div>
<!-- 镜像列表弹窗 -->
<div id="imageModal" class="modal" style="display: none;">
<div class="modal-content">

<!-- 表格 -->
<div class="ui form">
<form id="commitImageForm" action="{{$.Link}}/{{.JobID}}/commit_image" method="post" target="iframeContent">
<form id="commitImageForm" action="{{$.RepoLink}}/cloudbrain/{{.JobID}}/commit_image" method="post" target="iframeContent">
{{$.CsrfTokenHtml}}
<div class="row">
<p style="display: inline;">提交任务镜像</p>
@@ -433,10 +411,8 @@
<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>
<div class="ui divider"></div>
<div class="inline field">
<label></label>
<button class="ui green button" onclick="showmask()">
@@ -448,24 +424,17 @@
</div>
</div>



</div>
</div>
{{end}} {{template "base/paginate" .}}
</div>

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

</div>
</div>

</div>

<!-- 确认模态框 -->
@@ -494,6 +463,10 @@

<script>
// 调试和评分新开窗口
let url={{.RepoLink}}
let getParam=location.search.split('?debugListType=').pop()
let dropdownValue = getParam==='all'||getParam==='' ? '全部' : getParam
localStorage.setItem('all',location.href)
function stop(obj) {
if (obj.style.color != "rgb(204, 204, 204)") {
obj.target = '_blank'
@@ -501,7 +474,6 @@
return
}
}

// 删除时用户确认
function assertDelete(obj) {
if (obj.style.color == "rgb(204, 204, 204)") {
@@ -535,30 +507,24 @@
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
if (job.textContent.trim() == 'STOPPED' || job.textContent.trim() == 'FAILED') {
const computeResource = job.dataset.resource
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED']
if (initArray.includes(job.textContent.trim())) {
return
}
$.get(`/api/v1/repos/${repoPath}/cloudbrain/${jobID}`, (data) => {
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()) {
console.log("---------")
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)
//if (status == 'STOPPED') {
// window.location.reload()
//}
}
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')
@@ -567,23 +533,40 @@
$('#model-image-'+jobID).addClass('disabled')

}
if(status!=="STOPPED" || status!=="FAILED"){
if(["RUNNING","WAITING"].includes(status)){
$('#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"){
if(["CREATING","STOPPING","STARTING","STOPPED","FAILED","START_FAILED"].includes(status)){
$('#stop-model-debug-'+jobID).removeClass('blue')
$('#stop-model-debug-'+jobID).addClass('disabled')
$('#model-delete-'+jobID).removeClass('disabled')
$('#model-delete-'+jobID).addClass('red')
}
if(status==="STOPPED" || status==="FAILED"|| status==="START_FAILED"){
$('#model-delete-'+jobID).removeClass('disabled')
$('#model-delete-'+jobID).addClass('blue')
}else{
$('#model-delete-'+jobID).removeClass('blue')
$('#model-delete-'+jobID).addClass('disabled')
}
}).fail(function(err) {
console.log(err);
});
});
};
$(document).ready(function(){
$('.default.text').text(dropdownValue)
$('.ui.dropdown')
.dropdown({
action: 'hide',
})
$('.ui.selection.dropdown').dropdown({
onChange:function(value){
location.href = `${url}/debugjob?debugListType=${value}`
}
})
})

// 获取弹窗
var modal = document.getElementById('imageModal');
@@ -638,4 +621,5 @@
}
})
}
</script>

+ 20
- 85
templates/repo/header.tmpl View File

@@ -97,16 +97,21 @@
<span>{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}} <i class="dropdown icon"></i></span>
</a>
<div class="dropdown-content">
{{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo) }}
<a style="border: none;" class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases">
{{svg "octicon-tag" 16}} {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .NumReleases}}gray{{else}}blue{{end}} small label">{{.NumReleases}}</span>
</a>
{{end}}
{{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}}
<a style="border: none;" class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki" {{if (.Permission.CanRead $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}>
{{svg "octicon-book" 16}} {{.i18n.Tr "repo.wiki"}}
</a>
{{end}}
{{if and (.Permission.CanReadAny $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsEmptyRepo)}}
<a style="border: none;" class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity">
{{svg "octicon-pulse" 16}} {{.i18n.Tr "repo.activity"}}
</a>
{{end}}
</div>
</div>
@@ -134,12 +139,23 @@

{{if .Permission.CanRead $.UnitTypeDatasets}}
<a class="{{if .PageIsDataset}}active{{end}} item" href="{{.RepoLink}}/datasets?type=0">
{{svg "octicon-inbox" 16}} {{.i18n.Tr "datasets"}}
<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="M20.083 15.2l1.202.721a.5.5 0 0 1 0 .858l-8.77 5.262a1 1 0 0 1-1.03 0l-8.77-5.262a.5.5 0 0 1 0-.858l1.202-.721L12 20.05l8.083-4.85zm0-4.7l1.202.721a.5.5 0 0 1 0 .858L12 17.65l-9.285-5.571a.5.5 0 0 1 0-.858l1.202-.721L12 15.35l8.083-4.85zm-7.569-9.191l8.771 5.262a.5.5 0 0 1 0 .858L12 13 2.715 7.429a.5.5 0 0 1 0-.858l8.77-5.262a1 1 0 0 1 1.03 0zM12 3.332L5.887 7 12 10.668 18.113 7 12 3.332z"/></svg>
{{.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>
<a class="{{if .PageIsCloudBrain}}active{{end}} item" href="{{.RepoLink}}/debugjob?debugListType=all">
<span>
<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="M4 3h16a1 1 0 0 1 1 1v7H3V4a1 1 0 0 1 1-1zM3 13h18v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-7zm4 3v2h3v-2H7zM7 6v2h3V6H7z"/></svg>
{{.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>
</a>
{{end}}
<!-- {{if .IsSigned}}
@@ -164,86 +180,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
- 551
templates/repo/modelarts/notebook/index.tmpl View File

@@ -1,551 +0,0 @@
<!-- 头部导航栏 -->
{{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">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>

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

<div class="repository release dataset-list view">
{{template "repo/header" .}}
<!-- 列表容器 -->
<div class="ui container">
{{template "base/alert" .}}

<!-- 中间云脑和新建任务按钮 -->

<div class="ui two column stackable grid ">

<div class="column">
<div class="ui blue small menu compact selectcloudbrain">
<a class="active item" href="{{.RepoLink}}/modelarts/notebook">{{$.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);"> Ascend NPU</div>
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{.RepoLink}}/cloudbrain" data-value="11">CPU / GPU</a>
<a class="item" href="{{.RepoLink}}/modelarts/notebook" data-value="22">Ascend NPU</a>
</div>
</div>
{{if .Permission.CanWrite $.UnitTypeCloudBrain}}
<a class="ui green button" href="{{.RepoLink}}/modelarts/notebook/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="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
</div>
<div class="column right aligned">
<div class="ui right dropdown type jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i>
</span>
</div>
</div>
</div>
</div> -->

<!-- 任务展示 -->
<div class="dataset list">

<!-- 表头 -->
<div class="ui grid stackable" style="background: #f0f0f0;;">
<div class="row">
<div class="six wide column">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span>
</div>
<div class="three wide column">
<span>{{$.i18n.Tr "repo.cloudbrain_status_createtime"}}</span>
</div>
<div class="two wide column text center">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="five wide column text center">
<span style="margin-left: 5rem;">{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>

</div>
</div>



{{range .Tasks}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="six wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>

<div class="three wide column">
<!--任务状态 -->
<!-- <span class="ui compact button job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
{{.Status}}
</span> -->
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
<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>
<!-- 任务创建时间 -->
<span style="font-size: 12px;margin-left: 0.4rem;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>

<div class="two 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 right">
<div class="ui compact buttons">
<!-- <a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}">
查看
</a> -->
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a id="model-debug-{{.JobID}}" style="margin-right: 2rem;" class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank">
{{$.i18n.Tr "repo.debug"}}
</a>
{{else}}
<a style="margin-right: 2rem;" class="ui basic disabled button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank">
{{$.i18n.Tr "repo.debug"}}
</a>
{{end}}

<form id="stopForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/stop" method="post" style="margin-left:-1px;">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a id="stop-model-debug-{{.JobID}}" style="margin-right: 2rem;" class="ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "STOPPING" "CREATING" "WAITING" "STARTING"}}disabled {{else}}blue {{end}}button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a style="margin-right: 2rem;" class="ui basic disabled button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}

</form>
</div>

<!-- <div class="ui compact buttons" style="margin-right:10px;"> -->
<!-- 模型下载 -->
<!-- <a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}/models" target="_blank">
模型下载
</a> -->
<!-- 接收结果 -->
<!-- <iframe src="" frameborder="0" name="iframeContent" style="display: none;"></iframe>
<a class="imageBtn ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" value="{{.CanDebug}}">提交镜像</a> -->
<!-- </div> -->

<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/del" method="post">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a id="model-delete-{{.JobID}}" class="ui basic button {{if eq .Status "RUNNING" "CREATING" "WAITING" "STARTING" "STOPPING" }}disabled {{else}} blue {{end}}" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
<a class="ui basic blue button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
</form>
</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>
// 调试和评分新开窗口
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;
if (job.textContent.trim() == 'STOPPED' || job.textContent.trim() == 'START_FAILED' || job.textContent.trim() == 'CREATE_FAILED') {
return
}

$.get(`/api/v1/repos/${repoPath}/modelarts/notebook/${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 == 'STOPPED') {
// window.location.reload()
//}
}
if(status==="RUNNING"){
$('#model-debug-'+jobID).removeClass('disabled')
$('#model-debug-'+jobID).addClass('blue')

}
if(status!=="RUNNING"){
$('#model-debug-'+jobID).removeClass('blue')
$('#model-debug-'+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')
}
if(status=="START_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() {
$('#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>

+ 20
- 88
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">
@@ -110,6 +29,23 @@
</h3>
<div class="ui attached segment">
<!-- <br> -->
<div class="inline required field">
<label>计算资源</label>
<div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/cloudbrain/create">
<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 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/>
</svg>
CPU/GPU</a>
<a class="active item" href="{{.RepoLink}}/modelarts/notebook/create">
<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 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/>
</svg>
Ascend NPU</a>
</div>
</div>
<div class="inline required field">
<label>任务名称</label>
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
@@ -156,7 +92,7 @@
<button class="ui green button">
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
<a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
</div>
</form>
@@ -166,10 +102,6 @@
{{template "base/footer" .}}

<script>
// 取消创建跳转
let url_href = window.location.pathname.split('create')[0]
$(".ui.button").attr('href',url_href)

// 判断必填选项是否填写正确
let form = document.getElementById('form_id');



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

@@ -7,11 +7,11 @@

<h4 class="ui header" id="vertical-segment">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/cloudbrain">
<a class="section" href="{{.RepoLink}}/debugjob?debugListType=all">
{{.i18n.Tr "repo.cloudbrain"}}
</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/modelarts/notebook">
<a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType=NPU">
{{$.i18n.Tr "repo.modelarts.notebook"}}
</a>
<div class="divider"> / </div>


+ 26
- 275
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;
}
@@ -207,77 +29,38 @@
<!-- 列表容器 -->
<div class="ui container">
{{template "base/alert" .}}

<!-- 中间云脑和新建任务按钮 -->
<!-- <div class="ui three column stack able grid">
<div class="column">
<h2>{{.i18n.Tr "repo.cloudbrain2"}}</h2>
</div>

<div class="column">
</div>

<div class="column right aligned">
{{if .Permission.CanWrite $.UnitTypeCloudBrain}}
<a class="ui green button" href="{{.RepoLink}}/modelarts/create">{{.i18n.Tr "repo.cloudbrain.new"}}</a> {{end}}
</div>

<div class="column right aligned">
{{if .Permission.CanWrite $.UnitTypeCloudBrain}}
<a class="ui blue button" href="{{.RepoLink}}/modelarts/train-job/create">{{.i18n.Tr "repo.cloudbrain.new"}}</a> {{end}}
</div>
</div>

<p>使用鹏城云脑计算资源进行调试,云脑1提供CPU / GPU资源,云脑2提供Ascend NPU资源;调试使用的数据集也需要上传到对应的环境。</p>
<div class="ui blue mini menu selectcloudbrain">
<a class="item" href="{{.RepoLink}}/cloudbrain">{{svg "octicon-server" 16}} CPU / GPU</a>
<a class="active item" href="{{.RepoLink}}/modelarts">{{svg "octicon-server" 16}} Ascend NPU</a>
</div> -->

<div class="ui two column stackable grid ">

<div class="column">
<div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/cloudbrain">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="active item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
</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);"> Ascend NPU</div>
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{.RepoLink}}/cloudbrain" data-value="11">CPU / GPU</a>
<a class="item" href="{{.RepoLink}}/modelarts/notebook" data-value="22">Ascend NPU</a>
</div>
</div> -->
{{if .Permission.CanWrite $.UnitTypeCloudBrain}}
<a class="ui green button" href="{{.RepoLink}}/modelarts/train-job/create">{{$.i18n.Tr "repo.modelarts.train_job.new_train"}}</a>{{end}}
<a class="ui green button" href="{{.RepoLink}}/modelarts/train-job/create">{{$.i18n.Tr "repo.modelarts.train_job.new_train"}}</a>
{{else}}
<a class="ui disabled button" >{{$.i18n.Tr "repo.modelarts.train_job.new_train"}}</a>
{{end}}
</div>
</div>

{{if eq 0 (len .Tasks)}}
<div class="ui placeholder segment bgtask-none">
<div class="ui icon header bgtask-header-pic"></div>
<div class="bgtask-content-header">未创建过训练任务</div>
<div class="bgtask-content">
{{if $.RepoIsEmpty}}
<div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div>
{{end}}
<div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div>
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div>
</div>
</div>
{{else}}
<!-- 中下列表展示区 -->
<div class="ui grid">
<div class="row">
<div class="ui sixteen wide column">

<!-- 排序区 -->
<!-- <div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
</div>
<div class="column right aligned">
<div class="ui right dropdown type jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i>
</span>
</div>
</div>
</div>
</div> -->

<!-- 任务展示 -->
<div class="dataset list">

@@ -308,13 +91,9 @@
<div class="three wide column text center padding0">
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>

</div>
</div>
</div>



{{range .Tasks}}
<div class="ui grid stackable item">
<div class="row">
@@ -340,14 +119,6 @@
<div class="two wide column text center padding0">
<span style="font-size: 12px;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>
<!-- <div class="two wide column">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
<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>
<span style="font-size: 12px;margin-left: 0.4rem;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div> -->
<!-- 任务运行时间 -->
<div class="two wide column text center padding0">
<span style="font-size: 12px;" id="duration-{{.JobID}}"></span>
@@ -366,31 +137,10 @@
</div>

<div class="three wide column text center padding0">
<!-- <div class="ui compact buttons">
<form id="stopForm-{{.JobID}}" action="/api/v1/repos{{$.Link}}/{{.JobID}}/stop_version" method="post">
<input type="hidden" name="version_name" value="{{.VersionName}}">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<a style="padding: 0.5rem 1rem;" id="stop-model-debug-{{.JobID}}" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}}blue {{end}}button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a style="padding: 0.5rem 1rem;" class="ui basic disabled button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}
</form>
</div> -->
<!-- 模型下载 -->
<!-- <div class="ui compact buttons">
<a style="padding: 0.5rem;" class="ui basic blue button" href="{{$.Link}}/{{.JobID}}/models" target="_blank">
{{$.i18n.Tr "repo.model_download"}}
</a>
</div> -->
<!-- 删除任务 -->
<!-- 停止任务 -->
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})">
{{$.i18n.Tr "repo.stop"}}
</a>
@@ -401,15 +151,16 @@
{{end}}

</div>
<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/del" method="post">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
</form>
@@ -422,6 +173,7 @@
</div>
</div>
</div>
{{end}}

</div>

@@ -455,7 +207,6 @@
{{template "base/footer" .}}

<script>

console.log({{.Tasks}})
// 调试和评分新开窗口
function stop(obj) {


+ 5
- 84
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>
@@ -183,9 +103,9 @@
{{end}}
{{end}}
{{else}}
<option name="branch_name" value="{{.BranchName}}">{{.BranchName}}</option>
<option name="branch_name" value="{{.branchName}}">{{.branchName}}</option>
{{range $k, $v :=.Branches}}
{{ if ne $v $.BranchName }}
{{ if ne $v $.branchName }}
<option name="branch_name" value="{{$v}}">{{$v}}</option>
{{end}}
{{end}}
@@ -227,6 +147,7 @@
<span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
</span>
<a href="https://git.openi.org.cn/OpenIOSSG/MINIST_Example" target="_blank">查看样例</a>
</div>
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label>
@@ -239,6 +160,7 @@
<option name="attachment" value="{{.UUID}}">{{.Attachment.Name}}</option>
{{end}}
</select>
<span class="tooltips">数据集位置存储在环境变量data_url中,训练输出路径存储在环境变量train_url中。</span>
</div>
<div class="inline unite min_title field">
@@ -347,7 +269,6 @@
sever_num.val(parseInt(sever_num.val())+1)
}
})

// 参数增加、删除、修改、保存
function Add_parameter(i){
value = '<div class="two fields width85" id= "para'+ i +'">' +


+ 19
- 47
templates/repo/modelarts/trainjob/show.tmpl View File

@@ -165,7 +165,6 @@ td, th {
{{template "repo/header" .}}
<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}}/cloudbrain">
{{.i18n.Tr "repo.cloudbrain"}}
@@ -187,22 +186,21 @@ td, th {
<span class="accordion-panel-title-content">
<span>
<div style="float: right;">
<!-- <a class="ti-action-menu-item {{if ne .Status "COMPLETED"}}disabled {{end}}">创建模型</a> -->
{{$.CsrfTokenHtml}}
{{if $.canNewJob}}
{{if .CanModify}}
<a class="ti-action-menu-item" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a>
{{else}}
<a class="ti-action-menu-item disabled" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a>
{{end}}
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
{{if .CanDel}}
<a class="ti-action-menu-item {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{end}}" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a>
{{end}}
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
{{if .CanDel}}
<a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a>
{{else}}
<a class="ti-action-menu-item disabled" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a>
@@ -243,12 +241,11 @@ td, th {
<tbody class="ti-text-form">
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.cloudbrain_task"}}
{{$.i18n.Tr "repo.cloudbrain_task"}}
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.JobName}}
{{.JobName}}
</div>
</td>
</tr>
@@ -259,7 +256,7 @@ td, th {

<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-status">
{{.Status}}
{{.Status}}
</div>
</td>
</tr>
@@ -270,7 +267,7 @@ td, th {
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.VersionName}}
{{.VersionName}}
</div>
</td>
</tr>
@@ -287,7 +284,7 @@ td, th {
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}}
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}}
</td>
<td class="ti-text-form-content">
@@ -309,9 +306,8 @@ td, th {
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}}
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.WorkServerNumber}}
@@ -326,9 +322,8 @@ td, th {
<tbody class="ti-text-form">
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}}
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.EngineName}}
@@ -348,12 +343,12 @@ td, th {
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}}
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.BootFile}}
{{.BootFile}}
</div>
</td>
</tr>
@@ -379,17 +374,7 @@ td, th {
</div>
</td>
</tr>
<!-- <tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
训练输出位置
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.TrainUrl}}
</div>
</td>
</tr> -->
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.description"}}
@@ -414,15 +399,7 @@ td, th {
<div class="ui message message{{.VersionName}}" style="display: none;">
<div id="header"></div>
</div>
<!-- <div class="ui top attached segment" style="background: #f0f0f0;">
<div class="center aligned">
<label>{{$.i18n.Tr "repo.modelarts.log"}}:</label>
</div>
</div> -->
<div class="ui attached log" onscroll="logScroll({{.VersionName}})" id="log{{.VersionName}}" style="height: 300px !important; overflow: auto;">
<!-- <input type="hidden" class="version_name" name="version_name" value={{.VersionName}}> -->
<input type="hidden" name="end_line" value>
<input type="hidden" name="start_line" value>
<pre id="log_file{{.VersionName}}"></pre>
@@ -474,9 +451,7 @@ td, th {

<script>
console.log({{.version_list_task}})
console.log({{.}})
$('.menu .item').tab()
// $('.ui.style.accordion').accordion();

$(document).ready(function(){
$('.ui.accordion').accordion({selector:{trigger:'.icon'}});
@@ -503,8 +478,6 @@ td, th {
e.cancelBubble = true; //ie兼容
}
}
// let timeid = window.setInterval(refreshStatus(version_name), 30000);
// document.ready(refreshStatus(version_name))
let timeid = window.setInterval(loadJobStatus, 30000);
$(document).ready(loadJobStatus);
@@ -574,7 +547,12 @@ td, th {
},
onApprove: function() {
$.post(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/del_version`,{version_name:version_name},(data)=>{
$('#accordion'+version_name).remove()
if(data.VersionListCount===0){
location.href=`/${userName}/${repoPath}/modelarts/train-job`
}else{
$('#accordion'+version_name).remove()
}
}).fail(function(err) {
console.log(err);
});
@@ -658,12 +636,6 @@ td, th {
}
}
function downloadModelFile(version_name,filename){
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/model_download?version_name=${version_name}&file_name=${filename}`, (data) => {
console.log(data)

})
}
function renderDir(data,version_name){
let html=""
html += "<div class='ui grid' style='margin:0;'>"


+ 2
- 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">
@@ -240,6 +159,7 @@
<span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
</span>
<a href="https://git.openi.org.cn/OpenIOSSG/MINIST_Example" target="_blank">查看样例</a>
</div>
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label>
@@ -254,6 +174,7 @@
{{end}}
{{end}}
</select>
<span class="tooltips">数据集位置存储在环境变量data_url中,训练输出路径存储在环境变量train_url中。</span>
</div>
<div class="inline unite min_title field">


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

@@ -0,0 +1,240 @@
<!-- 头部导航栏 -->
{{template "base/head" .}}
<!-- 弹窗 -->
<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" style="display: none;">
<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" style="display: none;">
<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 id="model_header"></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;" id="label" name="Label" maxlength="255" placeholder='{{.i18n.Tr "repo.modelarts.train_job.label_place"}}'>
</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.model.manage.sava_model"}}
</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(){
$('#model_header').text("导入新模型")
$('input[name="Version"]').addClass('model_disabled')
$('.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').text()
$('.ui.error.message').css('display','none')
}
})
.modal('show')
}

$(function(){
$('#choice_model').dropdown({
onChange:function(value){
$(".ui.dropdown.selection.search.width70").addClass("loading")
$('#choice_version').dropdown('clear')
$("#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")
$('#choice_model .default.text').text(data[0].JobName)
$('#choice_model input[name="JobId"]').val(data[0].JobID)
loadTrainVersion()
})
}
function loadTrainVersion(value){
let JobID = !value ?$('#choice_model input[name="JobId"]').val(): value
$.get(`${repolink}/modelmanage/query_train_job_version?JobID=${JobID}`, (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>'
}
if(data.length){
$("#job-version").append(train_html)
$(".ui.dropdown.selection.search.width70").removeClass("loading")
$('#choice_version .default.text').text(data[0].VersionName)
$('#choice_version input[name="VersionName"]').val(data[0].VersionName)
}
})
}
</script>


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

@@ -0,0 +1,279 @@
{{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 class="half-table">
<span class="model_header_text">基本信息</span>
<table class="tableStyle" style="margin-top:20px;">
<tbody>
<tr>
<td class="ti-text-form-label text-width80">模型名称</td>
<td class="ti-text-form-content word-elipsis"><span id="ModelName" title=""></span></td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">版本</td>
<td class="ti-text-form-content word-elipsis"><span id="Version" title=""></span></td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">标签</td>
<td class="ti-text-form-content">
<div id="Label" style="overflow: hidden;width: 95%;">
</div>

</td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">大小</td>
<td class="ti-text-form-content word-elipsis"><span id="Size" title=""></span></td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">创建时间</td>
<td class="ti-text-form-content word-elipsis"><span id="CreateTime" title=""></span></td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">模型描述</td>
<td class="ti-text-form-content" ><div id="edit-td" style="display:flex"><span id="Description" title="" class="iword-elipsis"></span><i id="edit-pencil" data-id="" data-desc="" class="pencil alternate icon" style="cursor:pointer;vertical-align: top;" id="editor" onclick="editorFn(this)"></div></td>
</tr>
</tbody>
</table>
</div>
<div class="half-table">
<span class="model_header_text">模型精度</span>
<table class="tableStyle" style="margin-top:20px;">
<tbody>
<tr>
<td class="ti-text-form-label text-width80">准确率</td>
<td class="ti-text-form-content word-elipsis"><span id="Accuracy" title=""></span></td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">F1</td>
<td class="ti-text-form-content word-elipsis"><span id="F1" title=""></span></td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">精确率</td>
<td class="ti-text-form-content word-elipsis"><span id="Precision" title=""></span></td>
</tr>
<tr>
<td class="ti-text-form-label text-width80">召回率</td>
<td class="ti-text-form-content word-elipsis"><span id="Recall" title=""></span></td>
</tr>
</tbody>
</table>
</div>
<div style="clear: both;"></div>
</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 returnArray = []
returnArray = transObj(versionData)
let [initObj,initModelAcc,id] = returnArray
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++){
if(!data[i].IsCanOper){
$("#edit-pencil").css("display","none")
}
html += `<option value="${data[i].Version}">${data[i].Version}</option>`
}
$('#dropdown').append(html)
let returnArray = []
returnArray = transObj(data)
let [initObj,initModelAcc,id] = returnArray
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(context){
let id= context.dataset.id
let text = context.dataset.desc
console.log(id,text)
$('#edit-td').replaceWith("<div id='edit-div' style='width:80%;display: inline-block;'><textarea id='textarea-value' value='' rows='3' maxlength='255' style='width:80%;' id='edit-text'>"+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 id="Description" title="${text}" class="iword-elipsis">${text}</span><i id="edit-pencil" data-id="${id}" data-desc="${text}" class="pencil alternate icon" style="cursor:pointer;vertical-align: top;" id="editor" onclick="editorFn(this)"></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 id="Description" title="${description}" class="iword-elipsis">${description}</span><i id="edit-pencil" data-id="${id}" data-desc="${description}" class="pencil alternate icon" style="cursor:pointer;vertical-align: top;" id="editor" onclick="editorFn(this)"></div>`)
})
}
function renderInfo(obj,accObj,id){
for(let key in obj){
if(key==="Description"){
$(`#${key}`).text(obj[key])
$(`#${key}`).attr("title",obj[key])
$('#edit-pencil').attr("data-id",id)
$('#edit-pencil').attr("data-desc",obj[key])
}
else if(key==="Label"){
$('#Label').empty()
if(obj[key]==='--'){
$('#Label').text(obj[key])
}else{
let labelArray = obj[key].trim().replace(/ +/g,' ').split(' ')
let html=''
for(let i=0;i<labelArray.length;i++){
html += `<a class="ui label" title="${labelArray[i]}">${labelArray[i]}</a>`
}
$('#Label').append(html)
}
}
else{
$(`#${key}`).text(obj[key])
$(`#${key}`).attr("title",obj[key])
}
}
for(let key in accObj){
$(`#${key}`).text(accObj[key])
$(`#${key}`).attr("title",accObj[key])
}
}

</script>

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

@@ -149,7 +149,14 @@
<label>{{.i18n.Tr "repo.settings.dataset_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>
{{$isCloudBrainEnabled := .Repository.UnitEnabled $.UnitTypeCloudBrain }}
<div class="inline field">
<label>{{.i18n.Tr "repo.cloudbrain"}}</label>
@@ -158,7 +165,6 @@
<label>{{.i18n.Tr "repo.settings.cloudbrain_desc"}}</label>
</div>
</div>

{{$isWikiEnabled := or (.Repository.UnitEnabled $.UnitTypeWiki) (.Repository.UnitEnabled $.UnitTypeExternalWiki)}}
<div class="inline field">
<label>{{.i18n.Tr "repo.wiki"}}</label>


+ 60
- 8
templates/user/profile.tmpl View File

@@ -50,16 +50,46 @@
{{end}}
<li>{{svg "octicon-clock" 16}} {{.i18n.Tr "user.join_on"}} {{.Owner.CreatedUnix.FormatShort}}</li>
{{if and .Orgs .HasOrgsVisible}}
<li>
<ul class="user-orgs">
<li style="border-bottom: none;padding-bottom: 0;"><div style="border-bottom: 1px solid #eaeaea;padding-top: 5px;padding-bottom:5px"> <b> 组织</b></div></li>
<li style="padding-bottom: 0px;">
<!-- <ul class="user-orgs">
{{range .Orgs}}
{{if (or .Visibility.IsPublic (and ($.SignedUser) (or .Visibility.IsLimited (and (.IsUserPartOfOrg $.SignedUserID) .Visibility.IsPrivate) ($.IsAdmin))))}}
<li>
<a href="{{.HomeLink}}"><img class="ui image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a>
<ul>
<a href="{{.HomeLink}}"><img class="ui image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a>
<span>{{.Name}}</span>
</ul>
</li>
{{end}}
{{end}}
</ul>
</ul> -->
{{range .Orgs}}
<ul class="user-orgs">
{{if (or .Visibility.IsPublic (and ($.SignedUser) (or .Visibility.IsLimited (and (.IsUserPartOfOrg $.SignedUserID) .Visibility.IsPrivate) ($.IsAdmin))))}}
<li class="infor" style="width: 15%;" >
<a href="{{.HomeLink}}"><img class="ui image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a>
</li>
<li class="infor" style="width: 35%;">
<a class="ui image poping up" style="color: #0366D6;overflow: hidden; white-space: nowrap; text-overflow: ellipsis;" href="{{.HomeLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted">{{.Name}}</a>
</li>
<li class="infor" style="width: 25%;">
<img style="width: 14px; height: 14px;border: none;" src="/img/member.svg" >
<span style="color: rgba(0,0,0,.4);padding-left: 5px;">{{.NumMembers}}</span>
</li>
<li class="infor" style="width: 25%;">
<img style="width: 14px; height: 14px" src="/img/pro_num.svg" >
<span style="color: rgba(0,0,0,.4);padding-left: 5px;">{{.NumRepos}}</span>
</li>
{{end}}
</ul>
{{end}}
</li>
{{end}}
{{if and .IsSigned (ne .SignedUserName .Owner.Name)}}
@@ -83,15 +113,18 @@
</div>
<div class="ui eleven wide column">
<div class="ui secondary stackable pointing menu">
<a class='{{if and (ne .TabName "datasets") (ne .TabName "activity") (ne .TabName "following") (ne .TabName "followers") (ne .TabName "stars")}}active{{end}} item' href="{{.Owner.HomeLink}}">
<!-- <a class='{{if and (ne .TabName "datasets") (ne .TabName "activity") (ne .TabName "following") (ne .TabName "followers") (ne .TabName "stars")}}active{{end}} item' href="{{.Owner.HomeLink}}">
{{svg "octicon-repo" 16}} {{.i18n.Tr "user.repositories"}}
</a> -->
<a class='{{if eq .TabName "activity"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=activity">
{{svg "octicon-rss" 16}} {{.i18n.Tr "user.activity"}}
</a>
<a class='{{if eq .TabName "repository" }}active{{end}} item' href="{{.Owner.HomeLink}}?tab=repository">
{{svg "octicon-repo" 16}} {{.i18n.Tr "user.repositories"}}
</a>
<a class='{{if eq .TabName "datasets"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=datasets">
{{svg "octicon-inbox" 16}} {{.i18n.Tr "user.datasets"}}
</a>
<a class='{{if eq .TabName "activity"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=activity">
{{svg "octicon-rss" 16}} {{.i18n.Tr "user.activity"}}
</a>
<a class='{{if eq .TabName "stars"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=stars">
{{svg "octicon-star" 16}} {{.i18n.Tr "user.starred"}}
<div class="ui label">{{.Owner.NumStars}}</div>
@@ -144,3 +177,22 @@
</div>
</div>
{{template "base/footer" .}}

<style>
.infor{
margin: auto 0;
}
.user-orgs{
padding: 10px 0px;
}
.user-orgs li{
max-width: 50%;
float: right;
}
.user.profile .ui.card .extra.content ul {
padding: 5px 0;
}

</style>

+ 12
- 4
web_src/js/components/DataAnalysis.vue View File

@@ -3,7 +3,7 @@
<el-tabs tab-position="left" v-model="activeName" style="height:100%" @tab-click="handleClick" >
<el-tab-pane label="概览" name="first" >
<span slot="label">
<el-image style="width: 13px; height: 13px" src="/img/overview.png">
<el-image style="width: 13px; height: 13px" src="/img/overview_rgb.svg">
</el-image>
概览
</span>
@@ -13,14 +13,14 @@
<el-tab-pane label="项目分析" name="second" id="second" >
<ProAnalysis ref='ProAnalysis'id="pro" v-if="isRouterAlive"></ProAnalysis>
<span slot="label">
<el-image style="width: 13px; height: 13px" src="/img/pro_new.svg">
<el-image style="width: 13px; height: 13px" src="/img/pro_rgb.svg">
</el-image>
项目分析
</span>
</el-tab-pane>
<el-tab-pane name="third" id='third' >
<span slot='label'>
<el-image style="width: 13px; height: 13px" src="/img/name.png">
<el-image style="width: 13px; height: 13px" src="/img/user_rgb.svg">
</el-image>
用户分析
</span>
@@ -121,6 +121,14 @@
/deep/ .el-tabs__item {
padding: 0px 20px 0px 20px;
}

/deep/ .el-tabs__item.is-active .el-image{
filter:none
}
/deep/ .el-tabs__item:hover .el-image{
filter:none
}
/deep/ .el-image{
filter:grayscale(100%)
}
</style>

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

@@ -0,0 +1,520 @@
<template>
<div>
<div class="ui container" id="header">
<el-row style="margin-top:15px;">
<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? '#':'/'+scope.row.UserName" :title="scope.row.UserName||defaultAvatarName">
<img class="ui avatar image" :src="scope.row.UserRelAvatarLink||defaultAvatar">
</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,scope.row.Label)">创建新版本</a>
<a :href="loadhref+scope.row.ID" :class="{'disabled':!scope.row.IsCanOper}">下载</a>
<a :class="{'disabled':!scope.row.IsCanOper}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a>
</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="[5,10,15]"
: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: [],
url:'',
isLoading:true,
loadNodeMap:new Map(),
submitId:{},
defaultAvatar:'/user/avatar/Ghost/-1',
defaultAvatarName:'Ghost',
data:''
};
},
methods: {
load(tree, treeNode, resolve) {
try{
this.loadNodeMap.set(tree.cName, {tree,treeNode,resolve})
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||[])
})
}
catch(e){
this.loading = false;
}
},
tableHeaderStyle({row,column,rowIndex,columnIndex}){
if(rowIndex===0){
return 'background:#f5f5f6;color:#606266'
}
},
handleSizeChange(val){
this.params.pageSize = val
this.getModelList()
},
handleCurrentChange(val){
this.params.page = val
this.getModelList()
},
showcreateVue(name,version,label){
$('.ui.modal.second')
.modal({
centered: false,
onShow:function(){
$('#model_header').text("创建模型新版本")
$('input[name="Name"]').addClass('model_disabled')
$('input[name="Name"]').attr('readonly','readonly')
$('input[name="Version"]').addClass('model_disabled')
$('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"})
$("#job-name").empty()
$('#name').val(name)
$('#label').val(label)
let version_string = versionAdd(version)
$('#version').val(version_string)
loadTrainList()
},
onHide:function(){
document.getElementById("formId").reset();
$('input[name="Name"]').removeClass('model_disabled')
$('input[name="Name"]').removeAttr('readonly')
$('#choice_model').dropdown('clear')
$('#choice_version').dropdown('clear')
$('.ui.dimmer').css({"background-color":""})
$('.ui.error.message').text()
$('.ui.error.message').css('display','none')
}
})
.modal('show')
},
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
},
submit(){
let context = this
let flag= this.check()
if(flag){
let cName = $("input[name='Name']").val()
let version = $("input[name='Version']").val()
let data = $("#formId").serialize()
$("#mask").css({"display":"block","z-index":"9999"})
$.ajax({
url:url_href,
type:'POST',
data:data,
success:function(res){
// context.loadrefresh1(row)
context.getModelList()
$('.ui.modal.second').modal('hide')
},
error: function(xhr){
// 隐藏 loading
// 只有请求不正常(状态码不为200)才会执行
$('.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
}
},
loadrefresh(row){
const store = this.$refs.table.store
if(!this.loadNodeMap.get(row.cName)){
const parent = store.states.data
const index = parent.findIndex(child => child.ID == row.ID)
parent.splice(index, 1)
}else{
let {tree,treeNode,resolve} = this.loadNodeMap.get(row.cName)
const keys = Object.keys(store.states.lazyTreeNodeMap);
if(keys.includes(row.ID)){
this.getModelList()
}else{
let parentRow = store.states.data.find(child => child.cName == row.cName);
let childrenIndex = store.states.lazyTreeNodeMap[parentRow.ID].findIndex(child => child.ID == row.ID)
parentRow.VersionCount = parentRow.VersionCount-1
const parent = store.states.lazyTreeNodeMap[parentRow.ID]
if(parent.length===1){
this.getModelList()
}else{
parent.splice(childrenIndex, 1);
}
}
}
},
deleteModel(id,name){
let row={cName:name,ID:id}
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.loadrefresh(row)
// _this.getModelList()
})
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}else{
$('.alert').html('删除成功').removeClass('alert-danger').addClass('alert-success').show().delay(1500).fadeOut();
}
}
})
.modal('show')
},
getModelList(){
try {
this.$refs.table.store.states.lazyTreeNodeMap = {}
this.$axios.get(location.href+'_api',{
params:this.params
}).then((res)=>{
$(".ui.grid").removeAttr("style")
$("#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
})
}catch (e) {
console.log(e)
}
},
},
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.submitId = document.getElementById("submitId")
this.getModelList()
this.url = location.href.split('show_model')[0]
this.submitId.addEventListener("click", this.submit)
},

beforeDestroy() { // 实例销毁之前对点击事件进行解绑
this.submitId.removeEventListener('click', this.submit);
}
};
</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;white-space: nowrap;}
/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>

+ 11
- 1
web_src/js/components/ProAnalysis.vue View File

@@ -1085,6 +1085,12 @@
return data[0]+''+data[1]+''+data[2]
}
},
goBack(){
if( $("#pro_detail").is(':visible') ){
document.getElementById("pro_main").style.display = "block";
document.getElementById("pro_detail").style.display = "none";
}
},

},
filters:{
@@ -1125,7 +1131,7 @@
return " <a href=\" mailto:" + value.email + "class=\"circular ui button\">" +value.user+ "</a>"
}
},
},
},
@@ -1140,6 +1146,10 @@
this.radarOpenI = this.$echarts.init(document.getElementById('radar_openi'))
this.echartsOITd = this.$echarts.init(document.getElementById('line_openi'))
this.echartsSelectData = this.$echarts.init(document.getElementById('selectData'))
if (window.history && window.history.pushState) {
history.pushState(null, null, document.URL);
window.addEventListener('popstate', this.goBack, false);
}
// window.onresize=function(){
// this.radarOpenI.resize();
// this.echartsOITd.resize();


+ 3
- 3
web_src/js/components/UserAnalysis.vue View File

@@ -27,10 +27,10 @@
</span>
<span style="float:right; margin-right: 20px;" >
<a style="display:inline-block;margin-left: 20px; " id = 'download'>
<a class="el-icon-download" v-if="tableData!=''" :href= "'../tool/query_user_static_page/?startDate='+this.params.startDate+'&endDate='+this.params.endDate+'&IsReturnFile=true'+'&userName='+this.params.userName" ></a>
<a class="el-icon-download" v-if="tableData!=''" :href= "'../api/v1/query_user_static_page/?startDate='+this.params.startDate+'&endDate='+this.params.endDate+'&IsReturnFile=true'+'&userName='+this.params.userName" ></a>
<i class="el-icon-download" v-else="tableData=''" href="#" style="color:rgba(187, 187, 187, 100);" @click='popMark()'></i>
<span >
<a v-if="tableData!=''" :href= "'../tool/query_user_static_page/?startDate='+this.params.startDate+'&endDate='+this.params.endDate+'&IsReturnFile=true'+'&userName='+this.params.userName" >下载报告</a>
<a v-if="tableData!=''" :href= "'../api/v1/query_user_static_page/?startDate='+this.params.startDate+'&endDate='+this.params.endDate+'&IsReturnFile=true'+'&userName='+this.params.userName" >下载报告</a>
<a v-else="tableData=''" href= "#" style="color:rgba(187, 187, 187, 100);" @click='popMark()'>下载报告</a>
</span>
</a>
@@ -335,7 +335,7 @@
}
};

this.$axios.get('../tool/query_user_static_page',{
this.$axios.get('../api/v1/query_user_static_page',{
params:this.params
}).then((res)=>{
this.tableData = res.data.data


+ 39
- 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();
@@ -2933,6 +2935,7 @@ $(document).ready(async () => {
initNotificationsTable();
initNotificationCount();
initTribute();
initDropDown();

// Repo clone url.
if ($('#repo-clone-url').length > 0) {
@@ -3646,7 +3649,7 @@ function initVueContributors() {

function initVueImages() {
const el = document.getElementById('images');
console.log("el",el)
if (!el) {
return;
@@ -3658,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)
@@ -3728,6 +3745,7 @@ function initFilterBranchTagDropdown(selector) {
});
});
$data.remove();
console.log("-this",this)
new Vue({
delimiters: ['${', '}'],
el: this,
@@ -4100,3 +4118,22 @@ $.get(`${window.config.StaticUrlPrefix}/img/svg/icons.svg`, (data) => {
div.innerHTML = new XMLSerializer().serializeToString(data.documentElement);
document.body.insertBefore(div, document.body.childNodes[0]);
});
function initDropDown() {
$("#dropdown_PageHome").dropdown({
on:'hover' ,//鼠标悬浮显示,默认值是click
});
$("#dropdown_explore").dropdown({
on:'hover' ,//鼠标悬浮显示,默认值是click
});
}

//云脑提示
$('.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"})
});

//云脑详情页面跳转回上一个页面
$(".section.backTodeBug").attr("href",localStorage.getItem('all'))
//新建调试取消跳转
$(".ui.button.cancel").attr("href",localStorage.getItem('all'))

+ 1
- 0
web_src/less/_svg.less View File

@@ -2,6 +2,7 @@
display: inline-block;
vertical-align: text-top;
fill: currentColor;
margin-right: 5px;

.middle & {
vertical-align: middle;


+ 251
- 2
web_src/less/openi.less View File

@@ -292,7 +292,8 @@ footer .column{margin-bottom:0!important; padding-bottom:0!important;}
}
.hover_active{
height: 100% !important;
margin-top: 1px !important;
}
.dropdown-menu:hover .dropdown-content {
display: block;
@@ -335,4 +336,252 @@ display: block;
margin-left: 4px !important;
color: #3291F8;
}
}
//缺省页
.bgtask-none{
background: #fff !important;
border: 0 !important;
box-shadow: none !important;
padding-bottom: 0 !important;
padding-top: 2em !important;
}
.bgtask-header-pic {
height: 80px;
text-align: center;
background: url(//ai-studio-static-online.cdn.bcebos.com/aistudio/dist/1638363279337/images/search_empty.png) top no-repeat;
background-size: 80px 80px;
}

.bgtask-content{
margin: 2rem auto 0;
/* width: 500px; */
text-align: left;
}
.bgtask-content-txt {
font-size: 14px;
color: #a6a6a6;
line-height: 22px;
margin-bottom: 4px;
}
.bgtask-content-header {
text-align: center;
color: #3F3F40;
font-size: 18px;
margin-bottom: 1rem;
}

.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;
}
.left2{
margin-left: -2px !important;
}
.width70{
width: 70% !important;
}
.width83{
width: 83% !important;
}
.content-padding{
padding: 40px !important;
}
.model_disabled{
background-color: rgba(245, 245, 246, 100) !important;
color: rgba(136, 136, 136, 100) !important;
}
.edge{
margin-left:0 !important;
margin-right: 0 !important;
padding-left:0 !important;
padding-right:0 !important;
}
.lfpd {
padding-left:0 !important;
padding-right: 0 !important;
}
.mglf{
margin-left:0.5em !important;
}
.tooltips{
display: inline-block;
margin-left: 5.5rem;
font-size: 12px;
margin-top: 0.7rem;
color: #888888;
}

Loading…
Cancel
Save