你确认删除该任务么?此任务一旦删除不可恢复。
+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"
+)
+
+//%[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 += "