Browse Source

Merge pull request 'V20211115' (#987) from V20211115 into develop

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/987
tags/v1.21.12.1
lewis 4 years ago
parent
commit
0ef1c90a6a
100 changed files with 6049 additions and 20598 deletions
  1. +1
    -0
      go.mod
  2. +5
    -0
      go.sum
  3. +181
    -94
      models/cloudbrain.go
  4. +47
    -0
      models/custom_migrations.go
  5. +9
    -0
      models/issue.go
  6. +4
    -2
      models/models.go
  7. +1
    -0
      models/org.go
  8. +22
    -1
      models/repo.go
  9. +112
    -12
      models/repo_activity_custom.go
  10. +11
    -0
      models/repo_collaboration.go
  11. +117
    -50
      models/repo_statistic.go
  12. +494
    -42
      models/user_business_analysis.go
  13. +5
    -0
      modules/auth/modelarts.go
  14. +4
    -15
      modules/cron/tasks_basic.go
  15. +1
    -1
      modules/git/repo_compare.go
  16. +4
    -2
      modules/git/repo_stats_custom.go
  17. +212
    -43
      modules/modelarts/modelarts.go
  18. +106
    -0
      modules/modelarts/resty.go
  19. +21
    -9
      modules/repository/elk_pagedata.go
  20. +24
    -13
      modules/setting/setting.go
  21. +69
    -22
      modules/storage/obs.go
  22. +2
    -0
      modules/templates/helper.go
  23. +5
    -0
      modules/timeutil/since.go
  24. +51
    -2
      options/locale/locale_en-US.ini
  25. +61
    -2
      options/locale/locale_zh-CN.ini
  26. +167
    -18485
      package-lock.json
  27. +5
    -1
      package.json
  28. +41
    -0
      public/img/git-logo.svg
  29. BIN
      public/img/name.png
  30. BIN
      public/img/overview.png
  31. BIN
      public/img/pro.png
  32. +1
    -0
      public/img/pro.svg
  33. +1
    -0
      public/img/pro_new.svg
  34. +21
    -1
      routers/api/v1/api.go
  35. +217
    -23
      routers/api/v1/repo/modelarts.go
  36. +641
    -0
      routers/api/v1/repo/repo_dashbord.go
  37. +8
    -3
      routers/home.go
  38. +0
    -0
      routers/org/members.go
  39. +2
    -1
      routers/private/internal.go
  40. +28
    -1
      routers/private/tool.go
  41. +5
    -3
      routers/repo/attachment.go
  42. +12
    -3
      routers/repo/cloudbrain.go
  43. +6
    -0
      routers/repo/http.go
  44. +628
    -347
      routers/repo/modelarts.go
  45. +130
    -63
      routers/repo/repo_statistic.go
  46. +160
    -7
      routers/repo/user_data_analysis.go
  47. +124
    -38
      routers/repo/view.go
  48. +15
    -16
      routers/routes/routes.go
  49. +4
    -4
      routers/user/auth.go
  50. +16
    -0
      services/mailer/mail.go
  51. +42
    -0
      templates/base/footer_content_fluid.tmpl
  52. +48
    -0
      templates/base/footer_fluid.tmpl
  53. +2
    -2
      templates/base/head.tmpl
  54. +208
    -0
      templates/base/head_fluid.tmpl
  55. +2
    -2
      templates/base/head_home.tmpl
  56. +14
    -0
      templates/base/head_navbar.tmpl
  57. +177
    -0
      templates/base/head_navbar_fluid.tmpl
  58. +6
    -0
      templates/base/head_navbar_home.tmpl
  59. +15
    -0
      templates/explore/data_analysis.tmpl
  60. +16
    -3
      templates/home.tmpl
  61. +6
    -5
      templates/org/home.tmpl
  62. +6
    -6
      templates/org/navber.tmpl
  63. +13
    -3
      templates/repo/activity.tmpl
  64. +10
    -10
      templates/repo/cloudbrain/index.tmpl
  65. +9
    -9
      templates/repo/cloudbrain/new.tmpl
  66. +13
    -1
      templates/repo/cloudbrain/show.tmpl
  67. +9
    -0
      templates/repo/contributors.tmpl
  68. +1
    -1
      templates/repo/create.tmpl
  69. +1
    -1
      templates/repo/datasets/index.tmpl
  70. +31
    -28
      templates/repo/header.tmpl
  71. +13
    -7
      templates/repo/home.tmpl
  72. +1
    -1
      templates/repo/issue/labels.tmpl
  73. +1
    -1
      templates/repo/issue/milestone_issues.tmpl
  74. +1
    -1
      templates/repo/issue/milestone_new.tmpl
  75. +1
    -1
      templates/repo/issue/milestones.tmpl
  76. +1
    -1
      templates/repo/issue/new.tmpl
  77. +1
    -1
      templates/repo/issue/new_form.tmpl
  78. +2
    -2
      templates/repo/issue/view.tmpl
  79. +1
    -1
      templates/repo/issue/view_title.tmpl
  80. +1
    -1
      templates/repo/migrate.tmpl
  81. +0
    -485
      templates/repo/modelarts/index.tmpl
  82. +0
    -240
      templates/repo/modelarts/new.tmpl
  83. +5
    -5
      templates/repo/modelarts/notebook/index.tmpl
  84. +6
    -6
      templates/repo/modelarts/notebook/new.tmpl
  85. +13
    -1
      templates/repo/modelarts/notebook/show.tmpl
  86. +0
    -122
      templates/repo/modelarts/show.tmpl
  87. +4
    -4
      templates/repo/modelarts/trainjob/edit_para.tmpl
  88. +120
    -94
      templates/repo/modelarts/trainjob/index.tmpl
  89. +78
    -94
      templates/repo/modelarts/trainjob/new.tmpl
  90. +1
    -1
      templates/repo/modelarts/trainjob/para_manage.tmpl
  91. +702
    -145
      templates/repo/modelarts/trainjob/show.tmpl
  92. +634
    -0
      templates/repo/modelarts/trainjob/version_new.tmpl
  93. +1
    -1
      templates/repo/pulls/commits.tmpl
  94. +1
    -1
      templates/repo/pulls/files.tmpl
  95. +1
    -1
      templates/repo/pulls/fork.tmpl
  96. +5
    -1
      templates/repo/release/list.tmpl
  97. +2
    -2
      templates/repo/release/new.tmpl
  98. +1
    -1
      templates/repo/settings/options.tmpl
  99. +15
    -0
      templates/repo/view_file.tmpl
  100. +7
    -0
      templates/repo/wiki/start.tmpl

+ 1
- 0
go.mod View File

@@ -17,6 +17,7 @@ require (
gitea.com/macaron/macaron v1.4.0
gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7
github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2
github.com/BurntSushi/toml v0.3.1
github.com/PuerkitoBio/goquery v1.5.0
github.com/RichardKnop/machinery v1.6.9


+ 5
- 0
go.sum View File

@@ -51,6 +51,8 @@ gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 h1:N9QFoeNsUXLhl14m
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7/go.mod h1:kgsbFPPS4P+acDYDOPDa3N4IWWOuDJt5/INKRUz7aks=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 h1:StMrA6UQ5Cm6206DxXGuV/NMqSIOIDoMXMYt8JPe1lE=
github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2/go.mod h1:EfRHD2k+Kd7ijnqlwOrH1IifwgWB9yYJ0pdXtBZmlpU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -538,6 +540,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
@@ -818,6 +822,7 @@ golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=


+ 181
- 94
models/cloudbrain.go View File

@@ -52,26 +52,46 @@ type Cloudbrain struct {
ID int64 `xorm:"pk autoincr"`
JobID string `xorm:"INDEX NOT NULL"`
JobType string `xorm:"INDEX NOT NULL DEFAULT 'DEBUG'"`
JobName string `xorm:"INDEX"`
Status string `xorm:"INDEX"`
UserID int64 `xorm:"INDEX"`
RepoID int64 `xorm:"INDEX"`
SubTaskName string `xorm:"INDEX"`
JobName string
Status string
UserID int64
RepoID int64
SubTaskName string
ContainerID string
ContainerIp string
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
Duration int64 `xorm:"INDEX duration"`
Duration int64
TrainJobDuration string
DeletedAt time.Time `xorm:"deleted"`
CanDebug bool `xorm:"-"`
CanDel bool `xorm:"-"`
Type int `xorm:"INDEX DEFAULT 0"`

VersionID int64 `xorm:"INDEX DEFAULT 0"`
VersionName string
Uuid string
DatasetName string
Type int

VersionID int64 //版本id
VersionName string `xorm:"INDEX"` //当前版本
Uuid string //数据集id
DatasetName string
VersionCount int //任务的当前版本数量,不包括删除的
IsLatestVersion string //是否是最新版本,1是,0否
CommitID string //提交的仓库代码id
PreVersionName string //父版本名称
ComputeResource string //计算资源,例如npu
EngineID int64 //引擎id

TrainUrl string //输出的obs路径
BranchName string //分支名称
Parameters string //传给modelarts的param参数
BootFile string //启动文件
DataUrl string //数据集的obs路径
LogUrl string //日志输出的obs路径
PreVersionId int64 //父版本的版本id
FlavorCode string //modelarts上的规格id
Description string `xorm:"varchar(256)"` //描述
WorkServerNumber int //节点数
FlavorName string //规格名称
EngineName string //引擎名称
TotalVersionCount int //任务的所有版本数量,包括删除的

User *User `xorm:"-"`
Repo *Repository `xorm:"-"`
@@ -150,13 +170,16 @@ type CloudbrainsOptions struct {
ListOptions
RepoID int64 // include all repos if empty
UserID int64
JobID int64
JobID string
SortType string
CloudbrainIDs []int64
// JobStatus CloudbrainStatus
Type int
JobType string
Type int
JobType string
VersionName string
IsLatestVersion string
}

type TaskPod struct {
TaskRoleStatus struct {
Name string `json:"name"`
@@ -353,6 +376,7 @@ type FlavorInfos struct {
type FlavorInfo struct {
Id int `json:"id"`
Value string `json:"value"`
Desc string `json:"desc"`
}

type PoolInfos struct {
@@ -578,20 +602,33 @@ type Config struct {
BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下
Parameter []Parameter `json:"parameter"`
DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL
//DatasetID string `json:"dataset_id"`
//DataVersionID string `json:"dataset_version_id"`
//DataSource []DataSource `json:"data_source"`
//SpecID int64 `json:"spec_id"`
EngineID int64 `json:"engine_id"`
//ModelID int64 `json:"model_id"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`
EngineID int64 `json:"engine_id"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`
//UserImageUrl string `json:"user_image_url"`
//UserCommand string `json:"user_command"`
CreateVersion bool `json:"create_version"`
//Volumes []Volumes `json:"volumes"`
Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
CreateVersion bool `json:"create_version"`
Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
}

type CreateTrainJobVersionParams struct {
Description string `json:"job_desc"`
Config TrainJobVersionConfig `json:"config"`
}

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

type CreateConfigParams struct {
@@ -602,20 +639,11 @@ type CreateConfigParams struct {
BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下
Parameter []Parameter `json:"parameter"`
DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL
//DatasetID string `json:"dataset_id"`
//DataVersionID string `json:"dataset_version_id"`
//DataSource []DataSource `json:"data_source"`
//SpecID int64 `json:"spec_id"`
EngineID int64 `json:"engine_id"`
//ModelID int64 `json:"model_id"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`
//UserImageUrl string `json:"user_image_url"`
//UserCommand string `json:"user_command"`
//CreateVersion bool `json:"create_version"`
//Volumes []Volumes `json:"volumes"`
Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
EngineID int64 `json:"engine_id"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`
Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
}

type Parameter struct {
@@ -729,18 +757,10 @@ type GetConfigResult struct {
BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下
Parameter []Parameter `json:"parameter"`
DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL
//DatasetID string `json:"dataset_id"`
//DataVersionID string `json:"dataset_version_id"`
//DataSource []DataSource `json:"data_source"`
//SpecID int64 `json:"spec_id"`
EngineID int64 `json:"engine_id"`
//ModelID int64 `json:"model_id"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`
//UserImageUrl string `json:"user_image_url"`
//UserCommand string `json:"user_command"`
//CreateVersion bool `json:"create_version"`
//Volumes []Volumes `json:"volumes"`
EngineID int64 `json:"engine_id"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`

Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
}
@@ -771,25 +791,18 @@ type GetTrainJobResult struct {
BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下
Parameter []Parameter `json:"parameter"`
DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL
//DatasetID string `json:"dataset_id"`
//DataVersionID string `json:"dataset_version_id"`
//DataSource []DataSource `json:"data_source"`
//SpecID int64 `json:"spec_id"`
EngineID int64 `json:"engine_id"`
EngineName string `json:"engine_name"`
EngineVersion string `json:"engine_version"`
//ModelID int64 `json:"model_id"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`
//UserImageUrl string `json:"user_image_url"`
//UserCommand string `json:"user_command"`
//Volumes []Volumes `json:"volumes"`
Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
PoolName string `json:"pool_name"`
NasMountPath string `json:"nas_mount_path"`
NasShareAddr string `json:"nas_share_addr"`
DatasetName string
EngineID int64 `json:"engine_id"`
EngineName string `json:"engine_name"`
EngineVersion string `json:"engine_version"`
TrainUrl string `json:"train_url"` //训练作业的输出文件OBS路径URL
LogUrl string `json:"log_url"`
Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"`
PoolName string `json:"pool_name"`
NasMountPath string `json:"nas_mount_path"`
NasShareAddr string `json:"nas_share_addr"`
DatasetName string
ModelMetricList string `json:"model_metric_list"` //列表里包含f1_score,recall,precision,accuracy,若有的话
}

type GetTrainJobLogResult struct {
@@ -836,7 +849,7 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
)
}

if (opts.JobID) > 0 {
if (opts.JobID) != "" {
cond = cond.And(
builder.Eq{"cloudbrain.job_id": opts.JobID},
)
@@ -854,16 +867,11 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
)
}

// switch opts.JobStatus {
// case JobWaiting:
// cond.And(builder.Eq{"cloudbrain.status": int(JobWaiting)})
// case JobFailed:
// cond.And(builder.Eq{"cloudbrain.status": int(JobFailed)})
// case JobStopped:
// cond.And(builder.Eq{"cloudbrain.status": int(JobStopped)})
// case JobSucceeded:
// cond.And(builder.Eq{"cloudbrain.status": int(JobSucceeded)})
// }
if (opts.IsLatestVersion) != "" {
cond = cond.And(
builder.Eq{"cloudbrain.is_latest_version": opts.IsLatestVersion},
)
}

if len(opts.CloudbrainIDs) > 0 {
cond = cond.And(builder.In("cloudbrain.id", opts.CloudbrainIDs))
@@ -891,16 +899,79 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
Find(&cloudbrains); err != nil {
return nil, 0, fmt.Errorf("Find: %v", err)
}
sess.Close()

return cloudbrains, count, nil
}

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

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

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

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

if (opts.JobID) != "" {
cond = cond.And(
builder.Eq{"cloudbrain.job_id": opts.JobID},
)
}

if (opts.JobType) != "" {
cond = cond.And(
builder.Eq{"cloudbrain.job_type": opts.JobType},
)
}

if len(opts.CloudbrainIDs) > 0 {
cond = cond.And(builder.In("cloudbrain.id", opts.CloudbrainIDs))
}

count, err := sess.Where(cond).Count(new(Cloudbrain))
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("cloudbrain.created_unix DESC")
cloudbrains := make([]*CloudbrainInfo, 0, setting.UI.IssuePagingNum)
if err := sess.Table(&Cloudbrain{}).Where(cond).
Join("left", "`user`", "cloudbrain.user_id = `user`.id").
Find(&cloudbrains); err != nil {
return nil, 0, fmt.Errorf("Find: %v", err)
}

return cloudbrains, int(count), nil
}

func CreateCloudbrain(cloudbrain *Cloudbrain) (err error) {
if _, err = x.Insert(cloudbrain); err != nil {
return err
}

return nil
}

@@ -924,6 +995,16 @@ func GetCloudbrainByJobID(jobID string) (*Cloudbrain, error) {
return getRepoCloudBrain(cb)
}

func GetCloudbrainByJobIDAndVersionName(jobID string, versionName string) (*Cloudbrain, error) {
cb := &Cloudbrain{JobID: jobID, VersionName: versionName}
return getRepoCloudBrain(cb)
}

func GetCloudbrainByJobIDAndIsLatestVersion(jobID string, isLatestVersion string) (*Cloudbrain, error) {
cb := &Cloudbrain{JobID: jobID, IsLatestVersion: isLatestVersion}
return getRepoCloudBrain(cb)
}

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)
@@ -948,6 +1029,12 @@ func SetTrainJobStatusByJobID(jobID string, status string, duration int64, train
return
}

func SetVersionCountAndLatestVersion(jobID string, versionName string, versionCount int, isLatestVersion string, totalVersionCount int) (err error) {
cb := &Cloudbrain{JobID: jobID, VersionName: versionName, VersionCount: versionCount, IsLatestVersion: isLatestVersion, TotalVersionCount: totalVersionCount}
_, err = x.Cols("version_Count", "is_latest_version", "total_version_count").Where("cloudbrain.job_id=? AND cloudbrain.version_name=?", jobID, versionName).Update(cb)
return
}

func UpdateJob(job *Cloudbrain) error {
return updateJob(x, job)
}
@@ -959,16 +1046,16 @@ func updateJob(e Engine, job *Cloudbrain) error {
return err
}

// func UpdateTrainJob(job *CloudbrainInfo) error {
// return updateTrainJob(x, job)
// }
func UpdateTrainJobVersion(job *Cloudbrain) error {
return updateJobTrainVersion(x, job)
}

// func updateTrainJob(e Engine, job *CloudbrainInfo) error {
// var sess *xorm.Session
// sess = e.Where("job_id = ?", job.Cloudbrain.JobID)
// _, err := sess.Cols("status", "container_id", "container_ip").Update(job)
// return err
// }
func updateJobTrainVersion(e Engine, job *Cloudbrain) error {
var sess *xorm.Session
sess = e.Where("job_id = ? AND version_name=?", job.JobID, job.VersionName)
_, err := sess.Cols("status", "train_job_duration").Update(job)
return err
}

func DeleteJob(job *Cloudbrain) error {
return deleteJob(x, job)


+ 47
- 0
models/custom_migrations.go View File

@@ -1,6 +1,8 @@
package models

import (
"fmt"

"code.gitea.io/gitea/modules/log"
"xorm.io/xorm"
)
@@ -10,10 +12,20 @@ type CustomMigration struct {
Migrate func(*xorm.Engine) error
}

type CustomMigrationStatic struct {
Description string
Migrate func(*xorm.Engine, *xorm.Engine) error
}

var customMigrations = []CustomMigration{
{"Custom v1 Topic struct change to support chinese", syncTopicStruct},
}

var customMigrationsStatic = []CustomMigrationStatic{
{"Delete organization user history data ", deleteNotDisplayUser},
{"update issue_fixed_rate to 1 if num_issues is 0 ", updateIssueFixedRate},
}

func MigrateCustom(x *xorm.Engine) {

for _, m := range customMigrations {
@@ -27,6 +39,17 @@ func MigrateCustom(x *xorm.Engine) {

}

func MigrateCustomStatic(x *xorm.Engine, static *xorm.Engine) {
for _, m := range customMigrationsStatic {
log.Info("Migration: %s", m.Description)
if err := m.Migrate(x, static); err != nil {

log.Error("Migration: %v", err)

}
}
}

func syncTopicStruct(x *xorm.Engine) error {

query := "ALTER TABLE topic ALTER COLUMN name TYPE varchar(105);"
@@ -34,3 +57,27 @@ func syncTopicStruct(x *xorm.Engine) error {
_, err := x.Exec(query)
return err
}

func deleteNotDisplayUser(x *xorm.Engine, static *xorm.Engine) error {

querySQL := "select id,name from public.user where type=1"
rows, err := x.Query(querySQL)
if err != nil {
log.Info("select db failed,err:", err)
return err
}

for i, userRow := range rows {
log.Info("delete zuzi user, i=" + fmt.Sprint(i) + " userName=" + string(userRow["name"]))
deleteSql := "delete from user_business_analysis where id=" + string(userRow["id"]) + " and name='" + string(userRow["name"]) + "'"
static.Exec(deleteSql)
}

return nil
}

func updateIssueFixedRate(x *xorm.Engine, static *xorm.Engine) error {
updateSQL := "update repo_statistic set issue_fixed_rate=1.0 where num_issues=0"
_, err := static.Exec(updateSQL)
return err
}

+ 9
- 0
models/issue.go View File

@@ -1356,6 +1356,15 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
return accum, nil
}

func GetPullCountByUserAndRepoId(repoId int64, userId int64) int64 {
issue := new(Issue)
total, err := x.Where("is_pull=true and repo_id=? and poster_id=?", repoId, userId).Count(issue)
if err != nil {
return 0
}
return total
}

func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats, error) {
stats := &IssueStats{}



+ 4
- 2
models/models.go View File

@@ -139,6 +139,7 @@ func init() {
new(RepoStatistic),
new(SummaryStatistic),
new(UserBusinessAnalysis),
new(UserBusinessAnalysisAll),
new(UserLoginLog),
)

@@ -190,7 +191,7 @@ func setEngine(engine *xorm.Engine, table []interface{}, database *setting.DBInf
engine.SetMaxIdleConns(setting.Database.MaxIdleConns)
engine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
engine.Sync2(table...)
MigrateCustom(engine)
return nil
}

@@ -222,7 +223,7 @@ func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err e
if err = newEngine(ctx, migrateFunc, x, tables, setting.Database); err != nil {
return fmt.Errorf("newEngine failed: %v", err)
}
MigrateCustom(x)
xStatistic, err = getEngine(setting.DatabaseStatistic)
if err != nil {
return fmt.Errorf("Failed to connect to database: %v", err)
@@ -230,6 +231,7 @@ func NewEngine(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err e
if err = newEngine(ctx, migrateFunc, xStatistic, tablesStatistic, setting.DatabaseStatistic); err != nil {
return fmt.Errorf("newEngine statistic failed: %v", err)
}
MigrateCustomStatic(x, xStatistic)

HasEngine = true



+ 1
- 0
models/org.go View File

@@ -182,6 +182,7 @@ func CreateOrganization(org, owner *User) (err error) {
if _, err = sess.Insert(&OrgUser{
UID: owner.ID,
OrgID: org.ID,
IsPublic: setting.Service.DefaultOrgMemberVisible,
}); err != nil {
return fmt.Errorf("insert org-user relation: %v", err)
}


+ 22
- 1
models/repo.go View File

@@ -210,9 +210,12 @@ type Repository struct {
Balance string `xorm:"NOT NULL DEFAULT '0'"`
BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"`

// git clone total count
// git clone and git pull total count
CloneCnt int64 `xorm:"NOT NULL DEFAULT 0"`

// only git clone total count
GitCloneCnt int64 `xorm:"NOT NULL DEFAULT 0"`

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

@@ -2473,6 +2476,24 @@ func (repo *Repository) IncreaseCloneCnt() {
return
}

func (repo *Repository) IncreaseGitCloneCnt() {
sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return
}
if _, err := sess.Exec("UPDATE `repository` SET git_clone_cnt = git_clone_cnt + 1 WHERE id = ?", repo.ID); err != nil {
return
}

if err := sess.Commit(); err != nil {
return
}

return
}

func UpdateRepositoryCommitNum(repo *Repository) error {
if _, err := x.Exec("UPDATE `repository` SET num_commit = ? where id = ?", repo.NumCommit, repo.ID); err != nil {
return err


+ 112
- 12
models/repo_activity_custom.go View File

@@ -2,29 +2,43 @@ package models

import (
"fmt"
"sort"
"strings"
"time"

"code.gitea.io/gitea/modules/log"

"code.gitea.io/gitea/modules/git"
)

type ContributorWithUserId struct {
git.Contributor
UserId int64
IsAdmin bool
RelAvatarLink string
}

func GetRepoKPIStats(repo *Repository) (*git.RepoKPIStats, error) {
wikiPath := ""
if repo.HasWiki() {
wikiPath = repo.WikiPath()
}
return getRepoKPIStats(repo.RepoPath(), wikiPath)
repoCreated := time.Unix(int64(repo.CreatedUnix), 0)
return getRepoKPIStats(repo.RepoPath(), repoCreated, wikiPath)
}

func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error) {
func getRepoKPIStats(repoPath string, repoCreated time.Time, wikiPath string) (*git.RepoKPIStats, error) {
stats := &git.RepoKPIStats{}

contributors, err := git.GetContributors(repoPath)
contributors, err := git.GetContributorsDetail(repoPath, repoCreated)
if err != nil {
return nil, err
}
timeUntil := time.Now()
fourMonthAgo := timeUntil.AddDate(0, -4, 0)
if fourMonthAgo.Before(repoCreated) {
fourMonthAgo = repoCreated
}
recentlyContributors, err := git.GetContributorsDetail(repoPath, fourMonthAgo)
newContributersDict := make(map[string]struct{})
if err != nil {
@@ -34,7 +48,7 @@ func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error
if contributors != nil {
contributorDistinctDict := make(map[string]int, 0)
keyContributorsDict := make(map[string]struct{}, 0)
var commitsCount int64
for _, contributor := range contributors {
if strings.Compare(contributor.Email, "") == 0 {
continue
@@ -60,33 +74,52 @@ func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error
setKeyContributerDict(contributorDistinctDict, contributor.Email, keyContributorsDict)
}

commitsCount += int64(contributor.CommitCnt)

}

if recentlyContributors != nil {
resentlyContributorDistinctDict := make(map[string]int, 0)
for _, recentlyContributor := range recentlyContributors {

user, err := GetUserByActivateEmail(recentlyContributor.Email)
var ok bool
if err == nil {
_, ok = contributorDistinctDict[user.Email]
value, ok := resentlyContributorDistinctDict[user.Email]
if !ok {
resentlyContributorDistinctDict[user.Email] = recentlyContributor.CommitCnt
} else {
resentlyContributorDistinctDict[user.Email] = value + recentlyContributor.CommitCnt
}

} else {
_, ok = contributorDistinctDict[recentlyContributor.Email]
value, ok := resentlyContributorDistinctDict[recentlyContributor.Email]
if !ok {
resentlyContributorDistinctDict[recentlyContributor.Email] = recentlyContributor.CommitCnt
} else {
resentlyContributorDistinctDict[recentlyContributor.Email] = value + recentlyContributor.CommitCnt
}

}

if !ok {
}

for k, v := range resentlyContributorDistinctDict {
count, ok := contributorDistinctDict[k]
if ok && count == v {
stats.ContributorsAdded++
newContributersDict[recentlyContributor.Email] = struct{}{}
}

}

}

stats.Contributors = int64(len(contributorDistinctDict))
stats.KeyContributors = int64(len(keyContributorsDict))
stats.Commits = int64(commitsCount)
}

err = git.SetDevelopAge(repoPath, stats)
err = git.SetDevelopAge(repoPath, stats, repoCreated)
if err != nil {
return nil, fmt.Errorf("FillFromGit: %v", err)
}
@@ -101,6 +134,72 @@ func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error

}

func GetTop10Contributor(repoPath string) ([]*ContributorWithUserId, error) {
contributors, err := git.GetContributors(repoPath)
if err != nil {
return make([]*ContributorWithUserId, 0), err
}
contributorDistinctDict := make(map[string]*ContributorWithUserId, 0)
if contributors != nil {
for _, contributor := range contributors {
if strings.Compare(contributor.Email, "") == 0 {
continue
}

user, err := GetUserByActivateEmail(contributor.Email)
if err == nil {

value, ok := contributorDistinctDict[user.Email]
if !ok {
contributorDistinctDict[user.Email] = &ContributorWithUserId{
git.Contributor{
contributor.CommitCnt,
user.Name,
user.Email,
},
user.ID,
user.IsAdmin,
user.RelAvatarLink(),
}
} else {

value.CommitCnt += contributor.CommitCnt
}

} else {
value, ok := contributorDistinctDict[contributor.Email]
if !ok {
contributorDistinctDict[contributor.Email] = &ContributorWithUserId{
contributor,
-1,
false,
"",
}
} else {
value.CommitCnt += contributor.CommitCnt
}

}

}
v := make([]*ContributorWithUserId, 0, len(contributorDistinctDict))
for _, value := range contributorDistinctDict {
v = append(v, value)
}

sort.Slice(v, func(i, j int) bool {
return v[i].CommitCnt > v[j].CommitCnt
})

if len(v) <= 10 {
return v, nil
} else {
return v[0:10], nil
}
}
return make([]*ContributorWithUserId, 0), nil
}

func setKeyContributerDict(contributorDistinctDict map[string]int, email string, keyContributorsDict map[string]struct{}) {
if contributorDistinctDict[email] >= 3 {
_, ok := keyContributorsDict[email]
@@ -122,7 +221,8 @@ func GetAllUserKPIStats() (map[string]*git.UserKPIStats, error) {
for _, repository := range repositorys {
authorsOneRepo, err1 := git.GetUserKPIStats(repository.RepoPath())
if err1 != nil {
return nil, err
log.Warn("get user kpi status err:"+repository.RepoPath(), err1.Error())
continue
}

for key, value := range authorsOneRepo {


+ 11
- 0
models/repo_collaboration.go View File

@@ -114,6 +114,17 @@ func (repo *Repository) isCollaborator(e Engine, userID int64) (bool, error) {
return e.Get(&Collaboration{RepoID: repo.ID, UserID: userID})
}

func (repo *Repository) GetCollaboratorMode(userID int64) int {
collaboration := &Collaboration{RepoID: repo.ID, UserID: userID}
has, err := x.Get(&collaboration)
if err != nil || !has {
return -1
} else {
return int(collaboration.Mode)
}

}

// IsCollaborator check if a user is a collaborator of a repository
func (repo *Repository) IsCollaborator(userID int64) (bool, error) {
return repo.isCollaborator(x, userID)


+ 117
- 50
models/repo_statistic.go View File

@@ -9,56 +9,58 @@ import (

// RepoStatistic statistic info of all repository
type RepoStatistic struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"unique(s) NOT NULL"`
Name string `xorm:"INDEX"`
IsPrivate bool
Date string `xorm:"unique(s) NOT NULL"`
NumWatches int64 `xorm:"NOT NULL DEFAULT 0"`
NumWatchesAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumStars int64 `xorm:"NOT NULL DEFAULT 0"`
NumStarsAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumForks int64 `xorm:"NOT NULL DEFAULT 0"`
NumForksAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumDownloads int64 `xorm:"NOT NULL DEFAULT 0"`
NumDownloadsAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumComments int64 `xorm:"NOT NULL DEFAULT 0"`
NumCommentsAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumVisits int64 `xorm:"NOT NULL DEFAULT 0"`
NumClosedIssues int64 `xorm:"NOT NULL DEFAULT 0"`
NumClosedIssuesAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumVersions int64 `xorm:"NOT NULL DEFAULT 0"`
NumDevMonths int64 `xorm:"NOT NULL DEFAULT 0"`
RepoSize int64 `xorm:"NOT NULL DEFAULT 0"`
DatasetSize int64 `xorm:"NOT NULL DEFAULT 0"`
NumModels int64 `xorm:"NOT NULL DEFAULT 0"`
NumWikiViews int64 `xorm:"NOT NULL DEFAULT 0"`
NumCommits int64 `xorm:"NOT NULL DEFAULT 0"`
NumCommitsAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumIssues int64 `xorm:"NOT NULL DEFAULT 0"`
NumIssuesAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumPulls int64 `xorm:"NOT NULL DEFAULT 0"`
NumPullsAdded int64 `xorm:"NOT NULL DEFAULT 0"`
IssueFixedRate float32 `xorm:"NOT NULL"`
NumContributor int64 `xorm:"NOT NULL DEFAULT 0"`
NumContributorAdded int64 `xorm:"NOT NULL DEFAULT 0"`
NumKeyContributor int64 `xorm:"NOT NULL DEFAULT 0"`

NumContributorsGrowth int64 `xorm:"NOT NULL DEFAULT 0"`
NumCommitsGrowth int64 `xorm:"NOT NULL DEFAULT 0"`
NumCommitLinesGrowth int64 `xorm:"NOT NULL DEFAULT 0"`
NumIssuesGrowth int64 `xorm:"NOT NULL DEFAULT 0"`
NumCommentsGrowth int64 `xorm:"NOT NULL DEFAULT 0"`

Impact float64 `xorm:"NOT NULL DEFAULT 0"`
Completeness float64 `xorm:"NOT NULL DEFAULT 0"`
Liveness float64 `xorm:"NOT NULL DEFAULT 0"`
ProjectHealth float64 `xorm:"NOT NULL DEFAULT 0"`
TeamHealth float64 `xorm:"NOT NULL DEFAULT 0"`
Growth float64 `xorm:"NOT NULL DEFAULT 0"`
RadarTotal float64 `xorm:"NOT NULL DEFAULT 0"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
ID int64 `xorm:"pk autoincr" json:"-"`
RepoID int64 `xorm:"unique(s) NOT NULL" json:"repo_id"`
Name string `xorm:"INDEX" json:"name"`
OwnerName string `json:"ownerName"`
IsPrivate bool `json:"isPrivate"`
IsMirror bool `json:"isMirror"`
Date string `xorm:"unique(s) NOT NULL" json:"date"`
NumWatches int64 `xorm:"NOT NULL DEFAULT 0" json:"watch"`
NumWatchesAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumStars int64 `xorm:"NOT NULL DEFAULT 0" json:"star"`
NumStarsAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumForks int64 `xorm:"NOT NULL DEFAULT 0" json:"fork"`
NumForksAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumDownloads int64 `xorm:"NOT NULL DEFAULT 0" json:"download"`
NumDownloadsAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumComments int64 `xorm:"NOT NULL DEFAULT 0" json:"comment"`
NumCommentsAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumVisits int64 `xorm:"NOT NULL DEFAULT 0" json:"view"`
NumClosedIssues int64 `xorm:"NOT NULL DEFAULT 0" json:"issueClosed"`
NumClosedIssuesAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumVersions int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumDevMonths int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
RepoSize int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
DatasetSize int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumModels int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumWikiViews int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumCommits int64 `xorm:"NOT NULL DEFAULT 0" json:"commit"`
NumCommitsAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumIssues int64 `xorm:"NOT NULL DEFAULT 0" json:"issue"`
NumIssuesAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumPulls int64 `xorm:"NOT NULL DEFAULT 0" json:"pr"`
NumPullsAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
IssueFixedRate float32 `xorm:"NOT NULL" json:"issueClosedRatio"`
NumContributor int64 `xorm:"NOT NULL DEFAULT 0" json:"contributor"`
NumContributorAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumKeyContributor int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`

NumContributorsGrowth int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumCommitsGrowth int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumCommitLinesGrowth int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumIssuesGrowth int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`
NumCommentsGrowth int64 `xorm:"NOT NULL DEFAULT 0" json:"-"`

Impact float64 `xorm:"NOT NULL DEFAULT 0" json:"impact"`
Completeness float64 `xorm:"NOT NULL DEFAULT 0" json:"completeness"`
Liveness float64 `xorm:"NOT NULL DEFAULT 0" json:"liveness"`
ProjectHealth float64 `xorm:"NOT NULL DEFAULT 0" json:"projectHealth"`
TeamHealth float64 `xorm:"NOT NULL DEFAULT 0" json:"teamHealth"`
Growth float64 `xorm:"NOT NULL DEFAULT 0" json:"growth"`
RadarTotal float64 `xorm:"NOT NULL DEFAULT 0" json:"openi"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created" json:"-"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated" json:"-"`
}

func DeleteRepoStatDaily(date string) error {
@@ -81,6 +83,57 @@ func DeleteRepoStatDaily(date string) error {
return nil
}

func CountRepoStatByRawSql(sql string) (int64, error) {

return xStatistic.SQL(sql).Count()

}

func GetRepoStatisticByRawSql(sql string) []*RepoStatistic {
repoStatistics := make([]*RepoStatistic, 0)
xStatistic.SQL(sql).Find(&repoStatistics)
return repoStatistics
}

func GetRepoStatLastUpdatedTime(repoId ...string) (string, string, error) {

repoStatistic := new(RepoStatistic)
var has bool
var err error
if len(repoId) == 0 {
has, err = xStatistic.Desc("created_unix").Limit(1).Cols("created_unix", "date").Get(repoStatistic)

} else {
has, err = xStatistic.Where("repo_id=?", repoId[0]).Desc("created_unix").Limit(1).Cols("created_unix", "date").Get(repoStatistic)
}

if err != nil {
return "", "", err
} else {
if has {
return repoStatistic.CreatedUnix.Format("2006-01-02 15:04:05"), repoStatistic.Date, nil
} else {
return "", "", fmt.Errorf("Can not get the latest record.")
}
}

}

func GetRepoStatisticByDateAndRepoId(date string, repoId int64) (*RepoStatistic, error) {
repoStatistic := new(RepoStatistic)
has, err := xStatistic.Where("date=? and repo_id=?", date, repoId).Get(repoStatistic)
if err != nil {
return nil, err
} else {
if has {
return repoStatistic, nil
} else {
return nil, fmt.Errorf("The num of return records is 0.")
}
}

}

func GetRepoStatisticByDate(date string, repoId int64) ([]*RepoStatistic, error) {
repoStatistics := make([]*RepoStatistic, 0)
err := xStatistic.Where("date = ? and repo_id=?", date, repoId).Find(&repoStatistics)
@@ -107,9 +160,23 @@ func InsertRepoStat(repoStat *RepoStatistic) (int64, error) {
return xStatistic.Insert(repoStat)
}

func RestoreRepoStatFork(numForks int64, repoId int64) error {
sql := "update repo_statistic set num_forks=? where repo_id=?"

_, err := xStatistic.Exec(sql, numForks, repoId)
return err
}

func UpdateRepoStat(repoStat *RepoStatistic) error {
sql := "update repo_statistic set impact=?,completeness=?,liveness=?,project_health=?,team_health=?,growth=?,radar_total=? where repo_id=? and date=?"

_, err := xStatistic.Exec(sql, repoStat.Impact, repoStat.Completeness, repoStat.Liveness, repoStat.ProjectHealth, repoStat.TeamHealth, repoStat.Growth, repoStat.RadarTotal, repoStat.RepoID, repoStat.Date)
return err
}

func UpdateRepoStatVisits(repoStat *RepoStatistic) error {
sql := "update repo_statistic set num_visits=? where repo_id=? and date=?"

_, err := xStatistic.Exec(sql, repoStat.NumVisits, repoStat.RepoID, repoStat.Date)
return err
}

+ 494
- 42
models/user_business_analysis.go View File

@@ -1,13 +1,83 @@
package models

import (
"encoding/json"
"fmt"
"sort"
"strconv"
"time"

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

type UserBusinessAnalysisAll struct {
ID int64 `xorm:"pk"`

CountDate int64 `xorm:"pk"`

//action :ActionMergePullRequest // 11
CodeMergeCount int `xorm:"NOT NULL DEFAULT 0"`

//action :ActionCommitRepo // 5
CommitCount int `xorm:"NOT NULL DEFAULT 0"`

//action :ActionCreateIssue // 10
IssueCount int `xorm:"NOT NULL DEFAULT 0"`

//comment table current date
CommentCount int `xorm:"NOT NULL DEFAULT 0"`

//watch table current date
FocusRepoCount int `xorm:"NOT NULL DEFAULT 0"`

//star table current date
StarRepoCount int `xorm:"NOT NULL DEFAULT 0"`

//follow table
WatchedCount int `xorm:"NOT NULL DEFAULT 0"`

// user table
GiteaAgeMonth int `xorm:"NOT NULL DEFAULT 0"`

//
CommitCodeSize int `xorm:"NOT NULL DEFAULT 0"`

//attachement table
CommitDatasetSize int `xorm:"NOT NULL DEFAULT 0"`

//0
CommitModelCount int `xorm:"NOT NULL DEFAULT 0"`

//issue, issueassignees
SolveIssueCount int `xorm:"NOT NULL DEFAULT 0"`

//baike
EncyclopediasCount int `xorm:"NOT NULL DEFAULT 0"`

//user
RegistDate timeutil.TimeStamp `xorm:"NOT NULL"`

//repo
CreateRepoCount int `xorm:"NOT NULL DEFAULT 0"`

//login count, from elk
LoginCount int `xorm:"NOT NULL DEFAULT 0"`

//openi index
OpenIIndex float64 `xorm:"NOT NULL DEFAULT 0"`

//user
Email string `xorm:"NOT NULL"`

//user
Name string `xorm:"NOT NULL"`

DataDate string `xorm:"NULL"`
}

type UserBusinessAnalysis struct {
ID int64 `xorm:"pk"`

@@ -19,7 +89,7 @@ type UserBusinessAnalysis struct {
//action :ActionCommitRepo // 5
CommitCount int `xorm:"NOT NULL DEFAULT 0"`

//action :ActionCommentIssue // 10
//action :ActionCreateIssue // 6
IssueCount int `xorm:"NOT NULL DEFAULT 0"`

//comment table current date
@@ -62,63 +132,260 @@ type UserBusinessAnalysis struct {
LoginCount int `xorm:"NOT NULL DEFAULT 0"`

//openi index
OpenIIndex int `xorm:"NOT NULL DEFAULT 0"`
OpenIIndex float64 `xorm:"NOT NULL DEFAULT 0"`

//user
Email string `xorm:"NOT NULL"`

//user
Name string `xorm:"NOT NULL"`

DataDate string `xorm:"NULL"`
}

type UserBusinessAnalysisQueryOptions struct {
ListOptions
UserName string
SortType string
StartTime int64
EndTime int64
IsAll bool
}

type UserBusinessAnalysisList []*UserBusinessAnalysis

func (ulist UserBusinessAnalysisList) Swap(i, j int) { ulist[i], ulist[j] = ulist[j], ulist[i] }
func (ulist UserBusinessAnalysisList) Len() int { return len(ulist) }
func (ulist UserBusinessAnalysisList) Less(i, j int) bool {
return ulist[i].ID > ulist[j].ID
}

type UserBusinessAnalysisAllList []*UserBusinessAnalysisAll

func (ulist UserBusinessAnalysisAllList) Swap(i, j int) { ulist[i], ulist[j] = ulist[j], ulist[i] }
func (ulist UserBusinessAnalysisAllList) Len() int { return len(ulist) }
func (ulist UserBusinessAnalysisAllList) Less(i, j int) bool {
return ulist[i].ID > ulist[j].ID
}

func getLastCountDate() int64 {
statictisSess := xStatistic.NewSession()
defer statictisSess.Close()
statictisSess.Limit(1, 0)
userBusinessAnalysisList := make([]*UserBusinessAnalysis, 0)
if err := statictisSess.Table("user_business_analysis").OrderBy("count_date desc").Limit(1, 0).
Find(&userBusinessAnalysisList); err == nil {
for _, userRecord := range userBusinessAnalysisList {
return userRecord.CountDate - 10000
}
} else {
log.Info("query error." + err.Error())
}
currentTimeNow := time.Now()
pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location())
return pageStartTime.Unix()
}

func QueryUserStaticData(startTime int64, endTime int64) []*UserBusinessAnalysis {
log.Info("query startTime =" + fmt.Sprint(startTime) + " endTime=" + fmt.Sprint(endTime))
func QueryUserStaticDataAll(opts *UserBusinessAnalysisQueryOptions) ([]*UserBusinessAnalysisAll, int64) {
log.Info("query startTime =" + fmt.Sprint(opts.StartTime) + " endTime=" + fmt.Sprint(opts.EndTime) + " isAll=" + fmt.Sprint(opts.IsAll))

statictisSess := xStatistic.NewSession()
defer statictisSess.Close()

statictisSess.Select("*").Table("user_business_analysis").Where(" count_date>=" + fmt.Sprint(startTime) + " and count_date<=" + fmt.Sprint(endTime)).OrderBy("count_date desc")
allCount, err := statictisSess.Count(new(UserBusinessAnalysisAll))
if err != nil {
log.Info("query error." + err.Error())
return nil, 0
}
log.Info("query return total:" + fmt.Sprint(allCount))
if allCount == 0 {
RefreshUserStaticAllTabel()
}
pageSize := 1000
totalPage := int(allCount) / pageSize
userBusinessAnalysisReturnList := UserBusinessAnalysisAllList{}
for i := 0; i <= int(totalPage); i++ {
userBusinessAnalysisAllList := make([]*UserBusinessAnalysisAll, 0)
if err := statictisSess.Table("user_business_analysis_all").OrderBy("id desc").Limit(pageSize, i*pageSize).
Find(&userBusinessAnalysisAllList); err != nil {
return nil, 0
}
log.Info("query " + fmt.Sprint(i+1) + " result size=" + fmt.Sprint(len(userBusinessAnalysisAllList)))
for _, userRecord := range userBusinessAnalysisAllList {
userBusinessAnalysisReturnList = append(userBusinessAnalysisReturnList, userRecord)
}
}

sort.Sort(userBusinessAnalysisReturnList)
log.Info("return size=" + fmt.Sprint(len(userBusinessAnalysisReturnList)))
return userBusinessAnalysisReturnList, allCount
}

func QueryUserStaticDataPage(opts *UserBusinessAnalysisQueryOptions) ([]*UserBusinessAnalysis, int64) {

log.Info("query startTime =" + fmt.Sprint(opts.StartTime) + " endTime=" + fmt.Sprint(opts.EndTime) + " isAll=" + fmt.Sprint(opts.IsAll))
statictisSess := xStatistic.NewSession()
defer statictisSess.Close()

currentTimeNow := time.Now()
pageStartTime := getLastCountDate()
pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()).Unix()

var cond = builder.NewCond()
if len(opts.UserName) > 0 {
cond = cond.And(
builder.Like{"name", opts.UserName},
)
}
cond = cond.And(
builder.Gte{"count_date": pageStartTime},
)
cond = cond.And(
builder.Lte{"count_date": pageEndTime},
)

count, err := statictisSess.Where(cond).Count(new(UserBusinessAnalysis))
if err != nil {
log.Info("query error." + err.Error())
return nil, 0
}

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

userBusinessAnalysisList := make([]*UserBusinessAnalysis, 0)
statictisSess.Find(&userBusinessAnalysisList)
if err := statictisSess.Table("user_business_analysis").Where(cond).OrderBy("id desc").
Find(&userBusinessAnalysisList); err != nil {
return nil, 0
}

resultMap := make(map[int64]*UserBusinessAnalysis)
log.Info("query result size=" + fmt.Sprint(len(userBusinessAnalysisList)))
for _, userRecord := range userBusinessAnalysisList {
if _, ok := resultMap[userRecord.ID]; !ok {
resultMap[userRecord.ID] = userRecord
} else {
resultMap[userRecord.ID].CodeMergeCount += userRecord.CodeMergeCount
resultMap[userRecord.ID].CommitCount += userRecord.CommitCount
resultMap[userRecord.ID].IssueCount += userRecord.IssueCount
resultMap[userRecord.ID].CommentCount += userRecord.CommentCount
resultMap[userRecord.ID].FocusRepoCount += userRecord.FocusRepoCount
resultMap[userRecord.ID].StarRepoCount += userRecord.StarRepoCount
resultMap[userRecord.ID].WatchedCount += userRecord.WatchedCount
resultMap[userRecord.ID].CommitCodeSize += userRecord.CommitCodeSize
resultMap[userRecord.ID].CommitDatasetSize += userRecord.CommitDatasetSize
resultMap[userRecord.ID].CommitModelCount += userRecord.CommitModelCount
resultMap[userRecord.ID].SolveIssueCount += userRecord.SolveIssueCount
resultMap[userRecord.ID].EncyclopediasCount += userRecord.EncyclopediasCount
resultMap[userRecord.ID].CreateRepoCount += userRecord.CreateRepoCount
resultMap[userRecord.ID].LoginCount += userRecord.LoginCount
}
}

userBusinessAnalysisReturnList := make([]*UserBusinessAnalysis, len(resultMap))
index := 0

if len(userBusinessAnalysisList) > 0 {
var newAndCond = builder.NewCond()
var newOrCond = builder.NewCond()
for _, userRecord := range userBusinessAnalysisList {
newOrCond = newOrCond.Or(
builder.Eq{"id": userRecord.ID},
)
}
newAndCond = newAndCond.And(
newOrCond,
)
if !opts.IsAll {
newAndCond = newAndCond.And(
builder.Gte{"count_date": opts.StartTime},
)
newAndCond = newAndCond.And(
builder.Lte{"count_date": opts.EndTime},
)
}

allCount, err := statictisSess.Where(newAndCond).Count(new(UserBusinessAnalysis))
if err != nil {
log.Info("query error." + err.Error())
return nil, 0
}

pageSize := 1000
totalPage := int(allCount) / pageSize

for i := 0; i <= int(totalPage); i++ {
userBusinessAnalysisList = make([]*UserBusinessAnalysis, 0)
if err := statictisSess.Table("user_business_analysis").Where(newAndCond).OrderBy("count_date desc").Limit(pageSize, i*pageSize).
Find(&userBusinessAnalysisList); err != nil {
return nil, 0
}
log.Info("query result size=" + fmt.Sprint(len(userBusinessAnalysisList)))
for _, userRecord := range userBusinessAnalysisList {
if _, ok := resultMap[userRecord.ID]; !ok {
resultMap[userRecord.ID] = userRecord
} else {
resultMap[userRecord.ID].CodeMergeCount += userRecord.CodeMergeCount
resultMap[userRecord.ID].CommitCount += userRecord.CommitCount
resultMap[userRecord.ID].IssueCount += userRecord.IssueCount
resultMap[userRecord.ID].CommentCount += userRecord.CommentCount
resultMap[userRecord.ID].FocusRepoCount += userRecord.FocusRepoCount
resultMap[userRecord.ID].StarRepoCount += userRecord.StarRepoCount
resultMap[userRecord.ID].WatchedCount += userRecord.WatchedCount
resultMap[userRecord.ID].CommitCodeSize += userRecord.CommitCodeSize
resultMap[userRecord.ID].CommitDatasetSize += userRecord.CommitDatasetSize
resultMap[userRecord.ID].CommitModelCount += userRecord.CommitModelCount
resultMap[userRecord.ID].SolveIssueCount += userRecord.SolveIssueCount
resultMap[userRecord.ID].EncyclopediasCount += userRecord.EncyclopediasCount
resultMap[userRecord.ID].CreateRepoCount += userRecord.CreateRepoCount
resultMap[userRecord.ID].LoginCount += userRecord.LoginCount
}
}
}
}

userBusinessAnalysisReturnList := UserBusinessAnalysisList{}
for _, v := range resultMap {
userBusinessAnalysisReturnList[index] = v
index += 1
userBusinessAnalysisReturnList = append(userBusinessAnalysisReturnList, v)
}
sort.Sort(userBusinessAnalysisReturnList)
log.Info("return size=" + fmt.Sprint(len(userBusinessAnalysisReturnList)))
return userBusinessAnalysisReturnList
return userBusinessAnalysisReturnList, count
}

func CounDataByDate(wikiCountMap map[string]int, startTime time.Time, endTime time.Time) {
func RefreshUserStaticAllTabel() {

statictisSess := xStatistic.NewSession()
defer statictisSess.Close()
log.Info("delete all data from table: user_business_analysis_all")
statictisSess.Exec("delete from user_business_analysis_all")

currentTimeNow := time.Now()
pageStartTime := getLastCountDate()
pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()).Unix()

var cond = builder.NewCond()
cond = cond.And(
builder.Gte{"count_date": pageStartTime},
)
cond = cond.And(
builder.Lte{"count_date": pageEndTime},
)
userBusinessAnalysisList := make([]*UserBusinessAnalysis, 0)
if err := statictisSess.Table("user_business_analysis").Where(cond).OrderBy("id desc").
Find(&userBusinessAnalysisList); err != nil {
return
}
log.Info("query all data from table: user_business_analysis,len=" + fmt.Sprint(len(userBusinessAnalysisList)))
for _, userRecord := range userBusinessAnalysisList {
log.Info("insert to UserBusinessAnalysisAll table,user id=" + fmt.Sprint(userRecord.ID))
allData := getAllData(userRecord.ID, statictisSess)
allData.ID = userRecord.ID
allData.CountDate = 0
allData.DataDate = userRecord.DataDate
allData.Email = userRecord.Email
allData.OpenIIndex = userRecord.OpenIIndex
allData.GiteaAgeMonth = userRecord.GiteaAgeMonth
allData.Name = userRecord.Name
allData.RegistDate = userRecord.RegistDate

_, err := statictisSess.Insert(&allData)
if err != nil {
log.Info("insert all data failed." + err.Error())
}
}
log.Info("refresh all data finished.")
}

func CounDataByDateAndReCount(wikiCountMap map[string]int, startTime time.Time, endTime time.Time, isReCount bool) error {

log.Info("start to count other user info data")
sess := x.NewSession()
defer sess.Close()
sess.Select("`user`.*").Table("user")
sess.Select("`user`.*").Table("user").Where("type != 1 and is_active=true")
userList := make([]*User, 0)
sess.Find(&userList)

@@ -132,12 +399,15 @@ func CounDataByDate(wikiCountMap map[string]int, startTime time.Time, endTime ti

//endTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location())
end_unix := endTime.Unix()

CountDate := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 1, 0, 0, currentTimeNow.Location())
if isReCount {
CountDate = time.Date(startTime.Year(), startTime.Month(), startTime.Day(), 0, 1, 0, 0, currentTimeNow.Location())
}

DataDate := startTime.Format("2006-01-02")
CodeMergeCountMap := queryPullRequest(start_unix, end_unix)
CommitCountMap := queryAction(start_unix, end_unix, 5)
IssueCountMap := queryAction(start_unix, end_unix, 10)
CommitCountMap := queryCommitAction(start_unix, end_unix, 5)
IssueCountMap := queryAction(start_unix, end_unix, 6)

CommentCountMap := queryComment(start_unix, end_unix)
FocusRepoCountMap := queryWatch(start_unix, end_unix)
@@ -154,6 +424,7 @@ func CounDataByDate(wikiCountMap map[string]int, startTime time.Time, endTime ti
SolveIssueCountMap := querySolveIssue(start_unix, end_unix)
CreateRepoCountMap := queryUserCreateRepo(start_unix, end_unix)
LoginCountMap := queryLoginCount(start_unix, end_unix)
OpenIIndexMap := queryUserRepoOpenIIndex(start_unix, end_unix)

statictisSess := xStatistic.NewSession()
defer statictisSess.Close()
@@ -170,6 +441,7 @@ func CounDataByDate(wikiCountMap map[string]int, startTime time.Time, endTime ti
dateRecord.RegistDate = userRecord.CreatedUnix
dateRecord.Name = userRecord.Name
dateRecord.GiteaAgeMonth = subMonth(currentTimeNow, userRecord.CreatedUnix.AsTime())
dateRecord.DataDate = DataDate
if _, ok := CodeMergeCountMap[dateRecord.ID]; !ok {
dateRecord.CodeMergeCount = 0
} else {
@@ -248,11 +520,114 @@ func CounDataByDate(wikiCountMap map[string]int, startTime time.Time, endTime ti
dateRecord.LoginCount = LoginCountMap[dateRecord.ID]
}

if _, ok := OpenIIndexMap[dateRecord.ID]; !ok {
dateRecord.OpenIIndex = 0
} else {
dateRecord.OpenIIndex = OpenIIndexMap[dateRecord.ID]
}

dateRecord.CommitModelCount = 0

statictisSess.Insert(&dateRecord)
_, err = statictisSess.Insert(&dateRecord)
if err != nil {
log.Info("insert daterecord failed." + err.Error())
return err
}

if isExistUserInAllTable(dateRecord.ID, statictisSess) {
updateCurrentData(dateRecord.ID, statictisSess, dateRecord)
} else {
log.Info("insert to UserBusinessAnalysisAll table,user id=" + fmt.Sprint(dateRecord.ID))
allData := getAllData(dateRecord.ID, statictisSess)
allData.ID = dateRecord.ID
allData.CountDate = 0
allData.DataDate = dateRecord.DataDate
allData.Email = dateRecord.Email
allData.OpenIIndex = dateRecord.OpenIIndex
allData.GiteaAgeMonth = dateRecord.GiteaAgeMonth
allData.Name = dateRecord.Name
allData.RegistDate = dateRecord.RegistDate

_, err = statictisSess.Insert(&allData)
if err != nil {
log.Info("insert all data failed." + err.Error())
return err
}
}

}
return nil
}

func updateCurrentData(userId int64, statictisSess *xorm.Session, currentData UserBusinessAnalysis) {

_, err := statictisSess.Update("update user_business_analysis_all set code_merge_count+=" + fmt.Sprint(currentData.CodeMergeCount) +
",commit_count+=" + fmt.Sprint(currentData.CommitCount) +
",issue_count+=" + fmt.Sprint(currentData.IssueCount) +
",comment_count+=" + fmt.Sprint(currentData.CommentCount) +
",focus_repo_count+=" + fmt.Sprint(currentData.FocusRepoCount) +
",star_repo_count+=" + fmt.Sprint(currentData.StarRepoCount) +
",watched_count+=" + fmt.Sprint(currentData.WatchedCount) +
",commit_code_size+=" + fmt.Sprint(currentData.CommitCodeSize) +
",commit_dataset_size+=" + fmt.Sprint(currentData.CommitDatasetSize) +
",commit_model_count+=" + fmt.Sprint(currentData.CommitModelCount) +
",solve_issue_count+=" + fmt.Sprint(currentData.SolveIssueCount) +
",encyclopedias_count+=" + fmt.Sprint(currentData.EncyclopediasCount) +
",create_repo_count+=" + fmt.Sprint(currentData.CreateRepoCount) +
",login_count+=" + fmt.Sprint(currentData.LoginCount) +
" where id=" + fmt.Sprint(userId))

if err != nil {
log.Info("update table failed." + err.Error())
}

}

func isExistUserInAllTable(userId int64, statictisSess *xorm.Session) bool {

allCount, err := statictisSess.Where("id=" + fmt.Sprint(userId)).Count(new(UserBusinessAnalysisAll))
if err != nil {
return false
}
return allCount > 0
}

func getAllData(userId int64, statictisSess *xorm.Session) UserBusinessAnalysisAll {
var dateRecord UserBusinessAnalysisAll

rows, err := statictisSess.Query("select sum(code_merge_count) as code_merge_count,sum(commit_count) as commit_count,sum(issue_count) as issue_count,sum(issue_count) as issue_count,sum(comment_count) as comment_count,sum(focus_repo_count) as focus_repo_count,sum(star_repo_count) as star_repo_count,sum(watched_count) as watched_count,sum(commit_code_size) as commit_code_size,sum(commit_dataset_size) as commit_dataset_size, sum(commit_model_count) as commit_model_count,sum(solve_issue_count) as solve_issue_count,sum(encyclopedias_count) as encyclopedias_count, sum(create_repo_count) as create_repo_count,sum(login_count) as login_count from public.user_business_analysis where id=" + fmt.Sprint(userId) + " group by id")
if err == nil {
for i, row := range rows {
log.Info("query user info, i=" + fmt.Sprint(i) + " code_merge_count=" + string(row["code_merge_count"]))
dateRecord.CodeMergeCount = getInt(string(row["code_merge_count"]))
dateRecord.CommitCount = getInt(string(row["commit_count"]))
dateRecord.IssueCount = getInt(string(row["issue_count"]))
dateRecord.CommentCount = getInt(string(row["comment_count"]))
dateRecord.FocusRepoCount = getInt(string(row["focus_repo_count"]))
dateRecord.StarRepoCount = getInt(string(row["star_repo_count"]))
dateRecord.WatchedCount = getInt(string(row["watched_count"]))
dateRecord.CommitCodeSize = getInt(string(row["commit_code_size"]))
dateRecord.CommitDatasetSize = getInt(string(row["commit_dataset_size"]))
dateRecord.CommitModelCount = getInt(string(row["commit_model_count"]))
dateRecord.SolveIssueCount = getInt(string(row["solve_issue_count"]))
dateRecord.EncyclopediasCount = getInt(string(row["encyclopedias_count"]))
dateRecord.CreateRepoCount = getInt(string(row["create_repo_count"]))
dateRecord.LoginCount = getInt(string(row["login_count"]))
}
}
return dateRecord
}

func getInt(str string) int {
re, err := strconv.ParseInt(str, 10, 32)
if err != nil {
return 0
}
return int(re)
}

func CounDataByDate(wikiCountMap map[string]int, startTime time.Time, endTime time.Time) {
CounDataByDateAndReCount(wikiCountMap, startTime, endTime, false)
}

func querySolveIssue(start_unix int64, end_unix int64) map[int64]int {
@@ -299,6 +674,24 @@ func queryPullRequest(start_unix int64, end_unix int64) map[int64]int {
return resultMap
}

func queryCommitAction(start_unix int64, end_unix int64, actionType int64) map[int64]int {
sess := x.NewSession()
defer sess.Close()
sess.Select("id,user_id,op_type,act_user_id").Table("action").Where("user_id=act_user_id and op_type=" + fmt.Sprint(actionType) + " and created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
actionList := make([]*Action, 0)
sess.Find(&actionList)
resultMap := make(map[int64]int)
log.Info("query action size=" + fmt.Sprint(len(actionList)))
for _, actionRecord := range actionList {
if _, ok := resultMap[actionRecord.UserID]; !ok {
resultMap[actionRecord.UserID] = 1
} else {
resultMap[actionRecord.UserID] += 1
}
}
return resultMap
}

func queryAction(start_unix int64, end_unix int64, actionType int64) map[int64]int {
sess := x.NewSession()
defer sess.Close()
@@ -386,10 +779,10 @@ func queryFollow(start_unix int64, end_unix int64) map[int64]int {
resultMap := make(map[int64]int)
log.Info("query Follow size=" + fmt.Sprint(len(followList)))
for _, followRecord := range followList {
if _, ok := resultMap[followRecord.UserID]; !ok {
resultMap[followRecord.UserID] = 1
if _, ok := resultMap[followRecord.FollowID]; !ok {
resultMap[followRecord.FollowID] = 1
} else {
resultMap[followRecord.UserID] += 1
resultMap[followRecord.FollowID] += 1
}
}
return resultMap
@@ -432,6 +825,62 @@ func queryUserCreateRepo(start_unix int64, end_unix int64) map[int64]int {
return resultMap
}

func queryUserRepoOpenIIndex(start_unix int64, end_unix int64) map[int64]float64 {
statictisSess := xStatistic.NewSession()
defer statictisSess.Close()
statictisSess.Select("repo_id,radar_total").Table("repo_statistic").Where("created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix))
repoStatisticList := make([]*RepoStatistic, 0)
statictisSess.Find(&repoStatisticList)
repoOpenIIndexMap := make(map[int64]float64)
log.Info("query repo_statistic size=" + fmt.Sprint(len(repoStatisticList)))
for _, repoRecord := range repoStatisticList {
if _, ok := repoOpenIIndexMap[repoRecord.RepoID]; !ok {
repoOpenIIndexMap[repoRecord.RepoID] = repoRecord.RadarTotal
}
}

sess := x.NewSession()
defer sess.Close()
sess.Select("id,owner_id,name").Table("repository").Where("is_fork=false")
repoList := make([]*Repository, 0)
sess.Find(&repoList)

userMap := make(map[int64]float64)

log.Info("query Repository size=" + fmt.Sprint(len(repoList)))
for _, repoRecord := range repoList {
if _, ok := userMap[repoRecord.OwnerID]; !ok {
if _, ok := repoOpenIIndexMap[repoRecord.ID]; ok {
userMap[repoRecord.OwnerID] = repoOpenIIndexMap[repoRecord.ID]
}
}
}

//query collaboration
sess.Select("repo_id,user_id,mode").Table("collaboration")
collaborationList := make([]*Collaboration, 0)
sess.Find(&collaborationList)

log.Info("query collaborationList size=" + fmt.Sprint(len(collaborationList)))

for _, collaborationRecord := range collaborationList {
if _, ok := userMap[collaborationRecord.UserID]; !ok {
if _, ok := repoOpenIIndexMap[collaborationRecord.RepoID]; ok {
userMap[collaborationRecord.UserID] = repoOpenIIndexMap[collaborationRecord.RepoID]
}
} else {
if _, ok := repoOpenIIndexMap[collaborationRecord.RepoID]; ok {
userMap[collaborationRecord.UserID] += repoOpenIIndexMap[collaborationRecord.RepoID]
}
}
}

userMapJson, _ := json.Marshal(userMap)
log.Info("userMapJson=" + string(userMapJson))

return userMap
}

func queryLoginCount(start_unix int64, end_unix int64) map[int64]int {
statictisSess := xStatistic.NewSession()
defer statictisSess.Close()
@@ -470,5 +919,8 @@ func subMonth(t1, t2 time.Time) (month int) {
}
monthInterval %= 12
month = yearInterval*12 + monthInterval
if month == 0 {
month = 1
}
return month
}

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

@@ -19,6 +19,7 @@ type CreateModelArtsNotebookForm struct {
JobName string `form:"job_name" binding:"Required"`
Attachment string `form:"attachment"`
Description string `form:"description"`
Flavor string `form:"flavor"`
}

func (f *CreateModelArtsNotebookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -38,6 +39,10 @@ type CreateModelArtsTrainJobForm struct {
IsSaveParam string `form:"is_save_para"`
ParameterTemplateName string `form:"parameter_template_name"`
PrameterDescription string `form:"parameter_description"`
BranchName string `form:"branch_name" binding:"Required"`
VersionName string `form:"version_name" binding:"Required"`
FlavorName string `form:"flaver_names" binding:"Required"`
EngineName string `form:"engine_names" binding:"Required"`
}

func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {


+ 4
- 15
modules/cron/tasks_basic.go View File

@@ -163,13 +163,13 @@ func registerHandleBlockChainUnSuccessCommits() {
})
}

func registerHandleRepoStatistic() {
RegisterTaskFatal("handle_repo_statistic", &BaseConfig{
func registerHandleRepoAndUserStatistic() {
RegisterTaskFatal("handle_repo_and_user_statistic", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@daily",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.RepoStatisticAuto()
repo.StatisticAuto()
return nil
})
}
@@ -184,16 +184,6 @@ func registerHandleSummaryStatistic() {
return nil
})
}
func registerHandleUserStatistic() {
RegisterTaskFatal("handle_user_statistic", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@daily",
}, func(ctx context.Context, _ *models.User, _ Config) error {
repo.TimingCountData()
return nil
})
}

func initBasicTasks() {
registerUpdateMirrorTask()
@@ -210,7 +200,6 @@ func initBasicTasks() {
registerHandleBlockChainMergedPulls()
registerHandleBlockChainUnSuccessCommits()

registerHandleRepoStatistic()
registerHandleUserStatistic()
registerHandleRepoAndUserStatistic()
registerHandleSummaryStatistic()
}

+ 1
- 1
modules/git/repo_compare.go View File

@@ -32,7 +32,7 @@ func (repo *Repository) GetMergeBase(tmpRemote string, base, head string) (strin
if tmpRemote != "origin" {
tmpBaseName := "refs/remotes/" + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags
_, err := NewCommand("fetch", tmpRemote, base+":"+tmpBaseName).RunInDir(repo.Path)
_, err := NewCommand("fetch", tmpRemote, base+":"+tmpBaseName,"--no-tags").RunInDir(repo.Path)
if err == nil {
base = tmpBaseName
}


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

@@ -18,6 +18,7 @@ type RepoKPIStats struct {
KeyContributors int64
DevelopAge int64
ContributorsAdded int64
Commits int64
CommitsAdded int64
CommitLinesModified int64
WikiPages int64
@@ -35,8 +36,9 @@ type UserKPITypeStats struct {
isNewContributor bool //是否是4个月内的新增贡献者
}

func SetDevelopAge(repoPath string, stats *RepoKPIStats) error {
args := []string{"log", "--no-merges", "--branches=*", "--format=%cd", "--date=short"}
func SetDevelopAge(repoPath string, stats *RepoKPIStats, fromTime time.Time) error {
since := fromTime.Format(time.RFC3339)
args := []string{"log", "--no-merges", "--branches=*", "--format=%cd", "--date=short", fmt.Sprintf("--since='%s'", since)}
stdout, err := NewCommand(args...).RunInDirBytes(repoPath)
if err != nil {
return err


+ 212
- 43
modules/modelarts/modelarts.go View File

@@ -2,6 +2,7 @@ package modelarts

import (
"encoding/json"
"fmt"
"path"
"strconv"

@@ -35,19 +36,24 @@ const (
// "{\"code\":\"modelarts.bm.910.arm.public.4\",\"value\":\"Ascend : 4 * Ascend 910 CPU:96 核 1024GiB\"}," +
// "{\"code\":\"modelarts.bm.910.arm.public.1\",\"value\":\"Ascend : 1 * Ascend 910 CPU:24 核 256GiB\"}" +
// "]}"
CodePath = "/code/"
OutputPath = "/output/"
LogPath = "/log/"
JobPath = "/job/"
OrderDesc = "desc" //向下查询
OrderAsc = "asc" //向上查询
Lines = 20
TrainUrl = "train_url"
DataUrl = "data_url"
PerPage = 10

SortByCreateTime = "create_time"
ConfigTypeCustom = "custom"
CodePath = "/code/"
OutputPath = "/output/"
LogPath = "/log/"
JobPath = "/job/"
OrderDesc = "desc" //向下查询
OrderAsc = "asc" //向上查询
Lines = 500
TrainUrl = "train_url"
DataUrl = "data_url"
PerPage = 10
IsLatestVersion = "1"
NotLatestVersion = "0"
ComputeResource = "NPU"
VersionCount = 1

SortByCreateTime = "create_time"
ConfigTypeCustom = "custom"
TotalVersionCount = 1
)

var (
@@ -56,19 +62,55 @@ var (
)

type GenerateTrainJobReq struct {
JobName string
Uuid string
Description string
CodeObsPath string
BootFile string
DataUrl string
TrainUrl string
FlavorCode string
LogUrl string
PoolID string
WorkServerNumber int
EngineID int64
Parameters []models.Parameter
JobName string
Uuid string
Description string
CodeObsPath string
BootFile string
BootFileUrl string
DataUrl string
TrainUrl string
FlavorCode string
LogUrl string
PoolID string
WorkServerNumber int
EngineID int64
Parameters []models.Parameter
CommitID string
IsLatestVersion string
Params string
BranchName string
PreVersionId int64
PreVersionName string
FlavorName string
VersionCount int
EngineName string
TotalVersionCount int
}

type GenerateTrainJobVersionReq struct {
JobName string
Uuid string
Description string
CodeObsPath string
BootFile string
BootFileUrl string
DataUrl string
TrainUrl string
FlavorCode string
LogUrl string
PoolID string
WorkServerNumber int
EngineID int64
Parameters []models.Parameter
Params string
PreVersionId int64
CommitID string
BranchName string
FlavorName string
EngineName string
PreVersionName string
TotalVersionCount int
}

type VersionInfo struct {
@@ -99,7 +141,23 @@ type ResourcePool struct {
} `json:"resource_pool"`
}

func GenerateTask(ctx *context.Context, jobName, uuid, description string) error {
// type Parameter struct {
// Label string `json:"label"`
// Value string `json:"value"`
// }

// type Parameters struct {
// Parameter []Parameter `json:"parameter"`
// }

type Parameters struct {
Parameter []struct {
Label string `json:"label"`
Value string `json:"value"`
} `json:"parameter"`
}

func GenerateTask(ctx *context.Context, jobName, uuid, description, flavor string) error {
var dataActualPath string
if uuid != "" {
dataActualPath = setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" + uuid + "/"
@@ -128,7 +186,7 @@ func GenerateTask(ctx *context.Context, jobName, uuid, description string) error
JobName: jobName,
Description: description,
ProfileID: setting.ProfileID,
Flavor: setting.Flavor,
Flavor: flavor,
Pool: models.Pool{
ID: poolInfos.PoolInfo[0].PoolId,
Name: poolInfos.PoolInfo[0].PoolName,
@@ -170,14 +228,14 @@ func GenerateTask(ctx *context.Context, jobName, uuid, description string) error
return nil
}

func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) error {
func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error) {
jobResult, err := createTrainJob(models.CreateTrainJobParams{
JobName: req.JobName,
Description: req.Description,
Config: models.Config{
WorkServerNum: req.WorkServerNumber,
AppUrl: req.CodeObsPath,
BootFileUrl: req.BootFile,
BootFileUrl: req.BootFileUrl,
DataUrl: req.DataUrl,
EngineID: req.EngineID,
TrainUrl: req.TrainUrl,
@@ -198,21 +256,38 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) error {
attach, err := models.GetAttachmentByUUID(req.Uuid)
if err != nil {
log.Error("GetAttachmentByUUID(%s) failed:%v", strconv.FormatInt(jobResult.JobID, 10), err.Error())
return nil
return err
}

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

if err != nil {
@@ -223,6 +298,96 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) error {
return nil
}

func GenerateTrainJobVersion(ctx *context.Context, req *GenerateTrainJobReq, jobId string) (err error) {
jobResult, err := createTrainJobVersion(models.CreateTrainJobVersionParams{
Description: req.Description,
Config: models.TrainJobVersionConfig{
WorkServerNum: req.WorkServerNumber,
AppUrl: req.CodeObsPath,
BootFileUrl: req.BootFileUrl,
DataUrl: req.DataUrl,
EngineID: req.EngineID,
TrainUrl: req.TrainUrl,
LogUrl: req.LogUrl,
PoolID: req.PoolID,
Flavor: models.Flavor{
Code: req.FlavorCode,
},
Parameter: req.Parameters,
PreVersionId: req.PreVersionId,
},
}, jobId)
if err != nil {
log.Error("CreateJob failed: %v", err.Error())
return err
}

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

repo := ctx.Repo.Repository
VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeTrain),
JobID: strconv.FormatInt(jobResult.JobID, 10),
})
if err != nil {
ctx.ServerError("Cloudbrain", err)
return err
}
//将当前版本的isLatestVersion设置为"1"和任务数量更新,任务数量包括当前版本数VersionCount和历史创建的总版本数TotalVersionCount

err = models.CreateCloudbrain(&models.Cloudbrain{
Status: TransTrainJobStatus(jobResult.Status),
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
JobID: strconv.FormatInt(jobResult.JobID, 10),
JobName: req.JobName,
JobType: string(models.JobTypeTrain),
Type: models.TypeCloudBrainTwo,
VersionID: jobResult.VersionID,
VersionName: jobResult.VersionName,
Uuid: req.Uuid,
DatasetName: attach.Name,
CommitID: req.CommitID,
IsLatestVersion: req.IsLatestVersion,
PreVersionName: req.PreVersionName,
ComputeResource: ComputeResource,
EngineID: req.EngineID,
TrainUrl: req.TrainUrl,
BranchName: req.BranchName,
Parameters: req.Params,
BootFile: req.BootFile,
DataUrl: req.DataUrl,
LogUrl: req.LogUrl,
PreVersionId: req.PreVersionId,
FlavorCode: req.FlavorCode,
Description: req.Description,
WorkServerNumber: req.WorkServerNumber,
FlavorName: req.FlavorName,
EngineName: req.EngineName,
TotalVersionCount: VersionTaskList[0].TotalVersionCount + 1,
VersionCount: VersionListCount + 1,
})
if err != nil {
log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error())
return err
}

//将训练任务的上一版本的isLatestVersion设置为"0"
err = models.SetVersionCountAndLatestVersion(strconv.FormatInt(jobResult.JobID, 10), VersionTaskList[0].VersionName, VersionCount, NotLatestVersion, TotalVersionCount)
if err != nil {
ctx.ServerError("Update IsLatestVersion failed", err)
return err
}

return err
}

func TransTrainJobStatus(status int) string {
switch status {
case 0:
@@ -273,6 +438,10 @@ func TransTrainJobStatus(status int) string {
default:
return strconv.Itoa(status)
}
}

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

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

@@ -366,6 +366,16 @@ sendjob:
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("createTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
BootFileErrorMsg := "Invalid OBS path '" + createJobParams.Config.BootFileUrl + "'."
DataSetErrorMsg := "Invalid OBS path '" + createJobParams.Config.DataUrl + "'."
if temp.ErrorMsg == BootFileErrorMsg {
log.Error("启动文件错误!createTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return &result, fmt.Errorf("启动文件错误!")
}
if temp.ErrorMsg == DataSetErrorMsg {
log.Error("数据集错误!createTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return &result, fmt.Errorf("数据集错误!")
}
return &result, fmt.Errorf("createTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}

@@ -377,6 +387,61 @@ sendjob:
return &result, nil
}

func createTrainJobVersion(createJobVersionParams models.CreateTrainJobVersionParams, jobID string) (*models.CreateTrainJobResult, error) {
checkSetting()
client := getRestyClient()
var result models.CreateTrainJobResult

retry := 0

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

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

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

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

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

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

return &result, nil
}

func GetResourceSpecs() (*models.GetResourceSpecsResult, error) {
checkSetting()
client := getRestyClient()
@@ -768,3 +833,44 @@ sendjob:

return &result, nil
}

func DelTrainJobVersion(jobID string, versionID string) (*models.TrainJobResult, error) {
checkSetting()
client := getRestyClient()
var result models.TrainJobResult

retry := 0

sendjob:
res, err := client.R().
SetAuthToken(TOKEN).
SetResult(&result).
Delete(HOST + "/v1/" + setting.ProjectID + urlTrainJob + "/" + jobID + "/versions/" + versionID)

if err != nil {
return &result, fmt.Errorf("resty DelTrainJobVersion: %v", err)
}

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

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("DelTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return &result, fmt.Errorf("删除训练作业版本失败(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}

if !result.IsSuccess {
log.Error("DelTrainJob(%s) failed", jobID)
return &result, fmt.Errorf("删除训练作业版本失败:%s", result.ErrorMsg)
}

return &result, nil
}

+ 21
- 9
modules/repository/elk_pagedata.go View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

@@ -113,13 +114,23 @@ func GetResultFromElk(resultInfo ResultInfo, jsonStr []byte) (loaded int, totalV
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
// panic(err)
return 0, 0, err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)

errs := json.Unmarshal([]byte(string(body)), &resultInfo)
log.Info("Get resultJson failed", errs)
if resp.StatusCode != 200 {
log.Error("ConnectToElk failed:%s", resp.Status)
return 0, 0, fmt.Errorf("ConnectToElk failed:%s", resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return 0, 0, err
}
err = json.Unmarshal([]byte(string(body)), &resultInfo)
if err != nil {
log.Error("Get resultJson failed", err)
return 0, 0, err
}

return resultInfo.Result.Loaded, resultInfo.Result.RawResponse.Hits.Total, err
}
@@ -138,6 +149,7 @@ func ProjectViewInit(User string, Project string, Gte string, Lte string) (proje
var timeRange Range
timeRange.Timestamptest.Gte = Gte
timeRange.Timestamptest.Lte = Lte
timeRange.Timestamptest.Format = "strict_date_optional_time"
inputStruct.Batch[0].Request.Params.Body.Query.BoolIn.Filter[0].Range = &timeRange
//限定用户
var userName FilterMatchPhrase
@@ -208,9 +220,10 @@ func TagNameInit(MessageInfo string, Tagname string, Gte string, Lte string) (pr

//向elk发送请求,将获取的结果只保留访问量,输入是初始化后的数据结构,返回访问量
func ViewInfo(viewInfo InputInfo) (totalView int, err error) {
jsons, errs := json.Marshal(viewInfo)
if errs != nil {
log.Info("errs:", errs)
jsons, err := json.Marshal(viewInfo)
if err != nil {
log.Error("jsons failed", err)
return 0, err
}
var jsonStr = []byte(jsons)
var resultInfo ResultInfo
@@ -221,7 +234,6 @@ func ViewInfo(viewInfo InputInfo) (totalView int, err error) {
if loaded == 0 {
loaded_next, totalView, err := GetResultFromElk(resultInfo, jsonStr)
time++
log.Info("time:", time)
if loaded_next != 0 && time < 100 {
return totalView, err
}


+ 24
- 13
modules/setting/setting.go View File

@@ -163,6 +163,7 @@ var (
// UI settings
UI = struct {
ExplorePagingNum int
ContributorPagingNum int
IssuePagingNum int
RepoSearchPagingNum int
MembersPagingNum int
@@ -203,19 +204,20 @@ var (
Keywords string
} `ini:"ui.meta"`
}{
ExplorePagingNum: 20,
IssuePagingNum: 10,
RepoSearchPagingNum: 10,
MembersPagingNum: 20,
FeedMaxCommitNum: 5,
GraphMaxCommitNum: 100,
CodeCommentLines: 4,
ReactionMaxUserNum: 10,
ThemeColorMetaTag: `#6cc644`,
MaxDisplayFileSize: 8388608,
DefaultTheme: `gitea`,
Themes: []string{`gitea`, `arc-green`},
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
ExplorePagingNum: 20,
ContributorPagingNum: 50,
IssuePagingNum: 10,
RepoSearchPagingNum: 10,
MembersPagingNum: 20,
FeedMaxCommitNum: 5,
GraphMaxCommitNum: 100,
CodeCommentLines: 4,
ReactionMaxUserNum: 10,
ThemeColorMetaTag: `#6cc644`,
MaxDisplayFileSize: 8388608,
DefaultTheme: `gitea`,
Themes: []string{`gitea`, `arc-green`},
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
Notification: struct {
MinTimeout time.Duration
TimeoutStep time.Duration
@@ -544,7 +546,11 @@ var (
GrowthContributors float64
GrowthCommit float64
GrowthComments float64
RecordBeginTime string
IgnoreMirrorRepo bool
}{}
Warn_Notify_Mails []string
)

// DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -1288,6 +1294,9 @@ func NewContext() {
ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time")

SetRadarMapConfig()

sec = Cfg.Section("warn_mail")
Warn_Notify_Mails = strings.Split(sec.Key("mails").MustString(""), ",")
}

func SetRadarMapConfig() {
@@ -1324,6 +1333,8 @@ func SetRadarMapConfig() {
RadarMap.GrowthContributors = sec.Key("growth_contributors").MustFloat64(0.2)
RadarMap.GrowthCommit = sec.Key("growth_commit").MustFloat64(0.2)
RadarMap.GrowthComments = sec.Key("growth_comments").MustFloat64(0.2)
RadarMap.RecordBeginTime = sec.Key("record_beigin_time").MustString("2021-11-05")
RadarMap.IgnoreMirrorRepo = sec.Key("ignore_mirror_repo").MustBool(true)

}



+ 69
- 22
modules/storage/obs.go View File

@@ -6,15 +6,17 @@ package storage

import (
"io"
"net/url"
"path"
"sort"
"strconv"
"strings"

"github.com/unknwon/com"

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

"github.com/unknwon/com"
)

type FileInfo struct {
@@ -27,20 +29,20 @@ type FileInfo struct {
}

//check if has the object
//todo:修改查询方式
func ObsHasObject(path string) (bool, error) {
hasObject := false
output, err := ObsCli.ListObjects(&obs.ListObjectsInput{Bucket: setting.Bucket})
if err != nil {
log.Error("ListObjects failed:%v", err)
return hasObject, err
}

for _, obj := range output.Contents {
//obj.Key:attachment/0/1/019fd24e-4ef7-41cc-9f85-4a7b8504d958
if path == obj.Key {
hasObject = true
break
input := &obs.GetObjectMetadataInput{}
input.Bucket = setting.Bucket
input.Key = path
_, err := ObsCli.GetObjectMetadata(input)
if err == nil {
hasObject = true
} else {
if obsError, ok := err.(obs.ObsError); ok {
log.Error("GetObjectMetadata failed(%d): %s", obsError.StatusCode, obsError.Message)
} else {
log.Error("%v", err.Error())
}
}

@@ -174,38 +176,56 @@ func ObsModelDownload(JobName string, fileName string) (io.ReadCloser, error) {
}
}

func GetObsListObject(jobName, parentDir string) ([]FileInfo, error) {
func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error) {
input := &obs.ListObjectsInput{}
input.Bucket = setting.Bucket
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/")
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, versionName, parentDir), "/")
strPrefix := strings.Split(input.Prefix, "/")
output, err := ObsCli.ListObjects(input)
fileInfos := make([]FileInfo, 0)
if err == nil {
for _, val := range output.Contents {
str1 := strings.Split(val.Key, "/")
var isDir bool
var fileName,nextParentDir string
var fileName, nextParentDir string
if strings.HasSuffix(val.Key, "/") {
//dirs in next level dir
if len(str1)-len(strPrefix) > 2 {
continue
}
fileName = str1[len(str1)-2]
isDir = true
nextParentDir = fileName
if fileName == parentDir || (fileName + "/") == setting.OutPutPath {
if parentDir == "" {
nextParentDir = fileName
} else {
nextParentDir = parentDir + "/" + fileName
}

if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath {
continue
}
} else {
//files in next level dir
if len(str1)-len(strPrefix) > 1 {
continue
}
fileName = str1[len(str1)-1]
isDir = false
nextParentDir = parentDir
}

fileInfo := FileInfo{
ModTime: val.LastModified.Format("2006-01-02 15:04:05"),
ModTime: val.LastModified.Local().Format("2006-01-02 15:04:05"),
FileName: fileName,
Size: val.Size,
IsDir:isDir,
Size: val.Size,
IsDir: isDir,
ParenDir: nextParentDir,
}
fileInfos = append(fileInfos, fileInfo)
}
sort.Slice(fileInfos, func(i, j int) bool {
return fileInfos[i].ModTime > fileInfos[j].ModTime
})
return fileInfos, err
} else {
if obsError, ok := err.(obs.ObsError); ok {
@@ -242,11 +262,12 @@ 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)
@@ -254,8 +275,34 @@ func GetObsCreateSignedUrl(jobName, parentDir, fileName string) (string, error)
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
input.Key = key

input.Expires = 60 * 60
input.Method = obs.HttpMethodGet
comma := strings.LastIndex(key, "/")
filename := key
if comma != -1 {
filename = key[comma+1:]
}
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
}

return output.SignedUrl, nil

}

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


+ 2
- 0
modules/templates/helper.go View File

@@ -92,6 +92,7 @@ func NewFuncMap() []template.FuncMap {
"Str2html": Str2html,
"TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix,
"TimeSinceUnix1": timeutil.TimeSinceUnix1,
"RawTimeSince": timeutil.RawTimeSince,
"FileSize": base.FileSize,
"PrettyNumber": base.PrettyNumber,
@@ -340,6 +341,7 @@ func NewTextFuncMap() []texttmpl.FuncMap {
},
"TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix,
"TimeSinceUnix1": timeutil.TimeSinceUnix1,
"RawTimeSince": timeutil.RawTimeSince,
"DateFmtLong": func(t time.Time) string {
return t.Format(time.RFC1123Z)


+ 5
- 0
modules/timeutil/since.go View File

@@ -162,3 +162,8 @@ func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML {
then.FormatInLocation(GetTimeFormat(lang), setting.DefaultUILocation),
timeSinceUnix(int64(then), int64(now), lang)))
}
func TimeSinceUnix1(then TimeStamp) string {
format := time.Unix(int64(then), 0).Format("2006-01-02 15:04:05")
return format
}

+ 51
- 2
options/locale/locale_en-US.ini View File

@@ -218,6 +218,7 @@ show_only_private = Showing only private
show_only_public = Showing only public

issues.in_your_repos = In your repositories
contributors = Contributors

[explore]
repos = Repositories
@@ -401,6 +402,26 @@ form.name_reserved = The username '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username.
form.name_chars_not_allowed = User name '%s' contains invalid characters.

static.sheetname=User Analysis
static.id=ID
static.name=User Name
static.codemergecount=PR Count
static.commitcount=Commit Count
static.issuecount=Issue Count
static.commentcount=Comment Count
static.focusrepocount=Focus Repo Count
static.starrepocount=Repo Star Count
static.logincount=Login Count
static.watchedcount=Watched Count
static.commitcodesize=Commit Code Line
static.solveissuecount=Solve Issue Count
static.encyclopediascount=Encyclopedias Count
static.createrepocount=Create Repo Count
static.openiindex=OpenI Index
static.registdate=Regist Date
static.countdate=Count Date
static.all=All

[settings]
profile = Profile
account = Account
@@ -755,6 +776,7 @@ unit_disabled = The site administrator has disabled this repository section.
language_other = Other
datasets = Datasets
datasets.desc = Enable Dataset
cloudbrain_helper=Use GPU/NPU resources to open notebooks, model training tasks, etc.

debug=Debug
stop=Stop
@@ -785,7 +807,15 @@ cloudbrain_operate = Operate
cloudbrain_status_createtime = Status/Createtime
cloudbrain_status_runtime = Running Time


record_begintime_get_err=Can not get the record begin time.
parameter_is_wrong=The input parameter is wrong.
total_count_get_error=Can not get the total page.
last_update_time_error=Can not get the last updated time.
get_repo_stat_error=Can not get the statistics of the repository.
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.notebook=Debug Task
modelarts.train_job=Train Task
modelarts.train_job.new_debug= New Debug Task
@@ -793,7 +823,11 @@ 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.modify=Modify
modelarts.current_version=Current version
modelarts.parent_version=Parent Version
modelarts.run_version=Run Version
modelarts.train_job.compute_node=Compute Node


modelarts.train_job.basic_info=Basic Info
@@ -814,6 +848,8 @@ modelarts.train_job.AI_driver=AI Engine
modelarts.train_job.start_file=Start File
modelarts.train_job.boot_file_helper=The startup file is the entry file that your program executes, and it must be a file ending in .py
modelarts.train_job.dataset=Dataset
modelarts.code_version = Code Version
modelarts.parents_version = Parents Version
modelarts.train_job.run_parameter=Run Parameter
modelarts.train_job.add_run_parameter=Add Run Parameter
modelarts.train_job.parameter_name=Parameter Name
@@ -2152,6 +2188,19 @@ repos.stars = Stars
repos.forks = Forks
repos.issues = Issues
repos.size = Size
repos.id=ID
repos.projectName=Project Name
repos.isPrivate=Private
repos.openi=OpenI
repos.visit=Visit
repos.download=Code Download
repos.pr=PR
repos.commit=Commit
repos.closedIssues=Closed Issue
repos.contributor=Contributor
repos.yes=Yes
repos.no=No


datasets.dataset_manage_panel= Dataset Manage
datasets.owner=Owner


+ 61
- 2
options/locale/locale_zh-CN.ini View File

@@ -220,6 +220,8 @@ show_only_public=只显示公开的

issues.in_your_repos=属于该用户项目的

contributors=贡献者

[explore]
repos=项目
users=用户
@@ -227,6 +229,7 @@ organizations=组织
images = 云脑镜像
search=搜索
code=代码
data_analysis=数字看板(内测)
repo_no_results=未找到匹配的项目。
dataset_no_results = 未找到匹配的数据集。
user_no_results=未找到匹配的用户。
@@ -402,6 +405,25 @@ form.name_reserved='%s' 用户名被保留。
form.name_pattern_not_allowed=用户名中不允许使用 "%s"。
form.name_chars_not_allowed=用户名 '%s' 包含无效字符。

static.sheetname=用户分析
static.id=ID
static.name=用户名
static.codemergecount=PR数
static.commitcount=commit次数
static.issuecount=提出任务数
static.commentcount=评论数
static.focusrepocount=关注项目数
static.starrepocount=点赞项目数
static.logincount=登录次数
static.watchedcount=关注者数
static.commitcodesize=commit代码行数
static.solveissuecount=已解决任务数
static.encyclopediascount=百科页面贡献次数
static.createrepocount=创建项目数
static.openiindex=OpenI指数
static.registdate=用户注册时间
static.countdate=系统统计时间
static.all=所有
[settings]
profile=个人信息
account=账号
@@ -757,6 +779,7 @@ unit_disabled=站点管理员已禁用此项目单元。
language_other=其它
datasets=数据集
datasets.desc=数据集功能
cloudbrain_helper=使用GPU/NPU资源,开启Notebook、模型训练任务等

debug=调试
stop=停止
@@ -787,13 +810,31 @@ cloudbrain_status_createtime=状态/创建时间
cloudbrain_status_runtime = 运行时长
cloudbrain_jobname_err=只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。

record_begintime_get_err=无法获取统计开始时间。
parameter_is_wrong=输入参数错误,请检查输入参数。
total_count_get_error=查询总页数失败。
last_update_time_error=查询最新更新时间失败。
get_repo_stat_error=查询当前仓库的统计信息失败。
get_repo_info_error=查询当前仓库信息失败。
generate_statistic_file_error=生成文件失败。
repo_stat_inspect=项目分析
all=所有

modelarts.status=状态
modelarts.createtime=创建时间
modelarts.version_nums=版本数
modelarts.computing_resources=计算资源
modelarts.notebook=调试任务
modelarts.train_job=训练任务
modelarts.train_job.new_debug=新建调试任务
modelarts.train_job.new_train=新建训练任务
modelarts.train_job.config=配置信息
modelarts.train_job.new=新建训练任务
modelarts.train_job.new_place=描述字数不超过256个字符
modelarts.train_job.new_place=描述字数不超过255个字符
modelarts.modify=修改
modelarts.current_version=当前版本
modelarts.parent_version=父版本
modelarts.run_version=运行版本



@@ -813,9 +854,14 @@ modelarts.train_job.frames=常用框架
modelarts.train_job.algorithm_origin=算法来源
modelarts.train_job.AI_driver=AI引擎
modelarts.train_job.start_file=启动文件
modelarts.train_job.boot_file_helper=启动文件是您程序执行的入口文件,必须是以.py结尾的文件。
modelarts.train_job.boot_file_helper=启动文件是您程序执行的入口文件,必须是以.py结尾的文件。比如train.py、main.py、example/train.py、case/main.py。
modelarts.train_job.boot_file_place=填写启动文件路径,默认为train.py
modelarts.train_job.dataset=数据集
modelarts.code_version=代码分支
modelarts.parents_version=基于版本
modelarts.train_job.compute_node=计算节点
modelarts.train_job.train_dataset=训练数据集

modelarts.train_job.run_parameter=运行参数
modelarts.train_job.add_run_parameter=增加运行参数
modelarts.train_job.parameter_name=参数名
@@ -2153,6 +2199,19 @@ repos.stars=点赞数
repos.forks=派生数
repos.issues=任务数
repos.size=大小
repos.id=ID
repos.projectName=项目名称
repos.isPrivate=私有
repos.openi=OpenI指数
repos.visit=浏览量
repos.download=代码下载量
repos.pr=PR数
repos.commit=Commit数
repos.closedIssues=已解决任务数
repos.contributor=贡献者数
repos.yes=是
repos.no=否


datasets.dataset_manage_panel=数据集管理
datasets.owner=所有者


+ 167
- 18485
package-lock.json
File diff suppressed because it is too large
View File


+ 5
- 1
package.json View File

@@ -19,11 +19,13 @@
"cssnano": "4.1.10",
"domino": "2.1.5",
"dropzone": "5.7.2",
"echarts": "3.8.5",
"element-ui": "2.15.5",
"esdk-obs-browserjs": "3.20.7",
"esdk-obs-nodejs": "3.20.11",
"fast-glob": "3.2.2",
"file-loader": "6.0.0",
"file-saver": "2.0.5",
"fomantic-ui": "2.8.4",
"fs": "0.0.1-security",
"highlight.js": "10.4.1",
@@ -55,13 +57,15 @@
"webpack": "4.43.0",
"webpack-cli": "3.3.11",
"webpack-fix-style-only-entries": "0.4.0",
"worker-loader": "2.0.0"
"worker-loader": "2.0.0",
"xlsx": "0.17.3"
},
"devDependencies": {
"eslint": "6.8.0",
"eslint-config-airbnb-base": "14.1.0",
"eslint-plugin-import": "2.20.2",
"eslint-plugin-vue": "6.2.2",
"script-loader": "0.7.2",
"stylelint": "13.3.3",
"stylelint-config-standard": "20.0.0",
"updates": "10.2.11"


+ 41
- 0
public/img/git-logo.svg View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 96 17.3" style="enable-background:new 0 0 96 17.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#5BB973;}
</style>
<g>
<path class="st0" d="M5.9,12.5l-1,3.8h-3L6.6,1.5H10l4.7,14.8h-3.1l-1-3.8H5.9z M6.5,10.2H10L9.6,8.5C9.4,7.8,9.1,7,8.9,6.1
C8.7,5.3,8.5,4.5,8.3,3.7H8.2C8,4.5,7.8,5.3,7.6,6.2S7.2,7.8,6.9,8.5L6.5,10.2z"/>
<path class="st0" d="M19.5,1.5v14.8h-3V1.5H19.5z"/>
<path class="st0" d="M27.7,6.6v10.6h-2.1V6.6h-1.8v-2h1.8V0.3h2.1v4.3h1.7v2H27.7z M31.6,7.4c-0.2,0.9-0.5,1.8-0.8,2.7
c-0.3,0.9-0.7,1.6-1.1,2.2c-0.1-0.1-0.2-0.2-0.4-0.3s-0.3-0.2-0.4-0.3c-0.2-0.1-0.3-0.2-0.5-0.3c-0.2-0.1-0.3-0.2-0.4-0.2
c0.4-0.5,0.7-1.1,1-1.9s0.5-1.5,0.6-2.3L31.6,7.4z M34.6,5.8c0,0.9-0.1,1.9-0.3,2.9c-0.1,1-0.4,2-0.7,3s-0.8,2-1.4,2.9
c-0.6,0.9-1.4,1.8-2.4,2.6c-0.1-0.1-0.2-0.2-0.3-0.4c-0.1-0.1-0.3-0.3-0.4-0.4c-0.1-0.1-0.3-0.3-0.4-0.4s-0.3-0.2-0.4-0.3
c0.9-0.7,1.6-1.5,2.1-2.3c0.6-0.8,1-1.7,1.3-2.5c0.3-0.9,0.5-1.7,0.7-2.6s0.2-1.7,0.3-2.5h-2.7V3.7h2.7V0.5h2v3.2h3.7
c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.3s0,0.2,0,0.3l-0.1,2.7l1.3-0.3c0.1,0.4,0.3,0.9,0.4,1.3c0.1,0.5,0.3,0.9,0.4,1.4
c0.1,0.5,0.2,0.9,0.3,1.3c0.1,0.4,0.2,0.8,0.2,1.1L39,12.8c-0.1-0.5-0.2-1.1-0.3-1.8s-0.3-1.4-0.5-2.1c0,1.2-0.1,2.1-0.1,3
c0,0.8-0.1,1.5-0.2,2.1c-0.1,0.6-0.1,1-0.2,1.4c-0.1,0.3-0.2,0.6-0.3,0.8c-0.2,0.3-0.4,0.5-0.7,0.7c-0.2,0.1-0.5,0.2-0.8,0.3
C35.6,17,35.3,17,34.8,17s-0.9,0-1.3,0c0-0.3-0.1-0.6-0.2-1c-0.1-0.4-0.3-0.7-0.4-1c0.4,0,0.9,0.1,1.2,0.1c0.4,0,0.7,0,0.8,0
c0.2,0,0.3,0,0.4-0.1c0.1,0,0.2-0.1,0.3-0.2c0.1-0.1,0.2-0.4,0.3-0.8c0.1-0.4,0.1-0.9,0.2-1.6s0.1-1.6,0.1-2.7
c0-1.1,0.1-2.4,0.1-3.9H34.6z"/>
<path class="st0" d="M47.8,1.1C47.6,1.7,47.3,2.4,47,3c-0.3,0.6-0.6,1.2-0.9,1.9v12.4h-2.2v-9c-0.2,0.2-0.4,0.5-0.6,0.7
S43,9.3,42.8,9.5c0-0.1-0.1-0.3-0.2-0.5c-0.1-0.2-0.2-0.4-0.3-0.6c-0.1-0.2-0.2-0.4-0.3-0.6s-0.2-0.4-0.3-0.5
c0.4-0.4,0.8-0.9,1.2-1.4c0.4-0.5,0.8-1.1,1.1-1.6c0.4-0.6,0.7-1.2,1-1.8c0.3-0.6,0.6-1.3,0.8-1.9L47.8,1.1z M53.5,13.2v4h-2.2V5.3
h-0.7c-0.4,0.7-0.7,1.3-1.1,1.9s-0.8,1.1-1.2,1.6c-0.1-0.1-0.2-0.2-0.3-0.4c-0.1-0.1-0.3-0.3-0.4-0.4S47.2,7.8,47,7.7
c-0.2-0.1-0.3-0.2-0.4-0.3C47,6.9,47.4,6.5,47.8,6c0.4-0.5,0.7-1.1,1-1.7s0.6-1.2,0.9-1.8c0.3-0.6,0.5-1.3,0.7-1.9l2.1,0.5
c-0.1,0.4-0.3,0.8-0.4,1.1s-0.3,0.7-0.5,1.1h7v2h-5.1v1.9h4.7v1.9h-4.7v2h4.9v2H53.5z"/>
<path class="st0" d="M69.3,11.3v6h-2.2v-6h-7V9.1h7V3.6H61V1.5h14.4v2.1h-6v5.5h7.1v2.2H69.3z M64.1,4.2c0.1,0.3,0.3,0.6,0.4,0.9
s0.3,0.6,0.4,1c0.1,0.3,0.2,0.6,0.4,0.9c0.1,0.3,0.2,0.6,0.2,0.8l-2.1,0.6c0-0.2-0.1-0.5-0.2-0.8s-0.2-0.6-0.3-1
c-0.1-0.3-0.2-0.7-0.4-1c-0.1-0.3-0.3-0.7-0.4-1L64.1,4.2z M74.6,4.7c-0.3,0.7-0.7,1.4-1,2.1c-0.3,0.7-0.7,1.2-1,1.7l-1.9-0.5
c0.1-0.3,0.3-0.6,0.4-0.9s0.3-0.6,0.4-1c0.1-0.3,0.3-0.7,0.4-1c0.1-0.3,0.2-0.6,0.3-0.9L74.6,4.7z"/>
<path class="st0" d="M89.4,2c0.4,0.4,0.9,0.8,1.3,1.3c0.5,0.5,0.9,0.9,1.3,1.4s0.8,0.9,1.2,1.4c0.4,0.5,0.7,0.9,0.9,1.3l-1.8,1.3
c-0.1-0.2-0.3-0.4-0.4-0.6c-0.2-0.2-0.3-0.5-0.5-0.7c-1.6,0.1-3,0.1-4.1,0.2c-1.2,0.1-2.1,0.1-3,0.2c-0.8,0-1.5,0.1-2,0.1
s-1,0.1-1.3,0.1s-0.6,0.1-0.8,0.1c-0.2,0-0.4,0.1-0.5,0.1c0-0.1-0.1-0.2-0.1-0.4c-0.1-0.2-0.1-0.4-0.2-0.6
c-0.1-0.2-0.1-0.4-0.2-0.6C79,6.3,78.9,6.2,78.9,6c0.3-0.1,0.6-0.2,0.9-0.5c0.3-0.3,0.7-0.6,1-1c0.4-0.4,0.7-0.8,1.1-1.2
c0.4-0.4,0.7-0.9,1-1.3c0.3-0.4,0.6-0.8,0.8-1.2c0.2-0.4,0.4-0.6,0.5-0.8L86.4,1c-0.6,0.8-1.2,1.7-1.9,2.4s-1.4,1.5-2,2.2l7.4-0.2
c-0.3-0.4-0.7-0.8-1.1-1.2c-0.4-0.4-0.7-0.7-1-1.1L89.4,2z M82.4,16.3v0.9h-2.2V9.3h12.2v7.9H90v-0.9H82.4z M82.4,14.2H90v-2.9
h-7.6V14.2z"/>
</g>
</svg>

BIN
public/img/name.png View File

Before After
Width: 300  |  Height: 300  |  Size: 5.1 kB

BIN
public/img/overview.png View File

Before After
Width: 64  |  Height: 64  |  Size: 1.5 kB

BIN
public/img/pro.png View File

Before After
Width: 64  |  Height: 64  |  Size: 2.0 kB

+ 1
- 0
public/img/pro.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="1636355832057" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8359" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M1024 767.6928c0 7.168-2.8672 12.0832-8.4992 14.9504L571.2896 1021.8496 569.1392 1021.8496C567.7056 1023.2832 565.5552 1024 562.688 1024c-2.8672 0-5.0176-0.7168-6.4512-2.1504L554.1888 1021.8496 109.9776 782.6432C104.2432 779.776 101.376 774.8608 101.376 767.6928L101.376 255.1808 101.376 253.0304c0-1.3312 0.7168-2.8672 2.1504-4.3008L103.5264 246.5792l4.3008-4.3008 2.1504-2.1504L554.1888 1.024c5.7344-1.3312 11.3664-1.3312 17.1008 0l444.2112 239.2064 2.1504 2.1504 4.3008 4.3008 0 2.1504L1024 253.0304l0 2.1504L1024 767.6928zM135.5776 757.0432l410.0096 222.1056 0-474.112L135.5776 282.9312 135.5776 757.0432zM154.8288 255.1808 562.688 475.136l407.8592-219.9552L562.688 35.2256 154.8288 255.1808zM989.7984 757.0432l0-474.112L579.7888 505.0368l0 474.112L989.7984 757.0432z" p-id="8360"></path></svg>

+ 1
- 0
public/img/pro_new.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="1637739132178" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1729" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><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="#8a8a8a" p-id="1730"></path></svg>

+ 21
- 1
routers/api/v1/api.go View File

@@ -523,6 +523,23 @@ func RegisterRoutes(m *macaron.Macaron) {
Get(notify.GetThread).
Patch(notify.ReadThread)
}, reqToken())
adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: 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.Group("/project", func() {
m.Get("", adminReq, repo.GetAllProjectsPeriodStatistics)

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

})
})
})

// Users
m.Group("/users", func() {
@@ -858,8 +875,11 @@ func RegisterRoutes(m *macaron.Macaron) {
})
m.Group("/train-job", func() {
m.Group("/:jobid", func() {
m.Get("", repo.GetModelArtsTrainJob)
m.Get("", repo.GetModelArtsTrainJobVersion)
m.Get("/log", repo.TrainJobGetLog)
m.Post("/del_version", repo.DelTrainJobVersion)
m.Post("/stop_version", repo.StopTrainJobVersion)
m.Get("/model_list", repo.ModelList)
})
})
}, reqRepoReader(models.UnitTypeCloudBrain))


+ 217
- 23
routers/api/v1/repo/modelarts.go View File

@@ -6,12 +6,15 @@
package repo

import (
"net/http"
"strconv"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/modelarts"
"net/http"
"strconv"
"code.gitea.io/gitea/modules/storage"
)

func GetModelArtsNotebook(ctx *context.APIContext) {
@@ -72,56 +75,247 @@ func GetModelArtsTrainJob(ctx *context.APIContext) {
}

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

}

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

log.Info("test")
jobID := ctx.Params(":jobid")
versionName := ctx.Query("version_name")
job, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName)
if err != nil {
ctx.NotFound(err)
return
}
result, err := modelarts.GetTrainJob(jobID, strconv.FormatInt(job.VersionID, 10))
if err != nil {
ctx.NotFound(err)
return
}

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

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

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

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

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

}

func addZero(t int64) (m string) {
if t < 10 {
m = "0" + strconv.FormatInt(t, 10)
return m
} else {
return strconv.FormatInt(t, 10)
}
}

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

var jobID = ctx.Params(":jobid")
var logFileName = ctx.Query("file_name")
var versionName = ctx.Query("version_name")
// var logFileName = ctx.Query("file_name")
var baseLine = ctx.Query("base_line")
var order = ctx.Query("order")
var lines = ctx.Query("lines")
lines_int, err := strconv.Atoi(lines)
if err != nil {
log.Error("change lines(%d) string to int failed", lines_int)
}

if order != modelarts.OrderDesc && order != modelarts.OrderAsc {
log.Error("order(%s) check failed", order)
ctx.JSON(http.StatusBadRequest, map[string]interface{}{
"err_msg": "order check failed",
"err_msg": "order check failed",
})
return
}

task, err := models.GetCloudbrainByJobID(jobID)
resultLogFile, result, err := trainJobGetLogContent(jobID, versionName, baseLine, order, lines_int)
if err != nil {
log.Error("GetCloudbrainByJobID(%s) failed:%v", jobID, err.Error())
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err_msg": "GetCloudbrainByJobID failed",
})
log.Error("trainJobGetLog(%s) failed:%v", jobID, err.Error())
// ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobShow, nil)
return
}

result, err := modelarts.GetTrainJobLog(jobID, strconv.FormatInt(task.VersionID, 10), baseLine, logFileName, order, modelarts.Lines)
ctx.Data["log_file_name"] = resultLogFile.LogFileList[0]

ctx.JSON(http.StatusOK, map[string]interface{}{
"JobID": jobID,
"LogFileName": resultLogFile.LogFileList[0],
"StartLine": result.StartLine,
"EndLine": result.EndLine,
"Content": result.Content,
"Lines": result.Lines,
})
}

func trainJobGetLogContent(jobID string, versionName string, baseLine string, order string, lines int) (*models.GetTrainJobLogFileNamesResult, *models.GetTrainJobLogResult, error) {
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName)
if err != nil {
log.Error("GetCloudbrainByJobID(%s) failed:%v", jobID, err.Error())
return nil, nil, err
}

resultLogFile, err := modelarts.GetTrainJobLogFileNames(jobID, strconv.FormatInt(task.VersionID, 10))
if err != nil {
log.Error("GetTrainJobLogFileNames(%s) failed:%v", jobID, err.Error())
return nil, nil, err
}

result, err := modelarts.GetTrainJobLog(jobID, strconv.FormatInt(task.VersionID, 10), baseLine, resultLogFile.LogFileList[0], order, lines)
if err != nil {
log.Error("GetTrainJobLog(%s) failed:%v", jobID, err.Error())
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err_msg": "GetTrainJobLog failed",
})
return nil, nil, err
}

return resultLogFile, result, err
}

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

var jobID = ctx.Params(":jobid")
var versionName = ctx.Query("version_name")
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName)
if err != nil {
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error())
ctx.NotFound(err)
return
}

//删除modelarts上的记录
_, err = modelarts.DelTrainJobVersion(jobID, strconv.FormatInt(task.VersionID, 10))
if err != nil {
log.Error("DelTrainJobVersion(%s) failed:%v", task.JobName, err.Error())
ctx.NotFound(err)
return
}

//删除数据库记录
err = models.DeleteJob(task)
if err != nil {
ctx.ServerError("DeleteJob failed", err)
ctx.NotFound(err)
return
}

//获取删除后的版本数量
repo := ctx.Repo.Repository
VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{
RepoID: repo.ID,
Type: models.TypeCloudBrainTwo,
JobType: string(models.JobTypeTrain),
JobID: jobID,
})
if err != nil {
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
}
}

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

func StopTrainJobVersion(ctx *context.APIContext) {
var (
err error
)
var jobID = ctx.Params(":jobid")
var versionName = ctx.Query("version_name")
task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName)
if err != nil {
log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error())
return
}

_, err = modelarts.StopTrainJob(jobID, strconv.FormatInt(task.VersionID, 10))
if err != nil {
log.Error("StopTrainJob(%s) failed:%v", task.JobName, err.Error())
return
}

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

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

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

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

+ 641
- 0
routers/api/v1/repo/repo_dashbord.go View File

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

import (
"fmt"
"net/http"
"net/url"
"strconv"
"time"

"github.com/360EntSecGroup-Skylar/excelize/v2"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"

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

const DEFAULT_PAGE_SIZE = 10
const DATE_FORMAT = "2006-01-02"
const EXCEL_DATE_FORMAT = "20060102"

type ProjectsPeriodData struct {
RecordBeginTime string `json:"recordBeginTime"`
LastUpdatedTime string `json:"lastUpdatedTime"`
PageSize int `json:"pageSize"`
TotalPage int `json:"totalPage"`
TotalCount int64 `json:"totalCount"`
PageRecords []*models.RepoStatistic `json:"pageRecords"`
}

type UserInfo struct {
User string `json:"user"`
Mode int `json:"mode"`
PR int64 `json:"pr"`
Commit int `json:"commit"`
RelAvatarLink string `json:"relAvatarLink"`
Email string `json:"email"`
}

type ProjectLatestData struct {
RecordBeginTime string `json:"recordBeginTime"`
LastUpdatedTime string `json:"lastUpdatedTime"`
CreatTime string `json:"creatTime"`
OpenI float64 `json:"openi"`
Comment int64 `json:"comment"`
View int64 `json:"view"`
Download int64 `json:"download"`
IssueClosedRatio float32 `json:"issueClosedRatio"`
Impact float64 `json:"impact"`
Completeness float64 `json:"completeness"`
Liveness float64 `json:"liveness"`
ProjectHealth float64 `json:"projectHealth"`
TeamHealth float64 `json:"teamHealth"`
Growth float64 `json:"growth"`
Description string `json:"description"`
Top10 []UserInfo `json:"top10"`
}

func RestoreForkNumber(ctx *context.Context) {
repos, err := models.GetAllRepositories()
if err != nil {
log.Error("GetAllRepositories failed: %v", err.Error())
return
}
for _, repo := range repos {
models.RestoreRepoStatFork(int64(repo.NumForks), repo.ID)
}

ctx.JSON(http.StatusOK, struct{}{})
}

func GetAllProjectsPeriodStatistics(ctx *context.Context) {

recordBeginTime, err := getRecordBeginTime()
if err != nil {
log.Error("Can not get record begin time", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
return
}
beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime)
if err != nil {
log.Error("Parameter is wrong", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.parameter_is_wrong"))
return
}
q := ctx.QueryTrim("q")
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
pageSize := ctx.QueryInt("pagesize")
if pageSize <= 0 {
pageSize = DEFAULT_PAGE_SIZE
}
orderBy := getOrderBy(ctx)

latestUpdatedTime, latestDate, err := models.GetRepoStatLastUpdatedTime()
if err != nil {
log.Error("Can not query the last updated time.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error"))
return
}

countSql := generateCountSql(beginTime, endTime, latestDate, q)
total, err := models.CountRepoStatByRawSql(countSql)
if err != nil {
log.Error("Can not query total count.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error"))
return
}
sql := generateSqlByType(ctx, beginTime, endTime, latestDate, q, orderBy, page, pageSize)

projectsPeriodData := ProjectsPeriodData{
RecordBeginTime: recordBeginTime.Format(DATE_FORMAT),
PageSize: pageSize,
TotalPage: getTotalPage(total, pageSize),
TotalCount: total,
LastUpdatedTime: latestUpdatedTime,
PageRecords: models.GetRepoStatisticByRawSql(sql),
}

ctx.JSON(http.StatusOK, projectsPeriodData)

}

func generateSqlByType(ctx *context.Context, beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string {
sql := ""
if ctx.QueryTrim("type") == "all" {
sql = generateTypeAllSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)
} else {
sql = generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)
}
return sql
}

func ServeAllProjectsPeriodStatisticsFile(ctx *context.Context) {

recordBeginTime, err := getRecordBeginTime()
if err != nil {
log.Error("Can not get record begin time", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
return
}
beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime)
if err != nil {
log.Error("Parameter is wrong", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.parameter_is_wrong"))
return
}
q := ctx.QueryTrim("q")
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
pageSize := 1000
orderBy := getOrderBy(ctx)

_, latestDate, err := models.GetRepoStatLastUpdatedTime()
if err != nil {
log.Error("Can not query the last updated time.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error"))
return
}

countSql := generateCountSql(beginTime, endTime, latestDate, q)
total, err := models.CountRepoStatByRawSql(countSql)
if err != nil {
log.Error("Can not query total count.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error"))
return
}
var projectAnalysis = ctx.Tr("repo.repo_stat_inspect")
fileName := getFileName(ctx, beginTime, endTime, projectAnalysis)

totalPage := getTotalPage(total, pageSize)

f := excelize.NewFile()

index := f.NewSheet(projectAnalysis)
f.DeleteSheet("Sheet1")

for k, v := range allProjectsPeroidHeader(ctx) {
f.SetCellValue(projectAnalysis, k, v)
}

var row = 2
for i := 0; i <= totalPage; i++ {

pageRecords := models.GetRepoStatisticByRawSql(generateSqlByType(ctx, beginTime, endTime, latestDate, q, orderBy, i+1, pageSize))
for _, record := range pageRecords {

for k, v := range allProjectsPeroidValues(row, record, ctx) {
f.SetCellValue(projectAnalysis, k, v)
}
row++

}

}
f.SetActiveSheet(index)

ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(fileName))
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")

f.WriteTo(ctx.Resp)

}

func ServeAllProjectsOpenIStatisticsFile(ctx *context.Context) {

page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
pageSize := 1000

_, latestDate, err := models.GetRepoStatLastUpdatedTime()

if err != nil {
log.Error("Can not query the last updated time.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error"))
return
}

date := ctx.QueryTrim("date")
if date == "" {
date = latestDate
}

countSql := generateOpenICountSql(date)
total, err := models.CountRepoStatByRawSql(countSql)
if err != nil {
log.Error("Can not query total count.", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error"))
return
}
var projectAnalysis = ctx.Tr("repo.repo_stat_inspect")
fileName := "项目分析_OPENI_" + date + ".xlsx"

totalPage := getTotalPage(total, pageSize)

f := excelize.NewFile()

index := f.NewSheet(projectAnalysis)
f.DeleteSheet("Sheet1")

for k, v := range allProjectsOpenIHeader() {
f.SetCellValue(projectAnalysis, k, v)
}

var row = 2
for i := 0; i <= totalPage; i++ {

pageRecords := models.GetRepoStatisticByRawSql(generateTypeAllOpenISql(date, i+1, pageSize))
for _, record := range pageRecords {

for k, v := range allProjectsOpenIValues(row, record, ctx) {
f.SetCellValue(projectAnalysis, k, v)
}
row++

}

}
f.SetActiveSheet(index)

ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(fileName))
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")

f.WriteTo(ctx.Resp)

}

func getFileName(ctx *context.Context, beginTime time.Time, endTime time.Time, projectAnalysis string) string {
baseName := projectAnalysis + "_"

if ctx.QueryTrim("q") != "" {
baseName = baseName + ctx.QueryTrim("q") + "_"
}
if ctx.QueryTrim("type") == "all" {
baseName = baseName + ctx.Tr("repo.all")
} else {
baseName = baseName + beginTime.AddDate(0, 0, -1).Format(EXCEL_DATE_FORMAT) + "_" + endTime.AddDate(0, 0, -1).Format(EXCEL_DATE_FORMAT)
}
frontName := baseName + ".xlsx"
return frontName
}

func allProjectsPeroidHeader(ctx *context.Context) map[string]string {

return map[string]string{"A1": ctx.Tr("admin.repos.id"), "B1": ctx.Tr("admin.repos.projectName"), "C1": ctx.Tr("repo.owner"), "D1": ctx.Tr("admin.repos.isPrivate"), "E1": ctx.Tr("admin.repos.openi"), "F1": ctx.Tr("admin.repos.visit"), "G1": ctx.Tr("admin.repos.download"), "H1": ctx.Tr("admin.repos.pr"), "I1": ctx.Tr("admin.repos.commit"),
"J1": ctx.Tr("admin.repos.watches"), "K1": ctx.Tr("admin.repos.stars"), "L1": ctx.Tr("admin.repos.forks"), "M1": ctx.Tr("admin.repos.issues"), "N1": ctx.Tr("admin.repos.closedIssues"), "O1": ctx.Tr("admin.repos.contributor")}

}

func allProjectsPeroidValues(row int, rs *models.RepoStatistic, ctx *context.Context) map[string]string {
return map[string]string{getCellName("A", row): strconv.FormatInt(rs.RepoID, 10), getCellName("B", row): rs.Name, getCellName("C", row): rs.OwnerName, getCellName("D", row): getIsPrivateDisplay(rs.IsPrivate, ctx), getCellName("E", row): strconv.FormatFloat(rs.RadarTotal, 'f', 2, 64),
getCellName("F", row): strconv.FormatInt(rs.NumVisits, 10), getCellName("G", row): strconv.FormatInt(rs.NumDownloads, 10), getCellName("H", row): strconv.FormatInt(rs.NumPulls, 10), getCellName("I", row): strconv.FormatInt(rs.NumCommits, 10),
getCellName("J", row): strconv.FormatInt(rs.NumWatches, 10), getCellName("K", row): strconv.FormatInt(rs.NumStars, 10), getCellName("L", row): strconv.FormatInt(rs.NumForks, 10), getCellName("M", row): strconv.FormatInt(rs.NumIssues, 10),
getCellName("N", row): strconv.FormatInt(rs.NumClosedIssues, 10), getCellName("O", row): strconv.FormatInt(rs.NumContributor, 10),
}
}

func allProjectsOpenIHeader() map[string]string {

return map[string]string{"A1": "ID", "B1": "项目名称", "C1": "拥有者", "D1": "是否私有", "E1": "OpenI指数",
"F1": "影响力", "G1": "成熟度", "H1": "活跃度", "I1": "项目健康度", "J1": "团队健康度", "K1": "项目发展趋势",
"L1": "关注数", "M1": "点赞数", "N1": "派生数", "O1": "代码下载量", "P1": "评论数", "Q1": "浏览量", "R1": "已解决任务数", "S1": "版本发布数量", "T1": "有效开发年龄",
"U1": "数据集", "V1": "模型数", "W1": "百科页面数量", "X1": "提交数", "Y1": "任务数", "Z1": "PR数", "AA1": "版本发布数量", "AB1": "任务完成比例", "AC1": "贡献者数", "AD1": "关键贡献者数",
"AE1": "新人增长量", "AF1": "代码规模增长量", "AG1": "任务增长量", "AH1": "新人增长量", "AI1": "提交增长量", "AJ1": "评论增长量",
}

}

func allProjectsOpenIValues(row int, rs *models.RepoStatistic, ctx *context.Context) map[string]string {

return map[string]string{getCellName("A", row): strconv.FormatInt(rs.RepoID, 10), getCellName("B", row): rs.Name, getCellName("C", row): rs.OwnerName, getCellName("D", row): getIsPrivateDisplay(rs.IsPrivate, ctx), getCellName("E", row): strconv.FormatFloat(rs.RadarTotal, 'f', 2, 64),
getCellName("F", row): strconv.FormatFloat(rs.Impact, 'f', 2, 64), getCellName("G", row): strconv.FormatFloat(rs.Completeness, 'f', 2, 64), getCellName("H", row): strconv.FormatFloat(rs.Liveness, 'f', 2, 64), getCellName("I", row): strconv.FormatFloat(rs.ProjectHealth, 'f', 2, 64), getCellName("J", row): strconv.FormatFloat(rs.TeamHealth, 'f', 2, 64), getCellName("K", row): strconv.FormatFloat(rs.Growth, 'f', 2, 64),
getCellName("L", row): strconv.FormatInt(rs.NumWatches, 10), getCellName("M", row): strconv.FormatInt(rs.NumStars, 10), getCellName("N", row): strconv.FormatInt(rs.NumForks, 10), getCellName("O", row): strconv.FormatInt(rs.NumDownloads, 10),

getCellName("P", row): strconv.FormatInt(rs.NumComments, 10), getCellName("Q", row): strconv.FormatInt(rs.NumVisits, 10), getCellName("R", row): strconv.FormatInt(rs.NumClosedIssues, 10), getCellName("S", row): strconv.FormatInt(rs.NumVersions, 10),
getCellName("T", row): strconv.FormatInt(rs.NumDevMonths, 10), getCellName("U", row): strconv.FormatInt(rs.DatasetSize, 10), getCellName("V", row): strconv.FormatInt(rs.NumModels, 10), getCellName("W", row): strconv.FormatInt(rs.NumWikiViews, 10),
getCellName("X", row): strconv.FormatInt(rs.NumCommits, 10), getCellName("Y", row): strconv.FormatInt(rs.NumIssues, 10), getCellName("Z", row): strconv.FormatInt(rs.NumPulls, 10), getCellName("AA", row): strconv.FormatInt(rs.NumVersions, 10),
getCellName("AB", row): strconv.FormatFloat(float64(rs.IssueFixedRate), 'f', 2, 64), getCellName("AC", row): strconv.FormatInt(rs.NumContributor, 10), getCellName("AD", row): strconv.FormatInt(rs.NumKeyContributor, 10), getCellName("AE", row): strconv.FormatInt(rs.NumContributorsGrowth, 10),
getCellName("AF", row): strconv.FormatInt(rs.NumCommitLinesGrowth, 10), getCellName("AG", row): strconv.FormatInt(rs.NumIssuesGrowth, 10), getCellName("AH", row): strconv.FormatInt(rs.NumContributorsGrowth, 10), getCellName("AI", row): strconv.FormatInt(rs.NumCommitsGrowth, 10), getCellName("AJ", row): strconv.FormatInt(rs.NumCommentsGrowth, 10),
}

}

func getCellName(col string, row int) string {
return col + strconv.Itoa(row)
}

func getIsPrivateDisplay(private bool, ctx *context.Context) string {
if private {
return ctx.Tr("admin.repos.yes")
} else {
return ctx.Tr("admin.repos.no")
}
}

func GetProjectLatestStatistics(ctx *context.Context) {
repoId := ctx.Params(":id")
recordBeginTime, err := getRecordBeginTime()
if err != nil {
log.Error("Can not get record begin time", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
return
}
latestUpdatedTime, latestDate, err := models.GetRepoStatLastUpdatedTime(repoId)
repoIdInt, _ := strconv.ParseInt(repoId, 10, 64)
repoStat, err := models.GetRepoStatisticByDateAndRepoId(latestDate, repoIdInt)
if err != nil {
log.Error("Can not get the repo statistics "+repoId, err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.get_repo_stat_error"))
return
}

repository, err := models.GetRepositoryByID(repoIdInt)
if err != nil {
log.Error("Can not get the repo info "+repoId, err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.get_repo_info_error"))
return
}
projectLatestData := ProjectLatestData{
RecordBeginTime: recordBeginTime.Format(DATE_FORMAT),
CreatTime: time.Unix(int64(repository.CreatedUnix), 0).Format(DATE_FORMAT),
LastUpdatedTime: latestUpdatedTime,
OpenI: repoStat.RadarTotal,
Comment: repoStat.NumComments,
View: repoStat.NumVisits,
Download: repoStat.NumDownloads,
IssueClosedRatio: repoStat.IssueFixedRate,
Impact: repoStat.Impact,
Completeness: repoStat.Completeness,
Liveness: repoStat.Liveness,
ProjectHealth: repoStat.ProjectHealth,
TeamHealth: repoStat.TeamHealth,
Growth: repoStat.Growth,
Description: repository.Description,
}

contributors, err := models.GetTop10Contributor(repository.RepoPath())
if err != nil {
log.Error("can not get contributors", err)
}
users := make([]UserInfo, 0)

for _, contributor := range contributors {
mode := repository.GetCollaboratorMode(contributor.UserId)
if mode == -1 {
if contributor.IsAdmin {
mode = int(models.AccessModeAdmin)
}
if contributor.UserId == repository.OwnerID {
mode = int(models.AccessModeOwner)
}
}

pr := models.GetPullCountByUserAndRepoId(repoIdInt, contributor.UserId)
userInfo := UserInfo{
User: contributor.Committer,
Commit: contributor.CommitCnt,
Mode: mode,
PR: pr,
RelAvatarLink: contributor.RelAvatarLink,
Email: contributor.Email,
}
users = append(users, userInfo)

}

projectLatestData.Top10 = users

ctx.JSON(http.StatusOK, projectLatestData)

}

func GetProjectPeriodStatistics(ctx *context.Context) {
repoId := ctx.Params(":id")
recordBeginTime, err := getRecordBeginTime()
if err != nil {
log.Error("Can not get record begin time", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
return
}

repoIdInt, _ := strconv.ParseInt(repoId, 10, 64)

if err != nil {
log.Error("Can not get record begin time", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
return
}
beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime)
isOpenI := ctx.QueryBool("openi")
var repositorys []*models.RepoStatistic
if isOpenI {
repositorys = models.GetRepoStatisticByRawSql(generateRadarSql(beginTime, endTime, repoIdInt))
} else {
repositorys = models.GetRepoStatisticByRawSql(generateTargetSql(beginTime, endTime, repoIdInt))
}
ctx.JSON(http.StatusOK, repositorys)
}

func generateRadarSql(beginTime time.Time, endTime time.Time, repoId int64) string {
sql := "SELECT date, impact, completeness, liveness, project_health, team_health, growth, radar_total FROM repo_statistic" +
" where repo_id=" + strconv.FormatInt(repoId, 10) + " and created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " order by created_unix"

return sql
}

func generateTargetSql(beginTime time.Time, endTime time.Time, repoId int64) string {
sql := "SELECT date, num_visits,num_downloads_added as num_downloads,num_commits_added as num_commits FROM repo_statistic" +
" where repo_id=" + strconv.FormatInt(repoId, 10) + " and created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " order by created_unix desc"

return sql
}

func generateCountSql(beginTime time.Time, endTime time.Time, latestDate string, q string) string {
countSql := "SELECT count(*) FROM " +
"(SELECT repo_id FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
"(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 + "%'"
}
return countSql
}

func generateOpenICountSql(latestDate string) string {
countSql := "SELECT count(*) FROM " +
"public.repo_statistic where date='" + latestDate + "'"

return countSql
}

func generateTypeAllSql(beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string {
sql := "SELECT A.repo_id,name,owner_name,is_private,radar_total,num_watches,num_visits,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor FROM " +
"(SELECT repo_id,sum(num_visits) as num_visits " +
" FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
"(SELECT repo_id,name,owner_name,is_private,radar_total,num_watches,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor from public.repo_statistic where date='" + latestDate + "') B" +
" where A.repo_id=B.repo_id"

if q != "" {
sql = sql + " and name like '%" + q + "%'"
}
sql = sql + " order by " + orderBy + " desc,repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
return sql
}

func generateTypeAllOpenISql(latestDate string, page int, pageSize int) string {
sql := "SELECT id, repo_id, date, num_watches, num_stars, num_forks, num_downloads, num_comments, num_visits, num_closed_issues, num_versions, num_dev_months, repo_size, dataset_size, num_models, num_wiki_views, num_commits, num_issues, num_pulls, issue_fixed_rate, num_contributor, num_key_contributor, num_contributors_growth, num_commits_growth, num_commit_lines_growth, num_issues_growth, num_comments_growth, impact, completeness, liveness, project_health, team_health, growth, radar_total, name, is_private, owner_name FROM " +
" public.repo_statistic where date='" + latestDate + "'"

sql = sql + " order by radar_total desc,repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
return sql
}

func generatePageSql(beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string {

sql := "SELECT A.repo_id,name,owner_name,is_private,radar_total,num_watches,num_visits,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor FROM " +
"(SELECT repo_id,sum(num_watches_added) as num_watches,sum(num_visits) as num_visits, sum(num_downloads_added) as num_downloads,sum(num_pulls_added) as num_pulls,sum(num_commits_added) as num_commits,sum(num_stars_added) as num_stars,sum(num_forks_added) num_forks,sum(num_issues_added) as num_issues,sum(num_closed_issues_added) as num_closed_issues,sum(num_contributor_added) as num_contributor " +
" FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
"(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 + " order by " + orderBy + " desc,A.repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
return sql
}

func getOrderBy(ctx *context.Context) string {
orderBy := ""
switch ctx.Query("sort") {
case "openi":
orderBy = "B.radar_total"
case "view":
orderBy = "A.num_visits"
case "download":
orderBy = "A.num_downloads"
case "pr":
orderBy = "A.num_pulls"
case "commit":
orderBy = "A.num_commits"
case "watch":
orderBy = "A.num_watches"
case "star":
orderBy = "A.num_stars"
case "fork":
orderBy = "A.num_forks"
case "issue":
orderBy = "A.num_issues"
case "issue_closed":
orderBy = "A.num_closed_issues"
case "contributor":
orderBy = "A.num_contributor"
default:
orderBy = "B.radar_total"
}
return orderBy
}

func getTimePeroid(ctx *context.Context, recordBeginTime time.Time) (time.Time, time.Time, error) {
queryType := ctx.QueryTrim("type")
now := time.Now()
recordBeginTimeTemp := recordBeginTime.AddDate(0, 0, 1)

beginTimeStr := ctx.QueryTrim("beginTime")
endTimeStr := ctx.QueryTrim("endTime")
var beginTime time.Time
var endTime time.Time
var err error
if queryType != "" {

if queryType == "all" {
beginTime = recordBeginTimeTemp
endTime = now
} else if queryType == "yesterday" {
endTime = now
beginTime = time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 0, 0, 0, 0, now.Location())

} else if queryType == "current_week" {
beginTime = now.AddDate(0, 0, -int(time.Now().Weekday())+2) //begin from monday
beginTime = time.Date(beginTime.Year(), beginTime.Month(), beginTime.Day(), 0, 0, 0, 0, now.Location())
endTime = now
} else if queryType == "current_month" {
endTime = now
beginTime = time.Date(endTime.Year(), endTime.Month(), 2, 0, 0, 0, 0, now.Location())
} else if queryType == "monthly" {
endTime = now
beginTime = now.AddDate(0, -1, 1)
beginTime = time.Date(beginTime.Year(), beginTime.Month(), beginTime.Day(), 0, 0, 0, 0, now.Location())

} else if queryType == "current_year" {
endTime = now
beginTime = time.Date(endTime.Year(), 1, 2, 0, 0, 0, 0, now.Location())

} else if queryType == "last_month" {

lastMonthTime := now.AddDate(0, -1, 0)
beginTime = time.Date(lastMonthTime.Year(), lastMonthTime.Month(), 2, 0, 0, 0, 0, now.Location())
endTime = time.Date(now.Year(), now.Month(), 2, 0, 0, 0, 0, now.Location())

} else {
return now, now, fmt.Errorf("The value of type parameter is wrong.")

}

} else {
if beginTimeStr == "" || endTimeStr == "" {
//如果查询类型和开始时间结束时间都未设置,按queryType=all处理
beginTime = recordBeginTimeTemp
endTime = now

} else {

beginTime, err = time.ParseInLocation("2006-01-02", beginTimeStr, time.Local)
if err != nil {
return now, now, err
}

endTime, err = time.ParseInLocation("2006-01-02", endTimeStr, time.Local)
if err != nil {
return now, now, err
}

beginTime = beginTime.AddDate(0, 0, 1)
endTime = endTime.AddDate(0, 0, 1)
}

}

if beginTime.Before(recordBeginTimeTemp) {
beginTime = recordBeginTimeTemp
}

return beginTime, endTime, nil

}

func getRecordBeginTime() (time.Time, error) {
return time.ParseInLocation(DATE_FORMAT, setting.RadarMap.RecordBeginTime, time.Local)
}

func getTotalPage(total int64, pageSize int) int {

another := 0
if int(total)%pageSize != 0 {
another = 1
}
return int(total)/pageSize + another

}

+ 8
- 3
routers/home.go View File

@@ -33,8 +33,9 @@ const (
// tplExploreOrganizations explore organizations page template
tplExploreOrganizations base.TplName = "explore/organizations"
// tplExploreCode explore code page template
tplExploreCode base.TplName = "explore/code"
tplExploreImages base.TplName = "explore/images"
tplExploreCode base.TplName = "explore/code"
tplExploreImages base.TplName = "explore/images"
tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis"
)

// Home render home page
@@ -146,7 +147,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
ctx.Data["SortType"] = "hot"
orderBy = models.SearchOrderByHot
}
orderBy = orderBy + ",id"
//todo:support other topics
keyword := strings.Trim(ctx.Query("q"), " ")
topic := strings.Trim(ctx.Query("topic"), " ")
@@ -501,6 +502,10 @@ func ExploreImages(ctx *context.Context) {
ctx.HTML(200, tplExploreImages)
}

func ExploreDataAnalysis(ctx *context.Context) {
ctx.HTML(200, tplExploreExploreDataAnalysis)
}

// NotFound render 404 page
func NotFound(ctx *context.Context) {
ctx.Data["Title"] = "Page Not Found"


+ 0
- 0
routers/org/members.go View File


+ 2
- 1
routers/private/internal.go View File

@@ -43,7 +43,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/manager/restart", Restart)
m.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues)
m.Post("/tool/update_all_repo_commit_cnt", UpdateAllRepoCommitCnt)
m.Post("/tool/repo_stat", RepoStatisticManually)
m.Post("/tool/repo_stat/:date", RepoStatisticManually)
m.Post("/tool/update_repo_visit/:date", UpdateRepoVisit)

}, CheckInternalToken)
}

+ 28
- 1
routers/private/tool.go View File

@@ -39,8 +39,35 @@ func UpdateAllRepoCommitCnt(ctx *macaron.Context) {
}

func RepoStatisticManually(ctx *macaron.Context) {
date := ctx.Query("date")
date := ctx.Params("date")
repo.RepoStatisticDaily(date)
repo.SummaryStatisticDaily(date)
repo.TimingCountDataByDate(date)
}

func UpdateRepoVisit(ctx *macaron.Context) {
date := ctx.Params("date")
log.Info("date(%s)", date)

repos, err := models.GetAllRepositories()
if err != nil {
log.Error("GetAllRepositories failed:%v", err.Error(), ctx.Data["MsgID"])
ctx.JSON(http.StatusInternalServerError, map[string]string{
"error_msg": "GetAllRepositories failed",
})
return
}

for i, repoStat := range repos {
log.Info("%d:begin UpdateRepoVisits(id = %d, name = %s)", i, repoStat.ID, repoStat.Name)
if err = repo.UpdateRepoVisits(ctx, repoStat, date); err != nil {
log.Error("UpdateRepoVisits(id = %d, name = %s) failed:%v", repoStat.ID, repoStat.Name, err.Error())
continue
}
log.Info("%d:finish UpdateRepoVisits(id = %d, name = %s)", i, repoStat.ID, repoStat.Name)
}

ctx.JSON(http.StatusOK, map[string]string{
"error_msg": "",
})
}

+ 5
- 3
routers/repo/attachment.go View File

@@ -360,6 +360,7 @@ func GetPresignedPutObjectURL(ctx *context.Context) {
// AddAttachment response for add attachment record
func AddAttachment(ctx *context.Context) {
typeCloudBrain := ctx.QueryInt("type")
fileName := ctx.Query("file_name")
err := checkTypeCloudBrain(typeCloudBrain)
if err != nil {
ctx.ServerError("checkTypeCloudBrain failed", err)
@@ -375,7 +376,7 @@ func AddAttachment(ctx *context.Context) {
return
}
} else {
has, err = storage.ObsHasObject(setting.BasePath + models.AttachmentRelativePath(uuid) + "/" + uuid)
has, err = storage.ObsHasObject(setting.BasePath + models.AttachmentRelativePath(uuid) + "/" + fileName)
if err != nil {
ctx.ServerError("ObsHasObject", err)
return
@@ -391,7 +392,7 @@ func AddAttachment(ctx *context.Context) {
UUID: uuid,
UploaderID: ctx.User.ID,
IsPrivate: true,
Name: ctx.Query("file_name"),
Name: fileName,
Size: ctx.QueryInt64("size"),
DatasetID: ctx.QueryInt64("dataset_id"),
Type: typeCloudBrain,
@@ -479,6 +480,7 @@ func UpdateAttachmentDecompressState(ctx *context.Context) {
func GetSuccessChunks(ctx *context.Context) {
fileMD5 := ctx.Query("md5")
typeCloudBrain := ctx.QueryInt("type")
fileName := ctx.Query("file_name")
var chunks string

err := checkTypeCloudBrain(typeCloudBrain)
@@ -510,7 +512,7 @@ func GetSuccessChunks(ctx *context.Context) {
return
}
} else {
isExist, err = storage.ObsHasObject(setting.BasePath + models.AttachmentRelativePath(fileChunk.UUID) + "/" + fileChunk.UUID)
isExist, err = storage.ObsHasObject(setting.BasePath + models.AttachmentRelativePath(fileChunk.UUID) + "/" + fileName)
if err != nil {
ctx.ServerError("ObsHasObject failed", err)
return


+ 12
- 3
routers/repo/cloudbrain.go View File

@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"regexp"
"sort"
"strconv"
"strings"
"time"
@@ -199,12 +200,12 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
command := form.Command
uuid := form.Attachment
jobType := form.JobType
gpuQueue := setting.JobType
gpuQueue := form.GpuType
codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath
resourceSpecId := form.ResourceSpecId

if !jobNamePattern.MatchString(jobName) {
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplModelArtsNew, &form)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplCloudBrainNew, &form)
return
}

@@ -242,7 +243,6 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {

benchmarkPath := setting.JobPath + jobName + cloudbrain.BenchMarkMountPath
if setting.IsBenchmarkEnabled && jobType == string(models.JobTypeBenchmark) {
gpuQueue = form.GpuType
var gpuType string
for _, gpuInfo := range gpuInfos.GpuInfo {
if gpuInfo.Queue == gpuQueue {
@@ -528,6 +528,15 @@ func CloudBrainShowModels(ctx *context.Context) {
return
}

for i, fileInfo := range fileInfos {
temp, _ := time.Parse("2006-01-02 15:04:05", fileInfo.ModTime)
fileInfos[i].ModTime = temp.Local().Format("2006-01-02 15:04:05")
}

sort.Slice(fileInfos, func(i, j int) bool {
return fileInfos[i].ModTime > fileInfos[j].ModTime
})

ctx.Data["Path"] = dirArray
ctx.Data["Dirs"] = fileInfos
ctx.Data["task"] = task


+ 6
- 0
routers/repo/http.go View File

@@ -317,6 +317,12 @@ func HTTP(ctx *context.Context) {
go repo.IncreaseCloneCnt()
}

if ctx.Req.Method == "POST" {
if strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
go repo.IncreaseGitCloneCnt()
}
}

w := ctx.Resp
r := ctx.Req.Request
cfg := &serviceConfig{


+ 628
- 347
routers/repo/modelarts.go
File diff suppressed because it is too large
View File


+ 130
- 63
routers/repo/repo_statistic.go View File

@@ -1,20 +1,26 @@
package repo

import (
"errors"
"time"

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

"code.gitea.io/gitea/modules/normalization"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/normalization"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/mailer"

"gitea.com/macaron/macaron"
)

//auto daily or manually
func StatisticAuto() {
RepoStatisticAuto()
TimingCountData()
}

//auto daily
func RepoStatisticAuto() {
log.Info("", time.Now())
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
setting.UpdateRadarMap()
RepoStatisticDaily(yesterday)
@@ -24,14 +30,17 @@ func RepoStatisticDaily(date string) {
log.Info("%s", date)
log.Info("begin Repo Statistic")
t, _ := time.Parse("2006-01-02", date)
warnEmailMessage := "项目统计信息入库失败,请尽快定位。"
if err := models.DeleteRepoStatDaily(date); err != nil {
log.Error("DeleteRepoStatDaily failed: %v", err.Error())
mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage)
return
}

repos, err := models.GetAllRepositories()
if err != nil {
log.Error("GetAllRepositories failed: %v", err.Error())
mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage)
return
}

@@ -40,12 +49,14 @@ func RepoStatisticDaily(date string) {
var minRepoRadar models.RepoStatistic
var maxRepoRadar models.RepoStatistic

for i, repo := range repos {
log.Info("start statistic: %s", repo.Name)
var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth int64
isInitMinMaxRadar := false

for _, repo := range repos {
log.Info("start statistic: %s", getDistinctProjectName(repo))
var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth, numCommits int64
repoGitStat, err := models.GetRepoKPIStats(repo)
if err != nil {
log.Error("GetRepoKPIStats failed: %s", repo.Name)
log.Error("GetRepoKPIStats failed: %s", getDistinctProjectName(repo))
} else {
numDevMonths = repoGitStat.DevelopAge
numKeyContributor = repoGitStat.KeyContributors
@@ -54,36 +65,40 @@ func RepoStatisticDaily(date string) {
numCommitsGrowth = repoGitStat.CommitsAdded
numCommitLinesGrowth = repoGitStat.CommitLinesModified
numContributorsGrowth = repoGitStat.ContributorsAdded
numCommits = repoGitStat.Commits

}

var issueFixedRate float32
if repo.NumIssues != 0 {
issueFixedRate = float32(repo.NumClosedIssues) / float32(repo.NumIssues)
} else {
issueFixedRate = 1.0
}

var numVersions int64
numVersions, err = models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{})
if err != nil {
log.Error("GetReleaseCountByRepoID failed(%s): %v", repo.Name, err)
log.Error("GetReleaseCountByRepoID failed(%s): %v", getDistinctProjectName(repo), err)
}

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

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

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

repoStat := models.RepoStatistic{
@@ -91,8 +106,11 @@ func RepoStatisticDaily(date string) {
Date: date,
Name: repo.Name,
IsPrivate: repo.IsPrivate,
IsMirror: repo.IsMirror,
OwnerName: repo.OwnerName,
NumWatches: int64(repo.NumWatches),
NumStars: int64(repo.NumStars),
NumForks: int64(repo.NumForks),
NumDownloads: repo.CloneCnt,
NumComments: numComments,
NumVisits: int64(numVisits),
@@ -103,7 +121,7 @@ func RepoStatisticDaily(date string) {
DatasetSize: datasetSize,
NumModels: 0,
NumWikiViews: numWikiViews,
NumCommits: repo.NumCommit,
NumCommits: numCommits,
NumIssues: int64(repo.NumIssues),
NumPulls: int64(repo.NumPulls),
IssueFixedRate: issueFixedRate,
@@ -144,14 +162,16 @@ func RepoStatisticDaily(date string) {
}

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

tempRepoStat := models.RepoStatistic{
RepoID: repoStat.RepoID,
Date: repoStat.Date,
IsMirror: repoStat.IsMirror,
Impact: normalization.GetImpactInitValue(repoStat.NumWatches, repoStat.NumStars, repoStat.NumForks, repoStat.NumDownloads, repoStat.NumComments, repoStat.NumVisits),
Completeness: normalization.GetCompleteInitValue(repoStat.NumClosedIssues, repoStat.NumVersions, repoStat.NumDevMonths, repoStat.DatasetSize, repoStat.NumModels, repoStat.NumWikiViews),
Liveness: normalization.GetLivenessInitValue(repoStat.NumCommits, repoStat.NumIssues, repoStat.NumPulls, repoStat.NumVisits),
@@ -162,74 +182,91 @@ func RepoStatisticDaily(date string) {

reposRadar = append(reposRadar, &tempRepoStat)

if i == 0 {
minRepoRadar = tempRepoStat
maxRepoRadar = tempRepoStat
} else {
if !isInitMinMaxRadar {

if tempRepoStat.Impact < minRepoRadar.Impact {
minRepoRadar.Impact = tempRepoStat.Impact
if !setting.RadarMap.IgnoreMirrorRepo || (setting.RadarMap.IgnoreMirrorRepo && !tempRepoStat.IsMirror) {
minRepoRadar = tempRepoStat
maxRepoRadar = tempRepoStat
isInitMinMaxRadar = true
}

if tempRepoStat.Impact > maxRepoRadar.Impact {
maxRepoRadar.Impact = tempRepoStat.Impact
}
} else {
if !setting.RadarMap.IgnoreMirrorRepo || (setting.RadarMap.IgnoreMirrorRepo && !tempRepoStat.IsMirror) {
if tempRepoStat.Impact < minRepoRadar.Impact {
minRepoRadar.Impact = tempRepoStat.Impact
}

if tempRepoStat.Completeness < minRepoRadar.Completeness {
minRepoRadar.Completeness = tempRepoStat.Completeness
}
if tempRepoStat.Impact > maxRepoRadar.Impact {
maxRepoRadar.Impact = tempRepoStat.Impact
}

if tempRepoStat.Completeness > maxRepoRadar.Completeness {
maxRepoRadar.Completeness = tempRepoStat.Completeness
}
if tempRepoStat.Completeness < minRepoRadar.Completeness {
minRepoRadar.Completeness = tempRepoStat.Completeness
}

if tempRepoStat.Liveness < minRepoRadar.Completeness {
minRepoRadar.Liveness = tempRepoStat.Liveness
}
if tempRepoStat.Completeness > maxRepoRadar.Completeness {
maxRepoRadar.Completeness = tempRepoStat.Completeness
}

if tempRepoStat.Liveness > maxRepoRadar.Liveness {
maxRepoRadar.Liveness = tempRepoStat.Liveness
}
if tempRepoStat.Liveness < minRepoRadar.Completeness {
minRepoRadar.Liveness = tempRepoStat.Liveness
}

if tempRepoStat.ProjectHealth < minRepoRadar.ProjectHealth {
minRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth
}
if tempRepoStat.Liveness > maxRepoRadar.Liveness {
maxRepoRadar.Liveness = tempRepoStat.Liveness
}

if tempRepoStat.ProjectHealth > maxRepoRadar.ProjectHealth {
maxRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth
}
if tempRepoStat.ProjectHealth < minRepoRadar.ProjectHealth {
minRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth
}

if tempRepoStat.TeamHealth < minRepoRadar.TeamHealth {
minRepoRadar.TeamHealth = tempRepoStat.TeamHealth
}
if tempRepoStat.ProjectHealth > maxRepoRadar.ProjectHealth {
maxRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth
}

if tempRepoStat.TeamHealth > maxRepoRadar.TeamHealth {
maxRepoRadar.TeamHealth = tempRepoStat.TeamHealth
}
if tempRepoStat.TeamHealth < minRepoRadar.TeamHealth {
minRepoRadar.TeamHealth = tempRepoStat.TeamHealth
}

if tempRepoStat.Growth < minRepoRadar.Growth {
minRepoRadar.Growth = tempRepoStat.Growth
}
if tempRepoStat.TeamHealth > maxRepoRadar.TeamHealth {
maxRepoRadar.TeamHealth = tempRepoStat.TeamHealth
}

if tempRepoStat.Growth < minRepoRadar.Growth {
minRepoRadar.Growth = tempRepoStat.Growth
}

if tempRepoStat.Growth > maxRepoRadar.Growth {
maxRepoRadar.Growth = tempRepoStat.Growth
}

if tempRepoStat.Growth > maxRepoRadar.Growth {
maxRepoRadar.Growth = tempRepoStat.Growth
}

}

log.Info("finish statistic: %s", repo.Name)
log.Info("finish statistic: %s", getDistinctProjectName(repo))
}

//radar map
log.Info("begin statistic radar")
for _, radarInit := range reposRadar {
radarInit.Impact = normalization.Normalization(radarInit.Impact, minRepoRadar.Impact, maxRepoRadar.Impact)
radarInit.Completeness = normalization.Normalization(radarInit.Completeness, minRepoRadar.Completeness, maxRepoRadar.Completeness)
radarInit.Liveness = normalization.Normalization(radarInit.Liveness, minRepoRadar.Liveness, maxRepoRadar.Liveness)
radarInit.ProjectHealth = normalization.Normalization(radarInit.ProjectHealth, minRepoRadar.ProjectHealth, maxRepoRadar.ProjectHealth)
radarInit.TeamHealth = normalization.Normalization(radarInit.TeamHealth, minRepoRadar.TeamHealth, maxRepoRadar.TeamHealth)
radarInit.Growth = normalization.Normalization(radarInit.Growth, minRepoRadar.Growth, maxRepoRadar.Growth)
radarInit.RadarTotal = normalization.GetRadarValue(radarInit.Impact, radarInit.Completeness, radarInit.Liveness, radarInit.ProjectHealth, radarInit.TeamHealth, radarInit.Growth)
if radarInit.IsMirror && setting.RadarMap.IgnoreMirrorRepo {
radarInit.Impact = 0
radarInit.Completeness = 0
radarInit.Liveness = 0
radarInit.ProjectHealth = 0
radarInit.TeamHealth = 0
radarInit.Growth = 0
radarInit.RadarTotal = 0
} else {
radarInit.Impact = normalization.Normalization(radarInit.Impact, minRepoRadar.Impact, maxRepoRadar.Impact)
radarInit.Completeness = normalization.Normalization(radarInit.Completeness, minRepoRadar.Completeness, maxRepoRadar.Completeness)
radarInit.Liveness = normalization.Normalization(radarInit.Liveness, minRepoRadar.Liveness, maxRepoRadar.Liveness)
radarInit.ProjectHealth = normalization.Normalization(radarInit.ProjectHealth, minRepoRadar.ProjectHealth, maxRepoRadar.ProjectHealth)
radarInit.TeamHealth = normalization.Normalization(radarInit.TeamHealth, minRepoRadar.TeamHealth, maxRepoRadar.TeamHealth)
radarInit.Growth = normalization.Normalization(radarInit.Growth, minRepoRadar.Growth, maxRepoRadar.Growth)
radarInit.RadarTotal = normalization.GetRadarValue(radarInit.Impact, radarInit.Completeness, radarInit.Liveness, radarInit.ProjectHealth, radarInit.TeamHealth, radarInit.Growth)
}
models.UpdateRepoStat(radarInit)
}

@@ -237,6 +274,10 @@ func RepoStatisticDaily(date string) {

}

func getDistinctProjectName(repo *models.Repository) string {
return repo.OwnerName + "/" + repo.Name
}

func getDatasetSize(repo *models.Repository) (int64, error) {
dataset, err := models.GetDatasetByRepo(repo)
if err != nil {
@@ -257,3 +298,29 @@ func getStatTime(timeStr string) (string, string) {

return beginTime, endTime
}

func UpdateRepoVisits(ctx *macaron.Context, repo *models.Repository, date string) error {
beginTime, endTime := getStatTime(date)
var numVisits int
numVisits, err := repository.AppointProjectView(repo.OwnerName, repo.Name, beginTime, endTime)
if err != nil {
log.Error("AppointProjectView failed(%s): %v", getDistinctProjectName(repo), err)
return err
}

repoStat, err := models.GetRepoStatisticByDate(date, repo.ID)
if err != nil {
log.Error("GetRepoStatisticByDate failed(%s): %v", getDistinctProjectName(repo), err)
return err
} else if len(repoStat) != 1 {
log.Error("GetRepoStatisticByDate failed(%s): %v", getDistinctProjectName(repo), err)
return errors.New("not find repo")
}
repoStat[0].NumVisits = int64(numVisits)

if err = models.UpdateRepoStatVisits(repoStat[0]); err != nil {
log.Error("UpdateRepoStatVisits failed(%s): %v", getDistinctProjectName(repo), err)
return err
}
return nil
}

+ 160
- 7
routers/repo/user_data_analysis.go View File

@@ -1,14 +1,157 @@
package repo

import (
"fmt"
"net/http"
"net/url"
"time"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/mailer"
"github.com/360EntSecGroup-Skylar/excelize/v2"
)

func TimingCountDataByDate(date string) {
func QueryUserStaticDataPage(ctx *context.Context) {
startDate := ctx.Query("startDate")
endDate := ctx.Query("endDate")
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
pageSize := ctx.QueryInt("pageSize")
if pageSize <= 0 {
pageSize = setting.UI.IssuePagingNum
}
userName := ctx.Query("userName")
IsReturnFile := ctx.QueryBool("IsReturnFile")
log.Info("startDate=" + startDate + " endDate=" + endDate + " userName=" + userName + " page=" + fmt.Sprint(page))
var startTime time.Time
var endTime time.Time
var isAll bool
if startDate == "all" {
isAll = true
startTime = time.Now()
endTime = time.Now()
} else {
startTime, _ = time.ParseInLocation("2006-01-02", startDate, time.Local)
startTime = time.Date(startTime.Year(), startTime.Month(), startTime.Day(), 12, 0, 0, 0, startTime.Location())
settingStartTime, _ := time.Parse("2006-01-02", setting.RadarMap.RecordBeginTime)
if startTime.Unix() < settingStartTime.Unix() {
startTime = settingStartTime
startDate = settingStartTime.Format("2006-01-02")
}
endTime, _ = time.ParseInLocation("2006-01-02", endDate, time.Local)
endTime = endTime.AddDate(0, 0, 1)
endTime = time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 23, 59, 59, 0, startTime.Location())

isAll = false
log.Info("startTime=" + fmt.Sprint(startTime.Unix()) + " endDate=" + fmt.Sprint(endTime.Unix()))
}

if IsReturnFile {
page = -1
pageSize = -1
}

pageOpts := &models.UserBusinessAnalysisQueryOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: pageSize,
},
UserName: userName,
StartTime: startTime.Unix(),
EndTime: endTime.Unix(),
IsAll: isAll,
}

if IsReturnFile {
re, count := models.QueryUserStaticDataAll(pageOpts)
log.Info("return count=" + fmt.Sprint(count))
//writer exec file.
xlsx := excelize.NewFile()
sheetName := ctx.Tr("user.static.sheetname")
index := xlsx.NewSheet(sheetName)
xlsx.DeleteSheet("Sheet1")
dataHeader := map[string]string{
"A1": ctx.Tr("user.static.id"),
"B1": ctx.Tr("user.static.name"),
"C1": ctx.Tr("user.static.codemergecount"),
"D1": ctx.Tr("user.static.commitcount"),
"E1": ctx.Tr("user.static.issuecount"),
"F1": ctx.Tr("user.static.commentcount"),
"G1": ctx.Tr("user.static.focusrepocount"),
"H1": ctx.Tr("user.static.starrepocount"),
"I1": ctx.Tr("user.static.logincount"),
"J1": ctx.Tr("user.static.watchedcount"),
"K1": ctx.Tr("user.static.commitcodesize"),
"L1": ctx.Tr("user.static.solveissuecount"),
"M1": ctx.Tr("user.static.encyclopediascount"),
"N1": ctx.Tr("user.static.createrepocount"),
"O1": ctx.Tr("user.static.openiindex"),
"P1": ctx.Tr("user.static.registdate"),
"Q1": ctx.Tr("user.static.countdate"),
}
for k, v := range dataHeader {
//设置单元格的值
xlsx.SetCellValue(sheetName, k, v)
}

for i, userRecord := range re {
rows := fmt.Sprint(i + 2)

xlsx.SetCellValue(sheetName, "A"+rows, userRecord.ID)
xlsx.SetCellValue(sheetName, "B"+rows, userRecord.Name)
xlsx.SetCellValue(sheetName, "C"+rows, userRecord.CodeMergeCount)
xlsx.SetCellValue(sheetName, "D"+rows, userRecord.CommitCount)
xlsx.SetCellValue(sheetName, "E"+rows, userRecord.IssueCount)
xlsx.SetCellValue(sheetName, "F"+rows, userRecord.CommentCount)
xlsx.SetCellValue(sheetName, "G"+rows, userRecord.FocusRepoCount)
xlsx.SetCellValue(sheetName, "H"+rows, userRecord.StarRepoCount)
xlsx.SetCellValue(sheetName, "I"+rows, userRecord.LoginCount)
xlsx.SetCellValue(sheetName, "J"+rows, userRecord.WatchedCount)
xlsx.SetCellValue(sheetName, "K"+rows, userRecord.CommitCodeSize)
xlsx.SetCellValue(sheetName, "L"+rows, userRecord.SolveIssueCount)
xlsx.SetCellValue(sheetName, "M"+rows, userRecord.EncyclopediasCount)
xlsx.SetCellValue(sheetName, "N"+rows, userRecord.CreateRepoCount)
xlsx.SetCellValue(sheetName, "O"+rows, fmt.Sprintf("%.2f", userRecord.OpenIIndex))

formatTime := userRecord.RegistDate.Format("2006-01-02 15:04:05")
xlsx.SetCellValue(sheetName, "P"+rows, formatTime[0:len(formatTime)-3])

formatTime = userRecord.DataDate
xlsx.SetCellValue(sheetName, "Q"+rows, formatTime+" 00:01")
}

//设置默认打开的表单
xlsx.SetActiveSheet(index)

filename := sheetName + "_" + ctx.Tr("user.static.all") + ".xlsx"

ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(filename))
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
if _, err := xlsx.WriteTo(ctx.Resp); err != nil {
log.Info("writer exel error." + err.Error())
}

} else {
mapInterface := make(map[string]interface{})
re, count := models.QueryUserStaticDataPage(pageOpts)
mapInterface["data"] = re
mapInterface["count"] = count
ctx.JSON(http.StatusOK, mapInterface)
}
}

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())
@@ -18,10 +161,11 @@ func TimingCountDataByDate(date string) {
//query wiki data
log.Info("start to time count data")
wikiMap := make(map[string]int)
warnEmailMessage := "用户统计信息入库失败,请尽快定位。"
repoList, err := models.GetAllRepositories()
if err != nil {
log.Error("query repo error.")
log.Error("query repo error." + err.Error())
mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage)
return
}
log.Info("start to query wiki data")
@@ -56,16 +200,25 @@ func TimingCountDataByDate(date string) {
}
}
//other user info data
models.CounDataByDate(wikiMap, startTime, endTime)
err = models.CounDataByDateAndReCount(wikiMap, startTime, endTime, isReCount)
if err != nil {
log.Error("count user info error." + err.Error())
mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage)
}

if isReCount {
models.RefreshUserStaticAllTabel()
}
}

func TimingCountData() {
func TimingCountDataByDate(date string) {
TimingCountDataByDateAndReCount(date, true)
}

func TimingCountData() {
log.Info("start to time count data")
currentTimeNow := time.Now()
log.Info("current time:" + currentTimeNow.Format("2006-01-02 15:04:05"))
startTime := currentTimeNow.AddDate(0, 0, -1).Format("2006-01-02")

TimingCountDataByDate(startTime)
TimingCountDataByDateAndReCount(startTime, false)
}

+ 124
- 38
routers/repo/view.go View File

@@ -12,8 +12,10 @@ import (
"fmt"
gotemplate "html/template"
"io/ioutil"
"net/http"
"net/url"
"path"
"sort"
"strings"
"time"

@@ -31,11 +33,12 @@ import (
)

const (
tplRepoEMPTY base.TplName = "repo/empty"
tplRepoHome base.TplName = "repo/home"
tplWatchers base.TplName = "repo/watchers"
tplForks base.TplName = "repo/forks"
tplMigrating base.TplName = "repo/migrating"
tplRepoEMPTY base.TplName = "repo/empty"
tplRepoHome base.TplName = "repo/home"
tplWatchers base.TplName = "repo/watchers"
tplForks base.TplName = "repo/forks"
tplMigrating base.TplName = "repo/migrating"
tplContributors base.TplName = "repo/contributors"
)

type namedBlob struct {
@@ -243,6 +246,11 @@ func renderDirectory(ctx *context.Context, treeLink string) {
ctx.Data["ReadmeInList"] = true
ctx.Data["ReadmeExist"] = true
ctx.Data["FileIsSymlink"] = readmeFile.isSymlink
ctx.Data["ReadmeName"] = readmeFile.name

if ctx.Repo.CanEnableEditor() {
ctx.Data["CanEditFile"] = true
}

dataRc, err := readmeFile.blob.DataAsync()
if err != nil {
@@ -570,19 +578,29 @@ func safeURL(address string) string {
}

type ContributorInfo struct {
UserInfo *models.User // nil for contributor who is not a registered user
Email string
CommitCnt int
UserInfo *models.User // nil for contributor who is not a registered user
RelAvatarLink string `json:"rel_avatar_link"`
UserName string `json:"user_name"`
Email string `json:"email"`
CommitCnt int `json:"commit_cnt"`
}

type GetContributorsInfo struct {
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
Count int `json:"count"`
ContributorInfo []*ContributorInfo `json:"contributor_info"`
}

func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo{
func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo {
for _, c := range contributorInfos {
if strings.Compare(c.Email,email) == 0 {
if strings.Compare(c.Email, email) == 0 {
return c
}
}
return nil
}

// Home render repository home page
func Home(ctx *context.Context) {
if len(ctx.Repo.Units) > 0 {
@@ -591,35 +609,41 @@ func Home(ctx *context.Context) {
if err == nil && contributors != nil {
startTime := time.Now()
var contributorInfos []*ContributorInfo
contributorInfoHash:= make(map[string]*ContributorInfo)
contributorInfoHash := make(map[string]*ContributorInfo)
count := 0
for _, c := range contributors {
if strings.Compare(c.Email,"") == 0 {
if count >= 25 {
continue
}
if strings.Compare(c.Email, "") == 0 {
continue
}
// get user info from committer email
user, err := models.GetUserByActivateEmail(c.Email)
if err == nil {
// committer is system user, get info through user's primary email
if existedContributorInfo,ok:=contributorInfoHash[user.Email];ok {
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
}else{
} else {
// new committer info
var newContributor = &ContributorInfo{
user, user.Email,c.CommitCnt,
user, user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt,
}
contributorInfos = append(contributorInfos, newContributor )
count++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[user.Email] = newContributor
}
} else {
// committer is not system user
if existedContributorInfo,ok:=contributorInfoHash[c.Email];ok {
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
}else{
} else {
var newContributor = &ContributorInfo{
user, c.Email,c.CommitCnt,
user, "", "",c.Email, c.CommitCnt,
}
count++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[c.Email] = newContributor
}
@@ -627,7 +651,7 @@ func Home(ctx *context.Context) {
}
ctx.Data["ContributorInfo"] = contributorInfos
var duration = time.Since(startTime)
log.Info("getContributorInfo cost: %v seconds",duration.Seconds())
log.Info("getContributorInfo cost: %v seconds", duration.Seconds())
}
if ctx.Repo.Repository.IsBeingCreated() {
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
@@ -694,13 +718,13 @@ func renderLicense(ctx *context.Context) {
log.Error("failed to get license content: %v, err:%v", f, err)
continue
}
if bytes.Compare(buf,license) == 0 {
log.Info("got matched license:%v",f)
if bytes.Compare(buf, license) == 0 {
log.Info("got matched license:%v", f)
ctx.Data["LICENSE"] = f
return
}
}
log.Info("not found matched license,repo:%v",ctx.Repo.Repository.Name)
log.Info("not found matched license,repo:%v", ctx.Repo.Repository.Name)
}

func renderLanguageStats(ctx *context.Context) {
@@ -800,32 +824,29 @@ func renderCode(ctx *context.Context) {
*/
baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath())
defer baseGitRepo.Close()
var compareInfo *git.CompareInfo
if err != nil {
log.Error("error open baseRepo:%s",ctx.Repo.Repository.BaseRepo.RepoPath())
log.Error("error open baseRepo:%s", ctx.Repo.Repository.BaseRepo.RepoPath())
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
}else{
if _,error:= baseGitRepo.GetBranch(ctx.Repo.BranchName);error==nil{
} else {
if _, error := baseGitRepo.GetBranch(ctx.Repo.BranchName); error == nil {
//base repo has the same branch, then compare between current repo branch and base repo's branch
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName
ctx.SetParams("*",compareUrl)
compareInfo, err = baseGitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(), ctx.Repo.BranchName, ctx.Repo.BranchName)
ctx.Data["UpstreamSameBranchName"] = true
}else{
} else {
//else, compare between current repo branch and base repo's default branch
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.Repository.BaseRepo.DefaultBranch
ctx.SetParams("*",compareUrl)
compareInfo, err = baseGitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(), ctx.Repo.BranchName, ctx.Repo.Repository.BaseRepo.DefaultBranch)
ctx.Data["UpstreamSameBranchName"] = false
}
_, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx)
defer headGitRepo.Close()
if compareInfo!= nil {
if compareInfo.Commits!=nil {
log.Info("compareInfoCommits数量:%d",compareInfo.Commits.Len())
if err==nil && compareInfo != nil {
if compareInfo.Commits != nil {
log.Info("compareInfoCommits数量:%d", compareInfo.Commits.Len())
ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len()
}else{
} else {
log.Info("compareInfo nothing different")
ctx.Data["FetchUpstreamCnt"] = 0
}
}else{
} else {
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
}
}
@@ -893,3 +914,68 @@ func Forks(ctx *context.Context) {

ctx.HTML(200, tplForks)
}

func Contributors(ctx *context.Context) {
ctx.Data["PageIsViewCode"] = true
ctx.HTML(http.StatusOK, tplContributors)
}

func ContributorsAPI(ctx *context.Context) {
count := 0
errorCode := 0
errorMsg := ""
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath())
var contributorInfos []*ContributorInfo
if err == nil && contributors != nil {
contributorInfoHash := make(map[string]*ContributorInfo)
for _, c := range contributors {
if strings.Compare(c.Email, "") == 0 {
continue
}
// get user info from committer email
user, err := models.GetUserByActivateEmail(c.Email)
if err == nil {
// committer is system user, get info through user's primary email
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
} else {
// new committer info
var newContributor = &ContributorInfo{
user, user.RelAvatarLink(),user.Name, user.Email,c.CommitCnt,
}
count++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[user.Email] = newContributor
}
} else {
// committer is not system user
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
} else {
var newContributor = &ContributorInfo{
user, "", "",c.Email,c.CommitCnt,
}
count++
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[c.Email] = newContributor
}
}
}
sort.Slice(contributorInfos, func(i, j int) bool {
return contributorInfos[i].CommitCnt > contributorInfos[j].CommitCnt
})
} else {
log.Error("GetContributors failed: %v", err)
errorCode = -1
errorMsg = err.Error()
}

ctx.JSON(http.StatusOK, GetContributorsInfo{
ErrorCode: errorCode,
ErrorMsg: errorMsg,
Count: count,
ContributorInfo: contributorInfos,
})
}

+ 15
- 16
routers/routes/routes.go View File

@@ -311,7 +311,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Head("/", func() string {
return ""
})
m.Get("/", routers.Dashboard)
m.Get("/", routers.Home)
m.Get("/dashboard", routers.Dashboard)
m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) {
@@ -325,6 +325,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/organizations", routers.ExploreOrganizations)
m.Get("/code", routers.ExploreCode)
m.Get("/images", routers.ExploreImages)
m.Get("/data_analysis", routers.ExploreDataAnalysis)
}, ignSignIn)
m.Combo("/install", routers.InstallInit).Get(routers.Install).
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
@@ -615,6 +616,11 @@ func RegisterRoutes(m *macaron.Macaron) {
//reqRepoBlockChainWriter := context.RequireRepoWriter(models.UnitTypeBlockChain)

// ***** START: Organization *****
m.Group("/org", func() {
m.Group("/:org", func() {
m.Get("/members", org.Members)
}, context.OrgAssignment())
})
m.Group("/org", func() {
m.Group("", func() {
m.Get("/create", org.Create)
@@ -625,7 +631,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/dashboard", user.Dashboard)
m.Get("/^:type(issues|pulls)$", user.Issues)
m.Get("/milestones", reqMilestonesDashboardPageEnabled, user.Milestones)
m.Get("/members", org.Members)
//m.Get("/members", org.Members)
m.Post("/members/action/:action", org.MembersAction)

m.Get("/teams", org.Teams)
@@ -786,9 +792,11 @@ 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)
m.Get("/contributors/list", repo.ContributorsAPI)
m.Group("/milestone", func() {
m.Get("/:id", repo.MilestoneIssuesAndPulls)
}, reqRepoIssuesOrPullsReader, context.RepoRef())
@@ -962,16 +970,6 @@ func RegisterRoutes(m *macaron.Macaron) {
}, context.RepoRef())

m.Group("/modelarts", func() {
// m.Get("", reqRepoCloudBrainReader, repo.ModelArtsIndex)
// m.Group("/:jobid", func() {
// m.Get("", reqRepoCloudBrainReader, repo.ModelArtsShow)
// m.Get("/debug", reqRepoCloudBrainReader, repo.ModelArtsDebug)
// m.Post("/stop", reqRepoCloudBrainWriter, repo.ModelArtsStop)
// m.Post("/del", reqRepoCloudBrainWriter, repo.ModelArtsDel)
// })
// m.Get("/create", reqRepoCloudBrainWriter, repo.ModelArtsNew)
// m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsForm{}), repo.ModelArtsCreate)

m.Group("/notebook", func() {
m.Get("", reqRepoCloudBrainReader, repo.NotebookIndex)
m.Group("/:jobid", func() {
@@ -990,12 +988,13 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", reqRepoCloudBrainReader, repo.TrainJobShow)
m.Post("/stop", reqRepoCloudBrainWriter, repo.TrainJobStop)
m.Post("/del", reqRepoCloudBrainWriter, repo.TrainJobDel)
m.Get("/log", reqRepoCloudBrainReader, repo.TrainJobGetLog)
m.Get("/models", reqRepoCloudBrainReader, repo.TrainJobShowModels)
m.Get("/download_model", reqRepoCloudBrainReader, repo.TrainJobDownloadModel)
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.Get("/create", reqRepoCloudBrainReader, repo.TrainJobNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate)

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


+ 4
- 4
routers/user/auth.go View File

@@ -544,7 +544,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR

if err := models.UpdateUserCols(u, "language"); err != nil {
log.Error(fmt.Sprintf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language))
return setting.AppSubURL + "/"
return setting.AppSubURL + "/dashboard"
}
} else {
// Language setting of the user use the one previously set
@@ -562,7 +562,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
u.SetLastLogin()
if err := models.UpdateUserCols(u, "last_login_unix"); err != nil {
ctx.ServerError("UpdateUserCols", err)
return setting.AppSubURL + "/"
return setting.AppSubURL + "/dashboard"
}

if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
@@ -574,9 +574,9 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
}

if obeyRedirect {
ctx.Redirect(setting.AppSubURL + "/")
ctx.Redirect(setting.AppSubURL + "/dashboard")
}
return setting.AppSubURL + "/"
return setting.AppSubURL + "/dashboard"
}

// SignInOAuth handles the OAuth2 login buttons


+ 16
- 0
services/mailer/mail.go View File

@@ -115,6 +115,22 @@ func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAdd
SendAsync(msg)
}

func SendWarnNotifyMail(emails []string, message string) {
if setting.MailService == nil {
log.Warn("SendWarnNotifyMail is being invoked but mail service hasn't been initialized")
return
}
if len(emails) == 0 {
log.Warn("SendWarnNotifyMail is being invoked but do not have email to send")
return
}
msg := NewMessage(emails, message, message)
msg.Info = fmt.Sprintf(message)

SendAsync(msg)

}

// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
func SendRegisterNotifyMail(locale Locale, u *models.User) {
if setting.MailService == nil {


+ 42
- 0
templates/base/footer_content_fluid.tmpl View File

@@ -0,0 +1,42 @@
<footer>
<div class="ui fluid container" style="padding: 0px 10px;">
<div class="ui grid">
<div class="sixteen wide mobile eight wide tablet eight wide computer column">
<div class="ui three column grid">
<div class="column ui vertical text menu">
<div class="header item">{{.i18n.Tr "custom.head.community"}}</div>
<a href="https://openi.org.cn/html/Club/2019/0227/14.html" class="item">{{.i18n.Tr "custom.foot.council"}}</a>
<a href="https://openi.org.cn/html/Club/2019/0227/14.html" class="item">{{.i18n.Tr "custom.foot.technical_committee"}}</a>
<a href="https://openi.org.cn/html/Club/2019/0228/17.html" class="item">{{.i18n.Tr "custom.foot.join"}}</a>
</div>
<div class="column ui vertical text menu">
<div class="header item">{{.i18n.Tr "custom.foot.news"}}</div>
<a href="https://openi.org.cn/html/news/dongtai/" class="item">{{.i18n.Tr "custom.foot.community_news"}}</a>
<a href="https://openi.org.cn/html/news/huodong/" class="item">{{.i18n.Tr "custom.foot.member_news"}}</a>
<a href="https://openi.org.cn/html/news/zixun/" class="item">{{.i18n.Tr "custom.foot.industry_advisory"}}</a>
</div>
<div class="column ui vertical text menu">
<div class="header item">{{.i18n.Tr "custom.foot.help"}}</div>
<div class="ui language bottom floating slide up dropdown link item">
<i class="world icon"></i>
<div class="text">{{.LangName}}</div>
<div class="menu">
{{range .AllLangs}}
<a lang="{{.Lang}}" class="item {{if eq $.Lang .Lang}}active selected{{end}}" href="{{if eq $.Lang .Lang}}#{{else}}{{$.Link}}?lang={{.Lang}}{{end}}">{{.Name}}</a>
{{end}}
</div>
</div>
{{if .EnableSwagger}}<a href="/api/swagger" class="ui item">API</a>{{end}}
{{template "custom/extra_links_footer" .}}
</div>
</div>
</div>
<div class="sixteen wide mobile eight wide tablet eight wide computer column" style=" margin:2.0rem 0">
{{.i18n.Tr "custom.foot.copyright"}} <a href="http://beian.miit.gov.cn/" target="_blank">京ICP备18004880号</a>
<br>
{{.i18n.Tr "Powered_by 鹏城实验室云脑、"}}<a href="https://www.trustie.net/" target="_blank">Trustie确实</a>{{.i18n.Tr "、gitea"}}
<br>
</div>
</div>
</div>
</footer>

+ 48
- 0
templates/base/footer_fluid.tmpl View File

@@ -0,0 +1,48 @@
{{/*
<html>
<body>
<div>
*/}}
{{template "custom/body_inner_post" .}}
</div>
{{template "custom/body_outer_post" .}}
{{template "base/footer_content_fluid" .}}
<script src="{{StaticUrlPrefix}}/js/jquery.js?v={{MD5 AppVer}}"></script>
{{if .RequireSimpleMDE}}
<script src="{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.js"></script>
<script src="{{StaticUrlPrefix}}/vendor/plugins/codemirror/addon/mode/loadmode.js"></script>
<script src="{{StaticUrlPrefix}}/vendor/plugins/codemirror/mode/meta.js"></script>
<script>
CodeMirror.modeURL = "{{StaticUrlPrefix}}/vendor/plugins/codemirror/mode/%N/%N.js";
</script>
{{end}}
<!-- Third-party libraries -->
{{if .RequireMinicolors}}
<script src="{{StaticUrlPrefix}}/vendor/plugins/jquery.minicolors/jquery.minicolors.min.js"></script>
{{end}}
{{if .RequireU2F}}
<script src="{{StaticUrlPrefix}}/vendor/plugins/u2f/index.js"></script>
{{end}}
{{if .EnableCaptcha}}
{{if eq .CaptchaType "recaptcha"}}
<script src='{{ URLJoin .RecaptchaURL "api.js"}}' async></script>
{{end}}
{{end}}
{{if .RequireTribute}}
<script src="{{StaticUrlPrefix}}/vendor/plugins/tribute/tribute.min.js"></script>
{{end}}
{{if .PageIsHome}}
<script rel="stylesheet" src="{{StaticUrlPrefix}}/vendor/plugins/jquery.particleground/jquery.particleground.min.js"></script>
{{end}}
<script src="{{StaticUrlPrefix}}/fomantic/semantic.min.js?v={{MD5 AppVer}}"></script>
<script src="{{StaticUrlPrefix}}/js/index.js?v={{MD5 AppVer}}"></script>
{{template "custom/footer" .}}
</body>
</html>

+ 2
- 2
templates/base/head.tmpl View File

@@ -180,8 +180,8 @@
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?7c4ef0a24be6109ab22e63c832ab21cf";
var s = document.getElementsByTagName("script")[0];
hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>


+ 208
- 0
templates/base/head_fluid.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_fluid" .}}
</div><!-- end bar -->
{{end}}
{{/*
</div>
</body>
</html>
*/}}

+ 2
- 2
templates/base/head_home.tmpl View File

@@ -181,8 +181,8 @@
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?7c4ef0a24be6109ab22e63c832ab21cf";
var s = document.getElementsByTagName("script")[0];
hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>


+ 14
- 0
templates/base/head_navbar.tmpl View File

@@ -7,6 +7,14 @@
<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}}
<a class="item {{if .PageIsDashboard}}active{{end}}" href="/dashboard">{{.i18n.Tr "index"}}</a>
@@ -29,6 +37,9 @@
<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}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
</div>
</div>
{{else if .IsLandingPageHome}}
@@ -44,6 +55,9 @@
<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}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
</div>
</div>
{{else if .IsLandingPageExplore}}


+ 177
- 0
templates/base/head_navbar_fluid.tmpl View File

@@ -0,0 +1,177 @@
<div class="ui fluid container" style = "padding: 0px 20px;" 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}}
<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">
{{.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}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
</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">
{{.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}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
</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">
<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">
{{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>

+ 6
- 0
templates/base/head_navbar_home.tmpl View File

@@ -29,6 +29,9 @@
<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}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
</div>
</div>
{{else if .IsLandingPageHome}}
@@ -44,6 +47,9 @@
<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}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}}
</div>
</div>
{{else if .IsLandingPageExplore}}


+ 15
- 0
templates/explore/data_analysis.tmpl View File

@@ -0,0 +1,15 @@
{{template "base/head_fluid" .}}
<div id="data_analysis" style="height: 100%;">

</div>

{{template "base/footer_fluid" .}}

<style>
.full.height {
/* flex-grow: 1; */
padding-bottom: 53px;
}

</style>

+ 16
- 3
templates/home.tmpl View File

@@ -7,7 +7,11 @@
</div>
</h1>
<p class="am-lh-18">免费私有代码仓库,免费计算资源,大容量数据存储,<br>多类型硬件环境(GPU、NPU),AI开发流水线(开发-调试-训练-迭代)</p>
<a class="circular ui secondary button" href="{{AppSubUrl}}/user/sign_up">立即使用 <i class="right arrow icon"></i></a>
{{if .IsSigned}}
<a class="circular ui secondary button" href="{{AppSubUrl}}/dashboard">立即使用 <i class="right arrow icon"></i></a>
{{else}}
<a class="circular ui secondary button" href="{{AppSubUrl}}/user/login">立即使用 <i class="right arrow icon"></i></a>
{{end}}
<div class="bannerpic"><img class="ui fluid image" src="/img/gitopeni-index-01.svg"></div>
</div>
</div><!-- end segment -->
@@ -33,7 +37,11 @@
<p class="am-lh-18">在这里为你和你的团队创建项目,基于Git工具,提交记录或者回滚代码修改。<br>
不论是公开或者私有仓库,都可免费使用所有功能。<br>
尽情将你喜欢的代码都放在这里,仓库数量、存储容量不受限</p>
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/sign_up">立即使用</a>
{{if .IsSigned}}
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/dashboard">立即使用 </a>
{{else}}
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/login">立即使用 </a>
{{end}}
</div>
<div class="ten wide column computer only i-code-pic am-pt-30">
<img class="ui fluid rounded image am-shadow-2 am-mt-10" src="/img/i-code-pic.jpg" style="position: absolute;">
@@ -184,7 +192,12 @@
开发者可以根据使用需求,自由选择相应计算资源,可以测试模型在不同硬件环境下的适配能力、性能、稳定性等<br>
如果您的模型需要更多的计算资源,也可以单独申请<br>
</p>
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/sign_up">马上使用</a> <a class="ui grey basic button am-mt-20" href="mailto:aiforge@openi.org.cn">单独申请</a>
{{if .IsSigned}}
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/dashboard">立即使用 </a><a class="ui grey basic button am-mt-20" href="mailto:aiforge@openi.org.cn">单独申请</a>
{{else}}
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/login">立即使用 </a><a class="ui grey basic button am-mt-20" href="mailto:aiforge@openi.org.cn">单独申请</a>
{{end}}
</div>
</div>
</div>


+ 6
- 5
templates/org/home.tmpl View File

@@ -38,11 +38,12 @@
<div class="ui sixteen wide mobile six wide tablet five wide computer column">
<h4 class="ui top attached header">
<strong>{{.i18n.Tr "org.people"}}</strong>
{{if .IsOrganizationMember}}
<div class="ui right">
<a class="text grey" href="{{.OrgLink}}/members">{{.Org.NumMembers}} {{svg "octicon-chevron-right" 16}}</a>
</div>
{{end}}
<div class="ui right">
<a class="text grey" href="{{.OrgLink}}/members">{{.MembersTotal}} {{svg "octicon-chevron-right" 16}}</a>
</div>
<!-- {{if .IsOrganizationMember}} -->
<!-- {{end}} -->
</h4>
<div class="ui attached segment members">
{{$isMember := .IsOrganizationMember}}


+ 6
- 6
templates/org/navber.tmpl View File

@@ -3,10 +3,10 @@
<a class="{{if $.PageIsOrgHome}}active{{end}} item" href="{{.HomeLink}}">
{{svg "octicon-home" 16}}&nbsp;{{$.i18n.Tr "org.home"}}
</a>
<a class="{{if $.PageIsOrgMembers}}active{{end}} item" href="{{$.OrgLink}}/members">
{{svg "octicon-organization" 16}}&nbsp;{{$.i18n.Tr "org.people"}}
</a>
{{if or ($.IsOrganizationMember) ($.IsOrganizationOwner)}}
<a class="{{if $.PageIsOrgMembers}}active{{end}} item" href="{{$.OrgLink}}/members">
{{svg "octicon-organization" 16}}&nbsp;{{$.i18n.Tr "org.people"}}
</a>
<a class="{{if $.PageIsOrgTeams}}active{{end}} item" href="{{$.OrgLink}}/teams">
{{svg "octicon-jersey" 16}}&nbsp;{{$.i18n.Tr "org.teams"}}
</a>
@@ -23,10 +23,10 @@
{{svg "octicon-home" 16}}&nbsp;{{$.i18n.Tr "org.home"}}
</a>
{{end}}
<a class="{{if $.PageIsOrgMembers}}active{{end}} item" href="{{$.OrgLink}}/members">
{{svg "octicon-organization" 16}}&nbsp;{{$.i18n.Tr "org.people"}}
</a>
{{if or ($.IsOrganizationMember) ($.IsOrganizationOwner)}}
<a class="{{if $.PageIsOrgMembers}}active{{end}} item" href="{{$.OrgLink}}/members">
{{svg "octicon-organization" 16}}&nbsp;{{$.i18n.Tr "org.people"}}
</a>
<a class="{{if $.PageIsOrgTeams}}active{{end}} item" href="{{$.OrgLink}}/teams">
{{svg "octicon-jersey" 16}}&nbsp;{{$.i18n.Tr "org.teams"}}
</a>


+ 13
- 3
templates/repo/activity.tmpl View File

@@ -2,8 +2,18 @@
<div class="repository commits">
{{template "repo/header" .}}
<div class="ui container">
<h2 class="ui header">{{.DateFrom}} - {{.DateUntil}}
<div class="ui right">
<div class="ui three column stackable grid" style="align-items: center;">
<div class="column">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}">{{.i18n.Tr "repo.code"}}</a>
<div class="divider"> / </div>
<div class="active section" href="{{.RepoLink}}/activity">{{.i18n.Tr "repo.activity"}}</div>
</div>
</div>
<div class="column center aligned" style="font-weight: 800;">
{{.DateFrom}} - {{.DateUntil}}
</div>
<div class="column right aligned">
<!-- Period -->
<div class="ui floating dropdown jump filter">
<div class="ui basic compact button">
@@ -23,7 +33,7 @@
</div>
</div>
</div>
</h2>
</div>
<div class="ui divider"></div>

{{if (or (.Permission.CanRead $.UnitTypeIssues) (.Permission.CanRead $.UnitTypePullRequests))}}


+ 10
- 10
templates/repo/cloudbrain/index.tmpl View File

@@ -239,8 +239,8 @@

<div class="column">
<div class="ui blue small menu compact selectcloudbrain">
<a class="active item">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<!-- <a class="item" href="{{.RepoLink}}/modelarts">训练任务</a> -->
<a class="active item" href="{{.RepoLink}}/cloudbrain">{{$.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">
@@ -307,9 +307,9 @@

<!-- 任务名 -->
<div class="five wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 15px;">
<span class="fitted" style="vertical-align: middle;">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted text_over" style="width: 90%;vertical-align: middle;margin-left: 0.4rem;">{{.JobName}}</span>
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted text_over" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>

@@ -380,7 +380,7 @@
{{end}}
</form>
</div>
<div class="ui compact buttons" style="margin-right:10px;">
<div class="ui compact buttons">
<!-- 模型下载 -->
<a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}/models" target="_blank">
{{$.i18n.Tr "repo.download"}}
@@ -399,11 +399,11 @@
<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 compact {{if not .CanDel}}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<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 compact disabled button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a class="ui basic blue button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
@@ -427,12 +427,12 @@
<div class="inline required field dis">
<label>镜像标签:</label>
<input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="255" style="width:75%">
<input name="tag" id="image_tag" tabindex="3" autofocus required maxlength="254" style="width:75%">
</div>
<div class="inline field">
<label class="label_after">镜像描述:</label>
<textarea name="description" rows="8" style="width:75%;margin-left: 0.2em;"></textarea>
<textarea name="description" maxlength="254" rows="8" style="width:75%;margin-left: 0.2em;"></textarea>
</div>
<div class="ui divider"></div>


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

@@ -131,7 +131,7 @@
<div class="ui attached segment">
<div class="inline required field">
<label>任务名称</label>
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255">
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
</div>

<div class="inline required field" style="{{if ((.is_benchmark_enabled) or (.is_snn4imagenet_enabled) or (.is_brainscore_enabled))}}display:block;{{else}}display:none;{{end}}">
@@ -165,7 +165,7 @@
</div>
<input id="store_category" type="hidden" name="get_benchmark_category">

<div class="inline required field cloudbrain_benchmark">
<div class="inline required field">
<label>GPU类型</label>
<select id="cloudbrain_gpu_type" class="ui search dropdown" placeholder="选择GPU类型" style='width:385px' name="gpu_type">
{{range .gpu_types}}
@@ -176,7 +176,7 @@

<div class="inline required field">
<label>镜像</label>
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image" required autofocus maxlength="255">
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image" required autofocus maxlength="254">
<datalist class="ui search" id="cloudbrain_image" style='width:385px;' name="image">
{{range .images}}
<option name="image" value="{{.Place}}">{{.PlaceView}}</option>
@@ -208,27 +208,27 @@

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


+ 13
- 1
templates/repo/cloudbrain/show.tmpl View File

@@ -6,7 +6,19 @@
{{template "base/alert" .}}

<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"}}
</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/cloudbrain">
{{$.i18n.Tr "repo.modelarts.notebook"}}
</a>
<div class="divider"> / </div>
{{with .task}}
<div class="active section">{{.JobName}}</div>
{{end}}
</div>
</h4>
<div>
<div class="ui yellow segment">


+ 9
- 0
templates/repo/contributors.tmpl View File

@@ -0,0 +1,9 @@
{{template "base/head" .}}
<div class="repository watchers">
{{template "repo/header" .}}
<div class="ui container" id="Contributors">
</div>
</div>
{{template "base/footer" .}}

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

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


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

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


+ 31
- 28
templates/repo/header.tmpl View File

@@ -92,16 +92,27 @@
{{if not .Repository.IsBeingCreated}}
<div class="ui tabular stackable menu navbar">
{{if .Permission.CanRead $.UnitTypeCode}}
<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}">
{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}}
<div class="dropdown-menu">
<a class="{{if or .PageIsViewCode .PageIsReleaseList .PageIsWiki .PageIsActivity .PageIsViewCode}}active{{end}} item hover_active" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}">
<span>{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}} <i class="dropdown icon"></i></span>
</a>
{{end}}
<div class="dropdown-content">
<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>
<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>
<a style="border: none;" class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity">
{{svg "octicon-pulse" 16}} {{.i18n.Tr "repo.activity"}}
</a>

{{if .Permission.CanRead $.UnitTypeDatasets}}
<a class="{{if .PageIsDataset}}active{{end}} item" href="{{.RepoLink}}/datasets?type=0">
{{svg "octicon-inbox" 16}} {{.i18n.Tr "datasets"}}
</a>
</div>
</div>
{{end}}


{{if .Permission.CanRead $.UnitTypeIssues}}
<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues">
@@ -109,11 +120,11 @@
</a>
{{end}}

{{if .Permission.CanRead $.UnitTypeExternalTracker}}
<!-- {{if .Permission.CanRead $.UnitTypeExternalTracker}}
<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoExternalIssuesLink}}" target="_blank" rel="noopener noreferrer">
{{svg "octicon-link-external" 16}} {{.i18n.Tr "repo.issues"}} </span>
</a>
{{end}}
{{end}} -->

{{if and .Repository.CanEnablePulls (.Permission.CanRead $.UnitTypePullRequests)}}
<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls">
@@ -121,35 +132,22 @@
</a>
{{end}}

{{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo) }}
<a 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>
{{if .Permission.CanRead $.UnitTypeDatasets}}
<a class="{{if .PageIsDataset}}active{{end}} item" href="{{.RepoLink}}/datasets?type=0">
{{svg "octicon-inbox" 16}} {{.i18n.Tr "datasets"}}
</a>
{{end}}

{{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}}
<a 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 class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity">
{{svg "octicon-pulse" 16}} {{.i18n.Tr "repo.activity"}}
</a>
{{end}}

{{if .Permission.CanRead $.UnitTypeCloudBrain}}
<a class="{{if .PageIsCloudBrain}}active{{end}} item" href="{{.RepoLink}}/cloudbrain">
{{svg "octicon-server" 16}} {{.i18n.Tr "repo.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>
{{end}}
{{if .IsSigned}}
<!-- {{if .IsSigned}}
<a class="{{if .PageIsBlockChain}}active{{end}} item " href="{{.RepoLink}}/blockchain">
{{svg "octicon-law" 16}}
{{.i18n.Tr "repo.balance"}}
</a>
{{end}}
{{end}} -->

{{template "custom/extra_tabs" .}}

@@ -243,4 +241,9 @@
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>

+ 13
- 7
templates/repo/home.tmpl View File

@@ -4,7 +4,7 @@
font-size: 1.0em;
margin-bottom: 1.0rem;
}
#contributorInfo > a:nth-child(n+25){
#contributorInfo > a:nth-child(n+26){
display:none;
}
#contributorInfo > a{
@@ -329,9 +329,15 @@

<div>
<h4 class="ui header">
{{$lenCon := len .ContributorInfo}}
{{if lt $lenCon 25 }}
<strong>贡献者 ({{len .ContributorInfo}})</strong>
{{else}}
<strong>贡献者 ({{len .ContributorInfo}}+)</strong>
{{end}}
<div class="ui right">
<a class="membersmore text grey" href="javascript:;">全部 {{svg "octicon-chevron-right" 16}}</a>
<a class="membersmore text grey" href="{{.RepoLink}}/contributors">全部 {{svg "octicon-chevron-right" 16}}</a>
</div>
</h4>
<div class="ui members" id="contributorInfo">
@@ -353,10 +359,10 @@
</div>

<script type="text/javascript">
$(document).ready(function(){
$(".membersmore").click(function(){
$("#contributorInfo > a:nth-child(n+25)").show();
});
});
// $(document).ready(function(){
// $(".membersmore").click(function(){
// $("#contributorInfo > a:nth-child(n+25)").show();
// });
// });
</script>
{{template "base/footer" .}}

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

@@ -4,7 +4,7 @@
<div class="ui container">
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.Title | RenderEmoji}}</div>


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

@@ -4,7 +4,7 @@
<div class="ui container">
<div class="ui three column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a>


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

@@ -4,7 +4,7 @@
<div class="ui container">
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a>


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

@@ -4,7 +4,7 @@
<div class="ui container">
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.Title | RenderEmoji}}</div>


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

@@ -4,7 +4,7 @@
<div class="ui container">
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues.new"}}</div>


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

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


+ 2
- 2
templates/repo/issue/view.tmpl View File

@@ -5,13 +5,13 @@
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
{{if .PageIsIssueList}}
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>
</div>
{{else}}
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>


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

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


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

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

<div class="inline field">


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

@@ -1,485 +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">

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

<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">调试任务</a>
<a class="item" href="{{.RepoLink}}/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" data-value="22">Ascend NPU</a>
</div>
</div>
<a class="ui green button" href="{{.RepoLink}}/modelarts/create">新建调试任务</a>
</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="five 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="one wide column">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="seven wide column text center">
<span style="margin-left: 10rem;">{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</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: 15px;">
<span class="fitted" style="vertical-align: middle;">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted" style="width: 90%;vertical-align: middle;margin-left: 0.4rem;">{{.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 style="vertical-align: middle;" class="{{.Status}}"></i><span 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="one wide column">
{{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="ui compact buttons" style="margin-right:10px;">
<a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}">
查看
</a>
<a class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank">
调试
</a>
<form id="stopForm-{{.JobID}}" action="{{if ne .Status "RUNNING"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/stop{{end}}" method="post" style="margin-left:-1px;">
{{$.CsrfTokenHtml}}
<a class="ui basic {{if ne .Status "RUNNING"}}disabled {{else}}blue {{end}}button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
停止
</a>
</form>
</div>

<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{if not .CanDel}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/del{{end}}" method="post">
{{$.CsrfTokenHtml}}
<a class="ui compact {{if not .CanDel}}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
删除
</a>
</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/${jobID}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
if (status != job.textContent.trim()) {
//$('#' + jobID).text(status)
//if (status == 'STOPPED') {
window.location.reload()
//}
}
}).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>

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

@@ -1,240 +0,0 @@
{{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;
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">
<div id="loadingPage">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
<div class="repository">
{{template "repo/header" .}}
<div class="repository new repo ui middle very relaxed page grid">
<div class="column">
{{template "base/alert" .}}
<div class="ui negative message" id="messageInfo">
<p></p>
</div>
<form class="ui form" id="form_id" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{.i18n.Tr "repo.cloudbrain.new"}}
</h3>
<div class="ui attached segment">
<!-- <br> -->
<div class="inline required field">
<label>任务名称</label>
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255">
</div>

<div class="inline field">
<label>数据集</label>
<input type="text" list="cloudbrain_dataset" placeholder="选择数据集" name="" id="answerInput" autofocus maxlength="36">
<datalist id="cloudbrain_dataset" class="ui search" style='width:385px' name="attachment">
{{range .attachments}}
<option name="attachment" data-value="{{.UUID}}">{{.Attachment.Name}}</option>
{{end}}
</datalist>
<input type="hidden" name="attachment" id="answerInput-hidden">
</div>

<div class="inline required field">
<label>工作环境</label>
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div>
<div class="inline required field">
<label>类型</label>
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div>
<div class="inline required field">
<label>规格</label>
<select id="cloudbrain_flavor" class="ui search dropdown" placeholder="选择规格" style='width:385px' name="flavor">
{{range .flavors}}
<option name="flavor" value="{{.Value}}">{{.Value}}</option>

{{end}}
</select>
</div>
<div class="inline required field">
<label>数据集存放路径</label>
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div>
<div class="inline field">
<label>描述</label>
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="255">
</div>
<div class="inline field">
<label></label>
<button class="ui green button">
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
</div>
</form>
</div>
</div>
</div>
{{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');

$('#messageInfo').css('display','none')

form.onsubmit = function(e){
let value_task = $("input[name='job_name']").val()
let re = /^[a-z0-9][a-z0-9-_]{1,34}[a-z0-9-]$/
let flag = re.test(value_task)
if(!flag){
$('#messageInfo').css('display','block')
let str = '只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。'
$('#messageInfo p').text(str)
return false
}
let min_value_task = value_task.toLowerCase()
$("input[name='job_name']").attr("value",min_value_task)
document.getElementById("mask").style.display = "block"
}
// 点击按钮后遮罩层显示
// function showmask() {
// document.getElementById("mask").style.display = "block"
// }

// 页面加载完毕后遮罩层隐藏
document.onreadystatechange = function() {
if (document.readyState === "complete") {
document.getElementById("mask").style.display = "none"
}
}

$('select.dropdown')
.dropdown();

$(function() {
$("#cloudbrain_job_type").change(function() {
if ($(this).val() == 'BENCHMARK') {
$(".cloudbrain_benchmark").show();
} else {
$(".cloudbrain_benchmark").hide();
}
})
})
document.querySelector('input[list]').addEventListener('input',function(e){
var input = e.target,
list = input.getAttribute('list'),
options = document.querySelectorAll('#'+list+' option'),
hiddenInput = document.getElementById(input.getAttribute('id')+'-hidden'),
inputValue = input.value;
hiddenInput.value = inputValue;
for (let i=0;i<options.length;i++){
var option = options[i]
if(option.innerText===inputValue){
hiddenInput.value = option.getAttribute('data-value');
break
}
}


})
</script>

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

@@ -280,9 +280,9 @@
<!-- 任务名 -->
<div class="six wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 15px;">
<span class="fitted" style="vertical-align: middle;">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted" style="width: 90%;vertical-align: middle;margin-left: 0.4rem;">{{.JobName}}</span>
<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>

@@ -352,11 +352,11 @@
<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 compact {{if eq .Status "RUNNING" "CREATING" "WAITING" "STARTING" "STOPPING" }}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<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 compact disabled button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a class="ui basic blue button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}


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

@@ -112,7 +112,7 @@
<!-- <br> -->
<div class="inline required field">
<label>任务名称</label>
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255">
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
</div>

<div class="inline field">
@@ -128,28 +128,28 @@

<div class="inline required field">
<label>工作环境</label>
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
</div>
<div class="inline required field">
<label>类型</label>
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
</div>
<div class="inline required field">
<label>规格</label>
<select id="cloudbrain_flavor" class="ui search dropdown" placeholder="选择规格" style='width:385px' name="flavor">
{{range .flavors}}
<option name="flavor" value="{{.Value}}">{{.Value}}</option>
<option name="flavor" value="{{.Value}}">{{.Desc}}</option>

{{end}}
</select>
</div>
<div class="inline required field">
<label>数据集存放路径</label>
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="254" readonly="readonly">
</div>
<div class="inline field">
<label>描述</label>
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="255">
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="254">
</div>
<div class="inline field">
<label></label>


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

@@ -6,7 +6,19 @@
{{template "base/alert" .}}

<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"}}
</a>
<div class="divider"> / </div>
<a class="section" href="{{.RepoLink}}/modelarts/notebook">
{{$.i18n.Tr "repo.modelarts.notebook"}}
</a>
<div class="divider"> / </div>
{{with .task}}
<div class="active section">{{.JobName}}</div>
{{end}}
</div>
</h4>
<div>
<div class="ui yellow segment">


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

@@ -1,122 +0,0 @@
{{template "base/head" .}}
<div class="repository">
{{template "repo/header" .}}
<div class="repository new repo ui middle very relaxed page grid">
<div class="column">
{{template "base/alert" .}}

<h4 class="ui header" id="vertical-segment">
<a href="javascript:window.history.back();"><i class="arrow left icon"></i>返回</a>
</h4>
<div>
<div class="ui yellow segment">
{{with .task}}
<p>任务名称: {{.JobName}}</p>
{{end}}
</div>
<div class="ui green segment">
<p>任务结果:</p>
{{with .result}}
<table class="ui celled striped table">
<tbody>
<tr>
<td class="four wide"> 状态 </td>
<td> {{.Status}} </td>
</tr>
<tr>
<td> 开始时间 </td>
<td>{{.CreateTime}}</td>
</tr>
<tr>
<td> 最后更新时间 </td>
<td>{{.LatestUpdateTime}}</td>
</tr>
</tbody>
</table>
{{end}}
</div>
<div class="ui blue segment">
{{with .result}}
<table class="ui celled striped table">
<thead>
<tr> <th colspan="2"> 配置信息 </th> </tr>
</thead>
<tbody>
<tr>
<td class="four wide"> 开发环境类型 </td>
<td>{{.Profile.DeType}}</td>
</tr>
<tr>
<td> 硬件类型 </td>
<td>{{.Profile.FlavorType}}</td>
</tr>
</tbody>
</table>

<table class="ui celled striped table">
<thead>
<tr> <th colspan="2"> 机器规格详情 </th> </tr>
</thead>
<tbody>
<tr>
<td class="four wide"> 机器规格 </td>
<td> {{.Flavor}} </td>
</tr>
<tr>
<td> 规格名称 </td>
<td>{{.FlavorDetails.Name}}</td>
</tr>
<tr>
<td> 规格销售状态 </td>
<td>{{.FlavorDetails.Status}}</td>
</tr>
<tr>
<td> 排队个数 </td>
<td>{{.FlavorDetails.QueuingNum}}</td>
</tr>
<tr>
<td> 排到队的剩余时间(秒) </td>
<td>{{.FlavorDetails.QueueLeftTime}}</td>
</tr>
<tr>
<td> 自动停止时间(秒) </td>
<td>{{.FlavorDetails.Duration}}</td>
</tr>
</tbody>
</table>

<table class="ui celled striped table" {{if eq .QueuingInfo.RemainTime 0}}hidden{{end}}>
<thead>
<tr> <th colspan="2"> 排队信息 </th> </tr>
</thead>
<tbody>
<tr>
<td> 实例状态 </td>
<td>{{.QueuingInfo.Status}}</td>
</tr>
<tr>
<td> 实例排队的开始时间 </td>
<td>{{.QueuingInfo.BeginTime}}</td>
</tr>
<tr>
<td> 排到队的剩余时间(秒) </td>
<td>{{.QueuingInfo.RemainTime}}</td>
</tr>
<tr>
<td> 实例排队的预计停止时间 </td>
<td>{{.QueuingInfo.EndTime}}</td>
</tr>
<tr>
<td> 实例在队列中的排位 </td>
<td>{{.QueuingInfo.Rank}}</td>
</tr>
</tbody>
</table>
{{end}}
</div>
</div>

</div>
</div>
</div>
{{template "base/footer" .}}

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

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


+ 120
- 94
templates/repo/modelarts/trainjob/index.tmpl View File

@@ -180,6 +180,12 @@
cursor: pointer;
pointer-events: none;
}
.fontsize14{
font-size: 14px;
}
.padding0{
padding: 0 !important;
}
</style>

<!-- 弹窗 -->
@@ -232,13 +238,13 @@

<div class="column">
<div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/modelarts/notebook">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/cloudbrain">{{$.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;">
<!-- <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>
@@ -246,7 +252,7 @@
<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>
</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}}
</div>
@@ -278,20 +284,29 @@
<!-- 表头 -->
<div class="ui grid stackable" style="background: #f0f0f0;;">
<div class="row">
<div class="five wide column">
<div class="three wide column padding0">
<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="one wide column text center padding0">
<span style="margin:0 6px">{{$.i18n.Tr "repo.modelarts.version_nums"}}</span>
</div>
<div class="two wide column">
<div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.modelarts.status"}}</span>
</div>
<div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.modelarts.createtime"}}</span>
</div>
<div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.cloudbrain_status_runtime"}}</span>
</div>
<div class="one wide column text center">
<div class="two wide column text center padding0">
<span>{{$.i18n.Tr "repo.modelarts.computing_resources"}}</span>
</div>
<div class="one wide column text center padding0">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="five wide column text center">
<span style="margin-left: 6rem;">{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
<div class="three wide column text center padding0">
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>

</div>
@@ -305,38 +320,44 @@
<div class="row">
<!-- 任务名 -->
<div class="five wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 15px;">
<span class="fitted" style="vertical-align: middle;">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted" style="width: 90%;vertical-align: middle;margin-left: 0.4rem;">{{.JobName}}</span>
<div class="three wide column padding0">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>

<div class="three wide column">
<!--任务状态 -->
<!-- <span class="ui compact button job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
{{.Status}}
</span> -->
<!-- 版本数量 -->
<div class="one wide column text center padding0">
<span style="font-size: 12px;">{{.VersionCount}} </span>
</div>
<!-- 任务状态 -->
<div class="two wide column padding0" style="padding-left: 2.2rem !important;">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<span><i id="{{.JobID}}-icon" style="vertical-align: middle;" class="{{.Status}}"></i><span id="{{.JobID}}-text" style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span>
</span>
</div>
<!-- 任务创建时间 -->
<div class="two wide column text center padding0">
<span style="font-size: 12px;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span>
</div>
<!-- <div class="two wide column">
<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>
</div>

<div class="two wide column">
<!--任务状态 -->
<!-- <span class="ui compact button job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}">
{{.Status}}
</span> -->
<span id="duration-{{.JobID}}"></span>
<!-- 任务创建时间 -->
<!-- <span style="font-size: 12px;margin-left: 0.4rem;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span> -->
<!-- 计算资源 -->
<div class="two wide column text center padding0">
<span style="font-size: 12px;">{{.ComputeResource}}</span>
</div>
<div class="one wide column text center">
<!-- 创建者 -->
<div class="one wide column text center padding0">
{{if .User.Name}}
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a>
{{else}}
@@ -344,56 +365,55 @@
{{end}}
</div>

<div class="five wide column text right">
<div class="ui compact buttons">
<!-- <a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}">
查看
</a>
<a class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank">
调试
</a> -->
<form id="stopForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/stop" method="post" style="margin-left:-1px;">
<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 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();">
<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 class="ui basic disabled button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();">
<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" style="margin-right:10px;">
<!-- 模型下载 -->
<a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}/models" target="_blank">
</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>
<!-- 接收结果 -->
<!-- <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>
</div> -->
<!-- 删除任务 -->
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}}
<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>
{{else}}
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic disabled button">
{{$.i18n.Tr "repo.stop"}}
</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 compact {{if or (eq .Status "RUNNING") (eq .Status "INIT") (eq .Status "CREATING") (eq .Status "WAITING") }}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<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 class="ui compact disabled button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<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>
{{end}}
</form>
</div>



</div>
</div>
{{end}} {{template "base/paginate" .}}
@@ -435,6 +455,8 @@
{{template "base/footer" .}}

<script>

console.log({{.Tasks}})
// 调试和评分新开窗口
function stop(obj) {
if (obj.style.color != "rgb(204, 204, 204)") {
@@ -484,11 +506,12 @@
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}`, (data) => {
const versionname = job.dataset.version
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => {
console.log(data)
const duration = data.JobDuration
const jobID = data.JobID
let train_duration = runtime(duration)
$('#duration-'+jobID).text(train_duration)
$('#duration-'+jobID).text(duration)
})
})
@@ -501,50 +524,24 @@
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
const versionname = job.dataset.version
if (job.textContent.trim() == 'IMAGE_FAILED' || job.textContent.trim() == 'SUBMIT_FAILED' || job.textContent.trim() == 'DELETE_FAILED'
|| job.textContent.trim() == 'KILLED' || job.textContent.trim() == 'COMPLETED' || job.textContent.trim() == 'FAILED'
|| job.textContent.trim() == 'CANCELED' || job.textContent.trim() == 'LOST') {
return
}

$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}`, (data) => {
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
const duration = data.JobDuration
$('#duration-'+jobID).text(duration)
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)

}
if(status==="RUNNING"){
$('#model-debug-'+jobID).removeClass('disabled')
$('#model-debug-'+jobID).addClass('blue')
let train_duration = runtime(duration)
$('#duration-'+jobID).text(train_duration)

}
if(status!=="RUNNING"){
$('#model-debug-'+jobID).removeClass('blue')
$('#model-debug-'+jobID).addClass('disabled')

}
if(status!=="KILLED" || 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=="KILLED" || status=="FAILED" || status=="KILLING"){
$('#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')
}
}).fail(function(err) {
console.log(err);
});
@@ -580,7 +577,36 @@
modal.style.display = "none";
}
}
function stopVersion(version_name,jobID){
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/train-job/'+jobID+'/stop_version'
$.post(url,{version_name:version_name},(data)=>{
if(data.StatusOK===0){
$('#'+version_name+'-stop').removeClass('blue')
$('#'+version_name+'-stop').addClass('disabled')
refreshStatus(version_name,jobID)
}
}).fail(function(err) {
console.log(err);
});
}
function refreshStatus(version_name,jobID){

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


}).fail(function(err) {
console.log(err);
});
}
// 显示弹窗,弹出相应的信息
function showmask() {
$('#imageModal').css('display', 'none')


+ 78
- 94
templates/repo/modelarts/trainjob/new.tmpl View File

@@ -103,7 +103,9 @@
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.left2{
margin-left: -2px;
}
@-webkit-keyframes sk-stretchdelay {
0%,
40%,
@@ -153,95 +155,81 @@
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update">
<input type="hidden" id="ai_engine_name" name="engine_names" value="">
<input type="hidden" id="ai_flaver_name" name="flaver_names" value="">
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4>
<div class="required unite min_title inline field">
<label>{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label>
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="255">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label>
<input style="width: 60%;" name="job_name" id="trainjob_job_name" placeholder={{.i18n.Tr "repo.modelarts.train_job.job_name"}} value="{{.job_name}}" tabindex="3" autofocus required maxlength="254">
</div>
<div class="unite min_title inline field">
<label for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label>
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="255" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 256)"></textarea>
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label>
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea>
</div>
<!-- <h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}</h4>
<div class="inline field">
<label>{{.i18n.Tr "repo.modelarts.train_job.fast_parameter_setting"}}</label>
<span>
{{.i18n.Tr "repo.modelarts.train_job.fast_parameter_setting_config"}}
<a class="item active parameter_config">{{.i18n.Tr "repo.modelarts.train_job.fast_parameter_setting_config_link"}}</a>
</span>
</div> -->
<div class="ui divider"></div>

<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}:</h4>

<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.code_version"}}</label>
<select class="ui dropdown width80 left2" id="code_version" name="branch_name">
{{if .branch_name}}
<option name="branch_name" value="{{.branch_name}}">{{.branch_name}}</option>
{{range $k, $v :=.Branches}}
{{ if ne $v $.branch_name }}
<option name="branch_name" value="{{$v}}">{{$v}}</option>
{{end}}
{{end}}
{{else}}
<option name="branch_name" value="{{.BranchName}}">{{.BranchName}}</option>
{{range $k, $v :=.Branches}}
{{ if ne $v $.BranchName }}
<option name="branch_name" value="{{$v}}">{{$v}}</option>
{{end}}
{{end}}
{{end}}
</select>
</div>



<div class="required unite min_title inline fields" style="width: 90%;">
<label>{{.i18n.Tr "repo.modelarts.train_job.AI_driver"}}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label>
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.AI_driver"}}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label>
<div class="field" style="flex: 1.5;">
<select class="ui dropdown width" id="trainjob_engines" >
{{range .engines}}
<option value="{{.Value}}">{{.Value}}</option>
{{end}}
</select>

</div>
<div class="field" style="flex: 2;">

<div class="field" style="flex: 2;" id="engine_name">
<select class="ui dropdown width" id="trainjob_engine_versions" style='width: 100%;' name="engine_id">
{{range .engine_versions}}
<option name="engine_id" value="{{.ID}}">{{.Value}}</option>
{{end}}
</select>
</div>

</div>
<!-- <div class="required inline field">
<label>{{.i18n.Tr "repo.modelarts.train_job.algorithm_origin"}}</label>
<div class="ui top attached tabular menu">
<a class="item active" data-tab="frame">{{svg "octicon-repo" 16}}{{.i18n.Tr "repo.modelarts.train_job.frames"}}</a>
</div>
<div class="ui bottom attached tab active segment" data-tab="frame">
<div class="required field">
<label>{{.i18n.Tr "repo.modelarts.train_job.AI_driver"}}</label>
<div class="two fields">
<div class="field">
<select class="ui search dropdown" id="trainjob_engines" style='width:385px'>
{{range .engines}}
<option value="{{.Value}}">{{.Value}}</option>
{{end}}
</select>
</div>
<div class="field">
<select class="ui search dropdown" id="trainjob_engine_versions" style='width:385px' name="engine_id">
{{range .engine_versions}}
<option name="engine_id" value="{{.ID}}">{{.Value}}</option>
{{end}}
</select>
</div>
</div>
</div>
<div class="inline required field">
<label>{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
<input name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255">
<span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
</span>
</div>
</div>
</div> -->

<div class="inline unite min_title field required">
<label>{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
{{if .bootFile}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="255" >
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.bootFile}}" tabindex="3" autofocus required maxlength="254" >
{{else}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="255" >
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254" >
{{end}}
<span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
</span>
</div>
<div class="required unite min_title inline field">
<label>{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label>
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label>
<select class="ui dropdown width80" id="trainjob_datasets" name="attachment" placeholder="选择数据集">
{{if $.uuid}}
<option name="attachment" value="{{$.uuid}}">{{$.datasetName}}</option>
@@ -254,19 +242,32 @@
</div>
<div class="inline unite min_title field">
<label>{{.i18n.Tr "repo.modelarts.train_job.run_parameter"}}</label>
<!-- <i class="plus square outline icon"></i> -->
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.run_parameter"}}</label>
<span id="add_run_para" style="margin-left: 0.5rem;cursor:pointer;color: rgba(3, 102, 214, 100);font-size: 14px;line-height: 26px;font-family: SourceHanSansSC-medium;"><i class="plus square outline icon"></i>{{.i18n.Tr "repo.modelarts.train_job.add_run_parameter"}}</span>
<input id="store_run_para" type="hidden" name="run_para_list">
<div class="dynamic field" style="margin-top: 1rem;"></div>
<!-- <div class="dynamic field">
</div> -->
<div class="dynamic field" style="margin-top: 1rem;">
{{if ne 0 (len .params)}}
{{range $k ,$v := .params}}
<div class="two fields width85" id="para{{$k}}">
<div class="field">
<input type="text" name="shipping_first-name" value={{$v.Label}} required>
</div>
<div class="field">
<input type="text" name="shipping_last-name" value={{$v.Value}} required>
</div>
<span>
<i class="trash icon"></i>
</span>

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

<!-- <h4 class="ui dividing header">{{.i18n.Tr "repo.modelarts.train_job.resource_setting"}}</h4> -->
<div class="required field " style="display: none;">
<label>{{.i18n.Tr "repo.modelarts.train_job.resource_pool"}}</label>
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.resource_pool"}}</label>
<select class="ui dropdown" id="trainjob_resource_pool" style='width:385px' name="pool_id">
{{range .resource_pools}}
<option value="{{.ID}}">{{.Value}}</option>
@@ -275,7 +276,7 @@
</div>

<div class="required grouped fields" style="display: none;">
<label for="resource_type">{{.i18n.Tr "repo.modelarts.train_job.resource_type"}}</label>
<label style="font-weight: normal;" for="resource_type">{{.i18n.Tr "repo.modelarts.train_job.resource_type"}}</label>
<div class="field">
<div class="ui grid">
<div class="column">
@@ -290,8 +291,8 @@
</div>
</div>

<div class="required unite min_title inline field">
<label>{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<div class="required unite min_title inline field" id="flaver_name">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width81" id="trainjob-flavor" style='width:385px' name="flavor">
{{range .flavor_infos}}
<option name="flavor" value="{{.Code}}">{{.Value}}</option>
@@ -299,39 +300,15 @@
</select>
</div>
<div class="inline required unite min_title field">
<label>{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label>
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label>
<div class="ui labeled input" style="width: 5%;">
<!-- <span class="min"><i class="minus icon"></i></span> -->
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255" value="1" readonly>
<input style="border-radius: 0;text-align: center;" name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="254" value="1" readonly>
<!-- <span class="add"><i class="plus icon"></i></span> -->
</div>
<!-- <input name="work_server_number" id="trainjob_work_server_num" tabindex="3" autofocus required maxlength="255"> -->
</div>
<!--
<div class="inline field">
<div class="ui save checkbox">
<input name="is_save_para" type="checkbox">
<label>{{.i18n.Tr "repo.modelarts.train_job.query_whether_save_parameter"}}
<span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.save_helper"}} data-position="right center" data-variation="mini"></i>
</span>
</label>
</div>
</div>
<div class="disabled field" id="save_para">
<div class="field">
<label>{{.i18n.Tr "repo.modelarts.train_job.job_parameter_name"}}</label>
<input name="parameter_template_name" id="parameter_template_name" tabindex="3" autofocus maxlength="255">
</div>
<div class="field">
<label for="parameter_description">{{.i18n.Tr "repo.modelarts.train_job.parameter_description"}}</label>
<textarea id="parameter_description" name="parameter_description" rows="2"></textarea>

</div>
</div>
-->
<div class="inline unite min_title field">
<button class="ui create_train_job green button">
@@ -598,8 +575,15 @@
msg = JSON.stringify(msg)
$('#store_run_para').val(msg)
}
function get_name(){
let name1=$("#engine_name .text").text()
let name2=$("#flaver_name .text").text()
$("input#ai_engine_name").val(name1)
$("input#ai_flaver_name").val(name2)

}
$('.ui.create_train_job.green.button').click(function(e) {
get_name()
send_run_para()
validate()
})

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

@@ -38,7 +38,7 @@
<!-- 任务名 -->
<div class="five wide column">
<a class="title" href="{{$.Link}}/{{.JobID}}">
<span class="fitted">{{svg "octicon-tasklist" 16}}</span>
<span class="fitted">{{.JobName}}</span>
</a>
</div>


+ 702
- 145
templates/repo/modelarts/trainjob/show.tmpl View File

@@ -1,139 +1,470 @@
{{template "base/head" .}}
<style>
.according-panel-heading{
box-sizing: border-box;
padding: 8px 16px;
color: #252b3a;
background-color: #f2f5fc;
line-height: 1.5;
cursor: pointer;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.accordion-panel-title {
margin-top: 0;
margin-bottom: 0;
color: #252b3a;
}
.accordion-panel-title-content{
vertical-align: middle;
display: inline-block;
width: calc(100% - 32px);
cursor: default;
}
.acc-margin-bottom {
margin-bottom: 5px;
}
.title_text {
font-size: 12px;
}
.ac-display-inblock {
display: inline-block;
}
.cti-mgRight-sm {
margin-right: 8px;
}
.ac-text-normal {
font-size: 14px;
color: #575d6c;
}
.uc-accordionTitle-black {
color: #333;
}
.accordion-border{
border:1px solid #cce2ff;
}
.padding0{
padding: 0 !important;
}
.content-pad{
padding: 15px 35px;
}
.content-margin{
margin:10px 5px ;
}
.tab_2_content {
min-height: 360px;
margin-left: 10px;
}
.ac-grid {
display: block;
*zoom: 1;
}
.ac-grid-col {
float: left;
width: 100%;
}
.ac-grid-col2 .ac-grid-col {
width: 50%;
}
.ti-form {
text-align: left;
max-width: 100%;
vertical-align: middle;
}
.ti-form>tbody {
font-size: 12px;
}
.ti-form>tbody, .ti-form>tbody>tr {
vertical-align: inherit;
}
.ti-text-form-label {
padding-bottom: 20px;
padding-right: 20px;
color: #8a8e99;
font-size: 12px;
white-space: nowrap !important;
width: 80px;
line-height: 30px;
}
.ti-text-form-content{
line-height: 30px;
padding-bottom: 20px;
}
.ti-form>tbody>tr>td {
vertical-align: top;
white-space: normal;
}
td, th {
padding: 0;
}
.ac-grid-col .text-span {
width: 450px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.redo-color{
color: #3291F8;
}
.ti-action-menu-item:not(:last-child){
margin-right: 10px;
padding-right: 11px;
text-decoration: none!important;
color: #526ecc;
cursor: pointer;
display: inline-block;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
position: relative;
}
.ti-action-menu-item:not(:last-child):after {
content: "";
display: inline-block;
position: absolute;
height: 12px;
right: 0;
top: 50%;
-webkit-transform: translateY(-6px);
-ms-transform: translateY(-6px);
-o-transform: translateY(-6px);
transform: translateY(-6px);
border-right: 1px solid #dfe1e6;
}
.text-width80{
width: 100px;
line-height: 30px;
}
.border-according{
border: 1px solid #dfe1e6;
}
.disabled {
cursor: default;
pointer-events: none;
color: rgba(0,0,0,.6) !important;
opacity: .45 !important;
}
.pad20{
border:0px !important;
}
.model_file_bread{
margin-bottom: -0.5rem !important;
padding-left: 1rem;
padding-top: 0.5rem ;
}
</style>
<div class="repository">
{{template "repo/header" .}}
<div class="repository new repo ui middle very relaxed page grid">
<div class="column">
{{template "base/alert" .}}
<h4 class="ui top attached header">
<div class="ui two column grid">
<div class="column">
{{$.i18n.Tr "repo.modelarts.version_manage"}}
<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"}}
</a>
<div class="divider"> / </div>
<a class="section" href="{{$.RepoLink}}/modelarts/train-job">
{{$.i18n.Tr "repo.modelarts.train_job"}}
</a>
<div class="divider"> / </div>
<div class="active section">{{.jobName}}</div>
</div>
</h4>
{{range $k ,$v := .version_list_task}}
<div class="ui accordion border-according" id="accordion{{.VersionName}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<div class="{{if eq $k 0}}active{{end}} title padding0">
<div class="according-panel-heading">
<div class="accordion-panel-title">
<i class="dropdown icon"></i>
<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}}
<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}}
<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}}
<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>
{{end}}
</div>
<div class="ac-display-inblock title_text acc-margin-bottom">
<span class="cti-mgRight-sm">{{TimeSinceUnix1 .Cloudbrain.CreatedUnix}}</span>
<span class="cti-mgRight-sm"> {{$.i18n.Tr "repo.modelarts.current_version"}}:{{.VersionName}}</span>
<span class="cti-mgRight-sm"> {{$.i18n.Tr "repo.modelarts.parent_version"}}:{{.PreVersionName}}</span>
<span class="cti-mgRight-sm">{{$.i18n.Tr "repo.modelarts.status"}}:
<span id="{{.VersionName}}-status-span"><i id="icon" style="vertical-align: middle;" class="{{.Status}}"></i><span id="text" style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span>
</span>
<span class="cti-mgRight-sm">{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}}:</span>
<span class="cti-mgRight-sm uc-accordionTitle-black" id="{{.VersionName}}-duration-span">{{.TrainJobDuration}}</span>
<span data-tooltip="刷新" style="cursor: pointer;" data-inverted="" onclick="refreshStatus({{.VersionName}})"><i class="redo icon redo-color"></i></span>

</div>
</span>
</span>
</div>
<div class="column right aligned">
<a href="javascript:window.history.back();">{{svg "octicon-reply" 16}}{{$.i18n.Tr "repo.modelarts.back"}}</a>
</div>
</div>
<div class="{{if eq $k 0}}active{{end}} content">
<div class="content-pad">
<div class="ui pointing secondary menu" style="border-bottom: 1px solid rgba(34,36,38,.15);">
<a class="active item" data-tab="first{{$k}}">{{$.i18n.Tr "repo.modelarts.train_job.config"}}</a>
<a class="item" data-tab="second{{$k}}" onclick="loadLog({{.VersionName}})">{{$.i18n.Tr "repo.modelarts.log"}}</a>
<a class="item" data-tab="third{{$k}}" onclick="loadModelFile({{.VersionName}},'','','init')">{{$.i18n.Tr "repo.model_download"}}</a>
</div>
</div>
</h4>
<div class="ui tab active" data-tab="first{{$k}}">
<div style="padding-top: 10px;">
<div class="tab_2_content">
<div class="ac-grid ac-grid-col2">
<div class="ac-grid-col">
<table class="ti-form">
<tbody class="ti-text-form">
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.cloudbrain_task"}}
</td>

<div class="ui attached segment">
<div class="ui style accordion">
<div class="title active">
<i class="dropdown icon"></i>
{{$.i18n.Tr "repo.modelarts.train_job.version"}}
</div>
<div class="content active">
<div class="ui container">
<div class="ui top attached tabular menu">
<a class="item active" data-tab="configs">{{$.i18n.Tr "repo.modelarts.train_job.config"}}</a>
<a class="item logs" data-tab="logs">{{$.i18n.Tr "repo.modelarts.log"}}</a>
<!-- <a class="item" data-tab="resources">资源占用情况</a> -->
</div>
<div class="ui bottom attached tab segment active" data-tab="configs">
<div>
<div class="ui yellow segment">
<table class="ui celled striped table">
<thead>
<tr> <th colspan="2"> {{.i18n.Tr "repo.modelarts.train_job.basic_info"}} </th> </tr>
</thead>
<tbody>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.job_name"}} </td>
<td>{{.result.JobName}}</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.job_status"}} </td>
<td>{{.result.Status}}</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.version"}} </td>
<td>{{.result.VersionName}}</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.start_time"}} </td>
<td>{{.result.CreateTime}}</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.dura_time"}} </td>
<td>{{.result.TrainJobDuration}}</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.description"}} </td>
<td>{{.result.Description}}</td>
</tr>
</tbody>
</table>
</div>
<div class="ui green segment">
<table class="ui celled striped table">
<thead>
<tr> <th colspan="2"> {{.i18n.Tr "repo.modelarts.train_job.parameter_setting_info"}} </th> </tr>
</thead>
<tbody>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.AI_driver"}} </td>
<td>{{.result.EngineName}} | {{.result.EngineVersion}}</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.start_file"}}</td>
<td>{{.result.BootFileUrl}}</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.JobName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.status"}}
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-status">
{{.Status}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.run_version"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.VersionName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.start_time"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
<span style="font-size: 12px;" class="">{{TimeSinceUnix1 .Cloudbrain.CreatedUnix}}</span>
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" id="{{.VersionName}}-duration">
{{.TrainJobDuration}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.standard"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.FlavorName}}
</div>
</td>
</tr>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.WorkServerNumber}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="ac-grid-col">
<table class="ti-form">
<tbody class="ti-text-form">
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.EngineName}}
</div>
</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.dataset"}} </td>
<td>{{.result.DatasetName}}</td>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.code_version"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.BranchName}}
</div>
</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.run_parameter"}} </td>
<td>{{.result.Parameter}}</td>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.BootFile}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="ui blue segment">
<table class="ui celled striped table">
<thead>
<tr> <th colspan="2"> {{.i18n.Tr "repo.modelarts.train_job.resource_setting_info"}} </th> </tr>
</thead>
<tbody>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.resource_pool"}} </td>
<td>{{.result.PoolName}}</td>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.train_dataset"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w">
{{.DatasetName}}
</div>
</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</td>
<td>{{.result.WorkServerNum}}</td>
<tr class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80" >
{{$.i18n.Tr "repo.modelarts.train_job.run_parameter"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" title="{{.Parameters}}">
{{.Parameters}}
</div>
</td>
</tr>
<tr>
<td class="four wide"> {{.i18n.Tr "repo.modelarts.train_job.NAS_mount_path"}} </td>
<td>{{.result.NasMountPath}}</td>
<!-- <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 class="ti-no-ng-animate">
<td class="ti-no-ng-animate ti-text-form-label text-width80">
{{$.i18n.Tr "repo.modelarts.train_job.description"}}
</td>
<td class="ti-text-form-content">
<div class="text-span text-span-w" title="{{.Cloudbrain.Description}}">
{{.Cloudbrain.Description}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="ui bottom attached tab segment" data-tab="logs">
<div class="ui message" style="display: none;">
<div class="header">
</div>
</div>
<div class="ui top attached segment" style="background: #f0f0f0;">
<div class="center aligned">
<label>{{$.i18n.Tr "repo.modelarts.log"}}:</label>
<span class="fitted file_name">{{.log_file_name}}</span>
<input type="hidden" name="file_name" value={{.log_file_name}}>
<input type="hidden" name="start_line" value={{.log.StartLine}}>
<input type="hidden" name="end_line" value={{.log.EndLine}}>
</div>
</div>
<div class="ui attached segment log" style="height: 300px !important; overflow: auto;">
<pre>{{.log.Content}}</pre>
</div>
</div>
</div>
</div>
<div class="ui tab" data-tab="second{{$k}}">
<div>
<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> -->
<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>
</div>
</div>
</div>
<div class="ui tab" data-tab="third{{$k}}">
<input type="hidden" name="model{{.VersionName}}" value="-1">
<input type="hidden" name="modelback{{.VersionName}}" value="-1">
<div class='ui breadcrumb model_file_bread' id='file_breadcrumb{{.VersionName}}'>
<div class="active section">{{.VersionName}}</div>
<div class="divider"> / </div>

</div>
<div id="dir_list{{.VersionName}}">
</div>
</div>

</div>
</div>
</div>
{{end}} {{template "base/paginate" .}}
</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>
@@ -142,59 +473,285 @@
{{template "base/footer" .}}

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

var userName
var repoPath
var jobID
$(document).ready(function(){
var url = window.location.href;
var urlArr = url.split('/')
$('.ui.accordion').accordion({selector:{trigger:'.icon'}});
});
$(document).ready(function(){
$('.secondary.menu .item').tab();
});
let userName
let repoPath
let jobID
$(document).ready(function(){
let url = window.location.href;
let urlArr = url.split('/')
userName = urlArr.slice(-5)[0]
repoPath = urlArr.slice(-4)[0]
jobID = urlArr.slice(-1)[0]
})
function stopBubbling(e) {
e = window.event || e;
if (e.stopPropagation) {
e.stopPropagation(); //阻止事件 冒泡传播
} else {
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);
$(".log").scroll(function () {
var scrollTop = $(this)[0].scrollTop; // 滚动距离
var scrollHeight = $(this)[0].scrollHeight; // 文档高度
var divHeight = $(this).height(); // 可视区高度
var file_name = $('input[name=file_name]').val()
function renderSize(value){
if(null==value||value==''){
return "0 Bytes";
}
var unitArr = new Array("Bytes","KB","MB","GB","TB","PB","EB","ZB","YB");
var index=0;
var srcsize = parseFloat(value);
index=Math.floor(Math.log(srcsize)/Math.log(1024));
var size =srcsize/Math.pow(1024,index);
size=size.toFixed(2);//保留的小数位数
return size+unitArr[index];
}
function loadJobStatus() {
$(".ui.accordion.border-according").each((index, job) => {
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
const versionname = job.dataset.version
if (job.textContent.trim() == 'IMAGE_FAILED' || job.textContent.trim() == 'SUBMIT_FAILED' || job.textContent.trim() == 'DELETE_FAILED'
|| job.textContent.trim() == 'KILLED' || job.textContent.trim() == 'COMPLETED' || job.textContent.trim() == 'FAILED'
|| job.textContent.trim() == 'CANCELED' || job.textContent.trim() == 'LOST') {
return
}
let stopArray=["KILLED","FAILED","START_FAILED","KILLING","COMPLETED"]
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => {
$(`#${versionname}-duration-span`).text(data.JobDuration)
$(`#${versionname}-status-span span`).text(data.JobStatus)
$(`#${versionname}-status-span i`).attr("class",data.JobStatus)
// detail status and duration
$('#'+versionname+'-duration').text(data.JobDuration)
$('#'+versionname+'-status').text(data.JobStatus)
if(stopArray.includes(data.JobStatus)){
$('#'+versionname+'-stop').addClass('disabled')
}
}).fail(function(err) {
console.log(err);
});
});
};

function refreshStatus(version_name){
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}?version_name=${version_name}`,(data)=>{
// header status and duration
$(`#${version_name}-duration-span`).text(data.JobDuration)
$(`#${version_name}-status-span span`).text(data.JobStatus)
$(`#${version_name}-status-span i`).attr("class",data.JobStatus)
// detail status and duration
$('#'+version_name+'-duration').text(data.JobDuration)
$('#'+version_name+'-status').text(data.JobStatus)
loadLog(version_name)

if(parseInt(scrollTop) + divHeight + 29 == scrollHeight){
var end_line = $('input[name=end_line]').val()
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/log?file_name=${file_name}&base_line=${end_line}&order=desc`, (data) => {
if (data.lines == 0){
$('.header').text('您已翻阅至日志底部')
$('.message').css('display', 'block')

}).fail(function(err) {
console.log(err);
});
stopBubbling(arguments.callee.caller.arguments[0])
}
function deleteVersion(version_name){
stopBubbling(arguments.callee.caller.arguments[0])
let flag = 1;
$('.ui.basic.modal').modal({
onDeny: function() {
flag = false
},
onApprove: function() {
$.post(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/del_version`,{version_name:version_name},(data)=>{
$('#accordion'+version_name).remove()
}).fail(function(err) {
console.log(err);
});
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
}
function stopVersion(version_name){
stopBubbling(arguments.callee.caller.arguments[0])
$.post(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/stop_version`,{version_name:version_name},(data)=>{
if(data.StatusOK===0){
$('#'+version_name+'-stop').addClass('disabled')
refreshStatus(version_name)
}
}).fail(function(err) {
console.log(err);
});
}
function loadLog(version_name){
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/log?version_name=${version_name}&lines=50&order=asc`, (data) => {
$('input[name=end_line]').val(data.EndLine)
$('input[name=start_line]').val(data.StartLine)
$(`#log_file${version_name}`).text(data.Content)
}).fail(function(err) {
console.log(err);
});
}
function loadModelFile(version_name,parents,filename,init){
parents = parents || ''
filename = filename || ''
init = init || ''
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/model_list?version_name=${version_name}&parentDir=${parents}`, (data) => {
$(`#dir_list${version_name}`).empty()
renderDir(data,version_name)
if(init==="init"){
$(`input[name=model${version_name}]`).val("")
$(`input[name=modelback${version_name}]`).val(version_name)
$(`#file_breadcrumb${version_name}`).empty()
let htmlBread = ""
htmlBread += `<div class='active section'>${version_name}</div>`
htmlBread += "<div class='divider'> / </div>"
$(`#file_breadcrumb${version_name}`).append(htmlBread)
}else{
renderBrend(version_name,parents,filename,init)
}
}).fail(function(err) {
console.log(err,version_name);
});
}
function renderBrend(version_name,parents,filename,init){
if(init=="folder"){
let htmlBrend = ""
let sectionName=$(`#file_breadcrumb${version_name} .active.section`).text()
let parents1 = $(`input[name=model${version_name}]`).val()
let filename1 = $(`input[name=modelback${version_name}]`).val()
if(parents1===""){
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','','init')">${sectionName}</a>`)
}else{
$(`#file_breadcrumb${version_name} .active.section`).replaceWith(`<a class='section' onclick="loadModelFile('${version_name}','${parents1}','${filename1}')">${sectionName}</a>`)
}
htmlBrend += `<div class='active section'>${filename}</div>`
htmlBrend += "<div class='divider'> / </div>"
$(`#file_breadcrumb${version_name}`).append(htmlBrend)
$(`input[name=model${version_name}]`).val(parents)
$(`input[name=modelback${version_name}]`).val(filename)
}else{
$(`input[name=model${version_name}]`).val(parents)
$(`input[name=modelback${version_name}]`).val(filename)
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).nextAll().remove()
$(`#file_breadcrumb${version_name} a.section:contains(${filename})`).replaceWith(`<div class='active section'>${filename}</div>`)
$(`#file_breadcrumb${version_name} div.section:contains(${filename})`).append("<div class='divider'> / </div>")
}
}
function 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;'>"
html += "<div class='row' style='padding: 0;'>"
html += "<div class='ui sixteen wide column' style='padding:1rem;'>"
html += "<div class='dir list'>"
html += "<table id='repo-files-table' class='ui single line table pad20'>"
html += '<tbody>'
// html += "</tbody>"
for(let i=0;i<data.Dirs.length;i++){
let dirs_size = renderSize(data.Dirs[i].Size)
html += "<tr>"
html += "<td class='name six wid'>"
html += "<span class='truncate'>"
html += "<span class='octicon octicon-file-directory'>"
html += "</span>"
if(data.Dirs[i].IsDir){
html += `<a onclick="loadModelFile('${version_name}','${data.Dirs[i].ParenDir}','${data.Dirs[i].FileName}','folder')">`
html += "<span class='fitted'><i class='folder icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}else{
html += `<a href="${location.href}/model_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">`
html += "<span class='fitted'><i class='file icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}
html += '</a>'
html += "</span>"
html += "</td>"
html += "<td class='message seven wide'>"
html += "<span class='truncate has-emoji'>"+ `${dirs_size}` + "</span>"
html += "</td>"

html += "<td class='text right age three wide'>"
html += "<span class='truncate has-emoji'>" + data.Dirs[i].ModTime + "</span>"
html += "</td>"
html += "</tr>"
}
html += "</tbody>"
html += "</table>"
html += "</div>"
html += "</div>"
html += "</div>"
html += "</div>"
$(`#dir_list${version_name}`).append(html)
}
function logScroll(version_name) {
let container = document.querySelector(`#log${version_name}`)
let scrollTop = container.scrollTop
let scrollHeight = container.scrollHeight
let clientHeight = container.clientHeight
if(parseInt(scrollTop) + clientHeight == scrollHeight || parseInt(scrollTop) + clientHeight +1 == scrollHeight || parseInt(scrollTop) + clientHeight - 1 == scrollHeight){
let end_line = $(`#log${version_name} input[name=end_line]`).val()
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/log?version_name=${version_name}&base_line=${end_line}&lines=50&order=desc`, (data) => {
if (data.Lines == 0){
$(`.message${version_name} #header`).text('您已翻阅至日志底部')
$(`.message${version_name}`).css('display', 'block')
setTimeout(function(){
$('.message').css('display', 'none')
$(`.message${version_name}`).css('display', 'none')
}, 1000)
}else{
$('input[name=end_line]').val(data.EndLine)
$('.log').append('<pre>' + data.Content)
if(end_line===data.EndLine){
return
}
else{
$(`#log${version_name} input[name=end_line]`).val(data.EndLine)
$(`#log${version_name}`).append('<pre>' + data.Content)
}
}
}).fail(function(err) {
console.log(err);
});
}
if(scrollTop == 0){
var start_line = $('input[name=start_line]').val()
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/log?file_name=${file_name}&base_line=${start_line}&order=asc`, (data) => {
if (data.lines == 0){
$('.header').text('您已翻阅至日志顶部')
$('.message').css('display', 'block')
let start_line = $(`#log${version_name} input[name=start_line]`).val()
$.get(`/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/log?version_name=${version_name}&base_line=${start_line}&lines=50&order=asc`, (data) => {
if (data.Lines == 0){
$(`.message${version_name} #header`).text('您已翻阅至日志顶部')
$(`.message${version_name}`).css('display', 'block')
setTimeout(function(){
$('.message').css('display', 'none')
$(`.message${version_name}`).css('display', 'none')
}, 1000)
}else{
$('input[name=start_line]').val(data.StartLine) //如果变动就改变所对应的值
$(".log").prepend('<pre>' + data.Content)
$(`#log${version_name} input[name=start_line]`).val(data.StartLine) //如果变动就改变所对应的值
$(`#log${version_name}`).prepend('<pre>' + data.Content)
}
}).fail(function(err) {
console.log(err);
});
}
})
}
</script>

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

@@ -0,0 +1,634 @@
{{template "base/head" .}}
<style>

.unite{
font-family: SourceHanSansSC-medium !important;
color: rgba(16, 16, 16, 100) !important;
}

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

}
.width{
width:100% !important;
}
.width80{
width: 80.7% !important;
margin-left: 10px;
}
.width85{
width: 85% !important;
margin-left: 4.5rem !important;
}
.width81{
margin-left: 1.5rem;
width: 81% !important;
}

.add{font-size: 18px;
padding: 0.5rem;
border: 1px solid rgba(187, 187, 187, 100);
border-radius: 0px 5px 5px 0px;
line-height: 21px;
text-align: center;
color: #C2C7CC;
}
.min{
font-size: 18px;
padding: 0.5rem;
border: 1px solid rgba(187, 187, 187, 100);
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);
}
}

</style>
<!-- <div class="ui page dimmer">
<div class="ui text loader">{{.i18n.Tr "loading"}}</div>
</div> -->
<div id="mask">
<div id="loadingPage">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
<div class="repository">
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{.i18n.Tr "repo.modelarts.train_job.new"}}
</h4>
<div class="ui attached segment">
<!-- equal width -->
<form class="ui form" action="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update">
{{if .version_name}}
<input type="hidden" name="version_name" value="{{.version_name}}">
{{else}}
<input type="hidden" name="version_name" value="">
{{end}}
<input type="hidden" id="ai_engine_name" name="engine_names" value="">
<input type="hidden" id="ai_flaver_name" name="flaver_names" value="">
<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.basic_info"}}:</h4>
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.job_name"}}</label>
<input type="hidden" style="width: 60%;" name="job_name" id="trainjob_job_name" value="{{.job_name}}">
<input style="width: 60%;" value="{{.job_name}}" tabindex="3" disabled >
</div>
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.parents_version"}}</label>
{{if .version_name}}
<input style="width: 60%;" value="{{.version_name}}" tabindex="3" disabled >
{{else}}
<input id="parents_version" style="width: 60%;" value="" tabindex="3" disabled >
{{end}}
</div>
<div class="unite min_title inline field">
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label>
<textarea style="width: 80%;" id="description" value="{{.description}}" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 256)">{{.description}}</textarea>
</div>
<div class="ui divider"></div>

<h4 class="unite title ui header ">{{.i18n.Tr "repo.modelarts.train_job.parameter_setting"}}:</h4>

<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.code_version"}}</label>
<select class="ui dropdown width80 left2" id="code_version" name="branch_name">
{{if .branch_name}}
<option name="branch_name" value="{{.branch_name}}">{{.branch_name}}</option>
{{end}}
{{range $k, $v :=.branches}}
{{if ne $.branch_name $v}}
<option name="branch_name" value="{{$v}}">{{$v}}</option>
{{end}}
{{end}}
</select>
</div>



<div class="required unite min_title inline fields" style="width: 90%;">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.AI_driver"}}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label>
<div class="field" style="flex: 1.5;">
<select class="ui dropdown width" id="trainjob_engines" >
{{range .engines}}
<option value="{{.Value}}">{{.Value}}</option>
{{end}}
</select>
</div>

<div class="field" style="flex: 2;" id="engine_name">
<select class="ui dropdown width" id="trainjob_engine_versions" style='width: 100%;' name="engine_id">
{{if .engine_id}}
<option name="engine_id" value="{{.engine_id}}">{{.engine_name}}</option>
{{end}}
{{range .engine_versions}}
{{if ne $.engine_id .ID}}
<option name="engine_id" value="{{.ID}}">{{.Value}}</option>
{{end}}
{{end}}
</select>

</div>

</div>

<div class="inline unite min_title field required">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.start_file"}}</label>
{{if .boot_file}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="{{.boot_file}}" tabindex="3" autofocus required maxlength="254" >
{{else}}
<input style="width: 33.5%;" name="boot_file" id="trainjob_boot_file" value="" tabindex="3" autofocus required maxlength="254" >
{{end}}
<span>
<i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i>
</span>
</div>
<div class="required unite min_title inline field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label>
<select class="ui dropdown width80" id="trainjob_datasets" name="attachment" placeholder="选择数据集">
{{if .dataset_name}}
<option name="attachment" value="{{.uuid}}">{{.dataset_name}}</option>
{{end}}
{{range .attachments}}
<option value="">选择数据集</option>
{{if ne $.uuid .UUID}}
<option name="attachment" value="{{.UUID}}">{{.Attachment.Name}}</option>
{{end}}
{{end}}
</select>
</div>
<div class="inline unite min_title field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.run_parameter"}}</label>
<span id="add_run_para" style="margin-left: 0.5rem;cursor:pointer;color: rgba(3, 102, 214, 100);font-size: 14px;line-height: 26px;font-family: SourceHanSansSC-medium;"><i class="plus square outline icon"></i>{{.i18n.Tr "repo.modelarts.train_job.add_run_parameter"}}</span>
<input id="store_run_para" type="hidden" name="run_para_list">
<div class="dynamic field" style="margin-top: 1rem;">
{{if ne 0 (len .params)}}
{{range $k ,$v := .params}}
<div class="two fields width85" id="para{{$k}}">
<div class="field">
<input type="text" name="shipping_first-name" value={{$v.Label}} required>
</div>
<div class="field">
<input type="text" name="shipping_last-name" value={{$v.Value}} required>
</div>
<span>
<i class="trash icon"></i>
</span>

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


<div class="required field " style="display: none;">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.resource_pool"}}</label>
<select class="ui dropdown" id="trainjob_resource_pool" style='width:385px' name="pool_id">
{{range .resource_pools}}
<option value="{{.ID}}">{{.Value}}</option>
{{end}}
</select>
</div>

<div class="required grouped fields" style="display: none;">
<label style="font-weight: normal;" for="resource_type">{{.i18n.Tr "repo.modelarts.train_job.resource_type"}}</label>
<div class="field">
<div class="ui grid">
<div class="column">
<div class="ui radio checkbox">
<input type="radio" name="resource_type" checked="" tabindex="0">
</div>
</div>
<div class="three wide column">train-private-1</div>
<div class="three wide column">{{svg "octicon-verified" 16}} 运行中</div>
<div class="three wide column"> CPU:192 核 2048GiB</div>
</div>
</div>
</div>

<div class="required unite min_title inline field" id="flaver_name">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width81" id="trainjob-flavor" style='width:385px' name="flavor">
{{if .flavor_name}}
<option name="flavor" value="{{.flavor_code}}">{{.flavor_name}}</option>
{{end}}
{{range .flavor_infos}}
{{if ne $.flavor_code .Code}}
<option name="flavor" value="{{.Code}}">{{.Value}}</option>
{{end}}
{{end}}
</select>
</div>
<div class="inline required unite min_title field">
<label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label>
<div class="ui labeled input" style="width: 5%;">

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

</div>
</div>
<div class="inline unite min_title field">
<button class="ui create_train_job green button">
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
<!-- 模态框 -->
</form>
</div>
</div>
</div>
{{template "base/footer" .}}

<script>
let url_href = location.pathname.split('create_version')[0]
let url_post = location.pathname
let version_name = location.search.split('?version_name=')[1]
$("#parents_version").val(version_name)
$(".ui.button").attr('href',url_href)
$(".ui.form").attr('action',url_post)
$("input[name=version_name]").attr('value',version_name)
$('select.dropdown')
.dropdown();

$('.menu .item')
.tab();

let sever_num = $('#trainjob_work_server_num')
$('.add').click(function(){
sever_num.val(parseInt(sever_num.val())+1)
if(sever_num.val()>=26){
sever_num.val(parseInt(sever_num.val())-1)
}
})
$('.min').click(function(){
sever_num.val(parseInt(sever_num.val())-1)
if(sever_num.val()<=0){
sever_num.val(parseInt(sever_num.val())+1)
}
})

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

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

$(".dynamic.field").on("click",".trash.icon", function() {
var index = $(this).parent().parent().index()
$(this).parent().parent().remove()
var len = $(".dynamic.field .two.fields").length
$(".dynamic.field .two.fields").each(function(){
var cur_index = $(this).index()
$(this).attr('id', 'para' + cur_index)
})
});

$('.ui.parameter.green.button').click(function(){
var parameters = [];
$('table tr').each(function() {
$(this).find('td:eq(1)').each(function(){
parameters.push($(this).text());
})
$(this).find('input').each(function(){
parameters.push($(this).text())
})
});
$('.ui.parameter.modal')
.modal('hide');
for(var i = 2; i < parameters.length; i++){
switch(i) {
// 数据集uuid待完成
// case (2):
// console.log(1)
// break;
// $("#trainjob_datasets").val(parameters[i]);
// console.log($("#trainjob_datasets").val())
case (3):
$("input[name='boot_file']").val(parameters[i]);
break;
case (4):
var para = parameters[i].split(" ")
for(var j = 0; j < para.length; j++){
var para_name = para[j].split('=')[0]
var para_value = para[j].split('=')[1]
var len = $(".dynamic.field .two.fields").length
Add_parameter(len)
var pid = 'para' + len
$(".dynamic.field"+ " #" + pid + "").find("input[name=shipping_first-name]").val(para_name)
$(".dynamic.field"+ " #" + pid + "").find("input[name=shipping_last-name]").val(para_value)
}
break;
// 数据集pool_id待完成
// case (5):
// $("select[name='pool_id']").val(parameters[i]);
// break;
case (6):
$("input[name='work_server_number']").val(parameters[i]);
break;
}
}
})

$('.ui.save.checkbox').click(function(){
$(this).checkbox({
onChange: function(){
if ($('.ui.save.checkbox').checkbox('is checked')){
$('#save_para').removeClass("disabled")
}else{
$('#save_para').addClass("disabled")
}
}
});
})

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

$(".item.active.parameter_config").click(function(){
$('.ui.parameter.modal')
.modal('setting', 'closable', false)
.modal('show');
})

$('.ui.deny.button').click(function(){
$('.ui.parameter.modal')
.modal('hide');
})
$('select.dropdown')
.dropdown();

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

},
work_server_number: {
identifier : 'work_server_number',
rules: [
{
type : 'integer[1..25]',
prompt : '计算节点需要在1-25之间,请您键入正确的值'
}
]
},
run_para_list:{
identifier : 'run_para_list',
rules: [
{
type: 'maxLength[255]',
prompt : '所有字符最长不超过255个字符。'
}
]
},
},
})



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

},
work_server_number: {
identifier : 'work_server_number',
rules: [
{
type : 'integer[1..25]',
prompt : '计算节点需要在1-25之间,请您键入正确的值'
}
]
},
run_para_list:{
identifier : 'run_para_list',
rules: [
{
type: 'maxLength[255]',
prompt : '所有字符最长不超过255个字符。'
}
]
},
},
onSuccess: function(){
// $('.ui.page.dimmer').dimmer('show')
document.getElementById("mask").style.display = "block"
},
onFailure: function(e){
return false;
}
})
}
document.onreadystatechange = function() {
if (document.readyState === "complete") {

document.getElementById("mask").style.display = "none"
}
}
function send_run_para(){
var run_parameters = []
var msg = {}
$(".dynamic.field .two.fields").each(function(){
var para_name = $(this).find('input[name=shipping_first-name]').val()
var para_value = $(this).find('input[name=shipping_last-name]').val()
run_parameters.push({"label": para_name, "value": para_value})
})
msg["parameter"] = run_parameters
msg = JSON.stringify(msg)
$('#store_run_para').val(msg)
}
function get_name(){
let name1=$("#engine_name .text").text()
let name2=$("#flaver_name .text").text()
$("input#ai_engine_name").val(name1)
$("input#ai_flaver_name").val(name2)

}

$('.ui.create_train_job.green.button').click(function(e) {
get_name()
send_run_para()
validate()
})
</script>

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

@@ -10,7 +10,7 @@
</div> -->
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>


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

@@ -10,7 +10,7 @@
</div> -->
<div class="ui two column stackable grid">
<div class="column" style="display: flex;align-items: center;">
<div class="ui large breadcrumb">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a>
<div class="divider"> / </div>
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div>


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

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

<div class="inline field">


+ 5
- 1
templates/repo/release/list.tmpl View File

@@ -4,7 +4,11 @@
<div class="ui container">
{{template "base/alert" .}}
<h2 class="ui header">
{{.i18n.Tr "repo.release.releases"}}
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}">{{.i18n.Tr "repo.code"}}</a>
<div class="divider"> / </div>
<div class="active section" href="{{.RepoLink}}/releases">{{.i18n.Tr "repo.releases"}}</div>
</div>
{{if .CanCreateRelease}}
<div class="ui right">
<a class="ui small green button" href="{{$.RepoLink}}/releases/new">


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

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


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

@@ -41,7 +41,7 @@
{{end}}
<div class="field {{if .Err_Description}}error{{end}}">
<label for="description">{{$.i18n.Tr "repo.repo_desc"}}</label>
<textarea id="description" name="description" rows="2">{{.Repository.Description}}</textarea>
<textarea id="description" name="description" rows="2" maxlength="254">{{.Repository.Description}}</textarea>
</div>
<div class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.i18n.Tr "repo.settings.site"}}</label>


+ 15
- 0
templates/repo/view_file.tmpl View File

@@ -34,6 +34,21 @@
</div>
{{end}}
</div>

{{if .ReadmeInList}}
<div class="file-header-right">
<div class="ui right file-actions">
{{if .Repository.CanEnableEditor}}
{{if .CanEditFile}}
<a href="{{.RepoLink}}/_edit/{{EscapePound .BranchName}}/{{EscapePound .ReadmeName}}"><span class="btn-octicon poping up" data-content="{{.EditFileTooltip}}" data-position="bottom center" data-variation="tiny inverted">{{svg "octicon-pencil" 16}}</span></a>
{{else}}
<span class="btn-octicon poping up disabled" data-content="{{.EditFileTooltip}}" data-position="bottom center" data-variation="tiny inverted">{{svg "octicon-pencil" 16}}</span>
{{end}}
{{end}}
</div>
</div>
{{end}}
{{if not .ReadmeInList}}
<div class="file-header-right">
<div class="ui right file-actions">


+ 7
- 0
templates/repo/wiki/start.tmpl View File

@@ -2,6 +2,13 @@
<div class="repository wiki start">
{{template "repo/header" .}}
<div class="ui container">
<h2 class="ui header">
<div class="ui breadcrumb">
<a class="section" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}">{{.i18n.Tr "repo.code"}}</a>
<div class="divider"> / </div>
<div class="active section" href="{{.RepoLink}}/wiki">{{.i18n.Tr "repo.wiki"}}</div>
</div>
</h2>
<div class="ui center segment">
{{svg "octicon-book" 32}}
<h2>{{.i18n.Tr "repo.wiki.welcome"}}</h2>


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save