Browse Source

Merge pull request 'V20220228合入develop' (#1598) from V20220228 into develop

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/1598
tags/v1.22.2.2
lewis 3 years ago
parent
commit
42f14ca461
100 changed files with 2707 additions and 936 deletions
  1. +141
    -17
      custom/public/css/git.openi.css
  2. +8
    -0
      models/action.go
  3. +14
    -1
      models/attachment.go
  4. +62
    -12
      models/cloudbrain.go
  5. +1
    -0
      models/models.go
  6. +8
    -0
      models/repo_statistic.go
  7. +9
    -0
      models/user.go
  8. +98
    -0
      models/wechat_bind.go
  9. +2
    -1
      modules/auth/modelarts.go
  10. +67
    -0
      modules/auth/wechat/access_token.go
  11. +71
    -0
      modules/auth/wechat/bind.go
  12. +5
    -0
      modules/auth/wechat/call.go
  13. +126
    -0
      modules/auth/wechat/client.go
  14. +76
    -0
      modules/auth/wechat/event_handle.go
  15. +13
    -0
      modules/auth/wechat/qr_code.go
  16. +10
    -3
      modules/cloudbrain/cloudbrain.go
  17. +61
    -9
      modules/context/auth.go
  18. +15
    -0
      modules/context/context.go
  19. +54
    -18
      modules/modelarts/modelarts.go
  20. +4
    -1
      modules/modelarts/resty.go
  21. +15
    -0
      modules/notification/action/action.go
  22. +2
    -0
      modules/notification/base/notifier.go
  23. +4
    -0
      modules/notification/base/null.go
  24. +7
    -0
      modules/notification/notification.go
  25. +87
    -0
      modules/redis/redis_client/client.go
  26. +16
    -0
      modules/redis/redis_key/key_base.go
  27. +14
    -0
      modules/redis/redis_key/wechat_redis_key.go
  28. +40
    -0
      modules/redis/redis_lock/lock.go
  29. +21
    -2
      modules/setting/setting.go
  30. +4
    -3
      modules/structs/attachment.go
  31. +33
    -0
      options/locale/locale_en-US.ini
  32. +32
    -1
      options/locale/locale_zh-CN.ini
  33. +140
    -0
      package-lock.json
  34. +4
    -0
      package.json
  35. +87
    -12
      public/home/home.js
  36. BIN
      public/img/qrcode_reload.png
  37. +225
    -0
      routers/admin/cloudbrains.go
  38. +9
    -1
      routers/api/v1/api.go
  39. +15
    -12
      routers/api/v1/repo/cloudbrain.go
  40. +1
    -0
      routers/api/v1/repo/modelarts.go
  41. +11
    -11
      routers/api/v1/repo/repo_dashbord.go
  42. +126
    -0
      routers/authentication/wechat.go
  43. +47
    -0
      routers/authentication/wechat_event.go
  44. +5
    -0
      routers/home.go
  45. +2
    -1
      routers/repo/ai_model_manage.go
  46. +4
    -2
      routers/repo/attachment.go
  47. +28
    -9
      routers/repo/cloudbrain.go
  48. +2
    -1
      routers/repo/download.go
  49. +55
    -29
      routers/repo/modelarts.go
  50. +2
    -1
      routers/repo/repo_statistic.go
  51. +4
    -2
      routers/repo/user_data_analysis.go
  52. +34
    -15
      routers/routes/routes.go
  53. +1
    -1
      routers/user/notification.go
  54. +1
    -1
      services/socketwrap/clientManager.go
  55. +316
    -0
      templates/admin/cloudbrain/list.tmpl
  56. +51
    -0
      templates/admin/cloudbrain/search.tmpl
  57. +3
    -0
      templates/admin/navbar.tmpl
  58. +2
    -0
      templates/base/footer_content.tmpl
  59. +9
    -5
      templates/base/head.tmpl
  60. +9
    -5
      templates/base/head_fluid.tmpl
  61. +9
    -5
      templates/base/head_home.tmpl
  62. +9
    -5
      templates/base/head_pro.tmpl
  63. +8
    -4
      templates/home.tmpl
  64. +14
    -2
      templates/org/home.tmpl
  65. +14
    -2
      templates/org/home_courses.tmpl
  66. +0
    -1
      templates/org/member/course_members.tmpl
  67. +13
    -12
      templates/org/select_pro.tmpl
  68. +13
    -1
      templates/org/team/courseTeams.tmpl
  69. +13
    -1
      templates/org/team/teams.tmpl
  70. +7
    -178
      templates/repo/cloudbrain/benchmark/index.tmpl
  71. +17
    -5
      templates/repo/cloudbrain/benchmark/new.tmpl
  72. +4
    -4
      templates/repo/cloudbrain/benchmark/show.tmpl
  73. +1
    -1
      templates/repo/cloudbrain/new.tmpl
  74. +1
    -2
      templates/repo/cloudbrain/show.tmpl
  75. +2
    -2
      templates/repo/courseHome.tmpl
  76. +0
    -2
      templates/repo/createCourse.tmpl
  77. +2
    -2
      templates/repo/datasets/dataset_list.tmpl
  78. +2
    -3
      templates/repo/datasets/index.tmpl
  79. +17
    -177
      templates/repo/debugjob/index.tmpl
  80. +1
    -1
      templates/repo/header.tmpl
  81. +3
    -3
      templates/repo/home.tmpl
  82. +0
    -34
      templates/repo/issue/new_form.tmpl
  83. +3
    -3
      templates/repo/issue/view_content/pull.tmpl
  84. +5
    -117
      templates/repo/modelarts/inferencejob/index.tmpl
  85. +2
    -2
      templates/repo/modelarts/inferencejob/new.tmpl
  86. +7
    -2
      templates/repo/modelarts/inferencejob/show.tmpl
  87. +14
    -8
      templates/repo/modelarts/notebook/new.tmpl
  88. +14
    -2
      templates/repo/modelarts/notebook/show.tmpl
  89. +5
    -162
      templates/repo/modelarts/trainjob/index.tmpl
  90. +9
    -5
      templates/repo/modelarts/trainjob/show.tmpl
  91. +0
    -1
      templates/repo/modelmanage/showinfo.tmpl
  92. +1
    -3
      templates/repo/repo_name.tmpl
  93. +15
    -0
      templates/repo/wx_autorize.tmpl
  94. +70
    -0
      templates/terms.tmpl
  95. +32
    -2
      templates/user/dashboard/feeds.tmpl
  96. +0
    -3
      templates/user/dashboard/issues.tmpl
  97. +0
    -3
      templates/user/dashboard/milestones.tmpl
  98. +1
    -0
      templates/user/profile.tmpl
  99. +66
    -0
      templates/user/settings/profile.tmpl
  100. +0
    -0
      tsconfig.json

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

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

.opacity5{ opacity:0.5;}
.radius15{ border-radius:1.5rem !important; }
@@ -179,7 +185,7 @@
.homenews .ui.list>.item>.content{
color: #E8E8E8;
line-height: 1.8em;
width: calc(100% - 3.25em) !important;
width: calc(100% - 3.75em) !important;
}
.homenews .ui.list>.item{
padding: 0;
@@ -250,9 +256,13 @@
box-shadow: none !important;
}
.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;
z-index: 9;
padding: 1.0em 1.0em 3.0em;
@@ -261,42 +271,156 @@
.homepro-list .ui.card{
border-radius: 15px;
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;
}
.homepro-list .ui.card>.content>.header{
line-height: 40px !important;
}

.homepro-list .swiper-pagination-bullet-active{
.homepro-list .swiper-pagination-bullet-active, .homeorg-list .swiper-pagination-bullet-active{
width: 40px;
border-radius: 4px;
border-radius: 4px;
}
.i-env > div{
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) {
.am-mt-30{ margin-top: 1.5rem !important;}
.ui.secondary.hometop.segment{
margin-bottom: 2.0rem;
margin-bottom: 5.0rem;
}
.bannerpic, .i-code-pic{
.bannerpic{
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{
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;
}
}



+ 8
- 0
models/action.go View File

@@ -49,6 +49,14 @@ const (
ActionApprovePullRequest // 21
ActionRejectPullRequest // 22
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


+ 14
- 1
models/attachment.go View File

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

// DownloadURL returns the download url of the attached file
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


+ 62
- 12
models/cloudbrain.go View File

@@ -102,7 +102,7 @@ type Cloudbrain struct {
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
Duration int64
TrainJobDuration string
Image string //GPU镜像名称
Image string //镜像名称
GpuQueue string //GPU类型即GPU队列
ResourceSpecId int //GPU规格id
DeletedAt time.Time `xorm:"deleted"`
@@ -219,17 +219,20 @@ type GetImagesPayload struct {

type CloudbrainsOptions struct {
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
JobTypes []string
VersionName string
IsLatestVersion string
JobTypeNot bool
NeedRepoInfo bool
}

type TaskPod struct {
@@ -449,6 +452,16 @@ type FlavorInfo struct {
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 {
PoolInfo []*PoolInfo `json:"pool_info"`
}
@@ -1072,16 +1085,39 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {
}

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



+ 1
- 0
models/models.go View File

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

tablesStatistic = append(tablesStatistic,


+ 8
- 0
models/repo_statistic.go View File

@@ -12,6 +12,7 @@ type RepoStatistic struct {
ID int64 `xorm:"pk autoincr" json:"-"`
RepoID int64 `xorm:"unique(s) NOT NULL" json:"repo_id"`
Name string `xorm:"INDEX" json:"name"`
Alias string `xorm:"INDEX" json:"alias"`
OwnerName string `json:"ownerName"`
IsPrivate bool `json:"isPrivate"`
IsMirror bool `json:"isMirror"`
@@ -63,6 +64,13 @@ type RepoStatistic struct {
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 {
sess := xStatistic.NewSession()
defer sess.Close()


+ 9
- 0
models/user.go View File

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

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

// SearchOrganizationsOptions options to filter organizations
@@ -185,6 +189,11 @@ type SearchOrganizationsOptions struct {
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
func (u *User) ColorFormat(s fmt.State) {
log.ColorFprintf(s, "%d:%s",


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

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

@@ -19,7 +19,8 @@ type CreateModelArtsNotebookForm struct {
JobName string `form:"job_name" binding:"Required"`
Attachment string `form:"attachment"`
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 {


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

+ 10
- 3
modules/cloudbrain/cloudbrain.go View File

@@ -1,16 +1,17 @@
package cloudbrain

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

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

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

const (
@@ -221,13 +222,19 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath,
ComputeResource: models.GPUResource,
BenchmarkTypeID: benchmarkTypeID,
BenchmarkChildTypeID: benchmarkChildTypeID,
Description: description,
Description: description,
})

if err != nil {
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
}



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

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

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

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

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

// ToggleOptions contains required or check options
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
@@ -94,7 +98,14 @@ func Toggle(options *ToggleOptions) macaron.Handler {
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")
return
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
@@ -126,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.
if !options.SignOutRequired && !ctx.IsSigned && !auth.IsAPIPath(ctx.Req.URL.Path) &&
len(ctx.GetCookie(setting.CookieUserName)) > 0 {
@@ -159,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 {
var siteAuth = base64.StdEncoding.EncodeToString([]byte(setting.CBAuthUser + ":" + setting.CBAuthPassword))
auth := ctx.Req.Header.Get("Authorization")


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

@@ -354,3 +354,18 @@ func Contexter() macaron.Handler {
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",
})
}

+ 54
- 18
modules/modelarts/modelarts.go View File

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

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

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

DataSetMountPath = "/home/ma-user/work"
@@ -63,6 +65,7 @@ const (
var (
poolInfos *models.PoolInfos
FlavorInfos *models.FlavorInfos
ImageInfos *models.ImageInfosModelArts
)

type GenerateTrainJobReq struct {
@@ -259,35 +262,42 @@ func GenerateTask(ctx *context.Context, jobName, uuid, description, flavor strin
if err != nil {
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 string) error {
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: "59a6e9f5-93c0-44dd-85b0-82f390c5d53a",
ImageID: imageId,
PoolID: poolInfos.PoolInfo[0].PoolId,
Feature: models.NotebookFeature,
Volume: models.VolumeReq{
Capacity: 100,
Category: models.EVSCategory,
Ownership: models.ManagedOwnership,
Volume: models.VolumeReq{
Capacity: setting.Capacity,
Category: models.EVSCategory,
Ownership: models.ManagedOwnership,
},
WorkspaceID: "0",
WorkspaceID: "0",
})
if err != nil {
log.Error("createNotebook2 failed: %v", err.Error())
return err
}
err = models.CreateCloudbrain(&models.Cloudbrain{
Status: string(models.JobWaiting),
Status: jobResult.Status,
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
JobID: jobResult.ID,
@@ -296,12 +306,14 @@ func GenerateNotebook2(ctx *context.Context, jobName, uuid, description, flavor
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
}

@@ -335,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())
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: strconv.FormatInt(jobResult.JobID, 10),
JobID: jobId,
JobName: req.JobName,
JobType: string(models.JobTypeTrain),
Type: models.TypeCloudBrainTwo,
@@ -371,7 +383,7 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error
log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error())
return err
}
notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, jobId, req.JobName, models.ActionCreateTrainTask)
return nil
}

@@ -555,12 +567,12 @@ func GenerateInferenceJob(ctx *context.Context, req *GenerateInferenceJobReq) (e
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: strconv.FormatInt(jobResult.JobID, 10),
JobID: jobID,
JobName: req.JobName,
JobType: string(models.JobTypeInference),
Type: models.TypeCloudBrainTwo,
@@ -583,6 +595,7 @@ func GenerateInferenceJob(ctx *context.Context, req *GenerateInferenceJobReq) (e
EngineName: req.EngineName,
LabelName: req.LabelName,
IsLatestVersion: req.IsLatestVersion,
ComputeResource: models.NPUResource,
VersionCount: req.VersionCount,
TotalVersionCount: req.TotalVersionCount,
ModelName: req.ModelName,
@@ -595,6 +608,29 @@ func GenerateInferenceJob(ctx *context.Context, req *GenerateInferenceJobReq) (e
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
}

+ 4
- 1
modules/modelarts/resty.go View File

@@ -30,9 +30,12 @@ const (
errorCodeExceedLimit = "ModelArts.0118"

//notebook 2.0
urlNotebook2 = "/notebooks"
urlNotebook2 = "/notebooks"

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

func getRestyClient() *resty.Client {


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

@@ -330,3 +330,18 @@ func (a *actionNotifier) NotifySyncDeleteRef(doer *models.User, repo *models.Rep
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)
}
}

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

@@ -54,4 +54,6 @@ type Notifier interface {
NotifySyncPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits)
NotifySyncCreateRef(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)
}

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

@@ -154,3 +154,7 @@ func (*NullNotifier) NotifySyncCreateRef(doer *models.User, repo *models.Reposit
// NotifySyncDeleteRef places a place holder function
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())
}

// 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
func NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
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
}

+ 21
- 2
modules/setting/setting.go View File

@@ -470,7 +470,7 @@ var (
BenchmarkTypes string
BenchmarkGpuTypes string
BenchmarkResourceSpecs string
BenchmarkMaxDuration int64
BenchmarkMaxDuration int64

//snn4imagenet config
IsSnn4imagenetEnabled bool
@@ -513,6 +513,8 @@ var (
PoolInfos string
Flavor string
DebugHost string
ImageInfos string
Capacity int
//train-job
ResourcePools string
Engines string
@@ -529,6 +531,14 @@ var (
ElkTimeFormat string
PROJECT_LIMIT_PAGES []string

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

//nginx proxy
PROXYURL string
RadarMap = struct {
@@ -1326,7 +1336,8 @@ func NewContext() {
ProfileID = sec.Key("PROFILE_ID").MustString("")
PoolInfos = sec.Key("POOL_INFOS").MustString("")
Flavor = sec.Key("FLAVOR").MustString("")
DebugHost = sec.Key("DEBUG_SERVER_HOST").MustString("http://192.168.202.73")
ImageInfos = sec.Key("IMAGE_INFOS").MustString("")
Capacity = sec.Key("IMAGE_INFOS").MustInt(100)
ResourcePools = sec.Key("Resource_Pools").MustString("")
Engines = sec.Key("Engines").MustString("")
EngineVersions = sec.Key("Engine_Versions").MustString("")
@@ -1342,6 +1353,14 @@ func NewContext() {
ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time")
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()

sec = Cfg.Section("warn_mail")


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

@@ -16,9 +16,10 @@ type Attachment struct {
Size int64 `json:"size"`
DownloadCount int64 `json:"download_count"`
// 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


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

@@ -493,6 +493,14 @@ account_link = Linked Accounts
organization = Organizations
uid = Uid
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
profile_desc = Your email address will be used for notifications and other operations.
@@ -864,9 +872,13 @@ confirm_choice = confirm
cloudbran1_tips = Only data in zip format can create cloudbrain tasks
cloudbrain_creator=Creator
cloudbrain_task = Task Name
cloudbrain_task_type = Task Type
cloudbrain_task_name=Cloud Brain Task Name
cloudbrain_operate = Operate
cloudbrain_status_createtime = Status/Createtime
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.
parameter_is_wrong=The input parameter is wrong.
@@ -2059,6 +2071,9 @@ people = People
teams = Teams
lower_members = members
lower_repositories = repositories
lower_member=member
lower_repository = repository

create_new_team = New Team
create_team = Create Team
org_desc = Description
@@ -2078,6 +2093,9 @@ 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_pattern_not_allowed = The pattern '%s' is not allowed in an organization name.
@@ -2338,6 +2356,13 @@ datasets.owner=Owner
datasets.name=name
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.add_webhook = Add Default Webhook
hooks.update_webhook = Update Default Webhook
@@ -2677,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
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>`
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]
ago = %s ago
@@ -2751,6 +2783,7 @@ head.dataset = Datasets
foot.council = Council
foot.technical_committee = Technical Committee
foot.join = Join OpenI
foot.agreement=Use agreement
foot.news = News
foot.community_news = Community News
foot.member_news = Member news


+ 32
- 1
options/locale/locale_zh-CN.ini View File

@@ -496,6 +496,14 @@ account_link=已绑定帐户
organization=组织
uid=用户 ID
u2f=安全密钥
wechat_bind = 微信绑定
bind_wechat = 绑定微信
bind_account_information = 绑定账号信息
bind_time = 绑定时间
wechat = 微信
unbind_wc = 解除绑定
unbind_wechat = 确定要解绑微信?
unbind_computing = 解绑后将无法使用启智算力环境

public_profile=公开信息
profile_desc=您的电子邮件地址将用于通知和其他操作。
@@ -868,10 +876,13 @@ confirm_choice=确定
cloudbran1_tips=只有zip格式的数据集才能发起云脑任务
cloudbrain_creator=创建者
cloudbrain_task=任务名称
cloudbrain_task_type=任务类型
cloudbrain_task_name=云脑侧任务名称
cloudbrain_operate=操作
cloudbrain_status_createtime=状态/创建时间
cloudbrain_status_runtime = 运行时长
cloudbrain_jobname_err=只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。
cloudbrain_query_fail=查询云脑任务失败。

record_begintime_get_err=无法获取统计开始时间。
parameter_is_wrong=输入参数错误,请检查输入参数。
@@ -2069,6 +2080,8 @@ people=组织成员
teams=组织团队
lower_members=名成员
lower_repositories=个项目
lower_member=名成员
lower_repository=个项目
create_new_team=新建团队
create_team=创建团队
org_desc=组织描述
@@ -2088,6 +2101,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_pattern_not_allowed=组织名称中不允许使用 "%s"。
@@ -2349,6 +2365,13 @@ datasets.owner=所有者
datasets.name=名称
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.add_webhook=新增默认Web钩子
hooks.update_webhook=更新默认Web钩子
@@ -2415,7 +2438,7 @@ auths.sspi_auto_activate_users_helper=允许 SSPI 认证自动激活新用户
auths.sspi_strip_domain_names=从用户名中删除域名部分
auths.sspi_strip_domain_names_helper=如果选中此项,域名将从登录名中删除(例如,"DOMAIN\user"和"user@example.org",两者都将变成只是“用户”)。
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_helper=SSPI 认证方法为用户自动创建的默认语言。如果您想要自动检测到语言,请留空。
auths.tips=帮助提示
@@ -2688,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>
approve_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]
ago=%s前
@@ -2762,6 +2792,7 @@ head.dataset=数据集
foot.council=理事会
foot.technical_committee=技术委员会
foot.join=加入启智
foot.agreement=使用协议
foot.news=动态
foot.community_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": {
"version": "0.4.12",
"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",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
"qrcodejs2": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/qrcodejs2/-/qrcodejs2-0.0.2.tgz",
"integrity": "sha1-Rlr+Xjnxn6zsuTLBH3oYYQkUauE="
},
"qs": {
"version": "6.9.4",
"resolved": "https://registry.npm.taobao.org/qs/download/qs-6.9.4.tgz",
@@ -13869,6 +13879,130 @@
"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==",
"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": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
@@ -13928,6 +14062,12 @@
"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": {
"version": "0.7.21",
"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-datetimepicker": "2.5.21",
"jquery.are-you-sure": "1.9.0",
"js-cookie": "3.0.1",
"less-loader": "6.1.0",
"mini-css-extract-plugin": "0.9.0",
"monaco-editor": "0.20.0",
@@ -41,6 +42,7 @@
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.2",
"qrcodejs2": "0.0.2",
"qs": "6.9.4",
"remixicon": "2.5.0",
"spark-md5": "3.0.1",
@@ -69,6 +71,8 @@
"script-loader": "0.7.2",
"stylelint": "13.3.3",
"stylelint-config-standard": "20.0.0",
"ts-loader": "4.0.0",
"typescript": "4.5.5",
"updates": "10.2.11"
},
"browserslist": [


+ 87
- 12
public/home/home.js View File

@@ -6,7 +6,6 @@ if(isEmpty(token)){
token = meta.attr("content");
}
}

var swiperNewMessage = new Swiper(".newslist", {
direction: "vertical",
slidesPerView: 10,
@@ -17,7 +16,7 @@ var swiperNewMessage = new Swiper(".newslist", {
},
});
var swiperRepo = new Swiper(".homepro-list", {
slidesPerView: 3,
slidesPerView: 1,
slidesPerColumn: 2,
slidesPerColumnFill:'row',
spaceBetween: 30,
@@ -29,6 +28,37 @@ var swiperRepo = new Swiper(".homepro-list", {
delay: 2500,
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");
@@ -64,10 +94,12 @@ socket.onmessage = function (e) {
var currentTime = new Date().getTime();
for(var i = 0; i < messageQueue.length; i++){
var record = messageQueue[i];

var recordPrefix = getMsg(record);
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"){
html += recordPrefix + actionName;
html += " <a href=\"" + getIssueLink(record) + "\" rel=\"nofollow\">" + getIssueText(record) + "</a>"
@@ -96,11 +128,14 @@ socket.onmessage = function (e) {
actionName = actionName.replace("{oldRepoName}",record.Content);
html += recordPrefix + actionName;
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{
continue;
}

if(record.Repo != null){
var time = getTime(record.CreatedUnix,currentTime);
html += " " + time;
@@ -108,19 +143,44 @@ socket.onmessage = function (e) {
html += "</div>";
html += "</div>";
}

}
output.innerHTML = html;
swiperNewMessage.updateSlides();
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){
var html ="";
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 += " <a href=\"/" + record.ActUser.Name + "\" title=\"\">" + record.ActUser.Name + "</a>"
html += " <a href=\"/" + name + "\" title=\"\">" + name + "</a>"
return html;
}

@@ -246,7 +306,14 @@ var actionNameZH={
"15":"重新开启了合并请求",
"17":"从 {repoName} 删除分支 {deleteBranchName}",
"22":"建议变更",
"23":"评论了合并请求"
"23":"评论了合并请求",
"24":"上传了数据集文件",
"25":"创建了CPU/GPU类型调试任务",
"26":"创建了NPU类型调试任务",
"27":"创建了训练任务",
"28":"创建了推理任务",
"29":"创建了评测任务",
"30":"导入了新模型"
};

var actionNameEN={
@@ -264,7 +331,14 @@ var actionNameEN={
"15":" reopened pull request",
"17":" deleted branch {deleteBranchName} from {repoName}",
"22":" proposed changes",
"23":" commented on pull request"
"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={
@@ -392,7 +466,7 @@ function displayOrg(json){
if (json != null && json.length > 0){
for(var i = 0; i < json.length;i++){
var record = json[i]
html += "<div class=\"column\">";
html += "<div class=\"swiper-slide\">";
html += " <a href=\"/" + record["Name"] + "\" class=\"ui fluid card\">";
html += " <div class=\"content\">";
html += " <div class=\"ui small header\">";
@@ -408,4 +482,5 @@ function displayOrg(json){
}
}
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)
}

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

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

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

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
@@ -879,7 +881,7 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqAnyRepoReader())
m.Group("/cloudbrain", func() {
m.Get("/:jobid", repo.GetCloudbrainTask)
m.Get("/:jobid/log", repo.CloudbrainGetLog)
m.Get("/:jobname/log", repo.CloudbrainGetLog)
}, reqRepoReader(models.UnitTypeCloudBrain))
m.Group("/modelarts", func() {
m.Group("/notebook", func() {
@@ -995,6 +997,12 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/topics", func() {
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())
}



+ 15
- 12
routers/api/v1/repo/cloudbrain.go View File

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

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

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

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

// jobName := ctx.Params(":jobname")
// job, err := models.GetCloudbrainByName(jobName)
jobID := ctx.Params(":jobid")
repoID := ctx.Repo.Repository.ID
job, err := models.GetRepoCloudBrainByJobID(repoID, jobID)
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 {
ctx.NotFound(err)
return
@@ -85,6 +87,7 @@ func GetCloudbrainTask(ctx *context.APIContext) {

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

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

var hits []models.Hits
result, err := cloudbrain.GetJobLog(jobID)
if err != nil{
result, err := cloudbrain.GetJobLog(job.JobID)
if err != nil {
log.Error("GetJobLog failed: %v", err, ctx.Data["MsgID"])
ctx.ServerError(err.Error(), err)
return
@@ -115,7 +118,7 @@ func CloudbrainGetLog(ctx *context.Context) {
if len(result.Hits.Hits) >= cloudbrain.LogPageSize {
for {
resultNext, err := cloudbrain.GetJobAllLog(result.ScrollID)
if err != nil{
if err != nil {
log.Error("GetJobAllLog failed: %v", err, ctx.Data["MsgID"])
} else {
for _, hit := range resultNext.Hits.Hits {
@@ -142,8 +145,8 @@ func CloudbrainGetLog(ctx *context.Context) {
}

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

return


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

@@ -77,6 +77,7 @@ func GetModelArtsNotebook2(ctx *context.APIContext) {

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



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

@@ -297,7 +297,7 @@ func allProjectsPeroidHeader(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("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),
@@ -317,7 +317,7 @@ func allProjectsOpenIHeader() 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("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),

@@ -466,10 +466,10 @@ func generateCountSql(beginTime time.Time, endTime time.Time, latestDate string,
countSql := "SELECT count(*) FROM " +
"(SELECT repo_id FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
"(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" +
"(SELECT repo_id,name,alias,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" +
" where A.repo_id=B.repo_id"
if q != "" {
countSql = countSql + " and LOWER(B.name) like '%" + strings.ToLower(q) + "%'"
countSql = countSql + " and LOWER(B.alias) like '%" + strings.ToLower(q) + "%'"
}
return countSql
}
@@ -482,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 {
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 " +
" FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
"(SELECT repo_id,name,owner_name,is_private,radar_total,num_watches,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor from public.repo_statistic where date='" + latestDate + "') B" +
"(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"

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)
return sql
}

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

sql = sql + " order by radar_total desc,repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
@@ -506,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 {

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 " +
" FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
"(SELECT repo_id,name,owner_name,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" +
"(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"
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)
return sql


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

+ 5
- 0
routers/home.go View File

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

// Home render home page
@@ -596,3 +597,7 @@ func RecommendRepoFromPromote(ctx *context.Context) {
ctx.JSON(200, result)
}
}

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

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

@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
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)

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



+ 4
- 2
routers/repo/attachment.go View File

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

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

@@ -845,6 +845,9 @@ func CompleteMultipart(ctx *context.Context) {
ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err))
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 isCanDecompress(attachment.Name) {
@@ -865,7 +868,6 @@ func CompleteMultipart(ctx *context.Context) {
labelmsg.SendDecompressAttachToLabelOBS(string(attachjson))
}
} else {
dataset, _ := models.GetDatasetByID(attachment.DatasetID)
var labelMap map[string]string
labelMap = make(map[string]string)
labelMap["UUID"] = uuid


+ 28
- 9
routers/repo/cloudbrain.go View File

@@ -350,18 +350,16 @@ func CloudBrainShow(ctx *context.Context) {

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

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

if result != nil {
jobRes, _ := models.ConvertToJobResultPayload(result.Payload)
jobRes.Resource.Memory = strings.ReplaceAll(jobRes.Resource.Memory, "Mi", "MB")
@@ -369,6 +367,7 @@ func cloudBrainShow(ctx *context.Context, tpName base.TplName) {
ctx.Data["resource_spec"] = spec
taskRoles := jobRes.TaskRoles
if jobRes.JobStatus.State != string(models.JobFailed) {

taskRes, _ := models.ConvertToTaskPod(taskRoles[cloudbrain.SubTaskName].(map[string]interface{}))
ctx.Data["taskRes"] = taskRes
task.Status = taskRes.TaskStatuses[0].State
@@ -422,11 +421,12 @@ func cloudBrainShow(ctx *context.Context, tpName base.TplName) {

ctx.Data["duration"] = util.AddZero(duration/3600000) + ":" + util.AddZero(duration%3600000/60000) + ":" + util.AddZero(duration%60000/1000)
ctx.Data["task"] = task
ctx.Data["jobID"] = jobID
// 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)
}

@@ -582,12 +582,19 @@ func logErrorAndUpdateJobStatus(err error, taskInfo *models.Cloudbrain) {
}

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
}
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all")

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 {
@@ -1098,6 +1105,9 @@ func GetChildTypes(ctx *context.Context) {
}

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)
@@ -1200,6 +1210,9 @@ func CloudBrainBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainF
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)
@@ -1343,5 +1356,11 @@ func BenchmarkDel(ctx *context.Context) {
ctx.ServerError(err.Error(), err)
return
}
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain/benchmark")

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

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

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)

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


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

@@ -91,6 +91,7 @@ func DebugJobIndex(ctx *context.Context) {
ctx.Data["Tasks"] = ciTasks
ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx)
ctx.Data["RepoIsEmpty"] = repo.IsEmpty
ctx.Data["debugListType"] = debugListType
ctx.HTML(200, tplDebugJobIndex)
}

@@ -103,8 +104,13 @@ func MustEnableModelArts(ctx *context.Context) {
}

func NotebookNew(ctx *context.Context) {
ctx.Data["PageIsCloudBrain"] = true
notebookNewDataPrepare(ctx)

ctx.HTML(200, tplModelArtsNotebookNew)
}

func notebookNewDataPrepare(ctx *context.Context) error {
ctx.Data["PageIsCloudBrain"] = true
t := time.Now()
var jobName = jobNamePrefixValid(cutString(ctx.User.Name, 5)) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:]
ctx.Data["job_name"] = jobName
@@ -112,26 +118,14 @@ func NotebookNew(ctx *context.Context) {
attachs, err := models.GetModelArtsUserAttachments(ctx.User.ID)
if err != nil {
ctx.ServerError("GetAllUserAttachments failed:", err)
return
return err
}

ctx.Data["attachments"] = attachs
ctx.Data["dataset_path"] = modelarts.DataSetMountPath
ctx.Data["env"] = modelarts.NotebookEnv
ctx.Data["notebook_type"] = modelarts.NotebookType
if modelarts.FlavorInfos == nil {
json.Unmarshal([]byte(setting.FlavorInfos), &modelarts.FlavorInfos)
}
ctx.Data["flavors"] = modelarts.FlavorInfos.FlavorInfo

ctx.HTML(200, tplModelArtsNotebookNew)
}

func notebookNewDataPrepare(ctx *context.Context) error {
ctx.Data["PageIsCloudBrain"] = true
t := time.Now()
var jobName = jobNamePrefixValid(cutString(ctx.User.Name, 5)) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:]
ctx.Data["job_name"] = jobName
if modelarts.ImageInfos == nil {
json.Unmarshal([]byte(setting.ImageInfos), &modelarts.ImageInfos)
}
ctx.Data["images"] = modelarts.ImageInfos.ImageInfo

if modelarts.FlavorInfos == nil {
json.Unmarshal([]byte(setting.FlavorInfos), &modelarts.FlavorInfos)
@@ -191,8 +185,7 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm
uuid := form.Attachment
description := form.Description
flavor := form.Flavor

flavor = "modelarts.bm.910.arm.public.1"
imageId := form.ImageId

count, err := models.GetCloudbrainNotebookCountByUserID(ctx.User.ID)
if err != nil {
@@ -223,7 +216,7 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm
}
}

err = modelarts.GenerateNotebook2(ctx, jobName, uuid, description, flavor)
err = modelarts.GenerateNotebook2(ctx, jobName, uuid, description, flavor, imageId)
if err != nil {
log.Error("GenerateNotebook2 failed, %v", err, ctx.Data["MsgID"])
notebookNewDataPrepare(ctx)
@@ -235,6 +228,7 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm

func NotebookShow(ctx *context.Context) {
ctx.Data["PageIsCloudBrain"] = true
debugListType := ctx.Query("debugListType")

var jobID = ctx.Params(":jobid")
task, err := models.GetCloudbrainByJobID(jobID)
@@ -262,13 +256,24 @@ func NotebookShow(ctx *context.Context) {

result.CreateTime = time.Unix(int64(result.CreateAt/1000), 0).Format("2006-01-02 15:04:05")
result.LatestUpdateTime = time.Unix(int64(result.UpdateAt/1000), 0).Format("2006-01-02 15:04:05")
//result.QueuingInfo.BeginTime = time.Unix(int64(result.QueuingInfo.BeginTimestamp/1000), 0).Format("2006-01-02 15:04:05")
//result.QueuingInfo.EndTime = time.Unix(int64(result.QueuingInfo.EndTimestamp/1000), 0).Format("2006-01-02 15:04:05")
}

datasetDownloadLink := "-"
if ctx.IsSigned {
if task.Uuid != "" && task.UserID == ctx.User.ID {
attachment, err := models.GetAttachmentByUUID(task.Uuid)
if err == nil {
datasetDownloadLink = attachment.S3DownloadURL()
}
}
}

ctx.Data["datasetDownloadLink"] = datasetDownloadLink
ctx.Data["task"] = task
ctx.Data["jobID"] = jobID
ctx.Data["jobName"] = task.JobName
ctx.Data["result"] = result
ctx.Data["debugListType"] = debugListType
ctx.HTML(200, tplModelArtsNotebookShow)
}

@@ -307,7 +312,7 @@ func NotebookDebug2(ctx *context.Context) {
ctx.RenderWithErr(err.Error(), tplModelArtsNotebookIndex, nil)
return
}
ctx.Redirect(result.Url)
}

@@ -342,6 +347,10 @@ func NotebookManage(ctx *context.Context) {
break
}
} else if action == models.ActionRestart {
ctx.CheckWechatBind()
if ctx.Written() {
return
}
if task.Status != string(models.ModelArtsStopped) && task.Status != string(models.ModelArtsStartFailed) && task.Status != string(models.ModelArtsCreateFailed) {
log.Error("the job(%s) is not stopped", task.JobName, ctx.Data["MsgID"])
resultCode = "-1"
@@ -387,7 +396,7 @@ func NotebookManage(ctx *context.Context) {
log.Error("ManageNotebook2(%s) failed:%v", task.JobName, err.Error(), ctx.Data["MsgID"])
resultCode = "-1"
errorMsg = err.Error()
if strings.Contains(err.Error(), "ModelArts.6404") {
if strings.Contains(err.Error(), modelarts.NotebookNotFound) {
errorMsg = "the job's version is too old and can not be restarted"
}
break
@@ -417,6 +426,7 @@ func NotebookManage(ctx *context.Context) {

func NotebookDel(ctx *context.Context) {
var jobID = ctx.Params(":jobid")
var listType = ctx.Query("debugListType")
task := ctx.Cloudbrain

if task.Status != string(models.ModelArtsCreateFailed) && task.Status != string(models.ModelArtsStartFailed) && task.Status != string(models.ModelArtsStopped) {
@@ -428,8 +438,12 @@ func NotebookDel(ctx *context.Context) {
_, err := modelarts.DelNotebook2(jobID)
if err != nil {
log.Error("DelNotebook2(%s) failed:%v", task.JobName, err.Error())
ctx.ServerError("DelNotebook2 failed", err)
return
if strings.Contains(err.Error(), modelarts.NotebookNotFound) || strings.Contains(err.Error(), modelarts.NotebookNoPermission) {
log.Info("old notebook version")
} else {
ctx.ServerError("DelNotebook2 failed", err)
return
}
}

err = models.DeleteJob(task)
@@ -438,7 +452,12 @@ func NotebookDel(ctx *context.Context) {
return
}

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all")
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 TrainJobIndex(ctx *context.Context) {
@@ -1447,6 +1466,7 @@ func TrainJobShow(ctx *context.Context) {
ctx.Data["jobName"] = VersionListTasks[0].JobName
ctx.Data["version_list_task"] = VersionListTasks
ctx.Data["version_list_count"] = VersionListCount
ctx.Data["canDownload"] = cloudbrain.CanDeleteJob(ctx, &VersionListTasks[0].Cloudbrain)
ctx.HTML(http.StatusOK, tplModelArtsTrainJobShow)
}

@@ -1543,7 +1563,12 @@ func TrainJobDel(ctx *context.Context) {
DeleteJobStorage(VersionListTasks[0].JobName)
}

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

func TrainJobStop(ctx *context.Context) {
@@ -2046,6 +2071,7 @@ func InferenceJobShow(ctx *context.Context) {
ctx.Data["jobID"] = jobID
ctx.Data["jobName"] = task.JobName
ctx.Data["task"] = task
ctx.Data["canDownload"] = cloudbrain.CanDeleteJob(ctx, task)

tempUids := []int64{}
tempUids = append(tempUids, task.UserID)


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

@@ -107,6 +107,7 @@ func RepoStatisticDaily(date string) {
RepoID: repo.ID,
Date: date,
Name: repo.Name,
Alias: repo.Alias,
IsPrivate: repo.IsPrivate,
IsMirror: repo.IsMirror,
OwnerName: repo.OwnerName,
@@ -282,7 +283,7 @@ func RepoStatisticDaily(date string) {
}

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

func getDatasetSize(repo *models.Repository) (int64, error) {


+ 4
- 2
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)
var indexTotal int64
indexTotal = 0
row := 1
for {
re, _ := models.QueryUserStaticDataByTableName(int(indexTotal), PAGE_SIZE, tableName, queryObj, "")
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, "B"+rows, userRecord.Name)
xlsx.SetCellValue(sheetName, "C"+rows, userRecord.CodeMergeCount)


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

@@ -6,6 +6,7 @@ package routes

import (
"bytes"
"code.gitea.io/gitea/routers/authentication"
"encoding/gob"
"net/http"
"path"
@@ -274,6 +275,8 @@ func RegisterRoutes(m *macaron.Macaron) {
ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: 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
validation.AddBindingRules()
@@ -319,6 +322,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/action/notification", routers.ActionNotification)
m.Get("/recommend/org", routers.RecommendOrgFromPromote)
m.Get("/recommend/repo", routers.RecommendRepoFromPromote)
m.Get("/home/term", routers.HomeTerm)
m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/explore/repos")
@@ -391,6 +395,13 @@ func RegisterRoutes(m *macaron.Macaron) {
}, ignSignInAndCsrf, reqSignIn)
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.Get("", userSetting.Profile)
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), userSetting.ProfilePost)
@@ -507,6 +518,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", admin.Datasets)
// 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.Get("", admin.DefaultOrSystemWebhooks)
@@ -974,30 +989,34 @@ func RegisterRoutes(m *macaron.Macaron) {
}, context.RepoRef())

m.Group("/cloudbrain", func() {
m.Group("/:jobid", func() {
m.Group("/:jobname", func() {
m.Get("", reqRepoCloudBrainReader, repo.CloudBrainShow)
})
m.Group("/:jobid", func() {
m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDebug)
m.Post("/commit_image", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.CloudBrainCommitImage)
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainStop)
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("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels)
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("/:jobid", func() {
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", reqRepoCloudBrainWriter, repo.CloudBrainBenchmarkNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainBenchmarkCreate)
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())
@@ -1045,8 +1064,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/:action", reqRepoCloudBrainWriter, repo.NotebookManage)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel)
})
m.Get("/create", reqRepoCloudBrainWriter, repo.NotebookNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.NotebookNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create)
})

m.Group("/train-job", func() {
@@ -1056,11 +1075,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.TrainJobDel)
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)
})
@@ -1072,8 +1091,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/result_download", cloudbrain.AdminOrJobCreaterRight, repo.ResultDownload)
m.Get("/downloadall", repo.DownloadMultiResultFile)
})
m.Get("/create", reqRepoCloudBrainWriter, repo.InferenceJobNew)
m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.InferenceJobNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate)
})
}, context.RepoRef())



+ 1
- 1
routers/user/notification.go View File

@@ -132,7 +132,7 @@ func getNotifications(c *context.Context) {
}

c.Data["Title"] = c.Tr("notifications")
c.Data["Keyword"] = keyword
//c.Data["Keyword"] = keyword
c.Data["Status"] = status
c.Data["Notifications"] = notifications



+ 1
- 1
services/socketwrap/clientManager.go View File

@@ -10,7 +10,7 @@ import (
"github.com/elliotchance/orderedmap"
)

var opTypes = []int{1, 2, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 22, 23}
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 {
Clients *orderedmap.OrderedMap


+ 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">
{{.i18n.Tr "admin.datasets"}}
</a>
<a class="{{if .PageIsAdminCloudBrains}}active{{end}} item" href="{{AppSubUrl}}/admin/cloudbrains">
云脑任务
</a>
<a class="{{if .PageIsAdminHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/hooks">
{{.i18n.Tr "admin.hooks"}}
</a>


+ 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.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="{{AppSubUrl}}/home/term/" class="item">{{.i18n.Tr "custom.foot.agreement"}}</a>
</div>
<div class="column ui vertical text menu">
<div class="header item">{{.i18n.Tr "custom.foot.news"}}</div>


+ 9
- 5
templates/base/head.tmpl View File

@@ -226,10 +226,12 @@ var _hmt = _hmt || [];
}
let isShowNoticeTag = false;
let notices= {{.notices.Notices}}
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
if(notices != null && notices!=''){
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
}
}
}
if (isShowNoticeTag){
@@ -245,7 +247,9 @@ var _hmt = _hmt || [];

}
}else{
document.getElementById("notic_content").style.display='none'
if (document.getElementById("notic_content") != null){
document.getElementById("notic_content").style.display='none'
}
}
}


+ 9
- 5
templates/base/head_fluid.tmpl View File

@@ -227,10 +227,12 @@ var _hmt = _hmt || [];
}
let isShowNoticeTag = false;
let notices= {{.notices.Notices}}
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
if(notices != null && notices!=''){
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
}
}
}
if (isShowNoticeTag){
@@ -246,7 +248,9 @@ var _hmt = _hmt || [];

}
}else{
document.getElementById("notic_content").style.display='none'
if (document.getElementById("notic_content") != null){
document.getElementById("notic_content").style.display='none'
}
}
}


+ 9
- 5
templates/base/head_home.tmpl View File

@@ -231,10 +231,12 @@ var _hmt = _hmt || [];
}
let isShowNoticeTag = false;
let notices= {{.notices.Notices}}
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
if(notices != null && notices!=''){
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
}
}
}
if (isShowNoticeTag){
@@ -250,7 +252,9 @@ var _hmt = _hmt || [];

}
}else{
document.getElementById("notic_content").style.display='none'
if (document.getElementById("notic_content") != null){
document.getElementById("notic_content").style.display='none'
}
}
}


+ 9
- 5
templates/base/head_pro.tmpl View File

@@ -228,10 +228,12 @@ var _hmt = _hmt || [];
}
let isShowNoticeTag = false;
let notices= {{.notices.Notices}}
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
if(notices != null && notices!=''){
for (i =0;i<notices.length;i++){
if (notices[i].Visible==1){
isShowNoticeTag =true;
break;
}
}
}
if (isShowNoticeTag){
@@ -247,7 +249,9 @@ var _hmt = _hmt || [];

}
}else{
document.getElementById("notic_content").style.display='none'
if (document.getElementById("notic_content") != null){
document.getElementById("notic_content").style.display='none'
}
}
}


+ 8
- 4
templates/home.tmpl View File

@@ -20,7 +20,7 @@
<div id="homenews" class="ui container">
<p>* {{.page_only_dynamic}}</p>
<div class="ui grid">
<div class="twelve wide tablet ten wide computer column homenews">
<div class="sixteen wide mobile twelve wide tablet ten wide computer column homenews">
<div class="newslist">
<div class="ui mini aligned list swiper-wrapper" id="newmessage">
@@ -42,7 +42,11 @@
<a href="{{AppSubUrl}}/explore/organizations" class="circular ui primary basic button">{{.page_recommend_org_more}} <i class="arrow circle right icon"></i></a>
</div>
<div class="sixteen wide tablet twelve wide computer column">
<div class="ui stackable three column grid homeorg-list" id="recommendorg">
<div class="homeorg-list">
<div class="swiper-wrapper" id="recommendorg">
</div>
<div class="swiper-pagination"></div>
</div>
</div>
@@ -78,7 +82,7 @@
<h2>{{.page_dev_env}}</h2>
<p><span class="ui text grey">{{.page_dev_env_desc}}</p>
</div>
<div class="ui four stackable cards">
<div class="ui four doubling cards">
<div class="card">
<div class="image">
<img src="/img/i-pic-01.svg">
@@ -158,4 +162,4 @@
<script src="/home/home.js?v={{MD5 AppVer}}" type="text/javascript"></script>


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

+ 14
- 2
templates/org/home.tmpl View File

@@ -67,8 +67,20 @@
<div class="item">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong class="team-name">{{.Name}}</strong></a>
<p class="text grey">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}</a> ·
<a href="{{$.OrgLink}}/teams/{{.LowerName}}/repositories"><strong>{{.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}</a>
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.NumMembers}}</strong>
{{if le .NumMembers 1}}
{{$.i18n.Tr "org.lower_member"}}
{{else}}
{{$.i18n.Tr "org.lower_members"}}
{{end}}
</a> ·
<a href="{{$.OrgLink}}/teams/{{.LowerName}}/repositories"><strong>{{.NumRepos}}</strong>
{{if le .NumRepos 1}}
{{$.i18n.Tr "org.lower_repository"}}
{{else}}
{{$.i18n.Tr "org.lower_repositories"}}
{{end}}
</a>
</p>
</div>
{{end}}


+ 14
- 2
templates/org/home_courses.tmpl View File

@@ -221,8 +221,20 @@
<div style="margin-top: 10px;">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong class="team-name">{{.Name}}</strong></a>
<p class="text grey">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}</a> ·
<a href="{{$.OrgLink}}/teams/{{.LowerName}}/repositories"><strong>{{.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}</a>
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.NumMembers}}</strong>
{{if le .NumMembers 1}}
{{$.i18n.Tr "org.lower_member"}}
{{else}}
{{$.i18n.Tr "org.lower_members"}}
{{end}}
</a> ·
<a href="{{$.OrgLink}}/teams/{{.LowerName}}/repositories"><strong>{{.NumRepos}}</strong>
{{if le .NumRepos 1}}
{{$.i18n.Tr "org.lower_repository"}}
{{else}}
{{$.i18n.Tr "org.lower_repositories"}}
{{end}}
</a>
</p>
</div>
{{end}}


+ 0
- 1
templates/org/member/course_members.tmpl View File

@@ -120,7 +120,6 @@

<script>
function show_bt(bt_id){
console.log(bt_id)
document.getElementById(""+bt_id).getElementsByClassName("button_leaveOrg")[0].style.display="inline-block";
}


+ 13
- 12
templates/org/select_pro.tmpl View File

@@ -70,11 +70,11 @@
<div style="width: 100%;margin:15px 0;">
{{if .tags}}
<span class="header">
精选项目
{{.i18n.Tr "org.selected_project"}}
</span>
<!-- {{.IsOrganizationOwner}} -->
{{if .IsOrganizationOwner}}
<a class="text-right" id="model" onclick="showcreate()" >{{svg "octicon-gear" 16}}自定义</a>
<a class="text-right" id="model" onclick="showcreate()" >{{svg "octicon-gear" 16}}{{.i18n.Tr "org.customize"}}</a>
{{end}}
{{end}}
@@ -137,10 +137,10 @@

<div class="ui modal">
<div class="header" style="padding: 1rem;background-color: rgba(240, 240, 240, 100);">
<h4 id="model_header">自定义精选项目</h4>
<h4 id="model_header">{{.i18n.Tr "org.custom_select_projects"}}</h4>
</div>
<div class="content content-padding" style="color: black;">
<p>最多可选9个公开项目</p>
<p>{{.i18n.Tr "org.max_selectedPro"}}</p>
<div class="ui search" >
<div class="ui input" style="width: 100%;">
<input type="text" id = 'search_selectPro' placeholder="Search ..." value = '' oninput="search()">
@@ -223,12 +223,12 @@
function saveSeletedPro(typeTag){
var saveData=[];
$('input[name="select_pro_name"]:checked').each(function(){
console.log('值',this.dataset.repoid)
// console.log('值',this.dataset.repoid)

saveData.push(parseInt(this.dataset.repoid));
})
if(saveData.length>9){
alert("最多可选9个,保存失败")
alert("{{.i18n.Tr "org.save_fail_tips"}}")
return
}
// saveData = getSelecteDataID();
@@ -242,7 +242,7 @@
data:JSON.stringify({'repoList':saveData
}),
success:function(res){
console.log('保存成功');
// console.log('保存成功');
location.reload()

}
@@ -270,15 +270,15 @@
filterData.push(data[i])
}
}
console.log("选中的值:",selectedData)
console.log("筛选包括选中的值:",filterData)
// console.log("选中的值:",selectedData)
// console.log("筛选包括选中的值:",filterData)
var showData=[];
for(i=0;i<selectedData.length;i++){
filterData =filterData.filter((item)=>{
return item.RepoID!=selectedData[i].RepoID
});
}
console.log("筛选后不包括选中的值:",filterData)
// console.log("筛选后不包括选中的值:",filterData)
$("#org_list").empty()
if(searchValue!=""){
if (filterData.length!=0){
@@ -307,7 +307,7 @@
num++
if(num>9){
document.getElementById(id).checked=false
alert("选择超过9个,请重新选择!")
alert("{{.i18n.Tr "org.select_again"}}")
return
}
}
@@ -315,7 +315,8 @@
}

var show_num = 9-num;
document.getElementById("recommend").innerHTML="还能推荐"+show_num+"个"
let rec = "{{.i18n.Tr "org.recommend_remain_pro"}}"
document.getElementById("recommend").innerHTML=rec +" : "+ show_num

}


+ 13
- 1
templates/org/team/courseTeams.tmpl View File

@@ -44,7 +44,19 @@
{{end}}
</div>
<div class="ui bottom attached header">
<p class="team-meta">{{.NumMembers}} {{$.i18n.Tr "org.lower_members"}} · {{.NumRepos}} {{$.i18n.Tr "org.lower_repositories"}}</p>
<p class="team-meta">{{.NumMembers}}
{{if le .NumMembers 1}}
{{$.i18n.Tr "org.lower_member"}}
{{else}}
{{$.i18n.Tr "org.lower_members"}}
{{end}}
· {{.NumRepos}}
{{if le .NumRepos 1}}
{{$.i18n.Tr "org.lower_repository"}}
{{else}}
{{$.i18n.Tr "org.lower_repositories"}}
{{end}}
</p>
</div>
</div>
{{end}}


+ 13
- 1
templates/org/team/teams.tmpl View File

@@ -36,7 +36,19 @@
{{end}}
</div>
<div class="ui bottom attached header">
<p class="team-meta">{{.NumMembers}} {{$.i18n.Tr "org.lower_members"}} · {{.NumRepos}} {{$.i18n.Tr "org.lower_repositories"}}</p>
<p class="team-meta">{{.NumMembers}}
{{if le .NumMembers 1}}
{{$.i18n.Tr "org.lower_member"}}
{{else}}
{{$.i18n.Tr "org.lower_members"}}
{{end}}
· {{.NumRepos}}
{{if le .NumRepos 1}}
{{$.i18n.Tr "org.lower_repository"}}
{{else}}
{{$.i18n.Tr "org.lower_repositories"}}
{{end}}
</p>
</div>
</div>
{{end}}


+ 7
- 178
templates/repo/cloudbrain/benchmark/index.tmpl View File

@@ -99,14 +99,13 @@
<!-- 任务名 -->
<div class="three wide column padding0">
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 14px;">
<a class="title" href="{{$.Link}}/{{.JobName}}" title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>
<!-- 任务状态 -->
<div class="two wide column padding0" style="padding-left: 2.2rem !important;">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{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>
@@ -137,10 +136,9 @@
<form id="stopForm-{{.JobID}}" style="margin-left:-1px;">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a id="stop-model-debug-{{.JobID}}" class='ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "STOPPING" "CREATING" "STARTING" "SUCCEEDED"}}disabled {{else}}blue {{end}}button' onclick='stopDebug("{{.JobID}}","{{$.RepoLink}}/cloudbrain/benchmark/{{.JobID}}/stop")'>
{{$.i18n.Tr "repo.stop"}}
</a>
<a id="ai-stop-{{.JobID}}" class='ui basic ai_stop {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "STOPPING" "CREATING" "STARTING" "SUCCEEDED"}}disabled {{else}}blue {{end}}button' data-repopath="{{$.RepoLink}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/stop" data-jobid="{{.JobID}}">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a class="ui basic disabled button">
@@ -148,26 +146,21 @@
</a>
{{end}}
</form>

<a class="ui basic button {{if $.IsSigned}} blue{{else}} disabled{{end}}" href="{{$.RepoLink}}/cloudbrain/{{.JobID}}/rate" target="_blank">
评分
</a>

<!-- 删除任务 -->
<form id="delForm-{{.JobID}}" action="{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain/benchmark{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}/del" method="post">
<input type="hidden" name="debugListType" value="all">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a id="model-delete-{{.JobID}}" class='ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "SUCCEEDED"}}blue {{else}}disabled {{end}}button' onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a id="ai-delete-{{.JobID}}" class='ui basic ai_delete {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "SUCCEEDED"}}blue {{else}}disabled {{end}}button' style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
<a class="ui basic button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a class="ui basic button disabled" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
</form>
</div>
@@ -221,167 +214,3 @@

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

<script>
console.log({{.Tasks}})
// 调试和评分新开窗口
function stop(obj) {
if (obj.style.color != "rgb(204, 204, 204)") {
obj.target = '_blank'
} else {
return
}
}

// 删除时用户确认
function assertDelete(obj) {
if (obj.style.color == "rgb(204, 204, 204)") {
return
} else {
var delId = obj.parentNode.id
flag = 1;
$('.ui.basic.modal')
.modal({
onDeny: function() {
flag = false
},
onApprove: function() {
document.getElementById(delId).submit()
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
}
}
function runtime(time){
if(time){
let hours = time/3600000<10 ? "0"+parseInt(time/3600000):parseInt(time/3600000)
let miuns = time%3600000/60000<10 ? "0"+parseInt(time%3600000/60000):parseInt(time%3600000/60000)
let seconds = time%60000/1000<10 ? "0"+parseInt(time%60000/1000):parseInt(time%60000/1000)
return hours + ":" + miuns + ":" + seconds
}else{
return "00:00:00"
}
}

// 加载任务状态
var timeid = window.setInterval(loadJobStatus, 15000);
$(document).ready(loadJobStatus);
function loadJobStatus() {
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
const computeResource = job.dataset.resource
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED']
if (initArray.includes(job.textContent.trim())) {
return
}
const diffResource = computeResource == "NPU" ? 'modelarts/notebook' : 'cloudbrain'
$.get(`/api/v1/repos/${repoPath}/${diffResource}/${jobID}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)
}
if(status==="RUNNING"){
$('#model-debug-'+jobID).removeClass('disabled').addClass('blue').text('调试').css("margin","0 1rem")
$('#model-image-'+jobID).removeClass('disabled').addClass('blue')
}
if(status!=="RUNNING"){
// $('#model-debug-'+jobID).removeClass('blue')
// $('#model-debug-'+jobID).addClass('disabled')
$('#model-image-'+jobID).removeClass('blue').addClass('disabled')
}
if(["CREATING","STOPPING","WAITING","STARTING"].includes(status)){
$('#model-debug-'+jobID).removeClass('blue').addClass('disabled')
}
if(['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED'].includes(status)){
$('#model-debug-'+jobID).removeClass('disabled').addClass('blue').text('再次调试').css("margin","0")
}
if(["RUNNING","WAITING"].includes(status)){
$('#stop-model-debug-'+jobID).removeClass('disabled').addClass('blue')
}
if(["CREATING","STOPPING","STARTING","STOPPED","FAILED","START_FAILED","SUCCEEDED"].includes(status)){
$('#stop-model-debug-'+jobID).removeClass('blue').addClass('disabled')
}
if(status==="STOPPED" || status==="FAILED"|| status==="START_FAILED"){
$('#model-delete-'+jobID).removeClass('disabled').addClass('blue')
}else{
$('#model-delete-'+jobID).removeClass('blue').addClass('disabled')
}
}).fail(function(err) {
console.log(err);
});
});
};
// 获取弹窗
var modal = document.getElementById('imageModal');

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

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

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

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

// 在用户点击其他地方时,关闭弹窗
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
function stopDebug(JobID,stopUrl){
$.ajax({
type:"POST",
url:stopUrl,
data:$('#stopForm-'+JobID).serialize(),
success:function(res){
if(res.result_code==="0"){
$('#' + JobID+'-icon').removeClass().addClass(res.status)
$('#' + JobID+ '-text').text(res.status)
if(res.status==="STOPPED"){
$('#model-debug-'+JobID).removeClass('disabled').addClass('blue').text("再次调试").css("margin","0")
$('#model-image-'+JobID).removeClass('blue').addClass('disabled')
$('#stop-model-debug-'+JobID).removeClass('blue').addClass('disabled')
$('#model-delete-'+JobID).removeClass('disabled').addClass('blue')
}
else{
$('#model-debug-'+JobID).removeClass('blue').addClass('disabled')
$('#stop-model-debug-'+JobID).removeClass('blue').addClass('disabled')
}
}else{
$('.alert').html(res.error_msg).removeClass('alert-success').addClass('alert-danger').show().delay(2000).fadeOut();
}
},
error :function(res){
console.log(res)

}
})

}

</script>

+ 17
- 5
templates/repo/cloudbrain/benchmark/new.tmpl View File

@@ -49,6 +49,9 @@
text-align: center;
color: #C2C7CC;
}
.nowrapx {
white-space: nowrap !important;
}

</style>
<!-- <div class="ui page dimmer">
@@ -83,7 +86,7 @@
</div>
<div class="unite min_title inline field">
<label style="font-weight: normal;" for="description">{{.i18n.Tr "repo.modelarts.train_job.description"}}&nbsp;&nbsp;</label>
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea>
<textarea style="width: 80%;" id="description" name="description" rows="3" maxlength="254" placeholder={{.i18n.Tr "repo.modelarts.train_job.new_place"}} onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)">{{.description}}</textarea>
</div>

<div class="required unite min_title inline field">
@@ -102,13 +105,18 @@
<span>&nbsp;</span>
<select class="ui fluid selection search dropdown" id="benchmark_types_id" name="benchmark_types_id" >
{{range .benchmark_types}}
<option value="{{.Id}}">{{.First}}</option>
{{if eq .Id $.benchmarkTypeID}}
<option value="{{.Id}}" selected="true">{{.First}}</option>
{{else}}
<option value="{{.Id}}">{{.First}}</option>
{{end}}
{{end}}
</select>
</div>
<div class="eight wide field" id="engine_name">
<input type="hidden" id="benchmark_child_types_id_hidden" name="benchmark_child_types_id_hidden" value="{{.benchmark_child_types_id_hidden}}">
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.cloudbrain.benchmark.evaluate_child_type"}}</label>
<select class="ui fluid selection dropdown nowrap" id="benchmark_child_types_id" style='width: 100%;' name="benchmark_child_types_id">
<select class="ui fluid selection dropdown nowrapx" id="benchmark_child_types_id" style='width: 100%;' name="benchmark_child_types_id">
</select>
</div>
</div>
@@ -182,12 +190,16 @@

function setChildType(){
let type_id = $('#benchmark_types_id').val();
let child_selected_id = $('#benchmark_child_types_id_hidden').val();
$.get(`${repolink}/cloudbrain/benchmark/get_child_types?benchmark_type_id=${type_id}`, (data) => {
console.log(JSON.stringify(data))
const n_length = data['child_types'].length
let html=''
for (let i=0;i<n_length;i++){
html += `<option value="${data['child_types'][i].id}">${data['child_types'][i].value}</option>`;
if(child_selected_id == data['child_types'][i].id){
html += `<option value="${data['child_types'][i].id}" selected="true">${data['child_types'][i].value}</option>`;
}else{
html += `<option value="${data['child_types'][i].id}">${data['child_types'][i].value}</option>`;
}
}
document.getElementById("benchmark_child_types_id").innerHTML=html;
})


+ 4
- 4
templates/repo/cloudbrain/benchmark/show.tmpl View File

@@ -188,6 +188,7 @@ td, th {
</h4>
{{range $k ,$v := .version_list_task}}
<div class="ui accordion border-according" id="accordion{{.VersionName}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<input type="hidden" id="jobId_input" name="jobId_input" value="{{.JobID}}">
<div class="{{if eq $k 0}}active{{end}} title padding0">
<div class="according-panel-heading">
<div class="accordion-panel-title">
@@ -432,19 +433,18 @@ td, th {
let userName
let repoPath
let jobID
let jobName
$(document).ready(function(){
let url = window.location.href;
let urlArr = url.split('/')
userName = urlArr.slice(-5)[0]
repoPath = urlArr.slice(-4)[0]
jobID = urlArr.slice(-1)[0]
jobName = urlArr.slice(-1)[0]
})


function loadLog(version_name){
document.getElementById("mask").style.display = "block"
$.get(`/api/v1/repos/${userName}/${repoPath}/cloudbrain/${jobID}/log?version_name=${version_name}&lines=50&order=asc`, (data) => {
$.get(`/api/v1/repos/${userName}/${repoPath}/cloudbrain/${jobName}/log?version_name=${version_name}&lines=50&order=asc`, (data) => {
$('input[name=end_line]').val(data.EndLine)
$('input[name=start_line]').val(data.StartLine)
$(`#log_file${version_name}`).text(data.Content)


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

@@ -254,7 +254,7 @@
<button class="ui green button" >
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
<a class="ui button cancel" href="{{.RepoLink}}/debugjob?debugListType=all">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
</div>
</form>


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

@@ -11,7 +11,7 @@
{{.i18n.Tr "repo.cloudbrain"}}
</a>
<div class="divider"> / </div>
<a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType=CPU/GPU">
<a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType={{if eq $.debugListType "NPU"}}NPU{{else if eq $.debugListType "CPU/GPU"}}CPU/GPU{{else}}all{{end}}">
{{$.i18n.Tr "repo.modelarts.notebook"}}
</a>
<div class="divider"> / </div>
@@ -98,4 +98,3 @@
</div>
</div>
{{template "base/footer" .}}


+ 2
- 2
templates/repo/courseHome.tmpl View File

@@ -121,8 +121,8 @@
</div>
<a style="margin-left: 0.5rem;" id="manage_topic">
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<i style="cursor: pointer;" class="plus square outline icon"></i>{{end}}
{{.i18n.Tr "repo.issues.new.add_labels_title"}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<i style="cursor: pointer;" class="plus square outline icon"></i>{{.i18n.Tr "repo.issues.new.add_labels_title"}}{{end}}
</a>
<div id="topic_edit" class="vue_menu" style="display:none;">


+ 0
- 2
templates/repo/createCourse.tmpl View File

@@ -94,7 +94,6 @@ $(document).ready(function(){
$('#course_label_item').empty()
}else{
$.get(`/api/v1/topics/search?q=${query}`,(data)=>{
console.log(data)
if(data.topics.length!==0){
let html=''
$('#course_label_item').empty()
@@ -107,5 +106,4 @@ $(document).ready(function(){
}
});
})
console.log()
</script>

+ 2
- 2
templates/repo/datasets/dataset_list.tmpl View File

@@ -7,14 +7,14 @@
<div class="row">
<div class="eight wide column" data-tooltip="{{.Name}}">
<span class="ui right">{{.Size | FileSize}}</span>
<a class="title" href="{{.DownloadURL}}?type={{$.Type}}">
<a class="title" href="{{.DownloadURL}}">
{{svg "octicon-cloud-download" 16}} {{.Name}}
</a>
</div>
<div class="eight wide column right aligned">
<div class="ui left mini icon buttons">
<span class="ui basic button text left" data-tooltip='{{$.i18n.Tr "dataset.download_count"}}' data-position="bottom right" style="width: 60px; padding-left: 0;">{{svg "octicon-flame" 16}} {{(.DownloadCount | PrettyNumber)}}</span>
<span class="ui basic basic button clipboard" data-clipboard-text="{{.DownloadURL}}" data-tooltip='{{$.i18n.Tr "dataset.copy_url"}}' data-clipboard-action="copy"{{if ne $.Type 0}} style="display:none;"{{end}}>{{svg "octicon-file" 16}}</span>
<span class="ui basic basic button clipboard" data-clipboard-text="{{.DownloadURL}}" data-tooltip='{{$.i18n.Tr "dataset.copy_url"}}' data-clipboard-action="copy">{{svg "octicon-file" 16}}</span>
<span class="ui basic basic button clipboard" data-clipboard-text="{{.FileChunk.Md5}}" data-tooltip='{{$.i18n.Tr "dataset.copy_md5"}}' data-clipboard-action="copy">{{svg "octicon-file-binary" 16}}</span>
</div>
{{if eq .DecompressState 1}}


+ 2
- 3
templates/repo/datasets/index.tmpl View File

@@ -11,9 +11,8 @@
{{template "repo/header" .}}
<script>
$(document).ready(function() {
url = window.location.href
type = url.split('?type=')[1]
if (type == 0){
const params = new URLSearchParams(window.location.search);
if (params.get('type') == 0){
$('.contorl_component').attr("id", 'minioUploader')
}else{
$('.contorl_component').attr("id", 'obsUploader')


+ 17
- 177
templates/repo/debugjob/index.tmpl View File

@@ -287,13 +287,13 @@
<div class="row">
<!-- 任务名 -->
<div class="four wide column">
<a class="title" href='{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}' title="{{.JobName}}" style="font-size: 14px;">
<a class="title" href='{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain/{{.JobName}}{{else}}{{$.RepoLink}}/modelarts/notebook/{{.JobID}}{{end}}?debugListType={{$.debugListType}}' title="{{.JobName}}" style="font-size: 14px;">
<span class="fitted text_over" style="width: 90%;vertical-align: middle;">{{.JobName}}</span>
</a>
</div>
<div class="two wide column text center">
<!--任务状态 -->
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-resource="{{.ComputeResource}}">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}" data-jobid="{{.JobID}}" data-resource="{{.ComputeResource}}">
<span><i id="{{.JobID}}-icon" style="vertical-align: middle;" class="{{.Status}}"></i><span id="{{.JobID}}-text" style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span>
</span>
</div>
@@ -324,11 +324,11 @@
{{$.CsrfTokenHtml}}
{{if .CanDebug}}
{{if eq .Status "RUNNING" "WAITING" "CREATING" "STARTING"}}
<a style="margin: 0 1rem;" id="model-debug-{{.JobID}}" class='ui basic {{if eq .Status "CREATING" "STOPPING" "WAITING" "STARTING"}}disabled {{else}}blue {{end}}button' onclick='debugAgain("{{.JobID}}","{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}/")'>
<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='{{$.RepoLink}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/'>
{{$.i18n.Tr "repo.debug"}}
</a>
{{else}}
<a id="model-debug-{{.JobID}}" class='ui basic {{if eq .Status "CREATING" "STOPPING" "WAITING" "STARTING"}} disabled {{else}}blue {{end}}button' onclick='debugAgain("{{.JobID}}","{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}/")'>
<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='{{$.RepoLink}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/' data-linkpath='{{$.Link}}'>
{{$.i18n.Tr "repo.debug_again"}}
</a>
{{end}}
@@ -349,15 +349,9 @@
<form id="stopForm-{{.JobID}}" style="margin-left:-1px;">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
{{if eq .ComputeResource "CPU/GPU" }}
<a id="stop-model-debug-{{.JobID}}" class='ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "STOPPING" "CREATING" "STARTING" "SUCCEEDED"}}disabled {{else}}blue {{end}}button' onclick='stopDebug("{{.JobID}}","{{$.RepoLink}}/cloudbrain/{{.JobID}}/stop")'>
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a id="stop-model-debug-{{.JobID}}" class='ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "STOPPING" "CREATING" "STARTING" "SUCCEEDED"}}disabled {{else}}blue {{end}}button' onclick='stopDebug("{{.JobID}}","{{$.RepoLink}}/modelarts/notebook/{{.JobID}}/stop")'>
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}
<a id="ai-stop-{{.JobID}}" class='ui basic ai_stop {{if eq .Status "STOPPED" "FAILED" "START_FAILED" "STOPPING" "CREATING" "STARTING" "SUCCEEDED"}}disabled {{else}}blue {{end}}button' data-repopath="{{$.RepoLink}}{{if eq .ComputeResource "CPU/GPU"}}/cloudbrain{{else}}/modelarts/notebook{{end}}/{{.JobID}}/stop" data-jobid="{{.JobID}}">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a class="ui basic disabled button">
{{$.i18n.Tr "repo.stop"}}
@@ -366,22 +360,19 @@
</form>
<!-- 删除 -->
<form id="delForm-{{.JobID}}" action="{{if eq .ComputeResource "CPU/GPU"}}{{$.RepoLink}}/cloudbrain{{else}}{{$.RepoLink}}/modelarts/notebook{{end}}/{{.JobID}}/del" method="post">
<input type="hidden" name="debugListType" value="all">
<input type="hidden" name="debugListType" value="{{$.ListType}}">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a id="model-delete-{{.JobID}}" class='ui basic {{if eq .Status "STOPPED" "FAILED" "START_FAILED"}}blue {{else}}disabled {{end}}button' onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a id="ai-delete-{{.JobID}}" class='ui basic ai_delete {{if eq .Status "STOPPED" "FAILED" "START_FAILED"}}blue {{else}}disabled {{end}}button' style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
<a class="ui basic button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
<a class="ui basic button disabled" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{end}}
</form>
</div>
<!-- 删除任务 -->
</div>
<div class="ui compact buttons" style="{{if eq .ComputeResource "CPU/GPU"}} visibility: visible {{else}} visibility: hidden{{end}}">
<div class="ui dropdown" id="model_more" style="padding: .58928571em 1.125em .58928571em;">
<div class="text">更多</div>
@@ -411,7 +402,6 @@
</a>
</div>
{{end}}
</div>
</div>
</div>
@@ -487,15 +477,15 @@

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

<script>
// 调试和评分新开窗口
console.log({{.Tasks}})
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
let url={{.RepoLink}}
let redirect_to = {{$.Link}}
let getParam=getQueryVariable('debugListType')
let dropdownValue = getParam==='all'||getParam==='' ? '全部' : getParam
localStorage.setItem('all',location.href)
let dropdownValue = ['all','',false].includes(getParam)? '全部' : getParam
// localStorage.setItem('all',location.href)
function getQueryVariable(variable)
{
let query = window.location.search.substring(1);
@@ -506,153 +496,6 @@
}
return(false);
}
function stop(obj) {
if (obj.style.color != "rgb(204, 204, 204)") {
obj.target = '_blank'
} else {
return
}
}
// 删除时用户确认
function assertDelete(obj) {
if (obj.style.color == "rgb(204, 204, 204)") {
return
} else {
var delId = obj.parentNode.id
flag = 1;
$('.ui.basic.modal')
.modal({
onDeny: function() {
flag = false
},
onApprove: function() {
document.getElementById(delId).submit()
flag = true
$('.alert').html('操作成功!').removeClass('alert-danger').addClass('alert-success').show().delay(1500).fadeOut();
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
}
}
function debugAgain(JobID,debugUrl){
if($('#' + JobID+ '-text').text()==="RUNNING"){
window.open(debugUrl+'debug')
}else{
$.ajax({
type:"POST",
url:debugUrl+'restart',
data:$('#debugAgainForm-'+JobID).serialize(),
success:function(res){
if(res.result_code==="0"){
if(res.job_id!==JobID){
location.reload()
}else{
$('#' + JobID+'-icon').removeClass().addClass(res.status)
$('#' + JobID+ '-text').text(res.status)
$('#model-debug-'+JobID).removeClass('blue').addClass('disabled')
$('#model-delete-'+JobID).removeClass('blue').addClass('disabled')
$('#model-debug-'+JobID).text("调试").css("margin","0 1rem")
}
}else{
$('.alert').html(res.error_msg).removeClass('alert-success').addClass('alert-danger').show().delay(2000).fadeOut();
}
},
error :function(res){
console.log(res)

}
})
}
}
function stopDebug(JobID,stopUrl){
$.ajax({
type:"POST",
url:stopUrl,
data:$('#stopForm-'+JobID).serialize(),
success:function(res){
if(res.result_code==="0"){
$('#' + JobID+'-icon').removeClass().addClass(res.status)
$('#' + JobID+ '-text').text(res.status)
if(res.status==="STOPPED"){
$('#model-debug-'+JobID).removeClass('disabled').addClass('blue').text("再次调试").css("margin","0")
$('#model-image-'+JobID).removeClass('blue').addClass('disabled')
$('#stop-model-debug-'+JobID).removeClass('blue').addClass('disabled')
$('#model-delete-'+JobID).removeClass('disabled').addClass('blue')
}
else{
$('#model-debug-'+JobID).removeClass('blue').addClass('disabled')
$('#stop-model-debug-'+JobID).removeClass('blue').addClass('disabled')
}
}else{
$('.alert').html(res.error_msg).removeClass('alert-success').addClass('alert-danger').show().delay(2000).fadeOut();
}
},
error :function(res){
console.log(res)

}
})

}
// 加载任务状态
var timeid = window.setInterval(loadJobStatus, 15000);
$(document).ready(loadJobStatus);
function loadJobStatus() {
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid;
const repoPath = job.dataset.repopath;
const computeResource = job.dataset.resource
const initArray = ['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED','UNAVAILABLE','DELETED','RESIZE_FAILED']
if (initArray.includes(job.textContent.trim())) {
return
}
const diffResource = computeResource == "NPU" ? 'modelarts/notebook' : 'cloudbrain'
$.get(`/api/v1/repos/${repoPath}/${diffResource}/${jobID}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)
}
if(status==="RUNNING"){
$('#model-debug-'+jobID).removeClass('disabled').addClass('blue').text('调试').css("margin","0 1rem")
$('#model-image-'+jobID).removeClass('disabled').addClass('blue')
}
if(status!=="RUNNING"){
// $('#model-debug-'+jobID).removeClass('blue')
// $('#model-debug-'+jobID).addClass('disabled')
$('#model-image-'+jobID).removeClass('blue').addClass('disabled')
}
if(["CREATING","STOPPING","WAITING","STARTING"].includes(status)){
$('#model-debug-'+jobID).removeClass('blue').addClass('disabled')
}
if(['STOPPED','FAILED','START_FAILED','CREATE_FAILED','SUCCEEDED'].includes(status)){
$('#model-debug-'+jobID).removeClass('disabled').addClass('blue').text('再次调试').css("margin","0")
}
if(["RUNNING","WAITING"].includes(status)){
$('#stop-model-debug-'+jobID).removeClass('disabled').addClass('blue')
}
if(["CREATING","STOPPING","STARTING","STOPPED","FAILED","START_FAILED","SUCCEEDED"].includes(status)){
$('#stop-model-debug-'+jobID).removeClass('blue').addClass('disabled')
}
if(status==="STOPPED" || status==="FAILED"|| status==="START_FAILED"){
$('#model-delete-'+jobID).removeClass('disabled').addClass('blue')
}else{
$('#model-delete-'+jobID).removeClass('blue').addClass('disabled')
}
}).fail(function(err) {
console.log(err);
});
});
};
$(document).ready(function(){
dropdownValue = dropdownValue==="CPU%2FGPU"? 'CPU/GPU' : dropdownValue
$('.default.text').text(dropdownValue)
@@ -662,6 +505,7 @@
})
$('.ui.selection.dropdown').dropdown({
onChange:function(value){
location.href = `${url}/debugjob?debugListType=${value}`
}
})
@@ -672,9 +516,6 @@
.transition('fade')
})
})

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

@@ -704,7 +545,6 @@
modal.style.display = "none";
}
}

// 显示弹窗,弹出相应的信息
function showmask() {
var image_tag = !$('#image_tag').val()


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

@@ -132,7 +132,7 @@
{{end}} -->

{{if and .Repository.CanEnablePulls (.Permission.CanRead $.UnitTypePullRequests)}}
<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls">
<a class="{{if or .PageIsComparePull .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls">
{{svg "octicon-git-pull-request" 16}} {{.i18n.Tr "repo.pulls"}} <span class="ui {{if not .Repository.NumOpenPulls}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenPulls}}</span>
</a>
{{end}}


+ 3
- 3
templates/repo/home.tmpl View File

@@ -255,9 +255,9 @@
{{end}}
</div>
<div class="ui six wide tablet four wide computer column">
<div id="repo-desc">
<h4 id="about-desc" class="ui header">简介</h4>
<input type="hidden" id="edit-alias" value="{{.Repository.Alias}}">
<div id="repo-desc" data-IsAdmin= "{{.Permission.IsAdmin}}" data-IsArchived="{{.Repository.IsArchived}}" >
<h4 id="about-desc" class="ui header">简介</h4>
<input type="hidden" id="edit-alias" value="{{.Repository.Alias}}">
<p>
{{if .Repository.DescriptionHTML}}
<span class="description" style="word-break:break-all">{{.Repository.DescriptionHTML}}</span>


+ 0
- 34
templates/repo/issue/new_form.tmpl View File

@@ -185,40 +185,6 @@
</div>
</div>
</form>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>
<script>
// $(document).ready(function(){
// var reward_value = $('.ui.form').form('get value', 'dog')
// var reward_value = $('form').form('get value', 'dog')
// console.log(reward_value)
// alert(reward_value)
// $('.ui.green.button').click(function(){
// $('.ui.form')
// .form({
// // on: 'blur',
// inline: true,
// fields: {
// dog: {
// identifier: 'dog',
// rules: [
// {
// type: 'empty',
// prompt: '请您输入项目奖励'
// },
// {
// type : 'integer[0..100]',
// prompt : '项目奖励必须为整数,请您输入有效奖励金额'
// }
// ]
// }
// },
// onFailure: function(e){
// return false;
// }
// });
// });
// </script>
{{if .PageIsComparePull}}
<script>window.wipPrefixes = {{.PullRequestWorkInProgressPrefixes}}</script>
{{end}}

+ 3
- 3
templates/repo/issue/view_content/pull.tmpl View File

@@ -448,8 +448,8 @@
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>
<script>
<!-- <script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script> -->
<!-- <script>

$(document)
.ready(function() {
@@ -477,4 +477,4 @@
});
});
})
</script>
</script> -->

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

@@ -113,11 +113,11 @@
<!-- 模型版本 -->
<!-- href="{{$.RepoLink}}/modelmanage/show_model_info?name={{.ModelName}}" -->
<div class="three wide column text center padding0">
<a id="{{.JobName}}" href="javascript:void(0);" data-variation="inverted" data-position="top center" data-content="{{$.i18n.Tr "repo.modelarts.infer_job.tooltip"}}" onclick="getModelInfo({{.ModelName}},{{.ModelVersion}},{{.JobName}})">{{.ModelName}}&nbsp;</a>&nbsp;<span style="font-size: 12px;">{{.ModelVersion}} </span>
<a id="{{.JobName}}" class="goto_modelmanage" href="javascript:void(0);" data-variation="inverted" data-position="top center" data-content="{{$.i18n.Tr "repo.modelarts.infer_job.tooltip"}}" data-jobname={{.JobName}} data-modelname={{.ModelName}} data-version={{.ModelVersion}} data-repopath="{{$.RepoLink}}">{{.ModelName}}&nbsp;</a>&nbsp;<span style="font-size: 12px;">{{.ModelVersion}}</span>
</div>
<!-- 任务状态 -->
<div class="two wide column text center padding0" >
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}/modelarts/inference-job" 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>
@@ -147,11 +147,11 @@
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})">
<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="{{$.RepoRelPath}}/modelarts/inference-job" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic disabled button">
<a style="padding: 0.5rem 1rem;" id="ai-stop-{{.JobID}}" class="ui basic disabled button">
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}
@@ -174,7 +174,7 @@
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this,{{.VersionName}},{{.JobID}})" style="border-radius: .28571429rem;">
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic ai_delete blue button" data-repopath="{{$.RepoRelPath}}/modelarts/inference-job/{{.JobID}}/del_version" data-version="{{.VersionName}}" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
@@ -221,118 +221,6 @@
</div>
</div>
</div>

</div>
{{template "base/footer" .}}
<script>
// 加载任务状态
var timeid = window.setInterval(loadJobStatus, 15000);
$(document).ready(loadJobStatus);
function loadJobStatus() {
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid
const repoPath = job.dataset.repopath
const versionname = job.dataset.version
const status_text = $(`#${jobID}-text`).text()
const finalState = ['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED','SUBMIT_MODEL_FAILED','DEPLOY_SERVICE_FAILED','CHECK_FAILED']
if(finalState.includes(status_text)){
return
}
$.get(`/api/v1/repos/${repoPath}/modelarts/inference-job/${jobID}?version_name=${versionname}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
const duration = data.JobDuration
$('#duration-'+jobID).text(duration)
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)
finalState.includes(status) && $('#' + jobID + '-stop').removeClass('blue').addClass('disabled')
}
}).fail(function(err) {
console.log(err);
});
});
};
function getModelInfo(ID,version,JobName){
$.get("{{$.RepoLink}}/modelmanage/show_model_info_api?name="+ID,(data)=>{
if(data.length===0){
$(`#${JobName}`).popup('toggle')
}else{
let versionData = data.filter((item)=>{
return item.Version === version
})
if(versionData.length==0){
$(`#${JobName}`).popup('toggle')
}
else{
location.href = "{{$.RepoLink}}/modelmanage/show_model_info?name="+ID
}
}
})

}
function deleteVersion(version_name,jobID){
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/del_version'
$.post(url,{version_name:version_name},(data)=>{
if(data.StatusOK===0){
location.reload()
}
}).fail(function(err) {
console.log(err);
});
}
function stopVersion(version_name,jobID){
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/inference-job/'+jobID+'/stop_version'
$.post(url,{version_name:version_name},(data)=>{
if(data.StatusOK===0){
$('#'+version_name+'-stop').removeClass('blue')
$('#'+version_name+'-stop').addClass('disabled')
refreshStatus(version_name,jobID)
}
}).fail(function(err) {
console.log(err);
});
}
function refreshStatus(version_name,jobID){

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


}).fail(function(err) {
console.log(err);
});
}
function assertDelete(obj,version_name,jobID) {
if (obj.style.color == "rgb(204, 204, 204)") {
return
} else {
// var delId = obj.parentNode.id
flag = 1;
$('.ui.basic.modal')
.modal({
onDeny: function() {
flag = false
},
onApprove: function() {
// document.getElementById(delId).submit()
deleteVersion(version_name,jobID)
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
}
}
</script>

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

@@ -27,7 +27,7 @@
}


.nowrap {
.nowrapx {
white-space: nowrap !important;
}
</style>
@@ -140,7 +140,7 @@
</select>
</div>
<div class="eight wide field" id="engine_name">
<select class="ui fluid selection dropdown nowrap" id="trainjob_engine_versions" name="engine_id" style="white-space: nowrap;">
<select class="ui fluid selection dropdown nowrapx" id="trainjob_engine_versions" name="engine_id" style="white-space: nowrap;!">
{{range .engine_versions}}
<option name="engine_id" value="{{.ID}}">{{.Value}}</option>
{{end}}


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

@@ -469,13 +469,13 @@ td, th {
</div>
{{template "base/footer" .}}
<script>
console.log({{.task}})
$(document).ready(function(){
$('.secondary.menu .item').tab();
});
let userName
let repoPath
let jobID
let downlaodFlag = {{$.canDownload }}
$(document).ready(function(){
let url = window.location.href;
let urlArr = url.split('/')
@@ -621,7 +621,12 @@ function renderDir(data,version_name){
html += `<a onclick="loadModelFile('${version_name}','${data.Dirs[i].ParenDir}','${data.Dirs[i].FileName}','folder')">`
html += "<span class='fitted'><i class='folder icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}else{
html += `<a href="${location.href}/result_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">`
if(downlaodFlag){
html += `<a href="${location.href}/result_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">`
}
else{
html += `<a class="disabled">`
}
html += "<span class='fitted'><i class='file icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}
html += '</a>'


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

@@ -19,7 +19,7 @@
<div class="repository new repo ui middle very relaxed page grid">
<div class="column">
{{template "base/alert" .}}
<div class="ui positive message" id="messageInfo">
<div class="ui negative message" id="messageInfo">
<p></p>
</div>
<form class="ui form" id="form_id" action="{{.Link}}" method="post">
@@ -51,7 +51,16 @@
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255" onkeyup="this.value=this.value.replace(/[, ]/g,'')">
</div>

<!-- <div class="inline field">
<div class="inline required field">
<label>镜像</label>
<select id="cloudbrain_image" class="ui search dropdown" placeholder="选择镜像" style='width:385px' name="image_id">
{{range .images}}
<option name="image_id" value="{{.Id}}">{{.Value}}</option>
{{end}}
</select>
</div>

<div class="inline field">
<label>数据集</label>
<input type="text" list="cloudbrain_dataset" placeholder="选择数据集" name="" id="answerInput" autofocus maxlength="36">
<datalist id="cloudbrain_dataset" class="ui search" style='width:385px' name="attachment">
@@ -62,7 +71,7 @@
<input type="hidden" name="attachment" id="answerInput-hidden">
</div>

<div class="inline required field">
<!--<div class="inline required field">
<label>工作环境</label>
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div>
@@ -92,7 +101,7 @@
<button class="ui green button">
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
<a class="ui button cancel" href="{{.RepoLink}}/debugjob?debugListType=all">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
</div>
</form>
@@ -162,11 +171,8 @@
if(option.innerText===inputValue){
hiddenInput.value = option.getAttribute('data-value');
break
break
}
}


})
</script>

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

@@ -11,7 +11,7 @@
{{.i18n.Tr "repo.cloudbrain"}}
</a>
<div class="divider"> / </div>
<a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType=NPU">
<a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType={{if eq $.debugListType "NPU"}}NPU{{else if eq $.debugListType "CPU/GPU"}}CPU/GPU{{else}}all{{end}}">
{{$.i18n.Tr "repo.modelarts.notebook"}}
</a>
<div class="divider"> / </div>
@@ -27,7 +27,7 @@
{{end}}
</div>
<div class="ui green segment">
<p>任务结果:</p>
<p>任务详情:</p>
{{with .result}}
<table class="ui celled striped table">
<tbody>
@@ -35,6 +35,18 @@
<td class="four wide"> 状态 </td>
<td> {{.Status}} </td>
</tr>
<tr>
<td> 描述 </td>
<td style="max-width: 480px; word-wrap:break-word">{{$.task.Description}}</td>
</tr>
<tr>
<td> 镜像名称 </td>
<td>{{$.task.Image}}</td>
</tr>
<tr>
<td> 数据集下载地址 </td>
<td style="max-width: 480px; word-wrap:break-word">{{$.datasetDownloadLink}}</td>
</tr>
<tr>
<td> 开始时间 </td>
<td>{{.CreateTime}}</td>


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

@@ -113,7 +113,7 @@
</div>
<!-- 任务状态 -->
<div class="two wide column padding0" style="padding-left: 2.2rem !important;">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}/modelarts/train-job" 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>
@@ -143,11 +143,11 @@
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})">
<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="{{$.RepoRelPath}}/modelarts/train-job" data-jobid="{{.JobID}}" data-version="{{.VersionName}}">
{{$.i18n.Tr "repo.stop"}}
</a>
{{else}}
<a style="padding: 0.5rem 1rem;" id="{{.JobID}}-stop" class="ui basic disabled button">
<a style="padding: 0.5rem 1rem;" id="ai-stop-{{.JobID}}" class="ui basic disabled button">
{{$.i18n.Tr "repo.stop"}}
</a>
{{end}}
@@ -157,7 +157,7 @@
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/del" method="post">
{{$.CsrfTokenHtml}}
{{if .CanDel}}
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this)" style="border-radius: .28571429rem;">
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic ai_delete blue button" style="border-radius: .28571429rem;">
{{$.i18n.Tr "repo.delete"}}
</a>
{{else}}
@@ -204,162 +204,5 @@
</div>
</div>
</div>

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

<script>
console.log({{.Tasks}})
// 调试和评分新开窗口
function stop(obj) {
if (obj.style.color != "rgb(204, 204, 204)") {
obj.target = '_blank'
} else {
return
}
}

// 删除时用户确认
function assertDelete(obj) {
if (obj.style.color == "rgb(204, 204, 204)") {
return
} else {
var delId = obj.parentNode.id
flag = 1;
$('.ui.basic.modal')
.modal({
onDeny: function() {
flag = false
},
onApprove: function() {
document.getElementById(delId).submit()
flag = true
},
onHidden: function() {
if (flag == false) {
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut();
}
}
})
.modal('show')
}
}
function runtime(time){
if(time){
let hours = time/3600000<10 ? "0"+parseInt(time/3600000):parseInt(time/3600000)
let miuns = time%3600000/60000<10 ? "0"+parseInt(time%3600000/60000):parseInt(time%3600000/60000)
let seconds = time%60000/1000<10 ? "0"+parseInt(time%60000/1000):parseInt(time%60000/1000)
return hours + ":" + miuns + ":" + seconds
}else{
return "00:00:00"
}
}

// 加载任务状态
var timeid = window.setInterval(loadJobStatus, 15000);
$(document).ready(loadJobStatus);
function loadJobStatus() {
$(".job-status").each((index, job) => {
const jobID = job.dataset.jobid
const repoPath = job.dataset.repopath
const versionname = job.dataset.version
const status_text = $(`#${jobID}-text`).text()
const finalState = ['IMAGE_FAILED','SUBMIT_FAILED','DELETE_FAILED','KILLED','COMPLETED','FAILED','CANCELED','LOST','START_FAILED','SUBMIT_MODEL_FAILED','DEPLOY_SERVICE_FAILED','CHECK_FAILED']
if(finalState.includes(status_text)){
return
}
$.get(`/api/v1/repos/${repoPath}/modelarts/train-job/${jobID}?version_name=${versionname}`, (data) => {
const jobID = data.JobID
const status = data.JobStatus
const duration = data.JobDuration
$('#duration-'+jobID).text(duration)
if (status != job.textContent.trim()) {
$('#' + jobID+'-icon').removeClass().addClass(status)
$('#' + jobID+ '-text').text(status)
finalState.includes(status) && $('#' + jobID + '-stop').removeClass('blue').addClass('disabled')

}
}).fail(function(err) {
console.log(err);
});
});
};
// 获取弹窗
var modal = document.getElementById('imageModal');

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

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

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

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

// 在用户点击其他地方时,关闭弹窗
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
function stopVersion(version_name,jobID){
const url = '/api/v1/repos/{{$.RepoRelPath}}/modelarts/train-job/'+jobID+'/stop_version'
$.post(url,{version_name:version_name},(data)=>{
if(data.StatusOK===0){
$('#'+version_name+'-stop').removeClass('blue')
$('#'+version_name+'-stop').addClass('disabled')
refreshStatus(version_name,jobID)
}
}).fail(function(err) {
console.log(err);
});
}
function refreshStatus(version_name,jobID){

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


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

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

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

+ 9
- 5
templates/repo/modelarts/trainjob/show.tmpl View File

@@ -518,6 +518,7 @@ td, th {
</div>
</div>
</div>
{{template "base/footer" .}}

<script>
@@ -532,6 +533,7 @@ td, th {
let userName
let repoPath
let jobID
let downlaodFlag = {{$.canDownload }}
$(document).ready(function(){
let url = window.location.href;
let urlArr = url.split('/')
@@ -560,16 +562,13 @@ td, th {
$('input[name="JobId"]').val(obj.JobID)
$('input[name="VersionName"]').val(obj.VersionName).addClass('model_disabled')
$('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"})
createModelName()
createModelName()
},
onHide:function(){
document.getElementById("formId").reset();
$('.ui.dimmer').css({"background-color":""})
$('.ui.error.message').text()
$('.ui.error.message').css('display','none')
}
})
.modal('show')
@@ -787,7 +786,12 @@ td, th {
html += `<a onclick="loadModelFile('${version_name}','${data.Dirs[i].ParenDir}','${data.Dirs[i].FileName}','folder')">`
html += "<span class='fitted'><i class='folder icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}else{
html += `<a href="${location.href}/model_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">`
if(downlaodFlag){
html += `<a href="${location.href}/model_download?version_name=${version_name}&file_name=${data.Dirs[i].FileName}&parent_dir=${data.Dirs[i].ParenDir}">`
}
else{
html += `<a class="disabled">`
}
html += "<span class='fitted'><i class='file icon' width='16' height='16' aria-hidden='true'></i>" + data.Dirs[i].FileName + "</span>"
}
html += '</a>'


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

@@ -186,7 +186,6 @@
<div class='ui breadcrumb model_file_bread' id='file_breadcrumb'>
<div class="active section"></div>
<div class="divider"> / </div>

</div>
<div id="dir_list">


+ 1
- 3
templates/repo/repo_name.tmpl View File

@@ -52,6 +52,4 @@
</div>

<script>
console.log({{$.Err_Alias}})
</script>

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

@@ -0,0 +1,15 @@
<!-- 头部导航栏 -->
{{template "base/head" .}}
<div class="alert" style="top: 0;"></div>
<div class="repository release dataset-list view">
{{template "repo/header" .}}
{{template "base/alert" .}}
<input id="WxAutorize-qrcode" type="hidden" value="{{.qrcode.Ticket}}">
<div id="WxAutorize">
</div>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}


+ 70
- 0
templates/terms.tmpl View File

@@ -0,0 +1,70 @@
{{template "base/head_home" .}}
<div class="ui container">
<h1 class="ui center am-pt-30 am-pb-20">OpenI启智社区AI协作平台使用协议</h1>
<div class="ui divider am-pb-10"></div>
<p>
OpenI启智社区AI协作平台作为新一代人工智能领域开源开放开发协作平台,不仅为用户提供代码托管与数据集管理等服务,同时提供开发者所需的计算算力资源,一个良好的开发环境对用户、组织和项目都尤为重要。
</p>
<p>
为了保障用户权益,维护平台正常秩序,实现平台规范化运营,给开发者提供一个良好的开发环境,您不得在本平台进行恶意攻击、挖矿等任何违法或扰乱平台秩序的行为。<strong>一经发现,平台有权变更、暂停或终止您对平台服务的使用而无须事先声明,包括但不限于限制您使用平台服务的次数与资源、账号永久封闭等。</strong>
</p>
<h2>
AI协作平台使用协议及违规处罚
</h2>
<p>
1. 您需保证对平台服务的使用不违反国家各项法律法规的规定,且不侵害任何第三方权益,如因此造成任何后果及损失,由您自行承担全部责任。
</p>
<p>
<strong>2. 禁止行为</strong>
</p>
<p>
您充分理解并同意,您在使用平台服务时,应当遵守所有中华人民共和国的法律、法规、规章制度、规范、政策、行政命令、强制标准及行业标准等(统称为“法律法规”)。除非法律法规允许且启智社区事先书面许可,您不得从事以下活动,也不得同意、授权或指示任何第三人从事包括但不限于以下内容的活动:
<br>
(1) 对平台服务进行挖矿、逆向工程、反编辑等恶意行为损坏平台服务相关内容与数据,或不正当手段获取原始数据和其他数据等;<br>
(2) 对平台服务或者平台服务运行过程中释放出的任何数据或其他数据及平台服务运行过程中的交互数据进行复制、更改、修改等操作,包括但不限于使用插件、外挂或非经授权的第三方工具或服务接入平台服务和相关系统;
<br>
(3) 宣扬或提供关于非法行为的说明信息、宣扬针对任何团体或个人的人身伤害或传播任何病毒、蠕虫、缺陷、特洛伊木马或其他具有破坏性的内容等;<br>
(4) 删除本平台服务中包含的任何版权声明、商标声明或其他所有权声明;包括但不限于任何有损本平台一切相关知识产权的行为;<br>
(5) 创造任何网站或应用程序以重现或复制平台服务或本平台。
</p>
<p>
<strong>3. 遵守法律规范</strong>
</p>
<p>
您不得利用平台服务上传、上载、发布、发表、传播任何违法内容,包括但不限于您制作、复制、发布、传播下列信息:
</p>
<p>
(1)煽动抗拒、破坏宪法和法律、行政法规实施<br>
(2)煽动颠覆国家政权,推翻社会主义制度<br>
(3)煽动分裂国家、破坏国家统一<br>
(4)煽动民族仇恨、民族歧视、破坏民族团结<br>
(5)捏造或者歪曲事实、散布谣言,扰乱社会秩序<br>
(6)宣扬封建迷信、淫秽、色情、赌博、暴力、教唆犯罪<br>
(7)公然侮辱他人或者捏造事实诽谤他人<br>
(8)损害国家机关信誉或有损国家领导人荣誉<br>
(9)其他违反宪法和法律、行政法规<br>
(10)含有虚假、有害、胁迫、侵害他人隐私、骚扰、侵害、中伤、粗俗、猥亵或其他道德上令人反感的内容<br>
(11)含有中国法律、法规、规章、条例以及任何具有法律效力之规范所限制或禁止的其他内容
</p>
<p>
一经发现,平台将按照国家有关规定,及时删除网站中含有上述内容的地址、目录,并保留原始记录,在二十四小时之内向公安机关报告。
</p>
<p>
<strong>4. 保障网络信息安全</strong>
</p>
<p>
您不得利用平台服务从事以下危害计算机网络信息安全的活动:<br>
(1) 未经允许,进入计算机信息网络或者使用计算机信息网络资源的;<br>
(2) 未经允许,对计算机信息网络功能进行删除、修改或者增加的;<br>
(3) 未经允许,对进入计算机信息网络中存储、处理或者传输的数据和应用程序进行删除、修改或者增加的;<br>
(4) 故意制作、传播计算机病毒等破坏性程序的;<br>
(5) 其他危害计算机信息网络安全的行为。
</p>
<p>
5. 您充分理解并同意,您对自己使用平台服务的一切行为及由此产生的一切结果负责,包括但不限于您所发表的任何内容、提供的任何服务以及由此产生的任何后果。您应对平台服务的内容应自行判断并决定是否使用,并承担因使用平台服务及其相关内容而引起的所有风险,包括因对平台服务及其内容的真实性、完整性、准确性、及时性及实用性的依赖而产生的风险。平台不对此提供任何担保和保证,不对因前述风险而导致的任何后果或损失对您承担责任。
</p>
<p>
<strong>如果您违反了法律法规或本协议,OpenI启智社区有权依据合理判断对违反法律法规或本协议的行为作出处理,并保留对该违反行为采取法律所能提供的所有补救手段的权利。OpenI启智社区有权对违反法律法规及本协议的任何用户调查并采取适当的法律行动,包括但不限于民事诉讼等。OpenI启智社区有权根据违法违规行为的严重程度,将上述违法违规行为的线索和您的个人信息报告司法机关或其他执法机关,并配合司法机关或其他执法机关进行的调查、听证、起诉等。您应当自行承担由此产生的任何法律责任。</strong>
</p>
</div>
{{template "base/footer" .}}

+ 32
- 2
templates/user/dashboard/feeds.tmpl View File

@@ -69,7 +69,21 @@
{{$.i18n.Tr "action.reject_pull_request" .GetRepoLink $index .ShortRepoFullDisplayName | Str2html}}
{{else if eq .GetOpType 23}}
{{ $index := index .GetIssueInfos 0}}
{{$.i18n.Tr "action.comment_pull" .GetRepoLink $index .ShortRepoFullDisplayName | Str2html}}
{{$.i18n.Tr "action.comment_pull" .GetRepoLink $index .ShortRepoPath | Str2html}}
{{else if eq .GetOpType 24}}
{{$.i18n.Tr "action.upload_dataset" .GetRepoLink .Content .RefName | Str2html}}
{{else if eq .GetOpType 25}}
{{$.i18n.Tr "action.task_gpudebugjob" .GetRepoLink .RefName .RefName | Str2html}}
{{else if eq .GetOpType 26}}
{{$.i18n.Tr "action.task_npudebugjob" .GetRepoLink .Content .RefName | Str2html}}
{{else if eq .GetOpType 27}}
{{$.i18n.Tr "action.task_trainjob" .GetRepoLink .Content .RefName | Str2html}}
{{else if eq .GetOpType 28}}
{{$.i18n.Tr "action.task_inferencejob" .GetRepoLink .Content .RefName | Str2html}}
{{else if eq .GetOpType 29}}
{{$.i18n.Tr "action.task_benchmark" .GetRepoLink .RefName .RefName | Str2html}}
{{else if eq .GetOpType 30}}
{{$.i18n.Tr "action.task_createmodel" .GetRepoLink .RefName .RefName | Str2html}}
{{end}}
</p>
{{if or (eq .GetOpType 5) (eq .GetOpType 18)}}
@@ -101,7 +115,23 @@
</div>
</div>
<div class="ui two wide right aligned column">
<span class="text grey">{{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32}}</span>
{{if eq .GetOpType 24}}
<span class="text grey"><i class="ri-uninstall-line icon big"></i></span>
{{else if eq .GetOpType 25}}
<span class="text grey"><i class="ri-voice-recognition-line icon big"></i></span>
{{else if eq .GetOpType 26}}
<span class="text grey"><i class="ri-voice-recognition-line icon big"></i></span>
{{else if eq .GetOpType 27}}
<span class="text grey"><i class="ri-character-recognition-line icon big"></i></span>
{{else if eq .GetOpType 28}}
<span class="text grey"><i class="ri-haze-2-line icon big"></i></span>
{{else if eq .GetOpType 29}}
<span class="text grey"><i class="ri-vip-crown-line icon big"></i></span>
{{else if eq .GetOpType 30}}
<span class="text grey"><i class="ri-picture-in-picture-exit-line icon big"></i></span>
{{else}}
<span class="text grey">{{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32}}</span>
{{end}}
</div>
</div>
<div class="ui divider"></div>


+ 0
- 3
templates/user/dashboard/issues.tmpl View File

@@ -208,6 +208,3 @@
</div>
</div>
{{template "base/footer" .}}
<script>
console.log({{.Issues}})
</script>

+ 0
- 3
templates/user/dashboard/milestones.tmpl View File

@@ -117,6 +117,3 @@
</div>
</div>
{{template "base/footer" .}}
<script>
console.log({{.Milestones}})
</script>

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

@@ -141,6 +141,7 @@

{{if eq .TabName "activity"}}
{{if .EnableHeatmap}}
<div id="user-heatmap" style="padding-right: 40px">
<activity-heatmap :locale="locale" :suburl="suburl" :user="heatmapUser">
<div slot="loading">


+ 66
- 0
templates/user/settings/profile.tmpl View File

@@ -1,6 +1,7 @@
{{template "base/head" .}}
<div class="user settings profile">
{{template "user/settings/navbar" .}}
<div class="alert" style="top: 0;"></div>
<div class="ui container">
{{template "base/alert" .}}
<h4 class="ui top attached header">
@@ -102,6 +103,71 @@
</div>
</form>
</div>
<h4 class="ui top attached header">
{{$.i18n.Tr "settings.wechat_bind"}}
</h4>
{{if not .SignedUser.IsBindWechat}}
<div class="ui attached segment">
<a href="/authentication/wechat/bind?redirect_to=/user/settings" class="ui green button">{{$.i18n.Tr "settings.bind_wechat"}}</a>
</div>
{{else}}
<div class="ui attached segment">
<table class="ui celled striped table provider titleless">
<thead>
<th class="center aligned">
{{$.i18n.Tr "settings.bind_account_information"}}
</th>
<th class="center aligned">
{{$.i18n.Tr "settings.bind_time"}}
</th>
<th class="center aligned">
{{$.i18n.Tr "repo.cloudbrain_operate"}}
</th>
</thead>
<tbody>
<td class="center aligned">
{{$.i18n.Tr "settings.wechat"}}
</td>
<td class="center aligned">
{{TimeSinceUnix1 .SignedUser.WechatBindUnix}}
<td class="center aligned">
<div>
<a class="ui inverted orange button " onclick="showcreate(this)" href="javascript: void(0)">{{$.i18n.Tr "settings.unbind_wc"}}</a>
</div>
</td>
</tbody>
</table>
</div>
{{end}}
</div>
<div class="ui mini modal wx-unbind">
<div class="header">{{$.i18n.Tr "settings.unbind_wechat"}}</div>
<div class="content">
<p>{{$.i18n.Tr "settings.unbind_computing"}}</p>
</div>
<div class="actions">
<div class="ui blank cancel button">{{$.i18n.Tr "cancel"}}</div>
<div class="ui green approve button">{{$.i18n.Tr "repo.confirm_choice"}}</div>
</div>
</div>
</div>
{{template "base/footer" .}}
<script>
function showcreate(obj){
const {csrf} = window.config
$('.ui.modal.wx-unbind')
.modal({
onShow:function(){
},
onHide:function(){
},
onApprove:function($element){
$.post('/authentication/wechat/unbind',{_csrf:csrf},(data)=>{
$('.alert').html('解绑成功!').removeClass('alert-danger').addClass('alert-success').show().delay(1500).fadeOut();
window.location.href = '/user/settings'
})
}
})
.modal('show')
}
</script>

+ 0
- 0
tsconfig.json View File


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

Loading…
Cancel
Save