diff --git a/custom/public/css/git.openi.css b/custom/public/css/git.openi.css index 502ba8d88..c6ada7b28 100644 --- a/custom/public/css/git.openi.css +++ b/custom/public/css/git.openi.css @@ -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; } } diff --git a/models/action.go b/models/action.go index 4821910db..2a9d88399 100755 --- a/models/action.go +++ b/models/action.go @@ -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 diff --git a/models/attachment.go b/models/attachment.go index fd1df9e43..c322d391b 100755 --- a/models/attachment.go +++ b/models/attachment.go @@ -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 diff --git a/models/cloudbrain.go b/models/cloudbrain.go index 0a14ea7b4..8bb3357ae 100755 --- a/models/cloudbrain.go +++ b/models/cloudbrain.go @@ -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 } diff --git a/models/models.go b/models/models.go index 11f445830..0f4679b4f 100755 --- a/models/models.go +++ b/models/models.go @@ -136,6 +136,7 @@ func init() { new(AiModelManage), new(OfficialTag), new(OfficialTagRepos), + new(WechatBindLog), ) tablesStatistic = append(tablesStatistic, diff --git a/models/repo_statistic.go b/models/repo_statistic.go index d3eb65c30..809cb26c4 100755 --- a/models/repo_statistic.go +++ b/models/repo_statistic.go @@ -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() diff --git a/models/user.go b/models/user.go index b362472e8..f7857248b 100755 --- a/models/user.go +++ b/models/user.go @@ -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", diff --git a/models/wechat_bind.go b/models/wechat_bind.go new file mode 100644 index 000000000..b100221f2 --- /dev/null +++ b/models/wechat_bind.go @@ -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() +} diff --git a/modules/auth/modelarts.go b/modules/auth/modelarts.go index 821cd72f8..2d30de6ed 100755 --- a/modules/auth/modelarts.go +++ b/modules/auth/modelarts.go @@ -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 { diff --git a/modules/auth/wechat/access_token.go b/modules/auth/wechat/access_token.go new file mode 100644 index 000000000..0a63bc2de --- /dev/null +++ b/modules/auth/wechat/access_token.go @@ -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 +} diff --git a/modules/auth/wechat/bind.go b/modules/auth/wechat/bind.go new file mode 100644 index 000000000..7b4bffc02 --- /dev/null +++ b/modules/auth/wechat/bind.go @@ -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 +} diff --git a/modules/auth/wechat/call.go b/modules/auth/wechat/call.go new file mode 100644 index 000000000..f93535c6b --- /dev/null +++ b/modules/auth/wechat/call.go @@ -0,0 +1,5 @@ +package wechat + +type WechatCall interface { + call() +} diff --git a/modules/auth/wechat/client.go b/modules/auth/wechat/client.go new file mode 100644 index 000000000..6734977a1 --- /dev/null +++ b/modules/auth/wechat/client.go @@ -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 +} diff --git a/modules/auth/wechat/event_handle.go b/modules/auth/wechat/event_handle.go new file mode 100644 index 000000000..b40ab3101 --- /dev/null +++ b/modules/auth/wechat/event_handle.go @@ -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" +) + +// +// +// +// 123456789 +// +// +// +// +// +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 +} diff --git a/modules/auth/wechat/qr_code.go b/modules/auth/wechat/qr_code.go new file mode 100644 index 000000000..9d2f6ca04 --- /dev/null +++ b/modules/auth/wechat/qr_code.go @@ -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 +} diff --git a/modules/cloudbrain/cloudbrain.go b/modules/cloudbrain/cloudbrain.go index f15443b30..b86a2d3f4 100755 --- a/modules/cloudbrain/cloudbrain.go +++ b/modules/cloudbrain/cloudbrain.go @@ -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 } diff --git a/modules/context/auth.go b/modules/context/auth.go index 61a7b029b..287823dea 100755 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -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") diff --git a/modules/context/context.go b/modules/context/context.go index 65f4b3f5d..2c935881c 100755 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -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", + }) +} diff --git a/modules/modelarts/modelarts.go b/modules/modelarts/modelarts.go index 301c4cb0e..6d60b4e24 100755 --- a/modules/modelarts/modelarts.go +++ b/modules/modelarts/modelarts.go @@ -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 +} diff --git a/modules/modelarts/resty.go b/modules/modelarts/resty.go index afc5a14c0..65dd6fe6d 100755 --- a/modules/modelarts/resty.go +++ b/modules/modelarts/resty.go @@ -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 { diff --git a/modules/notification/action/action.go b/modules/notification/action/action.go index 4bc296657..2ac73c2c3 100644 --- a/modules/notification/action/action.go +++ b/modules/notification/action/action.go @@ -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) + } +} diff --git a/modules/notification/base/notifier.go b/modules/notification/base/notifier.go index 8325f710c..8d6fdeb52 100644 --- a/modules/notification/base/notifier.go +++ b/modules/notification/base/notifier.go @@ -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) } diff --git a/modules/notification/base/null.go b/modules/notification/base/null.go index a74c47980..0d3489882 100644 --- a/modules/notification/base/null.go +++ b/modules/notification/base/null.go @@ -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) { + +} diff --git a/modules/notification/notification.go b/modules/notification/notification.go index d12024663..0fd6fa471 100644 --- a/modules/notification/notification.go +++ b/modules/notification/notification.go @@ -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) { diff --git a/modules/redis/redis_client/client.go b/modules/redis/redis_client/client.go new file mode 100644 index 000000000..437aecdae --- /dev/null +++ b/modules/redis/redis_client/client.go @@ -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 + +} diff --git a/modules/redis/redis_key/key_base.go b/modules/redis/redis_key/key_base.go new file mode 100644 index 000000000..0efc6ed38 --- /dev/null +++ b/modules/redis/redis_key/key_base.go @@ -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 +} diff --git a/modules/redis/redis_key/wechat_redis_key.go b/modules/redis/redis_key/wechat_redis_key.go new file mode 100644 index 000000000..1858576fd --- /dev/null +++ b/modules/redis/redis_key/wechat_redis_key.go @@ -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") +} diff --git a/modules/redis/redis_lock/lock.go b/modules/redis/redis_lock/lock.go new file mode 100644 index 000000000..0faed3237 --- /dev/null +++ b/modules/redis/redis_lock/lock.go @@ -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 +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index c6828f9f7..2a29dd700 100755 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -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") diff --git a/modules/structs/attachment.go b/modules/structs/attachment.go old mode 100644 new mode 100755 index 7becd9433..0f3c2ed6a --- a/modules/structs/attachment.go +++ b/modules/structs/attachment.go @@ -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 diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b3ede3b06..0787e6ce6 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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 webhooks guide. hooks.add_webhook = Add Default Webhook hooks.update_webhook = Update Default Webhook @@ -2677,6 +2702,13 @@ mirror_sync_create = synced new reference %[2]s to %[2]s at %[3]s from mirror approve_pull_request = `approved %s#%[2]s` reject_pull_request = `suggested changes for %s#%[2]s` +upload_dataset=`upload dataset %s` +task_gpudebugjob=`created CPU/GPU type debugging task%s` +task_npudebugjob=`created NPU type debugging task %s` +task_trainjob=`created training task%s` +task_inferencejob=`created reasoning task %s` +task_benchmark=`created profiling task %s` +task_createmodel=`created new model %s` [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 diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 86e88184c..2eeb72735 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -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 钩子是默认值, 将复制到所有新建项目中。参阅 Web钩子指南 获取更多内容。 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=从镜像同步了新的引用 %[2]s mirror_sync_delete=从镜像同步并从 %[3]s 删除了引用 %[2]s approve_pull_request=`同意了 %s#%[2]s` reject_pull_request=`建议变更 %s#%[2]s` +upload_dataset=`上传了数据集文件 %s` +task_gpudebugjob=`创建了CPU/GPU类型调试任务 %s` +task_npudebugjob=`创建了NPU类型调试任务 %s` +task_trainjob=`创建了训练任务 %s` +task_inferencejob=`创建了推理任务 %s` +task_benchmark=`创建了评测任务 %s` +task_createmodel=`导入了新模型 %s` [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=成员动态 diff --git a/package-lock.json b/package-lock.json index a8e5e3e25..f5a941869 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index ba5459a07..0f93faaea 100644 --- a/package.json +++ b/package.json @@ -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": [ diff --git a/public/home/home.js b/public/home/home.js index f49c8248b..78b9d517c 100644 --- a/public/home/home.js +++ b/public/home/home.js @@ -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 += " " + getIssueText(record) + "" @@ -96,11 +128,14 @@ socket.onmessage = function (e) { actionName = actionName.replace("{oldRepoName}",record.Content); html += recordPrefix + actionName; html += " " + getRepotext(record) + "" + } + 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 += " " + record.RefName + "" } else{ continue; } - if(record.Repo != null){ var time = getTime(record.CreatedUnix,currentTime); html += " " + time; @@ -108,19 +143,44 @@ socket.onmessage = function (e) { html += ""; html += ""; } - } 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 += "
"; - html += " \"\"" + var name = ""; + if(record.ActUser != null){ + name = record.ActUser.Name; + }else{ + console.log("act user is null."); + } + html += " \"\"" html += "
" - html += " " + record.ActUser.Name + "" + html += " " + name + "" 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 += "
"; + html += "
"; html += " "; html += "
"; html += "
"; @@ -408,4 +482,5 @@ function displayOrg(json){ } } orgDiv.innerHTML = html; + swiperOrg.updateSlides(); } diff --git a/public/img/qrcode_reload.png b/public/img/qrcode_reload.png new file mode 100644 index 000000000..81ff160b1 Binary files /dev/null and b/public/img/qrcode_reload.png differ diff --git a/routers/admin/cloudbrains.go b/routers/admin/cloudbrains.go new file mode 100644 index 000000000..d7a8f2220 --- /dev/null +++ b/routers/admin/cloudbrains.go @@ -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) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 72a68a6d3..ef5ee6699 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -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()) } diff --git a/routers/api/v1/repo/cloudbrain.go b/routers/api/v1/repo/cloudbrain.go index dd468783d..cea8d18f2 100755 --- a/routers/api/v1/repo/cloudbrain.go +++ b/routers/api/v1/repo/cloudbrain.go @@ -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 diff --git a/routers/api/v1/repo/modelarts.go b/routers/api/v1/repo/modelarts.go index c9f8761c9..27df10e16 100755 --- a/routers/api/v1/repo/modelarts.go +++ b/routers/api/v1/repo/modelarts.go @@ -77,6 +77,7 @@ func GetModelArtsNotebook2(ctx *context.APIContext) { ctx.JSON(http.StatusOK, map[string]interface{}{ "JobID": jobID, + "JobName": job.JobName, "JobStatus": result.Status, }) diff --git a/routers/api/v1/repo/repo_dashbord.go b/routers/api/v1/repo/repo_dashbord.go index 46596330f..959fb4c9f 100644 --- a/routers/api/v1/repo/repo_dashbord.go +++ b/routers/api/v1/repo/repo_dashbord.go @@ -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 diff --git a/routers/authentication/wechat.go b/routers/authentication/wechat.go new file mode 100644 index 000000000..72871afb3 --- /dev/null +++ b/routers/authentication/wechat.go @@ -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 +} diff --git a/routers/authentication/wechat_event.go b/routers/authentication/wechat_event.go new file mode 100644 index 000000000..9b1cebec6 --- /dev/null +++ b/routers/authentication/wechat_event.go @@ -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 +} diff --git a/routers/home.go b/routers/home.go index 397e1990d..2db8d2112 100755 --- a/routers/home.go +++ b/routers/home.go @@ -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) +} diff --git a/routers/repo/ai_model_manage.go b/routers/repo/ai_model_manage.go index fe4d9794c..e2040e0d2 100644 --- a/routers/repo/ai_model_manage.go +++ b/routers/repo/ai_model_manage.go @@ -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 } diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index c2d096416..bc843c555 100755 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -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 diff --git a/routers/repo/cloudbrain.go b/routers/repo/cloudbrain.go index 0599fb03f..4cde5d40d 100755 --- a/routers/repo/cloudbrain.go +++ b/routers/repo/cloudbrain.go @@ -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") + } } diff --git a/routers/repo/download.go b/routers/repo/download.go index 5b8982102..f1b6e09b7 100755 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -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 diff --git a/routers/repo/modelarts.go b/routers/repo/modelarts.go index 7e9ae3c09..fad449fec 100755 --- a/routers/repo/modelarts.go +++ b/routers/repo/modelarts.go @@ -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) diff --git a/routers/repo/repo_statistic.go b/routers/repo/repo_statistic.go index dce183f49..06f45e608 100755 --- a/routers/repo/repo_statistic.go +++ b/routers/repo/repo_statistic.go @@ -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) { diff --git a/routers/repo/user_data_analysis.go b/routers/repo/user_data_analysis.go index e71e8cb3a..b4adfc347 100755 --- a/routers/repo/user_data_analysis.go +++ b/routers/repo/user_data_analysis.go @@ -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) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index aa85b8f2b..7fa06fa3b 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -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()) diff --git a/routers/user/notification.go b/routers/user/notification.go index 9724c8108..0fd6df007 100644 --- a/routers/user/notification.go +++ b/routers/user/notification.go @@ -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 diff --git a/services/socketwrap/clientManager.go b/services/socketwrap/clientManager.go index 53de73673..98b0e0aa9 100644 --- a/services/socketwrap/clientManager.go +++ b/services/socketwrap/clientManager.go @@ -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 diff --git a/templates/admin/cloudbrain/list.tmpl b/templates/admin/cloudbrain/list.tmpl new file mode 100644 index 000000000..521377838 --- /dev/null +++ b/templates/admin/cloudbrain/list.tmpl @@ -0,0 +1,316 @@ +{{template "base/head" .}} + +
+
+
+
+
+
+
+
+
+ +
+
+ {{template "admin/navbar" .}} +
+ {{template "base/alert" .}} +
+
+ {{template "admin/cloudbrain/search" .}} + +
+ +
+ +
+
+
+ {{$.i18n.Tr "repo.cloudbrain_task"}} +
+
+ {{$.i18n.Tr "repo.cloudbrain_task_type"}} +
+
+ {{$.i18n.Tr "repo.modelarts.status"}} +
+
+ {{$.i18n.Tr "repo.modelarts.createtime"}} +
+
+ {{$.i18n.Tr "repo.cloudbrain_status_runtime"}} +
+
+ {{$.i18n.Tr "repo.modelarts.computing_resources"}} +
+
+ {{$.i18n.Tr "repo.cloudbrain_creator"}} +
+
+ {{$.i18n.Tr "repository"}} +
+
+ {{.i18n.Tr "admin.cloudbrain.cloudbrain_name"}} +
+
+ {{$.i18n.Tr "repo.cloudbrain_operate"}} +
+
+
+ {{range .Tasks}} + {{if .Repo}} +
+
+ +
+ {{if or (eq .JobType "DEBUG") (eq .JobType "SNN4IMAGENET") (eq .JobType "BRAINSCORE")}} + + {{.JobName}} + + {{else if eq .JobType "INFERENCE"}} + + {{.JobName}} + + {{else if eq .JobType "TRAIN"}} + + {{.JobName}} + + {{else if eq .JobType "BENCHMARK"}} + + {{.JobName}} + + {{end}} +
+ +
+ {{.JobType}} +
+ +
+ + {{.Status}} + +
+ +
+ {{TimeSinceUnix1 .Cloudbrain.CreatedUnix}} +
+ +
+ {{if .TrainJobDuration}}{{.TrainJobDuration}}{{else}}--{{end}} +
+ +
+ {{if .ComputeResource}}{{.ComputeResource}}{{else}}--{{end}} +
+ +
+ {{if .User.Name}} + + {{else}} + + {{end}} +
+ + + +
+ {{.JobName}} +
+
+ {{if eq .JobType "DEBUG" "SNN4IMAGENET" "BRAINSCORE"}} +
+
+ {{$.CsrfTokenHtml}} + {{if eq .Status "RUNNING" "WAITING" "CREATING" "STARTING"}} + + {{$.i18n.Tr "repo.debug"}} + + {{else}} + + {{$.i18n.Tr "repo.debug_again"}} + + {{end}} +
+
+ {{end}} + +
+ {{if eq .JobType "DEBUG" "BENCHMARK" "SNN4IMAGENET" "BRAINSCORE"}} +
+ {{$.CsrfTokenHtml}} + + {{$.i18n.Tr "repo.stop"}} + +
+ {{else}} + + {{$.i18n.Tr "repo.stop"}} + + {{end}} +
+ +
+ {{$.CsrfTokenHtml}} + + {{$.i18n.Tr "repo.delete"}} + +
+
+
+
+ {{else}} +
+
+ +
+ {{if eq .JobType "DEBUG"}} + + {{.JobName}} + + {{else if eq .JobType "INFERENCE"}} + + {{.JobName}} + + {{else if eq .JobType "TRAIN"}} + + {{.JobName}} + + {{else if eq .JobType "BENCHMARK"}} + + {{.JobName}} + + {{end}} +
+ +
+ {{.JobType}} +
+ +
+ + {{.Status}} + +
+ +
+ {{TimeSinceUnix1 .Cloudbrain.CreatedUnix}} +
+ +
+ {{if .TrainJobDuration}}{{.TrainJobDuration}}{{else}}--{{end}} +
+ +
+ {{if .ComputeResource}}{{.ComputeResource}}{{else}}--{{end}} +
+ +
+ {{if .User.Name}} + + {{else}} + + {{end}} +
+ +
+ -- +
+ +
+ {{.JobName}} +
+
+ {{if eq .JobType "DEBUG"}} +
+
+ {{$.CsrfTokenHtml}} + {{if eq .Status "RUNNING" "WAITING" "CREATING" "STARTING"}} + + {{$.i18n.Tr "repo.debug"}} + + {{else}} + + {{$.i18n.Tr "repo.debug_again"}} + + {{end}} +
+
+ {{end}} + + + +
+ {{$.CsrfTokenHtml}} + + {{$.i18n.Tr "repo.delete"}} + +
+
+
+
+ + {{end}} + {{end}} +
+
+ + +
+
+
+ +
+
+
+
+ +
+ +
+
+{{template "base/footer" .}} + diff --git a/templates/admin/cloudbrain/search.tmpl b/templates/admin/cloudbrain/search.tmpl new file mode 100644 index 000000000..3c24a52e1 --- /dev/null +++ b/templates/admin/cloudbrain/search.tmpl @@ -0,0 +1,51 @@ +
+
+
+ + +
+
+
+
+ + + +
\ No newline at end of file diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index 2b9f8b7c4..47a9ee811 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -14,6 +14,9 @@ {{.i18n.Tr "admin.datasets"}} + + 云脑任务 + {{.i18n.Tr "admin.hooks"}} diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index 42084b871..bcf46f9f0 100755 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -8,6 +8,8 @@ {{.i18n.Tr "custom.foot.council"}} {{.i18n.Tr "custom.foot.technical_committee"}} {{.i18n.Tr "custom.foot.join"}} + {{.i18n.Tr "custom.foot.agreement"}} +
{{template "base/footer" .}} - diff --git a/templates/repo/modelarts/inferencejob/new.tmpl b/templates/repo/modelarts/inferencejob/new.tmpl index 504a85abf..d84b4a45e 100644 --- a/templates/repo/modelarts/inferencejob/new.tmpl +++ b/templates/repo/modelarts/inferencejob/new.tmpl @@ -27,7 +27,7 @@ } -.nowrap { +.nowrapx { white-space: nowrap !important; } @@ -140,7 +140,7 @@
- {{range .engine_versions}} {{end}} diff --git a/templates/repo/modelarts/inferencejob/show.tmpl b/templates/repo/modelarts/inferencejob/show.tmpl index 691087f66..30f7d7a88 100644 --- a/templates/repo/modelarts/inferencejob/show.tmpl +++ b/templates/repo/modelarts/inferencejob/show.tmpl @@ -469,13 +469,13 @@ td, th {
{{template "base/footer" .}} \ No newline at end of file diff --git a/templates/repo/modelarts/notebook/show.tmpl b/templates/repo/modelarts/notebook/show.tmpl index 4818f870c..93c35cca4 100755 --- a/templates/repo/modelarts/notebook/show.tmpl +++ b/templates/repo/modelarts/notebook/show.tmpl @@ -11,7 +11,7 @@ {{.i18n.Tr "repo.cloudbrain"}}
/
- + {{$.i18n.Tr "repo.modelarts.notebook"}}
/
@@ -27,7 +27,7 @@ {{end}}
-

任务结果:

+

任务详情:

{{with .result}} @@ -35,6 +35,18 @@ + + + + + + + + + + + + diff --git a/templates/repo/modelarts/trainjob/index.tmpl b/templates/repo/modelarts/trainjob/index.tmpl index 3818df67a..c376e0e8a 100755 --- a/templates/repo/modelarts/trainjob/index.tmpl +++ b/templates/repo/modelarts/trainjob/index.tmpl @@ -113,7 +113,7 @@
- + {{.Status}}
@@ -143,11 +143,11 @@
{{$.CsrfTokenHtml}} {{if .CanDel}} - + {{$.i18n.Tr "repo.stop"}} {{else}} - + {{$.i18n.Tr "repo.stop"}} {{end}} @@ -157,7 +157,7 @@
{{$.CsrfTokenHtml}} {{if .CanDel}} - + {{$.i18n.Tr "repo.delete"}} {{else}} @@ -204,162 +204,5 @@
- -{{template "base/footer" .}} - - \ No newline at end of file +{{template "base/footer" .}} \ No newline at end of file diff --git a/templates/repo/modelarts/trainjob/show.tmpl b/templates/repo/modelarts/trainjob/show.tmpl index 81d36c1e9..667b98a52 100755 --- a/templates/repo/modelarts/trainjob/show.tmpl +++ b/templates/repo/modelarts/trainjob/show.tmpl @@ -518,6 +518,7 @@ td, th { + {{template "base/footer" .}} \ No newline at end of file + \ No newline at end of file diff --git a/templates/repo/wx_autorize.tmpl b/templates/repo/wx_autorize.tmpl new file mode 100644 index 000000000..52b9f29bc --- /dev/null +++ b/templates/repo/wx_autorize.tmpl @@ -0,0 +1,15 @@ + +{{template "base/head" .}} +
+
+ {{template "repo/header" .}} + {{template "base/alert" .}} + +
+
+
+ + + +{{template "base/footer" .}} + diff --git a/templates/terms.tmpl b/templates/terms.tmpl new file mode 100644 index 000000000..3c02a119c --- /dev/null +++ b/templates/terms.tmpl @@ -0,0 +1,70 @@ +{{template "base/head_home" .}} +
+

OpenI启智社区AI协作平台使用协议

+
+

+ OpenI启智社区AI协作平台作为新一代人工智能领域开源开放开发协作平台,不仅为用户提供代码托管与数据集管理等服务,同时提供开发者所需的计算算力资源,一个良好的开发环境对用户、组织和项目都尤为重要。 +

+

+ 为了保障用户权益,维护平台正常秩序,实现平台规范化运营,给开发者提供一个良好的开发环境,您不得在本平台进行恶意攻击、挖矿等任何违法或扰乱平台秩序的行为。一经发现,平台有权变更、暂停或终止您对平台服务的使用而无须事先声明,包括但不限于限制您使用平台服务的次数与资源、账号永久封闭等。 +

+

+ AI协作平台使用协议及违规处罚 +

+

+ 1. 您需保证对平台服务的使用不违反国家各项法律法规的规定,且不侵害任何第三方权益,如因此造成任何后果及损失,由您自行承担全部责任。 +

+

+ 2. 禁止行为 +

+

+ 您充分理解并同意,您在使用平台服务时,应当遵守所有中华人民共和国的法律、法规、规章制度、规范、政策、行政命令、强制标准及行业标准等(统称为“法律法规”)。除非法律法规允许且启智社区事先书面许可,您不得从事以下活动,也不得同意、授权或指示任何第三人从事包括但不限于以下内容的活动: +
+ (1) 对平台服务进行挖矿、逆向工程、反编辑等恶意行为损坏平台服务相关内容与数据,或不正当手段获取原始数据和其他数据等;
+ (2) 对平台服务或者平台服务运行过程中释放出的任何数据或其他数据及平台服务运行过程中的交互数据进行复制、更改、修改等操作,包括但不限于使用插件、外挂或非经授权的第三方工具或服务接入平台服务和相关系统; +
+ (3) 宣扬或提供关于非法行为的说明信息、宣扬针对任何团体或个人的人身伤害或传播任何病毒、蠕虫、缺陷、特洛伊木马或其他具有破坏性的内容等;
+ (4) 删除本平台服务中包含的任何版权声明、商标声明或其他所有权声明;包括但不限于任何有损本平台一切相关知识产权的行为;
+ (5) 创造任何网站或应用程序以重现或复制平台服务或本平台。 +

+

+ 3. 遵守法律规范 +

+

+ 您不得利用平台服务上传、上载、发布、发表、传播任何违法内容,包括但不限于您制作、复制、发布、传播下列信息: +

+

+ (1)煽动抗拒、破坏宪法和法律、行政法规实施
+ (2)煽动颠覆国家政权,推翻社会主义制度
+ (3)煽动分裂国家、破坏国家统一
+ (4)煽动民族仇恨、民族歧视、破坏民族团结
+ (5)捏造或者歪曲事实、散布谣言,扰乱社会秩序
+ (6)宣扬封建迷信、淫秽、色情、赌博、暴力、教唆犯罪
+ (7)公然侮辱他人或者捏造事实诽谤他人
+ (8)损害国家机关信誉或有损国家领导人荣誉
+ (9)其他违反宪法和法律、行政法规
+ (10)含有虚假、有害、胁迫、侵害他人隐私、骚扰、侵害、中伤、粗俗、猥亵或其他道德上令人反感的内容
+ (11)含有中国法律、法规、规章、条例以及任何具有法律效力之规范所限制或禁止的其他内容 +

+

+ 一经发现,平台将按照国家有关规定,及时删除网站中含有上述内容的地址、目录,并保留原始记录,在二十四小时之内向公安机关报告。 +

+

+ 4. 保障网络信息安全 +

+

+ 您不得利用平台服务从事以下危害计算机网络信息安全的活动:
+ (1) 未经允许,进入计算机信息网络或者使用计算机信息网络资源的;
+ (2) 未经允许,对计算机信息网络功能进行删除、修改或者增加的;
+ (3) 未经允许,对进入计算机信息网络中存储、处理或者传输的数据和应用程序进行删除、修改或者增加的;
+ (4) 故意制作、传播计算机病毒等破坏性程序的;
+ (5) 其他危害计算机信息网络安全的行为。 +

+

+ 5. 您充分理解并同意,您对自己使用平台服务的一切行为及由此产生的一切结果负责,包括但不限于您所发表的任何内容、提供的任何服务以及由此产生的任何后果。您应对平台服务的内容应自行判断并决定是否使用,并承担因使用平台服务及其相关内容而引起的所有风险,包括因对平台服务及其内容的真实性、完整性、准确性、及时性及实用性的依赖而产生的风险。平台不对此提供任何担保和保证,不对因前述风险而导致的任何后果或损失对您承担责任。 +

+

+ 如果您违反了法律法规或本协议,OpenI启智社区有权依据合理判断对违反法律法规或本协议的行为作出处理,并保留对该违反行为采取法律所能提供的所有补救手段的权利。OpenI启智社区有权对违反法律法规及本协议的任何用户调查并采取适当的法律行动,包括但不限于民事诉讼等。OpenI启智社区有权根据违法违规行为的严重程度,将上述违法违规行为的线索和您的个人信息报告司法机关或其他执法机关,并配合司法机关或其他执法机关进行的调查、听证、起诉等。您应当自行承担由此产生的任何法律责任。 +

+
+{{template "base/footer" .}} \ No newline at end of file diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index f3794989c..519dcda8f 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -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}}

{{if or (eq .GetOpType 5) (eq .GetOpType 18)}} @@ -101,7 +115,23 @@
- {{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32}} + {{if eq .GetOpType 24}} + + {{else if eq .GetOpType 25}} + + {{else if eq .GetOpType 26}} + + {{else if eq .GetOpType 27}} + + {{else if eq .GetOpType 28}} + + {{else if eq .GetOpType 29}} + + {{else if eq .GetOpType 30}} + + {{else}} + {{svg (printf "octicon-%s" (ActionIcon .GetOpType)) 32}} + {{end}}
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 85d1fb65c..eb0d76e5b 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -208,6 +208,3 @@ {{template "base/footer" .}} - \ No newline at end of file diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index 1c0ee84fc..4038ce5b0 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -117,6 +117,3 @@ {{template "base/footer" .}} - \ No newline at end of file diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index d42ed4058..fa5e0c9b7 100755 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -141,6 +141,7 @@ {{if eq .TabName "activity"}} {{if .EnableHeatmap}} +
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 54c6695ec..97918aedd 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -1,6 +1,7 @@ {{template "base/head" .}}
{{template "user/settings/navbar" .}} +
{{template "base/alert" .}}

@@ -102,6 +103,71 @@

+

+ {{$.i18n.Tr "settings.wechat_bind"}} +

+ {{if not .SignedUser.IsBindWechat}} + + {{else}} +
+
状态 {{.Status}}
描述 {{$.task.Description}}
镜像名称 {{$.task.Image}}
数据集下载地址 {{$.datasetDownloadLink}}
开始时间 {{.CreateTime}}
+ + + + + + + + + +
+ {{$.i18n.Tr "settings.bind_account_information"}} + + {{$.i18n.Tr "settings.bind_time"}} + + {{$.i18n.Tr "repo.cloudbrain_operate"}} +
+ {{$.i18n.Tr "settings.wechat"}} + + {{TimeSinceUnix1 .SignedUser.WechatBindUnix}} + + +
+
+ {{end}} +
+
{{template "base/footer" .}} + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..e69de29bb diff --git a/web_src/js/components/EditAboutInfo.vue b/web_src/js/components/EditAboutInfo.vue index 425cf4bcc..78cdb70a6 100644 --- a/web_src/js/components/EditAboutInfo.vue +++ b/web_src/js/components/EditAboutInfo.vue @@ -1,7 +1,7 @@