Browse Source

合并最新的代码。

Signed-off-by: zouap <zouap@pcl.ac.cn>
tags/v1.22.3.2^2
zouap 3 years ago
parent
commit
ed228a839f
100 changed files with 5771 additions and 660 deletions
  1. +141
    -17
      custom/public/css/git.openi.css
  2. BIN
      custom/public/img/ranking_list.jpg
  3. +1
    -1
      integrations/pull_update_test.go
  4. +11
    -2
      models/action.go
  5. +1
    -0
      models/ai_model_manage.go
  6. +14
    -1
      models/attachment.go
  7. +274
    -42
      models/cloudbrain.go
  8. +1
    -0
      models/models.go
  9. +131
    -37
      models/repo.go
  10. +22
    -1
      models/repo_list.go
  11. +8
    -0
      models/repo_statistic.go
  12. +10
    -5
      models/repo_tag.go
  13. +4
    -1
      models/repo_watch.go
  14. +9
    -0
      models/user.go
  15. +1
    -1
      models/user_business_analysis.go
  16. +98
    -0
      models/wechat_bind.go
  17. +13
    -8
      modules/auth/cloudbrain.go
  18. +26
    -1
      modules/auth/modelarts.go
  19. +12
    -0
      modules/auth/repo_form.go
  20. +5
    -0
      modules/auth/user_form.go
  21. +67
    -0
      modules/auth/wechat/access_token.go
  22. +71
    -0
      modules/auth/wechat/bind.go
  23. +5
    -0
      modules/auth/wechat/call.go
  24. +126
    -0
      modules/auth/wechat/client.go
  25. +76
    -0
      modules/auth/wechat/event_handle.go
  26. +13
    -0
      modules/auth/wechat/qr_code.go
  27. +49
    -34
      modules/cloudbrain/cloudbrain.go
  28. +101
    -0
      modules/cloudbrain/resty.go
  29. +66
    -9
      modules/context/auth.go
  30. +21
    -6
      modules/context/context.go
  31. +2
    -0
      modules/context/org.go
  32. +1
    -0
      modules/context/repo.go
  33. +198
    -10
      modules/modelarts/modelarts.go
  34. +244
    -0
      modules/modelarts/resty.go
  35. +31
    -0
      modules/notification/action/action.go
  36. +3
    -0
      modules/notification/base/notifier.go
  37. +8
    -0
      modules/notification/base/null.go
  38. +7
    -0
      modules/notification/notification.go
  39. +87
    -0
      modules/redis/redis_client/client.go
  40. +16
    -0
      modules/redis/redis_key/key_base.go
  41. +14
    -0
      modules/redis/redis_key/wechat_redis_key.go
  42. +40
    -0
      modules/redis/redis_lock/lock.go
  43. +10
    -1
      modules/repository/create.go
  44. +2
    -2
      modules/repository/fork.go
  45. +1
    -1
      modules/repository/fork_test.go
  46. +1
    -1
      modules/repository/init.go
  47. +44
    -5
      modules/setting/setting.go
  48. +17
    -8
      modules/storage/obs.go
  49. +4
    -3
      modules/structs/attachment.go
  50. +5
    -0
      modules/structs/repo.go
  51. +2
    -0
      modules/templates/helper.go
  52. +4
    -1
      modules/timeutil/since.go
  53. +3
    -0
      modules/validation/binding.go
  54. +125
    -34
      options/locale/locale_en-US.ini
  55. +97
    -6
      options/locale/locale_zh-CN.ini
  56. +140
    -0
      package-lock.json
  57. +4
    -0
      package.json
  58. +145
    -105
      public/home/home.js
  59. BIN
      public/img/qrcode_reload.png
  60. +225
    -0
      routers/admin/cloudbrains.go
  61. +21
    -1
      routers/api/v1/api.go
  62. +64
    -4
      routers/api/v1/repo/cloudbrain.go
  63. +1
    -1
      routers/api/v1/repo/fork.go
  64. +114
    -7
      routers/api/v1/repo/modelarts.go
  65. +1
    -0
      routers/api/v1/repo/repo.go
  66. +35
    -11
      routers/api/v1/repo/repo_dashbord.go
  67. +126
    -0
      routers/authentication/wechat.go
  68. +47
    -0
      routers/authentication/wechat_event.go
  69. +13
    -57
      routers/home.go
  70. +26
    -17
      routers/notice/notice.go
  71. +43
    -6
      routers/org/home.go
  72. +7
    -3
      routers/org/members.go
  73. +10
    -3
      routers/org/teams.go
  74. +47
    -22
      routers/repo/ai_model_manage.go
  75. +5
    -3
      routers/repo/attachment.go
  76. +465
    -24
      routers/repo/cloudbrain.go
  77. +196
    -0
      routers/repo/course.go
  78. +2
    -1
      routers/repo/download.go
  79. +730
    -61
      routers/repo/modelarts.go
  80. +2
    -1
      routers/repo/pull.go
  81. +2
    -1
      routers/repo/repo.go
  82. +18
    -10
      routers/repo/repo_statistic.go
  83. +40
    -6
      routers/repo/setting.go
  84. +13
    -11
      routers/repo/user_data_analysis.go
  85. +7
    -1
      routers/repo/view.go
  86. +69
    -8
      routers/routes/routes.go
  87. +17
    -1
      routers/user/auth.go
  88. +89
    -3
      services/repository/repository.go
  89. +2
    -2
      services/repository/transfer.go
  90. +41
    -8
      services/socketwrap/clientManager.go
  91. +316
    -0
      templates/admin/cloudbrain/list.tmpl
  92. +51
    -0
      templates/admin/cloudbrain/search.tmpl
  93. +3
    -0
      templates/admin/navbar.tmpl
  94. +1
    -1
      templates/admin/repo/list.tmpl
  95. +2
    -0
      templates/base/footer_content.tmpl
  96. +20
    -12
      templates/base/head.tmpl
  97. +209
    -0
      templates/base/head_course.tmpl
  98. +19
    -11
      templates/base/head_fluid.tmpl
  99. +19
    -11
      templates/base/head_home.tmpl
  100. +21
    -9
      templates/base/head_navbar.tmpl

+ 141
- 17
custom/public/css/git.openi.css View File

@@ -44,6 +44,12 @@
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
.ui.label{
font-weight: normal;
}
.active {
color: #0366D6 !important;
}


.opacity5{ opacity:0.5;} .opacity5{ opacity:0.5;}
.radius15{ border-radius:1.5rem !important; } .radius15{ border-radius:1.5rem !important; }
@@ -179,7 +185,7 @@
.homenews .ui.list>.item>.content{ .homenews .ui.list>.item>.content{
color: #E8E8E8; color: #E8E8E8;
line-height: 1.8em; line-height: 1.8em;
width: calc(100% - 3.25em) !important;
width: calc(100% - 3.75em) !important;
} }
.homenews .ui.list>.item{ .homenews .ui.list>.item{
padding: 0; padding: 0;
@@ -250,9 +256,13 @@
box-shadow: none !important; box-shadow: none !important;
} }
.homeorg-list .card .ui.small.header .content{ .homeorg-list .card .ui.small.header .content{
width: calc(100% - 3.25em);
width: calc(100% - 3.75em);
} }
.homepro-list{
.homepro-tit{
z-index: 9;
position: relative;
}
.homepro-list, .homeorg-list{
position: relative; position: relative;
z-index: 9; z-index: 9;
padding: 1.0em 1.0em 3.0em; padding: 1.0em 1.0em 3.0em;
@@ -261,42 +271,156 @@
.homepro-list .ui.card{ .homepro-list .ui.card{
border-radius: 15px; border-radius: 15px;
background-color: #FFF; background-color: #FFF;
box-shadow: 0px 5px 10px 0px rgba(105, 192, 255, 30);
border: 1px solid rgba(105, 192, 255, 40);
box-shadow: 0px 5px 10px 0px rgba(105, 192, 255, .3);
border: 1px solid rgba(105, 192, 255, .4);
min-height: 10.8em; min-height: 10.8em;
} }
.homepro-list .ui.card>.content>.header{ .homepro-list .ui.card>.content>.header{
line-height: 40px !important; line-height: 40px !important;
} }


.homepro-list .swiper-pagination-bullet-active{
.homepro-list .swiper-pagination-bullet-active, .homeorg-list .swiper-pagination-bullet-active{
width: 40px; width: 40px;
border-radius: 4px;
border-radius: 4px;
} }
.i-env > div{ .i-env > div{
position: relative; position: relative;
} }


/**seach**/
/**搜索导航条适配窄屏**/
.seachnav{
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none; /* firefox */
-ms-overflow-style: none; /* IE 10+ */
}
.seachnav::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
.ui.green.button, .ui.green.buttons .button{
background-color: #5BB973;
}
.seach .repos--seach{
padding-bottom: 0;
border-bottom: none;
}
.seach .ui.secondary.pointing.menu{
border-bottom: none;
}
.seach .ui.secondary.pointing.menu .item > i{
margin-right: 5px;
}
.seach .ui.secondary.pointing.menu .active.item{
border-bottom-width: 2px;
margin: 0 0 -1px;
}
.seach .ui.menu .active.item>.label {
background: #1684FC;
color: #FFF;
}
.seach .ui.menu .item>.label:not(.active.item>.label) {
background: #e8e8e8;
color: rgba(0,0,0,.6);
}

.highlight{
color: red;
}
.ui.list .list>.item>img.image+.content, .ui.list>.item>img.image+.content {
width: calc(100% - 3.0em);
margin-left: 0;
}

.seach .ui.list .list>.item .header, .seach .ui.list>.item .header{
margin-bottom: 0.5em;
font-size: 1.4rem !important;
font-weight: normal;
}
.seach .time, .seach .time a{
font-size: 12px;
color: grey;
}

.seach .list .item.members .ui.avatar.image {
width: 3.2em;
height: 3.2em;
}
.ui.list .list>.item.members>img.image+.content, .ui.list>.item.members>img.image+.content {
width: calc(100% - 4.0em);
margin-left: 0;
}

@media only screen and (max-width: 767px) { @media only screen and (max-width: 767px) {
.am-mt-30{ margin-top: 1.5rem !important;} .am-mt-30{ margin-top: 1.5rem !important;}
.ui.secondary.hometop.segment{ .ui.secondary.hometop.segment{
margin-bottom: 2.0rem;
margin-bottom: 5.0rem;
} }
.bannerpic, .i-code-pic{
.bannerpic{
display: none; display: none;
} }
.i-code h2::before {
left: calc(-5.0rem + 6px);
#homenews{
bottom: -3em;
} }
.i-code h2.am-bw::before{
left: calc(-4.0rem + 6px);
#homenews > p {
margin-left: 1.0em;
}
.homenews{
padding-left: 1.3em !important;
border-radius: 1.5em;
}
.homenews::before{
left: 2em;
}
.homepro-tit > p{
background: #FFF;
}
.homeorg{
padding-left: 3.5em;
}
.homeorg-tit::after {
left: -2.3em;
}
.homeorg-list{
margin: 0 0 2.0em !important;
}
.homeorg-list > .column{
width: 3em !important;
margin-left: -0.5em;
padding: 0.5rem 0 0 !important;
}
.homeorg-list .card{
background: none !important;
}
.homeorg-list .card > .content{
padding: 0 !important;
}
.homeorg-list > .column .card .ui.header>img{
width: 3.0em;
height: 3.0em;
border-radius: 2.0em;
border: 2px solid #FFF;
}
.homeorg-list > .column .card .ui.header > .content{
display: none;
} }
.leftline01{ .leftline01{
width: calc(50% - 4.0rem);
width: 4.0em;
bottom: 4em;
border-radius: 0 0 0 3.0em;
} }
.leftline02{
left: calc(50% - 1.0rem);
top: calc(-3.5rem - 2px);
.leftline02, .leftline02-2{
left: 6.0em;
top: calc(-4.0em - 2px);
border-radius: 0 3.0em 3.0em 0;
width: calc(50% - 6.0em);
}
.leftline02-2 {
width: calc(50% - 8.0em);
}
.i-env .ui.cards>.card>.content .description{
display: none;
} }
} }




BIN
custom/public/img/ranking_list.jpg View File

Before After
Width: 600  |  Height: 544  |  Size: 37 kB

+ 1
- 1
integrations/pull_update_test.go View File

@@ -58,7 +58,7 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullReq
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, baseRepo) assert.NotEmpty(t, baseRepo)


headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc")
headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc", "")
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, headRepo) assert.NotEmpty(t, headRepo)




+ 11
- 2
models/action.go View File

@@ -49,6 +49,14 @@ const (
ActionApprovePullRequest // 21 ActionApprovePullRequest // 21
ActionRejectPullRequest // 22 ActionRejectPullRequest // 22
ActionCommentPull // 23 ActionCommentPull // 23

ActionUploadAttachment //24
ActionCreateDebugGPUTask //25
ActionCreateDebugNPUTask //26
ActionCreateTrainTask //27
ActionCreateInferenceTask // 28
ActionCreateBenchMarkTask //29
ActionCreateNewModelTask //30
) )


// Action represents user operation type and other information to // Action represents user operation type and other information to
@@ -364,11 +372,12 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
return actions, nil return actions, nil
} }


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

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


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




+ 1
- 0
models/ai_model_manage.go View File

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


type AiModelQueryOptions struct { type AiModelQueryOptions struct {


+ 14
- 1
models/attachment.go View File

@@ -88,12 +88,25 @@ func (a *Attachment) APIFormat() *api.Attachment {
Size: a.Size, Size: a.Size,
UUID: a.UUID, UUID: a.UUID,
DownloadURL: a.DownloadURL(), DownloadURL: a.DownloadURL(),
S3DownloadURL: a.S3DownloadURL(),
} }
} }


// DownloadURL returns the download url of the attached file // DownloadURL returns the download url of the attached file
func (a *Attachment) DownloadURL() string { func (a *Attachment) DownloadURL() string {
return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID)
return fmt.Sprintf("%sattachments/%s?type=%d", setting.AppURL, a.UUID, a.Type)
}

// S3DownloadURL returns the s3 download url of the attached file
func (a *Attachment) S3DownloadURL() string {
url := ""
if a.Type == TypeCloudBrainOne {
url, _ = storage.Attachments.PresignedGetURL(setting.Attachment.Minio.BasePath+AttachmentRelativePath(a.UUID), a.Name)
} else if a.Type == TypeCloudBrainTwo {
url, _ = storage.ObsGetPreSignedUrl(a.UUID, a.Name)
}

return url
} }


// AttachmentRelativePath returns the relative path // AttachmentRelativePath returns the relative path


+ 274
- 42
models/cloudbrain.go View File

@@ -22,6 +22,16 @@ const (
NPUResource = "NPU" NPUResource = "NPU"
GPUResource = "CPU/GPU" GPUResource = "CPU/GPU"


//notebook storage category
EVSCategory = "EVS"
EFSCategory = "EFS"

ManagedOwnership = "MANAGED"
DetectedOwnership = "DEDICATED"

NotebookFeature = "NOTEBOOK"
DefaultFeature = "DEFAULT"

JobWaiting CloudbrainStatus = "WAITING" JobWaiting CloudbrainStatus = "WAITING"
JobStopped CloudbrainStatus = "STOPPED" JobStopped CloudbrainStatus = "STOPPED"
JobSucceeded CloudbrainStatus = "SUCCEEDED" JobSucceeded CloudbrainStatus = "SUCCEEDED"
@@ -33,6 +43,7 @@ const (
JobTypeSnn4imagenet JobType = "SNN4IMAGENET" JobTypeSnn4imagenet JobType = "SNN4IMAGENET"
JobTypeBrainScore JobType = "BRAINSCORE" JobTypeBrainScore JobType = "BRAINSCORE"
JobTypeTrain JobType = "TRAIN" JobTypeTrain JobType = "TRAIN"
JobTypeInference JobType = "INFERENCE"


//notebook //notebook
ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中 ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中
@@ -77,28 +88,30 @@ const (
) )


type Cloudbrain struct { type Cloudbrain struct {
ID int64 `xorm:"pk autoincr"`
JobID string `xorm:"INDEX NOT NULL"`
JobType string `xorm:"INDEX NOT NULL DEFAULT 'DEBUG'"`
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
TrainJobDuration string
Image string //GPU镜像名称
GpuQueue string //GPU类型即GPU队列
ResourceSpecId int //GPU规格id
DeletedAt time.Time `xorm:"deleted"`
CanDebug bool `xorm:"-"`
CanDel bool `xorm:"-"`
CanModify bool `xorm:"-"`
Type int
ID int64 `xorm:"pk autoincr"`
JobID string `xorm:"INDEX NOT NULL"`
JobType string `xorm:"INDEX NOT NULL DEFAULT 'DEBUG'"`
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
TrainJobDuration string
Image string //镜像名称
GpuQueue string //GPU类型即GPU队列
ResourceSpecId int //GPU规格id
DeletedAt time.Time `xorm:"deleted"`
CanDebug bool `xorm:"-"`
CanDel bool `xorm:"-"`
CanModify bool `xorm:"-"`
Type int
BenchmarkTypeID int
BenchmarkChildTypeID int


VersionID int64 //版本id VersionID int64 //版本id
VersionName string `xorm:"INDEX"` //当前版本 VersionName string `xorm:"INDEX"` //当前版本
@@ -111,7 +124,7 @@ type Cloudbrain struct {
ComputeResource string //计算资源,例如npu ComputeResource string //计算资源,例如npu
EngineID int64 //引擎id EngineID int64 //引擎id


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


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

User *User `xorm:"-"` User *User `xorm:"-"`
Repo *Repository `xorm:"-"` Repo *Repository `xorm:"-"`
} }
@@ -200,17 +219,20 @@ type GetImagesPayload struct {


type CloudbrainsOptions struct { type CloudbrainsOptions struct {
ListOptions ListOptions
RepoID int64 // include all repos if empty
UserID int64
JobID string
SortType string
CloudbrainIDs []int64
// JobStatus CloudbrainStatus
RepoID int64 // include all repos if empty
UserID int64
JobID string
SortType string
CloudbrainIDs []int64
JobStatus []string
JobStatusNot bool
Keyword string
Type int Type int
JobType string
JobTypes []string
VersionName string VersionName string
IsLatestVersion string IsLatestVersion string
JobTypeNot bool JobTypeNot bool
NeedRepoInfo bool
} }


type TaskPod struct { type TaskPod struct {
@@ -380,6 +402,24 @@ type Category struct {
Value string `json:"value"` Value string `json:"value"`
} }


type BenchmarkTypes struct {
BenchmarkType []*BenchmarkType `json:"type"`
}

type BenchmarkType struct {
Id int `json:"id"`
First string `json:"first"` //一级算法类型名称
Second []*BenchmarkDataset `json:"second"`
}

type BenchmarkDataset struct {
Id int `json:"id"`
Value string `json:"value"` //二级算法类型名称
Attachment string `json:"attachment"` //数据集的uuid
Owner string `json:"owner"` //评估脚本所在仓库的拥有者
RepoName string `json:"repo_name"` //评估脚本所在仓库的名称
}

type GpuInfos struct { type GpuInfos struct {
GpuInfo []*GpuInfo `json:"gpu_type"` GpuInfo []*GpuInfo `json:"gpu_type"`
} }
@@ -412,6 +452,16 @@ type FlavorInfo struct {
Desc string `json:"desc"` Desc string `json:"desc"`
} }


type ImageInfosModelArts struct {
ImageInfo []*ImageInfoModelArts `json:"image_info"`
}

type ImageInfoModelArts struct {
Id string `json:"id"`
Value string `json:"value"`
Desc string `json:"desc"`
}

type PoolInfos struct { type PoolInfos struct {
PoolInfo []*PoolInfo `json:"pool_info"` PoolInfo []*PoolInfo `json:"pool_info"`
} }
@@ -435,11 +485,83 @@ type CommitImageResult struct {
Payload map[string]interface{} `json:"payload"` Payload map[string]interface{} `json:"payload"`
} }


type GetJobLogParams struct {
Size string `json:"size"`
Sort string `json:"sort"`
QueryInfo QueryInfo `json:"query"`
}

type QueryInfo struct {
MatchInfo MatchInfo `json:"match"`
}

type MatchInfo struct {
PodName string `json:"kubernetes.pod.name"`
}

type GetJobLogResult struct {
ScrollID string `json:"_scroll_id"`
Took int `json:"took"`
TimedOut bool `json:"timed_out"`
Shards struct {
Total int `json:"total"`
Successful int `json:"successful"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
} `json:"_shards"`
Hits struct {
Hits []Hits `json:"hits"`
} `json:"hits"`
}

type Hits struct {
Index string `json:"_index"`
Type string `json:"_type"`
ID string `json:"_id"`
Source struct {
Message string `json:"message"`
} `json:"_source"`
Sort []int `json:"sort"`
}

type GetAllJobLogParams struct {
Scroll string `json:"scroll"`
ScrollID string `json:"scroll_id"`
}

type DeleteJobLogTokenParams struct {
ScrollID string `json:"scroll_id"`
}

type DeleteJobLogTokenResult struct {
Succeeded bool `json:"succeeded"`
NumFreed int `json:"num_freed"`
}

type CloudBrainResult struct { type CloudBrainResult struct {
Code string `json:"code"` Code string `json:"code"`
Msg string `json:"msg"` Msg string `json:"msg"`
} }


type CreateNotebook2Params struct {
JobName string `json:"name"`
Description string `json:"description"`
Duration int64 `json:"duration"` //ms
Feature string `json:"feature"`
PoolID string `json:"pool_id"`
Flavor string `json:"flavor"`
ImageID string `json:"image_id"`
WorkspaceID string `json:"workspace_id"`
Volume VolumeReq `json:"volume"`
}

type VolumeReq struct {
Capacity int `json:"capacity"`
Category string `json:"category"`
Ownership string `json:"ownership"`
Uri string `json:"uri"`
}

type CreateNotebookParams struct { type CreateNotebookParams struct {
JobName string `json:"name"` JobName string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
@@ -557,6 +679,42 @@ type GetNotebookResult struct {
} `json:"spec"` } `json:"spec"`
} }


type GetNotebook2Result struct {
ErrorCode string `json:"error_code"`
ErrorMsg string `json:"error_msg"`
FailReason string `json:"fail_reason"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Status string `json:"status"`
Url string `json:"url"` //实例访问的URL
Token string `json:"token"` //notebook鉴权使用的token信息
Flavor string `json:"flavor"`
CreateTime string
LatestUpdateTime string
CreateAt int64 `json:"create_at"` //实例创建的时间,UTC毫秒
UpdateAt int64 `json:"update_at"` //实例最后更新(不包括保活心跳)的时间,UTC毫秒
Image struct {
Name string `json:"name"`
Status string `json:"status"`
QueuingNum int `json:"queuing_num"`
QueueLeftTime int `json:"queue_left_time"` //s
Duration int `json:"duration"` //auto_stop_time s
} `json:"image"`
Lease struct {
CreateTime int64 `json:"create_at"` //实例创建的时间,UTC毫秒
Duration int64 `json:"duration"` //实例运行时长,以创建时间为起点计算,即“创建时间+duration > 当前时刻”时,系统会自动停止实例
UpdateTime int64 `json:"update_at"` //实例最后更新(不包括保活心跳)的时间,UTC毫秒
} `json:"lease"` //实例自动停止的倒计时信息
VolumeRes struct {
Capacity int `json:"capacity"`
Category string `json:"category"`
MountPath string `json:"mount_path"`
Ownership string `json:"ownership"`
Status string `json:"status"`
} `json:"volume"`
}

type GetTokenParams struct { type GetTokenParams struct {
Auth Auth `json:"auth"` Auth Auth `json:"auth"`
} }
@@ -610,6 +768,7 @@ type NotebookActionResult struct {
ErrorMsg string `json:"error_msg"` ErrorMsg string `json:"error_msg"`
CurrentStatus string `json:"current_status"` CurrentStatus string `json:"current_status"`
PreviousState string `json:"previous_state"` PreviousState string `json:"previous_state"`
Status string `json:"status"`
} }


type NotebookGetJobTokenResult struct { type NotebookGetJobTokenResult struct {
@@ -644,6 +803,25 @@ type Config struct {
Flavor Flavor `json:"flavor"` Flavor Flavor `json:"flavor"`
PoolID string `json:"pool_id"` PoolID string `json:"pool_id"`
} }
type CreateInferenceJobParams struct {
JobName string `json:"job_name"`
Description string `json:"job_desc"`
InfConfig InfConfig `json:"config"`
WorkspaceID string `json:"workspace_id"`
}

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


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


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


if (opts.IsLatestVersion) != "" { if (opts.IsLatestVersion) != "" {
cond = cond.And(
builder.Eq{"cloudbrain.is_latest_version": opts.IsLatestVersion},
)
cond = cond.And(builder.Or(builder.And(builder.Eq{"cloudbrain.is_latest_version": opts.IsLatestVersion}, builder.Eq{"cloudbrain.job_type": "TRAIN"}), builder.Neq{"cloudbrain.job_type": "TRAIN"}))
} }


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


count, err := sess.Where(cond).Count(new(Cloudbrain))
if len(opts.JobStatus) > 0 {
if opts.JobStatusNot {
cond = cond.And(
builder.NotIn("cloudbrain.status", opts.JobStatus),
)
} else {
cond = cond.And(
builder.In("cloudbrain.status", opts.JobStatus),
)
}
}

var count int64
var err error
condition := "cloudbrain.user_id = `user`.id"
if len(opts.Keyword) == 0 {
count, err = sess.Where(cond).Count(new(Cloudbrain))
} else {
lowerKeyWord := strings.ToLower(opts.Keyword)

cond = cond.And(builder.Or(builder.Like{"LOWER(cloudbrain.job_name)", lowerKeyWord}, builder.Like{"`user`.lower_name", lowerKeyWord}))
count, err = sess.Table(&Cloudbrain{}).Where(cond).
Join("left", "`user`", condition).Count(new(CloudbrainInfo))

}

if err != nil { if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err) return nil, 0, fmt.Errorf("Count: %v", err)
} }
@@ -934,11 +1135,25 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
sess.OrderBy("cloudbrain.created_unix DESC") sess.OrderBy("cloudbrain.created_unix DESC")
cloudbrains := make([]*CloudbrainInfo, 0, setting.UI.IssuePagingNum) cloudbrains := make([]*CloudbrainInfo, 0, setting.UI.IssuePagingNum)
if err := sess.Table(&Cloudbrain{}).Where(cond). if err := sess.Table(&Cloudbrain{}).Where(cond).
Join("left", "`user`", "cloudbrain.user_id = `user`.id").
Join("left", "`user`", condition).
Find(&cloudbrains); err != nil { Find(&cloudbrains); err != nil {
return nil, 0, fmt.Errorf("Find: %v", err) return nil, 0, fmt.Errorf("Find: %v", err)
} }


if opts.NeedRepoInfo {
var ids []int64
for _, task := range cloudbrains {
ids = append(ids, task.RepoID)
}
repositoryMap, err := GetRepositoriesMapByIDs(ids)
if err == nil {
for _, task := range cloudbrains {
task.Repo = repositoryMap[task.RepoID]
}
}

}

return cloudbrains, count, nil return cloudbrains, count, nil
} }


@@ -978,6 +1193,7 @@ func QueryModelTrainJobList(repoId int64) ([]*CloudbrainInfo, int, error) {
cond = cond.And( cond = cond.And(
builder.Eq{"job_type": "TRAIN"}, builder.Eq{"job_type": "TRAIN"},
) )

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


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


@@ -1194,8 +1410,8 @@ func GetCloudBrainUnStoppedJob() ([]*Cloudbrain, error) {
Find(&cloudbrains) Find(&cloudbrains)
} }


func GetCloudbrainCountByUserID(userID int64) (int, error) {
count, err := x.In("status", JobWaiting, JobRunning).And("job_type = ? and user_id = ? and type = ?", JobTypeDebug, userID, TypeCloudBrainOne).Count(new(Cloudbrain))
func GetCloudbrainCountByUserID(userID int64, jobType string) (int, error) {
count, err := x.In("status", JobWaiting, JobRunning).And("job_type = ? and user_id = ? and type = ?", jobType, userID, TypeCloudBrainOne).Count(new(Cloudbrain))
return int(count), err return int(count), err
} }


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


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

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

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


+ 1
- 0
models/models.go View File

@@ -136,6 +136,7 @@ func init() {
new(AiModelManage), new(AiModelManage),
new(OfficialTag), new(OfficialTag),
new(OfficialTagRepos), new(OfficialTagRepos),
new(WechatBindLog),
) )


tablesStatistic = append(tablesStatistic, tablesStatistic = append(tablesStatistic,


+ 131
- 37
models/repo.go View File

@@ -141,6 +141,7 @@ func NewRepoContext() {
// RepositoryStatus defines the status of repository // RepositoryStatus defines the status of repository
type RepositoryStatus int type RepositoryStatus int
type RepoBlockChainStatus int type RepoBlockChainStatus int
type RepoType int


// all kinds of RepositoryStatus // all kinds of RepositoryStatus
const ( const (
@@ -154,6 +155,11 @@ const (
RepoBlockChainFailed RepoBlockChainFailed
) )


const (
RepoNormal RepoType = iota
RepoCourse
)

// Repository represents a git repository. // Repository represents a git repository.
type Repository struct { type Repository struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@@ -167,7 +173,8 @@ type Repository struct {
OriginalServiceType api.GitServiceType `xorm:"index"` OriginalServiceType api.GitServiceType `xorm:"index"`
OriginalURL string `xorm:"VARCHAR(2048)"` OriginalURL string `xorm:"VARCHAR(2048)"`
DefaultBranch string DefaultBranch string

CreatorID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
Creator *User `xorm:"-"`
NumWatches int NumWatches int
NumStars int NumStars int
NumForks int NumForks int
@@ -176,11 +183,12 @@ type Repository struct {
NumOpenIssues int `xorm:"-"` NumOpenIssues int `xorm:"-"`
NumPulls int NumPulls int
NumClosedPulls int NumClosedPulls int
NumOpenPulls int `xorm:"-"`
NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumOpenMilestones int `xorm:"-"`
NumCommit int64 `xorm:"NOT NULL DEFAULT 0"`
NumOpenPulls int `xorm:"-"`
NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumOpenMilestones int `xorm:"-"`
NumCommit int64 `xorm:"NOT NULL DEFAULT 0"`
RepoType RepoType `xorm:"NOT NULL DEFAULT 0"`


IsPrivate bool `xorm:"INDEX"` IsPrivate bool `xorm:"INDEX"`
IsEmpty bool `xorm:"INDEX"` IsEmpty bool `xorm:"INDEX"`
@@ -223,9 +231,10 @@ type Repository struct {
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`


Hot int64 `xorm:"-"`
Active int64 `xorm:"-"`
Alias string
Hot int64 `xorm:"-"`
Active int64 `xorm:"-"`
Alias string `xorm:"INDEX"`
LowerAlias string `xorm:"INDEX"`
} }


// SanitizedOriginalURL returns a sanitized OriginalURL // SanitizedOriginalURL returns a sanitized OriginalURL
@@ -401,6 +410,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
ID: repo.ID, ID: repo.ID,
Owner: repo.Owner.APIFormat(), Owner: repo.Owner.APIFormat(),
Name: repo.Name, Name: repo.Name,
Alias: repo.Alias,
FullName: repo.FullName(), FullName: repo.FullName(),
FullDisplayName: repo.FullDisplayName(), FullDisplayName: repo.FullDisplayName(),
Description: repo.Description, Description: repo.Description,
@@ -565,6 +575,19 @@ func (repo *Repository) GetOwner() error {
return repo.getOwner(x) return repo.getOwner(x)
} }


func (repo *Repository) getCreator(e Engine) (err error) {
if repo.CreatorID == 0 {
return nil
}

repo.Creator, err = getUserByID(e, repo.CreatorID)
return err
}

func (repo *Repository) GetCreator() error {
return repo.getCreator(x)
}

func (repo *Repository) mustOwner(e Engine) *User { func (repo *Repository) mustOwner(e Engine) *User {
if err := repo.getOwner(e); err != nil { if err := repo.getOwner(e); err != nil {
return &User{ return &User{
@@ -943,13 +966,12 @@ func isRepositoryExist(e Engine, u *User, repoName string, alias string) (bool,
cond = cond.And(builder.Eq{"owner_id": u.ID}) cond = cond.And(builder.Eq{"owner_id": u.ID})
if alias != "" { if alias != "" {
subCon := builder.NewCond() subCon := builder.NewCond()
subCon = subCon.Or(builder.Eq{"alias": alias}, builder.Eq{"lower_name": repoName})
subCon = subCon.Or(builder.Eq{"lower_alias": strings.ToLower(alias)}, builder.Eq{"lower_name": strings.ToLower(repoName)})
cond = cond.And(subCon) cond = cond.And(subCon)
} else { } else {
cond = cond.And(builder.Eq{"lower_name": repoName})
cond = cond.And(builder.Eq{"lower_name": strings.ToLower(repoName)})
} }
count, err := e.Where(cond).Count(&Repository{}) count, err := e.Where(cond).Count(&Repository{})
//todo 确定从 && 改成 || 是否有问题
return count > 0 || com.IsDir(RepoPath(u.Name, repoName)), err return count > 0 || com.IsDir(RepoPath(u.Name, repoName)), err
} }


@@ -958,6 +980,33 @@ func IsRepositoryExist(u *User, repoName string, alias string) (bool, error) {
return isRepositoryExist(x, u, repoName, alias) return isRepositoryExist(x, u, repoName, alias)
} }


// IsRepositoryAliasExist returns true if the repository with given alias under user has already existed.
func IsRepositoryAliasExist(u *User, alias string) (bool, error) {
return isRepositoryAliasExist(x, u, alias)
}

func isRepositoryAliasExist(e Engine, u *User, alias string) (bool, error) {
var cond = builder.NewCond()
cond = cond.And(builder.Eq{"owner_id": u.ID})
cond = cond.And(builder.Eq{"lower_alias": strings.ToLower(alias)})
count, err := e.Where(cond).Count(&Repository{})
return count > 0, err
}

func IsRepositoryAliasAvailable(doer *User, alias string) error {
if err := IsUsableRepoAlias(alias); err != nil {
return err
}

has, err := IsRepositoryAliasExist(doer, alias)
if err != nil {
return fmt.Errorf("IsRepositoryExist: %v", err)
} else if has {
return ErrRepoAlreadyExist{doer.Name, alias}
}
return nil
}

// CloneLink represents different types of clone URLs of repository. // CloneLink represents different types of clone URLs of repository.
type CloneLink struct { type CloneLink struct {
SSH string SSH string
@@ -1037,6 +1086,8 @@ type CreateRepoOptions struct {
IsMirror bool IsMirror bool
AutoInit bool AutoInit bool
Status RepositoryStatus Status RepositoryStatus
IsCourse bool
Topics []string
} }


// GetRepoInitFile returns repository init files // GetRepoInitFile returns repository init files
@@ -1082,7 +1133,11 @@ func IsUsableRepoAlias(name string) error {
} }


// CreateRepository creates a repository for the user/organization. // CreateRepository creates a repository for the user/organization.
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) {
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, opts ...CreateRepoOptions) (err error) {
if repo.Alias == "" {
repo.Alias = repo.Name
}
repo.LowerAlias = strings.ToLower(repo.Alias)
if err = IsUsableRepoName(repo.Name); err != nil { if err = IsUsableRepoName(repo.Name); err != nil {
return err return err
} }
@@ -1096,7 +1151,10 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
} else if has { } else if has {
return ErrRepoAlreadyExist{u.Name, repo.Name} return ErrRepoAlreadyExist{u.Name, repo.Name}
} }

isCourse := isCourse(opts)
if isCourse {
repo.CreatorID = doer.ID
}
if _, err = ctx.e.Insert(repo); err != nil { if _, err = ctx.e.Insert(repo); err != nil {
return err return err
} }
@@ -1130,17 +1188,23 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true},
}) })
} else if tp == UnitTypeDatasets { } else if tp == UnitTypeDatasets {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &DatasetConfig{EnableDataset: true},
})
if !isCourse {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &DatasetConfig{EnableDataset: true},
})
}

} else if tp == UnitTypeCloudBrain { } else if tp == UnitTypeCloudBrain {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &CloudBrainConfig{EnableCloudBrain: true},
})
if !isCourse {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &CloudBrainConfig{EnableCloudBrain: true},
})
}

} else if tp == UnitTypeBlockChain { } else if tp == UnitTypeBlockChain {
units = append(units, RepoUnit{ units = append(units, RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
@@ -1148,11 +1212,13 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Config: &BlockChainConfig{EnableBlockChain: true}, Config: &BlockChainConfig{EnableBlockChain: true},
}) })
} else if tp == UnitTypeModelManage { } else if tp == UnitTypeModelManage {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &ModelManageConfig{EnableModelManage: true},
})
if !isCourse {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &ModelManageConfig{EnableModelManage: true},
})
}
} else { } else {
units = append(units, RepoUnit{ units = append(units, RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
@@ -1222,6 +1288,14 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
return nil return nil
} }


func isCourse(opts []CreateRepoOptions) bool {
var isCourse = false
if len(opts) > 0 {
isCourse = opts[0].IsCourse
}
return isCourse
}

func countRepositories(userID int64, private bool) int64 { func countRepositories(userID int64, private bool) int64 {
sess := x.Where("id > 0") sess := x.Where("id > 0")


@@ -1887,6 +1961,26 @@ func getRepositoryByOwnerAndName(e Engine, ownerName, repoName string) (*Reposit
return &repo, nil return &repo, nil
} }


// GetRepositoryByOwnerAndAlias returns the repository by given ownername and reponame.
func GetRepositoryByOwnerAndAlias(ownerName, alias string) (*Repository, error) {
return getRepositoryByOwnerAndAlias(x, ownerName, alias)
}

func getRepositoryByOwnerAndAlias(e Engine, ownerName, alias string) (*Repository, error) {
var repo Repository
has, err := e.Table("repository").Select("repository.*").
Join("INNER", "`user`", "`user`.id = repository.owner_id").
Where("repository.lower_alias = ?", strings.ToLower(alias)).
And("`user`.lower_name = ?", strings.ToLower(ownerName)).
Get(&repo)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRepoNotExist{0, 0, ownerName, alias}
}
return &repo, nil
}

// GetRepositoryByName returns the repository by given name under user if exists. // GetRepositoryByName returns the repository by given name under user if exists.
func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { func GetRepositoryByName(ownerID int64, name string) (*Repository, error) {
repo := &Repository{ repo := &Repository{
@@ -2560,6 +2654,14 @@ func UpdateRepositoryCommitNum(repo *Repository) error {
return nil return nil
} }


func GenerateDefaultRepoName(ownerName string) string {
if len(ownerName) > 5 {
ownerName = ownerName[:5]
}
now := time.Now().Format("20060102150405")
return ownerName + now + fmt.Sprint(rand.Intn(10))
}

type RepoFile struct { type RepoFile struct {
CommitId string CommitId string
Content []byte Content []byte
@@ -2609,11 +2711,3 @@ func ReadLatestFileInRepo(userName, repoName, refName, treePath string) (*RepoFi
} }
return &RepoFile{CommitId: commitID, Content: buf}, nil return &RepoFile{CommitId: commitID, Content: buf}, nil
} }

func GenerateDefaultRepoName(ownerName string) string {
if len(ownerName) > 5 {
ownerName = ownerName[:5]
}
now := time.Now().Format("20060102150405")
return ownerName + now + fmt.Sprint(rand.Intn(10))
}

+ 22
- 1
models/repo_list.go View File

@@ -48,9 +48,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error {


set := make(map[int64]struct{}) set := make(map[int64]struct{})
repoIDs := make([]int64, len(repos)) repoIDs := make([]int64, len(repos))
setCreator := make(map[int64]struct{})
for i := range repos { for i := range repos {
set[repos[i].OwnerID] = struct{}{} set[repos[i].OwnerID] = struct{}{}
repoIDs[i] = repos[i].ID repoIDs[i] = repos[i].ID
setCreator[repos[i].CreatorID] = struct{}{}

} }


// Load owners. // Load owners.
@@ -61,8 +64,18 @@ func (repos RepositoryList) loadAttributes(e Engine) error {
Find(&users); err != nil { Find(&users); err != nil {
return fmt.Errorf("find users: %v", err) return fmt.Errorf("find users: %v", err)
} }
//Load creator
creators := make(map[int64]*User, len(set))
if err := e.
Where("id > 0").
In("id", keysInt64(setCreator)).
Find(&creators); err != nil {
return fmt.Errorf("find create repo users: %v", err)
}

for i := range repos { for i := range repos {
repos[i].Owner = users[repos[i].OwnerID] repos[i].Owner = users[repos[i].OwnerID]
repos[i].Creator = creators[repos[i].CreatorID]
} }


// Load primary language. // Load primary language.
@@ -174,6 +187,10 @@ type SearchRepoOptions struct {
// True -> include just has milestones // True -> include just has milestones
// False -> include just has no milestone // False -> include just has no milestone
HasMilestones util.OptionalBool HasMilestones util.OptionalBool
// None -> include all repos
// True -> include just courses
// False -> include just no courses
Course util.OptionalBool
} }


//SearchOrderBy is used to sort the result //SearchOrderBy is used to sort the result
@@ -321,7 +338,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
var likes = builder.NewCond() var likes = builder.NewCond()
for _, v := range strings.Split(opts.Keyword, ",") { for _, v := range strings.Split(opts.Keyword, ",") {
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
likes = likes.Or(builder.Like{"alias", strings.ToLower(v)})
likes = likes.Or(builder.Like{"alias", v})
if opts.IncludeDescription { if opts.IncludeDescription {
likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)})
} }
@@ -351,6 +368,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue})
} }


if opts.Course == util.OptionalBoolTrue {
cond = cond.And(builder.Eq{"repo_type": RepoCourse})
}

if opts.Actor != nil && opts.Actor.IsRestricted { if opts.Actor != nil && opts.Actor.IsRestricted {
cond = cond.And(accessibleRepositoryCondition(opts.Actor)) cond = cond.And(accessibleRepositoryCondition(opts.Actor))
} }


+ 8
- 0
models/repo_statistic.go View File

@@ -12,6 +12,7 @@ type RepoStatistic struct {
ID int64 `xorm:"pk autoincr" json:"-"` ID int64 `xorm:"pk autoincr" json:"-"`
RepoID int64 `xorm:"unique(s) NOT NULL" json:"repo_id"` RepoID int64 `xorm:"unique(s) NOT NULL" json:"repo_id"`
Name string `xorm:"INDEX" json:"name"` Name string `xorm:"INDEX" json:"name"`
Alias string `xorm:"INDEX" json:"alias"`
OwnerName string `json:"ownerName"` OwnerName string `json:"ownerName"`
IsPrivate bool `json:"isPrivate"` IsPrivate bool `json:"isPrivate"`
IsMirror bool `json:"isMirror"` IsMirror bool `json:"isMirror"`
@@ -63,6 +64,13 @@ type RepoStatistic struct {
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated" json:"-"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated" json:"-"`
} }


func (repo *RepoStatistic) DisplayName() string {
if repo.Alias == "" {
return repo.Name
}
return repo.Alias
}

func DeleteRepoStatDaily(date string) error { func DeleteRepoStatDaily(date string) error {
sess := xStatistic.NewSession() sess := xStatistic.NewSession()
defer sess.Close() defer sess.Close()


+ 10
- 5
models/repo_tag.go View File

@@ -28,6 +28,7 @@ type OfficialTagRepos struct {
type TagReposBrief struct { type TagReposBrief struct {
RepoID int64 RepoID int64
RepoName string RepoName string
Alias string
TagID int64 TagID int64
} }


@@ -41,7 +42,7 @@ type TagsDetail struct {
TagId int64 TagId int64
TagName string TagName string
TagLimit int TagLimit int
RepoList []Repository
RepoList []*Repository
} }


func GetTagByID(id int64) (*OfficialTag, error) { func GetTagByID(id int64) (*OfficialTag, error) {
@@ -97,7 +98,7 @@ func UpdateTagReposByID(tagID, orgID int64, repoIdList []int64) error {


func GetTagRepos(tagID, orgID int64) ([]TagReposSelected, error) { func GetTagRepos(tagID, orgID int64) ([]TagReposSelected, error) {
t := make([]TagReposBrief, 0) t := make([]TagReposBrief, 0)
const SQLCmd = "select t1.id as repo_id,t1.name as repo_name,t2.id as tag_id from repository t1 left join official_tag_repos t2 on (t1.id = t2.repo_id and t2.tag_id = ?) where t1.owner_id = ? and t1.is_private = false order by t1.updated_unix desc"
const SQLCmd = "select t1.id as repo_id,t1.name as repo_name,t1.alias,t2.id as tag_id from repository t1 left join official_tag_repos t2 on (t1.id = t2.repo_id and t2.tag_id = ?) where t1.owner_id = ? and t1.is_private = false order by t1.updated_unix desc"


if err := x.SQL(SQLCmd, tagID, orgID).Find(&t); err != nil { if err := x.SQL(SQLCmd, tagID, orgID).Find(&t); err != nil {
return nil, err return nil, err
@@ -108,9 +109,13 @@ func GetTagRepos(tagID, orgID int64) ([]TagReposSelected, error) {
if v.TagID > 0 { if v.TagID > 0 {
selected = true selected = true
} }
repoName := v.Alias
if v.Alias == "" {
repoName = v.RepoName
}
r = append(r, TagReposSelected{ r = append(r, TagReposSelected{
RepoID: v.RepoID, RepoID: v.RepoID,
RepoName: v.RepoName,
RepoName: repoName,
Selected: selected, Selected: selected,
}) })
} }
@@ -141,8 +146,8 @@ func GetAllOfficialTagRepos(orgID int64, isOwner bool) ([]TagsDetail, error) {
return result, nil return result, nil
} }


func GetOfficialTagDetail(orgID, tagId int64) ([]Repository, error) {
t := make([]Repository, 0)
func GetOfficialTagDetail(orgID, tagId int64) ([]*Repository, error) {
t := make([]*Repository, 0)
const SQLCmd = "select t2.* from official_tag_repos t1 inner join repository t2 on t1.repo_id = t2.id where t1.org_id = ? and t1.tag_id=? order by t2.updated_unix desc" const SQLCmd = "select t2.* from official_tag_repos t1 inner join repository t2 on t1.repo_id = t2.id where t1.org_id = ? and t1.tag_id=? order by t2.updated_unix desc"


if err := x.SQL(SQLCmd, orgID, tagId).Find(&t); err != nil { if err := x.SQL(SQLCmd, orgID, tagId).Find(&t); err != nil {


+ 4
- 1
models/repo_watch.go View File

@@ -305,7 +305,10 @@ func NotifyWatchersActions(acts []*Action) error {
return err return err
} }
} }
return sess.Commit()

err := sess.Commit()
producer(acts...)
return err
} }


func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error { func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error {


+ 9
- 0
models/user.go View File

@@ -177,6 +177,10 @@ type User struct {
//BlockChain //BlockChain
PublicKey string `xorm:"INDEX"` PublicKey string `xorm:"INDEX"`
PrivateKey string `xorm:"INDEX"` PrivateKey string `xorm:"INDEX"`

//Wechat
WechatOpenId string `xorm:"INDEX"`
WechatBindUnix timeutil.TimeStamp
} }


// SearchOrganizationsOptions options to filter organizations // SearchOrganizationsOptions options to filter organizations
@@ -185,6 +189,11 @@ type SearchOrganizationsOptions struct {
All bool All bool
} }


// GenerateRandomAvatar generates a random avatar for user.
func (u *User) IsBindWechat() bool {
return u.WechatOpenId != ""
}

// ColorFormat writes a colored string to identify this struct // ColorFormat writes a colored string to identify this struct
func (u *User) ColorFormat(s fmt.State) { func (u *User) ColorFormat(s fmt.State) {
log.ColorFprintf(s, "%d:%s", log.ColorFprintf(s, "%d:%s",


+ 1
- 1
models/user_business_analysis.go View File

@@ -387,7 +387,7 @@ func refreshUserStaticTable(wikiCountMap map[string]int, CommitCodeSizeMap map[s


OpenIIndexMap := queryUserRepoOpenIIndex(startTime.Unix(), end_unix) OpenIIndexMap := queryUserRepoOpenIIndex(startTime.Unix(), end_unix)


DataDate := currentTimeNow.Format("2006-01-02")
DataDate := currentTimeNow.Format("2006-01-02") + " 00:01"


cond := "type != 1 and is_active=true" cond := "type != 1 and is_active=true"
count, err := sess.Where(cond).Count(new(User)) count, err := sess.Where(cond).Count(new(User))


+ 98
- 0
models/wechat_bind.go View File

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

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"time"
)

type WechatBindAction int

const (
WECHAT_BIND WechatBindAction = iota + 1
WECHAT_UNBIND
)

type WechatBindLog struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX"`
WechatOpenId string `xorm:"INDEX"`
Action int
CreateTime time.Time `xorm:"INDEX created"`
}

func BindWechatOpenId(userId int64, wechatOpenId string) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

param := &User{WechatOpenId: wechatOpenId, WechatBindUnix: timeutil.TimeStampNow()}
n, err := sess.Where("ID = ?", userId).Update(param)
if err != nil {
log.Error("update wechat_open_id failed,e=%v", err)
if e := sess.Rollback(); e != nil {
log.Error("BindWechatOpenId: sess.Rollback: %v", e)
}
return err
}
if n == 0 {
log.Error("update wechat_open_id failed,user not exist,userId=%d", userId)
if e := sess.Rollback(); e != nil {
log.Error("BindWechatOpenId: sess.Rollback: %v", e)
}
return nil
}

logParam := &WechatBindLog{
UserID: userId,
WechatOpenId: wechatOpenId,
Action: int(WECHAT_BIND),
}
sess.Insert(logParam)
return sess.Commit()
}

func GetUserWechatOpenId(userId int64) string {
param := &User{}
x.Cols("wechat_open_id").Where("ID =?", userId).Get(param)
return param.WechatOpenId
}

func GetUserByWechatOpenId(wechatOpenId string) *User {
user := &User{}
x.Where("wechat_open_id = ?", wechatOpenId).Get(user)
return user
}

func UnbindWechatOpenId(userId int64, oldWechatOpenID string) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

n, err := x.Table(new(User)).Where("ID = ? AND wechat_open_id =?", userId, oldWechatOpenID).Update(map[string]interface{}{"wechat_open_id": "", "wechat_bind_unix": nil})
if err != nil {
log.Error("update wechat_open_id failed,e=%v", err)
if e := sess.Rollback(); e != nil {
log.Error("UnbindWechatOpenId: sess.Rollback: %v", e)
}
return err
}
if n == 0 {
log.Error("update wechat_open_id failed,user not exist,userId=%d", userId)
if e := sess.Rollback(); e != nil {
log.Error("UnbindWechatOpenId: sess.Rollback: %v", e)
}
return nil
}
logParam := &WechatBindLog{
UserID: userId,
WechatOpenId: oldWechatOpenID,
Action: int(WECHAT_UNBIND),
}
sess.Insert(logParam)
return sess.Commit()
}

+ 13
- 8
modules/auth/cloudbrain.go View File

@@ -6,14 +6,19 @@ import (
) )


type CreateCloudBrainForm struct { type CreateCloudBrainForm struct {
JobName string `form:"job_name" binding:"Required"`
Image string `form:"image" binding:"Required"`
Command string `form:"command" binding:"Required"`
Attachment string `form:"attachment" binding:"Required"`
JobType string `form:"job_type" binding:"Required"`
BenchmarkCategory string `form:"get_benchmark_category"`
GpuType string `form:"gpu_type"`
ResourceSpecId int `form:"resource_spec_id" binding:"Required"`
JobName string `form:"job_name" binding:"Required"`
Image string `form:"image" binding:"Required"`
Command string `form:"command" binding:"Required"`
Attachment string `form:"attachment" binding:"Required"`
JobType string `form:"job_type" binding:"Required"`
BenchmarkCategory string `form:"get_benchmark_category"`
GpuType string `form:"gpu_type"`
TrainUrl string `form:"train_url"`
TestUrl string `form:"test_url"`
Description string `form:"description"`
ResourceSpecId int `form:"resource_spec_id" binding:"Required"`
BenchmarkTypeID int `form:"benchmark_types_id"`
BenchmarkChildTypeID int `form:"benchmark_child_types_id"`
} }


type CommitImageCloudBrainForm struct { type CommitImageCloudBrainForm struct {


+ 26
- 1
modules/auth/modelarts.go View File

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


func (f *CreateModelArtsNotebookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateModelArtsNotebookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
@@ -45,6 +46,30 @@ type CreateModelArtsTrainJobForm struct {
EngineName string `form:"engine_names" binding:"Required"` EngineName string `form:"engine_names" binding:"Required"`
} }


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

func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) return validate(errs, ctx.Data, f, ctx.Locale)
} }

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

@@ -728,3 +728,15 @@ type DeadlineForm struct {
func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) return validate(errs, ctx.Data, f, ctx.Locale)
} }

type CreateCourseForm struct {
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"`
Topics string
Description string `binding:"MaxSize(1024)"`
}

// Validate validates the fields
func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

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

@@ -105,6 +105,11 @@ func (f RegisterForm) IsEmailDomainWhitelisted() bool {
} }


domain := strings.ToLower(f.Email[n+1:]) domain := strings.ToLower(f.Email[n+1:])
//support edu.cn
if strings.HasSuffix(domain, "edu.cn") {
return true
}


for _, v := range setting.Service.EmailDomainWhitelist { for _, v := range setting.Service.EmailDomainWhitelist {
if strings.ToLower(v) == domain { if strings.ToLower(v) == domain {


+ 67
- 0
modules/auth/wechat/access_token.go View File

@@ -0,0 +1,67 @@
package wechat

import (
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock"
"time"
)

const EMPTY_REDIS_VAL = "Nil"

var accessTokenLock = redis_lock.NewDistributeLock()

func GetWechatAccessToken() string {
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" {
if token == EMPTY_REDIS_VAL {
return ""
}
live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey())
//refresh wechat access token when expire time less than 5 minutes
if live > 0 && live < 300 {
refreshAccessToken()
}
return token
}
return refreshAndGetAccessToken()
}

func refreshAccessToken() {
if ok := accessTokenLock.Lock(redis_key.AccessTokenLockKey(), 3*time.Second); ok {
defer accessTokenLock.UnLock(redis_key.AccessTokenLockKey())
callAccessTokenAndUpdateCache()
}
}

func refreshAndGetAccessToken() string {
if ok := accessTokenLock.LockWithWait(redis_key.AccessTokenLockKey(), 3*time.Second, 3*time.Second); ok {
defer accessTokenLock.UnLock(redis_key.AccessTokenLockKey())
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" {
if token == EMPTY_REDIS_VAL {
return ""
}
return token
}
return callAccessTokenAndUpdateCache()
}
return ""

}

func callAccessTokenAndUpdateCache() string {
r := callAccessToken()

var token string
if r != nil {
token = r.Access_token
}

if token == "" {
redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second)
return ""
}
redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second)
return token
}

+ 71
- 0
modules/auth/wechat/bind.go View File

@@ -0,0 +1,71 @@
package wechat

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"fmt"
)

type QRCode4BindCache struct {
UserId int64
Status int
}

const (
BIND_STATUS_UNBIND = 0
BIND_STATUS_SCANNED = 1
BIND_STATUS_BOUND = 2
BIND_STATUS_EXPIRED = 9
)

const (
BIND_REPLY_SUCCESS = "扫码成功,您可以使用OpenI启智社区算力环境。"
BIND_REPLY_WECHAT_ACCOUNT_USED = "认证失败,您的微信号已绑定其他启智账号"
BIND_REPLY_OPENI_ACCOUNT_USED = "认证失败,您待认证的启智账号已绑定其他微信号"
BIND_REPLY_FAILED_DEFAULT = "微信认证失败"
)

type WechatBindError struct {
Reply string
}

func NewWechatBindError(reply string) WechatBindError {
return WechatBindError{Reply: reply}
}

func (err WechatBindError) Error() string {
return fmt.Sprint("wechat bind error,reply=%s", err.Reply)
}

func BindWechat(userId int64, wechatOpenId string) error {
if !IsWechatAccountAvailable(userId, wechatOpenId) {
log.Error("bind wechat failed, because user use wrong wechat account to bind,userId=%d wechatOpenId=%s", userId, wechatOpenId)
return NewWechatBindError(BIND_REPLY_WECHAT_ACCOUNT_USED)
}
if !IsUserAvailableForWechatBind(userId, wechatOpenId) {
log.Error("openI account has been used,userId=%d wechatOpenId=%s", userId, wechatOpenId)
return NewWechatBindError(BIND_REPLY_OPENI_ACCOUNT_USED)
}
return models.BindWechatOpenId(userId, wechatOpenId)
}

func UnbindWechat(userId int64, oldWechatOpenId string) error {
return models.UnbindWechatOpenId(userId, oldWechatOpenId)
}

//IsUserAvailableForWechatBind if user has bound wechat and the bound openId is not the given wechatOpenId,return false
//otherwise,return true
func IsUserAvailableForWechatBind(userId int64, wechatOpenId string) bool {
currentOpenId := models.GetUserWechatOpenId(userId)
return currentOpenId == "" || currentOpenId == wechatOpenId
}

//IsWechatAccountAvailable if wechat account used by another account,return false
//if wechat account not used or used by the given user,return true
func IsWechatAccountAvailable(userId int64, wechatOpenId string) bool {
user := models.GetUserByWechatOpenId(wechatOpenId)
if user != nil && user.WechatOpenId != "" && user.ID != userId {
return false
}
return true
}

+ 5
- 0
modules/auth/wechat/call.go View File

@@ -0,0 +1,5 @@
package wechat

type WechatCall interface {
call()
}

+ 126
- 0
modules/auth/wechat/client.go View File

@@ -0,0 +1,126 @@
package wechat

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"encoding/json"
"fmt"
"github.com/go-resty/resty/v2"
"strconv"
"time"
)

var (
client *resty.Client
)

const (
GRANT_TYPE = "client_credential"
ACCESS_TOKEN_PATH = "/cgi-bin/token"
QR_CODE_Path = "/cgi-bin/qrcode/create"
ACTION_QR_STR_SCENE = "QR_STR_SCENE"

ERR_CODE_ACCESSTOKEN_EXPIRE = 42001
ERR_CODE_ACCESSTOKEN_INVALID = 40001
)

type AccessTokenResponse struct {
Access_token string
Expires_in int
}

type QRCodeResponse struct {
Ticket string `json:"ticket"`
Expire_Seconds int `json:"expire_seconds"`
Url string `json:"url"`
}

type QRCodeRequest struct {
Action_name string `json:"action_name"`
Action_info ActionInfo `json:"action_info"`
Expire_seconds int `json:"expire_seconds"`
}

type ActionInfo struct {
Scene Scene `json:"scene"`
}

type Scene struct {
Scene_str string `json:"scene_str"`
}

type ErrorResponse struct {
Errcode int
Errmsg string
}

func getWechatRestyClient() *resty.Client {
if client == nil {
client = resty.New()
client.SetTimeout(time.Duration(setting.WechatApiTimeoutSeconds) * time.Second)
}
return client
}

// api doc:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
func callAccessToken() *AccessTokenResponse {
client := getWechatRestyClient()

var result AccessTokenResponse
_, err := client.R().
SetQueryParam("grant_type", GRANT_TYPE).
SetQueryParam("appid", setting.WechatAppId).
SetQueryParam("secret", setting.WechatAppSecret).
SetResult(&result).
Get(setting.WechatApiHost + ACCESS_TOKEN_PATH)
if err != nil {
log.Error("get wechat access token failed,e=%v", err)
return nil
}
return &result
}

//callQRCodeCreate call the wechat api to create qr-code,
// api doc: https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) {
client := getWechatRestyClient()

body := &QRCodeRequest{
Action_name: ACTION_QR_STR_SCENE,
Action_info: ActionInfo{Scene: Scene{Scene_str: sceneStr}},
Expire_seconds: setting.WechatQRCodeExpireSeconds,
}
bodyJson, _ := json.Marshal(body)
var result QRCodeResponse
r, err := client.R().
SetHeader("Content-Type", "application/json").
SetQueryParam("access_token", GetWechatAccessToken()).
SetBody(bodyJson).
SetResult(&result).
Post(setting.WechatApiHost + QR_CODE_Path)
if err != nil {
log.Error("create QR code failed,e=%v", err)
return nil, false
}
errCode := getErrorCodeFromResponse(r)
if errCode == ERR_CODE_ACCESSTOKEN_EXPIRE || errCode == ERR_CODE_ACCESSTOKEN_INVALID {
return nil, true
}
if result.Url == "" {
return nil, false
}
log.Info("%v", r)
return &result, false
}

func getErrorCodeFromResponse(r *resty.Response) int {
a := r.Body()
resultMap := make(map[string]interface{}, 0)
json.Unmarshal(a, &resultMap)
code := resultMap["errcode"]
if code == nil {
return -1
}
c, _ := strconv.Atoi(fmt.Sprint(code))
return c
}

+ 76
- 0
modules/auth/wechat/event_handle.go View File

@@ -0,0 +1,76 @@
package wechat

import (
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"encoding/json"
"encoding/xml"
"strings"
"time"
)

//<xml>
// <ToUserName><![CDATA[toUser]]></ToUserName>
// <FromUserName><![CDATA[FromUser]]></FromUserName>
// <CreateTime>123456789</CreateTime>
// <MsgType><![CDATA[event]]></MsgType>
// <Event><![CDATA[SCAN]]></Event>
// <EventKey><![CDATA[SCENE_VALUE]]></EventKey>
// <Ticket><![CDATA[TICKET]]></Ticket>
//</xml>
type WechatEvent struct {
ToUserName string
FromUserName string
CreateTime int64
MsgType string
Event string
EventKey string
Ticket string
}

type EventReply struct {
XMLName xml.Name `xml:"xml"`
ToUserName string
FromUserName string
CreateTime int64
MsgType string
Content string
}

const (
WECHAT_EVENT_SUBSCRIBE = "subscribe"
WECHAT_EVENT_SCAN = "SCAN"
)

const (
WECHAT_MSG_TYPE_TEXT = "text"
)

func HandleSubscribeEvent(we WechatEvent) string {
eventKey := we.EventKey
if eventKey == "" {
return ""
}
sceneStr := strings.TrimPrefix(eventKey, "qrscene_")
key := redis_key.WechatBindingUserIdKey(sceneStr)
val, _ := redis_client.Get(key)
if val == "" {
return ""
}
qrCache := new(QRCode4BindCache)
json.Unmarshal([]byte(val), qrCache)
if qrCache.Status == BIND_STATUS_UNBIND {
err := BindWechat(qrCache.UserId, we.FromUserName)
if err != nil {
if err, ok := err.(WechatBindError); ok {
return err.Reply
}
return BIND_REPLY_FAILED_DEFAULT
}
qrCache.Status = BIND_STATUS_BOUND
jsonStr, _ := json.Marshal(qrCache)
redis_client.Setex(redis_key.WechatBindingUserIdKey(sceneStr), string(jsonStr), 60*time.Second)
}

return BIND_REPLY_SUCCESS
}

+ 13
- 0
modules/auth/wechat/qr_code.go View File

@@ -0,0 +1,13 @@
package wechat

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

func GetWechatQRCode4Bind(sceneStr string) *QRCodeResponse {
result, retryFlag := callQRCodeCreate(sceneStr)
if retryFlag {
log.Info("retry wechat qr-code calling,sceneStr=%s", sceneStr)
refreshAccessToken()
result, _ = callQRCodeCreate(sceneStr)
}
return result
}

+ 49
- 34
modules/cloudbrain/cloudbrain.go View File

@@ -1,24 +1,30 @@
package cloudbrain package cloudbrain


import ( import (
"code.gitea.io/gitea/modules/storage"
"encoding/json" "encoding/json"
"errors" "errors"
"strconv" "strconv"


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


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
) )


const ( const (
Command = `pip3 install jupyterlab==2.2.5 -i https://pypi.tuna.tsinghua.edu.cn/simple;service ssh stop;jupyter lab --no-browser --ip=0.0.0.0 --allow-root --notebook-dir="/code" --port=80 --LabApp.token="" --LabApp.allow_origin="self https://cloudbrain.pcl.ac.cn"`
Command = `pip3 install jupyterlab==2.2.5 -i https://pypi.tuna.tsinghua.edu.cn/simple;
service ssh stop;
jupyter lab --no-browser --ip=0.0.0.0 --allow-root --notebook-dir="/code" --port=80 --LabApp.token="" --LabApp.allow_origin="self https://cloudbrain.pcl.ac.cn"`
//CommandBenchmark = `echo "start benchmark";python /code/test.py;echo "end benchmark"`
CommandBenchmark = `echo "start benchmark";cd /benchmark && bash run_bk.sh;echo "end benchmark"`
CodeMountPath = "/code" CodeMountPath = "/code"
DataSetMountPath = "/dataset" DataSetMountPath = "/dataset"
ModelMountPath = "/model" ModelMountPath = "/model"
BenchMarkMountPath = "/benchmark" BenchMarkMountPath = "/benchmark"
BenchMarkResourceID = 1
Snn4imagenetMountPath = "/snn4imagenet" Snn4imagenetMountPath = "/snn4imagenet"
BrainScoreMountPath = "/brainscore" BrainScoreMountPath = "/brainscore"
TaskInfoName = "/taskInfo" TaskInfoName = "/taskInfo"
@@ -102,7 +108,7 @@ func AdminOrJobCreaterRight(ctx *context.Context) {


} }


func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, brainScorePath, jobType, gpuQueue string, resourceSpecId int) error {
func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, brainScorePath, jobType, gpuQueue, description string, benchmarkTypeID, benchmarkChildTypeID, resourceSpecId int) error {
dataActualPath := setting.Attachment.Minio.RealPath + dataActualPath := setting.Attachment.Minio.RealPath +
setting.Attachment.Minio.Bucket + "/" + setting.Attachment.Minio.Bucket + "/" +
setting.Attachment.Minio.BasePath + setting.Attachment.Minio.BasePath +
@@ -201,25 +207,34 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath,


var jobID = jobResult.Payload["jobId"].(string) var jobID = jobResult.Payload["jobId"].(string)
err = models.CreateCloudbrain(&models.Cloudbrain{ err = models.CreateCloudbrain(&models.Cloudbrain{
Status: string(models.JobWaiting),
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
JobID: jobID,
JobName: jobName,
SubTaskName: SubTaskName,
JobType: jobType,
Type: models.TypeCloudBrainOne,
Uuid: uuid,
Image: image,
GpuQueue: gpuQueue,
ResourceSpecId: resourceSpecId,
ComputeResource: models.GPUResource,
Status: string(models.JobWaiting),
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
JobID: jobID,
JobName: jobName,
SubTaskName: SubTaskName,
JobType: jobType,
Type: models.TypeCloudBrainOne,
Uuid: uuid,
Image: image,
GpuQueue: gpuQueue,
ResourceSpecId: resourceSpecId,
ComputeResource: models.GPUResource,
BenchmarkTypeID: benchmarkTypeID,
BenchmarkChildTypeID: benchmarkChildTypeID,
Description: description,
}) })


if err != nil { if err != nil {
return err return err
} }


if string(models.JobTypeBenchmark) == jobType {
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, jobID, jobName, models.ActionCreateBenchMarkTask)
} else {
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, jobID, jobName, models.ActionCreateDebugGPUTask)
}

return nil return nil
} }


@@ -270,7 +285,7 @@ func RestartTask(ctx *context.Context, task *models.Cloudbrain, newJobID *string
Volumes: []models.Volume{ Volumes: []models.Volume{
{ {
HostPath: models.StHostPath{ HostPath: models.StHostPath{
Path: storage.GetMinioPath(jobName, CodeMountPath + "/"),
Path: storage.GetMinioPath(jobName, CodeMountPath+"/"),
MountPath: CodeMountPath, MountPath: CodeMountPath,
ReadOnly: false, ReadOnly: false,
}, },
@@ -284,28 +299,28 @@ func RestartTask(ctx *context.Context, task *models.Cloudbrain, newJobID *string
}, },
{ {
HostPath: models.StHostPath{ HostPath: models.StHostPath{
Path: storage.GetMinioPath(jobName, ModelMountPath + "/"),
Path: storage.GetMinioPath(jobName, ModelMountPath+"/"),
MountPath: ModelMountPath, MountPath: ModelMountPath,
ReadOnly: false, ReadOnly: false,
}, },
}, },
{ {
HostPath: models.StHostPath{ HostPath: models.StHostPath{
Path: storage.GetMinioPath(jobName, BenchMarkMountPath + "/"),
Path: storage.GetMinioPath(jobName, BenchMarkMountPath+"/"),
MountPath: BenchMarkMountPath, MountPath: BenchMarkMountPath,
ReadOnly: true, ReadOnly: true,
}, },
}, },
{ {
HostPath: models.StHostPath{ HostPath: models.StHostPath{
Path: storage.GetMinioPath(jobName, Snn4imagenetMountPath + "/"),
Path: storage.GetMinioPath(jobName, Snn4imagenetMountPath+"/"),
MountPath: Snn4imagenetMountPath, MountPath: Snn4imagenetMountPath,
ReadOnly: true, ReadOnly: true,
}, },
}, },
{ {
HostPath: models.StHostPath{ HostPath: models.StHostPath{
Path: storage.GetMinioPath(jobName, BrainScoreMountPath + "/"),
Path: storage.GetMinioPath(jobName, BrainScoreMountPath+"/"),
MountPath: BrainScoreMountPath, MountPath: BrainScoreMountPath,
ReadOnly: true, ReadOnly: true,
}, },
@@ -323,18 +338,18 @@ func RestartTask(ctx *context.Context, task *models.Cloudbrain, newJobID *string


var jobID = jobResult.Payload["jobId"].(string) var jobID = jobResult.Payload["jobId"].(string)
newTask := &models.Cloudbrain{ newTask := &models.Cloudbrain{
Status: string(models.JobWaiting),
UserID: task.UserID,
RepoID: task.RepoID,
JobID: jobID,
JobName: task.JobName,
SubTaskName: task.SubTaskName,
JobType: task.JobType,
Type: task.Type,
Uuid: task.Uuid,
Image: task.Image,
GpuQueue: task.GpuQueue,
ResourceSpecId: task.ResourceSpecId,
Status: string(models.JobWaiting),
UserID: task.UserID,
RepoID: task.RepoID,
JobID: jobID,
JobName: task.JobName,
SubTaskName: task.SubTaskName,
JobType: task.JobType,
Type: task.Type,
Uuid: task.Uuid,
Image: task.Image,
GpuQueue: task.GpuQueue,
ResourceSpecId: task.ResourceSpecId,
ComputeResource: task.ComputeResource, ComputeResource: task.ComputeResource,
} }




+ 101
- 0
modules/cloudbrain/resty.go View File

@@ -2,7 +2,10 @@ package cloudbrain


import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http"
"strconv"
"strings" "strings"


"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@@ -23,6 +26,8 @@ const (
JobHasBeenStopped = "S410" JobHasBeenStopped = "S410"
Public = "public" Public = "public"
Custom = "custom" Custom = "custom"
LogPageSize = 500
LogPageTokenExpired = "5m"
) )


func getRestyClient() *resty.Client { func getRestyClient() *resty.Client {
@@ -270,3 +275,99 @@ sendjob:


return nil return nil
} }

func GetJobLog(jobID string) (*models.GetJobLogResult, error) {
checkSetting()
client := getRestyClient()
var result models.GetJobLogResult
req := models.GetJobLogParams{
Size: strconv.Itoa(LogPageSize),
Sort: "log.offset",
QueryInfo: models.QueryInfo{
MatchInfo: models.MatchInfo{
PodName: jobID + "-task1-0",
},
},
}

res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetBody(req).
SetResult(&result).
Post(HOST + "es/_search?_source=message&scroll=" + LogPageTokenExpired)

if err != nil {
log.Error("GetJobLog failed: %v", err)
return &result, fmt.Errorf("resty GetJobLog: %v, %s", err, res.String())
}

if !strings.Contains(res.Status(), strconv.Itoa(http.StatusOK)) {
log.Error("res.Status(): %s, response: %s", res.Status(), res.String())
return &result, errors.New(res.String())
}

return &result, nil
}

func GetJobAllLog(scrollID string) (*models.GetJobLogResult, error) {
checkSetting()
client := getRestyClient()
var result models.GetJobLogResult
req := models.GetAllJobLogParams{
Scroll: LogPageTokenExpired,
ScrollID: scrollID,
}

res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetBody(req).
SetResult(&result).
Post(HOST + "es/_search/scroll")

if err != nil {
log.Error("GetJobAllLog failed: %v", err)
return &result, fmt.Errorf("resty GetJobAllLog: %v, %s", err, res.String())
}

if !strings.Contains(res.Status(), strconv.Itoa(http.StatusOK)) {
log.Error("res.Status(): %s, response: %s", res.Status(), res.String())
return &result, errors.New(res.String())
}

return &result, nil
}

func DeleteJobLogToken(scrollID string) (error) {
checkSetting()
client := getRestyClient()
var result models.DeleteJobLogTokenResult
req := models.DeleteJobLogTokenParams{
ScrollID: scrollID,
}

res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetBody(req).
SetResult(&result).
Delete(HOST + "es/_search/scroll")

if err != nil {
log.Error("DeleteJobLogToken failed: %v", err)
return fmt.Errorf("resty DeleteJobLogToken: %v, %s", err, res.String())
}

if !strings.Contains(res.Status(), strconv.Itoa(http.StatusOK)) {
log.Error("res.Status(): %s, response: %s", res.Status(), res.String())
return errors.New(res.String())
}

if !result.Succeeded {
log.Error("DeleteJobLogToken failed")
return errors.New("DeleteJobLogToken failed")
}

return nil
}

+ 66
- 9
modules/context/auth.go View File

@@ -6,12 +6,14 @@
package context package context


import ( import (
"encoding/base64"
"net/http"
"strings"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"encoding/base64"
"net/http"


"gitea.com/macaron/csrf" "gitea.com/macaron/csrf"
"gitea.com/macaron/macaron" "gitea.com/macaron/macaron"
@@ -21,12 +23,14 @@ import (


// ToggleOptions contains required or check options // ToggleOptions contains required or check options
type ToggleOptions struct { type ToggleOptions struct {
SignInRequired bool
SignOutRequired bool
AdminRequired bool
DisableCSRF bool
BasicAuthRequired bool
OperationRequired bool
SignInRequired bool
SignOutRequired bool
AdminRequired bool
DisableCSRF bool
BasicAuthRequired bool
OperationRequired bool
WechatAuthRequired bool
WechatAuthRequiredForAPI bool
} }


// Toggle returns toggle options as middleware // Toggle returns toggle options as middleware
@@ -64,6 +68,11 @@ func Toggle(options *ToggleOptions) macaron.Handler {
ctx.Redirect(setting.AppSubURL + "/") ctx.Redirect(setting.AppSubURL + "/")
return return
} }

if ctx.QueryBool("course") {
ctx.Redirect(setting.AppSubURL + "/" + setting.Course.OrgName)
return
}
} }


// Redirect to dashboard if user tries to visit any non-login page. // Redirect to dashboard if user tries to visit any non-login page.
@@ -89,7 +98,14 @@ func Toggle(options *ToggleOptions) macaron.Handler {
return return
} }


ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
tempUrl := ctx.Req.URL.RequestURI()

if strings.Contains(tempUrl, "action/star?") || strings.Contains(tempUrl, "action/watch?") {
redirectForStarAndWatch(ctx, tempUrl)

} else {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
}
ctx.Redirect(setting.AppSubURL + "/user/login") ctx.Redirect(setting.AppSubURL + "/user/login")
return return
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm { } else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
@@ -121,6 +137,36 @@ func Toggle(options *ToggleOptions) macaron.Handler {
} }
} }


if setting.WechatAuthSwitch && options.WechatAuthRequired {
if !ctx.IsSigned {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}
if ctx.User.WechatOpenId == "" {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
ctx.Redirect(setting.AppSubURL + "/authentication/wechat/bind")
}
}

if setting.WechatAuthSwitch && options.WechatAuthRequiredForAPI {
if !ctx.IsSigned {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}
if ctx.User.WechatOpenId == "" {
redirectUrl := ctx.Query("redirect_to")
if redirectUrl == "" {
redirectUrl = ctx.Req.URL.RequestURI()
}
ctx.SetCookie("redirect_to", setting.AppSubURL+redirectUrl, 0, setting.AppSubURL)
ctx.JSON(200, map[string]string{
"WechatRedirectUrl": setting.AppSubURL + "/authentication/wechat/bind",
})
}
}

// Redirect to log in page if auto-signin info is provided and has not signed in. // Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !ctx.IsSigned && !auth.IsAPIPath(ctx.Req.URL.Path) && if !options.SignOutRequired && !ctx.IsSigned && !auth.IsAPIPath(ctx.Req.URL.Path) &&
len(ctx.GetCookie(setting.CookieUserName)) > 0 { len(ctx.GetCookie(setting.CookieUserName)) > 0 {
@@ -154,6 +200,17 @@ func Toggle(options *ToggleOptions) macaron.Handler {
} }
} }


func redirectForStarAndWatch(ctx *Context, tempUrl string) {
splits := strings.Split(tempUrl, "?")
if len(splits) > 1 {
redirectArguments := strings.Split(splits[1], "=")

if len(redirectArguments) > 0 && redirectArguments[0] == "redirect_to" {
ctx.SetCookie("redirect_to", setting.AppSubURL+strings.Replace(redirectArguments[1], "%2f", "/", -1), 0, setting.AppSubURL)
}
}
}

func basicAuth(ctx *Context) bool { func basicAuth(ctx *Context) bool {
var siteAuth = base64.StdEncoding.EncodeToString([]byte(setting.CBAuthUser + ":" + setting.CBAuthPassword)) var siteAuth = base64.StdEncoding.EncodeToString([]byte(setting.CBAuthUser + ":" + setting.CBAuthPassword))
auth := ctx.Req.Header.Get("Authorization") auth := ctx.Req.Header.Get("Authorization")


+ 21
- 6
modules/context/context.go View File

@@ -45,8 +45,8 @@ type Context struct {
IsSigned bool IsSigned bool
IsBasicAuth bool IsBasicAuth bool


Repo *Repository
Org *Organization
Repo *Repository
Org *Organization
Cloudbrain *models.Cloudbrain Cloudbrain *models.Cloudbrain
} }


@@ -328,7 +328,7 @@ func Contexter() macaron.Handler {
} }
} }


ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
//ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)


ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken()) ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken())
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`) ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
@@ -347,10 +347,25 @@ func Contexter() macaron.Handler {
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn


notice, _ := notice.GetNewestNotice()
if notice != nil {
ctx.Data["notice"] = *notice
notices, _ := notice.GetNewestNotice()
if notices != nil {
ctx.Data["notices"] = notices
} }
c.Map(ctx) c.Map(ctx)
} }
} }

// CheckWechatBind
func (ctx *Context) CheckWechatBind() {
if !setting.WechatAuthSwitch || ctx.User.IsBindWechat() {
return
}
redirectUrl := ctx.Query("redirect_to")
if redirectUrl == "" {
redirectUrl = ctx.Req.URL.RequestURI()
}
ctx.SetCookie("redirect_to", setting.AppSubURL+redirectUrl, 0, setting.AppSubURL)
ctx.JSON(200, map[string]string{
"WechatRedirectUrl": setting.AppSubURL + "/authentication/wechat/bind",
})
}

+ 2
- 0
modules/context/org.go View File

@@ -63,6 +63,8 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
org := ctx.Org.Organization org := ctx.Org.Organization
ctx.Data["Org"] = org ctx.Data["Org"] = org


ctx.Data["IsCourse"] = ctx.Org.Organization.Name == setting.Course.OrgName

// Force redirection when username is actually a user. // Force redirection when username is actually a user.
if !org.IsOrganization() { if !org.IsOrganization() {
ctx.Redirect(setting.AppSubURL + "/" + org.Name) ctx.Redirect(setting.AppSubURL + "/" + org.Name)


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

@@ -402,6 +402,7 @@ func RepoAssignment() macaron.Handler {
} }
ctx.Repo.Owner = owner ctx.Repo.Owner = owner
ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["IsCourse"] = owner.Name == setting.Course.OrgName


// Get repository. // Get repository.
repo, err := models.GetRepositoryByName(owner.ID, repoName) repo, err := models.GetRepositoryByName(owner.ID, repoName)


+ 198
- 10
modules/modelarts/modelarts.go View File

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


import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"path" "path"
"strconv" "strconv"
@@ -9,14 +10,16 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
) )


const ( const (
//notebook //notebook
storageTypeOBS = "obs"
autoStopDuration = 4 * 60 * 60
storageTypeOBS = "obs"
autoStopDuration = 4 * 60 * 60
autoStopDurationMs = 4 * 60 * 60 * 1000


DataSetMountPath = "/home/ma-user/work" DataSetMountPath = "/home/ma-user/work"
NotebookEnv = "Python3" NotebookEnv = "Python3"
@@ -38,6 +41,7 @@ const (
// "]}" // "]}"
CodePath = "/code/" CodePath = "/code/"
OutputPath = "/output/" OutputPath = "/output/"
ResultPath = "/result/"
LogPath = "/log/" LogPath = "/log/"
JobPath = "/job/" JobPath = "/job/"
OrderDesc = "desc" //向下查询 OrderDesc = "desc" //向下查询
@@ -45,6 +49,8 @@ const (
Lines = 500 Lines = 500
TrainUrl = "train_url" TrainUrl = "train_url"
DataUrl = "data_url" DataUrl = "data_url"
ResultUrl = "result_url"
CkptUrl = "ckpt_url"
PerPage = 10 PerPage = 10
IsLatestVersion = "1" IsLatestVersion = "1"
NotLatestVersion = "0" NotLatestVersion = "0"
@@ -59,6 +65,7 @@ const (
var ( var (
poolInfos *models.PoolInfos poolInfos *models.PoolInfos
FlavorInfos *models.FlavorInfos FlavorInfos *models.FlavorInfos
ImageInfos *models.ImageInfosModelArts
) )


type GenerateTrainJobReq struct { type GenerateTrainJobReq struct {
@@ -113,6 +120,36 @@ type GenerateTrainJobVersionReq struct {
TotalVersionCount int TotalVersionCount int
} }


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

type VersionInfo struct { type VersionInfo struct {
Version []struct { Version []struct {
ID int `json:"id"` ID int `json:"id"`
@@ -225,7 +262,58 @@ func GenerateTask(ctx *context.Context, jobName, uuid, description, flavor strin
if err != nil { if err != nil {
return err return err
} }
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, jobResult.ID, jobName, models.ActionCreateDebugNPUTask)
return nil
}

func GenerateNotebook2(ctx *context.Context, jobName, uuid, description, flavor, imageId string) error {
if poolInfos == nil {
json.Unmarshal([]byte(setting.PoolInfos), &poolInfos)
}

imageName, err := GetNotebookImageName(imageId)
if err != nil {
log.Error("GetNotebookImageName failed: %v", err.Error())
return err
}


jobResult, err := createNotebook2(models.CreateNotebook2Params{
JobName: jobName,
Description: description,
Flavor: flavor,
Duration: autoStopDurationMs,
ImageID: imageId,
PoolID: poolInfos.PoolInfo[0].PoolId,
Feature: models.NotebookFeature,
Volume: models.VolumeReq{
Capacity: setting.Capacity,
Category: models.EVSCategory,
Ownership: models.ManagedOwnership,
},
WorkspaceID: "0",
})
if err != nil {
log.Error("createNotebook2 failed: %v", err.Error())
return err
}
err = models.CreateCloudbrain(&models.Cloudbrain{
Status: jobResult.Status,
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
JobID: jobResult.ID,
JobName: jobName,
JobType: string(models.JobTypeDebug),
Type: models.TypeCloudBrainTwo,
Uuid: uuid,
ComputeResource: models.NPUResource,
Image: imageName,
Description: description,
})

if err != nil {
return err
}
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, jobResult.ID, jobName, models.ActionCreateDebugNPUTask)
return nil return nil
} }


@@ -259,12 +347,12 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error
log.Error("GetAttachmentByUUID(%s) failed:%v", strconv.FormatInt(jobResult.JobID, 10), err.Error()) log.Error("GetAttachmentByUUID(%s) failed:%v", strconv.FormatInt(jobResult.JobID, 10), err.Error())
return err return err
} }
jobId := strconv.FormatInt(jobResult.JobID, 10)
err = models.CreateCloudbrain(&models.Cloudbrain{ err = models.CreateCloudbrain(&models.Cloudbrain{
Status: TransTrainJobStatus(jobResult.Status), Status: TransTrainJobStatus(jobResult.Status),
UserID: ctx.User.ID, UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
JobID: strconv.FormatInt(jobResult.JobID, 10),
JobID: jobId,
JobName: req.JobName, JobName: req.JobName,
JobType: string(models.JobTypeTrain), JobType: string(models.JobTypeTrain),
Type: models.TypeCloudBrainTwo, Type: models.TypeCloudBrainTwo,
@@ -295,7 +383,7 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error
log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error()) log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error())
return err return err
} }
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, jobId, req.JobName, models.ActionCreateTrainTask)
return nil return nil
} }


@@ -329,12 +417,14 @@ func GenerateTrainJobVersion(ctx *context.Context, req *GenerateTrainJobReq, job
return err return err
} }


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


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

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

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

if err != nil {
log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error())
return err
}
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, jobID, req.JobName, models.ActionCreateInferenceTask)
return nil
}

func GetNotebookImageName(imageId string) (string, error) {
var validImage = false
var imageName = ""

if ImageInfos == nil {
json.Unmarshal([]byte(setting.ImageInfos), &ImageInfos)
}

for _, imageInfo := range ImageInfos.ImageInfo {
if imageInfo.Id == imageId {
validImage = true
imageName = imageInfo.Value
}
}

if !validImage {
log.Error("the image id(%s) is invalid", imageId)
return imageName, errors.New("the image id is invalid")
}

return imageName, nil
}

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

@@ -28,6 +28,14 @@ const (
urlResourceSpecs = "/job/resource-specs" urlResourceSpecs = "/job/resource-specs"
urlTrainJobConfig = "/training-job-configs" urlTrainJobConfig = "/training-job-configs"
errorCodeExceedLimit = "ModelArts.0118" errorCodeExceedLimit = "ModelArts.0118"

//notebook 2.0
urlNotebook2 = "/notebooks"

//error code
modelartsIllegalToken = "ModelArts.6401"
NotebookNotFound = "ModelArts.6404"
NotebookNoPermission = "ModelArts.6403"
) )


func getRestyClient() *resty.Client { func getRestyClient() *resty.Client {
@@ -174,6 +182,50 @@ sendjob:
return &result, nil return &result, nil
} }


func GetNotebook2(jobID string) (*models.GetNotebook2Result, error) {
checkSetting()
client := getRestyClient()
var result models.GetNotebook2Result

retry := 0

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

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

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

var response models.NotebookResult
err = json.Unmarshal(res.Body(), &response)
if err != nil {
log.Error("json.Unmarshal failed: %s", err.Error())
return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error())
}

if len(response.ErrorCode) != 0 {
log.Error("GetJob failed(%s): %s", response.ErrorCode, response.ErrorMsg)
if response.ErrorCode == modelartsIllegalToken && retry < 1 {
retry++
_ = getToken()
goto sendjob
}
return &result, fmt.Errorf("GetJob failed(%s): %s", response.ErrorCode, response.ErrorMsg)
}

return &result, nil
}

func ManageNotebook(jobID string, param models.NotebookAction) (*models.NotebookActionResult, error) { func ManageNotebook(jobID string, param models.NotebookAction) (*models.NotebookActionResult, error) {
checkSetting() checkSetting()
client := getRestyClient() client := getRestyClient()
@@ -214,6 +266,50 @@ sendjob:
return &result, nil return &result, nil
} }


func ManageNotebook2(jobID string, param models.NotebookAction) (*models.NotebookActionResult, error) {
checkSetting()
client := getRestyClient()
var result models.NotebookActionResult

retry := 0

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetResult(&result).
Post(HOST + "/v1/" + setting.ProjectID + urlNotebook2 + "/" + jobID + "/" + param.Action + "?duration=" + strconv.Itoa(autoStopDurationMs))

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

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

var response models.NotebookResult
err = json.Unmarshal(res.Body(), &response)
if err != nil {
log.Error("json.Unmarshal failed: %s", err.Error())
return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error())
}

if len(response.ErrorCode) != 0 {
log.Error("ManageNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg)
if response.ErrorCode == modelartsIllegalToken && retry < 1 {
retry++
_ = getToken()
goto sendjob
}
return &result, fmt.Errorf("ManageNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg)
}

return &result, nil
}

func DelNotebook(jobID string) (*models.NotebookDelResult, error) { func DelNotebook(jobID string) (*models.NotebookDelResult, error) {
checkSetting() checkSetting()
client := getRestyClient() client := getRestyClient()
@@ -253,6 +349,50 @@ sendjob:
return &result, nil return &result, nil
} }


func DelNotebook2(jobID string) (*models.NotebookDelResult, error) {
checkSetting()
client := getRestyClient()
var result models.NotebookDelResult

retry := 0

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

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

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

var response models.NotebookResult
err = json.Unmarshal(res.Body(), &response)
if err != nil {
log.Error("json.Unmarshal failed: %s", err.Error())
return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error())
}

if len(response.ErrorCode) != 0 {
log.Error("DelNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg)
if response.ErrorCode == modelartsIllegalToken && retry < 1 {
retry++
_ = getToken()
goto sendjob
}
return &result, fmt.Errorf("DelNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg)
}

return &result, nil
}

func DelJob(jobID string) (*models.NotebookDelResult, error) { func DelJob(jobID string) (*models.NotebookDelResult, error) {
checkSetting() checkSetting()
client := getRestyClient() client := getRestyClient()
@@ -874,3 +1014,107 @@ sendjob:


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

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

retry := 0

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

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

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

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

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

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

return &result, nil
}

func createNotebook2(createJobParams models.CreateNotebook2Params) (*models.CreateNotebookResult, error) {
checkSetting()
client := getRestyClient()
var result models.CreateNotebookResult

retry := 0

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

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

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

var response models.NotebookResult
err = json.Unmarshal(res.Body(), &response)
if err != nil {
log.Error("json.Unmarshal failed: %s", err.Error())
return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error())
}

if len(response.ErrorCode) != 0 {
log.Error("createNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg)
if response.ErrorCode == errorCodeExceedLimit {
response.ErrorMsg = "所选规格使用数量已超过最大配额限制。"
}
if response.ErrorCode == modelartsIllegalToken && retry < 1 {
retry++
_ = getToken()
goto sendjob
}
return &result, fmt.Errorf("createNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg)
}

return &result, nil
}

+ 31
- 0
modules/notification/action/action.go View File

@@ -154,6 +154,22 @@ func (a *actionNotifier) NotifyRenameRepository(doer *models.User, repo *models.
} }
} }


func (a *actionNotifier) NotifyAliasRepository(doer *models.User, repo *models.Repository, oldAlias string) {
log.Trace("action.ChangeRepositoryAlias: %s/%s", doer.Name, repo.Alias)

if err := models.NotifyWatchers(&models.Action{
ActUserID: doer.ID,
ActUser: doer,
OpType: models.ActionRenameRepo,
RepoID: repo.ID,
Repo: repo,
IsPrivate: repo.IsPrivate,
Content: oldAlias,
}); err != nil {
log.Error("NotifyWatchers: %v", err)
}
}

func (a *actionNotifier) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) { func (a *actionNotifier) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) {
if err := models.NotifyWatchers(&models.Action{ if err := models.NotifyWatchers(&models.Action{
ActUserID: doer.ID, ActUserID: doer.ID,
@@ -314,3 +330,18 @@ func (a *actionNotifier) NotifySyncDeleteRef(doer *models.User, repo *models.Rep
log.Error("notifyWatchers: %v", err) log.Error("notifyWatchers: %v", err)
} }
} }

func (a *actionNotifier) NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType) {
if err := models.NotifyWatchers(&models.Action{
ActUserID: doer.ID,
ActUser: doer,
OpType: optype,
RepoID: repo.ID,
Repo: repo,
IsPrivate: repo.IsPrivate,
RefName: name,
Content: id,
}); err != nil {
log.Error("notifyWatchers: %v", err)
}
}

+ 3
- 0
modules/notification/base/notifier.go View File

@@ -18,6 +18,7 @@ type Notifier interface {
NotifyDeleteRepository(doer *models.User, repo *models.Repository) NotifyDeleteRepository(doer *models.User, repo *models.Repository)
NotifyForkRepository(doer *models.User, oldRepo, repo *models.Repository) NotifyForkRepository(doer *models.User, oldRepo, repo *models.Repository)
NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string) NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string)
NotifyAliasRepository(doer *models.User, repo *models.Repository, oldAlias string)
NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string)


NotifyNewIssue(*models.Issue) NotifyNewIssue(*models.Issue)
@@ -53,4 +54,6 @@ type Notifier interface {
NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits)
NotifySyncCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string) NotifySyncCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string)
NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string)

NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType)
} }

+ 8
- 0
modules/notification/base/null.go View File

@@ -135,6 +135,10 @@ func (*NullNotifier) NotifyDeleteRef(doer *models.User, repo *models.Repository,
func (*NullNotifier) NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string) { func (*NullNotifier) NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string) {
} }


func (a *NullNotifier) NotifyAliasRepository(doer *models.User, repo *models.Repository, oldAlias string) {

}

// NotifyTransferRepository places a place holder function // NotifyTransferRepository places a place holder function
func (*NullNotifier) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) { func (*NullNotifier) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) {
} }
@@ -150,3 +154,7 @@ func (*NullNotifier) NotifySyncCreateRef(doer *models.User, repo *models.Reposit
// NotifySyncDeleteRef places a place holder function // NotifySyncDeleteRef places a place holder function
func (*NullNotifier) NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) { func (*NullNotifier) NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) {
} }

func (*NullNotifier) NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType) {

}

+ 7
- 0
modules/notification/notification.go View File

@@ -37,6 +37,13 @@ func NewContext() {
RegisterNotifier(action.NewNotifier()) RegisterNotifier(action.NewNotifier())
} }


// NotifyUploadAttachment notifies attachment upload message to notifiers
func NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType) {
for _, notifier := range notifiers {
notifier.NotifyOtherTask(doer, repo, id, name, optype)
}
}

// NotifyCreateIssueComment notifies issue comment related message to notifiers // NotifyCreateIssueComment notifies issue comment related message to notifiers
func NotifyCreateIssueComment(doer *models.User, repo *models.Repository, func NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) { issue *models.Issue, comment *models.Comment) {


+ 87
- 0
modules/redis/redis_client/client.go View File

@@ -0,0 +1,87 @@
package redis_client

import (
"code.gitea.io/gitea/modules/labelmsg"
"fmt"
"github.com/gomodule/redigo/redis"
"math"
"strconv"
"time"
)

func Setex(key, value string, timeout time.Duration) (bool, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

seconds := int(math.Floor(timeout.Seconds()))
reply, err := redisClient.Do("SETEX", key, seconds, value)
if err != nil {
return false, err
}
if reply != "OK" {
return false, nil
}
return true, nil

}

func Setnx(key, value string, timeout time.Duration) (bool, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

seconds := int(math.Floor(timeout.Seconds()))
reply, err := redisClient.Do("SET", key, value, "NX", "EX", seconds)
if err != nil {
return false, err
}
if reply != "OK" {
return false, nil
}
return true, nil

}

func Get(key string) (string, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

reply, err := redisClient.Do("GET", key)
if err != nil {
return "", err
}
if reply == nil {
return "", err
}
s, _ := redis.String(reply, nil)
return s, nil

}

func Del(key string) (int, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

reply, err := redisClient.Do("DEL", key)
if err != nil {
return 0, err
}
if reply == nil {
return 0, err
}
s, _ := redis.Int(reply, nil)
return s, nil

}

func TTL(key string) (int, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

reply, err := redisClient.Do("TTL", key)
if err != nil {
return 0, err
}
n, _ := strconv.Atoi(fmt.Sprint(reply))
return n, nil

}

+ 16
- 0
modules/redis/redis_key/key_base.go View File

@@ -0,0 +1,16 @@
package redis_key

import "strings"

const KEY_SEPARATE = ":"

func KeyJoin(keys ...string) string {
var build strings.Builder
for _, v := range keys {
build.WriteString(v)
build.WriteString(KEY_SEPARATE)
}
s := build.String()
s = strings.TrimSuffix(s, KEY_SEPARATE)
return s
}

+ 14
- 0
modules/redis/redis_key/wechat_redis_key.go View File

@@ -0,0 +1,14 @@
package redis_key

const PREFIX = "wechat"

func WechatBindingUserIdKey(sceneStr string) string {
return KeyJoin(PREFIX, sceneStr, "scene_userId")
}

func WechatAccessTokenKey() string {
return KeyJoin(PREFIX, "access_token")
}
func AccessTokenLockKey() string {
return KeyJoin(PREFIX, "access_token_lock")
}

+ 40
- 0
modules/redis/redis_lock/lock.go View File

@@ -0,0 +1,40 @@
package redis_lock

import (
"code.gitea.io/gitea/modules/redis/redis_client"
"time"
)

type DistributeLock struct {
}

func NewDistributeLock() *DistributeLock {
return &DistributeLock{}
}

func (lock *DistributeLock) Lock(lockKey string, expireTime time.Duration) bool {
isOk, _ := redis_client.Setnx(lockKey, "", expireTime)
return isOk
}

func (lock *DistributeLock) LockWithWait(lockKey string, expireTime time.Duration, waitTime time.Duration) bool {
start := time.Now().Unix() * 1000
duration := waitTime.Milliseconds()
for {
isOk, _ := redis_client.Setnx(lockKey, "", expireTime)
if isOk {
return true
}
if time.Now().Unix()*1000-start > duration {
return false
}
time.Sleep(50 * time.Millisecond)
}

return false
}

func (lock *DistributeLock) UnLock(lockKey string) error {
_, err := redis_client.Del(lockKey)
return err
}

+ 10
- 1
modules/repository/create.go View File

@@ -22,6 +22,10 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
Limit: u.MaxRepoCreation, Limit: u.MaxRepoCreation,
} }
} }
var RepoType = models.RepoNormal
if opts.IsCourse {
RepoType = models.RepoCourse
}


repo := &models.Repository{ repo := &models.Repository{
OwnerID: u.ID, OwnerID: u.ID,
@@ -38,10 +42,15 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status, Status: opts.Status,
IsEmpty: !opts.AutoInit, IsEmpty: !opts.AutoInit,
RepoType: RepoType,
Topics: opts.Topics,
} }


err = models.WithTx(func(ctx models.DBContext) error { err = models.WithTx(func(ctx models.DBContext) error {
if err = models.CreateRepository(ctx, doer, u, repo); err != nil {
if err = models.CreateRepository(ctx, doer, u, repo, opts); err != nil {
return err
}
if err = models.SaveTopics(repo.ID, opts.Topics...); err != nil {
return err return err
} }




+ 2
- 2
modules/repository/fork.go View File

@@ -15,7 +15,7 @@ import (
) )


// ForkRepository forks a repository // ForkRepository forks a repository
func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc string) (_ *models.Repository, err error) {
func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc, alias string) (_ *models.Repository, err error) {
forkedRepo, err := oldRepo.GetUserFork(owner.ID) forkedRepo, err := oldRepo.GetUserFork(owner.ID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -33,7 +33,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name,
Owner: owner, Owner: owner,
OwnerName: owner.Name, OwnerName: owner.Name,
Name: name, Name: name,
Alias: oldRepo.Alias,
Alias: alias,
LowerName: strings.ToLower(name), LowerName: strings.ToLower(name),
Description: desc, Description: desc,
DefaultBranch: oldRepo.DefaultBranch, DefaultBranch: oldRepo.DefaultBranch,


+ 1
- 1
modules/repository/fork_test.go View File

@@ -18,7 +18,7 @@ func TestForkRepository(t *testing.T) {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User) user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository)


fork, err := ForkRepository(user, user, repo, "test", "test")
fork, err := ForkRepository(user, user, repo, "test", "test", "test")
assert.Nil(t, fork) assert.Nil(t, fork)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, models.IsErrForkAlreadyExist(err)) assert.True(t, models.IsErrForkAlreadyExist(err))


+ 1
- 1
modules/repository/init.go View File

@@ -51,7 +51,7 @@ func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, re


cloneLink := repo.CloneLink() cloneLink := repo.CloneLink()
match := map[string]string{ match := map[string]string{
"Name": repo.Name,
"Name": repo.DisplayName(),
"Description": repo.Description, "Description": repo.Description,
"CloneURL.SSH": cloneLink.SSH, "CloneURL.SSH": cloneLink.SSH,
"CloneURL.HTTPS": cloneLink.HTTPS, "CloneURL.HTTPS": cloneLink.HTTPS,


+ 44
- 5
modules/setting/setting.go View File

@@ -462,11 +462,15 @@ var (
MaxDuration int64 MaxDuration int64


//benchmark config //benchmark config
IsBenchmarkEnabled bool
BenchmarkOwner string
BenchmarkName string
BenchmarkServerHost string
BenchmarkCategory string
IsBenchmarkEnabled bool
BenchmarkOwner string
BenchmarkName string
BenchmarkServerHost string
BenchmarkCategory string
BenchmarkTypes string
BenchmarkGpuTypes string
BenchmarkResourceSpecs string
BenchmarkMaxDuration int64


//snn4imagenet config //snn4imagenet config
IsSnn4imagenetEnabled bool IsSnn4imagenetEnabled bool
@@ -508,6 +512,9 @@ var (
ProfileID string ProfileID string
PoolInfos string PoolInfos string
Flavor string Flavor string
DebugHost string
ImageInfos string
Capacity int
//train-job //train-job
ResourcePools string ResourcePools string
Engines string Engines string
@@ -524,6 +531,14 @@ var (
ElkTimeFormat string ElkTimeFormat string
PROJECT_LIMIT_PAGES []string PROJECT_LIMIT_PAGES []string


//wechat config
WechatApiHost string
WechatApiTimeoutSeconds int
WechatAppId string
WechatAppSecret string
WechatQRCodeExpireSeconds int
WechatAuthSwitch bool

//nginx proxy //nginx proxy
PROXYURL string PROXYURL string
RadarMap = struct { RadarMap = struct {
@@ -568,6 +583,11 @@ var (
}{} }{}


Warn_Notify_Mails []string Warn_Notify_Mails []string

Course = struct {
OrgName string
TeamName string
}{}
) )


// DateLang transforms standard language locale name to corresponding value in datetime plugin. // DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -1271,6 +1291,10 @@ func NewContext() {
BenchmarkName = sec.Key("NAME").MustString("") BenchmarkName = sec.Key("NAME").MustString("")
BenchmarkServerHost = sec.Key("HOST").MustString("") BenchmarkServerHost = sec.Key("HOST").MustString("")
BenchmarkCategory = sec.Key("CATEGORY").MustString("") BenchmarkCategory = sec.Key("CATEGORY").MustString("")
BenchmarkTypes = sec.Key("TYPES").MustString("")
BenchmarkGpuTypes = sec.Key("GPU_TYPES").MustString("")
BenchmarkResourceSpecs = sec.Key("RESOURCE_SPECS").MustString("")
BenchmarkMaxDuration = sec.Key("MAX_DURATION").MustInt64(14400)


sec = Cfg.Section("snn4imagenet") sec = Cfg.Section("snn4imagenet")
IsSnn4imagenetEnabled = sec.Key("ENABLED").MustBool(false) IsSnn4imagenetEnabled = sec.Key("ENABLED").MustBool(false)
@@ -1313,6 +1337,8 @@ func NewContext() {
ProfileID = sec.Key("PROFILE_ID").MustString("") ProfileID = sec.Key("PROFILE_ID").MustString("")
PoolInfos = sec.Key("POOL_INFOS").MustString("") PoolInfos = sec.Key("POOL_INFOS").MustString("")
Flavor = sec.Key("FLAVOR").MustString("") Flavor = sec.Key("FLAVOR").MustString("")
ImageInfos = sec.Key("IMAGE_INFOS").MustString("")
Capacity = sec.Key("IMAGE_INFOS").MustInt(100)
ResourcePools = sec.Key("Resource_Pools").MustString("") ResourcePools = sec.Key("Resource_Pools").MustString("")
Engines = sec.Key("Engines").MustString("") Engines = sec.Key("Engines").MustString("")
EngineVersions = sec.Key("Engine_Versions").MustString("") EngineVersions = sec.Key("Engine_Versions").MustString("")
@@ -1328,10 +1354,23 @@ func NewContext() {
ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time") ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time")
PROJECT_LIMIT_PAGES = strings.Split(sec.Key("project_limit_pages").MustString(""), ",") PROJECT_LIMIT_PAGES = strings.Split(sec.Key("project_limit_pages").MustString(""), ",")


sec = Cfg.Section("wechat")
WechatApiHost = sec.Key("HOST").MustString("https://api.weixin.qq.com")
WechatApiTimeoutSeconds = sec.Key("TIMEOUT_SECONDS").MustInt(3)
WechatAppId = sec.Key("APP_ID").MustString("wxba77b915a305a57d")
WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198")
WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120)
WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(true)

SetRadarMapConfig() SetRadarMapConfig()


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

sec = Cfg.Section("course")
Course.OrgName = sec.Key("org_name").MustString("")
Course.TeamName = sec.Key("team_name").MustString("")

} }


func SetRadarMapConfig() { func SetRadarMapConfig() {


+ 17
- 8
modules/storage/obs.go View File

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

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


//check if has the object //check if has the object
func ObsHasObject(path string) (bool, error) { func ObsHasObject(path string) (bool, error) {
@@ -50,8 +57,8 @@ func ObsHasObject(path string) (bool, error) {
return hasObject, nil return hasObject, nil
} }


func GetObsPartInfos(uuid string, uploadID string) (string, error) {
key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, uuid)), "/")
func GetObsPartInfos(uuid, uploadID, fileName string) (string, error) {
key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, fileName)), "/")


output, err := ObsCli.ListParts(&obs.ListPartsInput{ output, err := ObsCli.ListParts(&obs.ListPartsInput{
Bucket: setting.Bucket, Bucket: setting.Bucket,
@@ -333,7 +340,8 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er
input.MaxKeys = 100 input.MaxKeys = 100
input.Prefix = prefix input.Prefix = prefix
index := 1 index := 1
fileInfos := make([]FileInfo, 0)
fileInfoList := FileInfoList{}

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


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


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


+ 4
- 3
modules/structs/attachment.go View File

@@ -16,9 +16,10 @@ type Attachment struct {
Size int64 `json:"size"` Size int64 `json:"size"`
DownloadCount int64 `json:"download_count"` DownloadCount int64 `json:"download_count"`
// swagger:strfmt date-time // swagger:strfmt date-time
Created time.Time `json:"created_at"`
UUID string `json:"uuid"`
DownloadURL string `json:"browser_download_url"`
Created time.Time `json:"created_at"`
UUID string `json:"uuid"`
DownloadURL string `json:"browser_download_url"`
S3DownloadURL string
} }


// EditAttachmentOptions options for editing attachments // EditAttachmentOptions options for editing attachments


+ 5
- 0
modules/structs/repo.go View File

@@ -49,6 +49,7 @@ type Repository struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Owner *User `json:"owner"` Owner *User `json:"owner"`
Name string `json:"name"` Name string `json:"name"`
Alias string `json:"alias"`
FullName string `json:"full_name"` FullName string `json:"full_name"`
FullDisplayName string `json:"full_display_name"` FullDisplayName string `json:"full_display_name"`
Description string `json:"description"` Description string `json:"description"`
@@ -99,6 +100,10 @@ type CreateRepoOption struct {
// required: true // required: true
// unique: true // unique: true
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"`
// Alias of the repository to create
// required: false
// unique: true
Alias string `json:"alias" binding:"AlphaDashDotChinese;MaxSize(100)"`
// Description of the repository to create // Description of the repository to create
Description string `json:"description" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(255)"`
// Whether the repository is private // Whether the repository is private


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

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


+ 4
- 1
modules/timeutil/since.go View File

@@ -165,5 +165,8 @@ func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML {
func TimeSinceUnix1(then TimeStamp) string { func TimeSinceUnix1(then TimeStamp) string {
format := time.Unix(int64(then), 0).Format("2006-01-02 15:04:05") format := time.Unix(int64(then), 0).Format("2006-01-02 15:04:05")
return format return format
}
func TimeSinceUnixShort(then TimeStamp) string {
format := time.Unix(int64(then), 0).Format("2006-01-02")
return format
} }

+ 3
- 0
modules/validation/binding.go View File

@@ -128,6 +128,9 @@ func addAlphaDashDotChineseRule() {
return strings.HasPrefix(rule, "AlphaDashDotChinese") return strings.HasPrefix(rule, "AlphaDashDotChinese")
}, },
IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) {
if val == "" {
return true, errs
}
if !ValidAlphaDashDotChinese(fmt.Sprintf("%v", val)) { if !ValidAlphaDashDotChinese(fmt.Sprintf("%v", val)) {
errs.Add([]string{name}, ErrAlphaDashDotChinese, "ErrAlphaDashDotChinese") errs.Add([]string{name}, ErrAlphaDashDotChinese, "ErrAlphaDashDotChinese")
return false, errs return false, errs


+ 125
- 34
options/locale/locale_en-US.ini View File

@@ -50,6 +50,8 @@ repository = Repository
organization = Organization organization = Organization
mirror = Mirror mirror = Mirror
new_repo = New Repository new_repo = New Repository
new_course=Publish Course
course_desc = Course Description
new_migrate = New Migration new_migrate = New Migration
new_dataset = New Dataset new_dataset = New Dataset
edit_dataset = Edit Dataset edit_dataset = Edit Dataset
@@ -219,46 +221,49 @@ show_only_public = Showing only public


issues.in_your_repos = In your repositories issues.in_your_repos = In your repositories
contributors = Contributors contributors = Contributors
contributor = Contributor


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


[explore] [explore]
repos = Repositories repos = Repositories
select_repos = Select the project select_repos = Select the project
users = Users users = Users
organizations = Organizations organizations = Organizations
images = CloudImages
images = Cloudbrain Mirror
search = Search search = Search
search_pro=Search projects
code = Code code = Code
data_analysis=Digital Bulletin Board (test)
repo_no_results = No matching repositories found. repo_no_results = No matching repositories found.
dataset_no_results = No matching datasets found. dataset_no_results = No matching datasets found.
user_no_results = No matching users found. user_no_results = No matching users found.
@@ -266,8 +271,8 @@ org_no_results = No matching organizations found.
code_no_results = No source code matching your search term found. code_no_results = No source code matching your search term found.
code_search_results = Search results for '%s' code_search_results = Search results for '%s'
code_last_indexed_at = Last indexed %s code_last_indexed_at = Last indexed %s
save=save
cancel=cancel
save=Save
cancel=Cancel


[auth] [auth]
create_new_account = Register Account create_new_account = Register Account
@@ -321,7 +326,7 @@ openid_register_title = Create new account
openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here. openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here.
openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry. openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry.
disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator. disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator.
email_domain_blacklisted = You cannot register with your email address.
email_domain_blacklisted = You cannot register with this kind of email address.
authorize_application = Authorize Application authorize_application = Authorize Application
authorize_redirect_notice = You will be redirected to %s if you authorize this application. authorize_redirect_notice = You will be redirected to %s if you authorize this application.
authorize_application_created_by = This application was created by %s. authorize_application_created_by = This application was created by %s.
@@ -347,7 +352,11 @@ modify = Update
[form] [form]
UserName = Username UserName = Username
Alias = Repository name Alias = Repository name
RepoName = Repository path
courseAlias = Course Name
courseAdress = Course Path
RepoPath = Repository path
RepoAdress = Repository Adress
course_Adress = Course Address
Email = Email address Email = Email address
Password = Password Password = Password
Retype = Re-Type Password Retype = Re-Type Password
@@ -371,6 +380,8 @@ SSPIDefaultLanguage = Default Language
require_error = ` cannot be empty.` require_error = ` cannot be empty.`
alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.`
alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.`
reponame_dash_dot_error=` Please enter Chinese, alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. `
repoadd_dash_dot_error=` Path only allows input alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. `
git_ref_name_error = ` must be a well-formed Git reference name.` git_ref_name_error = ` must be a well-formed Git reference name.`
alpha_dash_dot_chinese_error= ` should contain only alphanumeric, chinese, dash ('-') and underscore ('_') characters.` alpha_dash_dot_chinese_error= ` should contain only alphanumeric, chinese, dash ('-') and underscore ('_') characters.`
size_error = ` must be size %s.` size_error = ` must be size %s.`
@@ -386,7 +397,8 @@ password_not_match = The passwords do not match.
lang_select_error = Select a language from the list. lang_select_error = Select a language from the list.


username_been_taken = The username is already taken. username_been_taken = The username is already taken.
repo_name_been_taken = The repository name is already used.
repo_name_been_taken = The repository name or path is already used.
course_name_been_taken=The course name or path is already used.
visit_rate_limit = Remote visit addressed rate limitation. visit_rate_limit = Remote visit addressed rate limitation.
2fa_auth_required = Remote visit required two factors authentication. 2fa_auth_required = Remote visit required two factors authentication.
org_name_been_taken = The organization name is already taken. org_name_been_taken = The organization name is already taken.
@@ -481,6 +493,14 @@ account_link = Linked Accounts
organization = Organizations organization = Organizations
uid = Uid uid = Uid
u2f = Security Keys u2f = Security Keys
bind_wechat = Bind WeChat
wechat_bind = WeChat Binding
bind_account_information = Bind account information
bind_time = Bind Time
wechat = Wechat
unbind_wc = Unbind
unbind_wechat = Are you sure you want to unbind WeChat?
unbind_computing = After unbundling, the qizhi computing power environment will not be available


public_profile = Public Profile public_profile = Public Profile
profile_desc = Your email address will be used for notifications and other operations. profile_desc = Your email address will be used for notifications and other operations.
@@ -632,7 +652,7 @@ oauth2_application_create_description = OAuth2 applications gives your third-par
oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue? oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue?


authorized_oauth2_applications = Authorized OAuth2 Applications authorized_oauth2_applications = Authorized OAuth2 Applications
authorized_oauth2_applications_description = You've granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed.
authorized_oauth2_applications_description = You have granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed.
revoke_key = Revoke revoke_key = Revoke
revoke_oauth2_grant = Revoke Access revoke_oauth2_grant = Revoke Access
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure? revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure?
@@ -791,6 +811,7 @@ generate_from = Generate From
repo_desc = Description repo_desc = Description
repo_lang = Language repo_lang = Language
repo_gitignore_helper = Select .gitignore templates. repo_gitignore_helper = Select .gitignore templates.
repo_label_helpe = Press Enter to complete
issue_labels = Issue Labels issue_labels = Issue Labels
issue_labels_helper = Select an issue label set. issue_labels_helper = Select an issue label set.
license = License license = License
@@ -799,6 +820,8 @@ readme = README
readme_helper = Select a README file template. readme_helper = Select a README file template.
auto_init = Initialize Repository (Adds .gitignore, License and README) auto_init = Initialize Repository (Adds .gitignore, License and README)
create_repo = Create Repository create_repo = Create Repository
create_course = Publish Course
failed_to_create_course=Fail to publish course, please try again later.
default_branch = Default Branch default_branch = Default Branch
mirror_prune = Prune mirror_prune = Prune
mirror_prune_desc = Remove obsolete remote-tracking references mirror_prune_desc = Remove obsolete remote-tracking references
@@ -849,9 +872,13 @@ confirm_choice = confirm
cloudbran1_tips = Only data in zip format can create cloudbrain tasks cloudbran1_tips = Only data in zip format can create cloudbrain tasks
cloudbrain_creator=Creator cloudbrain_creator=Creator
cloudbrain_task = Task Name cloudbrain_task = Task Name
cloudbrain_task_type = Task Type
cloudbrain_task_name=Cloud Brain Task Name
cloudbrain_operate = Operate cloudbrain_operate = Operate
cloudbrain_status_createtime = Status/Createtime cloudbrain_status_createtime = Status/Createtime
cloudbrain_status_runtime = Running Time cloudbrain_status_runtime = Running Time
cloudbrain_jobname_err=Name must start with a lowercase letter or number,can include lowercase letter,number,_ and -,can not end with _, and can be up to 36 characters long.
cloudbrain_query_fail=Failed to query cloudbrain information.


record_begintime_get_err=Can not get the record begin time. record_begintime_get_err=Can not get the record begin time.
parameter_is_wrong=The input parameter is wrong. parameter_is_wrong=The input parameter is wrong.
@@ -862,6 +889,11 @@ get_repo_info_error=Can not get the information of the repository.
generate_statistic_file_error=Fail to generate file. generate_statistic_file_error=Fail to generate file.
repo_stat_inspect=ProjectAnalysis repo_stat_inspect=ProjectAnalysis
all=All all=All

computing.all = All
computing.Introduction=Introduction
computing.success=Join Success

modelarts.status=Status modelarts.status=Status
modelarts.createtime=CreateTime modelarts.createtime=CreateTime
modelarts.version_nums = Version Nums modelarts.version_nums = Version Nums
@@ -871,6 +903,7 @@ modelarts.notebook=Debug Task
modelarts.train_job=Train Task modelarts.train_job=Train Task
modelarts.train_job.new_debug= New Debug Task modelarts.train_job.new_debug= New Debug Task
modelarts.train_job.new_train=New Train Task modelarts.train_job.new_train=New Train Task
modelarts.train_job.new_infer=New Inference Task
modelarts.train_job.config=Configuration information modelarts.train_job.config=Configuration information
modelarts.train_job.new=New train Task modelarts.train_job.new=New train Task
modelarts.train_job.new_place=The description should not exceed 256 characters modelarts.train_job.new_place=The description should not exceed 256 characters
@@ -884,6 +917,8 @@ modelarts.parent_version=Parent Version
modelarts.run_version=Run Version modelarts.run_version=Run Version
modelarts.train_job.compute_node=Compute Node modelarts.train_job.compute_node=Compute Node
modelarts.create_model = Create Model modelarts.create_model = Create Model
modelarts.model_label=Model Label
modelarts.infer_dataset = Inference Dataset




modelarts.train_job.basic_info=Basic Info modelarts.train_job.basic_info=Basic Info
@@ -930,6 +965,21 @@ modelarts.train_job_para_admin=train_job_para_admin
modelarts.train_job_para.edit=train_job_para.edit modelarts.train_job_para.edit=train_job_para.edit
modelarts.train_job_para.connfirm=train_job_para.connfirm modelarts.train_job_para.connfirm=train_job_para.connfirm



modelarts.evaluate_job=Model Evaluation
modelarts.evaluate_job.new_job=New Model Evaluation
cloudbrain.benchmark.evaluate_type=Evaluation Type
cloudbrain.benchmark.evaluate_child_type=Child Type
cloudbrain.benchmark.evaluate_mirror=Mirror
cloudbrain.benchmark.evaluate_train=Train Script
cloudbrain.benchmark.evaluate_test=Test Script
modelarts.infer_job_model = Model
modelarts.infer_job_model_file = Model File
modelarts.infer_job = Inference Job
modelarts.infer_job.model_version = Model/Version
modelarts.infer_job.select_model = Select Model
modelarts.infer_job.tooltip = The model has been deleted and cannot be viewed.

model.manage.import_new_model=Import New Model model.manage.import_new_model=Import New Model
model.manage.create_error=Equal Name and Version has existed. model.manage.create_error=Equal Name and Version has existed.
model.manage.model_name = Model Name model.manage.model_name = Model Name
@@ -956,14 +1006,21 @@ template.avatar = Avatar
template.issue_labels = Issue Labels template.issue_labels = Issue Labels
template.one_item = Must select at least one template item template.one_item = Must select at least one template item
template.invalid = Must select a template repository template.invalid = Must select a template repository
template.repo_adress=Adress
template.repo_path=path
template.repo_name=Name


archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
archive.issue.nocomment = This repo is archived. You cannot comment on issues. archive.issue.nocomment = This repo is archived. You cannot comment on issues.
archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. archive.pull.nocomment = This repo is archived. You cannot comment on pull requests.


form.reach_limit_of_creation = You have already reached your limit of %d repositories. form.reach_limit_of_creation = You have already reached your limit of %d repositories.
form.reach_limit_of_course_creation=You have already reached your limit of %d courses or repositories.
form.name_reserved = The repository name '%s' is reserved. form.name_reserved = The repository name '%s' is reserved.
form.course_name_reserved=The course name '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
form.course_name_pattern_not_allowed=The pattern '%s' is not allowed in a course name.
add_course_org_fail=Fail to add organization, please try again later.


need_auth = Clone Authorization need_auth = Clone Authorization
migrate_type = Migration Type migrate_type = Migration Type
@@ -2008,11 +2065,15 @@ org_full_name_holder = Organization Full Name
org_name_helper = Organization names should be short and memorable. org_name_helper = Organization names should be short and memorable.
create_org = Create Organization create_org = Create Organization
repo_updated = Updated repo_updated = Updated
repo_released = Post
home = Home home = Home
people = People people = People
teams = Teams teams = Teams
lower_members = members lower_members = members
lower_repositories = repositories lower_repositories = repositories
lower_member=member
lower_repository = repository

create_new_team = New Team create_new_team = New Team
create_team = Create Team create_team = Create Team
org_desc = Description org_desc = Description
@@ -2024,6 +2085,17 @@ team_access_desc = Repository access
team_permission_desc = Permission team_permission_desc = Permission
team_unit_desc = Allow Access to Repository Sections team_unit_desc = Allow Access to Repository Sections
team_unit_disabled = (Disabled) team_unit_disabled = (Disabled)
selected_couse=Selected Courses
release_course = Publish Course
all_keywords=All keywords
max_selectedPro= Select up to 9 public projects
custom_select_courses = Customize selected courses
recommend_remain_pro = Remain
save_fail_tips = The upper limit is exceeded
select_again = Select more than 9, please select again!
custom_select_projects = Customize selected projects
customize = Customize
selected_project=Selected Projects


form.name_reserved = The organization name '%s' is reserved. form.name_reserved = The organization name '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name.
@@ -2067,6 +2139,8 @@ members.remove = Remove
members.leave = Leave members.leave = Leave
members.invite_desc = Add a new member to %s: members.invite_desc = Add a new member to %s:
members.invite_now = Invite Now members.invite_now = Invite Now
course_members.remove = Remove
course_members.leave = Leave


teams.join = Join teams.join = Join
teams.leave = Leave teams.leave = Leave
@@ -2109,6 +2183,7 @@ teams.all_repositories_helper = Team has access to all repositories. Selecting t
teams.all_repositories_read_permission_desc = This team grants <strong>Read</strong> access to <strong>all repositories</strong>: members can view and clone repositories. teams.all_repositories_read_permission_desc = This team grants <strong>Read</strong> access to <strong>all repositories</strong>: members can view and clone repositories.
teams.all_repositories_write_permission_desc = This team grants <strong>Write</strong> access to <strong>all repositories</strong>: members can read from and push to repositories. teams.all_repositories_write_permission_desc = This team grants <strong>Write</strong> access to <strong>all repositories</strong>: members can read from and push to repositories.
teams.all_repositories_admin_permission_desc = This team grants <strong>Admin</strong> access to <strong>all repositories</strong>: members can read from, push to and add collaborators to repositories. teams.all_repositories_admin_permission_desc = This team grants <strong>Admin</strong> access to <strong>all repositories</strong>: members can read from, push to and add collaborators to repositories.
teams.join_teams=Join in


[admin] [admin]
dashboard = Dashboard dashboard = Dashboard
@@ -2281,6 +2356,13 @@ datasets.owner=Owner
datasets.name=name datasets.name=name
datasets.private=Private datasets.private=Private


cloudbrain.all_task_types=All Task Types
cloudbrain.all_computing_resources=All Computing Resources
cloudbrain.all_status=All Status
cloudbrain.download_report=Download Report
cloudbrain.cloudbrain_name=Cloudbrain Name
cloudbrain.search = Seach Task Name/Creter

hooks.desc = Webhooks automatically make HTTP POST requests to a server when certain openi events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the <a target="_blank" rel="noopener" href="https://docs.openi.io/en-us/webhooks/">webhooks guide</a>. hooks.desc = Webhooks automatically make HTTP POST requests to a server when certain openi events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the <a target="_blank" rel="noopener" href="https://docs.openi.io/en-us/webhooks/">webhooks guide</a>.
hooks.add_webhook = Add Default Webhook hooks.add_webhook = Add Default Webhook
hooks.update_webhook = Update Default Webhook hooks.update_webhook = Update Default Webhook
@@ -2620,6 +2702,13 @@ mirror_sync_create = synced new reference <a href="%s/src/%s">%[2]s</a> to <a hr
mirror_sync_delete = synced and deleted reference <code>%[2]s</code> at <a href="%[1]s">%[3]s</a> from mirror mirror_sync_delete = synced and deleted reference <code>%[2]s</code> at <a href="%[1]s">%[3]s</a> from mirror
approve_pull_request = `approved <a href="%s/pulls/%s">%s#%[2]s</a>` approve_pull_request = `approved <a href="%s/pulls/%s">%s#%[2]s</a>`
reject_pull_request = `suggested changes for <a href="%s/pulls/%s">%s#%[2]s</a>` reject_pull_request = `suggested changes for <a href="%s/pulls/%s">%s#%[2]s</a>`
upload_dataset=`upload dataset <a href="%s/datasets?type=%s">%s</a>`
task_gpudebugjob=`created CPU/GPU type debugging task<a href="%s/cloudbrain/%s">%s</a>`
task_npudebugjob=`created NPU type debugging task <a href="%s/modelarts/notebook/%s">%s</a>`
task_trainjob=`created training task<a href="%s/modelarts/train-job/%s">%s</a>`
task_inferencejob=`created reasoning task <a href="%s/modelarts/inference-job/%s">%s</a>`
task_benchmark=`created profiling task <a href="%s/cloudbrain/benchmark/%s">%s</a>`
task_createmodel=`created new model <a href="%s/modelmanage/show_model_info?name=%s">%s</a>`


[tool] [tool]
ago = %s ago ago = %s ago
@@ -2689,10 +2778,12 @@ error.unit_not_allowed = You are not allowed to access this repository section.
head.community = Community head.community = Community
head.project = Repositories head.project = Repositories
head.openi = OpenI head.openi = OpenI
head.openi.repo = OpenI Projects
head.dataset = Datasets head.dataset = Datasets
foot.council = Council foot.council = Council
foot.technical_committee = Technical Committee foot.technical_committee = Technical Committee
foot.join = Join OpenI foot.join = Join OpenI
foot.agreement=Use agreement
foot.news = News foot.news = News
foot.community_news = Community News foot.community_news = Community News
foot.member_news = Member news foot.member_news = Member news


+ 97
- 6
options/locale/locale_zh-CN.ini View File

@@ -50,6 +50,8 @@ repository=项目
organization=组织 organization=组织
mirror=镜像 mirror=镜像
new_repo=创建项目 new_repo=创建项目
new_course=发布课程
course_desc=课程描述
new_dataset=创建数据集 new_dataset=创建数据集
new_migrate=迁移外部项目 new_migrate=迁移外部项目
edit_dataset = Edit Dataset edit_dataset = Edit Dataset
@@ -221,6 +223,7 @@ show_only_public=只显示公开的
issues.in_your_repos=属于该用户项目的 issues.in_your_repos=属于该用户项目的


contributors=贡献者 contributors=贡献者
contributor=贡献者


page_title=探索更好的AI page_title=探索更好的AI
page_small_title=启智AI开发协作平台 page_small_title=启智AI开发协作平台
@@ -325,7 +328,7 @@ openid_register_title=创建新帐户
openid_register_desc=所选的 OpenID URI 未知。在这里关联一个新帐户。 openid_register_desc=所选的 OpenID URI 未知。在这里关联一个新帐户。
openid_signin_desc=输入您的 OpenID URI。例如: https://anne.me、bob.openid.org.cn 或 gnusocial.net/carry。 openid_signin_desc=输入您的 OpenID URI。例如: https://anne.me、bob.openid.org.cn 或 gnusocial.net/carry。
disable_forgot_password_mail=帐户恢复功能已被禁用。请与网站管理员联系。 disable_forgot_password_mail=帐户恢复功能已被禁用。请与网站管理员联系。
email_domain_blacklisted=您不能使用您的电子邮件地址注册。
email_domain_blacklisted=暂不支持此类电子邮件地址注册。
authorize_application=应用授权 authorize_application=应用授权
authorize_redirect_notice=如果您授权此应用,您将会被重定向到 %s。 authorize_redirect_notice=如果您授权此应用,您将会被重定向到 %s。
authorize_application_created_by=此应用由%s创建。 authorize_application_created_by=此应用由%s创建。
@@ -352,6 +355,11 @@ modify=更新
UserName=用户名 UserName=用户名
RepoName=项目路径 RepoName=项目路径
Alias=项目名称 Alias=项目名称
courseAlias=课程名称
courseAdress=课程路径
RepoPath=项目路径
RepoAdress=项目地址
course_Adress = 课程地址
Email=邮箱地址 Email=邮箱地址
Password=密码 Password=密码
Retype=重新输入密码 Retype=重新输入密码
@@ -375,6 +383,8 @@ SSPIDefaultLanguage=默认语言
require_error=不能为空。 require_error=不能为空。
alpha_dash_error=应该只包含字母数字、破折号 ('-') 和下划线 ('_') 字符。 alpha_dash_error=应该只包含字母数字、破折号 ('-') 和下划线 ('_') 字符。
alpha_dash_dot_error=应该只包含字母数字, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。 alpha_dash_dot_error=应该只包含字母数字, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。
reponame_dash_dot_error=请输入中文、字母、数字和-_ .,最多100个字符。
repoadd_dash_dot_error=路径只允许字母、数字和-_ .,最多100个字符。
git_ref_name_error=` 必须是格式良好的 git 引用名称。` git_ref_name_error=` 必须是格式良好的 git 引用名称。`
alpha_dash_dot_chinese_error=应该只包含字母数字中文, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。 alpha_dash_dot_chinese_error=应该只包含字母数字中文, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。
size_error=长度必须为 %s。 size_error=长度必须为 %s。
@@ -390,7 +400,8 @@ password_not_match=密码不匹配。
lang_select_error=从列表中选出语言 lang_select_error=从列表中选出语言


username_been_taken=用户名已被使用。 username_been_taken=用户名已被使用。
repo_name_been_taken=项目名称已被使用。
repo_name_been_taken=项目名称或项目路径已被使用。
course_name_been_taken=课程名称或地址已被使用。
visit_rate_limit=远程访问达到速度限制。 visit_rate_limit=远程访问达到速度限制。
2fa_auth_required=远程访问需要双重验证。 2fa_auth_required=远程访问需要双重验证。
org_name_been_taken=组织名称已被使用。 org_name_been_taken=组织名称已被使用。
@@ -485,6 +496,14 @@ account_link=已绑定帐户
organization=组织 organization=组织
uid=用户 ID uid=用户 ID
u2f=安全密钥 u2f=安全密钥
wechat_bind = 微信绑定
bind_wechat = 绑定微信
bind_account_information = 绑定账号信息
bind_time = 绑定时间
wechat = 微信
unbind_wc = 解除绑定
unbind_wechat = 确定要解绑微信?
unbind_computing = 解绑后将无法使用启智算力环境


public_profile=公开信息 public_profile=公开信息
profile_desc=您的电子邮件地址将用于通知和其他操作。 profile_desc=您的电子邮件地址将用于通知和其他操作。
@@ -796,6 +815,7 @@ generate_from=生成自
repo_desc=项目描述 repo_desc=项目描述
repo_lang=项目语言 repo_lang=项目语言
repo_gitignore_helper=选择 .gitignore 模板。 repo_gitignore_helper=选择 .gitignore 模板。
repo_label_helpe=输入完成后回车键完成标签确定。
issue_labels=任务标签 issue_labels=任务标签
issue_labels_helper=选择一个任务标签集 issue_labels_helper=选择一个任务标签集
license=授权许可 license=授权许可
@@ -804,6 +824,8 @@ readme=自述
readme_helper=选择自述文件模板。 readme_helper=选择自述文件模板。
auto_init=初始化存储库 (添加. gitignore、许可证和自述文件) auto_init=初始化存储库 (添加. gitignore、许可证和自述文件)
create_repo=创建项目 create_repo=创建项目
create_course=发布课程
failed_to_create_course=发布课程失败,请稍后再试。
default_branch=默认分支 default_branch=默认分支
mirror_prune=修剪 mirror_prune=修剪
mirror_prune_desc=删除过时的远程跟踪引用 mirror_prune_desc=删除过时的远程跟踪引用
@@ -833,7 +855,7 @@ debug=调试
debug_again=再次调试 debug_again=再次调试
stop=停止 stop=停止
delete=删除 delete=删除
model_download=模型下载
model_download=结果下载
submit_image=提交镜像 submit_image=提交镜像
download=模型下载 download=模型下载


@@ -854,10 +876,13 @@ confirm_choice=确定
cloudbran1_tips=只有zip格式的数据集才能发起云脑任务 cloudbran1_tips=只有zip格式的数据集才能发起云脑任务
cloudbrain_creator=创建者 cloudbrain_creator=创建者
cloudbrain_task=任务名称 cloudbrain_task=任务名称
cloudbrain_task_type=任务类型
cloudbrain_task_name=云脑侧任务名称
cloudbrain_operate=操作 cloudbrain_operate=操作
cloudbrain_status_createtime=状态/创建时间 cloudbrain_status_createtime=状态/创建时间
cloudbrain_status_runtime = 运行时长 cloudbrain_status_runtime = 运行时长
cloudbrain_jobname_err=只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。 cloudbrain_jobname_err=只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。
cloudbrain_query_fail=查询云脑任务失败。


record_begintime_get_err=无法获取统计开始时间。 record_begintime_get_err=无法获取统计开始时间。
parameter_is_wrong=输入参数错误,请检查输入参数。 parameter_is_wrong=输入参数错误,请检查输入参数。
@@ -869,6 +894,10 @@ generate_statistic_file_error=生成文件失败。
repo_stat_inspect=项目分析 repo_stat_inspect=项目分析
all=所有 all=所有


computing.all=全部
computing.Introduction=简介
computing.success=加入成功

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






@@ -931,7 +963,7 @@ modelarts.train_job.NAS_mount_path=NAS挂载路径
modelarts.train_job.query_whether_save_parameter=保存作业参数 modelarts.train_job.query_whether_save_parameter=保存作业参数
modelarts.train_job.save_helper=保存当前作业的配置参数,后续您可以使用已保存的配置参数快速创建训练作业。 modelarts.train_job.save_helper=保存当前作业的配置参数,后续您可以使用已保存的配置参数快速创建训练作业。
modelarts.train_job.common_frame=常用框架 modelarts.train_job.common_frame=常用框架
modelarts.train_job.amount_of_compute_node=计算节点
modelarts.train_job.amount_of_compute_node=计算节点数
modelarts.train_job.job_parameter_name=任务参数名称 modelarts.train_job.job_parameter_name=任务参数名称
modelarts.train_job.parameter_description=任务参数描述 modelarts.train_job.parameter_description=任务参数描述
modelarts.log=日志 modelarts.log=日志
@@ -940,6 +972,22 @@ modelarts.back=返回
modelarts.train_job_para_admin=任务参数管理 modelarts.train_job_para_admin=任务参数管理
modelarts.train_job_para.edit=编辑 modelarts.train_job_para.edit=编辑
modelarts.train_job_para.connfirm=确定 modelarts.train_job_para.connfirm=确定
modelarts.evaluate_job=评测任务
modelarts.evaluate_job.new_job=新建评测任务
cloudbrain.benchmark.evaluate_type=评测类型
cloudbrain.benchmark.evaluate_child_type=子类型
cloudbrain.benchmark.evaluate_mirror=镜像
cloudbrain.benchmark.evaluate_train=训练程序
cloudbrain.benchmark.evaluate_test=测试程序


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


model.manage.import_new_model=导入新模型 model.manage.import_new_model=导入新模型
model.manage.create_error=相同的名称和版本的模型已经存在。 model.manage.create_error=相同的名称和版本的模型已经存在。
@@ -967,14 +1015,21 @@ template.avatar=头像
template.issue_labels=任务标签 template.issue_labels=任务标签
template.one_item=必须至少选择一个模板项 template.one_item=必须至少选择一个模板项
template.invalid=必须选择一个模板项目 template.invalid=必须选择一个模板项目
template.repo_adress=项目地址
template.repo_path=项目地址
template.repo_name=项目名称


archive.title=此项目已存档。您可以查看文件和克隆,但不能推送或创建任务/合并请求。 archive.title=此项目已存档。您可以查看文件和克隆,但不能推送或创建任务/合并请求。
archive.issue.nocomment=此项目已存档,您不能在此任务添加评论。 archive.issue.nocomment=此项目已存档,您不能在此任务添加评论。
archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。 archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。


form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。 form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。
form.reach_limit_of_course_creation=你已经达到了您的 %d 课程的限制。
form.name_reserved=项目名称 '%s' 是被保留的。 form.name_reserved=项目名称 '%s' 是被保留的。
form.course_name_reserved=课程名称 '%s' 是被保留的。
form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。 form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。
form.course_name_pattern_not_allowed=课程名称中不允许使用模式 "%s"。
add_course_org_fail=加入组织失败,请稍后重试。


need_auth=需要授权验证 need_auth=需要授权验证
migrate_type=迁移类型 migrate_type=迁移类型
@@ -2019,11 +2074,14 @@ org_full_name_holder=组织全名
org_name_helper=组织名字应该简单明了。 org_name_helper=组织名字应该简单明了。
create_org=创建组织 create_org=创建组织
repo_updated=最后更新于 repo_updated=最后更新于
repo_released=发布于
home=组织主页 home=组织主页
people=组织成员 people=组织成员
teams=组织团队 teams=组织团队
lower_members=名成员 lower_members=名成员
lower_repositories=个项目 lower_repositories=个项目
lower_member=名成员
lower_repository=个项目
create_new_team=新建团队 create_new_team=新建团队
create_team=创建团队 create_team=创建团队
org_desc=组织描述 org_desc=组织描述
@@ -2035,6 +2093,17 @@ team_access_desc=项目权限
team_permission_desc=权限 team_permission_desc=权限
team_unit_desc=允许访问项目单元 team_unit_desc=允许访问项目单元
team_unit_disabled=(已禁用) team_unit_disabled=(已禁用)
selected_couse=精选课程
release_course = 发布课程
all_keywords=全部关键字
max_selectedPro= 最多可选9个公开项目
custom_select_courses = 自定义精选课程
recommend_remain_pro = 还能推荐
save_fail_tips = 最多可选9个,保存失败
select_again = 选择超过9个,请重新选择!
custom_select_projects = 自定义精选项目
customize = 自定义
selected_project=精选项目


form.name_reserved=组织名称 '%s' 是被保留的。 form.name_reserved=组织名称 '%s' 是被保留的。
form.name_pattern_not_allowed=组织名称中不允许使用 "%s"。 form.name_pattern_not_allowed=组织名称中不允许使用 "%s"。
@@ -2078,6 +2147,8 @@ members.remove=移除成员
members.leave=离开组织 members.leave=离开组织
members.invite_desc=邀请新的用户加入 %s: members.invite_desc=邀请新的用户加入 %s:
members.invite_now=立即邀请 members.invite_now=立即邀请
course_members.remove=移除
course_members.leave=离开


teams.join=加入团队 teams.join=加入团队
teams.leave=离开团队 teams.leave=离开团队
@@ -2121,6 +2192,10 @@ teams.all_repositories_read_permission_desc=此团队授予<strong>读取</stron
teams.all_repositories_write_permission_desc=此团队授予<strong>修改</strong><strong>所有项目</strong>的访问权限: 成员可以查看和推送至项目。 teams.all_repositories_write_permission_desc=此团队授予<strong>修改</strong><strong>所有项目</strong>的访问权限: 成员可以查看和推送至项目。
teams.all_repositories_admin_permission_desc=该团队拥有 <strong>管理</strong> <strong>所有项目</strong>的权限:团队成员可以读取、克隆、推送以及添加其它项目协作者。 teams.all_repositories_admin_permission_desc=该团队拥有 <strong>管理</strong> <strong>所有项目</strong>的权限:团队成员可以读取、克隆、推送以及添加其它项目协作者。


teams.join_teams=加入该组织



[admin] [admin]
dashboard=管理面板 dashboard=管理面板
users=帐户管理 users=帐户管理
@@ -2290,6 +2365,13 @@ datasets.owner=所有者
datasets.name=名称 datasets.name=名称
datasets.private=私有 datasets.private=私有


cloudbrain.all_task_types=全部任务类型
cloudbrain.all_computing_resources=全部计算资源
cloudbrain.all_status=全部状态
cloudbrain.download_report=下载此报告
cloudbrain.cloudbrain_name=云脑侧名称
cloudbrain.search = 搜索任务名称/创建者

hooks.desc=当某些 openi 事件触发时, Web 钩子会自动向服务器发出 HTTP POST 请求。此处定义的 Web 钩子是默认值, 将复制到所有新建项目中。参阅 <a target="_blank" rel="noopener" href="https://docs.openi.io/en-us/webhooks/">Web钩子指南</a> 获取更多内容。 hooks.desc=当某些 openi 事件触发时, Web 钩子会自动向服务器发出 HTTP POST 请求。此处定义的 Web 钩子是默认值, 将复制到所有新建项目中。参阅 <a target="_blank" rel="noopener" href="https://docs.openi.io/en-us/webhooks/">Web钩子指南</a> 获取更多内容。
hooks.add_webhook=新增默认Web钩子 hooks.add_webhook=新增默认Web钩子
hooks.update_webhook=更新默认Web钩子 hooks.update_webhook=更新默认Web钩子
@@ -2356,7 +2438,7 @@ auths.sspi_auto_activate_users_helper=允许 SSPI 认证自动激活新用户
auths.sspi_strip_domain_names=从用户名中删除域名部分 auths.sspi_strip_domain_names=从用户名中删除域名部分
auths.sspi_strip_domain_names_helper=如果选中此项,域名将从登录名中删除(例如,"DOMAIN\user"和"user@example.org",两者都将变成只是“用户”)。 auths.sspi_strip_domain_names_helper=如果选中此项,域名将从登录名中删除(例如,"DOMAIN\user"和"user@example.org",两者都将变成只是“用户”)。
auths.sspi_separator_replacement=要使用的分隔符代替\, / 和 @ auths.sspi_separator_replacement=要使用的分隔符代替\, / 和 @
auths.sspi_separator_replacement_helper=用于替换下级登录名称分隔符的字符 (例如) "DOMAIN\user") 中的 \ 和用户主名字(如"user@example.org中的 @ )。
auths.sspi_separator_replacement_helper=用于替换下级登录名称分隔符的字符 (例如) "DOMAIN\user") 中的 \ 和用户主名字(如"user@example.org"中的 @ )。
auths.sspi_default_language=默认语言 auths.sspi_default_language=默认语言
auths.sspi_default_language_helper=SSPI 认证方法为用户自动创建的默认语言。如果您想要自动检测到语言,请留空。 auths.sspi_default_language_helper=SSPI 认证方法为用户自动创建的默认语言。如果您想要自动检测到语言,请留空。
auths.tips=帮助提示 auths.tips=帮助提示
@@ -2629,6 +2711,13 @@ mirror_sync_create=从镜像同步了新的引用 <a href="%s/src/%s">%[2]s</a>
mirror_sync_delete=从镜像同步并从 <a href="%[1]s">%[3]s</a> 删除了引用 <code>%[2]s</code> mirror_sync_delete=从镜像同步并从 <a href="%[1]s">%[3]s</a> 删除了引用 <code>%[2]s</code>
approve_pull_request=`同意了 <a href="%s/pulls/%s">%s#%[2]s</a>` approve_pull_request=`同意了 <a href="%s/pulls/%s">%s#%[2]s</a>`
reject_pull_request=`建议变更 <a href="%s/pulls/%s">%s#%[2]s</a>` reject_pull_request=`建议变更 <a href="%s/pulls/%s">%s#%[2]s</a>`
upload_dataset=`上传了数据集文件 <a href="%s/datasets?type=%s">%s</a>`
task_gpudebugjob=`创建了CPU/GPU类型调试任务 <a href="%s/cloudbrain/%s">%s</a>`
task_npudebugjob=`创建了NPU类型调试任务 <a href="%s/modelarts/notebook/%s">%s</a>`
task_trainjob=`创建了训练任务 <a href="%s/modelarts/train-job/%s">%s</a>`
task_inferencejob=`创建了推理任务 <a href="%s/modelarts/inference-job/%s">%s</a>`
task_benchmark=`创建了评测任务 <a href="%s/cloudbrain/benchmark/%s">%s</a>`
task_createmodel=`导入了新模型 <a href="%s/modelmanage/show_model_info?name=%s">%s</a>`


[tool] [tool]
ago=%s前 ago=%s前
@@ -2698,10 +2787,12 @@ error.unit_not_allowed=您没有权限访问此项目单元
head.community=启智社区 head.community=启智社区
head.project=项目 head.project=项目
head.openi=启智项目 head.openi=启智项目
head.openi.repo = 启智项目
head.dataset=数据集 head.dataset=数据集
foot.council=理事会 foot.council=理事会
foot.technical_committee=技术委员会 foot.technical_committee=技术委员会
foot.join=加入启智 foot.join=加入启智
foot.agreement=使用协议
foot.news=动态 foot.news=动态
foot.community_news=社区动态 foot.community_news=社区动态
foot.member_news=成员动态 foot.member_news=成员动态


+ 140
- 0
package-lock.json View File

@@ -7787,6 +7787,11 @@
} }
} }
}, },
"js-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz",
"integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw=="
},
"js-file-download": { "js-file-download": {
"version": "0.4.12", "version": "0.4.12",
"resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz",
@@ -11147,6 +11152,11 @@
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
}, },
"qrcodejs2": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/qrcodejs2/-/qrcodejs2-0.0.2.tgz",
"integrity": "sha1-Rlr+Xjnxn6zsuTLBH3oYYQkUauE="
},
"qs": { "qs": {
"version": "6.9.4", "version": "6.9.4",
"resolved": "https://registry.npm.taobao.org/qs/download/qs-6.9.4.tgz", "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.9.4.tgz",
@@ -13869,6 +13879,130 @@
"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==",
"dev": true "dev": true
}, },
"ts-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-4.0.0.tgz",
"integrity": "sha512-iissbnuJkqbB3YAmnWyEbmdNcGcoiiXopKHKyqdoCrFQVi9pnplXeveQDXJnQOCYNNcb2pjT2zzSYTX6c9QtAA==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
"enhanced-resolve": "^4.0.0",
"loader-utils": "^1.0.2",
"micromatch": "^3.1.4",
"semver": "^5.0.1"
},
"dependencies": {
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
}
},
"to-regex-range": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"dev": true,
"requires": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
}
}
}
},
"tslib": { "tslib": {
"version": "1.13.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
@@ -13928,6 +14062,12 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"typescript": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
"dev": true
},
"ua-parser-js": { "ua-parser-js": {
"version": "0.7.21", "version": "0.7.21",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",


+ 4
- 0
package.json View File

@@ -33,6 +33,7 @@
"jquery": "3.5.1", "jquery": "3.5.1",
"jquery-datetimepicker": "2.5.21", "jquery-datetimepicker": "2.5.21",
"jquery.are-you-sure": "1.9.0", "jquery.are-you-sure": "1.9.0",
"js-cookie": "3.0.1",
"less-loader": "6.1.0", "less-loader": "6.1.0",
"mini-css-extract-plugin": "0.9.0", "mini-css-extract-plugin": "0.9.0",
"monaco-editor": "0.20.0", "monaco-editor": "0.20.0",
@@ -41,6 +42,7 @@
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"postcss-preset-env": "6.7.0", "postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.2", "postcss-safe-parser": "4.0.2",
"qrcodejs2": "0.0.2",
"qs": "6.9.4", "qs": "6.9.4",
"remixicon": "2.5.0", "remixicon": "2.5.0",
"spark-md5": "3.0.1", "spark-md5": "3.0.1",
@@ -69,6 +71,8 @@
"script-loader": "0.7.2", "script-loader": "0.7.2",
"stylelint": "13.3.3", "stylelint": "13.3.3",
"stylelint-config-standard": "20.0.0", "stylelint-config-standard": "20.0.0",
"ts-loader": "4.0.0",
"typescript": "4.5.5",
"updates": "10.2.11" "updates": "10.2.11"
}, },
"browserslist": [ "browserslist": [


+ 145
- 105
public/home/home.js View File

@@ -4,10 +4,8 @@ if(isEmpty(token)){
var meta = $("meta[name=_uid]"); var meta = $("meta[name=_uid]");
if(!isEmpty(meta)){ if(!isEmpty(meta)){
token = meta.attr("content"); token = meta.attr("content");
console.log("token is uid:" + token);
} }
} }

var swiperNewMessage = new Swiper(".newslist", { var swiperNewMessage = new Swiper(".newslist", {
direction: "vertical", direction: "vertical",
slidesPerView: 10, slidesPerView: 10,
@@ -18,7 +16,7 @@ var swiperNewMessage = new Swiper(".newslist", {
}, },
}); });
var swiperRepo = new Swiper(".homepro-list", { var swiperRepo = new Swiper(".homepro-list", {
slidesPerView: 3,
slidesPerView: 1,
slidesPerColumn: 2, slidesPerColumn: 2,
slidesPerColumnFill:'row', slidesPerColumnFill:'row',
spaceBetween: 30, spaceBetween: 30,
@@ -30,33 +28,64 @@ var swiperRepo = new Swiper(".homepro-list", {
delay: 2500, delay: 2500,
disableOnInteraction: false, disableOnInteraction: false,
}, },
breakpoints: {
768: {
slidesPerView: 2,
},
1024: {
slidesPerView: 3,
},
},
});

var swiperOrg = new Swiper(".homeorg-list", {
slidesPerView: 1,
slidesPerColumn: 4,
slidesPerColumnFill:'row',
spaceBetween: 15,
pagination: {
el: ".swiper-pagination",
clickable: true,
},
autoplay: {
delay: 4500,
disableOnInteraction: false,
},
breakpoints: {
768: {
slidesPerView: 2,
},
1024: {
slidesPerView: 3,
},
},
}); });


var output = document.getElementById("newmessage"); var output = document.getElementById("newmessage");
var socket = new WebSocket("ws://" + document.location.host + "/action/notification");
var url = "ws://" + document.location.host + "/action/notification";
if(document.location.host == "git.openi.org.cn" || document.URL.startsWith("https")){
url = "wss://" + document.location.host + "/action/notification"
}
var socket = new WebSocket(url);


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


var messageQueue = [];
var maxSize = 20; var maxSize = 20;
var html =document.documentElement; var html =document.documentElement;
var lang = html.attributes["lang"] var lang = html.attributes["lang"]
var isZh = true; var isZh = true;
if(lang != null && lang.nodeValue =="en-US" ){ if(lang != null && lang.nodeValue =="en-US" ){
console.log("the language is " + lang.nodeValue);
isZh=false; isZh=false;
}else{ }else{
console.log("default lang=zh");
} }


socket.onmessage = function (e) { socket.onmessage = function (e) {
var data =JSON.parse(e.data) var data =JSON.parse(e.data)
console.log("recevie data=" + e.data)
var html = ""; var html = "";
if (data != null){ if (data != null){
console.log("queue length=" + messageQueue.length);
if(messageQueue.length > maxSize){ if(messageQueue.length > maxSize){
delete messageQueue[0]; delete messageQueue[0];
}else{ }else{
@@ -65,22 +94,24 @@ socket.onmessage = function (e) {
var currentTime = new Date().getTime(); var currentTime = new Date().getTime();
for(var i = 0; i < messageQueue.length; i++){ for(var i = 0; i < messageQueue.length; i++){
var record = messageQueue[i]; var record = messageQueue[i];
var recordPrefix = getMsg(record);
var actionName = getAction(record.OpType,isZh); var actionName = getAction(record.OpType,isZh);
if(record.ActUser == null){
console.log("receive action type=" + record.OpType + " name=" + actionName + " but user is null.");
continue;
}
var recordPrefix = getMsg(record);
if(record.OpType == "6" || record.OpType == "10" || record.OpType == "12" || record.OpType == "13"){ if(record.OpType == "6" || record.OpType == "10" || record.OpType == "12" || record.OpType == "13"){
html += recordPrefix + actionName; html += recordPrefix + actionName;
html += " <a href=\"" + getIssueLink(record) + "\" rel=\"nofollow\">" + getIssueText(record) + "</a>"
html += " <a href=\"" + getIssueLink(record) + "\" rel=\"nofollow\">" + getIssueText(record) + "</a>"
} }
else if(record.OpType == "7" || record.OpType == "11" || record.OpType == "14" || record.OpType == "15" || record.OpType == "22" else if(record.OpType == "7" || record.OpType == "11" || record.OpType == "14" || record.OpType == "15" || record.OpType == "22"
|| record.OpType == "23"){ || record.OpType == "23"){
html += recordPrefix + actionName; html += recordPrefix + actionName;
html += " <a href=\"" + getPRLink(record) + "\" rel=\"nofollow\">" + getPRText(record) + "</a>"
html += " <a href=\"" + getPRLink(record) + "\" rel=\"nofollow\">" + getPRText(record) + "</a>"
} }
else if(record.OpType == "1"){ else if(record.OpType == "1"){
html += recordPrefix + actionName; html += recordPrefix + actionName;
html += " <a href=\"" + getRepoLink(record) + "\" rel=\"nofollow\">" + getRepotext(record) + "</a>"
html += " <a href=\"" + getRepoLink(record) + "\" rel=\"nofollow\">" +getRepotext(record) + "</a>"
} }
else if(record.OpType == "9" || record.OpType == "5"){ else if(record.OpType == "9" || record.OpType == "5"){
branch = "<a href=\"" + getRepoLink(record) + "/src/branch/" + record.RefName + "\" rel=\"nofollow\">" + record.RefName + "</a>" branch = "<a href=\"" + getRepoLink(record) + "/src/branch/" + record.RefName + "\" rel=\"nofollow\">" + record.RefName + "</a>"
@@ -97,11 +128,14 @@ socket.onmessage = function (e) {
actionName = actionName.replace("{oldRepoName}",record.Content); actionName = actionName.replace("{oldRepoName}",record.Content);
html += recordPrefix + actionName; html += recordPrefix + actionName;
html += " <a href=\"" + getRepoLink(record) + "\" rel=\"nofollow\">" + getRepotext(record) + "</a>" html += " <a href=\"" + getRepoLink(record) + "\" rel=\"nofollow\">" + getRepotext(record) + "</a>"
}
else if(record.OpType == "24" || record.OpType == "25" || record.OpType == "26" || record.OpType == "27" || record.OpType == "28" || record.OpType == "29" || record.OpType == "30"){
html += recordPrefix + actionName;
html += " <a href=\"" + getTaskLink(record) + "\" rel=\"nofollow\">" + record.RefName + "</a>"
} }
else{ else{
continue; continue;
} }

if(record.Repo != null){ if(record.Repo != null){
var time = getTime(record.CreatedUnix,currentTime); var time = getTime(record.CreatedUnix,currentTime);
html += " " + time; html += " " + time;
@@ -109,28 +143,44 @@ socket.onmessage = function (e) {
html += "</div>"; html += "</div>";
html += "</div>"; html += "</div>";
} }
/*
<div class="swiper-slide item">
<img class="ui avatar image" src="/user/avatar/zhoupzh/-1" alt="">
<div class="middle aligned content">
<a href="/zhoupzh" title="">zhoupzh</a> 合并了合并请求 <a href="/OpenI/aiforge/pulls/1168" rel="nofollow">OpenI/aiforge#1168</a><span class="time-since">22 分钟前</span>
</div>
</div>
*/

} }
console.log("html=" + html)
output.innerHTML = html; output.innerHTML = html;
swiperNewMessage.updateSlides(); swiperNewMessage.updateSlides();
swiperNewMessage.updateProgress(); swiperNewMessage.updateProgress();
}; };


function getTaskLink(record){
var re = getRepoLink(record);
if(record.OpType == 24){
return re + "/datasets?type=" + record.Content;
}else if(record.OpType == 25){
return re + "/cloudbrain/" + record.RefName;
}else if(record.OpType == 26){
return re + "/modelarts/notebook/" + record.Content;
}else if(record.OpType == 27){
return re + "/modelarts/train-job/" + record.Content;
}else if(record.OpType == 28){
return re + "/modelarts/inference-job/" + record.Content;
}else if(record.OpType == 29){
return re + "/cloudbrain/benchmark/" + record.RefName;
}else if(record.OpType == 30){
return re + "/modelmanage/show_model_info?name=" + record.RefName;
}
return re;
}

function getMsg(record){ function getMsg(record){
var html =""; var html ="";
html += "<div class=\"swiper-slide item\">"; html += "<div class=\"swiper-slide item\">";
html += " <img class=\"ui avatar image\" src=\"/user/avatar/" + record.ActUser.Name + "/-1\" alt=\"\">"
var name = "";
if(record.ActUser != null){
name = record.ActUser.Name;
}else{
console.log("act user is null.");
}
html += " <img class=\"ui avatar image\" src=\"/user/avatar/" + name + "/-1\" alt=\"\">"
html += " <div class=\"middle aligned content nowrap\">" html += " <div class=\"middle aligned content nowrap\">"
html += " <a href=\"/" + record.ActUser.Name + "\" title=\"\">" + record.ActUser.Name + "</a>"
html += " <a href=\"/" + name + "\" title=\"\">" + name + "</a>"
return html; return html;
} }


@@ -143,6 +193,7 @@ function getRepotext(record){
} }
function getRepoLink(record){ function getRepoLink(record){
return record.Repo.OwnerName + "/" + record.Repo.Name; return record.Repo.OwnerName + "/" + record.Repo.Name;

} }


function getTime(UpdatedUnix,currentTime){ function getTime(UpdatedUnix,currentTime){
@@ -152,8 +203,7 @@ function getTime(UpdatedUnix,currentTime){
if( timeEscSecond < 0){ if( timeEscSecond < 0){
timeEscSecond = 1; timeEscSecond = 1;
} }
console.log("currentTime=" + currentTime + " updateUnix=" + UpdatedUnix);

var hours= Math.floor(timeEscSecond / 3600); var hours= Math.floor(timeEscSecond / 3600);
//计算相差分钟数 //计算相差分钟数
var leave2 = Math.floor(timeEscSecond % (3600)); //计算小时数后剩余的秒数 var leave2 = Math.floor(timeEscSecond % (3600)); //计算小时数后剩余的秒数
@@ -163,12 +213,12 @@ function getTime(UpdatedUnix,currentTime){
var seconds= leave3; var seconds= leave3;


if(hours == 0 && minutes == 0){ if(hours == 0 && minutes == 0){
return seconds + getRepoOrOrg(6,isZh);
return seconds + getRepoOrOrg(6,isZh,seconds);
}else{ }else{
if(hours > 0){ if(hours > 0){
return hours + getRepoOrOrg(4,isZh);
return hours + getRepoOrOrg(4,isZh,hours);
}else{ }else{
return minutes + getRepoOrOrg(5,isZh);
return minutes + getRepoOrOrg(5,isZh,minutes);
} }
} }
} }
@@ -177,11 +227,16 @@ function getPRLink(record){
return "/" + record.Repo.OwnerName + "/" + record.Repo.Name + "/pulls/" + getIssueId(record); return "/" + record.Repo.OwnerName + "/" + record.Repo.Name + "/pulls/" + getIssueId(record);
} }
function getPRText(record){ function getPRText(record){
return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record);
if(record.Repo.Alias){
return record.Repo.OwnerName + "/" + record.Repo.Alias + "#" + getIssueId(record);
}else{
return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record);
}

} }


function getIssueLink(record){ function getIssueLink(record){
return "/" + record.Repo.OwnerName + "/" + record.Repo.Name + "/issues/" + getIssueId(record); return "/" + record.Repo.OwnerName + "/" + record.Repo.Name + "/issues/" + getIssueId(record);
} }


@@ -202,7 +257,12 @@ function getIssueId(record){
} }


function getIssueText(record){ function getIssueText(record){
return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record);
if(record.Repo.Alias){
return record.Repo.OwnerName + "/" + record.Repo.Alias + "#" + getIssueId(record);
}else{
return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record);
}

} }


/* /*
@@ -237,7 +297,7 @@ var actionNameZH={
"5":"推送了 {branch} 分支的代码到", "5":"推送了 {branch} 分支的代码到",
"6":"创建了任务", "6":"创建了任务",
"7":"创建了合并请求", "7":"创建了合并请求",
"9":"推送了 {branch} 分支的代码到",
"9":"推送了标签 {branch} 到",
"10":"评论了任务", "10":"评论了任务",
"11":"合并了合并请求", "11":"合并了合并请求",
"12":"关闭了任务", "12":"关闭了任务",
@@ -245,8 +305,15 @@ var actionNameZH={
"14":"关闭了合并请求", "14":"关闭了合并请求",
"15":"重新开启了合并请求", "15":"重新开启了合并请求",
"17":"从 {repoName} 删除分支 {deleteBranchName}", "17":"从 {repoName} 删除分支 {deleteBranchName}",
"22":"拒绝了合并请求",
"23":"评论了合并请求"
"22":"建议变更",
"23":"评论了合并请求",
"24":"上传了数据集文件",
"25":"创建了CPU/GPU类型调试任务",
"26":"创建了NPU类型调试任务",
"27":"创建了训练任务",
"28":"创建了推理任务",
"29":"创建了评测任务",
"30":"导入了新模型"
}; };


var actionNameEN={ var actionNameEN={
@@ -255,7 +322,7 @@ var actionNameEN={
"5":" pushed to {branch} at", "5":" pushed to {branch} at",
"6":" opened issue", "6":" opened issue",
"7":" created pull request", "7":" created pull request",
"9":" pushed to {branch} at",
"9":" pushed tag {branch} to ",
"10":" commented on issue", "10":" commented on issue",
"11":" merged pull request", "11":" merged pull request",
"12":" closed issue", "12":" closed issue",
@@ -263,26 +330,45 @@ var actionNameEN={
"14":" closed pull request", "14":" closed pull request",
"15":" reopened pull request", "15":" reopened pull request",
"17":" deleted branch {deleteBranchName} from {repoName}", "17":" deleted branch {deleteBranchName} from {repoName}",
"22":" rejected pull request",
"23":" commented on pull request"
"22":" proposed changes",
"23":" commented on pull request",
"24":" upload dataset ",
"25":" created CPU/GPU type debugging task ",
"26":" created NPU type debugging task ",
"27":" created training task",
"28":" created reasoning task",
"29":" created profiling task",
"30":" created new model"
}; };


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


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




@@ -306,11 +392,9 @@ function queryRecommendData(){
dataType:"json", dataType:"json",
async:false, async:false,
success:function(json){ success:function(json){
console.log(json);
displayOrg(json); displayOrg(json);
}, },
error:function(response) { error:function(response) {
console.log(response);
} }
}); });


@@ -323,40 +407,14 @@ function queryRecommendData(){
dataType:"json", dataType:"json",
async:false, async:false,
success:function(json){ success:function(json){
console.log(json);
displayRepo(json); displayRepo(json);
}, },
error:function(response) { error:function(response) {
console.log(response);
} }
}); });


} }


/*
<div class="swiper-slide">
<div class="ui fluid card">
<div class="content">
<span class="right floated meta">
<i class="star icon"></i>276 <i class="star icon"></i>32
</span>
<img class="left floated mini ui image" src="/repo-avatars/278-a9f45e21b92b86dbf969c9f70dff1efc">
<a class="header nowrap" href="/OpenI/aiforge">aiforge </a>
<div class="description nowrap-2">
本项目是群体化方法与技术的开源实现案例,在基于Gitea的基础上,进一步支持社交化的协同开发、协同学习、协同研究等群体创新实践服务,特别是针对新一代人工智能技术特点,重点支持项目管理、git代码管理、大数据集存储管理与智能计算平台接入。
</div>
<div class="ui tags nowrap am-mt-10">
<a class="ui small label topic" href="/explore/repos?q=ai%e5%bc%80%e5%8f%91%e5%b7%a5%e5%85%b7&amp;topic=">ai开发工具</a>
<a class="ui small label topic" href="/explore/repos?q=openi&amp;topic=">openi</a>
<a class="ui small label topic" href="/explore/repos?q=golang&amp;topic=">golang</a>
<a class="ui small label topic" href="/explore/repos?q=git&amp;topic=">git</a>
<a class="ui small label topic" href="/explore/repos?q=pcl&amp;topic=">pcl</a>
</div>
</div>
</div>
</div>
*/
function displayRepo(json){ function displayRepo(json){
var orgRepo = document.getElementById("recommendrepo"); var orgRepo = document.getElementById("recommendrepo");
var html = ""; var html = "";
@@ -370,7 +428,7 @@ function displayRepo(json){
html += " <i class=\"ri-star-line\"></i>" + record["NumStars"] + "<i class=\"ri-git-branch-line am-ml-10\"></i>" + record["NumForks"]; html += " <i class=\"ri-star-line\"></i>" + record["NumStars"] + "<i class=\"ri-git-branch-line am-ml-10\"></i>" + record["NumForks"];
html += " </span>"; html += " </span>";
html += " <img class=\"left floated mini ui image\" src=\"" + record["Avatar"] + "\">"; html += " <img class=\"left floated mini ui image\" src=\"" + record["Avatar"] + "\">";
html += " <a class=\"header nowrap\" href=\"/" + record["OwnerName"] + "/" + record["Name"] + "\">" + record["Name"] +"</a>";
html += " <a class=\"header nowrap\" href=\"/" + record["OwnerName"] + "/" + record["Name"] + "\">" + record["Alias"] +"</a>";
html += " <div class=\"description nowrap-2\">" + record["Description"] + " </div>"; html += " <div class=\"description nowrap-2\">" + record["Description"] + " </div>";
html += " <div class=\"ui tags nowrap am-mt-10\">" html += " <div class=\"ui tags nowrap am-mt-10\">"
if(record["Topics"] != null){ if(record["Topics"] != null){
@@ -391,50 +449,31 @@ function displayRepo(json){
swiperRepo.updateProgress(); swiperRepo.updateProgress();
} }


/**
*
* <div class="column">
<div class="ui fluid card">
<div class="content">
<div class="ui small header">
<img class="ui image" src="/user/avatar/OpenI/-1">
<div class="content nowrap">
<a href="/OpenI">OpenI</a> 启智社区
<div class="sub header">39 项目 ・ 60 成员 ・ 23 团队</div>
</div>
</div>
</div>
</div>
</div>
*/

//var repoAndOrgZH = new Map([['1', "项目"], ['2', "成员"], ['3', "团队"]]);
//var repoAndOrgEN = new Map([['1', "Repository"], ['2', "Members"], ['3', "Teams"]]);


function getRepoOrOrg(key,isZhLang){

function getRepoOrOrg(key,isZhLang,numbers=1){
if(numbers > 1){
key+="1";
}
if(isZhLang){ if(isZhLang){
return repoAndOrgZH[key]; return repoAndOrgZH[key];
}else{ }else{
return repoAndOrgEN[key]; return repoAndOrgEN[key];
} }
} }

function displayOrg(json){ function displayOrg(json){
var orgDiv = document.getElementById("recommendorg"); var orgDiv = document.getElementById("recommendorg");
var html = ""; var html = "";
if (json != null && json.length > 0){ if (json != null && json.length > 0){
for(var i = 0; i < json.length;i++){ for(var i = 0; i < json.length;i++){
var record = json[i] var record = json[i]
html += "<div class=\"column\">";
html += "<div class=\"swiper-slide\">";
html += " <a href=\"/" + record["Name"] + "\" class=\"ui fluid card\">"; html += " <a href=\"/" + record["Name"] + "\" class=\"ui fluid card\">";
html += " <div class=\"content\">"; html += " <div class=\"content\">";
html += " <div class=\"ui small header\">"; html += " <div class=\"ui small header\">";
html += " <img class=\"ui image\" src=\"" + record["Avatar"] + "\">"; html += " <img class=\"ui image\" src=\"" + record["Avatar"] + "\">";
html += " <div class=\"content nowrap\">"; html += " <div class=\"content nowrap\">";
html += " <span class=\"ui blue\">" + record["Name"] + "</span> " + record["FullName"]; html += " <span class=\"ui blue\">" + record["Name"] + "</span> " + record["FullName"];
html += " <div class=\"sub header\">" + record["NumRepos"] +" " + getRepoOrOrg(1,isZh) + " ・ " + record["NumMembers"] +" " + getRepoOrOrg(2,isZh) + " ・ " + record["NumTeams"] + " " + getRepoOrOrg(3,isZh) + "</div>";
html += " <div class=\"sub header\">" + record["NumRepos"] +" " + getRepoOrOrg(1,isZh,record["NumRepos"]) + " ・ " + record["NumMembers"] +" " + getRepoOrOrg(2,isZh,record["NumMembers"]) + " ・ " + record["NumTeams"] + " " + getRepoOrOrg(3,isZh,record["NumTeams"]) + "</div>";
html += " </div>"; html += " </div>";
html += " </div>"; html += " </div>";
html += " </div>"; html += " </div>";
@@ -443,4 +482,5 @@ function displayOrg(json){
} }
} }
orgDiv.innerHTML = html; orgDiv.innerHTML = html;
}
swiperOrg.updateSlides();
}

BIN
public/img/qrcode_reload.png View File

Before After
Width: 310  |  Height: 310  |  Size: 11 kB

+ 225
- 0
routers/admin/cloudbrains.go View File

@@ -0,0 +1,225 @@
package admin

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

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

"code.gitea.io/gitea/modules/modelarts"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)

const (
tplCloudBrains base.TplName = "admin/cloudbrain/list"
EXCEL_DATE_FORMAT = "20060102150405"
CREATE_TIME_FORMAT = "2006/01/02 15:04:05"
)

func CloudBrains(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.cloudBrains")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminCloudBrains"] = true

listType := ctx.Query("listType")
jobType := ctx.Query("jobType")
jobStatus := ctx.Query("jobStatus")

ctx.Data["ListType"] = listType
ctx.Data["JobType"] = jobType
ctx.Data["JobStatus"] = jobStatus

page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
debugType := modelarts.DebugType
if listType == models.GPUResource {
debugType = models.TypeCloudBrainOne
} else if listType == models.NPUResource {
debugType = models.TypeCloudBrainTwo
}

var jobTypes []string
jobTypeNot := false
if jobType == string(models.JobTypeDebug) {
jobTypes = append(jobTypes, string(models.JobTypeSnn4imagenet), string(models.JobTypeBrainScore), string(models.JobTypeDebug))
} else if jobType != "all" && jobType != "" {
jobTypes = append(jobTypes, jobType)
}

var jobStatuses []string
jobStatusNot := false
if jobStatus == "other" {
jobStatusNot = true
jobStatuses = append(jobStatuses, string(models.ModelArtsTrainJobWaiting), string(models.ModelArtsTrainJobFailed), string(models.ModelArtsRunning), string(models.ModelArtsTrainJobCompleted),
string(models.ModelArtsStarting), string(models.ModelArtsRestarting), string(models.ModelArtsStartFailed),
string(models.ModelArtsStopping), string(models.ModelArtsStopped), string(models.JobSucceeded))
} else if jobStatus != "all" && jobStatus != "" {
jobStatuses = append(jobStatuses, jobStatus)
}

keyword := strings.Trim(ctx.Query("q"), " ")

ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
Keyword: keyword,
Type: debugType,
JobTypeNot: jobTypeNot,
JobStatusNot: jobStatusNot,
JobStatus: jobStatuses,
JobTypes: jobTypes,
NeedRepoInfo: true,
IsLatestVersion: modelarts.IsLatestVersion,
})
if err != nil {
ctx.ServerError("Get job failed:", err)
return
}

for i, task := range ciTasks {
ciTasks[i].CanDebug = true
ciTasks[i].CanDel = true
ciTasks[i].Cloudbrain.ComputeResource = task.ComputeResource
}

pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, getTotalPage(count, setting.UI.IssuePagingNum))
pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "listType", "ListType")
ctx.Data["Page"] = pager
ctx.Data["PageIsCloudBrain"] = true
ctx.Data["Tasks"] = ciTasks
ctx.Data["CanCreate"] = true
ctx.Data["Keyword"] = keyword

ctx.HTML(200, tplCloudBrains)

}

func DownloadCloudBrains(ctx *context.Context) {

page := 1

pageSize := 300

var cloudBrain = ctx.Tr("repo.cloudbrain")
fileName := getFileName(cloudBrain)

_, total, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: 1,
},
Type: modelarts.DebugType,
NeedRepoInfo: false,
IsLatestVersion: modelarts.IsLatestVersion,
})

if err != nil {
log.Warn("Can not get cloud brain info", err)
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.cloudbrain_query_fail"))
return
}

totalPage := getTotalPage(total, pageSize)

f := excelize.NewFile()

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

for k, v := range allHeader(ctx) {
f.SetCellValue(cloudBrain, k, v)
}

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

pageRecords, _, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: pageSize,
},
Type: modelarts.DebugType,
NeedRepoInfo: true,
IsLatestVersion: modelarts.IsLatestVersion,
})
if err != nil {
log.Warn("Can not get cloud brain info", err)
continue
}
for _, record := range pageRecords {

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

}

page++
}
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 allValues(row int, rs *models.CloudbrainInfo, ctx *context.Context) map[string]string {
return map[string]string{getCellName("A", row): rs.JobName, getCellName("B", row): rs.JobType, getCellName("C", row): rs.Status, getCellName("D", row): time.Unix(int64(rs.Cloudbrain.CreatedUnix), 0).Format(CREATE_TIME_FORMAT), getCellName("E", row): getDurationTime(rs),
getCellName("F", row): rs.ComputeResource, getCellName("G", row): rs.Name, getCellName("H", row): getRepoPathName(rs), getCellName("I", row): rs.JobName,
}
}

func getRepoPathName(rs *models.CloudbrainInfo) string {
if rs.Repo != nil {
return rs.Repo.OwnerName + "/" + rs.Repo.Alias
}
return ""
}

func getDurationTime(rs *models.CloudbrainInfo) string {
if rs.JobType == "TRAIN" || rs.JobType == "INFERENCE" {
return rs.TrainJobDuration
} else {
return "-"
}
}

func getFileName(baseName string) string {
return baseName + "_" + time.Now().Format(EXCEL_DATE_FORMAT) + ".xlsx"

}

func getTotalPage(total int64, pageSize int) int {

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

}

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

return map[string]string{"A1": ctx.Tr("repo.cloudbrain_task"), "B1": ctx.Tr("repo.cloudbrain_task_type"), "C1": ctx.Tr("repo.modelarts.status"), "D1": ctx.Tr("repo.modelarts.createtime"), "E1": ctx.Tr("repo.modelarts.train_job.dura_time"), "F1": ctx.Tr("repo.modelarts.computing_resources"), "G1": ctx.Tr("repo.cloudbrain_creator"), "H1": ctx.Tr("repo.repo_name"), "I1": ctx.Tr("repo.cloudbrain_task_name")}

}

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

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

@@ -62,6 +62,8 @@ import (
"net/http" "net/http"
"strings" "strings"


"code.gitea.io/gitea/routers/authentication"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@@ -534,6 +536,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/downloadAllOpenI", repo.ServeAllProjectsOpenIStatisticsFile) m.Get("/downloadAllOpenI", repo.ServeAllProjectsOpenIStatisticsFile)
m.Group("/project", func() { m.Group("/project", func() {
m.Get("", repo.GetAllProjectsPeriodStatistics) m.Get("", repo.GetAllProjectsPeriodStatistics)
m.Get("/numVisit", repo.ProjectNumVisit)


m.Group("/:id", func() { m.Group("/:id", func() {
m.Get("", repo.GetProjectLatestStatistics) m.Get("", repo.GetProjectLatestStatistics)
@@ -878,10 +881,12 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqAnyRepoReader()) }, reqAnyRepoReader())
m.Group("/cloudbrain", func() { m.Group("/cloudbrain", func() {
m.Get("/:jobid", repo.GetCloudbrainTask) m.Get("/:jobid", repo.GetCloudbrainTask)
m.Get("/:jobname/log", repo.CloudbrainGetLog)
}, reqRepoReader(models.UnitTypeCloudBrain)) }, reqRepoReader(models.UnitTypeCloudBrain))
m.Group("/modelarts", func() { m.Group("/modelarts", func() {
m.Group("/notebook", func() { m.Group("/notebook", func() {
m.Get("/:jobid", repo.GetModelArtsNotebook)
//m.Get("/:jobid", repo.GetModelArtsNotebook)
m.Get("/:jobid", repo.GetModelArtsNotebook2)
}) })
m.Group("/train-job", func() { m.Group("/train-job", func() {
m.Group("/:jobid", func() { m.Group("/:jobid", func() {
@@ -892,6 +897,15 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/model_list", repo.ModelList) m.Get("/model_list", repo.ModelList)
}) })
}) })
m.Group("/inference-job", func() {
m.Group("/:jobid", func() {
m.Get("", repo.GetModelArtsInferenceJob)
m.Get("/log", repo.TrainJobGetLog)
m.Post("/del_version", repo.DelTrainJobVersion)
m.Post("/stop_version", repo.StopTrainJobVersion)
m.Get("/result_list", repo.ResultList)
})
})
}, reqRepoReader(models.UnitTypeCloudBrain)) }, reqRepoReader(models.UnitTypeCloudBrain))
}, repoAssignment()) }, repoAssignment())
}) })
@@ -983,6 +997,12 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/topics", func() { m.Group("/topics", func() {
m.Get("/search", repo.TopicSearch) m.Get("/search", repo.TopicSearch)
}) })
m.Group("/from_wechat", func() {
m.Get("/event", authentication.ValidEventSource)
m.Post("/event", authentication.AcceptWechatEvent)
m.Get("/prd/event", authentication.ValidEventSource)
m.Post("/prd/event", authentication.AcceptWechatEvent)
})
}, securityHeaders(), context.APIContexter(), sudo()) }, securityHeaders(), context.APIContexter(), sudo())
} }




+ 64
- 4
routers/api/v1/repo/cloudbrain.go View File

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


import ( import (
"code.gitea.io/gitea/modules/log"
"net/http" "net/http"
"sort"
"time" "time"


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

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/cloudbrain" "code.gitea.io/gitea/modules/cloudbrain"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@@ -47,14 +49,15 @@ func GetCloudbrainTask(ctx *context.APIContext) {
err error err error
) )


// jobName := ctx.Params(":jobname")
// job, err := models.GetCloudbrainByName(jobName)
jobID := ctx.Params(":jobid") jobID := ctx.Params(":jobid")
repoID := ctx.Repo.Repository.ID repoID := ctx.Repo.Repository.ID
job, err := models.GetRepoCloudBrainByJobID(repoID, jobID) job, err := models.GetRepoCloudBrainByJobID(repoID, jobID)
if err != nil { if err != nil {
ctx.NotFound(err)
return
ctx.Data["error"] = err.Error()
} }
jobResult, err := cloudbrain.GetJob(jobID)
jobResult, err := cloudbrain.GetJob(job.JobID)
if err != nil { if err != nil {
ctx.NotFound(err) ctx.NotFound(err)
return return
@@ -84,6 +87,7 @@ func GetCloudbrainTask(ctx *context.APIContext) {


ctx.JSON(http.StatusOK, map[string]interface{}{ ctx.JSON(http.StatusOK, map[string]interface{}{
"JobID": result.Config.JobID, "JobID": result.Config.JobID,
"JobName": result.Config.JobName,
"JobStatus": result.JobStatus.State, "JobStatus": result.JobStatus.State,
"SubState": result.JobStatus.SubState, "SubState": result.JobStatus.SubState,
"CreatedTime": time.Unix(result.JobStatus.CreatedTime/1000, 0).Format("2006-01-02 15:04:05"), "CreatedTime": time.Unix(result.JobStatus.CreatedTime/1000, 0).Format("2006-01-02 15:04:05"),
@@ -91,3 +95,59 @@ func GetCloudbrainTask(ctx *context.APIContext) {
}) })


} }

func CloudbrainGetLog(ctx *context.Context) {
jobName := ctx.Params(":jobname")
job, err := models.GetCloudbrainByName(jobName)
if err != nil {
log.Error("GetCloudbrainByJobName failed: %v", err, ctx.Data["MsgID"])
ctx.ServerError(err.Error(), err)
return
}

var hits []models.Hits
result, err := cloudbrain.GetJobLog(job.JobID)
if err != nil {
log.Error("GetJobLog failed: %v", err, ctx.Data["MsgID"])
ctx.ServerError(err.Error(), err)
return
}
hits = result.Hits.Hits

//if the size equal page_size, then take the scroll_id to get all log and delete the scroll_id(the num of scroll_id is limited)
if len(result.Hits.Hits) >= cloudbrain.LogPageSize {
for {
resultNext, err := cloudbrain.GetJobAllLog(result.ScrollID)
if err != nil {
log.Error("GetJobAllLog failed: %v", err, ctx.Data["MsgID"])
} else {
for _, hit := range resultNext.Hits.Hits {
hits = append(hits, hit)
}
}

if len(resultNext.Hits.Hits) < cloudbrain.LogPageSize {
log.Info("get all log already")
break
}
}
}

cloudbrain.DeleteJobLogToken(result.ScrollID)

sort.Slice(hits, func(i, j int) bool {
return hits[i].Sort[0] < hits[j].Sort[0]
})

var content string
for _, log := range hits {
content += log.Source.Message + "\n"
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"JobName": jobName,
"Content": content,
})

return
}

+ 1
- 1
routers/api/v1/repo/fork.go View File

@@ -118,7 +118,7 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) {
forker = org forker = org
} }


fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description)
fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description, repo.Alias)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "ForkRepository", err) ctx.Error(http.StatusInternalServerError, "ForkRepository", err)
return return


+ 114
- 7
routers/api/v1/repo/modelarts.go View File

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


import ( import (
"code.gitea.io/gitea/modules/util"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"


"code.gitea.io/gitea/modules/util"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@@ -50,6 +51,38 @@ func GetModelArtsNotebook(ctx *context.APIContext) {


} }


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

jobID := ctx.Params(":jobid")
repoID := ctx.Repo.Repository.ID
job, err := models.GetRepoCloudBrainByJobID(repoID, jobID)
if err != nil {
ctx.NotFound(err)
return
}
result, err := modelarts.GetNotebook2(jobID)
if err != nil {
ctx.NotFound(err)
return
}

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

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

}

func GetModelArtsTrainJob(ctx *context.APIContext) { func GetModelArtsTrainJob(ctx *context.APIContext) {
var ( var (
err error err error
@@ -133,7 +166,6 @@ func TrainJobGetLog(ctx *context.APIContext) {


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


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

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

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

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

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

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

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

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

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

}

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

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


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

@@ -232,6 +232,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR
} }
repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{ repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{
Name: opt.Name, Name: opt.Name,
Alias: opt.Alias,
Description: opt.Description, Description: opt.Description,
IssueLabels: opt.IssueLabels, IssueLabels: opt.IssueLabels,
Gitignores: opt.Gitignores, Gitignores: opt.Gitignores,


+ 35
- 11
routers/api/v1/repo/repo_dashbord.go View File

@@ -12,6 +12,7 @@ import (


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


"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@@ -296,7 +297,7 @@ func allProjectsPeroidHeader(ctx *context.Context) map[string]string {
} }


func allProjectsPeroidValues(row int, rs *models.RepoStatistic, ctx *context.Context) map[string]string { 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),
return map[string]string{getCellName("A", row): strconv.FormatInt(rs.RepoID, 10), getCellName("B", row): rs.DisplayName(), 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("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("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), getCellName("N", row): strconv.FormatInt(rs.NumClosedIssues, 10), getCellName("O", row): strconv.FormatInt(rs.NumContributor, 10),
@@ -316,7 +317,7 @@ func allProjectsOpenIHeader() map[string]string {


func allProjectsOpenIValues(row int, rs *models.RepoStatistic, ctx *context.Context) map[string]string { 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),
return map[string]string{getCellName("A", row): strconv.FormatInt(rs.RepoID, 10), getCellName("B", row): rs.DisplayName(), 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("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("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),


@@ -465,10 +466,10 @@ func generateCountSql(beginTime time.Time, endTime time.Time, latestDate string,
countSql := "SELECT count(*) FROM " + countSql := "SELECT count(*) FROM " +
"(SELECT repo_id FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) + "(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," + " 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" +
"(SELECT repo_id,name,alias,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" +
" where A.repo_id=B.repo_id" " where A.repo_id=B.repo_id"
if q != "" { if q != "" {
countSql = countSql + " and LOWER(B.name) like '%" + strings.ToLower(q) + "%'"
countSql = countSql + " and LOWER(B.alias) like '%" + strings.ToLower(q) + "%'"
} }
return countSql return countSql
} }
@@ -481,22 +482,22 @@ func generateOpenICountSql(latestDate string) string {
} }


func generateTypeAllSql(beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string { 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 " +
sql := "SELECT A.repo_id,name,alias,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 " + "(SELECT repo_id,sum(num_visits) as num_visits " +
" FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) + " FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," + " 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" +
"(SELECT repo_id,name,alias,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" " where A.repo_id=B.repo_id"


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


func generateTypeAllOpenISql(latestDate string, page int, pageSize int) string { 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 " +
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,alias, is_private, owner_name FROM " +
" public.repo_statistic where date='" + latestDate + "'" " 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) sql = sql + " order by radar_total desc,repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
@@ -505,14 +506,14 @@ func generateTypeAllOpenISql(latestDate string, page int, pageSize int) string {


func generatePageSql(beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string { 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 " +
sql := "SELECT A.repo_id,name,alias,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 " + "(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) + " FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," + " 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" +
"(SELECT repo_id,name,alias,owner_name,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" +
" where A.repo_id=B.repo_id" " where A.repo_id=B.repo_id"
if q != "" { if q != "" {
sql = sql + " and LOWER(B.name) like '%" + strings.ToLower(q) + "%'"
sql = sql + " and LOWER(B.alias) like '%" + strings.ToLower(q) + "%'"
} }
sql = sql + " order by " + orderBy + " desc,A.repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize) sql = sql + " order by " + orderBy + " desc,A.repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
return sql return sql
@@ -640,3 +641,26 @@ func getTotalPage(total int64, pageSize int) int {
return int(total)/pageSize + another return int(total)/pageSize + another


} }

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

var userName = ctx.Query("user")
var projectName = ctx.Query("project")
var beginTime = ctx.Query("begintime")
var endTime = ctx.Query("endtime")

var ProjectNumVisits int
ProjectNumVisits, err = repository.AppointProjectView(userName, projectName, beginTime, endTime) //访问量
if err != nil {
ctx.NotFound(err)
}
log.Info("ProjectNumVisits is:", ProjectNumVisits)

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

+ 126
- 0
routers/authentication/wechat.go View File

@@ -0,0 +1,126 @@
package authentication

import (
"code.gitea.io/gitea/modules/auth/wechat"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/setting"
"encoding/json"
"errors"
gouuid "github.com/satori/go.uuid"
"time"
)

const tplBindPage base.TplName = "repo/wx_autorize"

type QRCodeResponse struct {
Url string
Ticket string
SceneStr string
ExpireSeconds int
}

// GetQRCode4Bind get QR code for wechat binding
func GetQRCode4Bind(ctx *context.Context) {
userId := ctx.User.ID

r, err := createQRCode4Bind(userId)
if err != nil {
ctx.JSON(200, map[string]interface{}{
"code": "9999",
"msg": "Get QR code failed",
})
return
}

ctx.JSON(200, map[string]interface{}{
"code": "00",
"msg": "success",
"data": r,
})
}

// GetBindStatus the web page will poll the service to get bind status
func GetBindStatus(ctx *context.Context) {
sceneStr := ctx.Query("sceneStr")
val, _ := redis_client.Get(redis_key.WechatBindingUserIdKey(sceneStr))
if val == "" {
ctx.JSON(200, map[string]interface{}{
"code": "00",
"msg": "QR code expired",
"data": map[string]interface{}{
"status": wechat.BIND_STATUS_EXPIRED,
},
})
return
}
qrCache := new(wechat.QRCode4BindCache)
json.Unmarshal([]byte(val), qrCache)
ctx.JSON(200, map[string]interface{}{
"code": "00",
"msg": "success",
"data": map[string]interface{}{
"status": qrCache.Status,
},
})
}

// UnbindWechat
func UnbindWechat(ctx *context.Context) {
if ctx.User.WechatOpenId != "" {
wechat.UnbindWechat(ctx.User.ID, ctx.User.WechatOpenId)
}

ctx.JSON(200, map[string]interface{}{
"code": "00",
"msg": "success",
})
}

// GetBindPage
func GetBindPage(ctx *context.Context) {
userId := ctx.User.ID
r, _ := createQRCode4Bind(userId)
if r != nil {
ctx.Data["qrcode"] = r
}
redirectUrl := ctx.Query("redirect_to")
if redirectUrl != "" {
ctx.SetCookie("redirect_to", setting.AppSubURL+redirectUrl, 0, setting.AppSubURL)
}
ctx.HTML(200, tplBindPage)
}

func createQRCode4Bind(userId int64) (*QRCodeResponse, error) {
log.Info("start to create qr-code for binding.userId=%d", userId)
sceneStr := gouuid.NewV4().String()
r := wechat.GetWechatQRCode4Bind(sceneStr)
if r == nil {
return nil, errors.New("createQRCode4Bind failed")
}

jsonStr, _ := json.Marshal(&wechat.QRCode4BindCache{
UserId: userId,
Status: wechat.BIND_STATUS_UNBIND,
})
isOk, err := redis_client.Setex(redis_key.WechatBindingUserIdKey(sceneStr), string(jsonStr), time.Duration(setting.WechatQRCodeExpireSeconds)*time.Second)
if err != nil {
log.Error("createQRCode4Bind failed.e=%+v", err)
return nil, err
}
if !isOk {
log.Error("createQRCode4Bind failed.redis reply is not ok")
return nil, errors.New("reply is not ok when set WechatBindingUserIdKey")
}

result := &QRCodeResponse{
Url: r.Url,
Ticket: r.Ticket,
SceneStr: sceneStr,
ExpireSeconds: setting.WechatQRCodeExpireSeconds,
}
return result, nil
}

+ 47
- 0
routers/authentication/wechat_event.go View File

@@ -0,0 +1,47 @@
package authentication

import (
"code.gitea.io/gitea/modules/auth/wechat"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"encoding/xml"
"io/ioutil"
"time"
)

// AcceptWechatEvent
// https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
// https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html
func AcceptWechatEvent(ctx *context.Context) {
b, _ := ioutil.ReadAll(ctx.Req.Request.Body)
we := wechat.WechatEvent{}
xml.Unmarshal(b, &we)

log.Info("accept wechat event= %+v", we)
var replyStr string
switch we.Event {
case wechat.WECHAT_EVENT_SUBSCRIBE, wechat.WECHAT_EVENT_SCAN:
replyStr = wechat.HandleSubscribeEvent(we)
break
}

if replyStr == "" {
log.Info("reply str is empty")
return
}
reply := &wechat.EventReply{
ToUserName: we.FromUserName,
FromUserName: we.ToUserName,
CreateTime: time.Now().Unix(),
MsgType: wechat.WECHAT_MSG_TYPE_TEXT,
Content: replyStr,
}
ctx.XML(200, reply)
}

// ValidEventSource
func ValidEventSource(ctx *context.Context) {
echostr := ctx.Query("echostr")
ctx.Write([]byte(echostr))
return
}

+ 13
- 57
routers/home.go View File

@@ -7,11 +7,11 @@ package routers


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


"code.gitea.io/gitea/services/repository"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@@ -38,6 +38,7 @@ const (
tplExploreCode base.TplName = "explore/code" tplExploreCode base.TplName = "explore/code"
tplExploreImages base.TplName = "explore/images" tplExploreImages base.TplName = "explore/images"
tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis" tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis"
tplHomeTerm base.TplName = "terms"
) )


// Home render home page // Home render home page
@@ -133,6 +134,7 @@ type RepoSearchOptions struct {
Restricted bool Restricted bool
PageSize int PageSize int
TplName base.TplName TplName base.TplName
Course util.OptionalBool
} }


var ( var (
@@ -211,6 +213,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
AllLimited: true, AllLimited: true,
TopicName: topic, TopicName: topic,
IncludeDescription: setting.UI.SearchRepoDescription, IncludeDescription: setting.UI.SearchRepoDescription,
Course: opts.Course,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepository", err) ctx.ServerError("SearchRepository", err)
@@ -559,7 +562,7 @@ func NotFound(ctx *context.Context) {


func RecommendOrgFromPromote(ctx *context.Context) { func RecommendOrgFromPromote(ctx *context.Context) {
url := setting.RecommentRepoAddr + "organizations" url := setting.RecommentRepoAddr + "organizations"
result, err := recommendFromPromote(url)
result, err := repository.RecommendFromPromote(url)
if err != nil { if err != nil {
ctx.ServerError("500", err) ctx.ServerError("500", err)
return return
@@ -586,62 +589,15 @@ func RecommendOrgFromPromote(ctx *context.Context) {
ctx.JSON(200, resultOrg) ctx.JSON(200, resultOrg)
} }


func recommendFromPromote(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil || resp.StatusCode != 200 {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}
bytes, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}

allLineStr := string(bytes)
lines := strings.Split(allLineStr, "\n")
result := make([]string, len(lines))
for i, line := range lines {
log.Info("i=" + fmt.Sprint(i) + " line=" + line)
result[i] = strings.Trim(line, " ")
}
return result, nil
}

func RecommendRepoFromPromote(ctx *context.Context) { func RecommendRepoFromPromote(ctx *context.Context) {
url := setting.RecommentRepoAddr + "projects"
result, err := recommendFromPromote(url)
result, err := repository.GetRecommendRepoFromPromote("projects")
if err != nil { if err != nil {
ctx.ServerError("500", err) ctx.ServerError("500", err)
return
}
resultRepo := make([]map[string]interface{}, 0)
//resultRepo := make([]*models.Repository, 0)
for _, repoName := range result {
tmpIndex := strings.Index(repoName, "/")
if tmpIndex == -1 {
log.Info("error repo name format.")
} else {
ownerName := strings.Trim(repoName[0:tmpIndex], " ")
repoName := strings.Trim(repoName[tmpIndex+1:], " ")
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err == nil {
repoMap := make(map[string]interface{})
repoMap["ID"] = fmt.Sprint(repo.ID)
repoMap["Name"] = repo.Name
repoMap["OwnerName"] = repo.OwnerName
repoMap["NumStars"] = repo.NumStars
repoMap["NumForks"] = repo.NumForks
repoMap["Description"] = repo.Description
repoMap["NumWatchs"] = repo.NumWatches
repoMap["Topics"] = repo.Topics
repoMap["Avatar"] = repo.RelAvatarLink()
resultRepo = append(resultRepo, repoMap)
} else {
log.Info("query repo error," + err.Error())
}
}
} else {
ctx.JSON(200, result)
} }
ctx.JSON(200, resultRepo)
}

func HomeTerm(ctx *context.Context) {
ctx.HTML(200, tplHomeTerm)
} }

+ 26
- 17
routers/notice/notice.go View File

@@ -16,27 +16,31 @@ const (
) )


type Notice struct { type Notice struct {
Title string
Link string
Visible int //0 invisible, 1 visible
Title string
Link string
Visible int //0 invisible, 1 visible
}

type NoticeResponse struct {
Notices []*Notice
CommitId string CommitId string
} }


var lock int32 = 0 var lock int32 = 0


func GetNewestNotice() (*Notice, error) {
func GetNewestNotice() (*NoticeResponse, error) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Error("recover error", err) log.Error("recover error", err)
} }
}() }()


var notice *Notice
var notice *NoticeResponse
var err error var err error
if setting.CacheOn { if setting.CacheOn {
notice, err = getNewestNoticeFromCacheAndDisk()
notice, err = getNewestNoticesFromCacheAndDisk()
} else { } else {
notice, err = getNewestNoticeFromDisk()
notice, err = getNewestNoticesFromDisk()
} }


if err != nil { if err != nil {
@@ -49,34 +53,39 @@ func getNoticeTimeout() time.Duration {
return time.Duration(setting.CacheTimeOutSecond) * time.Second return time.Duration(setting.CacheTimeOutSecond) * time.Second
} }


func getNewestNoticeFromDisk() (*Notice, error) {
func getNewestNoticesFromDisk() (*NoticeResponse, error) {
log.Debug("Get notice from disk") log.Debug("Get notice from disk")
repoFile, err := models.ReadLatestFileInRepo(setting.UserNameOfNoticeRepo, setting.RepoNameOfNoticeRepo, setting.RefNameOfNoticeRepo, setting.TreePathOfNoticeRepo)
repo, err := models.GetRepositoryByOwnerAndAlias(setting.UserNameOfNoticeRepo, setting.RepoNameOfNoticeRepo)
if err != nil {
log.Error("get notice repo failed, error=%v", err)
return nil, err
}
repoFile, err := models.ReadLatestFileInRepo(repo.OwnerName, repo.Name, setting.RefNameOfNoticeRepo, setting.TreePathOfNoticeRepo)
if err != nil { if err != nil {
log.Error("GetNewestNotice failed, error=%v", err) log.Error("GetNewestNotice failed, error=%v", err)
return nil, err return nil, err
} }
notice := &Notice{}
json.Unmarshal(repoFile.Content, notice)
if notice.Title == "" {
res := &NoticeResponse{}
json.Unmarshal(repoFile.Content, res)
if res == nil || len(res.Notices) == 0 {
return nil, err return nil, err
} }
notice.CommitId = repoFile.CommitId
return notice, nil
res.CommitId = repoFile.CommitId
return res, nil
} }


func getNewestNoticeFromCacheAndDisk() (*Notice, error) {
func getNewestNoticesFromCacheAndDisk() (*NoticeResponse, error) {
v, success := noticeCache.Get(NOTICE_CACHE_KEY) v, success := noticeCache.Get(NOTICE_CACHE_KEY)
if success { if success {
log.Debug("Get notice from cache,value = %v", v) log.Debug("Get notice from cache,value = %v", v)
if v == nil { if v == nil {
return nil, nil return nil, nil
} }
n := v.(*Notice)
n := v.(*NoticeResponse)
return n, nil return n, nil
} }


notice, err := getNewestNoticeFromDisk()
notice, err := getNewestNoticesFromDisk()
if err != nil { if err != nil {
log.Error("GetNewestNotice failed, error=%v", err) log.Error("GetNewestNotice failed, error=%v", err)
noticeCache.Set(NOTICE_CACHE_KEY, nil, 30*time.Second) noticeCache.Set(NOTICE_CACHE_KEY, nil, 30*time.Second)


+ 43
- 6
routers/org/home.go View File

@@ -7,6 +7,10 @@ package org
import ( import (
"strings" "strings"


"code.gitea.io/gitea/services/repository"

"code.gitea.io/gitea/modules/util"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@@ -14,7 +18,8 @@ import (
) )


const ( const (
tplOrgHome base.TplName = "org/home"
tplOrgHome base.TplName = "org/home"
tplOrgCourseHome base.TplName = "org/home_courses"
) )


// Home show organization home page // Home show organization home page
@@ -59,10 +64,16 @@ func Home(ctx *context.Context) {
case "fewestforks": case "fewestforks":
orderBy = models.SearchOrderByForks orderBy = models.SearchOrderByForks
default: default:
ctx.Data["SortType"] = "recentupdate"
orderBy = models.SearchOrderByRecentUpdated
}
if setting.Course.OrgName == org.Name {
ctx.Data["SortType"] = "newest"
orderBy = models.SearchOrderByNewest


} else {
ctx.Data["SortType"] = "recentupdate"
orderBy = models.SearchOrderByRecentUpdated
}
}
orderBy = orderBy + ",id"
keyword := strings.Trim(ctx.Query("q"), " ") keyword := strings.Trim(ctx.Query("q"), " ")
ctx.Data["Keyword"] = keyword ctx.Data["Keyword"] = keyword


@@ -76,9 +87,18 @@ func Home(ctx *context.Context) {
count int64 count int64
err error err error
) )
pageSize := setting.UI.User.RepoPagingNum
var CourseOptional util.OptionalBool = util.OptionalBoolNone
if setting.Course.OrgName == org.Name {
pageSize = 15
recommendCourseKeyWords, _ := repository.GetRecommendCourseKeyWords()
ctx.Data["CoursesKeywords"] = recommendCourseKeyWords

}

repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
ListOptions: models.ListOptions{ ListOptions: models.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
PageSize: pageSize,
Page: page, Page: page,
}, },
Keyword: keyword, Keyword: keyword,
@@ -87,6 +107,7 @@ func Home(ctx *context.Context) {
Private: ctx.IsSigned, Private: ctx.IsSigned,
Actor: ctx.User, Actor: ctx.User,
IncludeDescription: setting.UI.SearchRepoDescription, IncludeDescription: setting.UI.SearchRepoDescription,
Course: CourseOptional,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepository", err) ctx.ServerError("SearchRepository", err)
@@ -132,11 +153,27 @@ func Home(ctx *context.Context) {


//find org tag info //find org tag info
tags, err := models.GetAllOfficialTagRepos(org.ID, ctx.Org.IsOwner) tags, err := models.GetAllOfficialTagRepos(org.ID, ctx.Org.IsOwner)

if setting.Course.OrgName == org.Name {
for _, tag := range tags {
for _, repo := range tag.RepoList {
repo.GetCreator()
repo.GetOwner()
}
}

}

if err != nil { if err != nil {
ctx.ServerError("GetAllOfficialTagRepos", err) ctx.ServerError("GetAllOfficialTagRepos", err)
return return
} }


ctx.Data["tags"] = tags ctx.Data["tags"] = tags
ctx.HTML(200, tplOrgHome)
if setting.Course.OrgName == org.Name {
ctx.HTML(200, tplOrgCourseHome)
} else {
ctx.HTML(200, tplOrgHome)
}

} }

+ 7
- 3
routers/org/members.go View File

@@ -17,7 +17,8 @@ import (


const ( const (
// tplMembers template for organization members page // tplMembers template for organization members page
tplMembers base.TplName = "org/member/members"
tplMembers base.TplName = "org/member/members"
tplCourseMembers base.TplName = "org/member/course_members"
) )


// Members render organization users page // Members render organization users page
@@ -64,8 +65,11 @@ func Members(ctx *context.Context) {
ctx.Data["MembersIsPublicMember"] = membersIsPublic ctx.Data["MembersIsPublicMember"] = membersIsPublic
ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID) ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID)
ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus() ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus()

ctx.HTML(200, tplMembers)
if setting.Course.OrgName == org.Name {
ctx.HTML(200, tplCourseMembers)
} else {
ctx.HTML(200, tplMembers)
}
} }


// MembersAction response for operation to a member of organization // MembersAction response for operation to a member of organization


+ 10
- 3
routers/org/teams.go View File

@@ -10,6 +10,8 @@ import (
"path" "path"
"strings" "strings"


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

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@@ -22,7 +24,8 @@ import (


const ( const (
// tplTeams template path for teams list page // tplTeams template path for teams list page
tplTeams base.TplName = "org/team/teams"
tplTeams base.TplName = "org/team/teams"
tplCourseTeams base.TplName = "org/team/courseTeams"
// tplTeamNew template path for create new team page // tplTeamNew template path for create new team page
tplTeamNew base.TplName = "org/team/new" tplTeamNew base.TplName = "org/team/new"
// tplTeamMembers template path for showing team members page // tplTeamMembers template path for showing team members page
@@ -44,8 +47,12 @@ func Teams(ctx *context.Context) {
} }
} }
ctx.Data["Teams"] = org.Teams ctx.Data["Teams"] = org.Teams
if setting.Course.OrgName == org.Name {
ctx.HTML(200, tplCourseTeams)
} else {
ctx.HTML(200, tplTeams)
}


ctx.HTML(200, tplTeams)
} }


// TeamsAction response for join, leave, remove, add operations to team // TeamsAction response for join, leave, remove, add operations to team
@@ -143,7 +150,7 @@ func TeamsRepoAction(ctx *context.Context) {
case "add": case "add":
repoName := path.Base(ctx.Query("repo_name")) repoName := path.Base(ctx.Query("repo_name"))
var repo *models.Repository var repo *models.Repository
repo, err = models.GetRepositoryByName(ctx.Org.Organization.ID, repoName)
repo, err = models.GetRepositoryByOwnerAndAlias(ctx.Org.Organization.Name, repoName)
if err != nil { if err != nil {
if models.IsErrRepoNotExist(err) { if models.IsErrRepoNotExist(err) {
ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo")) ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo"))


+ 47
- 22
routers/repo/ai_model_manage.go View File

@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
uuid "github.com/satori/go.uuid" uuid "github.com/satori/go.uuid"
@@ -113,7 +114,7 @@ func saveModelByParameters(jobId string, versionName string, name string, versio
models.UpdateRepositoryUnits(ctx.Repo.Repository, units, deleteUnitTypes) models.UpdateRepositoryUnits(ctx.Repo.Repository, units, deleteUnitTypes)


log.Info("save model end.") log.Info("save model end.")
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, id, name, models.ActionCreateNewModelTask)
return nil return nil
} }


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


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


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


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

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

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


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


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


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

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


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

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


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




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

@@ -20,11 +20,11 @@ import (
"code.gitea.io/gitea/modules/labelmsg" "code.gitea.io/gitea/modules/labelmsg"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/minio_ext" "code.gitea.io/gitea/modules/minio_ext"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/worker" "code.gitea.io/gitea/modules/worker"

gouuid "github.com/satori/go.uuid" gouuid "github.com/satori/go.uuid"
) )


@@ -542,7 +542,7 @@ func GetSuccessChunks(ctx *context.Context) {
log.Error("GetPartInfos failed:%v", err.Error()) log.Error("GetPartInfos failed:%v", err.Error())
} }
} else { } else {
chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID)
chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID, fileName)
if err != nil { if err != nil {
log.Error("GetObsPartInfos failed:%v", err.Error()) log.Error("GetObsPartInfos failed:%v", err.Error())
} }
@@ -845,6 +845,9 @@ func CompleteMultipart(ctx *context.Context) {
ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err)) ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err))
return return
} }
dataset, _ := models.GetDatasetByID(attachment.DatasetID)
repository, _ := models.GetRepositoryByID(dataset.RepoID)
notification.NotifyOtherTask(ctx.User, repository, fmt.Sprint(attachment.Type), attachment.Name, models.ActionUploadAttachment)


if attachment.DatasetID != 0 { if attachment.DatasetID != 0 {
if isCanDecompress(attachment.Name) { if isCanDecompress(attachment.Name) {
@@ -865,7 +868,6 @@ func CompleteMultipart(ctx *context.Context) {
labelmsg.SendDecompressAttachToLabelOBS(string(attachjson)) labelmsg.SendDecompressAttachToLabelOBS(string(attachjson))
} }
} else { } else {
dataset, _ := models.GetDatasetByID(attachment.DatasetID)
var labelMap map[string]string var labelMap map[string]string
labelMap = make(map[string]string) labelMap = make(map[string]string)
labelMap["UUID"] = uuid labelMap["UUID"] = uuid


+ 465
- 24
routers/repo/cloudbrain.go View File

@@ -28,15 +28,21 @@ import (
) )


const ( const (
tplCloudBrainIndex base.TplName = "repo/cloudbrain/index"
tplCloudBrainNew base.TplName = "repo/cloudbrain/new" tplCloudBrainNew base.TplName = "repo/cloudbrain/new"
tplCloudBrainShow base.TplName = "repo/cloudbrain/show" tplCloudBrainShow base.TplName = "repo/cloudbrain/show"
tplCloudBrainShowModels base.TplName = "repo/cloudbrain/models/index" tplCloudBrainShowModels base.TplName = "repo/cloudbrain/models/index"

tplCloudBrainBenchmarkIndex base.TplName = "repo/cloudbrain/benchmark/index"
tplCloudBrainBenchmarkNew base.TplName = "repo/cloudbrain/benchmark/new"
tplCloudBrainBenchmarkShow base.TplName = "repo/cloudbrain/benchmark/show"
) )


var ( var (
gpuInfos *models.GpuInfos
categories *models.Categories
gpuInfos *models.GpuInfos
categories *models.Categories
benchmarkTypes *models.BenchmarkTypes
benchmarkGpuInfos *models.GpuInfos
benchmarkResourceSpecs *models.ResourceSpecs
) )


var jobNamePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-_]{1,34}[a-z0-9-]$`) var jobNamePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-_]{1,34}[a-z0-9-]$`)
@@ -124,11 +130,28 @@ func cloudBrainNewDataPrepare(ctx *context.Context) error {
} }
ctx.Data["benchmark_categories"] = categories.Category ctx.Data["benchmark_categories"] = categories.Category


if benchmarkTypes == nil {
if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil {
log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err, ctx.Data["MsgID"])
}
}
ctx.Data["benchmark_types"] = benchmarkTypes.BenchmarkType

if gpuInfos == nil { if gpuInfos == nil {
json.Unmarshal([]byte(setting.GpuTypes), &gpuInfos) json.Unmarshal([]byte(setting.GpuTypes), &gpuInfos)
} }
ctx.Data["gpu_types"] = gpuInfos.GpuInfo ctx.Data["gpu_types"] = gpuInfos.GpuInfo


if benchmarkGpuInfos == nil {
json.Unmarshal([]byte(setting.BenchmarkGpuTypes), &benchmarkGpuInfos)
}
ctx.Data["benchmark_gpu_types"] = benchmarkGpuInfos.GpuInfo

if benchmarkResourceSpecs == nil {
json.Unmarshal([]byte(setting.BenchmarkResourceSpecs), &benchmarkResourceSpecs)
}
ctx.Data["benchmark_resource_specs"] = benchmarkResourceSpecs.ResourceSpec

if cloudbrain.ResourceSpecs == nil { if cloudbrain.ResourceSpecs == nil {
json.Unmarshal([]byte(setting.ResourceSpecs), &cloudbrain.ResourceSpecs) json.Unmarshal([]byte(setting.ResourceSpecs), &cloudbrain.ResourceSpecs)
} }
@@ -155,9 +178,9 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
ctx.Data["PageIsCloudBrain"] = true ctx.Data["PageIsCloudBrain"] = true
jobName := form.JobName jobName := form.JobName
image := form.Image image := form.Image
command := form.Command
uuid := form.Attachment uuid := form.Attachment
jobType := form.JobType jobType := form.JobType
command := cloudbrain.Command
gpuQueue := form.GpuType gpuQueue := form.GpuType
codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath
resourceSpecId := form.ResourceSpecId resourceSpecId := form.ResourceSpecId
@@ -174,7 +197,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
return return
} }


count, err := models.GetCloudbrainCountByUserID(ctx.User.ID)
count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, jobType)
if err != nil { if err != nil {
log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"]) log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx) cloudBrainNewDataPrepare(ctx)
@@ -238,12 +261,14 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, storage.GetMinioPath(jobName, cloudbrain.CodeMountPath+"/"), err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, storage.GetMinioPath(jobName, cloudbrain.CodeMountPath+"/"),
storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/"),
storage.GetMinioPath(jobName, cloudbrain.BenchMarkMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.Snn4imagenetMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.BenchMarkMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.Snn4imagenetMountPath+"/"),
storage.GetMinioPath(jobName, cloudbrain.BrainScoreMountPath+"/"), jobType, gpuQueue, resourceSpecId)
storage.GetMinioPath(jobName, cloudbrain.BrainScoreMountPath+"/"), jobType, gpuQueue, form.Description,
0, 0, resourceSpecId)
if err != nil { if err != nil {
cloudBrainNewDataPrepare(ctx) cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form) ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form)
return return
} }

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all")
} }


@@ -276,7 +301,7 @@ func CloudBrainRestart(ctx *context.Context) {
break break
} }


count, err := models.GetCloudbrainCountByUserID(ctx.User.ID)
count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, string(models.JobTypeDebug))
if err != nil { if err != nil {
log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"]) log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"])
resultCode = "-1" resultCode = "-1"
@@ -310,25 +335,39 @@ func CloudBrainRestart(ctx *context.Context) {
}) })
} }


func CloudBrainBenchMarkShow(ctx *context.Context) {
if benchmarkTypes == nil {
if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil {
log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err, ctx.Data["MsgID"])
}
}
cloudBrainShow(ctx, tplCloudBrainBenchmarkShow)
}

func CloudBrainShow(ctx *context.Context) { func CloudBrainShow(ctx *context.Context) {
ctx.Data["PageIsCloudBrain"] = true
cloudBrainShow(ctx, tplCloudBrainShow)
}


var jobID = ctx.Params(":jobid")
task, err := models.GetCloudbrainByJobID(jobID)
func cloudBrainShow(ctx *context.Context, tpName base.TplName) {
ctx.Data["PageIsCloudBrain"] = true
var jobName = ctx.Params(":jobname")
debugListType := ctx.Query("debugListType")
task, err := models.GetCloudbrainByName(jobName)
if err != nil { if err != nil {
ctx.Data["error"] = err.Error() ctx.Data["error"] = err.Error()
} }

result, err := cloudbrain.GetJob(jobID)
result, err := cloudbrain.GetJob(task.JobID)
if err != nil { if err != nil {
ctx.Data["error"] = err.Error() ctx.Data["error"] = err.Error()
} }

if result != nil { if result != nil {
jobRes, _ := models.ConvertToJobResultPayload(result.Payload) jobRes, _ := models.ConvertToJobResultPayload(result.Payload)
jobRes.Resource.Memory = strings.ReplaceAll(jobRes.Resource.Memory, "Mi", "MB") jobRes.Resource.Memory = strings.ReplaceAll(jobRes.Resource.Memory, "Mi", "MB")
spec := "GPU数:" + strconv.Itoa(jobRes.Resource.NvidiaComGpu) + ",CPU数:" + strconv.Itoa(jobRes.Resource.CPU) + ",内存(MB):" + jobRes.Resource.Memory
ctx.Data["resource_spec"] = spec
taskRoles := jobRes.TaskRoles taskRoles := jobRes.TaskRoles
if jobRes.JobStatus.State != string(models.JobFailed) { if jobRes.JobStatus.State != string(models.JobFailed) {

taskRes, _ := models.ConvertToTaskPod(taskRoles[cloudbrain.SubTaskName].(map[string]interface{})) taskRes, _ := models.ConvertToTaskPod(taskRoles[cloudbrain.SubTaskName].(map[string]interface{}))
ctx.Data["taskRes"] = taskRes ctx.Data["taskRes"] = taskRes
task.Status = taskRes.TaskStatuses[0].State task.Status = taskRes.TaskStatuses[0].State
@@ -351,11 +390,44 @@ func CloudBrainShow(ctx *context.Context) {
} }


ctx.Data["result"] = jobRes ctx.Data["result"] = jobRes
} else {
log.Info("error:" + err.Error())
}
user, err := models.GetUserByID(task.UserID)
if err == nil {
task.User = user
} }


var duration int64
if task.Status == string(models.JobRunning) {
duration = time.Now().Unix() - int64(task.CreatedUnix)
} else {
duration = int64(task.UpdatedUnix) - int64(task.CreatedUnix)
}
if benchmarkTypes != nil {
for _, benchmarkType := range benchmarkTypes.BenchmarkType {
if task.BenchmarkTypeID == benchmarkType.Id {
ctx.Data["BenchmarkTypeName"] = benchmarkType.First
for _, benchmarkChildType := range benchmarkType.Second {
if task.BenchmarkChildTypeID == benchmarkChildType.Id {
ctx.Data["BenchmarkChildTypeName"] = benchmarkChildType.Value
break
}
}
break
}
}
}

ctx.Data["duration"] = util.AddZero(duration/3600000) + ":" + util.AddZero(duration%3600000/60000) + ":" + util.AddZero(duration%60000/1000)
ctx.Data["task"] = task ctx.Data["task"] = task
ctx.Data["jobID"] = jobID
ctx.HTML(200, tplCloudBrainShow)
// ctx.Data["jobID"] = task.JobID
ctx.Data["jobName"] = task.JobName
version_list_task := make([]*models.Cloudbrain, 0)
version_list_task = append(version_list_task, task)
ctx.Data["version_list_task"] = version_list_task
ctx.Data["debugListType"] = debugListType
ctx.HTML(200, tpName)
} }


func CloudBrainDebug(ctx *context.Context) { func CloudBrainDebug(ctx *context.Context) {
@@ -393,7 +465,7 @@ func CloudBrainStop(ctx *context.Context) {


task := ctx.Cloudbrain task := ctx.Cloudbrain
for { for {
if task.Status == string(models.JobStopped) || task.Status == string(models.JobFailed) {
if task.Status == string(models.JobStopped) || task.Status == string(models.JobFailed) || task.Status == string(models.JobSucceeded) {
log.Error("the job(%s) has been stopped", task.JobName, ctx.Data["msgID"]) log.Error("the job(%s) has been stopped", task.JobName, ctx.Data["msgID"])
resultCode = "-1" resultCode = "-1"
errorMsg = "system error" errorMsg = "system error"
@@ -510,22 +582,38 @@ func logErrorAndUpdateJobStatus(err error, taskInfo *models.Cloudbrain) {
} }


func CloudBrainDel(ctx *context.Context) { func CloudBrainDel(ctx *context.Context) {
var listType = ctx.Query("debugListType")
if err := deleteCloudbrainJob(ctx); err != nil {
log.Error("deleteCloudbrainJob failed: %v", err, ctx.Data["msgID"])
ctx.ServerError(err.Error(), err)
return
}

var isAdminPage = ctx.Query("isadminpage")
if ctx.IsUserSiteAdmin() && isAdminPage == "true" {
ctx.Redirect(setting.AppSubURL + "/admin" + "/cloudbrains")
} else {
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=" + listType)
}
}

func deleteCloudbrainJob(ctx *context.Context) error {
task := ctx.Cloudbrain task := ctx.Cloudbrain


if task.Status != string(models.JobStopped) && task.Status != string(models.JobFailed) {
if task.Status != string(models.JobStopped) && task.Status != string(models.JobFailed) && task.Status != string(models.JobSucceeded) {
log.Error("the job(%s) has not been stopped", task.JobName, ctx.Data["msgID"]) log.Error("the job(%s) has not been stopped", task.JobName, ctx.Data["msgID"])
ctx.ServerError("the job has not been stopped", errors.New("the job has not been stopped"))
return
return errors.New("the job has not been stopped")
} }


err := models.DeleteJob(task) err := models.DeleteJob(task)
if err != nil { if err != nil {
ctx.ServerError("DeleteJob failed", err)
return
log.Error("DeleteJob failed: %v", err, ctx.Data["msgID"])
return err
} }


deleteJobStorage(task.JobName, models.TypeCloudBrainOne) deleteJobStorage(task.JobName, models.TypeCloudBrainOne)
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all")

return nil
} }


func CloudBrainShowModels(ctx *context.Context) { func CloudBrainShowModels(ctx *context.Context) {
@@ -632,6 +720,12 @@ func CloudBrainDownloadModel(ctx *context.Context) {
} }


func GetRate(ctx *context.Context) { func GetRate(ctx *context.Context) {
isObjectDetcionAll := ctx.QueryBool("isObjectDetcionAll")
if isObjectDetcionAll {
ctx.Redirect(setting.BenchmarkServerHost + "?username=admin")
return
}

var jobID = ctx.Params(":jobid") var jobID = ctx.Params(":jobid")
job, err := models.GetCloudbrainByJobID(jobID) job, err := models.GetCloudbrainByJobID(jobID)
if err != nil { if err != nil {
@@ -640,6 +734,7 @@ func GetRate(ctx *context.Context) {
} }


if job.JobType == string(models.JobTypeBenchmark) { if job.JobType == string(models.JobTypeBenchmark) {
log.Info("url=" + setting.BenchmarkServerHost + "?username=" + ctx.User.Name)
ctx.Redirect(setting.BenchmarkServerHost + "?username=" + ctx.User.Name) ctx.Redirect(setting.BenchmarkServerHost + "?username=" + ctx.User.Name)
} else if job.JobType == string(models.JobTypeSnn4imagenet) { } else if job.JobType == string(models.JobTypeSnn4imagenet) {
ctx.Redirect(setting.Snn4imagenetServerHost) ctx.Redirect(setting.Snn4imagenetServerHost)
@@ -855,7 +950,14 @@ func SyncCloudbrainStatus() {
log.Error("UpdateJob(%s) failed:%v", task.JobName, err) log.Error("UpdateJob(%s) failed:%v", task.JobName, err)
} }


if task.Duration >= setting.MaxDuration {
var maxDuration int64
if task.JobType == string(models.JobTypeBenchmark) {
maxDuration = setting.BenchmarkMaxDuration
} else {
maxDuration = setting.MaxDuration
}

if task.Duration >= maxDuration {
log.Info("begin to stop job(%s), because of the duration", task.JobName) log.Info("begin to stop job(%s), because of the duration", task.JobName)
err = cloudbrain.StopJob(task.JobID) err = cloudbrain.StopJob(task.JobID)
if err != nil { if err != nil {
@@ -872,7 +974,8 @@ func SyncCloudbrainStatus() {
} }
} else if task.Type == models.TypeCloudBrainTwo { } else if task.Type == models.TypeCloudBrainTwo {
if task.JobType == string(models.JobTypeDebug) { if task.JobType == string(models.JobTypeDebug) {
result, err := modelarts.GetJob(task.JobID)
//result, err := modelarts.GetJob(task.JobID)
result, err := modelarts.GetNotebook2(task.JobID)
if err != nil { if err != nil {
log.Error("GetJob(%s) failed:%v", task.JobName, err) log.Error("GetJob(%s) failed:%v", task.JobName, err)
continue continue
@@ -923,3 +1026,341 @@ func SyncCloudbrainStatus() {


return return
} }

func CloudBrainBenchmarkIndex(ctx *context.Context) {
MustEnableCloudbrain(ctx)
repo := ctx.Repo.Repository
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}

var jobTypes []string
jobTypes = append(jobTypes, string(models.JobTypeBenchmark))
ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoID: repo.ID,
Type: models.TypeCloudBrainOne,
JobTypes: jobTypes,
})
if err != nil {
ctx.ServerError("Get debugjob faild:", err)
return
}

for i, task := range ciTasks {
ciTasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain)
ciTasks[i].Cloudbrain.ComputeResource = task.ComputeResource
var duration int64
if task.Status == string(models.JobRunning) {
duration = time.Now().Unix() - int64(task.Cloudbrain.CreatedUnix)
} else {
duration = int64(task.Cloudbrain.UpdatedUnix) - int64(task.Cloudbrain.CreatedUnix)
}
ciTasks[i].TrainJobDuration = util.AddZero(duration/3600000) + ":" + util.AddZero(duration%3600000/60000) + ":" + util.AddZero(duration%60000/1000)
}

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

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

func GetChildTypes(ctx *context.Context) {
benchmarkTypeID := ctx.QueryInt("benchmark_type_id")
re := make(map[string]interface{})
for {
if benchmarkTypes == nil {
if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil {
log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err, ctx.Data["MsgID"])
re["errMsg"] = "system error"
break
}
}
var isExist bool
for _, benchmarkType := range benchmarkTypes.BenchmarkType {
if benchmarkTypeID == benchmarkType.Id {
isExist = true
re["child_types"] = benchmarkType.Second
re["result_code"] = "0"
break
}
}
if !isExist {
re["result_code"] = "1"
log.Error("no such benchmark_type_id", ctx.Data["MsgID"])
re["errMsg"] = "system error"
break
}
break
}
ctx.JSON(200, re)
}

func CloudBrainBenchmarkNew(ctx *context.Context) {
ctx.Data["description"] = ""
ctx.Data["benchmarkTypeID"] = -1
ctx.Data["benchmark_child_types_id_hidden"] = -1
err := cloudBrainNewDataPrepare(ctx)
if err != nil {
ctx.ServerError("get new cloudbrain info failed", err)
return
}
ctx.HTML(200, tplCloudBrainBenchmarkNew)
}

func getBenchmarkAttachment(benchmarkTypeID, benchmarkChildTypeID int) (*models.BenchmarkDataset, error) {
var childInfo *models.BenchmarkDataset
if benchmarkTypes == nil {
if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil {
log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err)
return childInfo, err
}
}

var isExist bool
for _, benchmarkType := range benchmarkTypes.BenchmarkType {
if benchmarkType.Id == benchmarkTypeID {
for _, childType := range benchmarkType.Second {
if childType.Id == benchmarkChildTypeID {
childInfo = childType
isExist = true
break
}
}
break
}
}

if !isExist {
log.Error("no such benchmark_type_id&benchmark_child_type_id")
return childInfo, errors.New("no such benchmark_type_id&benchmark_child_type_id")
}

return childInfo, nil
}

func getBenchmarkGpuQueue(gpuQueue string) (string, error) {
queue := ""
if benchmarkGpuInfos == nil {
if err := json.Unmarshal([]byte(setting.BenchmarkGpuTypes), &benchmarkGpuInfos); err != nil {
log.Error("json.Unmarshal BenchmarkGpuTypes(%s) failed:%v", setting.BenchmarkGpuTypes, err)
return queue, err
}
}

var isExist bool
for _, gpuInfo := range benchmarkGpuInfos.GpuInfo {
if gpuQueue == gpuInfo.Queue {
isExist = true
queue = gpuQueue
break
}
}

if !isExist {
log.Error("no such gpuQueue, %s", gpuQueue)
return queue, errors.New("no such gpuQueue")
}

return queue, nil
}

func getBenchmarkResourceSpec(resourceSpecID int) (int, error) {
var id int
if benchmarkResourceSpecs == nil {
if err := json.Unmarshal([]byte(setting.BenchmarkResourceSpecs), &benchmarkResourceSpecs); err != nil {
log.Error("json.Unmarshal BenchmarkResourceSpecs(%s) failed:%v", setting.BenchmarkResourceSpecs, err)
return id, err
}
}

var isExist bool
for _, resourceSpec := range benchmarkResourceSpecs.ResourceSpec {
if resourceSpecID == resourceSpec.Id {
isExist = true
id = resourceSpecID
break
}
}

if !isExist {
log.Error("no such resourceSpecID, %d", resourceSpecID)
return id, errors.New("no such resourceSpec")
}

return id, nil
}

func CloudBrainBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
ctx.Data["PageIsCloudBrain"] = true
jobName := form.JobName
image := form.Image
gpuQueue := form.GpuType
command := cloudbrain.CommandBenchmark
codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath
resourceSpecId := cloudbrain.BenchMarkResourceID
benchmarkTypeID := form.BenchmarkTypeID
benchmarkChildTypeID := form.BenchmarkChildTypeID

ctx.Data["description"] = form.Description
ctx.Data["benchmarkTypeID"] = benchmarkTypeID
ctx.Data["benchmark_child_types_id_hidden"] = benchmarkChildTypeID
if !jobNamePattern.MatchString(jobName) {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplCloudBrainBenchmarkNew, &form)
return
}

childInfo, err := getBenchmarkAttachment(benchmarkTypeID, benchmarkChildTypeID)
if err != nil {
log.Error("getBenchmarkAttachment failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("benchmark type error", tplCloudBrainBenchmarkNew, &form)
return
}

_, err = getBenchmarkGpuQueue(gpuQueue)
if err != nil {
log.Error("getBenchmarkGpuQueue failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("gpu queue error", tplCloudBrainBenchmarkNew, &form)
return
}

_, err = getBenchmarkResourceSpec(resourceSpecId)
if err != nil {
log.Error("getBenchmarkResourceSpec failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("resource spec error", tplCloudBrainBenchmarkNew, &form)
return
}

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

_, err = models.GetCloudbrainByName(jobName)
if err == nil {
log.Error("the job name did already exist", ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("the job name did already exist", tplCloudBrainBenchmarkNew, &form)
return
} else {
if !models.IsErrJobNotExist(err) {
log.Error("GetCloudbrainByName failed, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form)
return
}
}
repo := ctx.Repo.Repository
os.RemoveAll(codePath)
if err := downloadCode(repo, codePath); err != nil {
log.Error("downloadCode failed, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form)
return
}

if _, err := os.Stat(codePath + "/train.py"); err != nil {
if os.IsNotExist(err) {
// file does not exist
log.Error("train.py does not exist, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("train.py does not exist", tplCloudBrainBenchmarkNew, &form)
} else {
log.Error("Stat failed, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form)
}
return
} else if _, err := os.Stat(codePath + "/test.py"); err != nil {
if os.IsNotExist(err) {
// file does not exist
log.Error("test.py does not exist, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("test.py does not exist", tplCloudBrainBenchmarkNew, &form)
} else {
log.Error("Stat failed, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form)
}
return
}

if err := uploadCodeToMinio(codePath+"/", jobName, cloudbrain.CodeMountPath+"/"); err != nil {
log.Error("uploadCodeToMinio failed, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form)
return
}

benchmarkPath := setting.JobPath + jobName + cloudbrain.BenchMarkMountPath
var gpuType string
for _, gpuInfo := range gpuInfos.GpuInfo {
if gpuInfo.Queue == gpuQueue {
gpuType = gpuInfo.Value
}
}

if err := downloadRateCode(repo, jobName, childInfo.Owner, childInfo.RepoName, benchmarkPath, form.BenchmarkCategory, gpuType); err != nil {
log.Error("downloadRateCode failed, %v", err, ctx.Data["MsgID"])
//cloudBrainNewDataPrepare(ctx)
//ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form)
//return
}

if err := uploadCodeToMinio(benchmarkPath+"/", jobName, cloudbrain.BenchMarkMountPath+"/"); err != nil {
log.Error("uploadCodeToMinio failed, %v", err, ctx.Data["MsgID"])
//cloudBrainNewDataPrepare(ctx)
//ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form)
//return
}

err = cloudbrain.GenerateTask(ctx, jobName, image, command, childInfo.Attachment, storage.GetMinioPath(jobName, cloudbrain.CodeMountPath+"/"),
storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/"),
storage.GetMinioPath(jobName, cloudbrain.BenchMarkMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.Snn4imagenetMountPath+"/"),
storage.GetMinioPath(jobName, cloudbrain.BrainScoreMountPath+"/"), string(models.JobTypeBenchmark), gpuQueue, form.Description,
benchmarkTypeID, benchmarkChildTypeID, resourceSpecId)
if err != nil {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(err.Error(), tplCloudBrainBenchmarkNew, &form)
return
}

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

func BenchmarkDel(ctx *context.Context) {
if err := deleteCloudbrainJob(ctx); err != nil {
log.Error("deleteCloudbrainJob failed: %v", err, ctx.Data["msgID"])
ctx.ServerError(err.Error(), err)
return
}

var isAdminPage = ctx.Query("isadminpage")
if ctx.IsUserSiteAdmin() && isAdminPage == "true" {
ctx.Redirect(setting.AppSubURL + "/admin" + "/cloudbrains")
} else {
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain/benchmark")
}
}

+ 196
- 0
routers/repo/course.go View File

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

import (
"net/http"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
repo_service "code.gitea.io/gitea/services/repository"
)

const (
tplCreateCourse base.TplName = "repo/createCourse"
)

func CreateCourse(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_course")
org, _ := models.GetUserByName(setting.Course.OrgName)

ctx.Data["Owner"] = org
ctx.Data["IsCourse"] = true

ctx.HTML(200, tplCreateCourse)

}

func CreateCoursePost(ctx *context.Context, form auth.CreateCourseForm) {
ctx.Data["Title"] = ctx.Tr("new_course")

if ctx.Written() {
return
}

org, _ := models.GetUserByName(setting.Course.OrgName)

ctx.Data["Owner"] = org
ctx.Data["IsCourse"] = true

var topics = make([]string, 0)
var topicsStr = strings.TrimSpace(form.Topics)
if len(topicsStr) > 0 {
topics = strings.Split(topicsStr, ",")
}

validTopics, invalidTopics := models.SanitizeAndValidateTopics(topics)

if len(validTopics) > 25 {
ctx.RenderWithErr(ctx.Tr("repo.topic.count_prompt"), tplCreateCourse, form)
return
}

if len(invalidTopics) > 0 {
ctx.RenderWithErr(ctx.Tr("repo.topic.format_prompt"), tplCreateCourse, form)
return
}

var repo *models.Repository
var err error

if setting.Course.OrgName == "" || setting.Course.TeamName == "" {
log.Error("then organization name or team name of course is empty.")
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}

org, team, err := getOrgAndTeam()

if err != nil {
log.Error("Failed to get team from db.", err)
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}
isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID})
if err != nil {
log.Error("Failed to get user in team from db.")
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}

if !isInTeam {
err = models.AddTeamMember(team, ctx.User.ID)
if err != nil {
log.Error("Failed to add user to team.")
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}
}

if ctx.HasError() {
ctx.HTML(200, tplCreateCourse)
return
}

repo, err = repo_service.CreateRepository(ctx.User, org, models.CreateRepoOptions{
Name: form.RepoName,
Alias: form.Alias,
Description: form.Description,
Gitignores: "",
IssueLabels: "",
License: "",
Readme: "Default",
IsPrivate: false,
DefaultBranch: "master",
AutoInit: true,
IsCourse: true,
Topics: validTopics,
})
if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, org.Name, repo.Name)
ctx.Redirect(setting.AppSubURL + "/" + org.Name + "/" + repo.Name)
return
}

handleCreateCourseError(ctx, org, err, "CreateCoursePost", tplCreateCourse, &form)
}

func AddCourseOrg(ctx *context.Context) {

_, team, err := getOrgAndTeam()

if err != nil {
log.Error("Failed to get team from db.", err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"message": ctx.Tr("repo.addCourseOrgFail"),
})
return
}
isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID})
if err != nil {
log.Error("Failed to get user in team from db.", err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"message": ctx.Tr("repo.add_course_org_fail"),
})
return
}

if !isInTeam {
err = models.AddTeamMember(team, ctx.User.ID)
if err != nil {
log.Error("Failed to add user to team.", err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"message": ctx.Tr("repo.add_course_org_fail"),
})
return
}
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 0,
"message": "",
})

}

func getOrgAndTeam() (*models.User, *models.Team, error) {
org, err := models.GetUserByName(setting.Course.OrgName)

if err != nil {
log.Error("Failed to get organization from db.", err)
return nil, nil, err
}

team, err := models.GetTeam(org.ID, setting.Course.TeamName)

if err != nil {
log.Error("Failed to get team from db.", err)

return nil, nil, err
}
return org, team, nil
}

func handleCreateCourseError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) {
switch {
case models.IsErrReachLimitOfRepo(err):
ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_course_creation", owner.MaxCreationLimit()), tpl, form)
case models.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.course_name_been_taken"), tpl, form)
case models.IsErrNameReserved(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.course_name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.course_name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form)
default:
ctx.ServerError(name, err)
}
}

+ 2
- 1
routers/repo/download.go View File

@@ -28,7 +28,8 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error {
buf = buf[:n] buf = buf[:n]
} }


ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400")
//ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400")
ctx.Resp.Header().Set("Cache-Control", "max-age=0")
name = path.Base(name) name = path.Base(name)


// Google Chrome dislike commas in filenames, so let's change it to a space // Google Chrome dislike commas in filenames, so let's change it to a space


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


+ 2
- 1
routers/repo/pull.go View File

@@ -105,6 +105,7 @@ func getForkRepository(ctx *context.Context) *models.Repository {
return nil return nil
} }
ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name
ctx.Data["ForkDisplayName"] = forkRepo.FullDisplayName()
ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID


if err := ctx.User.GetOwnedOrganizations(); err != nil { if err := ctx.User.GetOwnedOrganizations(); err != nil {
@@ -221,7 +222,7 @@ func ForkPost(ctx *context.Context, form auth.CreateRepoForm) {
} }
} }


repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description)
repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description, form.Alias)
if err != nil { if err != nil {
ctx.Data["Err_RepoName"] = true ctx.Data["Err_RepoName"] = true
switch { switch {


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

@@ -13,6 +13,7 @@ import (
"path" "path"
"regexp" "regexp"
"strings" "strings"
"unicode/utf8"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
@@ -566,7 +567,7 @@ func CheckName(ctx *context.Context) {
var r = make(map[string]string, 1) var r = make(map[string]string, 1)
q := ctx.Query("q") q := ctx.Query("q")
owner := ctx.Query("owner") owner := ctx.Query("owner")
if q == "" || owner == "" || len(q) > 100 || !validation.ValidAlphaDashDotChinese(q) {
if q == "" || owner == "" || utf8.RuneCountInString(q) > 100 || !validation.ValidAlphaDashDotChinese(q) {
r["name"] = "" r["name"] = ""
ctx.JSON(200, r) ctx.JSON(200, r)
return return


+ 18
- 10
routers/repo/repo_statistic.go View File

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


isInitMinMaxRadar := false isInitMinMaxRadar := false


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


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


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


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


repoStat := models.RepoStatistic{ repoStat := models.RepoStatistic{
RepoID: repo.ID, RepoID: repo.ID,
Date: date, Date: date,
Name: repo.Name, Name: repo.Name,
Alias: repo.Alias,
IsPrivate: repo.IsPrivate, IsPrivate: repo.IsPrivate,
IsMirror: repo.IsMirror, IsMirror: repo.IsMirror,
OwnerName: repo.OwnerName, OwnerName: repo.OwnerName,
@@ -162,9 +165,10 @@ func RepoStatisticDaily(date string) {
} }


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

continue continue
} }


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


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

//radar map //radar map
log.Info("begin statistic radar") log.Info("begin statistic radar")
for _, radarInit := range reposRadar { for _, radarInit := range reposRadar {
@@ -275,7 +283,7 @@ func RepoStatisticDaily(date string) {
} }


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


func getDatasetSize(repo *models.Repository) (int64, error) { func getDatasetSize(repo *models.Repository) (int64, error) {


+ 40
- 6
routers/repo/setting.go View File

@@ -6,6 +6,7 @@
package repo package repo


import ( import (
"code.gitea.io/gitea/modules/notification"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -50,6 +51,8 @@ func Settings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
SetModelCount(ctx)
SetJobCount(ctx)
ctx.HTML(200, tplSettingsOptions) ctx.HTML(200, tplSettingsOptions)
} }


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

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


switch ctx.Query("action") { switch ctx.Query("action") {
@@ -67,6 +71,30 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return return
} }


newAlias := form.Alias
var aliasChanged = false
// Check if repository alias has been changed.
if strings.ToLower(repo.Alias) != strings.ToLower(newAlias) {
aliasChanged = true
//check new alias is available or not
if err := models.IsRepositoryAliasAvailable(ctx.Repo.Owner, newAlias); err != nil {
ctx.Data["Err_Alias"] = true
switch {
case models.IsErrRepoAlreadyExist(err):
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form)
case models.IsErrNameReserved(err):
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplSettingsOptions, &form)
case models.IsErrNamePatternNotAllowed(err):
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
default:
ctx.ServerError("ChangeRepositoryName", err)
}
return
}

log.Trace("Repository alias changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Alias, newAlias)
}

newRepoName := form.RepoName newRepoName := form.RepoName
// Check if repository name has been changed. // Check if repository name has been changed.
if repo.LowerName != strings.ToLower(newRepoName) { if repo.LowerName != strings.ToLower(newRepoName) {
@@ -92,13 +120,19 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {


log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName) log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
} }
//notify
if aliasChanged {
notification.NotifyRenameRepository(ctx.Repo.Owner, repo, repo.Alias)
}

// In case it's just a case change. // In case it's just a case change.
repo.Name = newRepoName repo.Name = newRepoName
repo.LowerName = strings.ToLower(newRepoName) repo.LowerName = strings.ToLower(newRepoName)
repo.Description = form.Description repo.Description = form.Description
repo.Website = form.Website repo.Website = form.Website
repo.IsTemplate = form.Template repo.IsTemplate = form.Template
repo.Alias = form.Alias
repo.Alias = newAlias
repo.LowerAlias = strings.ToLower(newAlias)


// Visibility of forked repository is forced sync with base repository. // Visibility of forked repository is forced sync with base repository.
if repo.IsFork { if repo.IsFork {
@@ -378,7 +412,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Error(404) ctx.Error(404)
return return
} }
if repo.Name != form.RepoName {
if repo.Alias != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }
@@ -405,7 +439,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Error(404) ctx.Error(404)
return return
} }
if repo.Name != form.RepoName {
if repo.Alias != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }
@@ -443,7 +477,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Error(404) ctx.Error(404)
return return
} }
if repo.Name != form.RepoName {
if repo.Alias != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }
@@ -463,7 +497,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Error(404) ctx.Error(404)
return return
} }
if repo.Name != form.RepoName {
if repo.Alias != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }


+ 13
- 11
routers/repo/user_data_analysis.go View File

@@ -63,11 +63,13 @@ func queryUserDataPage(ctx *context.Context, tableName string, queryObj interfac
_, count := models.QueryUserStaticDataByTableName(1, 1, tableName, queryObj, userName) _, count := models.QueryUserStaticDataByTableName(1, 1, tableName, queryObj, userName)
var indexTotal int64 var indexTotal int64
indexTotal = 0 indexTotal = 0
row := 1
for { for {
re, _ := models.QueryUserStaticDataByTableName(int(indexTotal), PAGE_SIZE, tableName, queryObj, "") re, _ := models.QueryUserStaticDataByTableName(int(indexTotal), PAGE_SIZE, tableName, queryObj, "")
log.Info("return count=" + fmt.Sprint(count)) log.Info("return count=" + fmt.Sprint(count))
for i, userRecord := range re {
rows := fmt.Sprint(i + 2)
for _, userRecord := range re {
row++
rows := fmt.Sprint(row)
xlsx.SetCellValue(sheetName, "A"+rows, userRecord.ID) xlsx.SetCellValue(sheetName, "A"+rows, userRecord.ID)
xlsx.SetCellValue(sheetName, "B"+rows, userRecord.Name) xlsx.SetCellValue(sheetName, "B"+rows, userRecord.Name)
xlsx.SetCellValue(sheetName, "C"+rows, userRecord.CodeMergeCount) xlsx.SetCellValue(sheetName, "C"+rows, userRecord.CodeMergeCount)
@@ -88,22 +90,22 @@ func queryUserDataPage(ctx *context.Context, tableName string, queryObj interfac
xlsx.SetCellValue(sheetName, "P"+rows, formatTime[0:len(formatTime)-3]) xlsx.SetCellValue(sheetName, "P"+rows, formatTime[0:len(formatTime)-3])


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


//设置默认打开的表单
xlsx.SetActiveSheet(index)
filename := sheetName + "_" + ctx.Tr("user.static."+tableName) + ".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())
}
indexTotal += PAGE_SIZE indexTotal += PAGE_SIZE
if indexTotal >= count { if indexTotal >= count {
break break
} }
} }
//设置默认打开的表单
xlsx.SetActiveSheet(index)
filename := sheetName + "_" + ctx.Tr("user.static."+tableName) + ".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 { } else {
re, count := models.QueryUserStaticDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName) re, count := models.QueryUserStaticDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName)
mapInterface := make(map[string]interface{}) mapInterface := make(map[string]interface{})


+ 7
- 1
routers/repo/view.go View File

@@ -35,6 +35,7 @@ import (
const ( const (
tplRepoEMPTY base.TplName = "repo/empty" tplRepoEMPTY base.TplName = "repo/empty"
tplRepoHome base.TplName = "repo/home" tplRepoHome base.TplName = "repo/home"
tplCourseHome base.TplName = "repo/courseHome"
tplWatchers base.TplName = "repo/watchers" tplWatchers base.TplName = "repo/watchers"
tplForks base.TplName = "repo/forks" tplForks base.TplName = "repo/forks"
tplMigrating base.TplName = "repo/migrating" tplMigrating base.TplName = "repo/migrating"
@@ -855,7 +856,12 @@ func renderCode(ctx *context.Context) {
ctx.Data["TreeLink"] = treeLink ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink ctx.Data["BranchLink"] = branchLink
ctx.HTML(200, tplRepoHome)
if ctx.Repo.Repository.RepoType == models.RepoCourse {
ctx.HTML(200, tplCourseHome)
} else {
ctx.HTML(200, tplRepoHome)
}

} }


// RenderUserCards render a page show users according the input templaet // RenderUserCards render a page show users according the input templaet


+ 69
- 8
routers/routes/routes.go View File

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


"code.gitea.io/gitea/routers/authentication"

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


"code.gitea.io/gitea/routers/operation" "code.gitea.io/gitea/routers/operation"
@@ -274,6 +276,8 @@ func RegisterRoutes(m *macaron.Macaron) {
ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true}) reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
reqBasicAuth := context.Toggle(&context.ToggleOptions{BasicAuthRequired: true, DisableCSRF: true}) reqBasicAuth := context.Toggle(&context.ToggleOptions{BasicAuthRequired: true, DisableCSRF: true})
reqWechatBind := context.Toggle(&context.ToggleOptions{WechatAuthRequired: true})
reqWechatBindForApi := context.Toggle(&context.ToggleOptions{WechatAuthRequiredForAPI: true})


bindIgnErr := binding.BindIgnErr bindIgnErr := binding.BindIgnErr
validation.AddBindingRules() validation.AddBindingRules()
@@ -321,6 +325,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/recommend/repo", routers.RecommendRepoFromPromote) m.Get("/recommend/repo", routers.RecommendRepoFromPromote)
m.Get("/all/search/", routers.Search) m.Get("/all/search/", routers.Search)
m.Get("/all/dosearch/", routers.SearchApi) m.Get("/all/dosearch/", routers.SearchApi)
m.Get("/home/term", routers.HomeTerm)
m.Group("/explore", func() { m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) { m.Get("", func(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/explore/repos") ctx.Redirect(setting.AppSubURL + "/explore/repos")
@@ -393,6 +398,13 @@ func RegisterRoutes(m *macaron.Macaron) {
}, ignSignInAndCsrf, reqSignIn) }, ignSignInAndCsrf, reqSignIn)
m.Post("/login/oauth/access_token", bindIgnErr(auth.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth) m.Post("/login/oauth/access_token", bindIgnErr(auth.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth)


m.Group("/authentication/wechat", func() {
m.Get("/qrCode4Bind", authentication.GetQRCode4Bind)
m.Get("/bindStatus", authentication.GetBindStatus)
m.Post("/unbind", authentication.UnbindWechat)
m.Get("/bind", authentication.GetBindPage)
}, reqSignIn)

m.Group("/user/settings", func() { m.Group("/user/settings", func() {
m.Get("", userSetting.Profile) m.Get("", userSetting.Profile)
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), userSetting.ProfilePost) m.Post("", bindIgnErr(auth.UpdateProfileForm{}), userSetting.ProfilePost)
@@ -509,6 +521,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", admin.Datasets) m.Get("", admin.Datasets)
// m.Post("/delete", admin.DeleteDataset) // m.Post("/delete", admin.DeleteDataset)
}) })
m.Group("/cloudbrains", func() {
m.Get("", admin.CloudBrains)
m.Get("/download", admin.DownloadCloudBrains)
})


m.Group("/^:configType(hooks|system-hooks)$", func() { m.Group("/^:configType(hooks|system-hooks)$", func() {
m.Get("", admin.DefaultOrSystemWebhooks) m.Get("", admin.DefaultOrSystemWebhooks)
@@ -710,6 +726,13 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqSignIn) }, reqSignIn)
// ***** END: Organization ***** // ***** END: Organization *****


m.Group("/course", func() {
m.Get("/create", repo.CreateCourse)
m.Post("/create", bindIgnErr(auth.CreateCourseForm{}), repo.CreateCoursePost)
m.Get("/addOrg", repo.AddCourseOrg)

}, reqSignIn)

// ***** START: Repository ***** // ***** START: Repository *****
m.Group("/repo", func() { m.Group("/repo", func() {
m.Get("/create", repo.Create) m.Get("/create", repo.Create)
@@ -969,19 +992,36 @@ func RegisterRoutes(m *macaron.Macaron) {
}, context.RepoRef()) }, context.RepoRef())


m.Group("/cloudbrain", func() { m.Group("/cloudbrain", func() {
m.Group("/:jobid", func() {
m.Group("/:jobname", func() {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainShow) m.Get("", reqRepoCloudBrainReader, repo.CloudBrainShow)
})
m.Group("/:jobid", func() {
m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDebug) m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDebug)
m.Post("/commit_image", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.CloudBrainCommitImage) m.Post("/commit_image", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.CloudBrainCommitImage)
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainStop) m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainDel) m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainDel)
m.Post("/restart", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainRestart)
m.Post("/restart", reqWechatBindForApi, cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainRestart)
m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate) m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate)
m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels) m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels)
m.Get("/download_model", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDownloadModel) m.Get("/download_model", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDownloadModel)
}) })
m.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)

m.Group("/benchmark", func() {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainBenchmarkIndex)
m.Group("/:jobname", func() {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainBenchMarkShow)
})
m.Group("/:jobid", func() {
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.BenchmarkDel)
m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate)
})
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainBenchmarkNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainBenchmarkCreate)
m.Get("/get_child_types", repo.GetChildTypes)
})
}, context.RepoRef()) }, context.RepoRef())
m.Group("/modelmanage", func() { m.Group("/modelmanage", func() {
m.Post("/create_model", reqRepoModelManageWriter, repo.SaveModel) m.Post("/create_model", reqRepoModelManageWriter, repo.SaveModel)
@@ -1011,6 +1051,7 @@ func RegisterRoutes(m *macaron.Macaron) {


m.Group("/modelarts", func() { m.Group("/modelarts", func() {
m.Group("/notebook", func() { m.Group("/notebook", func() {
/* v1.0
m.Group("/:jobid", func() { m.Group("/:jobid", func() {
m.Get("", reqRepoCloudBrainReader, repo.NotebookShow) m.Get("", reqRepoCloudBrainReader, repo.NotebookShow)
m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.NotebookDebug) m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.NotebookDebug)
@@ -1019,6 +1060,15 @@ func RegisterRoutes(m *macaron.Macaron) {
}) })
m.Get("/create", reqRepoCloudBrainWriter, repo.NotebookNew) m.Get("/create", reqRepoCloudBrainWriter, repo.NotebookNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.NotebookCreate) m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.NotebookCreate)
*/
m.Group("/:jobid", func() {
m.Get("", reqRepoCloudBrainReader, repo.NotebookShow)
m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.NotebookDebug2)
m.Post("/:action", reqRepoCloudBrainWriter, repo.NotebookManage)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel)
})
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.NotebookNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create)
}) })


m.Group("/train-job", func() { m.Group("/train-job", func() {
@@ -1028,14 +1078,25 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobStop) m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobDel) m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobDel)
m.Get("/model_download", cloudbrain.AdminOrJobCreaterRight, repo.ModelDownload) m.Get("/model_download", cloudbrain.AdminOrJobCreaterRight, repo.ModelDownload)
m.Get("/create_version", cloudbrain.AdminOrJobCreaterRight, repo.TrainJobNewVersion)
m.Post("/create_version", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion)
m.Get("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRight, repo.TrainJobNewVersion)
m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion)
}) })
m.Get("/create", reqRepoCloudBrainWriter, repo.TrainJobNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.TrainJobNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate)


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

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


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


+ 17
- 1
routers/user/auth.go View File

@@ -116,8 +116,16 @@ func checkAutoLogin(ctx *context.Context) bool {
} }


if isSucceed { if isSucceed {

isCourse := ctx.QueryBool("course")
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))
if redirectTo == "" && isCourse {
redirectToCourse := setting.AppSubURL + "/" + setting.Course.OrgName
ctx.RedirectToFirst(redirectToCourse)
} else {
ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))

}
return true return true
} }


@@ -143,6 +151,7 @@ func SignIn(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_in") ctx.Data["Title"] = ctx.Tr("sign_in")
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsSignIn"] = true
ctx.Data["IsCourse"] = ctx.QueryBool("course")
ctx.Data["PageIsLogin"] = true ctx.Data["PageIsLogin"] = true
ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() ctx.Data["EnableSSPI"] = models.IsSSPIEnabled()
ctx.Data["EnableCloudBrain"] = true ctx.Data["EnableCloudBrain"] = true
@@ -182,6 +191,7 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsSignIn"] = true
ctx.Data["PageIsLogin"] = true ctx.Data["PageIsLogin"] = true
ctx.Data["IsCourse"] = ctx.QueryBool("course")
ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() ctx.Data["EnableSSPI"] = models.IsSSPIEnabled()


if ctx.HasError() { if ctx.HasError() {
@@ -565,6 +575,12 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
return setting.AppSubURL + "/dashboard" return setting.AppSubURL + "/dashboard"
} }


isCourse := ctx.QueryBool("course")
if isCourse {
redirectToCourse := setting.AppSubURL + "/" + setting.Course.OrgName
ctx.RedirectToFirst(redirectToCourse)
return redirectToCourse
}
if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) { if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
if obeyRedirect { if obeyRedirect {


+ 89
- 3
services/repository/repository.go View File

@@ -5,12 +5,17 @@
package repository package repository


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

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
"fmt"
) )


// CreateRepository creates a repository for the user/organization. // CreateRepository creates a repository for the user/organization.
@@ -31,8 +36,8 @@ func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) (
} }


// ForkRepository forks a repository // ForkRepository forks a repository
func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) {
repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc)
func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc, alias string) (*models.Repository, error) {
repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc, alias)
if err != nil { if err != nil {
if repo != nil { if repo != nil {
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
@@ -86,3 +91,84 @@ func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repo


return repo, nil return repo, nil
} }

func GetRecommendCourseKeyWords() ([]string, error) {

url := setting.RecommentRepoAddr + "course_keywords"
result, err := RecommendFromPromote(url)

if err != nil {
return []string{}, err
}
return result, err

}

func GetRecommendRepoFromPromote(filename string) ([]map[string]interface{}, error) {
resultRepo := make([]map[string]interface{}, 0)
url := setting.RecommentRepoAddr + filename
result, err := RecommendFromPromote(url)

if err != nil {

return resultRepo, err
}

//resultRepo := make([]*models.Repository, 0)
for _, repoName := range result {
tmpIndex := strings.Index(repoName, "/")
if tmpIndex == -1 {
log.Info("error repo name format.")
} else {
ownerName := strings.Trim(repoName[0:tmpIndex], " ")
repoName := strings.Trim(repoName[tmpIndex+1:], " ")
repo, err := models.GetRepositoryByOwnerAndAlias(ownerName, repoName)
if err == nil {
repoMap := make(map[string]interface{})
repoMap["ID"] = fmt.Sprint(repo.ID)
repoMap["Name"] = repo.Name
repoMap["Alias"] = repo.Alias
if repo.RepoType == models.RepoCourse {
//Load creator
repo.GetCreator()
repoMap["Creator"] = repo.Creator
}

repoMap["OwnerName"] = repo.OwnerName
repoMap["NumStars"] = repo.NumStars
repoMap["NumForks"] = repo.NumForks
repoMap["Description"] = repo.Description
repoMap["NumWatchs"] = repo.NumWatches
repoMap["Topics"] = repo.Topics
repoMap["Avatar"] = repo.RelAvatarLink()
resultRepo = append(resultRepo, repoMap)
} else {
log.Info("query repo error," + err.Error())
}
}
}
return resultRepo, nil
}

func RecommendFromPromote(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil || resp.StatusCode != 200 {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}
bytes, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}

allLineStr := string(bytes)
lines := strings.Split(allLineStr, "\n")
result := make([]string, len(lines))
for i, line := range lines {
log.Info("i=" + fmt.Sprint(i) + " line=" + line)
result[i] = strings.Trim(line, " ")
}
return result, nil
}

+ 2
- 2
services/repository/transfer.go View File

@@ -55,7 +55,7 @@ func TransferOwnership(doer, newOwner *models.User, repo *models.Repository, tea


// ChangeRepositoryName changes all corresponding setting from old repository name to new one. // ChangeRepositoryName changes all corresponding setting from old repository name to new one.
func ChangeRepositoryName(doer *models.User, repo *models.Repository, newRepoName string) error { func ChangeRepositoryName(doer *models.User, repo *models.Repository, newRepoName string) error {
oldRepoName := repo.Name
//oldRepoName := repo.Name


// Change repository directory name. We must lock the local copy of the // Change repository directory name. We must lock the local copy of the
// repo so that we can atomically rename the repo path and updates the // repo so that we can atomically rename the repo path and updates the
@@ -68,7 +68,7 @@ func ChangeRepositoryName(doer *models.User, repo *models.Repository, newRepoNam
} }
repoWorkingPool.CheckOut(com.ToStr(repo.ID)) repoWorkingPool.CheckOut(com.ToStr(repo.ID))


notification.NotifyRenameRepository(doer, repo, oldRepoName)
//notification.NotifyRenameRepository(doer, repo, oldRepoName)


return nil return nil
} }

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

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


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

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


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

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


user, err := models.GetUserByID(actions[i].UserID) user, err := models.GetUserByID(actions[i].UserID)
if err == nil { if err == nil {
if !user.IsOrganization() { if !user.IsOrganization() {
filterUserPrivateInfo(actions[i])
LastActionsQueue.Push(actions[i]) LastActionsQueue.Push(actions[i])
} }


@@ -87,3 +104,19 @@ func initActionQueue() {
} }
} }
} }

func filterUserPrivateInfo(action *models.Action) {
action.Comment = nil
action.ActUser.Email = ""
action.ActUser.Passwd = ""
action.ActUser.PasswdHashAlgo = ""
action.ActUser.PrivateKey = ""
action.ActUser.PublicKey = ""
action.ActUser.Salt = ""
action.ActUser.FullName = ""
action.ActUser.AvatarEmail = ""
action.ActUser.IsAdmin = false
action.ActUser.EmailNotificationsPreference = ""
action.ActUser.IsOperator = false

}

+ 316
- 0
templates/admin/cloudbrain/list.tmpl View File

@@ -0,0 +1,316 @@
{{template "base/head" .}}
<!-- 弹窗 -->
<div id="mask">
<div id="loadingPage">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
<!-- 提示框 -->
<div class="alert"></div>
<div class="admin user">
{{template "admin/navbar" .}}
<div class="ui container" style="width: 80%;">
{{template "base/alert" .}}
<div class="ui grid" >
<div class="row" style="border: 1px solid #d4d4d5;margin-top: 15px;padding-top: 0;">
{{template "admin/cloudbrain/search" .}}
<div class="ui ten wide column right aligned" style="margin: 1rem 0;">
<a class="ui compact blue basic icon button" style="box-shadow: none !important; padding: 0.8em;" href="/admin/cloudbrains/download"><i class="ri-download-line middle aligned icon"></i>{{.i18n.Tr "admin.cloudbrain.download_report"}}</a>
</div>
<div class="ui sixteen wide column">
<!-- 任务展示 -->
<div class="dataset list">
<!-- 表头 -->
<div class="ui grid stackable" style="background: #f0f0f0;;">
<div class="row">
<div class="two wide column nowrap">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span>
</div>
<div class="one wide column text center nowrap">
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task_type"}}</span>
</div>
<div class="two wide column text center nowrap" style="width: 10% !important;">
<span>{{$.i18n.Tr "repo.modelarts.status"}}</span>
</div>
<div class="two wide column text center nowrap" style="width: 10% !important;">
<span>{{$.i18n.Tr "repo.modelarts.createtime"}}</span>
</div>
<div class="one wide column text center nowrap">
<span>{{$.i18n.Tr "repo.cloudbrain_status_runtime"}}</span>
</div>
<div class="one wide column text center nowrap">
<span>{{$.i18n.Tr "repo.modelarts.computing_resources"}}</span>
</div>
<div class="one wide column text center nowrap">
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span>
</div>
<div class="two wide column text center nowrap">
<span>{{$.i18n.Tr "repository"}}</span>
</div>
<div class="two wide column text center nowrap">
<span>{{.i18n.Tr "admin.cloudbrain.cloudbrain_name"}}</span>
</div>
<div class="two wide column text center nowrap" style="width: 17.5%!important;">
<span>{{$.i18n.Tr "repo.cloudbrain_operate"}}</span>
</div>
</div>
</div>
{{range .Tasks}}
{{if .Repo}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="two wide column nowrap">
{{if or (eq .JobType "DEBUG") (eq .JobType "SNN4IMAGENET") (eq .JobType "BRAINSCORE")}}
<a class="title" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain/{{.JobName}}{{else}}/modelarts/notebook/{{.JobID}}{{end}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "INFERENCE"}}
<a class="title" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}/modelarts/inference-job/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "TRAIN"}}
<a class="title" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}/modelarts/train-job/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "BENCHMARK"}}
<a class="title" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}/cloudbrain/benchmark/{{.JobName}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{end}}
</div>
<!-- 任务类型 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;">{{.JobType}} </span>
</div>
<!-- 任务状态 -->
<div class="two wide column text center nowrap" style="padding-left: 2.2rem !important; width: 10% !important;">
<span class="job-status" id="{{.JobID}}" data-repopath='{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .JobType "DEBUG"}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}{{else if eq .JobType "INFERENCE"}}/modelarts/inference-job{{else if eq .JobType "TRAIN"}}/modelarts/train-job{{else if eq .JobType "BENCHMARK"}}/cloudbrain{{end}}' 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 nowrap" style="width: 10% !important;">
<span style="font-size: 12px;" class="">{{TimeSinceUnix1 .Cloudbrain.CreatedUnix}}</span>
</div>
<!-- 任务运行时间 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;" id="duration-{{.JobID}}">{{if .TrainJobDuration}}{{.TrainJobDuration}}{{else}}--{{end}}</span>
</div>
<!-- 计算资源 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;">{{if .ComputeResource}}{{.ComputeResource}}{{else}}--{{end}}</span>
</div>
<!-- 创建者 -->
<div class="one wide column text center nowrap">
{{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="two wide column text center nowrap">
<a href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}" title="{{.Repo.OwnerName}}/{{.Repo.Alias}}">{{.Repo.OwnerName}}/{{.Repo.Alias}}</a>
</div>
<!-- 云脑侧名称 -->
<div class="two wide column text center nowrap" style="overflow: hidden;text-overflow:ellipsis;">
<span class="fitted">{{.JobName}}</span>
</div>
<div class="two wide column text center nowrap" style="width: 17.5%!important;">
{{if eq .JobType "DEBUG" "SNN4IMAGENET" "BRAINSCORE"}}
<div class="ui compact buttons">
<form id="debugAgainForm-{{.JobID}}">
{{$.CsrfTokenHtml}}
{{if eq .Status "RUNNING" "WAITING" "CREATING" "STARTING"}}
<a style="margin: 0 1rem;" id="ai-debug-{{.JobID}}" class='ui basic ai_debug {{if eq .Status "CREATING" "STOPPING" "WAITING" "STARTING"}}disabled {{else}}blue {{end}}button' data-jobid="{{.JobID}}" data-repopath='{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/'>
{{$.i18n.Tr "repo.debug"}}
</a>
{{else}}
<a id="ai-debug-{{.JobID}}" class='ui basic ai_debug {{if eq .Status "CREATING" "STOPPING" "WAITING" "STARTING"}} disabled {{else}}blue {{end}}button' data-jobid="{{.JobID}}" data-repopath='{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/'>
{{$.i18n.Tr "repo.debug_again"}}
</a>
{{end}}
</form>
</div>
{{end}}
<!-- 停止任务 -->
<div class="ui compact buttons">
{{if eq .JobType "DEBUG" "BENCHMARK" "SNN4IMAGENET" "BRAINSCORE"}}
<form id="stopForm-{{.JobID}}" style="margin-left:-1px;">
{{$.CsrfTokenHtml}}
<a style="padding: 0.5rem 1rem;" id="ai-stop-{{.JobID}}" class='ui basic ai_stop {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED" "SUCCEEDED" "STOPPED" "STOPPING"}}disabled {{else}} blue {{end}}button' data-repopath='{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else if eq .JobType "BENCHMARK" }}/cloudbrain/benchmark{{else if eq .ComputeResource "NPU" }}/modelarts/notebook{{end}}/{{.JobID}}/stop' data-jobid="{{.JobID}}">
{{$.i18n.Tr "repo.stop"}}
</a>
</form>
{{else}}
<a style="padding: 0.5rem 1rem;" id="ai-stop-{{.JobID}}" class="ui basic ai_stop_version {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" data-repopath="{{.Repo.OwnerName}}/{{.Repo.Name}}/modelarts/{{if eq .JobType "INFERENCE"}}inference-job{{else}}train-job{{end}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}" >
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}
</div>
<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action='{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .JobType "BENCHMARK"}}/cloudbrain/benchmark{{else if eq .JobType "DEBUG"}}{{if eq .ComputeResource "NPU"}}/modelarts/notebook{{else}}/cloudbrain{{end}}{{else if eq .JobType "TRAIN"}}/modelarts/train-job{{end}}/{{.JobID}}/del?isadminpage=true' method="post">
{{$.CsrfTokenHtml}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="ai-delete-{{.JobID}}" data-repopath="{{.Repo.OwnerName}}/{{.Repo.Name}}/modelarts/inference-job/{{.JobID}}/del_version?isadminpage=true" data-version="{{.VersionName}}" class="ui basic ai_delete blue button" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
</form>
</div>
</div>
</div>
{{else}}
<div class="ui grid stackable item">
<div class="row">
<!-- 任务名 -->
<div class="two wide column nowrap">
{{if eq .JobType "DEBUG"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "INFERENCE"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "TRAIN"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{else if eq .JobType "BENCHMARK"}}
<a class="title" href="" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
{{end}}
</div>
<!-- 任务类型 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;">{{.JobType}} </span>
</div>
<!-- 任务状态 -->
<div class="two wide column text center nowrap" style="padding-left: 2.2rem !important; width: 10% !important;">
<span class="job-status" id="{{.JobID}}" 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 nowrap" style="width: 10% !important;">
<span style="font-size: 12px;" class="">{{TimeSinceUnix1 .Cloudbrain.CreatedUnix}}</span>
</div>
<!-- 任务运行时间 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;" id="duration-{{.JobID}}">{{if .TrainJobDuration}}{{.TrainJobDuration}}{{else}}--{{end}}</span>
</div>
<!-- 计算资源 -->
<div class="one wide column text center nowrap">
<span style="font-size: 12px;">{{if .ComputeResource}}{{.ComputeResource}}{{else}}--{{end}}</span>
</div>
<!-- 创建者 -->
<div class="one wide column text center nowrap">
{{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="two wide column text center nowrap">
<a href="" title="">--</a>
</div>
<!-- 云脑侧名称 -->
<div class="two wide column text center nowrap" style="overflow: hidden;text-overflow:ellipsis;">
<span class="fitted">{{.JobName}}</span>
</div>
<div class="two wide column text center nowrap" style="width: 17.5%!important;">
{{if eq .JobType "DEBUG"}}
<div class="ui compact buttons">
<form id="debugAgainForm-{{.JobID}}">
{{$.CsrfTokenHtml}}
{{if eq .Status "RUNNING" "WAITING" "CREATING" "STARTING"}}
<a style="margin: 0 1rem;" id="ai-debug-{{.JobID}}" class='ui basic disabled button' >
{{$.i18n.Tr "repo.debug"}}
</a>
{{else}}
<a id="ai-debug-{{.JobID}}" class='ui basic disabled button' >
{{$.i18n.Tr "repo.debug_again"}}
</a>
{{end}}
</form>
</div>
{{end}}
<!-- 停止任务 -->
<div class="ui compact buttons">
<a style="padding: 0.5rem 1rem;" id="ai-stop-{{.JobID}}" class="ui basic disabled button" data-jobid="{{.JobID}}" data-version="{{.VersionName}}" >
{{$.i18n.Tr "repo.stop"}}
</a>
</div>
<!-- 删除任务 -->
<form class="ui compact buttons" id="delForm-{{.JobID}}" action='' method="post">
{{$.CsrfTokenHtml}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="ai-delete-{{.JobID}}" class="ui basic disabled button" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
</form>
</div>
</div>
</div>

{{end}}
{{end}}
<div id="app" style="margin-top: 2rem;">
<div class="center">
<el-pagination
background
@current-change="handleCurrentChange"
:current-page="page"
:page-sizes="[10]"
:page-size="10"
layout="total, sizes, prev, pager, next, jumper"
:total="{{.Page.Paginater.Total}}">
</el-pagination>
</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 getParams(){
const params = new URLSearchParams(window.location.search)
let jobType = !params.get('jobType')? '{{.i18n.Tr "admin.cloudbrain.all_task_types"}}' : params.get('jobType')
let listType = !params.get('listType')? '{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}' : params.get('listType')
let jobStatus = !params.get('jobStatus')? '{{.i18n.Tr "admin.cloudbrain.all_status"}}' : params.get('jobStatus').toUpperCase()
const dropdownValueArray = [jobType,listType,jobStatus]
$('#adminCloud .default.text ').each(function(index,e){
$(e).text(dropdownValueArray[index])
})
}
getParams()
</script>

+ 51
- 0
templates/admin/cloudbrain/search.tmpl View File

@@ -0,0 +1,51 @@
<div class="ui attached segment">
<form class="ui form ignore-dirty" style="max-width: 90%">
<div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "admin.cloudbrain.search"}}..." autofocus>
<button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
</div>
</form>
</div>
<div class="ui six wide column" style="margin: 1rem 0;" id="adminCloud">
<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="default text" style="color: rgba(0,0,0,.87);">{{.i18n.Tr "admin.cloudbrain.all_task_types"}}</div>
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value='{{.i18n.Tr "admin.cloudbrain.all_task_types"}}'>{{.i18n.Tr "admin.cloudbrain.all_task_types"}}</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=DEBUG&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="DEBUG">DEBUG</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=TRAIN&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="TRAIN">TRAIN</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=INFERENCE&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="INFERENCE">INFERENCE</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=BENCHMARK&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="BENCHMARK">BENCHMARK</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=SNN4IMAGENET&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="BENCHMARK">SNN4IMAGENET</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType=BRAINSCORE&listType={{$.ListType}}&jobStatus={{$.JobStatus}}" data-value="BENCHMARK">BRAINSCORE</a>
</div>
</div>
<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="default text" style="color: rgba(0,0,0,.87);">{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}</div>
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType=&jobStatus={{$.JobStatus}}" data-value='{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}'>{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType=CPU/GPU&jobStatus={{$.JobStatus}}" data-value="CPU/GPU">CPU/GPU</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType=NPU&jobStatus={{$.JobStatus}}" data-value="NPU">NPU</a>
</div>
</div>
<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="default text" style="color: rgba(0,0,0,.87);">{{.i18n.Tr "admin.cloudbrain.all_status"}}</div>
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=" data-value='{{.i18n.Tr "admin.cloudbrain.all_status"}}'>{{.i18n.Tr "admin.cloudbrain.all_status"}}</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STARTING" data-value="STARTING">STARTING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=RUNNING" data-value="RUNNING">RUNNING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=RESTARTING" data-value="RESTARTING">RESTARTING </a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=START_FAILED" data-value="START_FAILED">START_FAILED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STOPPING" data-value="STOPPING">STOPPING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=STOPPED" data-value="STOPPED">STOPPED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=WAITING" data-value="WAITING">WAITING</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=COMPLETED" data-value="COMPLETED">COMPLETED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=SUCCEEDED" data-value="SUCCEEDED">SUCCEEDED</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=FAILED" data-value="FAILED">FAILED </a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&jobType={{$.JobType}}&listType={{$.ListType}}&jobStatus=other" data-value="OTHER">OTHER</a>
</div>
</div>
</div>

+ 3
- 0
templates/admin/navbar.tmpl View File

@@ -14,6 +14,9 @@
<a class="{{if .PageIsAdminDatasets}}active{{end}} item" href="{{AppSubUrl}}/admin/datasets"> <a class="{{if .PageIsAdminDatasets}}active{{end}} item" href="{{AppSubUrl}}/admin/datasets">
{{.i18n.Tr "admin.datasets"}} {{.i18n.Tr "admin.datasets"}}
</a> </a>
<a class="{{if .PageIsAdminCloudBrains}}active{{end}} item" href="{{AppSubUrl}}/admin/cloudbrains">
云脑任务
</a>
<a class="{{if .PageIsAdminHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/hooks"> <a class="{{if .PageIsAdminHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/hooks">
{{.i18n.Tr "admin.hooks"}} {{.i18n.Tr "admin.hooks"}}
</a> </a>


+ 1
- 1
templates/admin/repo/list.tmpl View File

@@ -36,7 +36,7 @@
<span class="text gold">{{svg "octicon-lock" 16}}</span> <span class="text gold">{{svg "octicon-lock" 16}}</span>
{{end}} {{end}}
</td> </td>
<td><a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Name}}</a></td>
<td><a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Alias}}</a></td>
<td><i class="fa fa{{if .IsPrivate}}-check{{end}}-square-o"></i></td> <td><i class="fa fa{{if .IsPrivate}}-check{{end}}-square-o"></i></td>
<td>{{.NumWatches}}</td> <td>{{.NumWatches}}</td>
<td>{{.NumStars}}</td> <td>{{.NumStars}}</td>


+ 2
- 0
templates/base/footer_content.tmpl View File

@@ -8,6 +8,8 @@
<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.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/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> <a href="https://openi.org.cn/html/Club/2019/0228/17.html" class="item">{{.i18n.Tr "custom.foot.join"}}</a>
<a href="{{AppSubUrl}}/home/term/" class="item">{{.i18n.Tr "custom.foot.agreement"}}</a>
</div> </div>
<div class="column ui vertical text menu"> <div class="column ui vertical text menu">
<div class="header item">{{.i18n.Tr "custom.foot.news"}}</div> <div class="header item">{{.i18n.Tr "custom.foot.news"}}</div>


+ 20
- 12
templates/base/head.tmpl View File

@@ -200,14 +200,7 @@ var _hmt = _hmt || [];
<div class="ui top secondary stackable main menu following bar dark"> <div class="ui top secondary stackable main menu following bar dark">
{{template "base/head_navbar" .}} {{template "base/head_navbar" .}}
</div><!-- end bar --> </div><!-- end bar -->
<div class="notic_content" id ="notic_content" >
<a href={{.notice.Link}} class="a_width">
<marquee behavior="scroll" direction="left">
{{.notice.Title}}
</marquee>
</a>
<i class="ri-close-fill x_icon" onclick="closeNoice()"></i>
</div>
{{template "base/head_notice" .}}
{{end}} {{end}}
{{/* {{/*
</div> </div>
@@ -231,7 +224,17 @@ var _hmt = _hmt || [];
}else{ }else{
isNewNotice=false; isNewNotice=false;
} }
if (JSON.parse("{{.notice.Visible}}")){
let isShowNoticeTag = false;
let notices= {{.notices.Notices}}
if(notices != null && notices!=''){
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
}
}
}
if (isShowNoticeTag){
if(isNewNotice){ if(isNewNotice){
document.getElementById("notic_content").style.display='block' document.getElementById("notic_content").style.display='block'
}else{ }else{
@@ -244,8 +247,13 @@ var _hmt = _hmt || [];


} }
}else{ }else{
document.getElementById("notic_content").style.display='none'
if (document.getElementById("notic_content") != null){
document.getElementById("notic_content").style.display='none'
}
} }
} }
isShowNotice();
</script>
if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) {
isShowNotice();
}
</script>

+ 209
- 0
templates/base/head_course.tmpl View File

@@ -0,0 +1,209 @@
<!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}}
<link rel="stylesheet" href="/RemixIcon_Fonts_v2.5.0/fonts/remixicon.css">
{{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" .}}
</div><!-- end bar -->
{{end}}
{{/*
</div>
</body>
</html>
*/}}


+ 19
- 11
templates/base/head_fluid.tmpl View File

@@ -201,14 +201,7 @@ var _hmt = _hmt || [];
<div class="ui top secondary stackable main menu following bar dark"> <div class="ui top secondary stackable main menu following bar dark">
{{template "base/head_navbar_fluid" .}} {{template "base/head_navbar_fluid" .}}
</div><!-- end bar --> </div><!-- end bar -->
<div class="notic_content" id ="notic_content" >
<a href={{.notice.Link}} class="a_width">
<marquee behavior="scroll" direction="left">
{{.notice.Title}}
</marquee>
</a>
<i class="ri-close-fill x_icon" onclick="closeNoice()"></i>
</div>
{{template "base/head_notice" .}}
{{end}} {{end}}
{{/* {{/*
</div> </div>
@@ -232,7 +225,17 @@ var _hmt = _hmt || [];
}else{ }else{
isNewNotice=false; isNewNotice=false;
} }
if (JSON.parse("{{.notice.Visible}}")){
let isShowNoticeTag = false;
let notices= {{.notices.Notices}}
if(notices != null && notices!=''){
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
}
}
}
if (isShowNoticeTag){
if(isNewNotice){ if(isNewNotice){
document.getElementById("notic_content").style.display='block' document.getElementById("notic_content").style.display='block'
}else{ }else{
@@ -245,8 +248,13 @@ var _hmt = _hmt || [];


} }
}else{ }else{
document.getElementById("notic_content").style.display='none'
if (document.getElementById("notic_content") != null){
document.getElementById("notic_content").style.display='none'
}
} }
} }
isShowNotice();
if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) {
isShowNotice();
}
</script> </script>

+ 19
- 11
templates/base/head_home.tmpl View File

@@ -205,14 +205,7 @@ var _hmt = _hmt || [];
<div class="ui top secondary stackable main menu following bar dark"> <div class="ui top secondary stackable main menu following bar dark">
{{template "base/head_navbar" .}} {{template "base/head_navbar" .}}
</div><!-- end bar --> </div><!-- end bar -->
<div class="notic_content" id ="notic_content" >
<a href={{.notice.Link}} class="a_width">
<marquee behavior="scroll" direction="left">
{{.notice.Title}}
</marquee>
</a>
<i class="ri-close-fill x_icon" onclick="closeNoice()"></i>
</div>
{{template "base/head_notice" .}}
{{end}} {{end}}
{{/* {{/*
</div> </div>
@@ -236,7 +229,17 @@ var _hmt = _hmt || [];
}else{ }else{
isNewNotice=false; isNewNotice=false;
} }
if (JSON.parse("{{.notice.Visible}}")){
let isShowNoticeTag = false;
let notices= {{.notices.Notices}}
if(notices != null && notices!=''){
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
}
}
}
if (isShowNoticeTag){
if(isNewNotice){ if(isNewNotice){
document.getElementById("notic_content").style.display='block' document.getElementById("notic_content").style.display='block'
}else{ }else{
@@ -249,8 +252,13 @@ var _hmt = _hmt || [];


} }
}else{ }else{
document.getElementById("notic_content").style.display='none'
if (document.getElementById("notic_content") != null){
document.getElementById("notic_content").style.display='none'
}
} }
} }
isShowNotice();
if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) {
isShowNotice();
}
</script> </script>

+ 21
- 9
templates/base/head_navbar.tmpl View File

@@ -25,7 +25,7 @@
<div class="dropdown-content" style="min-width: 110px;border-radius:4px"> <div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
</div> </div>
</div> </div>
@@ -43,7 +43,7 @@
{{if .IsOperator}} {{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}} {{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a>
</div> </div>
</div> </div>
{{else if .IsLandingPageHome}} {{else if .IsLandingPageHome}}
@@ -55,7 +55,7 @@
<div class="dropdown-content" style="min-width: 110px;border-radius:4px"> <div class="dropdown-content" style="min-width: 110px;border-radius:4px">
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
</div> </div>
</div> </div>
@@ -73,7 +73,7 @@
{{if .IsOperator}} {{if .IsOperator}}
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a>
{{end}} {{end}}
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi"}}</a>
<a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a>
</div> </div>
</div> </div>
{{else if .IsLandingPageExplore}} {{else if .IsLandingPageExplore}}
@@ -211,13 +211,25 @@
</div> </div>
</form> </form>
{{if .ShowRegistrationButton}} {{if .ShowRegistrationButton}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}
{{if .IsCourse}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="https://git.openi.org.cn/user/sign_up" target="_blank">
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}
</a>
{{else}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}}
</a>
{{end}}
{{end}}
{{if .IsCourse}}
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="https://git.openi.org.cn/user/login?course=true" target="_blank">
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}}
</a>
{{else}}
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login">
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}}
</a> </a>
{{end}} {{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 --> </div><!-- end anonymous right menu -->


{{end}} {{end}}


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

Loading…
Cancel
Save